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

sys_vmspace_create violates its own ABI contract: accepts id==NULL and silently ignores type/data instead of rejecting them

Field Value
ID DF-2835
Status new
Severity Info
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:N
CWE CWE-20 Improper Input Validation
File sys/vm/vm_vmspace.c
Lines 146-201 (contract at :138-139)
Area vm
Confidence certain
Discovered 2026-08-31
Pass 2 (GLM 5.3 second pass)
Bucket base:vm
Reported pending
Known CVE none
CVE match novel

Summary

The syscall documents "An id of NULL cannot be used. The type and data fields must currently be 0." but the implementation never checks uap->id, uap->type, or uap->data. A NULL id is accepted as an RB key and type/data are ignored entirely. No memory-safety consequence today (type is never used to index anything); contract-enforcement hardening so a future typed-vmspace extension cannot silently mis-parse old callers, and the documented NULL-id sentinel stays reserved. Fix: if (uap->id == NULL || uap->type != 0 || uap->data != NULL) return (EINVAL); at entry next to the vkernel_enable gate.

Timeline

  • 2026-08-31 Discovered during pass-2 audit of vm_vmspace.c (GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2835 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
vmsp_abi.c β€” 3.4 KB view raw
build.sh β€” 200 B view raw
run.sh β€” 252 B view raw
build.log β€” 202 B view raw
run.log β€” 754 B view raw
run.fixed.log β€” 1.3 KB view raw
env.txt β€” 1.4 KB view raw
fix.diff β€” 2.0 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 3.0 KB view raw

DF-2835 evidence pack β€” sys_vmspace_create ABI contract not enforced

File: sys/vm/vm_vmspace.c:146-201 (contract comment at :134-139) Severity: Info (CWE-20) β€” hardening; no memory-safety consequence today

Contract (sys/vm/vm_vmspace.c:134-139)

Create a VMSPACE under the control of the caller with the specified id.
An id of NULL cannot be used.  The type and data fields must currently
be 0.

The implementation never checked uap->id, uap->type, uap->data.

Verified on stock kernel (run.log)

call documented observed (stock)
[1] create(id=NULL, 0, NULL) -1/EINVAL rc=0 β€” ACCEPTED
[2] create(id=NULL) again -1/EINVAL -1/EEXIST β†’ NULL is a live RB-tree key
[3] create(id, type=123, data=0xdead0000) -1/EINVAL rc=0 β€” type/data silently ignored
[4] create(id, 0, NULL) 0 0 β€” legitimate use unaffected

Run as unprivileged uid 1001 with vm.vkernel_enable=1.

Fixed kernel (run.fixed.log)

[1] β†’ -1/EINVAL, [3] β†’ -1/EINVAL, [4] β†’ 0. Contract enforced, legitimate call path unchanged.

Files

  • vmsp_abi.c β€” the PoC (exit 0 = bug reproduced, exit 1 = enforced)
  • run.log / run.fixed.log β€” stock vs patched output
  • fix.diff β€” shared with DF-2834 (same patch adds the EINVAL guards)
  • build.log, env.txt, VERDICT.md, manifest.json, verdict.json

Reproduce

build.sh        # cc -O2 -Wall -o /tmp/vmsp_abi vmsp_abi.c
run.sh          # sysctl vm.vkernel_enable=1; /tmp/vmsp_abi as unpriv; restore
VERDICT.md
↓ download raw

VERDICT β€” DF-2835 (sys_vmspace_create ABI contract not enforced)

status: reproduced / impact: none (hardening) / confidence: certain

Guest: DragonFly 6.5-DEVELOPMENT #0 (stock INVARIANTS kernel, X86_64_GENERIC), QEMU/KVM. Run 2026-10-20 UTC as unprivileged uid 1001 with vm.vkernel_enable=1 (root sysctl, restored to 0 afterwards).

The contract

sys/vm/vm_vmspace.c:134-139 documents:

An id of NULL cannot be used.  The type and data fields must
currently be 0.

What the code does (stock kernel)

sys_vmspace_create (sys/vm/vm_vmspace.c:146-201) dereferences nothing of uap β€” it stores uap->id verbatim as the RB key at :184 (ve->id = uap->id) and inserts at :190. uap->type and uap->data are never read anywhere in the function. Consequently:

  1. id == NULL is accepted β€” observed rc=0. A second NULL create returned EEXIST (errno 17), proving the NULL id became a live key in the per-process vmspace RB tree (compare rb_vmspace_compare, sys/vm/vm_vmspace.c:581-589 β€” plain pointer compare, NULL is just a key). The documented NULL sentinel is therefore consumed: no future extension can use NULL for "no id".
  2. type/data are silently ignored β€” create(id, 123, 0xdead0000) observed rc=0. If a typed-vmspace extension ever gives type meaning, old callers passing garbage would be silently mis-parsed instead of rejected at the door.

No memory-safety consequence today: type never indexes anything and data is never dereferenced on this path (checked: no other use of uap->type/uap->data in sys/vm/vm_vmspace.c). Severity Info is correct.

Fix validation (A/B)

fix.diff (shared with DF-2834) adds at sys/vm/vm_vmspace.c, right after the vkernel_enable gate:

if (uap->id == NULL || uap->type != 0 || uap->data != NULL)
    return (EINVAL);

Patched kernel (#1: Tue Oct 20 11:55:22 UTC 2026, built in the guest scratch /usr/src, 6.1 min, warning-free):

call stock patched
create(NULL, 0, NULL) rc=0 ACCEPTED -1/EINVAL
create(NULL) 2nd -1/EEXIST -1/EINVAL
create(id, 123, 0xdead0000) rc=0 ACCEPTED -1/EINVAL
create(id, 0, NULL) 0 0 (unchanged)
destroy(legit id) 0 0

Contract enforced, legitimate path byte-identical. vm.vkernel_enable restored to 0; guest reset to the clean snapshot afterwards (guest_dirty = 0).

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
↓ fix.diffper-fix-DF-2835

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (stock kernel: [1] NULL accepted rc=0, [2] EEXIST proving live RB key, [3] type=123 data=0xdead0000 accepted rc=0, [4] legit create rc=0), run.fixed.log (patched kernel: [1] and [3] EINVAL, [4] rc=0), VERDICT.md table, build.log warning-free

PoC changes

no seed pack existed (findings/poc/DF-2835 was empty); PoC written fresh in verify phase using syscall 486 via libc syscall(); test folded into one program that also proves the EEXIST second-create and exercises destroy cleanup so repeated runs start from a clean tree

Verified recommended fix

reject at entry next to the vkernel_enable gate: if (uap->id == NULL || uap->type != 0 || uap->data != NULL) return (EINVAL);

Verdict

Verified on the stock INVARIANTS kernel as unprivileged uid 1001 with vm.vkernel_enable=1: create(id=NULL, 0, NULL) is accepted (rc=0), a second NULL create returns EEXIST proving the NULL id became a live key in the per-process vmspace RB tree (key stored verbatim at sys/vm/vm_vmspace.c:184, inserted at :190, compared by pointer at :581-589), and create(id, type=123, data=0xdead0000) is accepted with type and data never read anywhere in the function - the documented contract at sys/vm/vm_vmspace.c:134-139 (id NULL cannot be used, type and data must be 0) is not enforced. No memory-safety consequence today (type is never used to index anything, data is never dereferenced), so impact none / severity Info is correct; the value is contract hardening so a future typed-vmspace extension cannot silently mis-parse old callers and the NULL sentinel stays reserved. Fix validated A/B on a patched kernel: NULL id and nonzero type/data now return EINVAL at the vkernel_enable gate while the documented normal create and destroy behave identically (rc=0).