Fix xHCI heap corruption: allocate the Output Device Context full size at Address Device, never swap it (found at O-C)
xhci_cmd_address_device() sized the Output Device Context for Slot + EP0 (64 bytes); xhci_cmd_configure_endpoint() then allocated a bigger copy and repointed DCBAA[slot_id]. The controller keeps the address it was given at Address Device (QEMU caches it), so every bulk Endpoint Context was written past the 64-byte block into the next kmalloc header -- on every USB mass-storage attach, every ISA. After O-C's smoke campaign reshuffled the heap, a kfree at thumbdrive unplug walked into it: #GP in coalesce_neighbors(). Found with temporary instrumentation (load base, code bytes at RIP, event crumbs, a heap-list checker): the corrupt word was an EP Context (Bulk IN, Running, MPS 1024) exactly DCI 3 x 32 bytes into the 64-byte context. Now: XHCI_DEVICE_CTX_BYTES (32 contexts x 32 bytes) allocated once per slot at Address Device, zeroed whole, never reallocated; Configure Endpoint just fills it. Also ends the per-attach leak of the old context. Verified: heap checker clean from boot; campaign -> O-A -> MINT -> unplug -> re-attach passes with 0 exceptions; 3-ISA acceptance passes, parity and dict_hash set unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ee4c401d1c
commit
df150e8626
+22
-23
@@ -489,9 +489,18 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
sizeof(xhci_ep_ctx32_t), 64);
|
||||
if (!ms->input_ctx) return -1;
|
||||
}
|
||||
/* FABRIC-3.9.8 (found at Stage O-C, 2026-09-25): the Output Device
|
||||
* Context is allocated FULL SIZE here, once -- Slot Context plus all 31
|
||||
* Endpoint Contexts (XHCI_DEVICE_CTX_BYTES). It used to be sized for
|
||||
* Slot + EP0 only (64 bytes) and swapped for a bigger one in
|
||||
* xhci_cmd_configure_endpoint(), repointing DCBAA[slot_id]. But the
|
||||
* controller takes the Output Device Context's address at Address
|
||||
* Device and keeps writing there (QEMU caches it; it does not re-read
|
||||
* DCBAA), so the bulk endpoints' contexts landed past the end of the
|
||||
* 64-byte block -- corrupting the next kmalloc header on every mass
|
||||
* storage attach, until a later kfree walked into it and #GP'd. */
|
||||
if (!ms->device_ctx) {
|
||||
ms->device_ctx = kmalloc_aligned(
|
||||
sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t), 64);
|
||||
ms->device_ctx = kmalloc_aligned(XHCI_DEVICE_CTX_BYTES, 64);
|
||||
if (!ms->device_ctx) return -1;
|
||||
}
|
||||
if (!ms->ep0_ring) {
|
||||
@@ -522,7 +531,7 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
* controller writes this on Address Device success, software must not
|
||||
* pre-fill it. */
|
||||
uint8_t *dctx = (uint8_t *)ms->device_ctx;
|
||||
for (size_t i = 0; i < sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t); i++) dctx[i] = 0;
|
||||
for (size_t i = 0; i < XHCI_DEVICE_CTX_BYTES; i++) dctx[i] = 0;
|
||||
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)ms->device_ctx;
|
||||
|
||||
/* Input Context: Input Control Context, then Slot Context, then EP0
|
||||
@@ -602,26 +611,16 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
uint32_t out_dci = XHCI_EP_ADDR_TO_DCI(ms->bulk_out_ep_addr);
|
||||
uint32_t max_dci = (in_dci > out_dci) ? in_dci : out_dci;
|
||||
|
||||
/* DCBAA[slot_id]'s Device Context also needs to grow to hold the new
|
||||
* EP Contexts -- it's currently sized for Slot+EP0 only (from Address
|
||||
* Device). Unlike the Input Context below, this one's *existing*
|
||||
* content must be preserved, not zeroed: xHCI 1.2 spec section 4.6.6
|
||||
* only has the controller write the DCIs actually named in this
|
||||
* command's Add/Drop flags (EP0's entry here is neither), so it
|
||||
* expects to find EP0's live output state (its current TR Dequeue
|
||||
* Pointer in particular) still intact in the Device Context it reads
|
||||
* -- swapping in a freshly zeroed buffer would hand the controller a
|
||||
* blank EP0 out from under an endpoint it isn't being asked to touch. */
|
||||
size_t old_device_ctx_bytes = sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t);
|
||||
size_t new_device_ctx_bytes = (1 + (size_t)max_dci) * sizeof(xhci_ep_ctx32_t);
|
||||
void *new_device_ctx = kmalloc_aligned(new_device_ctx_bytes, 64);
|
||||
if (!new_device_ctx) return -1;
|
||||
uint8_t *ndctx = (uint8_t *)new_device_ctx;
|
||||
for (size_t i = 0; i < new_device_ctx_bytes; i++) ndctx[i] = 0;
|
||||
const uint8_t *odctx = (const uint8_t *)ms->device_ctx;
|
||||
for (size_t i = 0; i < old_device_ctx_bytes; i++) ndctx[i] = odctx[i];
|
||||
ms->device_ctx = new_device_ctx;
|
||||
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)ms->device_ctx;
|
||||
/* The Output Device Context is NOT grown or swapped here (FABRIC-3.9.8,
|
||||
* found at Stage O-C): xhci_cmd_address_device() allocated it full size
|
||||
* (XHCI_DEVICE_CTX_BYTES, every DCI) and pointed DCBAA[slot_id] at it,
|
||||
* and the controller keeps using that address. This used to allocate a
|
||||
* bigger copy and repoint DCBAA -- the controller never saw the swap,
|
||||
* wrote the new bulk EP Contexts past the end of the original 64-byte
|
||||
* block, and corrupted the heap. Every DCI up to max_dci already has
|
||||
* room; EP0's live output state is left exactly where the controller
|
||||
* put it. */
|
||||
if (max_dci >= XHCI_DEVICE_CTX_ENTRIES) return -1;
|
||||
|
||||
/* ms->input_ctx is reused from Address Device -- same allocation
|
||||
* (96 bytes: Input Control Ctx + Slot Ctx + one EP Ctx) is too small
|
||||
|
||||
Reference in New Issue
Block a user