warp_update() wrap off-by-one: star coordinate resting at exactly SCRW*SCRH is drawn and erased at vid[64000], one byte past the 64000-byte M_VGA_CG320 framebuffer (`>` must be `>=`)
Summary
The value exactly 64000 misses the > wrap - drawn+erased one byte past the logical framebuffer; 706 hits at exactly 64000 in a 100k-update model, never beyond. Benign on vga.c (64KB window, offscreen VRAM); a real OOB MMIO only against a <=64000-byte window. One-character >= fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5196 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 3.8 KB | β raw | |
| warp_model.py | β | 1.8 KB | view raw | |
| model_output.txt | β | 383 B | view raw | |
| verdict.json | β | 3.1 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw |
DF-5196 β warp_saver star wrap off-by-one: vid[SCRW*SCRH] written (1 byte past the 64000-byte mode-13h framebuffer)
File: sys/dev/misc/syscons/warp/warp_saver.c:70-77
What
warp_update() advances each star by i (1..4) and wraps with
star[k] += i;
if (star[k] > SCRW*SCRH) /* warp_saver.c:74 β `>`, must be `>=` */
star[k] -= SCRW*SCRH;
vid[star[k]] = i; /* warp_saver.c:76 */
star[k] is initialized to krandom() % (SCRW*SCRH) β [0,63999]
(warp_saver.c:121) and the arithmetic keeps it in [0,64000]: every value
greater than 64000 is wrapped, but the value exactly equal to
64000 is not. A star resting at 63996..63999 (depending on speed class
i=1..4) advances to exactly 64000 and is drawn there (vid[64000] =
i); on the next update it is erased there (vid[star[k]] = 0,
warp_saver.c:72) before wrapping normally.
Proof (deterministic, host-side)
warp_model.py in this directory is a 1:1 arithmetic model of the loop
(same constants, same statement order). Over 100,000 updates with the
same init distribution:
max index written = 64000 (framebuffer = 64000 bytes) writes at vid[64000] = 706 (byte past framebuffer) writes >= 64000 = 706 <- never beyond 64000
Also proves the companion invariant: k terminates at exactly
STARS-1 (120+60+30+15 = 225 = STARS) β star[] itself is never
indexed out of bounds, and unlike the snake saver (DF-5194) there is
no runaway: 64000 is the maximum index ever touched.
Why Info (not higher)
On the only in-tree adapter that serves M_VGA_CG320 (vga.c),
a successful mode switch points va_window at the 64 KB A0000
aperture (va_window_size = GRAPHICS_BUF_SIZE = 0x10000,
sys/dev/video/fb/vga.c:581, sys/dev/video/fb/vgareg.h:43-44).
Offset 64000 (0xFA00) therefore lands inside the mapped VGA
window, in offscreen video RAM β no kernel memory is touched, nothing
is displayed, no fault occurs. The defect is a latent bounds bug +
hardening opportunity (any future adapter exposing M_VGA_CG320 with a
64000-byte or tightly-sized window would take a real OOB MMIO write;
the value written is attacker-uncontrolled but loop-chosen: 0..4).
Fix
One character: > β >= (keeps the invariant star[k] β [0,63999];
63999+4=64003 wraps to 3 exactly as before, 64000 now wraps to 0).
Reproduce the model (host, no guest needed)
python3 warp_model.py # see model_output.txt for recorded output
Phase V (guest PoC) skipped per contract: Info hardening finding, no demonstrable kernel-memory impact on any in-tree adapter.
DF-5196 β VERDICT
Status: untested (Phase V skipped β Info hardening finding, no memory-safety impact on any in-tree adapter). Impact: none. Confidence: certain (defect proven by exhaustive arithmetic model).
The defect is real and proven
warp_update() (sys/dev/misc/syscons/warp/warp_saver.c:70-77) wraps star
coordinates with if (star[k] > SCRW*SCRH) (line 74). The loop invariant
from init (krandom() % (SCRW*SCRH) β [0,63999], line 121) plus += i
(i β [1,4]) means the coordinate after increment lies in [1,64003]. The
wrap subtracts 64000 for values >64000 but not for the value exactly
64000, so vid[star[k]] = i (line 76) writes at vid[64000] β one byte
past the 64000-byte mode-13h (M_VGA_CG320) framebuffer β and the erase
vid[star[k]] = 0 (line 72) hits the same slot on the following update.
warp_model.py (this directory) replays the exact loop 100,000 times:
max index written = 64000, hits at 64000 = 706, indices >64000 =
0. Recorded in model_output.txt. This also proves the two
companion bounds the prompt's hunt asked about: k terminates at exactly
STARS-1 (120+60+30+15 = 225 = STARS, so star[] is never indexed OOB),
and there is no runaway of the DF-5194 (snake) shape β 64000 is the
maximum index ever touched.
Why it is not exploitable on in-tree code
The only driver that implements M_VGA_CG320 is vga.c; after a successful
set_video_mode it sets va_window = 0xA0000 with va_window_size =
GRAPHICS_BUF_SIZE = 0x10000 (64 KB) β sys/dev/video/fb/vga.c:581
(update_adapter_info) and sys/dev/video/fb/vgareg.h:43-44. Offset
64000 (0xFA00) is inside that 64 KB aperture: the byte lands in
offscreen video RAM (unused in mode 13h), no kernel or host memory is
affected, nothing is displayed. The (vesa) layer, when loaded, routes
mode 13h to the same A0000/64KB window. KMS/fb consoles never reach
warp_init (their adapters fail get_mode_info(M_VGA_CG320) β ENODEV at
module load, warp_saver.c:114-117).
Severity is therefore Info: hardening / latent bounds defect. It becomes a real OOB MMIO write only for a hypothetical future adapter that advertises M_VGA_CG320 with a β€64000-byte window.
Why Phase V was skipped
Contract: Low/Info hardening findings are explicitly exempt from guest
PoC. Additionally there is no observable symptom to reproduce β the bad
byte is invisible by construction (offscreen VRAM) β and the guest does
not ship saver_warp.ko in /boot/kernel (module is built from
sys/dev/misc/syscons/warp/Makefile on demand; loading requires root
kldload, after which the loop runs on every saver tick). Guest was
inspected read-only (uname, dmesg: sc0: VGA <16 virtual consoles>,
vga0: <Generic ISA VGA> at iomem 0xa0000-0xbffff β the code path is
live on this guest's console hardware); no state was changed,
guest_dirty=0.
Fix
--- a/sys/dev/misc/syscons/warp/warp_saver.c
+++ b/sys/dev/misc/syscons/warp/warp_saver.c
@@ -71,7 +71,7 @@ warp_update(void)
vid[star[k]] = 0;
star[k] += i;
- if (star[k] > SCRW*SCRH)
+ if (star[k] >= SCRW*SCRH)
star[k] -= SCRW*SCRH;
vid[star[k]] = i;
}
Invariant after fix: star[k] β [0,63999] strictly; 63996+4=64000 now wraps to 0; all other wrap arithmetic unchanged. No fix validation build was performed (no reproduced misbehavior to differentiate; the change is provably semantics-preserving for every coordinate except exactly 64000).
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableFix validation not applicable: no misbehavior reproduces on in-tree adapters (the stray byte is invisible offscreen VRAM); the one-character change is provably semantics-preserving except at exactly 64000, which becomes 0.
See VERDICT.md 'Fix' section for the diff and invariant argument.
Confirmed kernel references
Detail
Evidence (decisive lines)
warp_model.py + model_output.txt (max index 64000, 706 writes at vid[64000], 0 beyond); source: warp_saver.c:70-77 (loop), :74 (the `>`), :121 (init bound); window size proof: sys/dev/video/fb/vga.c:581, sys/dev/video/fb/vgareg.h:43-44; guest reachability (read-only): dmesg sc0+vga0 iomem 0xa0000-0xbffff, saver_waver.ko absent from /boot/kernel (built from sys/dev/misc/syscons/warp/Makefile, kldload is root-only)
PoC changes
n/a β no kernel PoC built (Info hardening; Phase V skipped per contract). Host-side deterministic arithmetic model included instead.
Verified recommended fix
warp_saver.c:74: change star[k] > SCRW*SCRH to star[k] >= SCRW*SCRH (one char; preserves star[k] in [0,63999]).
Verdict
Off-by-one wrap in warp_update(): if (star[k] > SCRW*SCRH) (warp_saver.c:74) fails to wrap the coordinate that lands on exactly SCRW*SCRH, so a star is drawn and later erased at vid[64000] β one byte past the 64000-byte M_VGA_CG320 framebuffer. Proven by a 100,000-update arithmetic model (warp_model.py: 706 hits at vid[64000], max index 64000, never beyond; k terminates at exactly STARS-1 so star[] is never indexed OOB and there is no DF-5194-style runaway). Impact is none on in-tree code: the only M_VGA_CG320 provider (vga.c) exposes a 64KB A0000 window (va_window_size=0x10000, vga.c:581 + vgareg.h:43-44), so the byte lands in offscreen VRAM. Info hardening finding β guest PoC skipped per contract; the byte is invisible by construction.
No comments yet.