There will be a new option in gcc 4.9 named “-fstack-protector-strong
“, which offers an improved version of “-fstack-protector
” without going all the way to “-fstack-protector-all
“. The stack protector feature itself adds a known canary to the stack during function preamble, and checks it when the function returns. If it changed, there was a stack overflow, and the program aborts. This is fine, but figuring out when to include it is the reason behind the various options.
Since traditionally stack overflows happen with string-based manipulations, the default (-fstack-protector
), only includes the canary code when a function defines an 8 (--param=ssp-buffer-size=N
, N=8 by default) or more byte local character array. This means just a few functions get the checking, but they’re probably the most likely to need it, so it’s an okay balance. Various distributions ended up lowering their default --param=ssp-buffer-size
option down to 4, since there were still cases of functions that should have been protected but the conservative gcc upstream default of 8 wasn’t covering them.
However, even with the increased function coverage, there are rare cases when a stack overflow happens on other kinds of stack variables. To handle this more paranoid concern, -fstack-protector-all
was defined to add the canary to all functions. This results in substantial use of stack space for saving the canary on deep stack users, and measurable (though surprisingly still relatively low) performance hit due to all the saving/checking. For a long time, Chrome OS used this, since we’re paranoid. :)
In the interest of gaining back some of the lost performance and not hitting our Chrome OS build images with such a giant stack-protector hammer, Han Shen from the Chrome OS compiler team created the new option -fstack-protector-strong
, which enables the canary in many more conditions:
- local variable’s address used as part of the right hand side of an assignment or function argument
- local variable is an array (or union containing an array), regardless of array type or length
- uses register local variables
This meant we were covering all the more paranoid conditions that might lead to a stack overflow. Chrome OS has been using this option instead of -fstack-protector-all
for about 10 months now.
As a quick demonstration of the options, you can see this example program under various conditions. It tries to show off an example of shoving serialized data into a non-character variable, like might happen in some network address manipulations or streaming data parsing. Since I’m using memcpy
here for clarity, the builds will need to turn off FORTIFY_SOURCE, which would also notice the overflow.
#include <stdio.h> #include <stdlib.h> #include <string.h> struct no_chars { unsigned int len; unsigned int data; }; int main(int argc, char * argv[]) { struct no_chars info = { }; if (argc < 3) { fprintf(stderr, "Usage: %s LENGTH DATA...\n", argv[0]); return 1; } info.len = atoi(argv[1]); memcpy(&info.data, argv[2], info.len); return 0; }
Built with everything disabled, this faults trying to return to an invalid VMA:
$ gcc -Wall -O2 -U_FORTIFY_SOURCE -fno-stack-protector /tmp/boom.c -o /tmp/boom $ /tmp/boom 64 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (core dumped)
Built with FORTIFY_SOURCE enabled, we see the expected catch of the overflow in memcpy
:
$ gcc -Wall -O2 -D_FORTIFY_SOURCE=2 -fno-stack-protector /tmp/boom.c -o /tmp/boom $ /tmp/boom 64 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA *** buffer overflow detected ***: /tmp/boom terminated ...
So, we’ll leave FORTIFY_SOURCE disabled for our comparisons. With pre-4.9 gcc, we can see that -fstack-protector
does not get triggered to protect this function:
$ gcc -Wall -O2 -U_FORTIFY_SOURCE -fstack-protector /tmp/boom.c -o /tmp/boom $ /tmp/boom 64 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (core dumped)
However, using -fstack-protector-all
does trigger the protection, as expected:
$ gcc -Wall -O2 -U_FORTIFY_SOURCE -fstack-protector-all /tmp/boom.c -o /tmp/boom $ /tmp/boom 64 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA *** stack smashing detected ***: /tmp/boom terminated Aborted (core dumped)
And finally, using the gcc snapshot of 4.9, here is -fstack-protector-strong
doing its job:
$ /usr/lib/gcc-snapshot/bin/gcc -Wall -O2 -U_FORTIFY_SOURCE -fstack-protector-strong /tmp/boom.c -o /tmp/boom $ /tmp/boom 64 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA *** stack smashing detected ***: /tmp/boom terminated Aborted (core dumped)
For Linux 3.14, I’ve added support for -fstack-protector-strong
via the new CONFIG_CC_STACKPROTECTOR_STRONG
option. The old CONFIG_CC_STACKPROTECTOR
will be available as CONFIG_CC_STACKPROTECTOR_REGULAR
. When comparing the results on builds via size
and objdump -d
analysis, here’s what I found with gcc 4.9:
A normal x86_64 “defconfig” build, without stack protector had a kernel text size of 11430641 bytes with 36110 function bodies. Adding CONFIG_CC_STACKPROTECTOR_REGULAR
increased the kernel text size to 11468490 (a +0.33% change), with 1015 of 36110 functions stack-protected (2.81%). Using CONFIG_CC_STACKPROTECTOR_STRONG
increased the kernel text size to 11692790 (+2.24%), with 7401 of 36110 functions stack-protected (20.5%). And 20% is a far-cry from 100% if support for -fstack-protector-all
was added back to the kernel.
The next bit of work will be figuring out the best way to detect the version of gcc in use when doing Debian package builds, and using -fstack-protector-strong
instead of -fstack-protector
. For Ubuntu, it’s much simpler because it’ll just be the compiler default.
© 2014 – 2017, Kees Cook. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 License.