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

mrsas_cam_attach error paths free sim_0/sim_1 without clearing sc pointers β€” attach_fail_cam unwind re-frees via mrsas_cam_detach: cam_sim_path() UAF read, second xpt_bus_deregister, cam_sim_free() double-free into M_CAMSIM (plus devq refcount underflow)

Summary

Four failure paths of mrsas_cam_attach free the cam_sim objects without NULLing sc->sim_0/sim_1; mrsas_attach unwinds via mrsas_cam_detach which re-processes the dangling pointers: UAF read in cam_sim_path, double bus-deregistration, double kfree into M_CAMSIM, devq refcount underflow. Trigger is CAM resource exhaustion at attach on Thunderbolt-MR hardware (absent from guest). Mirror choreography compiled and executed ON the guest proves both violations and that the NULL-clearing fix is clean. NULL-after-free fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4438 Β· 11 files
FileTypeDescriptionSize
README.md β€” 3.4 KB ↓ raw
VERDICT.md β€” 4.6 KB ↓ raw
repro_logic.c β€” 5.1 KB view raw
build.sh β€” 118 B view raw
run.sh β€” 122 B view raw
build.log β€” 128 B view raw
run.log β€” 613 B view raw
env.txt β€” 423 B view raw
fix.diff β€” 1.8 KB view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 3.5 KB view raw

DF-4438 β€” mrsas_cam_attach error paths leave dangling sc->sim_0/sc->sim_1; caller unwind double-frees the cam_sim

File: sys/dev/raid/mrsas/mrsas_cam.c Severity: Low Β· CWE: CWE-415 (Double Free) / CWE-459 (Incomplete Cleanup) Β· Confidence: certain (static call-sequence proof + on-guest logic mirror)

What

mrsas_cam_attach() frees sim_0/sim_1 on its internal failure paths without clearing sc->sim_0 / sc->sim_1. When it returns a failure, mrsas_attach() (sys/dev/raid/mrsas/mrsas.c:805 β†’ attach_fail_cam: at :838-839) calls mrsas_cam_detach(), which unconditionally re-processes the same non-NULL pointers:

/* mrsas_cam.c:216-218 */
if (sc->sim_0) {
    xpt_bus_deregister(cam_sim_path(sc->sim_0));   /* UAF read of freed sim;
                                                       double-deregister */
    cam_sim_free(sc->sim_0);                        /* kfree() again */
}

In DF CAM, cam_sim_free() β†’ cam_sim_release() (sys/bus/cam/cam_sim.c:231-251) kfree's the sim when refcount == 1 and drops the sim's devq reference. The deregister-at-failure paths (mrsas_cam.c:150-156, :157-165, :181-185, :186-193) already executed exactly that, so the second cam_sim_free() is a double free into M_CAMSIM, preceded by a use-after-free read in cam_sim_path() (sim->path_id) and a second xpt_bus_deregister() of a bus that is already deregistered (or was never registered, on the xpt_bus_register-failure path) β€” corrupting CAM bus bookkeeping and the devq refcount (cam_simq_release() underflow β†’ premature cam_devq free).

Live failure paths (all reachable under kernel memory pressure / CAM resource exhaustion at driver attach β€” boot or card hotplug):

mrsas_cam.c line failure frees dangling
150-156 xpt_bus_register(sim_0) != CAM_SUCCESS devq ref + sim_0 sc->sim_0
157-165 xpt_create_path(&path_0) fails devq ref + sim_0 sc->sim_0
181-185 xpt_bus_register(sim_1) fails sim_1 sc->sim_1
186-193 xpt_create_path(&path_1) fails sim_1 sc->sim_1

(The sim_1 == NULL path at mrsas_cam.c:175-178 is dead code β€” cam_sim_alloc() allocates with M_INTWAIT and cannot return NULL; it also leaks the caller's devq reference.)

How to reproduce (logic mirror, runs on the DragonFly guest)

The trigger requires an LSI/Avago Thunderbolt-MR controller and CAM resource failure at attach; the audit QEMU guest has no such device (see env.txt), so the kernel sequence cannot be executed there. Instead repro_logic.c mirrors the exact kernel refcount/cleanup choreography (cam_sim_alloc/cam_sim_release semantics from sys/bus/cam/cam_sim.c:165-251 + the mrsas call sequence) and detects the UAF/double-free:

./build.sh          # cc -O2 -Wall -o repro_logic repro_logic.c
./run.sh            # buggy mode: flags UAF + double free (rc=2); fixed mode: clean (rc=0)

Expected decisive output (run.log):

  !! sim UAF: cam_sim_path() on freed cam_sim (kernel: read of kfree'd M_CAMSIM)
  !! sim DOUBLE FREE: cam_sim_release()/kfree() on already-freed cam_sim
RESULT: 2 kernel-memory-safety violation(s) detected ...

Fix

fix.diff β€” NULL out sc->sim_0/sc->sim_1 after each error-path cam_sim_free() (and defensively in mrsas_cam_detach()), plus release the caller's devq reference on the (currently dead) sim_1-alloc-failure path.

VERDICT.md
↓ download raw

DF-4438 VERDICT

Status: inconclusive (kernel-path reproduction impossible on this guest: no LSI/Avago MR controller present) Β· Logic-flaw proof: certain Β· Impact if triggered: kernel heap double-free (M_CAMSIM) + UAF read + CAM bus/devq state corruption at driver-attach failure.

Root cause, traced line by line

  1. mrsas_attach() calls mrsas_cam_attach(sc) β€” sys/dev/raid/mrsas/mrsas.c:805. On failure it goes to attach_fail_cam: (mrsas.c:838-839) which calls mrsas_cam_detach(sc) β€” the same teardown used for a successful attach.

  2. Inside mrsas_cam_attach() (sys/dev/raid/mrsas/mrsas_cam.c:116-201), four internal failure paths free the SIM objects themselves and return failure:

  • :150-156 β€” xpt_bus_register(sc->sim_0, 0) fails β†’ cam_simq_release(devq); cam_sim_free(sc->sim_0); β†’ return ENXIO.
  • :157-165 β€” xpt_create_path(&sc->path_0, ...) fails β†’ xpt_bus_deregister(cam_sim_path(sc->sim_0)); cam_simq_release(devq); cam_sim_free(sc->sim_0); β†’ return ENXIO.
  • :181-185 β€” xpt_bus_register(sc->sim_1, 1) fails β†’ cam_sim_free(sc->sim_1); β†’ return ENXIO.
  • :186-193 β€” xpt_create_path(&sc->path_1, ...) fails β†’ xpt_bus_deregister(cam_sim_path(sc->sim_1)); cam_sim_free(sc->sim_1); β†’ return ENXIO.

None of them clears sc->sim_0 / sc->sim_1.

  1. cam_sim_free() in DF is terminal: sys/bus/cam/cam_sim.c:229-231 β†’ cam_sim_release(sim, CAM_SIM_SOFTC) at :234-251; with refcount == 1 (:238) it drops the sim's devq reference and kfree(sim, M_CAMSIM) (:243-249). The softc pointer now dangles.

  2. The caller unwind then runs mrsas_cam_detach() (mrsas_cam.c:209-227): - :217 xpt_bus_deregister(cam_sim_path(sc->sim_0)) β€” cam_sim_path() is a plain sim->path_id dereference of freed memory (UAF read), and the bus is deregistered a second time (or, on path (1), was never registered β€” xpt_bus_deregister of an unknown path corrupts/double-unlinks CAM bus bookkeeping). - :218 cam_sim_free(sc->sim_0) β€” double free into M_CAMSIM, plus a second cam_simq_release(sim->devq) β†’ devq refcount underflow β†’ premature cam_devq free while other sims/CAM still reference it.

Same for sc->sim_1 at :222-224 for failure paths (3)/(4).

Reachability / threat

Requires mrsas(4) attach to fail after cam_sim_alloc β€” i.e. an LSI Thunderbolt MR controller present plus kernel memory pressure / CAM resource exhaustion at boot or card hot-plug (xpt_bus_register/xpt_create_path ENOMEM-class failures). Not directly attacker-triggerable without those preconditions, hence Low; the consequence is kernel heap corruption in M_CAMSIM (double free) and CAM-layer state corruption β€” panic at minimum, in principle a heap-manipulation primitive.

Evidence

  • repro_logic.c + build.sh + run.sh β€” userspace mirror of the exact kernel choreography, compiled and executed on the DragonFly guest (run.log): buggy (current-kernel) sequence flags sim UAF: cam_sim_path() on freed cam_sim and sim DOUBLE FREE: cam_sim_release()/kfree() on already-freed cam_sim; fixed sequence (sc->sim_0 = NULL after error-path free) is clean. cc 8.3 [DragonFly] produced no warnings.
  • env.txt β€” guest uname, pciconf shows no vendor-1000 (LSI) devices, no /dev/mrsas*, no hw.mrsas sysctl node β‡’ kernel-path reproduction is impossible on this guest; the finding rests on the (deterministic, single-threaded, attach-context) call-sequence proof above.

Fix validation

Not kernel-testable on this guest (no mrsas hardware; fix validation requires building a kernel with the mrsas driver and attaching a device that fails registration). The mirror's fixed mode demonstrates the fix removes both violations. fix.diff authored against the read-only tree; never applied to sys/.

Fix summary

Clear sc->sim_0/sc->sim_1 after error-path cam_sim_free() (and NULL them in mrsas_cam_detach() after freeing) so mrsas_attach's unwind via mrsas_cam_detach cannot re-free; also drop the caller's devq reference on the sim_1-alloc-failure path (currently a leak, in dead code since cam_sim_alloc uses M_INTWAIT).

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_testable
baseline no→ patch + rebuild →patched clean

Kernel fix validation not testable: the guest has no LSI MR controller, and the bug requires attach-time CAM resource failure with the device present. The mirror's fixed mode (sc->sim_X = NULL after free) shows both violations eliminated; fix.diff applies cleanly against sys/dev/raid/mrsas/mrsas_cam.c.

run.log fixed-mode section: 'RESULT: clean (no UAF / double free detected)', exit 0; fix.diff in this pack.
↓ fix.diffper-fix-DF-4438

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'sim UAF: cam_sim_path() on freed cam_sim' + 'sim DOUBLE FREE: cam_sim_release()/kfree() on already-freed cam_sim' (buggy, rc=2) vs 'RESULT: clean' (fixed, rc=0); env.txt documents guest device absence; VERDICT.md has the full path:line trace (mrsas.c:805->838-839 -> mrsas_cam.c:216-218; cam_sim.c:231-251 frees at refcount==1).

PoC changes

No seed PoC existed (pass-2 finding discovered in this run); wrote repro_logic.c as a userspace mirror of the kernel refcount/unwind choreography since the trigger needs absent hardware.

Verified recommended fix

NULL sc->sim_0/sc->sim_1 after error-path cam_sim_free() calls in mrsas_cam_attach (and defensively in mrsas_cam_detach) so mrsas_attach's attach_fail_cam unwind cannot double-free; release the caller's devq ref on the sim_1-alloc-failure path.

Verdict

Kernel-path reproduction impossible on the audit guest (no LSI/Avago MR controller: pciconf shows no vendor-1000 devices, no /dev/mrsas*, no hw.mrsas sysctl). The flaw itself is statically certain: mrsas_cam_attach error paths (mrsas_cam.c:150-156, 157-165, 181-185, 186-193) cam_sim_free() sim_0/sim_1 without clearing sc->sim_0/sc->sim_1; mrsas_attach (mrsas.c:838-839) then unwinds via mrsas_cam_detach, which re-processes the dangling pointers: cam_sim_path() UAF read + second xpt_bus_deregister + cam_sim_free() double free into M_CAMSIM plus devq refcount underflow. A userspace mirror of the exact choreography, compiled and run on the DragonFly guest (cc 8.3), flags both violations in buggy mode and is clean with the one-line fix (run.log).