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

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 with ENABLE_EFI, via the EFI runtime mapping.
  • Privileges gained or impact: Kernel panic (NULL dereference) β€” local DoS.
  • Required config or capabilities: A VM_MAPTYPE_NORMAL mapping backed by an OBJT_MGTDEVICE object whose cdev_pg_fault handler returns VM_PAGER_FAIL on a fault at a pindex where first_ba has no backing_ba.
  • Reachability: Currently latent β€” today's in-tree MGTDEVICE fault handlers (i915_gem_fault, ttm_bo_vm_fault, amdgpu_ttm_fault, radeon_ttm_fault, efi_pg_fault) return only VM_PAGER_OK or VM_PAGER_ERROR, never VM_PAGER_FAIL. The bug becomes a live unprivileged DoS the moment any MGTDEVICE driver returns VM_PAGER_FAIL (a one-line change in any DRM driver's error mapping, or a third-party/loadable DRM driver). The author of vm_fault.c clearly did not contemplate this combination: the MGTDEVICE special case and the FAIL path 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.

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

Timeline

  • 2026-07-05 Discovered during automated audit.
  • pending Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0944 Β· 2 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-confirmation narrative
↓ download 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.

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_testable
baseline reproduced→ patch + rebuild →patched clean

not_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).
↓ fix.diffDragonFly 6.5-DEVELOPMENT single-fix combined kernel (nativekernel rc=0 -Werror, 2026-07-23 09:17:55 UTC)

Confirmed kernel references

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.