# DF-3104 — VERDICT

**Status: REPRODUCED (leak, weak)** — OOB read of kernel static `.data` past
`mon_lens[2][12]`, with the read words observable via `stat()` timestamps
from an unprivileged user on a crafted UDF image.

## Root cause

`sys/vfs/udf/udf_vnops.c:220-223`:

```c
static int mon_lens[2][12] = { ... };
```

`:267-268`:

```c
	for (i = 1; i < time->month; i++)
		t->tv_sec += mon_lens[lpyear][i] * 3600 * 24;
```

`time->month` is `uint8_t` from the file entry's timestamp on disk, never
validated.  `lpyear` ∈ {0,1} (`udf_isaleapyear`, :225-235, always returns 0
or 1), so `mon_lens[1][i]` for `i ≥ 12` reads flat words `≥ 24` — beyond
the 24-word array, up to `i = 254` (month 255) → ~1 KB of adjacent `.data`.

## Reproduction (run.log)

Image files `T10..T31`: FE mtime `year=2020` (leap), `month = 10..31`,
`day=1`, identical otherwise.  As uid 1001 (`su maxx`):

```
T11  1604232000     2592000 in-range      <- mon_lens[1][10] = Oct 31d (valid)
T12  1606910400     2678400 in-range      <- mon_lens[1][11] = Nov 30d (valid)
T13  -452897216  -2059807616 OUT-OF-RANGE <- mon_lens[1][12] = flat 24 = OOB
T14 -1367233344   -914336128 OUT-OF-RANGE
...                                        (all 19 terms for months >= 13)
T31  5892316608   1039478784 OUT-OF-RANGE
DF3104_OOB_CONFIRMED: 19 of the month terms for months>=13 read
  out-of-range words (first at mon_lens[1][12], flat index 24)
```

Each consecutive-pair difference isolates exactly one OOB word scaled by
86400 (mod 2^32, signed); the values are stable per kernel build.  19 of 19
month-terms ≥ 13 are out of the {28,29,30,31}-day range — the array-bound
violation is total, not statistical.

## Impact

* Deterministic disclosure of kernel static `.data` (up to ~1 KB past
  `mon_lens`) through `stat()` mtime arithmetic: 25 of 32 bits per word
  recoverable (86400 = 2^7 × 675).  Whatever the linker places after the
  array — potentially function-pointer-bearing tables — leaks at
  build-deterministic offsets: a KASLR-defeat / build-fingerprint oracle.
* On this guest (no KASLR) the practical ceiling is low — hence Low
  severity — but the read itself is a genuine array-index violation fed by
  disk data, and the fix is one line.

## Fix validation (fix.diff)

`if (time->year < 1970 || time->month < 1 || time->month > 12)` guard
(FreeBSD-equivalent).  Patched module, same image (fix_run.log):

```
T13    0  ...    <- clamped to epoch (fix working)
...all months >= 13 -> st_mtime 0
```

Every `st_mtime` for months ≥ 13 becomes 0; months ≤ 12 keep valid values.
(The "OUT-OF-RANGE" labels printed by the stale guest-side PoC binary refer
to the clamp-to-zero diffs, not to OOB reads — the binary's heuristic only
compares diffs; the mtimes themselves show the clamp. The updated `poc.c` in
this pack treats `st_mtime == 0` as clamped.)

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