# DF-2476 — mdstrategy_preload stale bio_buf (UAF) — VERDICT

## Verdict: REPRODUCED-AS-LATENT (path unreachable at runtime on guest); fix validated to compile

- **status:** not_reproduced  *(runtime trigger: path is dead code on this guest)*
- **impact:** none at runtime (latent UAF / cross-request kernel memory corruption)
- **confidence:** certain (the source defect is unambiguous; the runtime reachability limit is real)

## The bug (confirmed in source, line-by-line)

`sys/dev/disk/md/md.c` `mdstrategy_preload()` (:345–400) caches the local
`struct buf *bp` **exactly once** from the initial `ap->a_bio` and never
refreshes it inside the `while(1)` service loop:

- :349  `struct buf *bp = bio->bio_buf;`        ← set ONCE
- :372  `bio = bioq_takefirst(&sc->bio_queue);` ← a *different* bio each iteration
- :379  `switch (bp->b_cmd) { … }`              ← STALE bp
- :383  `bcopy(sc->pl_ptr + bio->bio_offset, bp->b_data, bp->b_bcount);` ← STALE bp
- :395  `biodone(bio);`

The sibling `mdstrategy_malloc()` does it correctly at :239
(`bp = bio->bio_buf;` inside the loop). When ≥2 bios accumulate in
`sc->bio_queue` (concurrent I/O arriving while `sc->busy` is set), every
iteration after the first dereferences the **first** request's `bp` after
`biodone()` was already called on it. `physio` may have returned/freed that
buf → **use-after-free**. Even before the free lands, the I/O is
mis-targeted: iteration-2's `bio_offset` is used together with iteration-1's
`b_data`/`b_bcount`/`b_cmd` → cross-request data corruption / wrong-buffer
I/O into the preload image.

## Why it does not panic on this guest (reachability)

`mdcreate_preload()` (:437) is invoked **only** from `md_drvinit()` (:501) at
module-load/boot time, consuming loader-preloaded images of type
`md_image`/`mfs_root` (`preload_search_next_name`, :513). There is **no
runtime ioctl** that creates a preload md: `mdioctl()` (:162) is a stub
returning `ENOIOCTL`, and `mdconfig` is not even installed on the guest.

Consequently an `MD_PRELOAD` device exists only when the boot loader
preloaded an image. On the audit guest nothing is preloaded — `dmesg` shows
`md0: Malloc disk` (from `mdcreate_malloc`, :530) — so `mdstrategy`
(:191 `if (sc->type == MD_MALLOC) … else mdstrategy_preload`) always takes
the **correct** malloc branch and never enters the buggy preload path.

The PoC harness (`poc.c`) spawns 8 concurrent I/O threads on `/dev/md0`.
Run as root (md0 is `root:operator`):

```
[*] spawning 8 concurrent I/O threads on /dev/md0
[+] all threads completed; device path returned cleanly
RUN_EXIT=0   (guest still up — no panic)
```

This is the *expected* result on a malloc md: the racing bios are drained by
`mdstrategy_malloc`, which refreshes `bp` per iteration. It demonstrates the
path mismatch. To actually trip the UAF one would have to: (a) be root,
(b) reboot with a loader-preloaded md image (so a `MD_PRELOAD` device
exists), and (c) drive concurrent I/O at it. That is a **root→kernel**
path with no unprivileged→root escalation — a valid hard blocker for an
`uid=0` claim, and the runtime trigger is itself unavailable on the guest.

## Exploit chain

None applicable: the primitive is memory corruption (a UAF / OOB-write into
the preload image), but the path is reachable only from an already-root
context (loader preload + reboot) and is dead code at runtime on this guest.
There is no privilege boundary to cross, so there is no unpriv→root chain to
develop. This is a **latent** kernel memory-corruption defect that would
become live on a system that boots from a preloaded md/mfs image (e.g. an
installer or an mdroot appliance) under concurrent I/O.

## Fix

`fix.diff` adds the single missing refresh, matching the correct
`mdstrategy_malloc:239`:

```diff
@@ -373,6 +373,7 @@
 		crit_exit();
 		if (bio == NULL)
 			break;
+		bp = bio->bio_buf;
```

## Fix validation

`fix.diff` **applies** (`Hunk #1 succeeded at 373`) and **compiles cleanly**
into `md.ko` (`make` in `sys/dev/disk/md`, `rc=0`, `-Werror` clean — see
`fix_build.log`). Runtime before/after is **not_testable**: the buggy path
cannot be exercised on the guest because no preload md device exists and
creating one requires a boot-loader-preloaded image + reboot. The fix is
trivially correct by inspection (it makes the preload loop identical in
this respect to the already-correct malloc loop).

## PoC changes / artifacts

- `poc.c` — concurrent I/O harness; on md0 (malloc) it is a clean no-op that
  demonstrates the preload path is not in use. On a real preload md under
  concurrent I/O it would race bios into `sc->bio_queue` and trip the UAF.
- `build.sh` / `run.sh`, `build.log`, `run.log`, `env.txt`, `fix.diff`,
  `fix_build.log`, `manifest.json`.
