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

Legacy drm_mmap: page/byte hash-key mismatch, user-token double-count in phys math, and PFN/byte return-convention inconsistency β€” three latent memory-safety-relevant defects in a d_mmap handler unreachable on every shipped driver

Summary

The lookup uses the raw byte offset against page-number keys; phys math adds the absolute file offset instead of offset-within-map; the DMA branch returns bytes where the device pager wants a PFN; no map->size bound. All latent: vm_mmap only falls back to d_mmap when d_mmap_single returns ENODEV and every shipped driver is GEM/TTM (never ENODEV). Materially downgrades DF-2124/DF-2126 to dead-path; DF-2125's fictitious helpers remain live. Delete-the-dead-handler fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5122 Β· 4 files
FileTypeDescriptionSize
README.md β€” 4.5 KB ↓ raw
VERDICT.md β€” 5.1 KB ↓ raw
env.txt β€” 416 B view raw
verdict.json β€” 3.8 KB view raw

DF-5122 β€” Legacy drm_mmap path: page/byte key mismatch + token double-count + PFN/byte inconsistency β€” latent defects in a path unreachable on all shipped drivers

Severity: Info (hardening / triage-correction) Β· Confidence: certain (all claims verified by source citation; no runtime trigger exists on this or any stock system)

What this is

A pass-2 structural finding on sys/dev/drm/drm_vm.c. It documents that the legacy d_mmap handler drm_mmap() contains three additional correctness defects beyond the known trio (DF-2124/2125/2126), and that the entire handler is unreachable for every DRM driver DragonFly ships β€” which materially informs triage of DF-2124 and DF-2126 (they live in dead code), while DF-2125 (the fictitious helpers) is reachable at runtime via ttm_bo_vm.c:667 / i915_gem.c:2391.

The three defects (all in drm_mmap, drm_vm.c:44-139)

  1. Hash-key scale mismatch β€” drm_vm.c:91 looks up dev->map_hash with the raw byte mmap offset, but keys are inserted and removed as page numbers (drm_bufs.c:98 hash->key = user_token >> PAGE_SHIFT, drm_bufs.c:511-512). A lookup can therefore only succeed by numeric coincidence (byte offset == someone's page-number key). For a normal client that mmaps at the token it was given (user_token, bytes), the lookup always misses β†’ -EINVAL.

  2. Token double-count in the physical address β€” on a coincidental hit, drm_vm.c:121 computes phys = map->offset + offset and :130 vtophys((char *)map->handle + offset) where offset is the absolute file offset (== the token), not the offset within the map. The correct arithmetic is map->offset + (offset - user_token). As written, a hit at token T maps map->offset + T (and for _DRM_SHM, vtophys of handle + T, an arbitrary kernel-virtual expression).

  3. Return-convention inconsistency β€” the DMA branch returns a raw byte address (drm_vm.c:74 ap->a_result = phys), while the map branch returns a PFN (:137 ap->a_result = atop(phys)). The consumer, old_dev_pager_fault() (sys/vm/device_pager.c:360), feeds the result to pmap_phys_address(), which on pc64 is ptob(ppn) (sys/platform/pc64/x86_64/pmap.c:5985-5989) β€” i.e. the PFN convention is required; the DMA branch is off by 4096Γ— (this is the same family as DF-2126 but a distinct defect: DF-2126 is about pagelist contents, this is about the missing atop()). Also note Linux's original drm_mmap bounds the mapping against map->size; the DFly port has no equivalent (the per-page protocol of old_dev_pager_ctor, device_pager.c:329-332, is the only bound).

Why the path is unreachable (the reachability proof)

  • vm_mmap() dispatches a VCHR mmap first through d_mmap_single (sys/vm/vm_mmap.c:1372-1378) and only falls back to the old device pager (d_mmap) when that returns ENODEV (vm_mmap.c:1380-1386).
  • drm_mmap_single (sys/dev/drm/drm_drv.c:1256-1274) returns ENODEV only when dev->drm_ttm_bdev == NULL and the driver lacks DRIVER_GEM.
  • Every DRM driver shipped in sys/dev/drm/ sets DRIVER_GEM (i915: i915_drv.c:3284; radeon: radeon_drv.c:671; amdgpu: amdgpu_drv.c:1304), and radeon/amdgpu additionally set drm_ttm_bdev (radeon_ttm.c:913, amdgpu_ttm.c:1796).
  • Therefore drm_mmap() (.d_mmap = drm_mmap, drm_drv.c:1154) can never be invoked on a stock system; the only callers of dev_dmmap for the DRM cdev are the fallback path that cannot be taken.

Contrast: the fictitious helpers in the same file (vm_phys_fictitious_reg_range / to_vm_page / unreg_range, drm_vm.c:141-227) are hot: registered at GPU attach (i915_gem_gtt.c:2968, radeon_device.c:1493, amdgpu_device.c:2648) and consulted on every GPU VM fault (ttm_bo_vm.c:667, i915_gem.c:2391). DF-2125 applies to those and remains a live finding.

Reproduce (static)

No runtime reproduction exists or is possible on the QEMU guest: it has no Intel/AMD GPU (only the QEMU bochs display, env.txt), so the DRM core never attaches and /dev/dri does not exist. The verification for this finding is the source-level reachability proof above; see VERDICT.md.

Preferred: delete the dead handler (drop .d_mmap = drm_mmap from drm_cdevsw and the drm_mmap body) β€” no shipped driver can reach it. If a legacy (non-GEM/non-TTM) driver is ever ported, the key scale, token rebasing and atop() must all be fixed together (see fix.diff for the corrected arithmetic in advisory form).

VERDICT.md
↓ download raw

VERDICT β€” DF-5122

status: untested (runtime verification not possible: guest has no DRM hardware) reproduced: false Β· impact: none Β· confidence: certain (static proof)

Narrative

This is a static, structural finding: drm_mmap() in sys/dev/drm/drm_vm.c:44-139 is dead code on every stock DragonFly system, and it carries three latent defects that would be memory-safety-relevant if the path were ever re-enabled.

Step 1 β€” who calls drm_mmap, and with what offset?

drm_mmap is wired only as the cdevsw .d_mmap hook (drm_drv.c:1154). On DragonFly, d_mmap is invoked exclusively through the old device pager:

  • old_dev_pager_ctor (sys/vm/device_pager.c:329-332) β€” at mmap() time, walks every page offset of the requested mapping (foff, foff+PAGE_SIZE, …) and requires each dev_dmmap() call to succeed;
  • old_dev_pager_fault (device_pager.c:360) β€” at fault time, per page.

But the old device pager is only used for a cdev when vm_mmap()'s d_mmap_single dispatch returns ENODEV (sys/vm/vm_mmap.c:1372-1386). drm_mmap_single (drm_drv.c:1256-1274) returns ENODEV only for a driver with neither TTM (drm_ttm_bdev) nor DRIVER_GEM. All shipped drivers satisfy one of the two:

driver evidence
i915 DRIVER_GEM at i915_drv.c:3284 (no drm_ttm_bdev assignment anywhere in i915/)
radeon DRIVER_GEM at radeon_drv.c:671; drm_ttm_bdev set at radeon_ttm.c:913
amdgpu DRIVER_GEM at amdgpu_drv.c:1304; drm_ttm_bdev set at amdgpu_ttm.c:1796

Conclusion: drm_mmap() is unreachable on stock systems. DF-2124 (the NULL filp->private_data panic) and DF-2126 (pagelist KVA-as-PFN) both live inside this dead handler β€” their real-world severity ceiling is therefore lower than a live-path bug; they are correctness debt, not attack surface, today. (Any future port of a DRI1-era non-GEM driver would resurrect all of it, which is why deletion is recommended.)

Step 2 β€” the three latent defects (proven by citation)

  1. Key scale mismatch. Insert: hash->key = user_token >> PAGE_SHIFT (drm_bufs.c:98); remove: drm_ht_remove_key(..., user_token >> PAGE_SHIFT) (drm_bufs.c:511-512); lookup: drm_ht_find_item(&dev-> map_hash, offset, ...) with offset = raw byte file offset (drm_vm.c:91, ap->a_offset byte-accumulated in device_pager.c:329/360). drm_ht_find_key compares keys for full equality (drm_hashtab.c:83) β€” a hit requires the byte offset to numerically equal a page-number key. A well-behaved client (mmap at user_token) therefore always gets -EINVAL; only a client that mmaps at a page-number-key value can hit, and then:
  2. Token double-count. drm_vm.c:121 phys = map->offset + offset and :130 vtophys((char *)map->handle + offset) add the absolute mmap offset to the map base instead of the offset within the map (offset - user_token). On the only reachable hit shape, the mapped physical page is map->offset + K where K is the (page-scaled) key β€” an attacker-influenced page beyond the map base, not the map's first page; for _DRM_SHM the vtophys() argument is handle + K, an essentially arbitrary kernel-virtual address.
  3. PFN/byte inconsistency. device_pager.c:360 interprets the d_mmap result through pmap_phys_address() = ptob(ppn) (pmap.c:5985-5989), i.e. a PFN is required. The map branch complies (atop(phys), drm_vm.c:137); the DMA branch does not (ap->a_result = phys, :74) β€” off by 4096Γ—, a sibling of (but distinct from) DF-2126's pagelist-contents confusion.

Also absent: any map->size bound on the mapping (Linux's original drm_mmap checks the mapping length against map->size); the only bound is the per-page ctor loop.

Step 3 β€” what is live in this file

The fictitious-segment helpers (drm_vm.c:141-227) are live at runtime on GPU systems: registered at attach (i915_gem_gtt.c:2968, radeon_device.c:1493, amdgpu_device.c:2648), consulted on every GPU VM fault (ttm_bo_vm.c:667, i915_gem.c:2391). DF-2125 (lockless segment race) applies to them and is not downgraded by this finding.

Why no runtime PoC

The audit guest is QEMU/KVM with a bochs display device and no Intel/AMD GPU (env.txt: pciconf -l shows only vgapci0 … chip=0x11111234, ls /dev/dri β†’ No such file). The DRM core never attaches, so neither the live GEM/TTM paths nor the dead legacy path exist at runtime. Even on a GPU-equipped machine, the dead path cannot be triggered (Step 1). The correct verification for this class is the source reachability proof, which is complete and certain.

Fix validation

Not applicable (Info-class hardening finding; the advisory fix.diff was not built into a kernel β€” the path is unreachable, so no behavior could change observably).

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

Fix not built: the affected path is unreachable on every shipped driver and the guest has no DRM hardware, so neither baseline nor patched behavior is observable at runtime.

findings/poc/DF-5122/fix.diff (advisory, annotated)
↓ fix.diffper-fix-DF-5122

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5122/VERDICT.md (full reachability proof with path:line); README.md; env.txt shows the guest has no DRM hardware (pciconf: only bochs vgapci 0x1234:1111, /dev/dri absent), so no runtime PoC is possible even for the live paths

PoC changes

No PoC exists: the finding is a reachability/correctness proof, fully verified statically. Guest lacks any Intel/AMD GPU, so /dev/dri never exists (env.txt).

Verified recommended fix

Delete the dead legacy d_mmap handler (drop .d_mmap = drm_mmap from drm_cdevsw); if a non-GEM/non-TTM legacy driver is ever ported, fix the key scale, rebase by user_token, bound by map->size, and return atop(phys) on all branches (see fix.diff).

Verdict

Static structural finding, proven by source citation, with no runtime trigger on any stock system: drm_mmap() (drm_vm.c:44-139) is unreachable because vm_mmap() prefers d_mmap_single and only falls back to the old device pager on ENODEV (vm_mmap.c:1372-1386), while drm_mmap_single() never returns ENODEV for any shipped driver (all set DRIVER_GEM: i915_drv.c:3284, radeon_drv.c:671, amdgpu_drv.c:1304; radeon/amdgpu also set drm_ttm_bdev). Inside this dead handler are three latent defects beyond the known DF-2124/2126: (1) the map_hash lookup passes a byte offset where keys are page numbers (drm_vm.c:91 vs drm_bufs.c:98,511-512); (2) phys = map->offset + offset / vtophys(handle + offset) double-counts the user token instead of rebasing by it (drm_vm.c:121,130); (3) the DMA branch returns a byte address where the consumer old_dev_pager_fault (device_pager.c:360) requires a PFN via pmap_phys_address()=ptob(ppn) (pmap.c:5985-5989) - inconsistent with atop() at drm_vm.c:137. Triage value: DF-2124 and DF-2126 live in this dead path; DF-2125 (fictitious helpers, drm_vm.c:141-227) IS live at runtime (registered at i915_gem_gtt.c:2968 / radeon_device.c:1493 / amdgpu_device.c:2648, consulted per GPU VM fault at ttm_bo_vm.c:667 and i915_gem.c:2391) and is not downgraded.