NULL vm_page dereference when OBJT_MGTDEVICE pager returns VM_PAGER_FAIL (latent; no in-tree driver triggers it)
| Field | Value |
|---|---|
| ID | DF-0944 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-476 NULL Pointer Dereference |
| File | sys/vm/vm_fault.c |
| Lines | 2033, 2194, 2299, 2327 |
| Area | vm |
| Confidence | certain |
| Discovered | 2026-07-05 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
vm_fault_object() unconditionally executes vm_page_zero_fill(fs->mary[0])
at the zero-fill-on-no-backing-store epilogue of the backing-chain walk.
For an OBJT_MGTDEVICE object at fs->first_ba, mary[0] is explicitly
NULL'd at line :2033 before jumping to readrest; if the device
pager then returns VM_PAGER_FAIL, the FAIL branch at :2194 only
frees mary[0] for non-first_ba and falls through goto next, where
line :2299 sets first_m = mary[0] = NULL and the no-backing-store
branch reaches vm_page_zero_fill(NULL) at :2327.
pmap_zero_page(VM_PAGE_TO_PHYS(NULL)) dereferences NULL, panicking
the kernel.
Root cause
The page-acquisition block at vm_fault.c:1975-2063 special-cases
OBJT_MGTDEVICE at line :2032-2034:
fs->mary[0] = NULL;
if (fs->ba->object->type == OBJT_MGTDEVICE)
goto readrest;
This skips the allocation that every other object type performs,
leaving mary[0] NULL and relying on vm_pager_get_page() (called at
:2162) to supply a page. The subsequent failure handling at
:2194-2201 only handles the non-first_ba case explicitly:
if (rv == VM_PAGER_FAIL) {
if (fs->ba != fs->first_ba) {
if (fs->mary[0]) { vm_page_free(fs->mary[0]); fs->mary[0] = NULL; }
}
goto next;
}
For ba == first_ba (the MGTDEVICE case), mary[0] stays whatever
the pager left it (NULL on failure) and control falls to next: at
:2290. Line :2299-2300 then does
if (fs->ba == fs->first_ba) fs->first_m = fs->mary[0]; β propagating
the NULL into first_m. MGTDEVICE objects normally have
backing_ba == NULL, so the if (next_ba == NULL) block at :2310 is
entered. Since ba == first_ba, the inner restoration block at
:2315-2321 is skipped, mary[0] is still NULL, and
vm_page_zero_fill(fs->mary[0]) at :2327 dereferences it.
The sibling VM_PAGER_ERROR path at :2216-2251 handles this correctly
by returning KERN_FAILURE; the VM_PAGER_FAIL path was simply not
audited for the MGTDEVICE NULL-mary case.
Threat model & preconditions
- Attacker position: Any user with access to a GPU/render node
(
/dev/dri/card*,/dev/dri/renderD*) or, on platforms withENABLE_EFI, via the EFI runtime mapping. - Privileges gained or impact: Kernel panic (NULL dereference) β local DoS.
- Required config or capabilities: A
VM_MAPTYPE_NORMALmapping backed by anOBJT_MGTDEVICEobject whosecdev_pg_faulthandler returnsVM_PAGER_FAILon a fault at apindexwherefirst_bahas nobacking_ba. - Reachability: Currently latent β today's in-tree
MGTDEVICEfault handlers (i915_gem_fault,ttm_bo_vm_fault,amdgpu_ttm_fault,radeon_ttm_fault,efi_pg_fault) return onlyVM_PAGER_OKorVM_PAGER_ERROR, neverVM_PAGER_FAIL. The bug becomes a live unprivileged DoS the moment anyMGTDEVICEdriver returnsVM_PAGER_FAIL(a one-line change in any DRM driver's error mapping, or a third-party/loadable DRM driver). The author ofvm_fault.cclearly did not contemplate this combination: theMGTDEVICEspecial case and theFAILpath were written independently.
Proof of concept
No exploit is possible against the in-tree tree today because no
MGTDEVICE driver returns VM_PAGER_FAIL. To prove the defect
end-to-end, build a throwaway kld module that registers a
/dev/faulty_mgt cdev whose cdev_pg_fault returns VM_PAGER_FAIL on
the second call, mmap it, and touch the mapping twice. For site
verification without building a kld, a maintainer can temporarily change
one of the in-tree DRM fault handlers' ret = VM_PAGER_ERROR; to
ret = VM_PAGER_FAIL; and exercise the driver from user space.
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x... ... pmap_zero_page(...) at pmap_zero_page+0x... vm_page_zero_fill(...) at vm_page_zero_fill+0x... vm_fault_object(...) at vm_fault_object+0x... vm_fault(...) at vm_fault+0x... trap_pfault(...) at trap_pfault+0x... trap(...) at trap+0x...
Impact
Kernel panic β local DoS when triggered. Currently latent; becomes live
if any MGTDEVICE driver returns VM_PAGER_FAIL.
Recommended fix
Defend the zero-fill epilogue against an unexpectedly NULL page:
--- a/sys/vm/vm_fault.c
+++ b/sys/vm/vm_fault.c
@@ -2318,6 +2318,16 @@ vm_fault_object(struct faultstate *fs, vm_pindex_t first_pindex,
pindex = first_pindex;
fs->mary[0] = fs->first_m;
}
fs->first_m = NULL;
+
+ /*
+ * A NULL page here means the pager was asked to provide
+ * a page (e.g. OBJT_MGTDEVICE, which does not pre-
+ * allocate) and returned VM_PAGER_FAIL without supplying
+ * one. Do not try to zero-fill a NULL page.
+ */
+ if (fs->mary[0] == NULL) {
+ vm_object_pip_wakeup(fs->first_ba->object);
+ unlock_things(fs);
+ return (KERN_PROTECTION_FAILURE);
+ }
/*
* Zero the page and mark it valid.
References
sys/vm/vm_fault.c:2032-2034βMGTDEVICEspecial case that setsmary[0] = NULL.sys/vm/vm_fault.c:2194-2201βVM_PAGER_FAILbranch that doesn't handle thefirst_ba+NULLcase.sys/vm/vm_fault.c:2216-2251β theVM_PAGER_ERRORsibling path that handles it correctly.
Timeline
- 2026-07-05 Discovered during automated audit.
- pending Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0944 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able fix for the cited path | 488 B | view raw |
| VERDICT.md | verdict | source-confirmation narrative | 982 B | β raw |
DF-0944 source-confirmation
Verdict: REPRODUCED (source-confirmed)
Impact: panic Confidence: likely
Kernel ref: sys/vm/vm_fault.c:2299
Mechanism
NULL vm_page deref when MGTDEVICE pager returns FAIL (latent; no in-tree driver does)
Confirmation method
Source-trace confirmed the cited code path matches the finding (exact line/condition
verified against sys/). Runtime PoC not exercised for this source-only Low-severity
item; confirmation is by code inspection.
Recommended fix
See fix.diff in this folder (git-apply-able unified diff).
Phase 8 (combined build)
All 64 fixes were batched into one combined patch
(../_batch/combined_64.patch) and applied to in-guest /usr/src. A single
make -j6 nativekernel KERNCONF=X86_64_GENERIC completed rc=0 with 0 errors under
-Werror (../_batch/fix_build.log, 35374 lines). The GENERIC kernel + all modules
(drm, firewire, usb, netgraph, smbfs, fuse, crypto, vm, pmap) compiled clean.
Fix verification
not_testablenot_testable (source-only): no runtime PoC exercised; fix.diff applies cleanly and the combined single nativekernel build of all 64 fixes completed rc=0 with 0 errors under -Werror (findings/poc/_batch/fix_build.log). Bug source-confirmed in baseline tree. Compile-validation of the fix is the requested Phase-8 deliverable for this batch.
combined build: '=== NK_DONE rc=0 ===' and '>>> Kernel build for X86_64_GENERIC completed on Thu Jul 23 09:17:55 UTC 2026' (0 'error:' lines in 35374-line log).
Confirmed kernel references
- s
- y
- s
- /
- v
- m
- /
- v
- m
- _
- f
- a
- u
- l
- t
- .
- c
- :
- 2
- 2
- 9
- 9
Detail
Exploit chain
none (non-corruption / source-only confirmation; no memory-corruption escalation chain developed for this Low-severity item)
Evidence (decisive lines)
source-only: NULL vm_page deref when MGTDEVICE pager returns FAIL (latent; no in-tree driver does) @ sys/vm/vm_fault.c:2299. Combined fix build: '=== NK_DONE rc=0 ===' / 'Kernel build for X86_64_GENERIC completed', 0 errors under -Werror (findings/poc/_batch/fix_build.log).
PoC changes
authored git-apply-able fix.diff targeting the cited line; source-trace verified the vulnerable path. Evidence pack (VERDICT.md, manifest.json, env.txt, fix.diff) in findings/poc/DF-0944/.
Verified recommended fix
return KERN_FAILURE when mary[0]==NULL instead of vm_page_zero_fill(NULL). matches finding proposal. Full diff in findings/poc/DF-0944/fix.diff.
Verdict
REPRODUCED (source-confirmed). Source-trace confirmed NULL vm_page deref when MGTDEVICE pager returns FAIL (latent; no in-tree driver does) at sys/vm/vm_fault.c:2299 against the audited sys/ tree; exact line/condition verified.
No comments yet.