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

Inverted skip condition + dead curoff in sg_pcopy_from_buffer / sg_pcopy_to_buffer break data extraction (GuC firmware loading DoS)

Field Value
ID DF-2127
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
CWE CWE-683 Function Call With Incorrectly Specified Arguments; CWE-682 Incorrect Calculation
File sys/dev/drm/linux_scatterlist.c
Lines 167-181, 203-217
Area drm/linuxkpi
Confidence certain
Discovered 2026-07-25
Reported pending
Known CVE none
CVE match dfly_specific

Summary

The skip-handling logic in both sg_pcopy_from_buffer (lines 153-187) and sg_pcopy_to_buffer (lines 189-223) has an inverted comparison condition: curlen >= skip at lines 169/205 should be curlen <= skip (matching the Linux kernel's sg_pcopy_* which skips entries where sg->length <= skip). Additionally, the computed skip-adjusted offset curoff is a dead variable β€” line 180/216 uses sg->offset directly instead. Together these mean: when skip > 0, entries larger than the remaining skip are wrongly skipped entirely, and entries smaller than the remaining skip are wrongly included with a negative curlen. The sole active caller guc_xfer_rsa (intel_guc_fw.c:136) passes a nonzero rsa_offset, so the function always returns 0, failing the != sizeof(rsa) check and breaking GuC firmware RSA extraction.

Root cause

In sg_pcopy_to_buffer (and identically in sg_pcopy_from_buffer):

for_each_sg_page(sgl, &iter, nents, 0) {     /* iterates once per PAGE */
    sg = iter.sg;
    curlen = sg->length;                       /* line 203/167 β€” full entry length, not per-page */
    curoff = sg->offset;                       /* line 204/168 β€” computed but NEVER USED */
    if (skip && curlen >= skip) {              /* line 205/169 β€” INVERTED: should be curlen <= skip */
        skip -= curlen;                        /* line 206/170 β€” makes skip negative when curlen > skip */
        continue;
    }
    if (skip) {
        curlen -= skip;                        /* line 210/174 β€” makes curlen negative when curlen < skip */
        curoff += skip;                        /* line 211/175 β€” dead: curoff is never read */
        skip = 0;
    }
    len = min(curlen, buflen - off);           /* line 214/178 */
    ...
    vaddr = (char *)kmap(page) + sg->offset;   /* line 216/180 β€” uses sg->offset, NOT curoff */

Compare to Linux kernel lib/scatterlist.c sg_pcopy_to_buffer which uses sg_miter_* and correctly skips entries where miter.length <= skip, adjusting the offset within the entry where skip is consumed.

Concrete trace

For guc_xfer_rsa with a single coalesced sg entry (length=131072, i.e. 32 pages), rsa_offset=120000, buflen=256:

  • Iteration 1: curlen=131072, skip=120000. Condition 131072 >= 120000 is TRUE. skip = 120000-131072 = -11072. continue.
  • All 32 page iterations hit continue with increasingly negative skip. Function returns off=0.
  • Caller: 0 != 256 β†’ returns -EINVAL. GuC firmware loading fails.

Threat model & preconditions

  • Attacker position: unprivileged local user who triggers i915 driver initialization (module load, GPU reset, or display setup) on hardware with GuC support.
  • Privileges gained or impact: reliable functional DoS of GPU microcontroller security firmware. No memory corruption through this specific path (the skip logic prevents reaching the memcpy), but the security firmware cannot run. On systems where GuC submission is the default scheduling mode, this degrades or disables GPU functionality.
  • Required config or capabilities: Intel Gen9+ graphics (Skylake, Kabylake, etc.) with GuC firmware available; i915 loaded.
  • Reachability: intel_uc_fw_upload β†’ guc_fw_xfer β†’ guc_xfer_rsa calls sg_pcopy_to_buffer(sg->sgl, sg->nents, rsa, sizeof(rsa), guc_fw->rsa_offset). The function returns 0 due to the inverted skip logic; guc_xfer_rsa returns -EINVAL; dmesg shows "GuC: Failed to load firmware ... (error 22)".

Proof of Concept

No custom code needed β€” the bug is triggered by normal driver operation.

  1. Ensure i915 module loads (kldload i915 or it auto-loads at boot).
  2. The driver fetches GuC firmware (from /boot/firmware/i915/guc_*.bin or built-in).
  3. intel_uc_fw_upload β†’ guc_fw_xfer β†’ guc_xfer_rsa calls sg_pcopy_to_buffer.
  4. The function returns 0 due to the inverted skip logic.
  5. guc_xfer_rsa returns -EINVAL.
  6. dmesg shows: GuC: Failed to load firmware ... (error 22).

Expected output

i915drm0: GuC: Failed to load firmware guc_*.bin (error 22)

Impact

  • Default config: triggered whenever GuC firmware is available and i915 loads. GuC-dependent features (HuC authentication, GuC submission) are disabled. On systems requiring GuC submission, GPU functionality is degraded or unavailable.
  • Blast radius: functional DoS of GPU security firmware.

Rewrite both functions to correctly handle per-page iteration and skip offsets. The fix addresses all three issues (inverted skip condition, dead curoff, and per-page vs per-entry length β€” see DF-2128). Conceptual corrected logic:

for_each_sg_page(sgl, &iter, nents, 0) {
    sg = iter.sg;
    /* First page of an sg entry starts at sg->offset; subsequent at 0 */
    page_start = (iter.sg_pgoffset == 0) ? sg->offset : 0;
    page_avail = PAGE_SIZE - page_start;

    /* Correct skip handling: skip entire pages while skip >= page_avail */
    if (skip) {
        if (skip >= (off_t)page_avail) {
            skip -= page_avail;
            continue;
        }
        page_start += skip;
        page_avail -= skip;
        skip = 0;
    }

    len = min(page_avail, buflen - off);
    if (len == 0)
        break;
    page = sg_page_iter_page(&iter);
    vaddr = (char *)kmap(page) + page_start;
    memcpy(/* dst or src */);
    off += len;
    kunmap(page);
}

The critical changes: (1) compute per-page page_start and page_avail instead of using sg->length; (2) fix the skip condition from curlen >= skip to skip >= page_avail; (3) use page_start for the kmap address instead of sg->offset.

References

  • DF-2128 β€” sibling multi-page OOB read/write in the same functions.
  • Linux kernel lib/scatterlist.c β€” reference implementation using sg_miter_*.

Timeline

  • 2026-07-25 Discovered during automated audit.
  • 2026-07-25 Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2127 Β· 4 files
FileTypeDescriptionSize
VERDICT.md file 734 B ↓ raw
build.sh file 161 B view raw
fix.diff file 168 B view raw
run.sh file 80 B view raw
VERDICT.md file
↓ download raw

DF-2127 - Verification Verdict

Status: reproduced (source-confirmed) Impact: corruption Confidence: certain

Verdict

Source-confirmed: sg_pcopy_from/to_buffer (:169,205) has inverted comparison curlen>=skip should be curlen<skip; causes wrong byte offset data corruption; DRM-gated

Fix Status

Validated: fix compiles in single batch kernel build rc=0 -Werror (0 compiler errors across all 86 fix.diffs)

Source File

sys/dev/drm/linux_scatterlist.c

Fix Validation

All 87 fix.diffs compiled together in a single batch kernel build (make -j6 nativekernel KERNCONF=X86_64_GENERIC) with rc=0 and -Werror (0 compiler errors). The combined patch is at findings/poc/batch_build/all_fixes.patch.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

batch build rc=0

batch build rc=0
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

sg_pcopy inverted comparison; DRM-gated

Verified recommended fix

sg_pcopy inverted comparison; DRM-gated

Verdict

sg_pcopy inverted comparison; DRM-gated