# DF-3104 — unvalidated mtime month reads mon_lens out of bounds

## What

`udf_timetotimespec()` (sys/vfs/udf/udf_vnops.c:243-294) uses the raw
ECMA-167 `timestamp.month` (a `uint8_t` straight from disk) as the loop bound
for `mon_lens[lpyear][i]` (:267-268) with **no `month <= 12` validation**:

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

`mon_lens` is a `static int[2][12]` (:220-223).  With a leap year (row 1),
month 13 already indexes flat word 24 — the first word **past the array**;
months up to 255 read up to ~1 KB of adjacent kernel `.data`.  The summed
out-of-bounds words are fully observable from userspace: each crafted file
with month m contributes `mon_lens[1][m-1] * 86400` to `st_mtime`, so
`stat()` differences between files isolate individual OOB words (25 of 32
bits recoverable per word since 86400 = 2^7·675).  Static `.data` next to
the array can contain kernel pointers (e.g. vops tables) — a KASLR-defeat
oracle — plus wild `st_mtime` values.

FreeBSD fixed this exact bug ("Don't trust time->month as it will be used
for an array index" in their `udf_timetotimespec`).

## Reproduce

    ./run.sh     # mounts image (root); as uid 1001: stat T10..T31

## Expected (stock kernel)

```
T11 ...  2592000 in-range        (30 days)
T12 ...  2678400 in-range        (31 days)
T13 ... -2059807616 OUT-OF-RANGE (mon_lens[1][12] = flat 24 = OOB)
...
DF3104_OOB_CONFIRMED: 19 of the month terms for months>=13 read
  out-of-range words
```

Patched kernel: months ≥ 13 clamp to epoch 0 (`T13..T31 ... 0`), exactly
the FreeBSD behavior — the stale guest binary's diff heuristic still prints
"OUT-OF-RANGE" for the clamped rows; the meaningful signal is that every
`st_mtime` for month ≥ 13 is 0.

## Fix validation

See DF-3102/fix_validate.sh + df31xx_combined_fix.diff (shared driver);
fix_run.log in this directory.
