# DF-0935 — Verdict: FALSE POSITIVE (overflow cannot trigger)

## One-line verdict
**NOT REPRODUCED — false positive.** The unbounded `ksprintf` is real, but the
cited security impact (stack overflow) is mathematically unreachable: the
maximum possible output of `procfs_dorlimit` is **499 bytes**, below the
512-byte `psbuf`, under every achievable configuration including the most
extreme admin override. The finding's "600-byte maximum" arithmetic is wrong.

## Mechanism the finding claims
`procfs_dorlimit` (`sys/vfs/procfs/procfs_rlimit.c:55-110`) formats 12 rlimit
entries into `char psbuf[512]` (line 64) via unbounded `ksprintf` (lines 77,
88, 90, 99, 101). `ksprintf` is genuinely unbounded (`sys/kern/subr_prf.c:549`
expands `PCHAR` to `*d++=cc` with no length check). The finding claims total
output can reach 600 bytes (12 × 50), overflow `psbuf[512]` by 88 bytes, and
corrupt the kernel stack frame.

## Why the impact does NOT manifest (rigorous bound)

### The finding's math is wrong on two independent counts

**Count 1 — only 9 of the 12 rlimits can ever hold 19-digit values, not 12.**
The finding's "600 bytes" assumes every entry's `rlim_cur`/`rlim_max` can be
`RLIM_INFINITY-1` (9223372036854775806, 19 decimal digits). But three of the
twelve resources are clamped by `int` (not `u_quad_t`) sysctls inside
`kern_setrlimit` (`sys/kern/kern_plimit.c:358-380`):

| Resource        | Cap variable             | Type  | Max value (digits) |
|-----------------|--------------------------|-------|--------------------|
| `RLIMIT_NOFILE` (8)   | `maxfilesperproc`   | `int` | 2147483647 (10)    |
| `RLIMIT_NPROC` (7)    | `maxprocperuid`     | `int` | 2147483647 (10)    |
| `RLIMIT_POSIXLOCKS` (11) | `maxposixlocksperuid` | `int` | 2147483647 (10) |

`int` caps cannot store 19-digit values. `kern_setrlimit` clamps both
`rlim_cur` and `rlim_max` to the cap value (`kern_plimit.c:359-360, 366-367,
376-377`), so even when an attacker calls
`setrlimit(RLIMIT_NOFILE, {RLIM_INFINITY-1, RLIM_INFINITY-1})`, the stored
value is at most `INT_MAX` (10 digits).

**Count 2 — even raising `kern.maxdsiz` / `kern.maxssiz` (the finding's stated
precondition) leaves 13 bytes of headroom.** With every achievable cap raised
to its maximum (`maxdsiz` and `maxssiz` to `RLIM_INFINITY-1`; the three int
caps to `INT_MAX`), and the user setting every uncapped resource to
`RLIM_INFINITY-1`, the maximum possible output is:

```
       ident  cur          max          bytes
[ 0] cpu       19 digits    19 digits    44
[ 1] fsize     19 digits    19 digits    46
[ 2] data      19 digits    19 digits    45   (maxdsiz raised)
[ 3] stack     19 digits    19 digits    46   (maxssiz raised)
[ 4] core      19 digits    19 digits    45
[ 5] rss       19 digits    19 digits    44
[ 6] memlock   19 digits    19 digits    48
[ 7] nproc     10 digits    10 digits    28   (INT_MAX clamp)
[ 8] nofile    10 digits    10 digits    29   (INT_MAX clamp)
[ 9] sbsize    19 digits    19 digits    47
[10] vmem      19 digits    19 digits    45
[11] posixlock 10 digits    10 digits    32   (INT_MAX clamp)
                                         ----
                              Scenario A total: 499 bytes  (UNDER 512)
```

**The cited overflow (88 bytes past 512) cannot occur.** The realistic
ceiling is 499 bytes — 13 bytes of headroom remain even in the most contrived
admin configuration. On the **default** kernel, the realistic maximum is
431 bytes (user maximally inflates; `maxdsiz`/`maxssiz` stay at defaults of
32 GB / 512 MB).

### Per-scenario output (computed on-guest, see `maxcalc.c`)

| Scenario                                                        | Total bytes | vs psbuf[512] |
|-----------------------------------------------------------------|-------------|---------------|
| Default config, default rlimits (`/proc/self/rlimit`)           | 186         | safe          |
| **Default config, user maximally inflates** (the PoC's path)    | **431**     | safe          |
| `maxdsiz`+`maxssiz` raised to `RLIM_INFINITY-1`, others default | 467         | safe          |
| **Every cap maxed** (maxdsiz, maxssiz → 2^63-1; 3 int caps → INT_MAX) | **499** | **safe (13-byte headroom)** |
| Finding's claimed theoretical max                               | 600         | unreachable   |

### Empirical confirmation
- Original PoC: `./dfpoc-rlimit-overflow` → `read 397 bytes from /proc/870/rlimit`,
  `no overflow on this config`. (Full `run.log`.)
- Maximally-inflate harness (sets all 12 rlimits to `RLIM_INFINITY-1`,
  reads `/proc/self/rlimit`): `read 431 bytes`. No overflow. (`run.log`.)

## Why the finding's arithmetic was off
The reviewer applied `12 × (max_ident + 1 + max_digits + 1 + max_digits + 1)`
uniformly across all twelve entries, treating `max_digits` as 19 for every
resource. That assumption only holds for the seven resources NOT handled by
the `switch` in `kern_setrlimit` (`kern_plimit.c:307-381`) plus the two
`u_quad_t`-capped ones (`RLIMIT_DATA`, `RLIMIT_STACK`), whose caps CAN be
raised to `RLIM_INFINITY-1` via the loader tunables. It does **not** hold for
`RLIMIT_NOFILE`/`RLIMIT_NPROC`/`RLIMIT_POSIXLOCKS`, whose caps are `int`
sysctls (`subr_param.c:76, 80, 82`) — bounded at INT_MAX = 2147483647.

## Defense-in-depth note (still worth fixing, but not a security bug)
The unbounded `ksprintf` into a fixed stack buffer is fragile: it relies on an
arithmetic invariant across three independent `int` sysctls and two `u_quad_t`
tunables, plus `RLIM_NLIMITS` and `rlim_t`. If any of those changed (e.g. a
new rlimit added, `int` widened, or a new cap variable introduced), the bound
could silently break. The sibling `procfs_dostatus` uses bounded `ksnprintf`
with a `DOCHECK` macro for exactly this reason (`procfs_status.c:99-167`).

A defense-in-depth hardening fix (convert to `ksnprintf` with explicit
remaining-length accounting, mirroring `procfs_dostatus`) is provided in
`fix.diff`. **It is hardening, not a security fix** — the cited bug cannot
manifest on any current or achievable configuration.

## PoC changes
- `dfpoc-rlimit-overflow.c` — unchanged (compiles cleanly, runs cleanly,
  prints the expected "no overflow" result; this is itself the disproof).
- Added `maxcalc.c` — a small program that computes the exact maximum
  `procfs_dorlimit` output under three cap scenarios (default / maxdsiz+maxssiz
  raised / all caps maxed). All three stay under 512.
- Added `maxinflate.c` — a harness that maximally inflates all 12 rlimits as
  an unprivileged user and reads `/proc/self/rlimit`. Confirms 431 bytes.

## Conclusion
- `status` = `not_reproduced`
- `impact` = `none` (no overflow, no panic, no leak)
- `confidence` = `certain` (math is exact; confirmed on-guest)
- `fix_status` = `not_applicable` for the security claim; an optional
  defense-in-depth hardening diff is provided but does not need kernel build
  validation because there is no observable "before" misbehavior to make "gone".
