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

i915_gem_object_get_pages_phys() error path returns holding the GEM backing vm_object (leaked hold_count + LWKT token)

Summary

The DragonFly VM-lock glue in get_pages_phys() takes VM_OBJECT_LOCK(vm_obj) for the page-in copy loop but the in-loop failure jump 'goto err_phys' bypasses the only VM_OBJECT_UNLOCK, returning with a leaked hold reference and unbalanced token count on the object backing the GEM bo. The sibling get_pages_gtt() unlocks on its error path, proving the omission is accidental. Narrow reachability (display modesetting + swap-pager I/O failure page-inning a paged-out GEM page). Once hit: the vm_object is permanently pinned and thread token accounting corrupted - persistent leak / potential wedge of VM ops on that object. One-line symmetric fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3635 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
fix.diff β€” 200 B view raw
manifest.json β€” 597 B view raw
verdict.json β€” 2.6 KB view raw

DF-3635 β€” i915_gem_object_get_pages_phys() error path leaks the GEM shmem vm_object hold+token

What

i915_gem_object_get_pages_phys() takes the GEM backing-store vm_object lock for the whole page-in copy loop:

305 VM_OBJECT_LOCK(vm_obj);
306 for (i = 0; i < obj->base.size / PAGE_SIZE; i++) {
...
313     page = shmem_read_mapping_page(vm_obj, i);
315     if (IS_ERR(page)) {
316         err = PTR_ERR(page);
317         goto err_phys;          /* <-- lock still held */
318     }
...
328 VM_OBJECT_UNLOCK(vm_obj);          /* success path only */
...
357 err_phys:
358 drm_pci_free(obj->base.dev, phys);
359
360 return err;                          /* <-- returns with hold+token leaked */

(The #if 0 Linux original has no VM object locking at all; the lock/unlock pair is DragonFly-port glue, and the error jump bypasses the unlock. The sibling function i915_gem_object_get_pages_gtt() does unlock on its error paths β€” VM_OBJECT_UNLOCK(mapping) at i915_gem.c:3032 β€” proving the omission here is accidental.)

On this tree VM_OBJECT_LOCK is vm_object_hold() (sys/vm/vm_object.h:267) = refcount_acquire(&obj->hold_count) + lwkt_gettoken(&obj->token) (sys/vm/vm_object.c:283-294). Returning without the matching VM_OBJECT_UNLOCK/vm_object_drop therefore leaks both a hold reference and a token reference on the object that backs the GEM bo: the object can never be reclaimed (hold_count pinned), and the owning thread carries an unbalanced token count (serialization accounting corrupted for every later VM operation on that object by that thread).

Reachability (why Low)

  1. Only i915_gem_object_attach_phys() (i915_gem.c:6670) installs the phys ops whose ->get_pages is this function, and on this tree attach_phys is called only from the display modesetting path (intel_display.c:13183) β€” DRM master territory, not direct unprivileged ioctl.
  2. The error return requires shmem_read_mapping_page() to fail on the OBJT_DEFAULT backing object: fresh pages are zero-filled unconditionally (default pager haspage = FALSE, sys/vm/default_pager.c:138-141), so failure needs the page to have real swap backing (vm_pager_get_page β†’ swap_pager I/O error) β€” i.e. swap-device failure while the bo is paged out.

Once triggered: permanent vm_object leak + token imbalance; worst case a wedge of subsequent VM operations on that GEM object (local DoS, no memory-safety impact).

Fix

One-line unlock before the jump β€” see fix.diff.

Verification

Not attempted at runtime (Low severity, hardware path): the guest has no i915 device. Status: untested / source-certain. The identical error-path discipline in i915_gem_object_get_pages_gtt (:3026-3033) confirms the intended pattern.

VERDICT.md
↓ download raw

DF-3635 VERDICT

Status: untested (runtime) / certain (source). Severity Low.

Root cause (source-certain)

i915_gem_object_get_pages_phys(): - sys/dev/drm/i915/i915_gem.c:305 β€” VM_OBJECT_LOCK(vm_obj); (= vm_object_hold: refcount_acquire(hold_count) + lwkt_gettoken, sys/vm/vm_object.c:283-294, sys/vm/vm_object.h:267). - sys/dev/drm/i915/i915_gem.c:313-318 β€” on shmem_read_mapping_page() failure: goto err_phys without unlock. - sys/dev/drm/i915/i915_gem.c:357-360 β€” err_phys: frees the dma block and returns; the only VM_OBJECT_UNLOCK(vm_obj) is on the success path at :328. - Control-flow proof: the three other exits (drm_pci_alloc failure :301, kmalloc :332, sg_alloc :338) are taken before or after the locked region; only the in-loop page failure leaks.

Effect: leaked hold_count pins the GEM backing vm_object forever (never reclaimed; vm_object_terminate requires no holders) and leaves an unbalanced LWKT token count on the faulting thread.

Why not reproduced

  • Runtime path needs (a) i915 hardware (absent: guest is virtio-only, no /dev/dri β€” see DF-3634/run.log), (b) i915_gem_object_attach_phys via display modeset (intel_display.c:13183, DRM-master), (c) a swap-pager I/O failure during page-in of a paged-out GEM page (OBJT_DEFAULT fresh pages never fail: default_pager_haspage == FALSE, sys/vm/default_pager.c:138-141). The combination is not constructible on this audit infrastructure.

Impact ceiling

Object-lifetime/serialization corruption confined to one GEM object's backing store: permanent memory leak + possible wedge (local DoS), no memory-safety primitive. Low.

Fix

fix.diff: add VM_OBJECT_UNLOCK(vm_obj); before goto err_phys;, mirroring the pattern already used by the sibling shmem path i915_gem_object_get_pages_gtt() at i915_gem.c:3031-3033 (err_pages: label unlocks). Not kernel-built (no i915 target); one-line symmetric correction, low risk.

Kernel references

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-line symmetric correction matching the sibling get_pages_gtt error path; cannot be kernel-validated on this guest (no i915 device).

fix.diff; sibling pattern i915_gem.c:3031-3033.
↓ fix.diffper-fix-DF-3635

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md control-flow trace; fix.diff adds the missing VM_OBJECT_UNLOCK before goto err_phys, matching the sibling pattern at i915_gem.c:3031-3033.

PoC changes

No runnable PoC possible (no i915 device on the audit guest; trigger requires Intel hw + display modeset + swap I/O failure). Evidence is source-level.

Verified recommended fix

Add VM_OBJECT_UNLOCK(vm_obj) before goto err_phys in i915_gem_object_get_pages_phys (mirror of i915_gem.c:3031-3033).

Verdict

Source-certain error-path lock leak, not runtime-testable here. i915_gem_object_get_pages_phys() takes VM_OBJECT_LOCK(vm_obj) at i915_gem.c:305 and jumps to err_phys at :317 on shmem_read_mapping_page() failure without the matching unlock (only the success path at :328 unlocks; err_phys at :357-360 returns directly). VM_OBJECT_LOCK is vm_object_hold() (hold_count ref + LWKT token), so the leak permanently pins the GEM backing vm_object and corrupts token accounting. Reachability is narrow: attach_phys is only invoked from the display modesetting path (intel_display.c:13183) and the failure needs a swap-pager I/O error on a paged-out GEM page (fresh OBJT_DEFAULT pages are zero-filled and cannot fail, sys/vm/default_pager.c:138). Impact ceiling: permanent object leak / VM-accounting wedge (local DoS), no memory-safety primitive. Sibling function i915_gem_object_get_pages_gtt() demonstrates the correct error-path unlock at i915_gem.c:3031-3033, proving the omission is accidental.