# DF-3092 — VERDICT

**Status: FALSE POSITIVE** (mechanism refuted by source analysis + runtime
experiment; the trigger runs, the claimed effect cannot happen)

## What the finding claims

`fillinusemap()`'s tail-marking loop

```c
for (cn = pmp->pm_maxcluster + 1; cn < (pmp->pm_maxcluster + N_INUSEBITS) / N_INUSEBITS; cn++)
        pm_inusemap[cn / N_INUSEBITS] |= 1U << (cn % N_INUSEBITS);
```

(sys/vfs/msdosfs/msdosfs_fat.c:950-952) compares a **cluster number**
against a **word-index** bound and therefore never executes, allegedly
leaving the bits for clusters `pm_maxcluster+1 .. word-end` CLEAR
("phantom free").  `chainlength()`'s clamp

```c
start %= N_INUSEBITS;                     /* :636 start reduced mod 32 */
...
if (start + len > pmp->pm_maxcluster)     /* :642/:649/:664 word-relative
                                             start compared to ABSOLUTE
                                             maxcluster — never fires */
```

then allegedly fails to cap a run that crosses `pm_maxcluster`, so
`clusteralloc1()`/`chainalloc()` hand out nonexistent clusters:
KASSERT panic `cn too large` on INVARIANTS kernels
(msdosfs_fat.c:377-378), silent out-of-volume allocation on release
kernels.

## Why the mechanism is dead (line-by-line)

The phantom-free premise ignores the **first** loop of `fillinusemap()`:

```c
/* msdosfs_fat.c:897-898 */
for (cn = 0; cn < (pmp->pm_maxcluster + N_INUSEBITS) / N_INUSEBITS; cn++)
        pmp->pm_inusemap[cn] = FULL_RUN;
```

`pm_inusemap` is allocated with exactly
`howmany(pm_maxcluster + 1, N_INUSEBITS)` words
(msdosfs_vfsops.c:565-566).  For every `pm_maxcluster`,
`floor((pm_maxcluster+32)/32) == floor(pm_maxcluster/32)+1 ==
howmany(pm_maxcluster+1,32)` words, i.e. **the initial fill covers every
allocated word, including every bit beyond `pm_maxcluster` inside the
last word** — they start FULL_RUN (marked "used").  The FAT scan loop
(:906-946) only iterates `cn <= pm_maxcluster` and only ever *clears*
bits (via `usemap_free`) for genuinely-free clusters.  Therefore:

1. Bits for clusters `> pm_maxcluster` **stay SET ("used")** after
   mount — the dead tail loop at :950 was redundant belt-and-suspenders;
   its broken bound changes nothing.
2. In `chainlength(4104, 7)`: `map = pm_inusemap[128] & ~((1<<8)-1)`
   is **non-zero** (bits 14..31 set = clusters 4110..4127 used), so the
   `if (map)` branch returns `len = ffs(map)-1-start = 6` — the correct
   answer.  The broken word-relative clamp never matters because a free
   run can never extend past `pm_maxcluster` (tail bits are set).
3. `usemap_alloc(cn)` with `cn > pm_maxcluster` is therefore
   unreachable from this path — the KASSERT at :377-378 cannot fire on
   INVARIANTS kernels either (`chainlength`'s computation does not
   depend on INVARIANTS).

## Runtime proof (guest, stock release kernel)

Crafted image (crafttail.c): FAT16, `pm_maxcluster = 4109`, BIG.TXT owns
clusters 2..4103, real free 4104..4109 (6), phantom region 4110..4127,
image padded to 4200 sectors so cluster 4110's data area *exists* on the
backing device (a phantom allocation would write successfully).

| phase | operation | finding predicts | observed |
|---|---|---|---|
| A | append 3584 B (7 clusters) in one write(2) | phantom clusters allocated: write **succeeds** (release) / KASSERT panic (INVARIANTS) | **ENOSPC**, system alive, size unchanged, `FAT[4110..4351]` untouched, avail stays 6 |
| B | append 3072 B (exactly the 6 real clusters) | — | succeeds, size 2103296, avail 0 |
| C | append 512 B (1 more cluster) | cluster 4110 is "free" → succeeds | **ENOSPC** |
| D | offline tailcheck of unmounted image | FAT[4110..] zero, chain ends ≤ 4109 | ALL PASS: chain ends exactly at 4109 EOF |

Phase A is decisive: under the finding's hypothesis `chainlength(4104,7)`
returns 7 ≥ 7, `chainalloc(4104,7)` runs `usemap_alloc(4110)`, and on
this non-INVARIANTS kernel the write of 3584 bytes would have completed
**successfully** (with BIG.TXT's chain extending into clusters 4110
inside the padded image).  It instead returned ENOSPC with zero FAT
changes beyond the real tail — exactly the behavior predicted by the
:897-898 FULL_RUN analysis (`chainlength` returned 6, extendfile
allocated nothing durable, msdosfs_write:657-667 unwound the write).

## Residual real (but inert) code defects

* msdosfs_fat.c:950-952: the tail-marking loop is dead code (bound bug).
  **Caution for any fix**: naively "correcting" the bound to cluster
  units (`cn < pm_maxcluster + N_INUSEBITS`) makes the loop execute —
  and when `pm_maxcluster % 32 == 31` it writes
  `pm_inusemap[pm_maxcluster/32 + 1]`, one word **past** the
  `howmany(pm_maxcluster+1, 32)`-word allocation (msdosfs_vfsops.c:565)
  — i.e. the obvious fix would introduce a heap overflow.  Since the
  initial FULL_RUN fill already covers the whole tail, deleting the
  dead loop is the zero-risk fix.
* msdosfs_fat.c:636/642/649/664: `chainlength()` compares a
  word-reduced `start` against absolute `pm_maxcluster`.  Inert for the
  same reason (tail bits always set).  A cosmetic fix would clamp on the
  absolute cluster number.

Neither defect produces a phantom-free bit, an out-of-range
`usemap_alloc`, a KASSERT, or an out-of-volume allocation.  No security
impact.  No fix.diff shipped (false positive; the "fix" would be dead
code removal, not a security patch).

## Environment

DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, stock release kernel, no
INVARIANTS), 6 vCPU QEMU/KVM guest, guest /usr/src verified byte-identical
to the audited tree (md5 aecdffd0dbf7e0195421d73ccb959831 for
sys/vfs/msdosfs/msdosfs_fat.c).

## 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.

(No fix.diff for this item: documented recommendation only - see Fix above.)
