⬢ DragonFlyBSD Kernel Audit
DF-4536 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4536",
  "status": "not_reproduced",
  "reproduced": 0,
  "impact": "none",
  "confidence": "certain",
  "verdict": "The check-then-set of sc_state in lptopen() (lpt.c:458-462) is unsynchronized in the driver and, if ever executed concurrently, double-opens the unit: two kmalloc pairs (first leaks) and two closes that kfree the same second pair (heap double-free in the M_LPT zone). On the audited tree it cannot manifest: lpts dev_ops flags are 0 (lpt.c:191-198), so dev_needmplock() is true and dev_dopen() executes d_open under the kernel mplock (sys/kern/kern_device.c:119,142) - concurrent lptopen() entries are impossible. Verified empirically: 0 double-opens in 300 same-vnode full-path rounds, 300 cross-cdev rounds, and ~3,600 cross-devfs-mount rounds (a configuration that removes every other serializer, isolating the mplock). An initial calibration run that printed many double-successes was proven to be sequential pairs (sibling opening after the holders close) and the PoC was hardened with an fd-hold to eliminate the false signal. Filed at Info as a latent defect: it becomes a live double-free the day lpt is converted to D_MPSAFE without adding per-unit serialization (fix.diff adds a spinlock now).",
  "exploit_chain": "",
  "evidence": "run.log (all three race configurations + the calibration false-positive analysis); VERDICT.md (the mplock argument with path:line); fix.diff (git-apply-clean spinlock serialization)",
  "kernel_refs": [
    "sys/dev/misc/lpt/lpt.c:458",
    "sys/dev/misc/lpt/lpt.c:539",
    "sys/dev/misc/lpt/lpt.c:598",
    "sys/dev/misc/lpt/lpt.c:191",
    "sys/kern/kern_device.c:119",
    "sys/kern/kern_device.c:142",
    "sys/vfs/devfs/devfs_vnops.c:devfs_spec_open"
  ],
  "poc_changes": "PoC written for this finding (no seed). Two hardening iterations during verification: (1) per-child paths so different cdevs/vnodes/devfs-mounts can be raced; (2) fd-hold before close so sequential open/close pairs cannot produce false positives - the initial .ctl-vs-.ctl hits were shown to be sequential.",
  "attempts": 3,
  "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": 480,
  "guest_dirty": 0,
  "build_cmd": "cc -O -Wall -o lpt_openrace lpt_openrace.c",
  "run_cmd": "./lpt_openrace /dev/lpt0 /dev/lpt0 300 350 ; ./lpt_openrace /dev/lpt0 /dev/lpt0.ctl 300 450 ; ./lpt_openrace /dev/lpt0.ctl /mnt/df2/lpt0.ctl 30000 3 25000",
  "code_hash": "29d69127d5ddcc3c849af61ae805663685e14ce99c37a26ea9650180c11f7208",
  "notes": "Severity Info (latent): the driver-level TOCTOU is real but incidentally serialized by the mplock today; converting lpt to D_MPSAFE without the fix turns it into a reachable heap double-free. Device node is 0600 root:wheel regardless.",
  "recommended_fix": "Serialize the open state transition and close-time frees with a per-unit spinlock in struct lpt_data (fix.diff) - required before any D_MPSAFE conversion.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Fix validation not applicable: the defect does not manifest on the current (non-MPSAFE) configuration, so there is no baseline behavior to compare. The spinlock fix is defensive for a future D_MPSAFE conversion.",
  "fix_evidence": "fix.diff (git apply --check clean against sys/)"
}