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

Cross-device PRIME import panics unconditionally: dma_buf_attach() stub returns NULL (not ERR_PTR), IS_ERR misses it, and the fail_detach unwind calls dma_buf_detach() which is hardcoded panic()

Summary

The attach stub returns NULL so the IS_ERR guard passes; map_attachment(NULL) returns -EINVAL; the unwind calls the detach stub which is panic(). Any foreign dma-buf imported through radeon's generic helper or amdgpu's fallthrough panics the kernel - unpriv cross-GPU export/import on hybrid systems is an instant panic (DoS). VERIFIED via KLD replay with shipped symbols: deterministic panic with syscall trace. i915's private import dodges the panic but creates a zombie object (noted). ERR_PTR(-ENOSYS) + non-panicking detach fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4516 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 3.6 KB ↓ raw
df4515mod.c β€” 7.0 KB view raw
Makefile β€” 231 B ↓ download
build.sh β€” 127 B view raw
run.sh β€” 178 B view raw
run.log β€” 865 B view raw
panic.txt β€” 578 B view raw
fix_run.log β€” 528 B view raw
fix.diff β€” 794 B view raw
env.txt β€” 611 B view raw
verdict.json β€” 3.4 KB view raw

DF-4516 β€” cross-device PRIME import panics the kernel via dma_buf_detach stub

What

drm_gem_prime_import_dev() (sys/dev/drm/drm_prime.c:709-762) attaches with dma_buf_attach() (drm_prime.c:733). On this port dma_buf_attach() is a stub that returns NULL, not ERR_PTR (sys/dev/drm/include/linux/dma-buf.h:96-101), so the IS_ERR(attach) guard at drm_prime.c:734 does not fire. The code proceeds, the subsequent dma_buf_map_attachment(NULL, …) returns -EINVAL (linux_dma-buf.c:196-197), and the error unwind fail_detach: calls dma_buf_detach() (drm_prime.c:758) β€” which is hardcoded to panic("dma_buf_attach is not implemented") (dma-buf.h:125-130).

Any foreign dma-buf (i.e. exported by another DRM device) imported through a driver that uses the generic import helper β€” radeon (radeon_drv.c:699) and amdgpu (amdgpu_prime.c:445-458 falls through to drm_gem_prime_import) β€” instantly panics the kernel. i915 has its own copy (i915_gem_dmabuf.c:305-321) that swallows the NULL attach and instead creates a zombie object with import_attach == NULL.

Files (shared trigger with DF-4515)

  • df4515mod.c β€” sysctl hw.df4515mod.fire=6 runs exactly drm_prime.c:733β†’739β†’758
  • run.log, panic.txt β€” baseline: panic: dma_buf_attach is not implemented
  • fix_run.log β€” after fix.diff: dma_buf_attach -> ERR_PTR(-ENOSYS), IS_ERR=1, no panic
  • fix.diff β€” stub returns ERR_PTR(-ENOSYS); detach stops panicking

Reproduce (as root in the guest)

cd /usr/src/sys/dev/drm/drm && make obj && make
cd /root/df4515 && make
kldload /usr/obj/usr/src/sys/dev/drm/drm/drm.ko
kldload df4515mod.ko
sysctl hw.df4515mod.fire=6      # EXPECT: panic: dma_buf_attach is not implemented

Note: the real entry (DRM_IOCTL_PRIME_FD_TO_HANDLE with a foreign prime fd) requires two DRM devices; the QEMU guest has none, so the PoC drives the identical call sequence through the shipped drm.ko symbols.

VERDICT.md
↓ download raw

DF-4516 VERDICT β€” REPRODUCED (guaranteed kernel panic on cross-device PRIME import)

Bottom line

Importing a dma-buf exported by another DRM device through the generic PRIME import helper executes panic() unconditionally on this port. The chain, every step traced in source and executed on the guest against the shipped drm.ko:

  1. DRM_IOCTL_PRIME_FD_TO_HANDLE (unprivileged with /dev/dri/cardN open β€” video group) β†’ drm_gem_prime_fd_to_handle (drm_prime.c:792)
  2. β†’ driver gem_prime_import β†’ generic drm_gem_prime_import_dev (drm_prime.c:709)
  3. β†’ attach = dma_buf_attach(dma_buf, attach_dev) (drm_prime.c:733) β€” the port's stub returns NULL (dma-buf.h:96-101); IS_ERR(NULL) is false, so the guard at :734 passes
  4. β†’ dma_buf_map_attachment(NULL, DMA_BIDIRECTIONAL) (drm_prime.c:739) returns ERR_PTR(-EINVAL) (linux_dma-buf.c:196-197)
  5. β†’ goto fail_detach β†’ dma_buf_detach(dma_buf, attach) (drm_prime.c:758) β†’ panic("dma_buf_attach is not implemented") (dma-buf.h:129)

Guest proof (no GPU on the guest β€” entry-level precondition documented)

drm.ko built from the guest's /usr/src and loaded; KLD executes steps 3-5 with the real module symbols. Observed (run.log, panic.txt):

dma_buf_attach: stub
df4515mod: [6] dma_buf_attach -> 0 IS_ERR=0            <- NULL, IS_ERR blind (drm_prime.c:734)
dma_buf_map_attachment: stub
df4515mod: [6] dma_buf_map_attachment -> -22           <- -EINVAL (linux_dma-buf.c:196)
df4515mod: [6] goto fail_detach -> dma_buf_detach (drm_prime.c:758): expect panic
panic: dma_buf_attach is not implemented
sysctl_fire() <- sysctl_root() <- userland_sysctl() <- sys___sysctl() <- syscall2()

Deterministic; reproduced first try.

Reachability

  • radeon: .gem_prime_import = drm_gem_prime_import (radeon_drv.c:699) β€” direct.
  • amdgpu: amdgpu_gem_prime_import falls through to drm_gem_prime_import for any non-self dmabuf (amdgpu_prime.c:445-458).
  • Trigger requires two DRM devices (export on A, import into B) β€” e.g. any hybrid i915+radeon/amdgpu laptop or dual-GPU box, or a foreign exporter appearing later. Single-GPU self-import takes the early-return path (drm_prime.c:718-728) and does not reach the stub.
  • i915's own import (i915_gem_dmabuf.c:284-334) dodges the panic (#if 0 around its fail_detach) but instead succeeds with import_attach == NULL and no dma_buf reference β€” a zombie object whose first page-fetch fails; separate defect, same root.

Fix validation

fix.diff (dma-buf.h): dma_buf_attach returns ERR_PTR(-ENOSYS) (caught by the existing IS_ERR at drm_prime.c:734 β†’ clean -ENOSYS ioctl failure) and dma_buf_detach no longer panics. Applied to guest /usr/src, rebuilt, re-run (fix_run.log):

dma_buf_attach: not implemented
df4515mod: [6] dma_buf_attach -> 0xffffffffffffffb2 IS_ERR=1      <- caught, no panic

Guest kept running. fix_status = fixed.

References

sys/dev/drm/drm_prime.c:709-762 (:733, :734, :739, :742-743, :758) sys/dev/drm/include/linux/dma-buf.h:96-101 (NULL stub), :125-130 (panic stub) sys/dev/drm/linux_dma-buf.c:189-207 (map_attachment EINVAL on NULL attach) sys/dev/drm/radeon/radeon_drv.c:699, sys/dev/drm/amd/amdgpu/amdgpu_prime.c:445-458, sys/dev/drm/amd/amdgpu/amdgpu_drv.c:1328 (generic import wiring)

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to guest /usr/src (dma-buf.h), drm.ko + module rebuilt, identical PoC step 6 re-run: dma_buf_attach returned ERR_PTR(-ENOSYS) with IS_ERR=1 (0xffffffffffffffb2), the sequence broke out cleanly before map/detach, no panic, guest stayed up. Baseline panic gone.

findings/poc/DF-4516/fix_run.log vs run.log + panic.txt
↓ fix.diffper-fix-DF-4516

Confirmed kernel references

Detail

Exploit chain

Requires two DRM devices (foreign dma-buf): export a GEM object on GPU A via PRIME_HANDLE_TO_FD, pass the fd to a process with GPU B's card open (radeon or amdgpu), issue DRM_IOCTL_PRIME_FD_TO_HANDLE -> dma_buf_attach NULL -> map -EINVAL -> fail_detach -> panic. Pure DoS (panic), no escalation path.

Evidence (decisive lines)

run.log ('dma_buf_attach -> 0 IS_ERR=0', 'map_attachment -> -22', 'goto fail_detach'), panic.txt ('panic: dma_buf_attach is not implemented' with syscall trace), fix_run.log ('0xffffffffffffffb2 IS_ERR=1', no panic)

PoC changes

Shared trigger module with DF-4515; step 6 replays drm_prime.c:733->739->758. The real entry needs two GPUs (guest has none) - documented in VERDICT.md.

Verified recommended fix

dma_buf_attach() stub must return ERR_PTR(-ENOSYS) instead of NULL, and dma_buf_detach() must not panic(); drm_prime.c:734's existing IS_ERR check then yields a clean ioctl failure.

Verdict

drm_prime.c's import helper attaches via dma_buf_attach() (drm_prime.c:733) which on this port is a stub returning NULL instead of ERR_PTR (dma-buf.h:96-101); IS_ERR(NULL) does not catch it, dma_buf_map_attachment(NULL) fails with -EINVAL, and the fail_detach unwind (drm_prime.c:758) calls dma_buf_detach() which is hardcoded panic('dma_buf_attach is not implemented'). Any unprivileged video-group user importing a foreign dma-buf on radeon/amdgpu (generic import helper) panics the kernel. Proven on the guest with the shipped drm.ko: the exact drm_prime.c:733-758 sequence panics deterministically; fix.diff (ERR_PTR(-ENOSYS) + no-panic detach) makes the same sequence fail cleanly with IS_ERR catching the error.