DF-5686 / udev_getdevs_leak.c
/* * DF-5686 — udev_getdevs_ioctl() leaks the ctx.cdevs prop_array (and pins * every cdev's si_dict) on every UDEVPROP "getdevs" ioctl. * sys/kern/kern_udev.c:985 ctx.cdevs = prop_array_create(); * sys/kern/kern_udev.c:1004 prop_dictionary_set(odict, "array", ctx.cdevs); // retains * sys/kern/kern_udev.c:1013 prop_object_release(odict); // -1 * -> ctx.cdevs keeps its own +1 reference forever (prop_dictionary_set does * NOT transfer ownership). * * /dev/udev is 0600 root:wheel (kern_udev.c:1039-1041), so this is driven * as root — but devd(8) performs the same ioctl on default systems. * * Build: cc -O -o udev_getdevs_leak udev_getdevs_leak.c * Run: ./udev_getdevs_leak /dev/udev <iterations> * Success criterion: vmstat -m "prop array" InUse grows linearly with the * iteration count (one leaked array of N cdev-pointers per call). */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> struct plistref { void *pref_plist; size_t pref_len; }; #ifndef UDEVPROP #define UDEVPROP _IOWR('U', 0xBA, struct plistref) #endif static const char xml[] = "<plist><dict><key>command</key><string>getdevs</string></dict></plist>"; int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/udev"; long iters = argc > 2 ? atol(argv[2]) : 500; int fd, i; struct plistref pref; char *buf; fd = open(dev, O_RDWR); if (fd < 0) { perror(dev); return 1; } buf = strdup(xml); for (i = 0; i < iters; i++) { pref.pref_plist = buf; pref.pref_len = strlen(buf) + 1; if (ioctl(fd, UDEVPROP, &pref) != 0) { perror("ioctl(UDEVPROP)"); return 1; } /* kernel copyout mmap'ed a region into us; release it */ if (munmap(pref.pref_plist, pref.pref_len) != 0) perror("munmap"); } printf("did %d UDEVPROP getdevs ioctls on %s\n", i, dev); close(fd); return 0; } |