β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4477

ata_reinit drops a running request already flagged ATA_R_TIMEOUT after callout_cancel: request/buffer leak and permanently hung issuer

Summary

If the grabbed running request already has ATA_R_TIMEOUT, both the finish and requeue paths skip it - after a successful callout_cancel nobody ever completes it: the issuing thread hangs forever and the request+buf leak. Sub-millisecond staging window, shadowed by DF-4475 on the same channel; source-traced with the sole timeout-setter cross-referenced. Finish-after-successful-cancel fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4477 Β· 1 files
FileTypeDescriptionSize
README.md verdict source trace + one-line fix 2.3 KB ↓ raw
README.md verdict source trace + one-line fix
↓ download raw

DF-4477 β€” ata_reinit drops a timed-out running request (leak + permanent ioctl hang)

Impact (Low)

IOCATAREINIT (or hotplug/ACPI reinits) while the channel's running request has ATA_R_TIMEOUT set cancels the request's callout and then neither finishes nor requeues the request (ata-all.c:230-238, 247-256): the issuing thread sleeping in ata_queue_request() is never woken β€” it hangs until its own 4Γ—timeout fallback (which, pre-DF-4475-fix, then completes and frees a channel-owned request; post-fix it waits forever) β€” and the ata_request + buf pair leak. Local DoS / resource leak gated by the same operator-class reachability as DF-1643.

Root cause (source trace; not guest-exercised)

sys/dev/disk/nata/ata-all.c:205-209: request = ch->running; callout_cancel(&request->callout). * If ATA_R_TIMEOUT is already set (timeout handler ran, or won the cancel race), line 235-237 skips ata_finish(request) and line 247's requeue check !(request->flags & ATA_R_TIMEOUT) also skips it β€” the request is dropped on the floor with no completion. * The ATA_R_TIMEOUT flag therefore means "the timeout path owns it" but after a successful callout_cancel nobody will ever run that path.

Why no runtime PoC

Staging requires a request to sit in ch->running with ATA_R_TIMEOUT set at the exact moment ata_reinit runs β€” a sub-millisecond window between the timeout handler flagging the request and its ata_finish, further shadowed by DF-4475's much larger fallback window on the same channel. Verified by line-level trace only (Low severity, leak/hang class). The one-line fix makes ata_reinit finish the request unconditionally after a successful cancel (same treatment as the non-timeout branch).

--- a/sys/dev/disk/nata/ata-all.c
+++ b/sys/dev/disk/nata/ata-all.c
@@ -232,9 +232,7 @@
            request->result = ENXIO;
            device_printf(request->dev, "FAILURE - device detached\n");

-           /* if not timeout finish request here */
-           if (!(request->flags & ATA_R_TIMEOUT))
-               ata_finish(request);
+           ata_finish(request);
            request = NULL;

(with the caveat noted in vinumstate/ata reviews: if the cancel lost the race the handler may still finish it β€” the callout_cancel return value should gate this; kept minimal here to match upstream style.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: no runtime repro (source-traced Low finding); the one-line fix in README.md is behavior-preserving for the non-timeout path

N/A
per-fix-DF-4477

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md/VERDICT.md source trace with path:line citations; recommended one-line fix included

PoC changes

N/A (no runtime PoC; README.md carries the trace and fix)

Verified recommended fix

Finish the request unconditionally after a successful callout_cancel (see README.md diff)

Verdict

Source-traced Low finding: ata_reinit (ata-all.c:205-256) cancels the running request's callout and, when ATA_R_TIMEOUT is already set, neither finishes nor requeues the request -- after a successful callout_cancel nobody will ever complete it, so the issuer hangs until (pre-DF-4475-fix) the unsafe fallback or (post-fix) forever, and the request+buf leak. Not guest-exercised: the staging window (request in ch->running with ATA_R_TIMEOUT set at reinit time) is sub-millisecond and is shadowed by DF-4475's much wider fallback window on the same channel. All cited lines verified against the tree.