β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1858

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 precede va_window in 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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1858 Β· 2 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-only confirmation + HW/module gating explanation
↓ download 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.

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 --check on 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

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.