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

agp_amd64_via_init reads AGP3_VIA_ATTBASE as the RMW source for the AGP3_VIA_GARTCTRL write (register confusion, upstream-inherited)

Summary

The GARTCTRL write's preserve value is read from the adjacent ATTBASE register - the GATT physical address clobbers GARTCTRL's size/queue/enable bits. Every analogous RMW in the driver reads the register it writes; transcription slip inherited byte-identical from FreeBSD (verified 9.0 and stable/12). Boot-time config-space programming only; no memory safety. One-token fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4856 Β· 6 files
FileTypeDescriptionSize
README.md β€” 861 B ↓ raw
VERDICT.md β€” 3.1 KB ↓ raw
fix.diff β€” 503 B view raw
upstream_check.txt β€” 744 B view raw
manifest.json β€” 825 B view raw
verdict.json β€” 2.3 KB view raw

DF-4856 β€” agp_amd64_via_init programs GARTCTRL from the ATTBASE value

agp_amd64_via_init() writes the GATT base into AGP3_VIA_ATTBASE and then writes AGP3_VIA_GARTCTRL β€” but reads the source value from AGP3_VIA_ATTBASE (the GATT physical address just written), not from GARTCTRL itself:

Result: GARTCTRL is programmed to gatt->ag_physical | 0x180, clobbering whatever size/queue/translate bits the register held, instead of the intended gartctrl | 0x180 (set bit 0x180 = GART enable + TLB-entry-size field on the VIA secondary AGP3 bridge at 0:1:0).

No runtime PoC exists for a config-register programming bug on hardware the audit guest cannot present (needs a VIA K8T800-class host bridge with the 0:1:0 secondary AGP bridge). The evidence is the source itself plus the upstream comparison below.

VERDICT.md
↓ download raw

DF-4856 verdict β€” certain in source, hardware-gated at runtime

Root cause

sys/dev/agp/agp_amd64.c:454-463 (agp_amd64_via_init):

459:    agp_amd64_apbase_fixup(dev);
460:    pci_cfgregwrite(0, 1, 0, AGP3_VIA_ATTBASE, sc->gatt->ag_physical, 4);
461:    pci_cfgregwrite(0, 1, 0, AGP3_VIA_GARTCTRL,
462:        pci_cfgregread(0, 1, 0, AGP3_VIA_ATTBASE, 4) | 0x180, 4);

Line 462 reads AGP3_VIA_ATTBASE (0x98) as the preserve-mask source for the AGP3_VIA_GARTCTRL (0x90) write. Every other GARTCTRL-style RMW in this driver family reads the register it writes (compare agp_amd64.c:233-237, :306-308, :362-364). The register addresses are adjacent defines (agpreg.h:98-100: GARTCTRL 0x90, APSIZE 0x94, ATTBASE 0x98) β€” a transcription slip.

Effect: the VIA secondary bridge's GARTCTRL is loaded with sc->gatt->ag_physical | 0x180 β€” a 32-bit physical address of the GATT (e.g. 0x3f000000-ish) OR 0x180 β€” instead of its prior configuration with bits 0x180 set. GARTCTRL bits on VIA AGP3 control GART enable / queue / size; an arbitrary address value can disable the secondary GART or program nonsense sizes. On VIA K8T800/8385 boards where agp_amd64_via_match() agp_amd64.c:130-142 accepts the host bridge, AGP GART translation through the 0:1:0 bridge is misconfigured at attach. Boot-time hardware programming only; no attacker-controlled input reaches these writes; no kernel memory safety impact. Info severity.

Upstream provenance (inherited, not DF-introduced)

Verified byte-identical in FreeBSD:

DF's copy (header: $FreeBSD: src/sys/dev/agp/agp_amd64.c,v 1.17 2009/01/12) inherited the bug verbatim; upstream carried it until the file's removal. Log as divergence-with-upstream-ownership per AGENT.md contrib rules β€” but this is base sys/dev/agp code in DF's tree, so DF should fix it locally.

Why not_reproduced at runtime

Requires a VIA K8T800-class host bridge (ids 0x02041106/0x02381106/ 0x02821106/0x31881106, agp_amd64.c:104-111) plus the 0:1:0 secondary AGP bridge with AGPSEL bit clear. The QEMU audit guest presents a single i440FX host bridge (0x12378086); agp_amd64 cannot probe, so the register write never executes. There is no userspace-observable trigger for a config-space programming defect even on real hardware short of AGP not working β€” hence Info/likely (source-certain, impact reasoned not measured).

Fix

fix.diff (validated git apply --check): read GARTCTRL back from GARTCTRL:

    pci_cfgregwrite(0, 1, 0, AGP3_VIA_GARTCTRL,
-       pci_cfgregread(0, 1, 0, AGP3_VIA_ATTBASE, 4) | 0x180, 4);
+       pci_cfgregread(0, 1, 0, AGP3_VIA_GARTCTRL, 4) | 0x180, 4);

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

one-token register correction; git apply --check OK against the read-only tree; runtime validation hardware-gated

fix.diff
↓ fix.diffper-fix-DF-4856

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (root cause + upstream diff); upstream_check.txt (FreeBSD release/9.0.0 and stable/12 both carry the identical ATTBASE source read); fix.diff (git apply --check OK)

PoC changes

no PoC exists for a config-register programming defect; evidence pack is source + upstream comparison

Verified recommended fix

In agp_amd64_via_init, read the preserve value from AGP3_VIA_GARTCTRL, not AGP3_VIA_ATTBASE (see fix.diff)

Verdict

agp_amd64_via_init (agp_amd64.c:461-462) uses AGP3_VIA_ATTBASE (0x98) as the preserve source for the AGP3_VIA_GARTCTRL (0x90) read-modify-write, loading the VIA secondary AGP bridge's GARTCTRL with (gatt physical address | 0x180) and clobbering its size/queue/enable bits, instead of (gartctrl | 0x180). Every analogous RMW in the same file reads the register it writes. The bug is certain in source, inherited byte-identical from FreeBSD (verified release/9.0.0:467-469 and stable/12:464-466), and has no runtime trigger on the audit guest (needs a VIA K8T800-class host bridge + 0:1:0 secondary bridge; QEMU presents only i440FX 0x12378086, so agp_amd64 never probes). Impact ceiling: boot-time misprogramming of GART translation on ~2004 VIA K8 boards - no attacker-controlled input, no memory-safety consequence. Info.