DF-0856 / trigger.c
/* * DF-0856 โ dirfs_alloc_file openat error path leaks dirfs node + parent refcount * * Source-level analysis / PoC harness. * * dirfs is a vkernel64-only filesystem (sys/platform/vkernel64/conf/files * lists the three dirfs .c files as `optional dirfs`, and the default * sys/config/VKERNEL64 does NOT enable dirfs). dirfs is therefore not * compiled into the running guest kernel (X86_64_GENERIC) at all โ * `nm /boot/kernel/kernel.debug | grep -i dirfs` returns 0 hits โ and is * not even in the default VKERNEL64 build. * * The bug is a pure resource leak in an error path, fully visible in the * source at sys/vfs/dirfs/dirfs_subr.c:193-200. Reproduction for this * finding is therefore code-level (no escalation, no memory corruption โ * it is CWE-401 / Low). * * --- THE BUG (sys/vfs/dirfs/dirfs_subr.c, dirfs_alloc_file) ----------- * * 182: dnp = dirfs_node_alloc(mp); // kmalloc(sizeof(*dnp)) * ... * 186: dirfs_node_setname(dnp, ncp->nc_name, ...); // kmalloc name * 187: dnp->dn_parent = pdnp; * 188: dirfs_node_ref(pdnp); // ++parent refcount * ... * 193: if (openflags && vap != NULL) { * 194: dnp->dn_fd = openat(pathnp->dn_fd, tmp, * 195: openflags, vap->va_mode); * 196: if (dnp->dn_fd == -1) { * 197: dirfs_dropfd(dmp, pathnp, pathfree); // frees path only * 198: return errno; // LEAKS dnp + parent ref * 199: } * 200: } * * Compare the stat error path at lines 203-209 which does it correctly: * * 202: error = dirfs_node_stat(pathnp->dn_fd, tmp, dnp); * 203: if (error) { * 204: error = errno; * 205: if (vp) * 206: dirfs_free_vp(dmp, dnp); * 207: dirfs_node_free(dmp, dnp); // <-- frees dnp AND * 208: dirfs_dropfd(dmp, pathnp, pathfree); // drops parent ref * 209: return error; * 210: } * * dirfs_node_free() (line 106) drops the parent reference via * dirfs_node_drop(dmp, dnp->dn_parent) at lines 123-126, then kfrees * dn_name and the node itself. So the openat error path MUST call it too. * * --- IMPACT ---------------------------------------------------------- * * Each failed openat (e.g. O_CREAT in a read-only or quota-exceeded * directory, or any openat that returns -1) leaks: * - one struct dirfs_node (~sizeof dirfs_node, includes a lock and * pointers, ~200+ bytes) and * - one kmalloc'd dn_name (ncp->nc_nlen+1 bytes) * - and permanently inflates the parent's refcount (dirfs_node_ref on * line 188 is never balanced by dirfs_node_drop on the error path), * which prevents the parent from ever being freed and can leave the * mount un-unmountable. * * Sustained triggering yields kernel heap exhaustion (denial of service) * inside the vkernel. Because dirfs runs as a host userland process * (the vkernel), the leak is heap growth in the vkernel process, not in * the host kernel โ but it still denies service to the vkernel's * filesystem layer and can wedge unmount. * * --- NO ESCALATION --------------------------------------------------- * * This is a pure resource leak (CWE-401). No memory corruption, no * primitive for uid=0. Realistic impact: DoS of the vkernel via heap * exhaustion / unmount failure; no host-kernel impact because dirfs is * vkernel-only. */ int main(void) { return 0; } |