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

Pixel-mode cursor draw functions lack the cursor_base < font_height bound the text path enforces: negative font-buffer index and negative video-memory offset

Summary

draw_pxlcursor_direct/packed/planar compute (font_height - cursor_base - 1) without validating cursor_base (unlike draw_txtcharcursor which early-returns). cursor_base >= font_height yields negative scanline offsets: char_data points before scp->font and draw_pos above va_window. cursor_base is BIOS-derived and not ioctl-settable (QEMU yields 0), so the mismatch needs firmware cursor_end <= 7 - hardening gap. Mirror-the-text-path-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4422 Β· 5 files
FileTypeDescriptionSize
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
fix.diff β€” 975 B view raw
manifest.json β€” 605 B view raw
verdict.json β€” 2.2 KB view raw

DF-4422 β€” draw_pxlcursor_{direct,packed,planar} lack the cursor_base < font_height bound the text path enforces

scvgarndr.c text-mode path validates before use (draw_txtcharcursor:236): if (scp->cursor_base >= h) return; The three pixel-mode cursor draw functions instead use scp->cursor_base unvalidated: draw_pos = VIDEO_MEMORY_POS(scp, at, ...) + (scp->font_height - scp->cursor_base - 1) * line_width; (:789-790, :841-842, :890-891) char_data = &scp->font[... + scp->font_height - scp->cursor_base - 1];

With cursor_base >= font_height (char field, syscons.h:294) both offsets go NEGATIVE: OOB READ before scp->font and OOB WRITE (writel/writeb) that many scanlines ABOVE va_window (before the framebuffer/VGA aperture mapping).

Reachability analysis (why Low/speculative): scp->cursor_base is refreshed from sc->cursor_base via update_cursor_image/sc_set_cursor_image (syscons.c:3183,3205) which clamp only with imax(0, ...) β€” an upper bound is missing β€” but sc->cursor_base itself is only ever set at scinit from the BIOS cursor shape (syscons.c:3372: font_height(16) - bios_value.cursor_end - 1, bounded < 16), and no ioctl can set it. A mismatch therefore requires sc->cursor_base >= font_height of a LATER pixel mode with a smaller font (8/14), i.e. BIOS cursor_end <= 7 at boot with a subsequent 8-line pixel mode. QEMU/SeaBIOS reports cursor_end=15 (base 0), so the standard guest cannot trigger it; real firmware varies. CONS_CURSORTYPE (SC_CHAR_CURSOR, scvgarndr.c:221) is reachable unprivileged from the console tty, which is what makes the path live at all.

Verdict: untested (Low severity hardening; trigger is firmware-dependent, not attacker-controlled on this rig). Fix mirrors the text path check.

VERDICT.md
↓ download raw

DF-4422 β€” draw_pxlcursor_{direct,packed,planar} lack the cursor_base < font_height bound the text path enforces

scvgarndr.c text-mode path validates before use (draw_txtcharcursor:236): if (scp->cursor_base >= h) return; The three pixel-mode cursor draw functions instead use scp->cursor_base unvalidated: draw_pos = VIDEO_MEMORY_POS(scp, at, ...) + (scp->font_height - scp->cursor_base - 1) * line_width; (:789-790, :841-842, :890-891) char_data = &scp->font[... + scp->font_height - scp->cursor_base - 1];

With cursor_base >= font_height (char field, syscons.h:294) both offsets go NEGATIVE: OOB READ before scp->font and OOB WRITE (writel/writeb) that many scanlines ABOVE va_window (before the framebuffer/VGA aperture mapping).

Reachability analysis (why Low/speculative): scp->cursor_base is refreshed from sc->cursor_base via update_cursor_image/sc_set_cursor_image (syscons.c:3183,3205) which clamp only with imax(0, ...) β€” an upper bound is missing β€” but sc->cursor_base itself is only ever set at scinit from the BIOS cursor shape (syscons.c:3372: font_height(16) - bios_value.cursor_end - 1, bounded < 16), and no ioctl can set it. A mismatch therefore requires sc->cursor_base >= font_height of a LATER pixel mode with a smaller font (8/14), i.e. BIOS cursor_end <= 7 at boot with a subsequent 8-line pixel mode. QEMU/SeaBIOS reports cursor_end=15 (base 0), so the standard guest cannot trigger it; real firmware varies. CONS_CURSORTYPE (SC_CHAR_CURSOR, scvgarndr.c:221) is reachable unprivileged from the console tty, which is what makes the path live at all.

Verdict: untested (Low severity hardening; trigger is firmware-dependent, not attacker-controlled on this rig). Fix mirrors the text path check.

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: precondition not constructible on this guest (BIOS-derived cursor_base = 0)

↓ fix.diffper-fix-DF-4422

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md trace; syscons.c:3183/3205 (imax(0,...) only), syscons.c:3371-3376 (BIOS-derived bound), scvgarndr.c:236 (the check the pixel paths lack).

PoC changes

n/a

Verified recommended fix

in vga_pxlcursor_direct/packed/planar: bail out when scp->cursor_base < 0 || scp->cursor_base >= scp->font_height (mirroring scvgarndr.c:236)

Verdict

untested: Low-severity missing-bound hardening gap. draw_pxlcursor_direct/packed/planar (scvgarndr.c:789-791, 841-842, 890-891) compute (font_height - cursor_base - 1) without the cursor_base < font_height validation that the text-mode path performs at :236; a stale/larger cursor_base yields a negative font-buffer index (OOB read before scp->font) and a negative va_window offset (OOB write above the framebuffer mapping). The precondition (sc->cursor_base >= pixel-mode font_height) cannot be produced on this guest: sc->cursor_base is BIOS-derived (syscons.c:3372, bounded < boot font_height 16; QEMU SeaBIOS yields 0) and no ioctl sets it; only unusual firmware cursor_end values plus a smaller-font pixel mode can trip it. SC_PIXEL_MODE and the unprivileged CONS_CURSORTYPE ioctl path are compiled in and live (verified on the guest: opt_syscons.h SC_PIXEL_MODE=1, X86_64_GENERIC).