When writing a project, I ran into a strange issue.
This is the minimal code I managed to write to recreate the issue. I am intentionally storing an actual string in the
This is all because of -D_FORTIFY_SOURCE=2
intentionally crashing on what it decides is unsafe.
Some distros build gcc with -D_FORTIFY_SOURCE=2
enabled by default. Some don't. This explains all the differences between different compilers. Probably the ones that don't crash normally will if you build your code with -O3 -D_FORTIFY_SOURCE=2
.
Why does it fail only if optimization is on?
_FORTIFY_SOURCE
requires compiling with optimization (-O
) to keep track of object sizes through pointer casts / assignments. See the slides from this talk for more about _FORTIFY_SOURCE
.
Why does strcpy() fail? How can it?
gcc calls __memcpy_chk
for strcpy
only with -D_FORTIFY_SOURCE=2
. It passes 8
as the size of the target object, because that's what it thinks you mean / what it can figure out from the source code you gave it. Same deal for strncpy
calling __strncpy_chk
.
__memcpy_chk
aborts on purpose. _FORTIFY_SOURCE
may be going beyond things that are UB in C and disallowing things that look potentially dangerous. This gives it license to decide that your code is unsafe. (As others have pointed out, a flexible array member as the last member of your struct, and/or a union with a flexible-array member, is how you should express what you're doing in C.)
gcc even warns that the check will always fail:
In function 'strcpy',
inlined from 'main' at
(from gcc7.2 -O3 -Wall on the Godbolt compiler explorer).
Why doesn't
memcpy()
fail regardless of-O
level?
IDK.
gcc fully inlines it just an 8B load/store + a 1B load/store. (Seems like a missed optimization; it should know that malloc didn't modify it on the stack, so it could just store it from immediates again instead of reloading. (Or better keep the 8B value in a register.)