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

radeon_agp.c treats ai_aperture_size (BYTES tree-wide) as MB β€” dead <32MB guard and 2^20x-inflated gtt_size mis-programming GPU DMA-window registers; latent only (IS_ENABLED(CONFIG_AGP)==0, code not compiled on DragonFly)

Summary

The MB-unit math inherited from old Linux against DF's byte-unit source: the min-aperture guard is dead and <<20 inflates gtt_size 2^20x (64MB->64TB), inverting AIC_HI_ADDR windows and garbaging MC_AGP_TOP across 5 asic files. EMPIRICALLY PROVEN not compiled (kconfig.h lacks CONFIG_AGP; preprocessed with the module's exact flags - agp=AGP_DISABLED with positive controls). Units-demo reproduces the arithmetic. Fix the units for the day CONFIG_AGP is added.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5084 Β· 11 files
FileTypeDescriptionSize
README.md β€” 3.2 KB ↓ raw
VERDICT.md β€” 5.1 KB ↓ raw
iscfg2.c β€” 1.1 KB view raw
units_demo.c β€” 1.6 KB view raw
build.sh β€” 238 B view raw
run.sh β€” 141 B view raw
run.log β€” 2.2 KB view raw
env.txt β€” 308 B view raw
fix.diff β€” 1.3 KB view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 4.5 KB view raw

DF-5084 β€” radeon_agp.c treats ai_aperture_size (BYTES) as MB

Severity: Info (latent β€” code is not compiled on DragonFly today) File: sys/dev/drm/radeon/radeon_agp.c lines 152-157, 245 Status of bug: certain (source-level, proven); not compiled into any shipped DragonFly kernel/module build (IS_ENABLED(CONFIG_AGP) == 0).

What the bug is

sys/dev/drm/kconfig.h (the Linux kconfig shim force-included by every file in the drm/radeon build, sys/dev/drm/radeon/Makefile:148) does not define CONFIG_AGP. On this tree:

  • radeon_agp_init() compiles to #else return 0; (radeon_agp.c:256-258),
  • radeon_agp_fini() / radeon_agp_resume() compile to empty bodies,
  • the quirk table is not emitted.

Inside that dead body there is a genuine porting defect: the file was imported from an old Linux revision whose agp_info.aper_size was in MB, but DragonFly's agp_info.ai_aperture_size is in BYTES tree-wide (vm_size_t, sys/dev/agp/agpvar.h:50, filled from rman_get_size() of the aperture BAR at sys/dev/agp/agp.c:904; corroborated by sys/dev/drm/drm_agpsupport.c:500 which prints ai_aperture_size >> 20 as "…M"). Consequences if the code is ever compiled:

  • radeon_agp.c:152 ai_aperture_size < 32 intends "< 32 MB" β€” with bytes it is a dead check (an aperture is β‰₯ 4 MB = 4194304).
  • radeon_agp.c:245 ai_aperture_size << 20 intends MBβ†’bytes β€” with bytes it inflates mc.gtt_size by 2^20: a 64 MB aperture yields a 64 TB GTT (gtt_end ~2^46). Downstream 32-bit register writes truncate: r100.c:662-663 AIC_HI_ADDR (window inverts: hi < lo), r100.c:3864-3865, r300.c:1334-1335, r520.c:153-154 MC_AGP_TOP, evergreen.c:2892-2894 MC_VM_AGP_TOP/BOT, r600.c:1085-1086; and the TTM GTT heap is sized 2^20x too big (radeon_ttm.c:940-941). GATT binds beyond the real aperture are still rejected by sys/dev/agp/agp.c:520-522, so the kernel-visible impact of the wrong gtt_size is functional corruption of the GPU DMA window programming plus failed/DoS GTT allocations β€” not a direct GATT OOB write.

How to reproduce the analysis (no AGP hardware needed)

On the audit DragonFly guest (or any DF box with /usr/src):

vm.sh run_root 'cc -include /usr/src/sys/dev/drm/kconfig.h \
    -I/usr/src/sys/dev/drm/include -E /tmp/iscfg2.c | grep -v "^#" | grep -v "^$"'
cc -O2 -o /tmp/units_demo /tmp/units_demo.c && /tmp/units_demo

Expected (see run.log):

const char *agp   = "AGP_DISABLED";    <- the buggy body is NOT compiled
const char *pci   = "PCI_ENABLED";     <- control
const char *undef_ = "UNDEF_DISABLED"; <- control
const char *cik   = "CIK_ENABLED";     <- control
aper= 64M base=0xd0000000 -> gtt_size=0x0000400000000000 (64 TB) ... AIC_HI_ADDR=0xcfffffff (LO=0xd0000000 β†’ inverted)

Artifacts

  • iscfg2.c β€” preprocessor reachability proof (radeon module's exact flags)
  • units_demo.c β€” impact arithmetic demo (gtt_size / truncated registers)
  • build.sh, run.sh β€” exact commands
  • run.log β€” full decisive output; env.txt β€” guest env + md5 match vs audit tree
  • fix.diff β€” 3-line units fix (authored after verification; never applied to sys/)

Fix

Treat ai_aperture_size as bytes β€” see fix.diff.

VERDICT.md
↓ download raw

DF-5084 β€” VERDICT

Status: not_reproduced (runtime) / bug certain (source) β€” LATENT, code not compiled on DragonFly. Impact on any shipped DragonFly build: none.

1. What was audited and how the bug was found

sys/dev/drm/radeon/radeon_agp.c (285 LOC), pass 2. The AGP init path was checked acquire/release-balance, quirk-table termination, module-parameter validation, and the aperture-size β†’ mc.gtt_size math. All the conventional classes came back clean (see final JSON negative_notes), but the units of ai_aperture_size did not survive the port:

  • DF tree-wide convention is BYTES:
  • sys/dev/agp/agpvar.h:50 vm_size_t ai_aperture_size;
  • sys/dev/agp/agp.c:904 info->ai_aperture_size = rman_get_size(sc->as_aperture); (the aperture PCI BAR resource, i.e. bytes)
  • sys/dev/drm/drm_agpsupport.c:500 prints (int)(…ai_aperture_size >> 20) as "…M"
  • radeon_agp.c:152 compares it against 32 (intent: 32 MB, cf. the "(%juM) need at least 32M" message) β†’ dead guard in byte units.
  • radeon_agp.c:245 …ai_aperture_size << 20 (intent: MBβ†’bytes, inherited from old Linux aper_size which was MB) β†’ 2^20x inflation in byte units. Modern Linux uses aper_size (bytes) directly, with no shift.

2. Proof the buggy code is NOT compiled on DragonFly

sys/dev/drm/radeon/Makefile:148 force-includes sys/dev/drm/kconfig.h (which does not define CONFIG_AGP) and -I sys/dev/drm/include supplies the locally-modified IS_ENABLED() (sys/dev/drm/include/linux/kconfig.h:30-39).

On the audit guest (DragonFly 6.5-DEVELOPMENT, gcc 8.3 [DragonFly]), with the module's exact flags:

$ cc -include /usr/src/sys/dev/drm/kconfig.h -I/usr/src/sys/dev/drm/include -E /tmp/iscfg2.c
const char *agp   = "AGP_DISABLED";      <- #if IS_ENABLED(CONFIG_AGP) is FALSE
const char *pci   = "PCI_ENABLED";       <- control (kconfig.h defines it)
const char *undef_ = "UNDEF_DISABLED";   <- control
const char *cik   = "CIK_ENABLED";       <- control

Hence radeon_agp_init == { return 0; }, radeon_agp_fini == {}, radeon_agp_resume == {} in every shipped build. Guest /usr/src radeon_agp.c and kconfig.h are md5-identical to the audited tree (env.txt), so this is not a guest-tree artifact.

3. What WOULD happen if CONFIG_AGP were ever defined (impact ceiling)

units_demo (userland arithmetic identical to radeon_agp.c:245-247, output in run.log): every realistic AGP aperture yields

  • mc.gtt_size = 32–256 TB (e.g. 64 MB @ 0xd0000000 β†’ gtt_end = 0x4000cfffffff),
  • RADEON_AIC_HI_ADDR (r100.c:662-663) = low-32 of gtt_end = 0xcfffffff < AIC_LO_ADDR 0xd0000000 β†’ inverted GPU DMA window,
  • MC_AGP_TOP (r100.c:3864-3865, r300.c:1334-1335, r520.c:153-154) = 0x2000cfff-style truncated garbage,
  • TTM GTT heap sized 2^20x (radeon_ttm.c:940-941).

GATT binds past the real aperture still fail closed at sys/dev/agp/agp.c:520-522 (offset + mem->am_size > AGP_GET_APERTURE β†’ EINVAL), so the kernel-side consequence is mis-programmed GPU address-window registers + unallocatable GTT (functional DoS on AGP hardware); a chipset-mediated DMA-window corruption is speculative.

Related shipped-code note (not a separate finding): because the stub returns 0 = "success" (radeon_agp.c:256-258), on real AGP radeon hardware the ASIC init keeps RADEON_IS_AGP with mc.gtt_size == 0; the TT heap is then empty (radeon_ttm.c:940-941) and the !rdev->ddev->agp guard at radeon_ttm.c:147-160 is itself compiled out β€” the driver fails closed (no GTT objects), it does not corrupt. The guest has no AGP/ATI hardware (run.log: 7 PCI devices, QEMU std VGA only, no drm modules), so none of this is exercisable here.

4. Why verification stops here

  • Memory-corruption escalation chain: N/A β€” the code is not in the kernel; there is no primitive to groom.
  • Runtime reproduction: impossible on this guest (no AGP bridge, no radeon ASIC β€” QEMU does not emulate them) and meaningless while the code is compiled out.
  • Fix validation (fix.diff): not_testable β€” the patched lines are inside the same #if IS_ENABLED(CONFIG_AGP) and do not compile into any DF build today; a kernel build would demonstrate nothing observable. The fix is a 3-line units correction matching modern Linux and the DF AGP layer's byte convention.

5. Bottom line

File as Info / latent: a certain, tree-inconsistent units bug inherited from an old Linux port, sitting behind a config gate that DragonFly never opens. It becomes a real functional/DoS bug (and a speculative hardware-DMA-window hazard) the moment someone adds #define CONFIG_AGP 1 to sys/dev/drm/kconfig.h and boots AGP radeon hardware. One-line fix; apply fix.diff or leave documented as latent.

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: the patched lines live inside #if IS_ENABLED(CONFIG_AGP) which is 0 on DragonFly (proven this run), so baseline and patched builds compile to identical stubs - no observable behavior difference can be demonstrated on any DF build or on this guest (no AGP/radeon hardware). The fix is a 3-line units correction matching modern Linux (aper_size in bytes, no shift) and DF's own byte-unit AGP layer (drm_agpsupport.c:500).

fix.diff authored against read-only sys/ tree, never applied; reachability proof run.log:2 (AGP_DISABLED) explains why fix validation is not possible.
↓ fix.diffper-fix-DF-5084

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'agp = AGP_DISABLED' with controls pci/cik=ENABLED, undef=DISABLED (decisive: code not compiled); run.log units_demo table: 64MB@0xd0000000 -> gtt_size=0x400000000000 (64TB), AIC_HI_ADDR=0xcfffffff < AIC_LO=0xd0000000 (inverted DMA window); env.txt: guest md5 of radeon_agp.c/kconfig.h matches audit tree; VERDICT.md sections 2-4.

PoC changes

No seed PoC existed (finding discovered this pass). Built two artifacts: iscfg2.c (preprocessor reachability proof with positive/negative controls) and units_demo.c (exact radeon_agp.c:245-247 arithmetic + truncated 32-bit register values), both run on the QEMU guest with the radeon module's own -include/-I flags.

Verified recommended fix

Treat ai_aperture_size as bytes: compare against (32U<<20) at :152 (print >>20) and drop the <<20 at :245 - see fix.diff.

Verdict

Source-level units bug is CERTAIN (ai_aperture_size is BYTES tree-wide per agpvar.h:50 + agp.c:904 + drm_agpsupport.c:500, but radeon_agp.c:152 compares it to 32-as-MB and :245 shifts it <<20 as if MB), yet the entire buggy body is NOT COMPILED on DragonFly: proven on the guest with the radeon module's exact build flags (cc -include /usr/src/sys/dev/drm/kconfig.h -> IS_ENABLED(CONFIG_AGP)==0, controls PCI/CIK=1, UNDEF=0), guest /usr/src md5-identical to the audited tree. radeon_agp_init therefore compiles to { return 0; }. No runtime manifestation exists in any shipped build; the PoC demonstrates the latency (AGP_DISABLED) and, arithmetically, the 2^20x-inflated gtt_size + inverted AIC_HI_ADDR window that would result if CONFIG_AGP is ever defined. Kernel-visible impact ceiling if enabled: dead minimum-aperture guard, 64MB->64TB TTM GTT heap (radeon_ttm.c:940-941), truncated/inverted GPU DMA-window registers (r100.c:662-663, r100.c:3864-3865, r300.c:1334-1335, r520.c:153-154, evergreen.c:2892-2894) -> functional DoS on AGP radeon hardware; GATT OOB binds remain blocked by agp.c:520-522, so no direct kernel memory corruption. Filed as Info/latent.