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. Condition131072 >= 120000is TRUE.skip = 120000-131072 = -11072.continue. - All 32 page iterations hit
continuewith increasingly negative skip. Function returnsoff=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;
i915loaded. - Reachability:
intel_uc_fw_upload β guc_fw_xfer β guc_xfer_rsacallssg_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_rsareturns-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.
- Ensure
i915module loads (kldload i915or it auto-loads at boot). - The driver fetches GuC firmware (from
/boot/firmware/i915/guc_*.binor built-in). intel_uc_fw_upload β guc_fw_xfer β guc_xfer_rsacallssg_pcopy_to_buffer.- The function returns 0 due to the inverted skip logic.
guc_xfer_rsareturns-EINVAL.- 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
i915loads. 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.
Recommended fix
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 usingsg_miter_*.
Timeline
- 2026-07-25 Discovered during automated audit.
- 2026-07-25 Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2127 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
fixedbatch build rc=0
batch 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
No comments yet.