sysctl kern.stackgap_random accepts unbounded negative values: ALIGN(-gap) up to 2GB silently places every exec's stack contents outside the stack mapping β system-wide exec SIGSEGV from a single root sysctl (validation gap)
| Field | Value |
|---|---|
| ID | DF-2754 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-20 Improper Input Validation |
| File | sys/kern/kern_exec.c |
| Lines | 159-161 (consumer :1170-1171, :1181-1188) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
sysctl_kern_stackgap() validates only positive values. Negative values
are a documented feature ("static gap if negative") consumed unbounded
in exec_copyout_strings(): sgap = ALIGN(-gap), subtracted from destp.
A root-set value such as β1073741824 passes validation, yields sgap=1GB,
and pushes destp/stack_base ~1GB below USRSTACK β outside the
maxssiz=512MB stack mapping. copyout/suword return values are ignored,
so execve() reports success while the new image enters userland with an
unmapped stack pointer and every program faults at entry. INT_MIN
additionally makes βgap signed-overflow UB. Root-only misconfiguration
β hardening/validation gap.
Recommended fix
Validate the magnitude of negative values symmetrically (unsigned negation avoiding INT_MIN UB), keeping the static-gap feature β diff above.
Timeline
- 2026-08-30 Discovered during pass-2 audit of kern_exec.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2754 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | β | 1.7 KB | β raw | |
| README.md | β | 272 B | β raw | |
| build.sh | β | 88 B | view raw | |
| run.sh | β | 100 B | view raw | |
| env.txt | β | 314 B | view raw | |
| manifest.json | β | 704 B | view raw | |
| verdict.json | β | 2.4 KB | view raw | |
| fix.diff | β | 347 B | view raw | |
| stackgap_neg.c | β | 2.0 KB | view raw | |
| run.log | β | 346 B | view raw |
DF-2754 evidence pack
- finding: sys/kern/kern_exec.c (153-167 (sysctl), 1167-1176 (gap use))
- verdict: reproduced (certain) - see VERDICT.md
- runtime PoC: stackgap_neg.c
- reproduce: build.sh / run.sh ; expected output in run.log
- fix.diff: present, git-apply-able
DF-2754 - Verification Verdict
Status: reproduced Impact: denial_of_service Confidence: certain
Mechanism
sysctl_kern_stackgap validates only new_val > 0 (power-of-2 and <= 16*PAGE_SIZE); any negative int is accepted verbatim. exec then computes a STATIC gap ALIGN(-stackgap_random) (kern_exec.c:1170-1172), so a large negative value pushes the argv/env destination megabytes away from the stack mapping.
Verdict
REPRODUCED as root on the stock guest from a single process that restores the sysctl before exiting: kern.stackgap_random accepts -1073741824; execve of /usr/bin/true then fails system-wide (every new image dies - the kernel logged pid 1184 (true) uid 0 exited on signal 11) until the value is restored from the already-running process. One root sysctl write bricks exec for the whole system (no new process can run, including sysctl itself to undo it - recovery would need a reboot or a pre-positioned helper).
Threat / reachability
Root-only foot-gun / validation gap: a single sysctl permanently denies service; also a hostile-container-config primitive.
Privilege escalation assessment
n/a (requires root to set; breaks availability, not privilege)
Fix
Bound the negative (static-gap) form the same way: reject -new_val > 16*PAGE_SIZE.
Kernel references
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_testablefix.diff compile-validated in the batch combined kernel build (all 37 diffs of this batch applied to in-guest /usr/src; make -j6 nativekernel KERNCONF=X86_64_GENERIC, see batch build log referenced in VERDICT.md); no behavior A/B performed for this Info-tier item; behavior A/B intentionally skipped for Info tier
batch combined kernel build log (see findings/poc/_batch41 note in VERDICT.md)
Confirmed kernel references
Detail
Exploit chain
no escalation path: root-only sysctl; impact is system-wide exec denial (recovery needs a reboot or a pre-positioned process)
Evidence (decisive lines)
run.log: exec ok before; sysctl accepts -1073741824; exec fails system-wide after (kernel logged pid 1184 (true) uid 0 exited on signal 11); exec ok again after restore
PoC changes
/bin/true does not exist on DragonFly (first run compared ENOENT against ENOENT); switched to /usr/bin/true and the before/after/restore contrast became decisive. The PoC restores the sysctl from the same process before exiting so the guest is never bricked.
Verified recommended fix
Bound the negative (static-gap) form the same way: reject -new_val > 16*PAGE_SIZE.
Verdict
REPRODUCED as root on the stock guest from a single process that restores the sysctl before exiting: kern.stackgap_random accepts -1073741824; execve of /usr/bin/true then fails system-wide (every new image dies - the kernel logged pid 1184 (true) uid 0 exited on signal 11) until the value is restored from the already-running process. One root sysctl write bricks exec for the whole system (no new process can run, including sysctl itself to undo it - recovery would need a reboot or a pre-positioned helper).
No comments yet.