Stack Smashing Protector: Difference between revisions

[unchecked revision][unchecked revision]
Content deleted Content added
Rewrite article
m Bot: Replace deprecated source tag with syntaxhighlight
 
(8 intermediate revisions by 5 users not shown)
Line 1:
The '''Stack Smashing Protector''' ('''SSP''') compiler feature helps detect stack buffer overrun by aborting if a secret value on the stack is changed. This serves a dual purpose in making the occurrence of such bugs visible and as exploit mitigation against return-oriented programming. SSP merely detects stack buffer overruns, they are not prevented. The detection can be beaten by preparing the input such that the stack canary is overwritten with the correct value and thus does not offer perfect protection. The stack canary is native word sized and if chosen randomly, an attacker will have to guess the right value among 2^32 or 2^64 combinations (and revealing the bug if the guess is wrong), or resort to clever means of determining it.
 
== Description ==
 
Compilers implement this feature by selecting appropriate functions, storing the stack canary during the function prologue and, checking the value atin the epilogue, and invoking a failure handler if it was changed. For instance, consider the code:
<sourcesyntaxhighlight lang="c">
void foo(const char* str)
{
Line 10:
strcpy(buffer, str);
}
</syntaxhighlight>
</source>
SSP automatically illustratively transforms that code into this:
<sourcesyntaxhighlight lang="c">
/* Note how buffer overruns are undefined behavior and the compilers tend to
optimize these checks away if you wrote them yourself, this only works
Line 26:
__stack_chk_fail();
}
</syntaxhighlight>
</source>
Note how the secret value is stored in a global variable (initialized at program
load time) and is copied into the stack frame, and how the it is safely erased from
from the stack as part of the check. Since stacks grow downwards on many architectures,
architectures, the canary gets overwritten whenever input to strcpy is atmore than 16 leastcharacters.
16 characters. The caller return pointer exploited in return-oriented programming attacks is
programming attacks is not accessed until after the value was validated, thus preventing such attacks.
defusing such attacks.
 
The detection ismethod works perfectbecause it is a impossible to fakeget the correct value, i.e. thevia
trial and error. Since one incorrect canary value prevents further alterations,
attacker doesn't have full control over what bytes can be written. The attacker
cannotan changeattacker furthercannot stackkeep contentstrying undetected if fakinguntil the correct value is found. In the example
above, if the canary contained a zero byte, it would be impossible to guess its
stops the output. For instance, if the canary in the strcpy example above
existence and position by trial and error. This forces the attacker to either
contains a zero byte, it is impossible to fake that byte in the canary without
not attack, or be detected and be unable to alter the stack any further. This
stopping the output. This forces the attacker to either not attack, be detected,
does not mean that the buffer cannot be exploited. For example, if 16 bytes are
or not change any further stack contents. This doesn't mean the buffer overrun
written to the buffer above and it is not null-terminated, unintended behaviour
is always unexploitable: The string is now 16 characters instead of the intended
can still take place later on during program execution.
limit of 15 characters, this can cause other unintended behavior during the
continued program execution.
 
Note how there is only a single protective value, not every variable is
protected in this manner. The aOne heuristic isordering often used, thatwith firstthe (downwards)stack
storesgrowing downwards, is first storing the canary, then buffers (that might overflow into each other) and
overflow into each other) and finally all the small variables unaffected from overruns. This is based on theby
overruns. This is based on the idea that it is generally less dangerous if arrays are modified, compared to
arrays are modified, compared to variables that hold flags, pointers and function pointers, which may more
function pointers, which may more seriously alter execution.
 
Some compilers randomize the order of stack variables and randomize the stack
Line 60 ⟶ 58:
== Usage ==
 
Compilers such as [[GCC]] enablesenable this feature if requested through compiler
options, or if the compiler supplier enabled it by default. It is worth
considering enabling it by default if your operating system is security
conscious and you provide support. It is possible to use it in your entire
operating system (even kernel and standard library);, perhaps excusingexcluding ports with
really poor code quality). TheA feature enabled with the righta <tt>-ffoo option</tt> andoption can be
can be disabled with the <tt>-fno-foo</tt> counterpart. Several options exist that
provide different variants of SSP:
 
Line 85 ⟶ 83:
When you activate the feature, the compiler will attempt to link in libssp and
libssp_nonshared (if statically linked) for run-time support. This is disabled
if you pass <tt>-nostdlib</tt> as you do when linking a kernel and you'll need to supply
supply your own implementation. For user-space, you have two options:
 
* Supply your own implementation in libc (so libc can take advantage of the feature) and install empty libssp and libssp_nonshared libraries (or change your toolchain to not use them).
* Use the libssp implementation that comes with GCC.
 
It should also be noted that with the optimisations enabled via <tt>-O<n></tt> in GCC, the compiler may or may not "inline" a function. If a function has been inlined, then '''stack smash protection will not work for that function.''' To prevent this, one must use the <tt>noinline</tt> attribute like so:
<syntaxhighlight lang="c">
void __attribute__ ((noinline)) foo( /* args */ )
{
// Code goes here
}
</syntaxhighlight>
 
Disabling inlining in GCC can be done with the <tt>-fno-inline</tt> compile flag, however, that will not inline functions with the <tt>inline</tt> attribute. The <tt>-fno-inline-functions</tt> will not inline functions optimised with <tt>-O<n></tt>; but that has been proven ineffective for GCC versions 3.4.5 and over ([https://gcc.gnu.org/bugzilla/show_bug.cgi?id=28120 see bug report]).
 
If any tests do not work when trying to trip the protective mechanism, this may be the reason why it does not work!
 
== Implementation ==
Line 96 ⟶ 106:
failure handler. For instance, a minimal implementation could be:
 
<sourcesyntaxhighlight lang="c">
#include <stdint.h>
#include <stdlib.h>
Line 118 ⟶ 128:
}
 
</syntaxhighlight>
</source>
 
Note how the secret guard value is hard-coded rather than being decided during
Line 124 ⟶ 134:
the kernel) randomize the values. You can do this by putting the guard value in
a special segment that the loader knows to randomize. The numbers shown here are
not special, they are just exampleexamples of randomly generated numbers. You can still
take advantage of the bug-discovering properties of SSP even if the guard value
is not cryptographically secure (unless you anticipate sufficiently- obscure bugs
that intelligently circumvent SSP).
 
Line 133 ⟶ 143:
protection. This approach adds code complexity and early phases where language
features are not online. You may take such approaches with thread-local storage,
errno, paging, gdtGDT, scheduling, and so on, and suddenly a bootstrap is very
complex with many dependencies between language features. Once a function built
with stack-smashing protection is run, the guard value cannot be changed or a
Line 142 ⟶ 152:
Beware how you implement the stack smash detection handler: This code is only
run in cases where the bug was triggered innocently, or where the bug is being
exploited maliciously. By now the attacker is assumed to have, at least,
corrupted an unknown amount of this thread's stack. This means the environment
is hostile. The stack is currently under your control and none of the new local
Line 178 ⟶ 188:
The ideal approach is perhaps to have a special system call that does these
tasks and invoke it unconditionally and immediately. Kernel code must not trust
user-space code or be unsafely influenced it by it, so it can be considered
safe. It can then stop all threads in the process, investigate where the issue
seemed to occur in the process, and alert the user or system administrator
Line 185 ⟶ 195:
== libssp ==
 
Alternatively, to your own implementation, you can use the implementation that comes with GCC. This means you have to build libssp as part of your toolchain.
 
'''TODO''': I have never built it for osdev purposes before, but I guess that you do <tt>make all-target-libssp</tt> and <tt>make install-target-libssp</tt> like with libstdc++. It's probable that depends on libc for no good reason at all (as the gcc developers put fortify source functions in it and it wants to check whether they work).
Line 191 ⟶ 201:
The libssp approach is to have an initialization function marked as attribute constructor, which is run among the global constructors during process startup. This means SSP isn't properly online during the early parts of process initialization (but perhaps that's not a problem if all those C stack frames are gone before that point and the default null guard value was used until now). The startup code then proceeds to attempt opening <tt>/dev/urandom</tt> which might fail if you are in a chroot, are out of file descriptors, or your system doesn't have such a file (perhaps by design). If it fails, it falls back on a reasonable but known value. You can read the [https://gcc.gnu.org/viewcvs/gcc/trunk/libssp/ssp.c?view=markup#l67 libssp initialization code here].
 
The libssp <tt>__stack_chk_fail</tt> implementation tries to open the terminal, construct an error message with alloca, then use write to output it,. ifIf the terminal isn't accessible, it tries to the system log. It then attempts to destroy the process by invoking <tt>__builtin_trap()</tt>, writing a 0 int to the int at -1 (which is also undefined behavior and an unaligned pointer, in addition to probably crashing), and finally attempting to <tt>_exit().</tt> This exiting strategy doesn't feel super robust. You can read the [https://gcc.gnu.org/viewcvs/gcc/trunk/libssp/ssp.c?view=markup#l96 libssp handler code here].
 
Read the secure handling section above and read the code, then decide whether you want this linked into your programs, or whether it is cleaner to make your own implementation. You can also modify this code as part of your [[OS Specific Toolchain]].
 
== See Also ==
=== Articles ===
* [[Undefined_Behavior_Sanitization|Undefined Behavior Sanitization]]
 
=== Threads ===