# DF-2224 — `seq_printf()` forwards `__va_list` into `ksnprintf` instead of `kvsnprintf`

**Status: REPRODUCED (verbatim-code, kernel-compiled rig)** — demonstrated
primitive: every conversion garbled + `%p` prints a **kernel stack address**
(KASLR-defeating leak flavor) into the seq buffer. **LATENT in the stock tree**
(CONFIG_DEBUG_FS undefined → i915 show callbacks compiled out;
`drm_dp_downstream_debug` has zero in-tree callers). **Fix: A/B-validated in the
same harness kernel** (`fix.diff`, one token: `ksnprintf` → `kvsnprintf`).

Part of the DF-22X batch (DF-2216 / DF-2220 / DF-2223 / DF-2224) verified in ONE
harness kernel; this pack documents the DF-2224 section.

## What this pack contains

Same layout as the DF-2216 pack; finding-specific: `fix.diff` (against
`sys/dev/drm/linux_printf.c`), `run_section.log` (DF-2224 lines).

## How to reproduce

Identical to the DF-2216 pack (same batch build + run).

## Provenance

1. guest tree `linux_printf.c` sha256 `a0570e25...` == audited tree;
   `seq_printf` compiled into the kernel **verbatim**.
2. `seq_printf_fixed` = same bytes + `fix.diff`, def renamed
   (`fixed_printf.c` sha256 `553ca5b7...`).
3. Each case calls baseline and fixed back-to-back with **identical variadic
   arguments** into separate 64-byte buffers bracketed by 32-byte `0xCC`
   canary zones — exactly how production callers invoke the function (direct
   variadic calls; a va_list trampoline would have changed the register setup).

## Expected output (baseline = unmodified production code)

```
DF2224 BASELINE [int fmt ...] buf=[402699856/48879/5f5e100] canaries=INTACT ...
DF2224 FIXED    [int fmt ...] buf=[111/222222/beef] canaries=INTACT ...
DF2224 BASELINE [%p of 0xdeadbeef ...] buf=[ptr=0xfffff8011800b650] canaries=INTACT ...
DF2224 FIXED    [%p of 0xdeadbeef] buf=[ptr=0xdeadbeef] canaries=INTACT ...
DF2224 witness: rig kernel stack address for comparison: 0xfffff8011800b6ec ...
DF2224 BASELINE [%s of hi] buf=[s=[^P]] canaries=INTACT ...
DF2224 FIXED    [%s of hi] buf=[s=[hi]] canaries=INTACT ...
```

## What the baseline output proves (x86-64 SysV ABI)

`__va_list` is an array of one struct that decays to a pointer; passing it as
`ksnprintf`'s 4th C argument puts **the pointer itself** into ksnprintf's first
variadic slot (rcx):

- `%d` (first conversion) prints the low 32 bits of `&ap` — a kernel stack
  address (`402699856` = 0x17FFB7F0-ish; run-dependent with the stack address).
- Subsequent conversions read stale registers: `48879` (0xbeef) is the
  caller's *third* argument arriving in the *second* slot via leftover r8;
  `5f5e100` is stale r9 junk. The intended `111`/`222222` never print.
- `%p` prints the actual `&ap` kernel stack address — `0xfffff8011800b650`,
  just **0x9c bytes** from the rig's witness frame (`0xfffff8011800b6ec`).
  Across 3 runs the printed value is always a canonical kernel stack address
  in the same thread stack (0xfffff801166db650 / 0xfffff8011800b650 /
  0xfffff80118005650) — the leak-flavor primitive, stable in shape.
- `%s` consumes `&ap` as a `char *`; the va_list struct begins with
  `gp_offset = 16` (two named GP args before the varargs), so it prints the
  single byte `0x10` (`^P`) — a deterministic ABI artifact, not `hi`.
- Canary zones INTACT in every case: both `ksnprintf` and `kvsnprintf`
  bound-respect; the bug is a forwarding/garbage bug, not an overflow.

## Attacker input / reachability (honest: LATENT)

`seq_printf` callers compiled in the stock tree: the i915 debugfs show
callbacks (`i915_gem_gtt.c:1540+`, gen8/gen6 dump) are all inside
`#if defined(CONFIG_DEBUG_FS)` and `kconfig.h` does not define it;
`drm_dp_downstream_debug` (`drm_dp_helper.c:586-662`, EXPORT_SYMBOL) has zero
in-tree callers. So today there is **no runtime trigger on a stock system** —
the defect, its garbage output, and its kernel-stack-address disclosure are
proven through the verbatim function; any future port enabling debugfs-style
callers would light it up. No write primitive → no privesc route; severity
stays Low.
