// File: xhci_isoc_sniffer_oob_repro.c
//
// Standalone harness for:
//   VirtualBox xHCI Isochronous URB Length/Offset Mismatch -> USB Sniffer OOB Read
//
// The real device path is:
//   DevXHCI.cpp:xhciR3QueueIsochTD()
//       allocates pUrb->pbData from cbUrbMax = cIsoPackets * cbPktMax
//       records aIsocPkts[i].off = offCur
//       advances offCur by the full guest TD length
//       later writes pUrb->cbData = offCur
//
// If cbPktMax is small but guest TD lengths are large, pUrb->cbData and later
// packet offsets can exceed the actual pUrb->pbData allocation. The root-hub
// USB sniffer records submit events before device dispatch and copies
// pUrb->cbData bytes from pUrb->pbData for OUT transfers.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(__GNUC__) || defined(__clang__)
# define NOINLINE __attribute__((noinline))
#else
# define NOINLINE
#endif

#define VUSBDIRECTION_OUT 1
#define VUSBXFERTYPE_ISOC 2
#define VUSBSNIFFEREVENT_SUBMIT 1

typedef struct VUSBURBISOCPKT {
    uint32_t cb;
    uint32_t off;
    int32_t  enmStatus;
} VUSBURBISOCPKT;

typedef struct VUSBURB {
    uint8_t         enmType;
    uint8_t         enmDir;
    uint8_t         cIsocPkts;
    uint32_t        cbData;
    uint32_t        cbDataAllocated;
    uint8_t        *pbData;
    VUSBURBISOCPKT  aIsocPkts[8];
} VUSBURB;

static volatile uint32_t g_uSink;

static uint32_t rt_min_u32(uint32_t a, uint32_t b)
{
    return a < b ? a : b;
}

NOINLINE
#line 3538 "src/VBox/Devices/USB/DevXHCI.cpp"
static VUSBURB *xhci_build_malformed_isoc_urb(void)
{
    const uint8_t  cIsoPackets = 8;
    const uint32_t cbPktMax = 1;       /* Guest endpoint context: tiny ESIT packet. */
    const uint32_t cbGuestTd = 1024;   /* Guest transfer TRB length per TD. */
    const uint32_t cbUrbMax = cIsoPackets * cbPktMax;

    VUSBURB *pUrb = (VUSBURB *)calloc(1, sizeof(*pUrb));
    if (!pUrb)
        return NULL;

    pUrb->enmType = VUSBXFERTYPE_ISOC;
    pUrb->enmDir = VUSBDIRECTION_OUT;
    pUrb->cIsocPkts = cIsoPackets;
    pUrb->cbData = cbUrbMax;
    pUrb->cbDataAllocated = cbUrbMax;
    pUrb->pbData = (uint8_t *)malloc(cbUrbMax);
    if (!pUrb->pbData) {
        free(pUrb);
        return NULL;
    }
    memset(pUrb->pbData, 'G', cbUrbMax);

    uint32_t offCur = 0;
    for (uint32_t i = 0; i < cIsoPackets; ++i) {
#line 3610 "src/VBox/Devices/USB/DevXHCI.cpp"
        pUrb->aIsocPkts[i].cb = rt_min_u32(cbGuestTd, cbPktMax);
        pUrb->aIsocPkts[i].off = offCur;
        pUrb->aIsocPkts[i].enmStatus = 0;
#line 3627 "src/VBox/Devices/USB/DevXHCI.cpp"
        offCur += cbGuestTd;
    }

#line 3644 "src/VBox/Devices/USB/DevXHCI.cpp"
    pUrb->cbData = offCur;
    return pUrb;
}

NOINLINE
#line 336 "src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp"
static void vusbSnifferBlockAddData_reduced(const void *pvData, uint32_t cbData)
{
    uint8_t *copy = (uint8_t *)malloc(cbData);
    if (!copy)
        abort();
    memcpy(copy, pvData, cbData);
    for (uint32_t i = 0; i < cbData; ++i)
        g_uSink += copy[i];
    printf("sniffer copied %u bytes from URB data\n", cbData);
    free(copy);
}

NOINLINE
#line 568 "src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp"
static void vusbSnifferFmtPcapNgRecordEvent_reduced(VUSBURB *pUrb, int enmEvent)
{
    uint32_t cbUrbLength = 0;
    if (enmEvent == VUSBSNIFFEREVENT_SUBMIT)
#line 593 "src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp"
        cbUrbLength = pUrb->cbData;

    uint32_t cbDataLength = cbUrbLength;
    uint8_t *pbData = &pUrb->pbData[0];

    if (pUrb->enmDir == VUSBDIRECTION_OUT) {
        /* OUT submit keeps cbDataLength non-zero in the real sniffer path. */
    }

    if (cbDataLength)
#line 710 "src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp"
        vusbSnifferBlockAddData_reduced(pbData, cbDataLength);
}

int main(void)
{
    VUSBURB *pUrb = xhci_build_malformed_isoc_urb();
    if (!pUrb)
        return 1;

    printf("URB allocation=%u advertised_cbData=%u pkt0={off=%u cb=%u} pkt7={off=%u cb=%u}\n",
           pUrb->cbDataAllocated, pUrb->cbData,
           pUrb->aIsocPkts[0].off, pUrb->aIsocPkts[0].cb,
           pUrb->aIsocPkts[7].off, pUrb->aIsocPkts[7].cb);

    vusbSnifferFmtPcapNgRecordEvent_reduced(pUrb, VUSBSNIFFEREVENT_SUBMIT);

    free(pUrb->pbData);
    free(pUrb);
    return 0;
}
