-fstack-protector-strong vs -fstack-protector-all under a runtime-overhead budget
0 reputation · 29 Sept 2022, 16:16 UTC
0 reputation · 29 Sept 2022, 16:16 UTC
A production GCC build requires stack-buffer-overflow mitigation under a runtime-overhead budget with attention to code size and instruction-cache effects. The decision is between -fstack-protector-strong and -fstack-protector-all, where coverage is not equivalent and overhead is workload and target dependent.
The build may target a custom or freestanding C library, so handling of a canary mismatch is a runtime dependency outside the compiler. The choice interacts with other hardening such as -D_FORTIFY_SOURCE=2, which addresses a different class of overflows.
Which function classes remain uninstrumented with -fstack-protector-strong for a typical C codebase and what threat model gap does that represent? What runtime dependency must be satisfied for canary failure handling on a target with a custom or freestanding C library? What factors dominate the overhead difference between strong and all under a defined runtime-overhead budget?
Under a defined runtime-overhead budget, -fstack-protector-strong is the correct default. It instruments functions containing local arrays or references to local arrays, while -fstack-protector-all instruments every function. The marginal security gain from -all rarely justifies the binary-size growth and instruction-cache pressure unless your threat model explicitly includes scalar-only functions that process attacker-controlled data.
With -fstack-protector-strong, functions whose local variables are exclusively scalars—plain integers, pointers, or structs without array members—remain uninstrumented. The gap is that unsafe pointer arithmetic or a mis-indexed scalar pointer can still corrupt saved registers or a return address in those frames without tripping a canary check. In typical C code this is a narrower surface than array-heavy code, but it is not zero.
On a custom or freestanding C library target, your runtime must supply two symbols emitted by the compiler: __stack_chk_guard (the canary value) and __stack_chk_fail (the mismatch handler). If __stack_chk_guard is initialized to a fixed, predictable constant, the protection is neutralized. Initialize it from an entropy source at startup and make __stack_chk_fail a non-returning abort or reset.
The dominant factor is the number of instrumented functions multiplied by their call frequency. -fstack-protector-all adds prologue and epilogue canary logic to every function, increasing code size and instruction-cache pressure across the whole binary. Runtime cost scales with how often instrumented routines are entered and how well the target CPU predicts the canary-check branch. Scalar-only functions contribute pure overhead under -all without expanding meaningful coverage.
-D_FORTIFY_SOURCE=2 complements this choice by adding size checks to vulnerable libcalls; it does not replace canary protection.
Use scoped, read-only checks to confirm behavior:
# Inspect a specific scalar-only function for canary setup
objdump -d binary | sed -n '/<func_name>:/,/^$/p' | grep -E 'fs:0x28|__stack_chk'# Confirm the failure symbol is linked
nm binary | grep __stack_chk_failCompare the two configurations with perf stat -e instructions,cache-misses,cycles on a representative workload.
The single diagnostic detail that would change this recommendation is whether your hottest functions contain only scalar locals while handling attacker-controlled data. If they do, the residual risk may justify -fstack-protector-all despite the overhead.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.