⬢ DragonFlyBSD Kernel Audit
DF-3813 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3813",
  "status": "inconclusive",
  "reproduced": 0,
  "impact": "none",
  "confidence": "likely",
  "verdict": "Source-certain use-after-free: mpr_wait_command()s lksleep wakeup path (mpr.c:3578,3604) and mpr_request_polled()s poll loop (mpr.c:3641) never re-check for a concurrent reallocating diag reset, so a sleeper woken by mprsas_complete_all_commands() (which does not free WAKEUP commands, mpr_sas.c:1178-1199) keeps using a cm that mpr_iocfacts_free() kfreed with the whole commands[] array (mpr.c:791-797). Only the own-timeout path checks MPR_FLAGS_REALLOCATED (mpr.c:3611). Runtime verdict inconclusive: the audit guest has the mpr driver compiled in (kldstat: 142 pci/mpr) but no SAS3 controller (/dev/mpr0 absent), so the race cannot be fired; the compiled+run PoC harness exits with the honest ENXIO negative (guest_run.log).",
  "exploit_chain": "concurrent waiter (root ioctl / config-page / SATA-ID / TM) + facts-changing diag reset -> mpr_iocfacts_free() kfrees commands[] under sleeper -> sleeper returns 0 with dangling cm -> caller callout_stop/cm_reply read/mpr_free_command() on freed slab -> mpr_free_command re-inserts alias of a live new-pool command into req_list -> one mpr_command owned twice -> kernel memory corruption (controlled via subsequent SCSI/ioctl command contents); full chain requires hardware, not demonstrated on this guest.",
  "evidence": "VERDICT.md (step-by-step source trace); guest_run.log (BUILD_OK + open /dev/mpr0: No such file or directory + kldstat 142 pci/mpr); fix.diff (generation-counter fix).",
  "kernel_refs": [
    "sys/dev/raid/mpr/mpr.c:3578",
    "sys/dev/raid/mpr/mpr.c:3611",
    "sys/dev/raid/mpr/mpr.c:3641",
    "sys/dev/raid/mpr/mpr.c:791-797",
    "sys/dev/raid/mpr/mpr_sas.c:1178"
  ],
  "poc_changes": "Added missing #include <errno.h> to the seed sketch (first build failed, see build.log); otherwise the harness was structurally correct (ioctl 210 struct verified against mpr_ioctl.h:116-124, Function offset 3 per mpi2.h:855-866).",
  "attempts": 2,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026  root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 40,
  "guest_dirty": 0,
  "build_cmd": "cc -O -Wall -o poc poc_concurrent_reinit.c -lpthread",
  "run_cmd": "./poc",
  "code_hash": "402eed4d687f8ab13073beff1758bfefb6199f9c98baaae609fe6e263a7021b1",
  "notes": "mpr.c-side sibling of DF-3800 (same realloc pass; DF-3800 covers stale sassc->maxtargets). Adjacent caller-side consequences for other files pass-2: mpr_sas_lsi.c:1173 callout_stop on NULL/dangling cm; mpr_user.c:846 mprsas_free_tm(NULL).",
  "recommended_fix": "Bump a realloc generation counter on every reallocating pass and have mpr_wait_command()/mpr_request_polled() treat a generation change as command freed (*cmp = NULL, ENXIO).",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Fix validation impossible on this guest: no SAS3 controller, so neither baseline nor single-fix kernel can drive the mpr_wait_command sleeper path (fix.diff authored against the read-only sys/ tree, never applied).",
  "fix_evidence": "fix.diff (generation counter in mprvar.h + checks at both wait sites); guest_run.log device-absence proof."
}