DF-0002 / df0002_trigger.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | /* * DF-0002 trigger module. * * sys_fhopen() at sys/kern/vfs_syscalls.c:4933-4938 has a missing * `error = EINVAL` before `goto bad_drop` when a VREG vnode has v_object * == NULL after VOP_OPEN. The function returns 0 (success) while the * reserved fd slot is freed and sysmsg_result is never written, so the * caller sees "fd 0" (its stdin). * * No in-tree filesystem produces a VREG vnode with v_object == NULL on a * successful VOP_OPEN (UFS/HAMMER2/tmpfs/cd9660/msdosfs/etc. all call * vinitvmio() up front). The bug is therefore unreachable on default * filesystems; it requires an out-of-tree/broken FS that violates the * invariant. To demonstrate the bug we artificially recreate that * invariant violation. * * This KLD exposes a sysctl that takes an open fd for a tmpfs regular file. * It looks up the vnode, saves and NULLs v_object, and prints a marker. * The caller then issues fhopen(2) on a handle obtained earlier via * getfh(2); because tmpfs_open() (unlike ufs_open) does NOT re-create * v_object, sys_fhopen's VOP_OPEN succeeds but vp->v_object stays NULL and * the buggy branch at vfs_syscalls.c:4933 fires. * * Root-only by definition (kldload). The bug itself is also gated by * SYSCAP_RESTRICTEDROOT, so this is not a privilege escalation -- it is a * logic/correctness demonstration. There is no security impact. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/sysctl.h> #include <sys/proc.h> #include <sys/filedesc.h> #include <sys/file.h> #include <sys/vnode.h> #include <sys/mount.h> #include <vm/vm_object.h> static struct vnode *corrupted_vp = NULL; static vm_object_t saved_vobj = NULL; static int df0002_corrupt_fd(SYSCTL_HANDLER_ARGS) { int fd = -1; int error; struct file *fp = NULL; struct vnode *vp; error = sysctl_handle_int(oidp, &fd, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Restore any previously corrupted vnode before corrupting a new one. */ if (corrupted_vp != NULL) { struct vnode *ovp = corrupted_vp; corrupted_vp = NULL; vn_lock(ovp, LK_EXCLUSIVE | LK_RETRY); ovp->v_object = saved_vobj; saved_vobj = NULL; vput(ovp); } if (fd < 0) return (0); error = holdvnode(curthread, fd, &fp); if (error) { kprintf("DF-0002: holdvnode(%d) failed: %d\n", fd, error); return (error); } vp = (struct vnode *)fp->f_data; if (vp == NULL || vp->v_type != VREG) { kprintf("DF-0002: fd %d is not a regular file (type=%d)\n", fd, vp ? vp->v_type : -1); fdrop(fp); return (EINVAL); } /* Hold an extra reference on the vnode so it survives the fdrop(). */ vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); saved_vobj = vp->v_object; vp->v_object = NULL; vget(vp, LK_EXCLUSIVE | LK_RETRY); /* take extra ref for corrupted_vp */ vput(vp); /* release the lock from vn_lock */ corrupted_vp = vp; fdrop(fp); kprintf("DF-0002: corrupted vnode %p (cleared v_object, was %p, type=VREG)\n", vp, saved_vobj); return (0); } SYSCTL_NODE(_debug, OID_AUTO, df0002, CTLFLAG_RW, 0, "DF-0002 trigger"); SYSCTL_PROC(_debug_df0002, OID_AUTO, corrupt_fd, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df0002_corrupt_fd, "I", "Open fd whose vnode's v_object should be nulled to trigger the bug"); static int df0002_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("DF-0002: trigger module loaded. sysctl: df0002.corrupt_fd\n"); return (0); case MOD_UNLOAD: if (corrupted_vp != NULL) { struct vnode *ovp = corrupted_vp; corrupted_vp = NULL; vn_lock(ovp, LK_EXCLUSIVE | LK_RETRY); ovp->v_object = saved_vobj; saved_vobj = NULL; vput(ovp); } kprintf("DF-0002: trigger module unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t df0002_moddata = { "df0002_trigger", df0002_modevent, NULL }; DECLARE_MODULE(df0002_trigger, df0002_moddata, SI_SUB_PSEUDO, SI_ORDER_ANY); MODULE_VERSION(df0002_trigger, 1); |