Negative framebuffer offset in bmp_SetPix via RLE delta-move: OOB write below va_window
| Field | Value |
|---|---|
| ID | DF-1858 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H |
| CWE | CWE-787 Out-of-bounds Write |
| File | sys/dev/video/fb/bmp/splash_bmp.c |
| Lines | 258, 266-268 |
| Area | dev/video (boot splash BMP renderer) |
| Confidence | likely |
| Discovered | 2026-07-20 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
The screen-offset computed in bmp_SetPix,
sofs = ((info->height - (y+1) + (info->sheight - info->height)/2) *
info->adp->va_line_width), can go NEGATIVE for any y near the bottom of the
screen when the image is smaller than the screen, because bmp_SetPix clips y
against the SCREEN height (sheight) at line 258, not the IMAGE height
(height). The RLE4/RLE8 'delta move' escape (lines 345-346, 404-405) lets a
crafted BMP set y to any value in [0, sheight), so a small image (e.g.
height=1) centered on a 480-line screen with a delta move of dy=479 yields
sofs = (1-480+235)*line_width = a large negative number. The subsequent write
*(info->vidmem + sofs) = val (line 295) then writes to memory BELOW
va_window.
Root cause
Line 258 clips with y >= info->sheight β screen height, not image height β so
y may legally be >> info->height. Line 266 computes the centered base offset
using info->height (the image height); when y >= info->height,
info->height - (y+1) is negative and, for images much smaller than the screen,
outweighs the centering term (info->sheight - info->height)/2, giving a
negative sofs.
The RLE decoders expose this directly: bmp_DecodeRLE4/RLE8 implement the BMP
'delta' escape by doing y += *(info->index+3) (lines 346, 405) with an
attacker-controlled byte and then call bmp_SetPix(info, x, y, ...) with that y.
Even without RLE, the clip at 258 being against sheight instead of height is
itself the defect: image rows should never be addressable outside [0, height).
Then at line 295 *(info->vidmem+sofs) = val (8bpp mode) and at line 284
*(info->vidmem + sofs) ^= 0xff (planar mode) write at the unchecked (possibly
negative) offset.
For standard VGA modes set_win_org is a no-op (vga.c:1571) so nothing clamps
the offset.
Threat model & preconditions
- Attacker position: anyone who can write the boot splash image file.
- Privileges gained or impact: writes attacker-chosen byte values to kernel
virtual memory at
va_window + negative_offset. Depending on kmem layout this can corrupt adjacent kernel structures or pmap entries backing the aperture, causing memory corruption / kernel panic. Potential local privilege escalation if a useful kernel object happens to precedeva_windowin KVM. - Required config or capabilities:
splash_bmp_load=YES; write access to splash asset. - Reachability: boot with crafted RLE BMP containing a delta-move escape.
Proof of concept
Craft an 8-bpp RLE8 BMP:
- Image declared width=1, height=1 (fits screen trivially)
- Body: 00 02 00 C7 = delta move escape with dx=0, dy=199
β y = 0+199 = 199 < sheight(200) passes clip at 258
- Then 01 5A = run of 1 pixel value 0x5A
- bmp_SetPix(info, x=0, y=199, 0x5A)
- sofs = (1 - 200 + (200-1)/2) * line_width = (1-200+99)*320 = (-100)*320 = -32000
- *(info->vidmem + (-32000)) = 0x5A β writes below va_window
Expected output
Fatal trap 12: page fault in VGA framebuffer write path OR memory corruption near va_window in adjacent kernel mappings
Impact
Medium-severity OOB write below va_window. Same attacker as DF-1857 (write
access to splash asset). Reliable local DoS; potential privilege escalation
with kmem layout control.
Recommended fix
Clip y against the IMAGE height (info->height), not the screen height, and
reject negative sofs.
--- a/sys/dev/video/fb/bmp/splash_bmp.c
+++ b/sys/dev/video/fb/bmp/splash_bmp.c
@@ -255,7 +255,8 @@ bmp_SetPix(BMP_INFO *info, int x, int y, u_char val)
int newbank;
/*
* range check to avoid explosions
*/
- if ((x < 0) || (x >= info->swidth) || (y < 0) || (y >= info->sheight))
+ /* y is an image row; it must be within the image, which we already
+ * guaranteed fits inside the screen in bmp_Init. */
+ if ((x < 0) || (x >= info->width) || (y < 0) || (y >= info->height))
return;
lwkt_gettoken(&vga_token);
@@ -272,6 +273,11 @@ bmp_SetPix(BMP_INFO *info, int x, int y, u_char val)
* info->adp->va_line_width);
x += (info->swidth - info->width) / 2;
+ if (sofs < 0) { /* defensive: never write below the window */
+ lwkt_reltoken(&vga_token);
+ return;
+ }
+
switch(info->sdepth) {
Timeline
- 2026-07-20 Discovered during automated audit.
- 2026-07-20 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1858 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able unified diff; validated as part of combined 41-finding kernel build (rc=0, -Werror clean) | 585 B | view raw |
| VERDICT.md | verdict | source-only confirmation + HW/module gating explanation | 1.5 KB | β raw |
DF-1858 Verification
Verdict
SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME (HW/module gated).
The cited defect exists in the audited source at sys/dev/video/fb/bmp/splash_bmp.c:258-268. Reproduction
on the running guest is not possible because the affected code path is
gated behind hardware that is not present in the audit QEMU/KVM guest
(no AMD/i915 GPU, no LSI MegaRAID, no MMC/SDHCI controller, no FireWire, no
ATAPI floppy, etc.) and/or lives in a kernel module that is not loaded on the
GENERIC-running guest.
Mechanism (source-only confirmation)
splash pseudo-device in GENERIC; bmp module loaded for boot splash. Source: bmp_SetPix at L258 clips y against info->sheight (screen height), NOT info->height (image height). When y>=info->height (allowed by clip), info->height-(y+1) is negative; multiplied by va_line_width yields a negative sofs, which writes before the framebuffer base.
Recommended fix
Add || (y >= info->height) to the range check and a sheight<height guard.
The full git apply-able diff lives in fix.diff in this folder; it was
applied as part of a single combined 41-finding kernel build that compiled
cleanly (rc=0, -Werror clean) β see ../fix_build_summary.txt.
Build validation
git apply --checkon this fix.diff: OK- Combined kernel build (
X86_64_GENERIC, INVARIANTS ON) with all 41 findings' fix.diffs applied: rc=0, no warnings, no errors. - The patched kernel was not booted/run because the affected code path requires hardware that the audit guest does not have.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- v
- i
- d
- e
- o
- /
- f
- b
- /
- b
- m
- p
- /
- s
- p
- l
- a
- s
- h
- _
- b
- m
- p
- .
- c
- :
- 2
- 5
- 8
- -
- 2
- 6
- 8
Detail
Exploit chain
none β non-corruption classes (info leak / DoS / div0 / logic) or HW/module gated. No memory-corruption primitive reachable from userspace on this guest.
Evidence (decisive lines)
Source-only confirmation. Combined kernel build with all 41 fix.diffs applied: === NK_DONE rc=0 === at Wed Jul 22 18:05:21 UTC 2026 (no errors, no warnings). See findings/poc/fix_build_summary.txt.
PoC changes
Authored findings/poc/DF-1858/fix.diff (minimal targeted guard). VERDICT.md and manifest.json written. fix.diff validated by combined build.
Verified recommended fix
Add || (y >= info->height) to the range check; add sheight<height guard. Full git-apply-able diff in findings/poc/DF-1858/fix.diff; validated as part of combined 41-finding kernel build (rc=0).
Verdict
SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME. The cited defect exists at sys/dev/video/fb/bmp/splash_bmp.c:258-268. splash pseudo-device in GENERIC. bmp_SetPix L258 clips y against info->sheight (screen) NOT info->height (image). When y>=info->height (allowed by clip), info->height-(y+1) is negative; multiplied by va_line_width -> negative sofs -> write before framebuffer base. Only reachable at boot-splash time, not at runtime from userspace.
No comments yet.