oce_mq_create leaks the CQ (struct + 4KiB DMA ring) on two of its three error paths
Summary
mq_create creates its completion queue first but assigns mq->cq only on success; on ring-create or CREATE_MQ_EXT mailbox failure, oce_mq_free skips cq cleanup (mq->cq still NULL), leaking the cq struct plus its 256x16B DMA ring and leaving sc->cq[ncqs-1] dangling at it. Only the kmalloc-failure path deletes it. Hardware-gated one-time leak per failed attach. Assign-mq->cq-immediately fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4420 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 2.9 KB | β raw | |
| fix.diff | β | 622 B | view raw | |
| env.txt | β | 177 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-4420 β oce_mq_create leaks the CQ (ring DMA + struct) on two error paths
File: sys/dev/netif/oce/oce_queue.c (pass 2)
Severity: Info (correctness/resource leak; no unprivileged reachability)
What happens
oce_mq_create() (sys/dev/netif/oce/oce_queue.c:663) creates its completion
queue FIRST (line 675) and only assigns mq->cq = cq on the SUCCESS path
(line 744). On two of the three failure paths the cq created at the top is
never destroyed:
-
mq->ring = oce_create_ring_buffer(...)fails ->goto error(oce_queue.c:689-691) ->oce_mq_free(mq): -mq->ring == NULL-> ring block skipped (oce_queue.c:782) -mq->cq == NULL(still M_ZERO; never assigned on this path) -> CQ block skipped (oce_queue.c:796) - result:cq(kmalloc'd struct + oce_create_ring_buffer DMA ring created insideoce_cq_create, oce_queue.c:892-896) leaks, andsc->cq[sc->ncqs-1](assigned in oce_cq_create:910) keeps pointing at it. -
oce_mbox_post()/ firmware status fails ->goto error(oce_queue.c:735-741) -> sameoce_mq_free(mq)path: - the mq ring IS destroyed, butmq->cqis still NULL, so the CQ again leaks.
Only the third failure path (kmalloc of mq itself, oce_queue.c:681-685)
correctly calls oce_cq_del(sc, cq) before jumping to error.
Consequences
- One-time leak of
sizeof(struct oce_cq)+ a CQ DMA ring (CQ_LEN_256 x sizeof(struct oce_mq_cqe) = 256 x 16 = 4 KiB DMA memory plus bus_dma tag/map) per failedoce_mq_createinvocation. sc->ncqsstays incremented with a live pointer to the leaked CQ; nothing in the tree iteratessc->cq[]afteroce_queue_release_all(checked: thefor_all_cq_queuesconsumer macro has no callers in the teardown path), so this is a leak, not a UAF.- Trigger: requires the Emulex OneConnect firmware to fail CREATE_MQ_EXT (or a DMA ring alloc failure) during driver attach. No unprivileged-user reachability; no network reachability (mailbox command, attach-time only).
Why PoC was not run
- The audit QEMU guest has no Emulex BE2/BE3/Lancer hardware, so
oce(4)never attaches (pciconf -lshows no Emulex vendor 0x19a2 device) β the failure path cannot be entered on the guest. - Severity is Info (error-path resource leak, no security impact), which the audit contract exempts from mandatory PoC verification.
- Root cause is established by direct code reading with line-accurate accounting of every goto path (see VERDICT.md).
Fix
One-line move: assign mq->cq = cq immediately after the CQ is created so
that oce_mq_free()'s existing if (mq->cq != NULL) oce_cq_del(...)
(oce_queue.c:796-799) handles every error path. See fix.diff.
DF-4420 VERDICT
Status: untested (Info-severity leak; PoC exempt per contract β no oce hardware on the verification guest). Confidence in the bug itself: certain (every goto path traced below).
Path-by-path trace of oce_mq_create (sys/dev/netif/oce/oce_queue.c:662-762)
cq = oce_cq_create(sc, eq, CQ_LEN_256, sizeof(struct oce_mq_cqe), 1, 1, 0, 0);
(oce_queue.c:675-678)
-> on success cq owns a DMA ring and is registered in sc->cq[sc->ncqs++]
(oce_queue.c:910 inside oce_cq_create).
| failure after that point | goto path | what oce_mq_free(mq) does | cq freed? |
|---|---|---|---|
kmalloc(sizeof(*mq)) NULL (681-685) |
oce_cq_del(sc, cq); goto error; (683) |
mq_free(NULL) returns (779-780) | YES |
oce_create_ring_buffer NULL (689-691) |
goto error (691) -> oce_mq_free(mq) (759) |
ring NULL -> skip (782); mq->cq == NULL -> skip (796); kfree(mq) | NO β leak |
oce_mbox_post/fw status rc (735-741) |
goto error (741) -> oce_mq_free(mq) (759) |
ring destroyed (782-794); mq->cq == NULL -> skip (796); kfree(mq) | NO β leak |
mq->cq = cq is only executed on the success path (oce_queue.c:744), which
is why the CQ block in oce_mq_free never fires on the two leaking paths.
Downstream: sc->mq = oce_mq_create(...) in oce_queue_init_all
(oce_queue.c:150) receives NULL, goto error -> oce_queue_release_all β
sc->mq is NULL so mq_free is skipped there too (oce_queue.c:189-190). The
leaked cq is never referenced again: the only iterators of sc->cq[] are
for_all_cq_queues (no callers β verified by grep across sys/) and
oce_hw_update_multicast-style paths that do not run on this failure path.
No UAF; pure leak.
Not-testable-on-guest justification
- Guest
uname: DragonFly dfbsd 6.5-DEVELOPMENT #0 ... X86_64_GENERIC pciconf -lv/ dmesg on the guest: no vendor 0x19a2 (Emulex) device; oce driver never attaches, so neither the happy path nor the error path is reachable on the verification guest.- Per the audit contract, Low/Info findings are exempt from mandatory PoC; this is filed as Info precisely because the only trigger is a firmware/mailbox failure during attach on real hardware.
Fix validation
fix.diff moves mq->cq = cq up to right after the CQ creation so the
existing cleanup in oce_mq_free covers all paths (the !mq path still
deletes cq itself before mq exists, so no double-free). Not built on the
guest for the same hardware reason β the change is a one-line pointer
assignment reorder with no control-flow change, reviewed against all four
paths in the table above.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablefix.diff moves mq->cq = cq up to immediately after the cq creation so oce_mq_free()'s existing cleanup covers every error path; diff is patch(1)-validated against the pristine file (applies clean, see VERDICT.md). Kernel build/behavior validation impossible on the guest: no Emulex OneConnect device, so neither baseline nor patched behavior is exercisable.
findings/poc/DF-4420/fix.diff (validated with patch --dry-run against pristine oce_queue.c)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4420/VERDICT.md (goto-path table for all four failure paths of oce_mq_create); findings/poc/DF-4420/env.txt (guest has no oce hardware); sys/dev/netif/oce/oce_queue.c:675-678, 689-691, 735-741, 744, 779-803
PoC changes
no runnable PoC exists for this class (hardware mailbox failure path); evidence is the static path trace in VERDICT.md
Verified recommended fix
In oce_mq_create, set mq->cq = cq immediately after oce_cq_create succeeds so oce_mq_free releases the CQ on all error paths.
Verdict
oce_mq_create() creates its completion queue first but assigns mq->cq only on the success path (oce_queue.c:744), so when oce_create_ring_buffer (oce_queue.c:689-691) or the CREATE_MQ_EXT mailbox command (oce_queue.c:735-741) fails, oce_mq_free() skips its cq cleanup because mq->cq is still NULL β the cq struct plus its CQ_LEN_256 DMA ring leaks and sc->cq[ncqs-1] keeps a pointer to it. Pure resource leak on a real-hardware attach failure path: no unprivileged or network reachability, no UAF (nothing iterates sc->cq[] after release). Not runnable on the verification guest (no Emulex 0x19a2 PCI device), and Info-severity findings are PoC-exempt per contract; root cause proven by line-accurate goto-path trace.
No comments yet.