CTB ring math trusts GuC-firmware-written descriptor fields with only compiled-out GEM_BUG_ON validation β desc->size==0 gives divide-by-zero panic, inflated size/tail gives FW-offset wild write past the 1-page CT object
Summary
All ring indices and the free-space check derive from the GuC-DMA-writable shared descriptor page, and every invariant is a GEM_BUG_ON that compiles to a no-op in production: desc->size==0 -> #DE panic; inflated size/tail with arranged head -> cmds[tail] writes at an FW-chosen offset up to ~1GiB past the 4096-byte CT object (heap wild write with partially host-controlled data). Upstream later replaced exactly these checks with real -EPROTO validation. Info because the CT path is doubly unreachable on this tree (no has_guc_ct platform + enable_guc defaults 0). Real-descriptor-validation fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4567 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| fix.diff | β | 2.2 KB | view raw | |
| verdict.json | β | 4.1 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw |
DF-4567 β intel_guc_ct.c: CTB ring math trusts GuC-firmware-written descriptor fields with only compiled-out GEM_BUG_ON validation
- File:
sys/dev/drm/i915/intel_guc_ct.c - Severity: Info (hardening / defense-in-depth β see reachability)
- Confidence: certain (the absent validation is statically provable)
What the defect is
ctb_write() (sys/dev/drm/i915/intel_guc_ct.c:317-381) and ctb_read()
(:579-630) drive the CT send/receive rings entirely off the shared
struct guc_ct_buffer_desc (intel_guc_fwif.h:382-394) that lives in a page
also written by the GuC firmware over DMA (desc->head, desc->tail,
desc->size, is_in_error, fence, status are all in FW-writable shared memory;
the page is mapped WB at intel_guc_ct.c:189-201).
Every bound/invariant on those fields is enforced only by GEM_BUG_ON /
GEM_WARN_ON, which are no-ops in production builds
(sys/dev/drm/i915/i915_gem.h:56-61 β #define GEM_BUG_ON(expr)
BUILD_BUG_ON_INVALID(expr) unless CONFIG_DRM_I915_DEBUG_GEM):
- ctb_write:332-335 β
GEM_BUG_ON(desc->size % 4),desc->head % 4,desc->tail % 4,tail >= size: all no-ops. - ctb_read:590-594 β same family plus
head >= size: no-ops.
Consequences if the FW ever writes a bad descriptor (GuC bug, FW compromise, or desc corruption from a GT reset β the reason upstream i915 later replaced exactly these GEM_BUG_ONs with real error paths):
- Divide-by-zero panic:
tail = (tail + 1) % sizeat intel_guc_ct.c:366/369/373 andhead = (head + 1) % sizeat :608/:624 βsize = desc->size / 4;desc->size == 0β% 0β #DE β kernel panic. - Wild OOB write: the ENOSPC space check (:347) and every ring index in
ctb_write are computed from FW-written
desc->head/tail/size(:324-326). A descriptor with inflatedsize/tail(andheadarranged sousedlooks small) makes the check pass andcmds[tail] = header(:365-374) land at an FW-chosen offset up to ~1 GiB past the 1-page CT object β kernel heap corruption with partially host-controlled data (header dword, fence, action payload). - Space-check bypass / stream corruption on the RECV side for shrunk or
wrapped
size/tail(mis-countedavailable, :597-605).
Why it is only Info on this tree (reachability β verified)
The entire CT path is dead code in this tree, twice over:
i915_modparams.enable_gucdefaults to 0 (i915_params.h:49param(int, enable_guc, 0));intel_uc_init_hw()returns early unlessUSES_GUC()(intel_uc.c:44-48, :342-348), andenable_gucis a root-only loader tunable (mode 0400, i915_params.c:153-156).- Even with
enable_guc=1/3,guc_enable_communication()only callsintel_guc_ct_enable()whenHAS_GUC_CT(i915)(intel_uc.c:218-219) β and no platform in this tree ever setsinfo.has_guc_ct. The field is declared in the device-info FUNC list (intel_device_info.h:94) buti915_pci.conly ever sets.has_guc = 1(i915_pci.c:469, :520);rg has_guc_ctacross sys/dev/drm finds no other assignment. Sointel_guc_ct_enable(),ctb_write(),ctb_read(),intel_guc_send_ct()are never called on this tree, on any hardware.
The file is compiled into the i915 module (sys/conf/files:2453).
PoC status
untested β no runtime PoC is possible or meaningful:
- the QEMU guest has bochs std-VGA (no Intel dGPU β no i915 GuC at all);
- statically, the code path cannot be selected on this tree (see above);
- triggering the defect additionally requires the signed GuC firmware to
write an out-of-contract descriptor, which no unprivileged input can
induce.
Fix
Validate the FW-written descriptor fields for real in ctb_write/ctb_read
(replace the no-op GEM_BUG_ONs with if (...) return -EPROTO;), as upstream
i915 did when it hardened the CT code. See fix.diff in this directory
(authored against the read-only sys/ tree; never applied).
DF-4567 β VERDICT
Status: untested Β· impact: none Β· severity Info (hardening) Β· defect confidence: certain
What was verified, and how
This is a static verdict. The finding was proven at the source level, line by line; no dynamic reproduction exists, and none is possible on this audit guest. Three independent claims were verified:
1. The descriptor trust is real (defect confirmed)
ctb_write (sys/dev/drm/i915/intel_guc_ct.c:317) and ctb_read
(intel_guc_ct.c:579) take head, tail, size straight from
ctb->desc (intel_guc_ct.c:324-326, :582-584) β the shared
guc_ct_buffer_desc (intel_guc_fwif.h:382-394) that the GuC firmware
updates by DMA ("head: offset updated by GuC", "tail: offset updated by
owner" β both live in the same WB-mapped page pinned at intel_guc_ct.c:189).
The only validation is:
GEM_BUG_ON(desc->size % 4); /* intel_guc_ct.c:332-335, 590-594 */
GEM_BUG_ON(desc->head % 4);
GEM_BUG_ON(desc->tail % 4);
GEM_BUG_ON(tail >= size);
and on this tree GEM_BUG_ON compiles to BUILD_BUG_ON_INVALID(expr)
(sys/dev/drm/i915/i915_gem.h:60) unless CONFIG_DRM_I915_DEBUG_GEM
(i915_gem.h:38) β i.e. nothing is checked in the production kernel.
Arithmetic consequences:
desc->size == 0(FW write) βsize = 0β% sizeat intel_guc_ct.c:366/369/373 (ctb_write) and :608/:624 (ctb_read) β integer divide-by-zero β kernel panic.- Inflated
desc->size/desc->tailwithdesc->headchosen soused + len + 1 < sizepasses at intel_guc_ct.c:347 βcmds[tail]writes at intel_guc_ct.c:365-374 execute with an FW-chosen index (up tosize-1dwords pastcmds, size up to 0x3FFFFFFF) β wild kernel-memory write past the 1-page (4096-byte) CT object (layout at intel_guc_ct.c:158-178). Data written is partially host-controlled (header/fence/action payload). - Shrunk/wrapped values on the RECV side mis-derive
available(intel_guc_ct.c:597-605) β stream confusion, bounded reads (indexes are masked by% size), plus the same% 0panic.
2. Reachability on this tree: none (verified twice over)
enable_gucdefault 0: sys/dev/drm/i915/i915_params.h:49 (param(int, enable_guc, 0)); help text "0=disable [default]" (i915_params.c:153-156).intel_uc_init_hwbails unlessUSES_GUC()(intel_uc.c:342-348; predicate intel_uc.h:44-48).- Even with GuC enabled, CT is only selected by
HAS_GUC_CT(i915)(intel_uc.c:218-219) =info.has_guc_ct(i915_drv.h:2665). That flag is declared in intel_device_info.h:94 and set by no platform: grep across sys/dev/drm finds only the declaration, the accessor, and.has_guc = 1in i915_pci.c:469/:520 (GEN9_FEATURES, i915_pci.c:463-474). Sointel_guc_ct_enable()(intel_guc_ct.c:861) is unreachable code on this tree; MMIO mode is always chosen.
The file is compiled (sys/conf/files:2453) but inert.
3. Guest PoC feasibility: none
Guest video hardware is QEMU bochs std-VGA (vgapci0@pci0:0:2.0:
chip=0x11111234), so no i915 device binds at all; and triggering the defect
requires the signed GuC firmware to write an out-of-contract descriptor β
not inducible by unprivileged guest input even on real Intel hardware.
Bottom line
Real robustness/hardening defect (the exact class upstream i915 later fixed
by replacing these GEM_BUG_ONs with -EPROTO validation), but on this tree it
is doubly unreachable (no platform sets has_guc_ct; enable_guc defaults 0,
root-only) and requires GuC-firmware misbehavior. Filed as Info hardening
for upstream contribution. No PoC run: untested, impact: none,
fix_status: not_testable.
fix.diff
Included (authored against read-only sys/, never applied): converts the
ctb_write/ctb_read GEM_BUG_ONs into real descriptor validation returning
-EPROTO before any divide or index write.
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_testablenot_testable: the vulnerable path is unreachable on this tree (no platform sets has_guc_ct; enable_guc default 0) and the guest has no Intel GPU, so baseline bad behavior cannot be produced to compare against a patched kernel; the diff is line-accurate against the read-only sys/ tree and git apply --check passes.
findings/poc/DF-4567/fix.diff (git apply --check clean); VERDICT.md section 'fix.diff'.
Confirmed kernel references
- sys/dev/drm/i915/intel_guc_ct.c:317
- sys/dev/drm/i915/intel_guc_ct.c:324
- sys/dev/drm/i915/intel_guc_ct.c:332
- sys/dev/drm/i915/intel_guc_ct.c:347
- sys/dev/drm/i915/intel_guc_ct.c:365
- sys/dev/drm/i915/intel_guc_ct.c:366
- sys/dev/drm/i915/intel_guc_ct.c:373
- sys/dev/drm/i915/intel_guc_ct.c:579
- sys/dev/drm/i915/intel_guc_ct.c:590
- sys/dev/drm/i915/intel_guc_ct.c:608
- sys/dev/drm/i915/intel_guc_ct.c:624
- sys/dev/drm/i915/intel_guc_fwif.h:382
- sys/dev/drm/i915/i915_gem.h:56
- sys/dev/drm/i915/intel_uc.c:218
- sys/dev/drm/i915/i915_pci.c:469
- sys/dev/drm/i915/i915_params.h:49
Detail
Evidence (decisive lines)
findings/poc/DF-4567/VERDICT.md (full line-cited narrative); decisive lines: intel_guc_ct.c:324-335 (unchecked desc math + no-op GEM_BUG_ON), :347 (ENOSPC check on FW data), :365-373 (cmds[tail] write + % size divide), :582-594, :608/:624; i915_gem.h:60 (GEM_BUG_ON no-op); i915_pci.c:469 (has_guc only); intel_uc.c:218-223; i915_params.h:49. fix.diff applies clean (git apply --check verified).
PoC changes
none β no PoC source exists; the finding is a hardening defect on a path that cannot be selected on this tree (no platform sets has_guc_ct; enable_guc defaults 0). Runtime trigger additionally requires the signed GuC firmware to write an out-of-contract descriptor.
Verified recommended fix
Replace the compiled-out GEM_BUG_ON descriptor checks in ctb_write/ctb_read with real validation of desc->size (== PAGE_SIZE/4 as allocated), desc->head/tail alignment and < size, returning -EPROTO before any divide or ring write (see fix.diff).
Verdict
Static verdict, defect proven at source level: ctb_write/ctb_read (sys/dev/drm/i915/intel_guc_ct.c:317-381,579-630) perform all ring arithmetic on GuC-firmware-writable descriptor fields (desc->size/head/tail, intel_guc_fwif.h:382-394) validated only by GEM_BUG_ON, which compiles to a no-op in production (i915_gem.h:56-61). desc->size==0 gives a divide-by-zero panic at intel_guc_ct.c:366/369/373 and :608/:624; inflated size/tail with suitable head passes the ENOSPC check (:347) and turns cmds[tail] writes (:365-374) into a wild kernel write up to ~1GiB past the 1-page CT object. However the whole CT path is unreachable on this tree twice over: no platform ever sets info.has_guc_ct (declared intel_device_info.h:94; i915_pci.c only sets .has_guc at :469/:520) so guc_enable_communication always picks MMIO mode (intel_uc.c:218-223), and enable_guc defaults to 0 as a root-only tunable (i915_params.h:49). Filed as Info hardening; no runtime PoC exists or is possible (guest has bochs std-VGA, no i915 device, and triggering requires signed-GuC-firmware misbehavior).
No comments yet.