# DF-4220 VERDICT — reproduced (arithmetic, on-guest harness); kernel path latent

## Bottom line

`build_evenly_distributed_points()` in
`sys/dev/drm/amd/display/modules/color/color_gamma.c:1066-1107` trusts its
`numberof_points` argument. The module's two public entry points pass
`ramp->num_entries` through unvalidated (color_gamma.c:1407-1410 and
1617-1620) after allocating `num_entries + _EXTRA_POINTS` entries
(color_gamma.c:1382-1384/1392-1393 and 1593-1595/1602-1604). Two degenerate
inputs fault, with release-kernel semantics (ASSERT == WARN_ON no-op,
`sys/dev/drm/amd/display/dc/os_types.h:76`):

1. **n == 0** — the do/while writes one point (p[0]), then the three "extra"
   points are written at p[1], p[2], p[3] (color_gamma.c:1092-1106). With the
   n==0-sized allocation of 3 entries (indices 0..2), **p[3] is a 24-byte heap
   OOB write** past a 72-byte `kcalloc` object; `p_last = p + n - 1 = p - 1`
   (color_gamma.c:1078) is additionally an OOB **read** (24 bytes below the
   object) whose value feeds the three OOB writes.
2. **n == 1** — the loop's single iteration computes
   `dc_fixpt_from_fraction(0, numberof_points - 1)` = 0/0
   (color_gamma.c:1081-1082) ⇒ `complete_integer_division_u64`
   (`sys/dev/drm/amd/display/dc/basics/fixpt31_32.c:43-55`) calls
   `div64_u64_rem(dividend, 0, ...)` — plain `%`/`/` per
   `sys/dev/drm/include/linux/math64.h:75-80` — ⇒ **integer divide-by-zero**:
   SIGFPE in userspace, **#DE ⇒ kernel panic** in-kernel. The ASSERT(divisor)
   at fixpt31_32.c:50 does not prevent this in release builds.

## How it was verified

Guest: `DragonFly dfbsd 6.5-DEVELOPMENT #0 ... x86_64` (see `env.txt`).
Harness `trigger/color_gamma_harness.c`:

* compiles the **verbatim** kernel fixed-point engine
  (`trigger/fixpt31_32.c`, sha256 `bc09111d…` == kernel file) against the
  **verbatim** `include/fixed31_32.h` (sha256 `a6bdd016…`);
* contains a **verbatim** copy of `build_evenly_distributed_points`
  (color_gamma.c:1066-1107) with the exact `dividers` the callers build
  (color_gamma.c:1401-1403);
* `dm_services.h` stub reproduces release ASSERT semantics (no-op) and the
  exact `div64_u64_rem` from math64.h.

Results (4 runs at -O2, 1 at -O0 — `run.log`, `run.2.log`, `run.3.log`,
`run.O0.log`):

* T3 control n=256: monotone 0..1, extras exactly 2/3, 1/2, 2/5
  (0xaaaaaaab / 0x80000000 / 0x66666666) — proves faithful arithmetic.
* T2 n=1: **SIGFPE** caught (the 0/0 division). 5/5.
* T1 n=0: 72-byte object placed flush against a PROT_NONE guard page ⇒
  **SIGSEGV at exactly obj+72** — the p[3] extra-point write. 5/5. (The
  p[-1] read stays mapped in this layout; it is OOB by construction and is
  the read that feeds the OOB writes on a real heap.)

So the defect is *reproduced as module code executing the exact faulty
arithmetic*; `impact=none` because **no reachable kernel path exists in this
tree today**: the sole in-tree caller validates
`num_entries ∈ {256, 4096}` (amdgpu_dm_color.c:148-157, 270-277), and the
whole AMD display tree is standalone module source not wired into
`sys/conf/files` nor `X86_64_GENERIC` (no AMD GPU on the audit guest either).
Threat: latent exported-API hazard — any future caller passing a
user-controlled LUT size (legacy DRM gamma ioctl, newer amdgpu_dm ports)
converts this into a heap overflow (n==0) or panic (n==1).

## Exploit chain

None developed — the write is 24 bytes of partially-controlled fixed-point
values at `obj+72..95` on kmalloc-72-class memory, but the path is not
reachable in-tree; escalation work would be against a hypothetical caller.

## Fix validation

`fix.diff` adds (a) the missing boundary validation to both public entries
(after the pre-existing early-returns so the documented
`!mapUserRamp && num_entries==0` ROM-SRGB fast path at color_gamma.c:1584-1589
keeps its current behavior), and (b) an in-function guard
`if (numberof_points < 2) return;` replacing the ineffective ASSERT.

Validated in `trigger/fix_run.log` via `color_gamma_harness_fixed.c` (same
harness + the in-function guard): T2/T1 RETURN WITHOUT FAULT, T3 control
unchanged. `fix_baseline_reproduced=1` (faults present), 
`fix_patched_reproduced=0` (faults gone). A kernel-level rebuild was not
performed: the file is not part of any kernel config in this tree (module
source only), so a kernel build would validate nothing beyond what the
harness already shows.

## Kernel references

* sys/dev/drm/amd/display/modules/color/color_gamma.c:1066-1107 (function)
* sys/dev/drm/amd/display/modules/color/color_gamma.c:1081-1082 (0/0 division)
* sys/dev/drm/amd/display/modules/color/color_gamma.c:1092-1106 (3 extra points ⇒ OOB write at n==0)
* sys/dev/drm/amd/display/modules/color/color_gamma.c:1382-1384, 1392-1393, 1593-1595, 1602-1604 (n+3 allocations)
* sys/dev/drm/amd/display/modules/color/color_gamma.c:1407-1410, 1617-1620 (unvalidated pass-through)
* sys/dev/drm/amd/display/dc/basics/fixpt31_32.c:43-55, 67-82 (division, ASSERT no-op)
* sys/dev/drm/amd/display/dc/os_types.h:72-77 (ASSERT == WARN_ON in release)
* sys/dev/drm/include/linux/math64.h:75-80 (div64_u64_rem)
* sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c:148-157, 270-277 (only in-tree caller validates)

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