β¬’ 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.9 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.

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.

Confirmed kernel references

Detail

Exploit chain

none - refuted at runtime (instrumented guest run + /dev/mem low-1MB pattern scan, guest stayed up)

Evidence (decisive lines)

findings/poc/DF-4769/ (instrumented run log, dmesg trace, pattern scan)

Verdict

RETROACTIVE IMPACT CORRECTION (pass-2 splash_bmp re-audit, negative-evidence pack findings/poc/DF-4769/): the RLE-delta negative sofs that DF-1858 claimed writes below va_window is neutralized by the unsigned modulo at splash_bmp.c:294 (sofs %= va_window_size with size_t va_window_size, fbio.h:273) - a negative sofs converts to an in-window non-negative remainder (live instrumented run on the audit guest: sofs=-320 -> +65216, write inside the 64K window; negative biWidth centering overflow likewise lands in-window at +31840). bmp_SetPix cannot write below/above va_window for ANY sofs on this path; realistic ceiling is cosmetic wrong pixels. Downgrading severity Medium->Low; the file's only real memory-safety issue remains the read-side DF-1857.