# DF-3057 — dirfs_write discards the bread() error and the bwrite() result: unfilled blocks are persisted (stale/foreign data written into the target file) and failures reported as success

## Verdict
**REPRODUCED** (deterministic harness, identical over 3 runs) — the write
loop clobbers the read error and ignores the write result:

```c
:740  error = bread(vp, base_offset, BSIZE, &bp);
:741  error = uiomovebp(bp, (char *)bp->b_data + offset, len, uio);   /* :740 overwritten */
:742  if (error) { brelse(bp); break; }
...
:752  if (ap->a_ioflag & IO_SYNC)
:753          bwrite(bp);          /* return value ignored */
:754  else
:755          bdwrite(bp);
```

`bread`/`breadnx` (sys/kern/vfs_bio.c:900-960) always sets `*bpp` via getblk
but on a failed strategy (dirfs_strategy marks the buffer B_ERROR when the
host pread/pwrite fails — host I/O error on the backing file, invalid/closed
dn_fd, stale dn_size making `iosize` negative) the buffer contents are NOT
filled. dirfs_write then uiomoves the user's bytes into the unfilled 16KB
block and flushes the WHOLE block through dirfs_strategy -> pwrite(dn_fd):
everything outside the user-modified span is whatever the getblk buffer
previously held — stale data of ANOTHER file on the same mount — and it is
persisted into the target file, which the user can read back: an information
disclosure into an attacker-readable file (CWE-909), plus silent data
corruption, plus write() returning success despite the failed read and
despite a failed IO_SYNC flush (:753).

Harness proof (real file, real pread-back):

```
bread failed with EIO but write() returns: 0 (SUCCESS — error swallowed at :741)
read back target file: STALE DATA FROM ANOTHER FILE present at offset 7000
=> INFO LEAK INTO ATTACKER-READABLE FILE CONFIRMED
FIXED: write() returns: 5 (EIO) — failure reported, nothing persisted; no stale data
```

## Trigger reality check

The failed read requires the underlying host read of the backing file to
fail (host I/O error / device error on the backing store / dn_fd invalidated).
The extension case does NOT fail this way (pread past EOF returns 0 and
dirfs_strategy bzeros the block), so this needs a genuinely failing backing
file — narrow but real on vkernel deployments whose backing storage can
return EIO. Rated Medium (limited disclosure + silent corruption, unusual
precondition), consistent with the severity rubric.

## Fix validation

`fix.diff`: check the bread error before uiomove (brelse+break) and propagate
the bwrite error for IO_SYNC. `git apply --check` RC=0 locally and on guest
/usr/src; compile-neutral (identical first compiler error patched vs
unpatched); harness FIXED variant reports EIO and persists nothing.
Live boot: not_testable (dirfs vkernel-only).
