brk/sbrk mappings are created RWX (VM_PROT_ALL as both prot and max_protection) β executable heap defeats future W^X policy
| Field | Value |
|---|---|
| ID | DF-2965 |
| 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-732 |
| File | sys/vm/vm_unix.c |
| Lines | 137-141, 223-227 |
| Area | vm |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:vm |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
Both sys_sbrk and sys_obreak pass VM_PROT_ALL (RWX) as both the protection and max_protection arguments to vm_map_find, so every page of the malloc/brk heap is mapped read-write-execute. On amd64 the pmap honors VM_PROT_EXECUTE via the NX bit, so heap memory is genuinely executable. This is historical BSD behavior (FreeBSD's obreak does the same) and adds no exposure beyond what an unprivileged process already gets from mmap(PROT_READ|PROT_WRITE|PROT_EXEC); filed as defense-in-depth so the heap is not an implicit W^X escape hatch the day W^X enforcement lands β and so shellcode stashed by a memory-corruption bug can run directly from the heap without a second mprotect step. Fix: VM_PROT_RW (optionally keep max_protection=ALL as a compat escape hatch).
Timeline
- 2026-09-02 Discovered during pass-2 audit of vm_unix.c (GLM 5.3). DF-0961/0962 re-verified still present, not re-reported.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2965 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 640 B | β raw | |
| VERDICT.md | β | 1.5 KB | β raw | |
| brk_exec.c | β | 1.0 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| env.txt | β | 509 B | view raw | |
| fix.diff | β | 604 B | view raw | |
| manifest.json | β | 498 B | view raw | |
| run.log | β | 51 B | view raw | |
| run.sh | β | 71 B | view raw | |
| verdict.json | β | 2.6 KB | view raw |
DF-2965 β brk/sbrk mappings RWX (Info-tier hardening verification)
What
brk/sbrk mappings RWX
Build
cc -Wall -O2 -o brk_exec brk_exec.c
Run
./brk_exec
Expected
baseline (stock kernel): see verdict below fix kernel (all-41-fixes build): the bad behavior is gone
Files
README.md VERDICT.md brk_exec.c build.sh env.txt fix.diff manifest.json run.log run.sh verdict.json
Shared harness: h41.c (combined KLD, sysctl debug.h41.cmd=N driven).
Full batch context: the fix kernel contains all 41 hardening fixes of
this batch compiled together (build RC=0) and passed the complete A/B
suite plus a clean reboot.
DF-2965 β VERDICT
status: reproduced | reproduced: 1 | impact: none | confidence: certain
Mechanically reproduced on the stock guest: sbrk(4096) + copying a 5-byte ret-42 thunk into the page + calling it printed 42 - the brk heap is genuinely executable (sys_sbrk vm_unix.c:141 and sys_obreak :227 pass VM_PROT_ALL as BOTH prot and max_protection; on amd64 the pmap honors VM_PROT_EXECUTE). A/B nuance: with the fix (prot=VM_PROT_RW, max_protection kept VM_PROT_ALL as a compat escape hatch) the brk vm_map entry now reports rw- (/proc/self/map: 0x402000-0x406000 rw- NCOW NNC none), but CALLING heap code still succeeds on this guest - a control mmap(PROT_READ|PROT_WRITE) is equally executable here, i.e. this QEMU vCPU configuration does not enforce NX for user pages despite machdep.pmap_nx_enable=1 and CPUID NX. The kernel-side mapping change is real and verified; hardware enforcement of the dropped EXEC bit cannot be demonstrated on this platform configuration. Filed as defense-in-depth (historical BSD behavior; adds nothing beyond mmap RWX which any process can request), so impact stays none.
Platform note
On the fix kernel the brk vm_map entry reports rw- (0x402000-0x406000 rw- NCOW NNC none) where stock reports rwx; calling heap code still succeeds because THIS QEMU vCPU configuration does not enforce NX for user pages (control: plain mmap RW is also executable despite machdep.pmap_nx_enable=1).
Baseline vs fix kernel
baseline: see verdict above / run log fix: behavior changed as designed (see verdict)
Fix verification
not_testablevm_map-level fix verified (brk entry rw-); execution still succeeds because NX is not enforced by this vCPU configuration for ANY mapping (control experiment) - the max_protection=ALL escape hatch additionally permits mprotect back to X by design.
Confirmed kernel references
- sys/vm/vm_unix.c:141
- sys/vm/vm_unix.c:227
- sys/sys/vm.h:87
Detail
Evidence (decisive lines)
run.log baseline: ret=42 (heap exec); fix kernel: /proc/self/map brk entry rw- (was rwx) + NX-platform control (plain RW mmap also executable)
PoC changes
added /proc/self/map protection dump and an RW-mmap exec control to separate map-level from plevel enforcement
Verified recommended fix
Map brk VM_PROT_RW (optionally keep max VM_PROT_ALL as compat escape hatch)
Verdict
Mechanically reproduced on the stock guest: sbrk(4096) + copying a 5-byte ret-42 thunk into the page + calling it printed 42 - the brk heap is genuinely executable (sys_sbrk vm_unix.c:141 and sys_obreak :227 pass VM_PROT_ALL as BOTH prot and max_protection; on amd64 the pmap honors VM_PROT_EXECUTE). A/B nuance: with the fix (prot=VM_PROT_RW, max_protection kept VM_PROT_ALL as a compat escape hatch) the brk vm_map entry now reports rw- (/proc/self/map: 0x402000-0x406000 rw- NCOW NNC none), but CALLING heap code still succeeds on this guest - a control mmap(PROT_READ|PROT_WRITE) is equally executable here, i.e. this QEMU vCPU configuration does not enforce NX for user pages despite machdep.pmap_nx_enable=1 and CPUID NX. The kernel-side mapping change is real and verified; hardware enforcement of the dropped EXEC bit cannot be demonstrated on this platform configuration. Filed as defense-in-depth (historical BSD behavior; adds nothing beyond mmap RWX which any process can request), so impact stays none.
No comments yet.