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)
PoC verification
Evidence pack
findings/poc/DF-2835 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 outputfix.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 β 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:
- id == NULL is accepted β observed
rc=0. A second NULL create returnedEEXIST(errno 17), proving the NULL id became a live key in the per-process vmspace RB tree (comparerb_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". - type/data are silently ignored β
create(id, 123, 0xdead0000)observedrc=0. If a typed-vmspace extension ever givestypemeaning, 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_testableConfirmed 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).
No comments yet.