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)
PoC verification
Evidence pack
findings/poc/DF-3635 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)
- Only
i915_gem_object_attach_phys()(i915_gem.c:6670) installs the phys ops whose->get_pagesis this function, and on this treeattach_physis called only from the display modesetting path (intel_display.c:13183) β DRM master territory, not direct unprivileged ioctl. - The error return requires
shmem_read_mapping_page()to fail on the OBJT_DEFAULT backing object: fresh pages are zero-filled unconditionally (default pagerhaspage= 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.
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_physvia 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
- sys/dev/drm/i915/i915_gem.c:305 (lock taken)
- sys/dev/drm/i915/i915_gem.c:315-317 (leaking jump)
- sys/dev/drm/i915/i915_gem.c:328 (success-only unlock)
- sys/dev/drm/i915/i915_gem.c:357-360 (err_phys without unlock)
- sys/dev/drm/i915/i915_gem.c:3031-3033 (correct pattern, sibling fn)
- sys/vm/vm_object.c:283-294 (VM_OBJECT_LOCK semantics)
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_testableOne-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.
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.
No comments yet.