Kernel stack-smashing protection is a no-op: canary is an unseeded compile-time constant (0x00000aff) from a dead ABI, __stack_chk_guard/__stack_chk_fail absent from the kernel, and -fno-stack-protector is forced for all kernel/KLD code
Summary
The file implements the 2002 propolike ABI (__guard, link-time constant 0x00000aff, never reseeded - karc4random is never even called here; identical across independent boots and matching the ELF .data cell) while the modern ABI every supported compiler emits (__stack_chk_guard/__stack_chk_fail) is defined NOWHERE and kern.mk:7 forces -fno-stack-protector on all kernel/KLD code. Net: zero canary protection - live-proven by a default-built KLD doing a 48->16 strcpy: saved RBP smashed to 0x4242424242424242, kernel dead at the ret with no canary in frame; the -fstack-protector-all variant cannot even kldload (undefined __stack_chk_guard). Terminator property also wrong on LE (first NUL at byte 2). The threat is systemic: every kernel stack-overflow bug anywhere lands on completely unprotected frames on a no-SMEP/SMAP/KASLR guest. seed-at-SI_SUB_DRIVERS + modern-ABI + flip-to-strong fix (git-apply-able, validated boot-ordering against arc4 init).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5713 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 6.4 KB | β raw | |
| verdict.json | β | 5.0 KB | view raw | |
| guest/guard_read.c | β | 1.0 KB | view raw | |
| guest/ssp_smash/ | β | 4.0 KB | β download | |
| guest/guard_probe/ | β | 4.0 KB | β download | |
| guest/guard_panic/ | β | 4.0 KB | β download | |
| build.log | β | 4.2 KB | view raw | |
| run.log | β | 289 B | view raw | |
| run.2.log | β | 970 B | view raw | |
| run.3.log | β | 0 B | β download | |
| run.4.log | β | 53 B | view raw | |
| panic.txt | β | 921 B | view raw | |
| panic2.txt | β | 805 B | view raw | |
| console_full.log | β | 4.5 KB | view raw | |
| env.txt | β | 594 B | view raw | |
| fix.diff | β | 2.8 KB | view raw |
DF-5713 β stack_protector.c: kernel stack-smashing protection is a no-op
What this PoC proves (all demonstrated live on the QEMU guest)
-
The canary is a compile-time constant, never seeded.
sys/libkern/stack_protector.c:36(int __guard = 0x00000aff;) is the only definition/assignment of__guardin the entire tree (tree-wide grep). Live proof: -/dev/kmemread of the running kernel's__guard(0xffffffff8113bae0):ff 0a 00 00=0x00000affβ run.2.log - KLDguard_probe.koreads the exported symbol:__guard=0xaffβ run.2.log / msgbuf - The shipped/boot/kernel/kernelELF.datacell at the symbol address holdsff0a0000β identical, i.e. nothing rewrote it at boot. - Two independent fresh boots (vm.sh reset with-src) read the sameff 0a 00 00β no per-boot seeding exists (run.2.log, run.4.log). -
The modern stack-protector ABI does not exist in the kernel.
sys/contains no__stack_chk_guard/__stack_chk_faildefinition (tree-wide grep) andnm /boot/kernel/kernelconfirms absence. A KLD compiled with-fstack-protector-all(ssp_smash_ssp.ko, manually built because the build system strips the flag) fails to load:link_elf_obj_obj: symbol __stack_chk_guard undefinedβ panic.txt / console_full.log. -
Kernel code is force-built WITHOUT any stack protector.
sys/platform/pc64/conf/kern.mk:7βCFLAGS+= -fno-stack-protector ...(grep of the guest's/usr/src). The default-builtssp_smash.koperforms a 48-bytestrcpyinto a 16-byte kernel-stack buffer and the kernel dies instantly with the saved frame pointer smashed:frame pointer = 0x10:0x4242424242424242 ... Stopped at boom+0x2c: retβ panic.txt. No canary sits between the buffer and the saved frame. -
The legacy fail path works but is attached to nothing.
guard_panic.kocalls__stack_smash_handler()directly βpanic: stack overflow in function guard_panic_smash_testβ panic2.txt.
Build
# on the guest (sources pushed via the pack below): cc -O2 -o /tmp/guest/guard_read /tmp/guest/guard_read.c cd /tmp/guest/guard_probe && make -m /usr/share/mk cd /tmp/guest/guard_panic && make -m /usr/share/mk cd /tmp/guest/ssp_smash && make -m /usr/share/mk # default (no SSP) # SSP variant (build system strips -fstack-protector-all, so compile by hand): cc <kern flags> -fstack-protector-all -c ssp_smash.c -o ssp_smash_ssp.o cc -nostdlib -r -o ssp_smash_ssp.ko ssp_smash_ssp.o # exact flags in build.log
Run
/tmp/guest/guard_read 0xffffffff8113bae0 /dev/kmem # -> ff 0a 00 00, rc=42 kldload /tmp/guest/ssp_smash/ssp_smash_ssp.ko # -> Undefined symbol __stack_chk_guard kldload /tmp/guest/guard_probe/guard_probe.ko # -> __guard=0xaff kldload /tmp/guest/ssp_smash/ssp_smash.ko # -> kernel GPF, fp=0x4242...42 (PANIC) kldload /tmp/guest/guard_panic/guard_panic.ko # -> panic: stack overflow in function ... (PANIC) # leave the guest clean: vm.sh reset with-src
Expected results
See the four numbered points above; all captured in build.log, run.log, run.2.log, run.3.log, run.4.log, panic.txt, panic2.txt, console_full.log.
DF-5713 VERDICT β reproduced (hardening failure; impact: none by itself)
Bottom line: every factual claim of the finding was demonstrated live on
the DragonFly 6.5-DEVELOPMENT X86_64_GENERIC guest. The kernel's only
stack-canary symbol, __guard, is a compile-time constant
(sys/libkern/stack_protector.c:36, 0x00000aff) that is never seeded
from any entropy source at any point in boot; the modern
__stack_chk_guard/__stack_chk_fail ABI is absent from the kernel
entirely; and sys/platform/pc64/conf/kern.mk:7 force-disables
-fstack-protector for all kernel and KLD code. The "security-critical
mitigation layer" therefore protects exactly nothing: it is 51 lines of dead
code force-linked into every pc64 kernel
(sys/platform/pc64/conf/files:119), whose only exported value is a
publicly-known constant.
Mitigation-family checklist (as tasked)
(a) Canary source / seeding vs first use
- Source:
sys/libkern/stack_protector.c:36(0x00000affLE) /:38(0xff0a0000BE) β a link-time constant in .data, not karc4random. - Tree-wide grep for
__guardwrites: only lines 36/38. No SYSINIT, no reseed, nothing. The DF-5694/5695 (arc4 bias) and DF-2243 (zero-window) concerns are moot but in the worst way: the entropy path does not exist at all, so the canary is not "weakly random" β it is known to everyone who can read the public source. - Live:
/dev/kmemreadsff 0a 00 00on two independent fresh boots (run.2.log, run.4.log); the ELF.datacell at0xffffffff8113bae0holds the same bytes; KLDguard_probe.koreads0xaffthrough the exported symbol. Constant from link time through runtime, forever.
(b) Guard placement / infoleak surface
- Single global
intin.dataat a fixed link-time address (ffffffff8113bae0on this build), exported in the kernel symbol table (nm), readable by any loaded KLD and by root via/dev/kem///dev/mem. Not per-CPU, not per-boot. There is nothing to leak, though β the value is public in the source. (sys/libkern/stack_protector.c:36) - Notably the file is force-linked into every pc64 kernel via
sys/platform/pc64/conf/files:119(libkern/stack_protector.c standard) even though no in-tree code references__guardor__stack_smash_handler(tree-wide grep) β pure dead weight.sys/conf/files:2219has the equivalent line commented out; pc32 (i386) doesn't list the file at all.
(c) Fail path
__stack_smash_handler()panics withstack overflow in function <name>(sys/libkern/stack_protector.c:49; i386 variant at:47prints the caller PC from4(%ebp)β cosmetic only, and that asm is dead for amd64). Demonstrated live: loadingguard_panic.koβpanic: stack overflow in function guard_panic_smash_testwith a clean backtrace throughmodule_register_init(panic2.txt). The handler works; it is simply connected to no instrumentation.
(d) Terminator-canary property
- LE value bytes in memory:
ff 0a 00 00β first NUL at byte 2, not byte 0. A proper terminator canary places the NUL in the lowest byte so an ascendingstrcpystops inside the canary before touching anything past it. Here a byte-string overflow first clobbersff 0a(both publicly known, so an attacker with a controlled copy simply re-writes them) and terminates inside the canary. With a known constant canary the property is irrelevant anyway: any arbitrary-content overflow primitive (memcpy/bcopy/read) forges all 4 bytes. The BE variant (0xff0a0000β bytes00 00 0a fflowβhigh) has the NUL low, ironically correct β but pc64 is LE.
The decisive live demonstration (why this is a real hardening failure)
The default-built KLD ssp_smash.ko (exactly what bsd.kmod.mk produces:
-fno-stack-protector forced by sys/platform/pc64/conf/kern.mk:7)
executes a textbook 48-byte strcpy into a 16-byte kernel-stack buffer:
Fatal trap 9: general protection fault while in kernel mode frame pointer = 0x10:0x4242424242424242 Stopped at boom+0x2c: ret
The saved RBP is 0x4242β¦ β the attacker payload β and the kernel dies at
boom's ret on the way to the smashed return address. There is no
canary anywhere in that frame, because no kernel/KLD code is ever
instrumented. The identical module recompiled with -fstack-protector-all
(by hand; the build system silently strips the flag) cannot even load:
link_elf_obj_obj: symbol __stack_chk_guard undefined β the kernel
provides no modern ABI to link against.
Exploit chain
None developed β none exists through this file. The finding is a missing
mitigation (CWE-693), not a corruption primitive: unprivileged attack
surface of the file itself is zero (it defines two symbols and a panic).
The impact is systemic: every kernel stack-overflow bug in DragonFly
lands on unprotected frames, so any such bug elsewhere in sys/ is
strictly more exploitable than on a stack-protected kernel; and if SSP were
enabled without fixing the file, the only available canary
(__guard, propolice ABI) is a publicly-known constant β also decorative.
Why not uid0 / why impact=none
The panics in the evidence were root-deliberate (kldload of a test module). No unprivileged-reachable path through this file exists. Severity Low (hardening/defense-in-depth), confidence certain.
Fix validation
Not performed (fix_status: not_testable): the fix
(fix.diff β modern __stack_chk_guard/__stack_chk_fail, SYSINIT seeding
from karc4random_buf() after arc4_init_pcpu() has run, kern.mk switched
to -fstack-protector-strong) requires a full kernel rebuild/boot cycle
whose payoff is a hardening A/B, not a behavior A/B of a reproduced
corruption. git apply --check passes against the tree. Upstream should
build/boot-test fix.diff (especially the kern.mk hunk) before commit.
Guest state
Guest panicked twice (both deliberate evidence runs), restored with
vm.sh reset with-src after each; final state: up, clean snapshot,
read-only /dev/kmem probe only after the last reset. 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_testableFix not build-validated: this is a hardening finding (no memory-corruption reproduction to A/B), and the kern.mk hunk requires a full kernel rebuild+boot validation that belongs upstream. fix.diff passes git apply --check against the tree; SYSINIT ordering (SI_SUB_DRIVERS=0x2400000) is provably after arc4_init_pcpu (SI_BOOT2_FINISH_SMP=0x1c80000, sys/platform/pc64/x86_64/mp_machdep.c:571) so karc4random_buf() is safe at seed time.
findings/poc/DF-5713/fix.diff (git apply --check clean); ordering refs sys/platform/pc64/x86_64/mp_machdep.c:571, sys/sys/kernel.h:158,167,187
Confirmed kernel references
- sys/libkern/stack_protector.c:36
- sys/libkern/stack_protector.c:38
- sys/libkern/stack_protector.c:49
- sys/platform/pc64/conf/files:119
- sys/platform/pc64/conf/kern.mk:7
- sys/conf/files:2219
- stand/boot/dloader/Makefile:36
- sys/libkern/arc4random.c:168
Detail
Evidence (decisive lines)
run.2.log (live /dev/kmem read 'ff 0a 00 00', guard_probe KLD __guard=0xaff, objdump ELF cell ff0a0000); run.4.log (fresh boot #2 identical bytes β no per-boot seeding); panic.txt (link_elf_obj_obj: symbol __stack_chk_guard undefined + Fatal trap 9 with frame pointer 0x4242424242424242, Stopped at boom+0x2c: ret); panic2.txt (panic: stack overflow in function guard_panic_smash_test with backtrace); build.log (kmod CFLAGS show -fno-stack-protector from kern.mk; manual -fstack-protector-all build shows U __stack_chk_guard/__stack_chk_fail); console_full.log (full serial); guest/ (all sources); fix.diff (git apply --check clean)
PoC changes
Rewrote from scratch: original task sketch assumed karc4random-seeded __stack_chk_guard; the file actually implements the 2002 propolice ABI (constant int __guard + __stack_smash_handler). Built 4 artifacts: guard_read.c (/dev/kmem reader; fixed lseek error check for sign-extended kernel addresses), ssp_smash KLD (smash probe, two variants), guard_probe KLD (live symbol read), guard_panic KLD (fail-path trigger). bsd.kmod.mk (not kmod.mk) is the include name on DF; -fstack-protector-all must be compiled by hand because kern.mk strips it.
Verified recommended fix
Provide the modern ABI in this file (unsigned long __stack_chk_guard seeded once from karc4random_buf at SI_SUB_DRIVERS + __stack_chk_fail panic; also reseed legacy __guard) and flip kern.mk from -fno-stack-protector to -fstack-protector-strong; see fix.diff.
Verdict
Every claim reproduced live on the 6.5-DEVELOPMENT X86_64_GENERIC guest: the kernel's only canary symbol __guard is the compile-time constant 0x00000aff (sys/libkern/stack_protector.c:36) β /dev/kmem reads 'ff 0a 00 00' on two independent fresh boots and the ELF .data cell matches, so no seeding exists anywhere in boot (karc4random is never involved); the modern __stack_chk_guard/__stack_chk_fail ABI is absent from the kernel (a -fstack-protector-all KLD fails kldload with 'link_elf_obj_obj: symbol __stack_chk_guard undefined'); sys/platform/pc64/conf/kern.mk:7 force-disables -fstack-protector for all kernel/KLD code, so a default-built module's 48->16 kernel-stack strcpy smashes the saved frame pointer to 0x4242424242424242 and kills the kernel at boom+0x2c 'ret' with no canary in the frame; the legacy __stack_smash_handler panic path itself works (panic: stack overflow in function guard_panic_smash_test). The mitigation layer protects exactly nothing; it is dead code force-linked via sys/platform/pc64/conf/files:119 with a publicly-known canary value. Hardening failure (CWE-693/CWE-330): no unprivileged attack path through the file itself, hence impact none β but every kernel stack-overflow bug elsewhere in sys/ lands on unprotected frames, and any future SSP enablement would inherit the known-constant canary unless the file is fixed.
No comments yet.