DF-4528 / verdict.json
{ "finding_id": "DF-4528", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Code-certain lock-protocol violation, not dynamically isolated on the guest. nvme_submit_request() (nvme.c:514-529) documents Caller must hold the queue lock and mutates subq_tail/unsubmitted/ksubq[tail] unlocked; the I/O path honors it (nvme_disk.c:336-338) but every admin-queue submitter (nvme_ioctl.c:56 getlog; nvme.c:800/843/877/902 queue create/delete helpers; nvme_admin.c:233/319/534/598/629 identify flows) submits with no lock. Two concurrent admin submitters exist exactly when the admin threads attach/detach command storm overlaps a user NVMEIOCGETLOG on the same controller. On the guest: the admin ioctl path was load-tested clean (1.35M ioctls, 0 errors, 6-way concurrent; getlog submitters serialize on ioctl_lk among themselves), and the deterministic trigger environment was built (nvmebang on-demand attach/detach storms) -- but the DF-4529 teardown panic (same rig, same files) fires before attach-storm race data could be collected, so the submit-side interleaving itself was not observed. Consequences if hit: double-written ksubq slot / lost command -> nvme_wait_requests unbounded, non-interruptible loop (unkillable thread holding comq lock), or corrupted unsubmitted accounting driving get_request into its NULL path (KKASSERT is a no-op on production kernels) -> NULL deref in nvme_get_admin_request (nvme.c:336).", "exploit_chain": "", "evidence": "README.md (contract citations), run.log (1.35M clean ioctls + rig), ../DF-4529 panic.txt (the sibling teardown race that masks it), fix.diff", "kernel_refs": [ "sys/dev/disk/nvme/nvme.c:509-529", "sys/dev/disk/nvme/nvme.c:446-453", "sys/dev/disk/nvme/nvme.c:480-485", "sys/dev/disk/nvme/nvme_ioctl.c:56", "sys/dev/disk/nvme/nvme_admin.c:233", "sys/dev/disk/nvme/nvme_disk.c:336-338" ], "poc_changes": "Built nvgetlog/nvspam/nvslow load tools + nvmebang trigger module; QEMU relaunched with an emulated nvme controller (vm_nvme.sh).", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64", "runtime_sec": 900, "guest_dirty": 1, "build_cmd": "guest: cc -O2 -o /root/nvspam nvspam.c; cc -O2 -o /root/nvslow nvslow.c; kld nvmebang.ko", "run_cmd": "for i in 1..6: /root/nvslow /dev/nvme0 90 0.2 & ; sysctl hw.nvmebang.action=2 (attach storm) / =1 (detach storm)", "code_hash": "848bce2a1b7ec79fc6aea3509266047d60c263a94f23b827ecaf7e661f2bc003", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (nvme_submit_admin_request() wrapper taking queue->lk; all qid-0 submitters converted) authored against the read-only tree; not kernel-tested because the race itself was not dynamically isolated (DF-4529 masking). Compiles against the same headers used by the validated DF-4529 fix build.", "fix_evidence": "fix.diff", "notes": "Same rig as DF-4529; a clean dynamic demo requires the DF-4529 fix in place (or an instrumented kernel).", "recommended_fix": "Route all admin-queue submissions through a wrapper that takes subqueues[0].lk around nvme_submit_request()." } |