|
|
Subscribe / Log in / New account

"Strong" stack protection for GCC

By Jake Edge
February 5, 2014

Stack buffer overflows are a longstanding problem for C programs that leads to all manner of ills, many of which are security vulnerabilities. The biggest problems have typically been with string buffers on the stack coupled with bad or missing length tests. A programmer who mistakenly leaves open the possibility of overrunning a buffer on a function's stack may be allowing attackers to overwrite the return pointer pushed onto the stack earlier. Since the attackers may be able to control what gets written, they can control where the function returns—with potentially dire results. GCC, like many compilers, offers features to help detect buffer overflows; the upcoming 4.9 release offers a new stack-protection mode with a different tradeoff between security and performance impact.

GCC has supported stack protection for some time. It currently supports two different types of stack protection. Recently, Google engineers have come up with another style that tries to chart a middle course between the two existing options. It has made its way into GCC 4.9 (expected later this year) and the upcoming 3.14 kernel has support for building with that option.

The basic idea behind stack protection is to push a "canary" (a randomly chosen integer) on the stack just after the function return pointer has been pushed. The canary value is then checked before the function returns; if it has changed, the program will abort. Generally, stack buffer overflow (aka "stack smashing") attacks will have to change the value of the canary as they write beyond the end of the buffer before they can get to the return pointer. Since the value of the canary is unknown to the attacker, it cannot be replaced by the attack. Thus, the stack protection allows the program to abort when that happens rather than return to wherever the attacker wanted it to go.

There is a downside to using canaries. The value must be generated and checked, which takes some time, but more importantly there must be code added to handle the canary for each function that is protected that way. That extra code results in some level of performance degradation, perhaps mostly due to a larger cache footprint. For this reason, it can make sense to restrict stack protection to a subset of all the functions in a program.

So the question has always been: "Which functions should be protected?" Putting stack protection into every function is both overkill and may hurt performance, so one of the GCC options chooses a subset of functions to protect. The existing -fstack-protector-all option will protect all functions, while the -fstack-protector option chooses any function that declares a character array of eight bytes or more in length on its stack. Some distributions have lowered that threshold (e.g. to four) in their builds by using the --param=ssp-buffer-size=N option.

That "character array" test catches the most "at risk" functions, but it leaves a number of other functions behind. As Kees Cook pointed out in a recent blog post, the Google Chrome OS team had been using -fstack-protector-all since the team is "paranoid", but a new -fstack-protector-strong option has been developed to broaden the scope of the stack protection without extending it to every function in the program.

In addition to the protections offered by -fstack-protector, the new option will guard any function that declares any type or length of local array, even those in structs or unions. It will also protect functions that use a local variable's address in a function argument or on the right-hand side of an assignment. In addition, any function that uses local register variables will be protected. According to Cook, Chrome OS has been using -fstack-protector-strong (instead of protecting all functions) for ten months or so.

During the 3.14 merge window, Linus Torvalds pulled Cook's patches to add the ability to build the kernel using the strong stack protection. In Ingo Molnar's pull request (and Cook's post), the results of using strong protection on the kernel were presented. The kernel with -fstack-protector turned on is 0.33% larger and covers 2.81% of the functions in the kernel. For -fstack-protector-strong, those numbers are an increase of 2.4% in code size over an unprotected kernel, but 20.5% of the functions are covered.

The CONFIG_CC_STACKPROTECTOR_STRONG kernel configuration option adds the strong protection, while the CONFIG_CC_STACKPROTECTOR option for the "regular" protection has been renamed to reflect that: CONFIG_CC_STACKPROTECTOR_REGULAR. The default CONFIG_CC_STACKPROTECTOR_NONE does just what its name would imply.

While stack protection certainly isn't a panacea for security woes, it will catch a significant portion of real-world attacks. Having an option that strikes a balance between the ultra-paranoid "all" and the regular variant (not to mention the wide-open "none" option) is likely to catch more bugs—and attack vectors. We will likely see some of the more security-conscious distributions building their user-space programs and kernels with the "strong" option moving forward.


Index entries for this article
SecurityTools/Compilers
SecurityVulnerabilities/Buffer overflow


to post comments

"Strong" stack protection for GCC

Posted Feb 6, 2014 6:25 UTC (Thu) by eru (subscriber, #2753) [Link] (2 responses)

In addition, any function that uses local register variables will be protected.

I don't understand this condition. How would using some "register int i" make a stack problem more likely? It seems to me it would have no effect on the issue, and when optimizing, the compiler treats "register" just as a hint that may be ignored anyway. Tried already looking at the linked gcc mailing list entry, but there was no explanation either.

"Strong" stack protection for GCC

Posted Feb 6, 2014 11:40 UTC (Thu) by sorokin (guest, #88478) [Link] (1 responses)

User 'kees' answered this question in comment to the blog post:

"It was to catch unusual ways to get a reference to the frame address, with things like “register unsigned rsp __asm__(“rsp”);”, etc"

"Strong" stack protection for GCC

Posted Feb 6, 2014 12:52 UTC (Thu) by nix (subscriber, #2304) [Link]

Note that 'register variables' in this context are not variables declared with the obsolescent 'register' qualifier: they are registers that use the local register variable language extension.

"Strong" stack protection for GCC

Posted Feb 7, 2014 4:08 UTC (Fri) by jtc (guest, #6246) [Link] (27 responses)

"Generally, stack buffer overflow (aka "stack smashing") attacks will have to change the value of the canary as they write beyond the end of the buffer before they can get to the return pointer."

Does the word 'generally' imply that there are some cases where this is not true (i.e., canary value does not have to be changed)? I suspect not. And if the answer is no, I believe the meaning of the sentence would be clearer without the introductory 'Generally, '.

"Strong" stack protection for GCC

Posted Feb 7, 2014 4:28 UTC (Fri) by jimparis (guest, #38647) [Link]

> Does the word 'generally' imply that there are some cases where this is not true (i.e., canary value does not have to be changed)? I suspect not. And if the answer is no..

The answer is definitely yes. The canary, which is checked before return, protects against the case that the return address was overwritten. But a buffer overflow may overwrite other things too that would be just as exploitable. For example, a local variable containing a function pointer that gets called before the function returns. Or even just an integer that is later used to index an array, which (after overflow) could be changed to point anywhere.

"Strong" stack protection for GCC

Posted Feb 7, 2014 15:01 UTC (Fri) by jzbiciak (guest, #5246) [Link] (5 responses)

jimparis above gave a couple excellent examples, but how about another?

void vulnerable( char *too_big )
{
    char too_small[8];

    // ...
    strcpy( too_small, too_big );
    // ...
}

On a machine whose stack grows up instead of down (Alpha was one such architecture), strcpy() could end up smashing its own return address due to a buffer overflow in its caller. A canary in vulnerable() won't help.

"Strong" stack protection for GCC

Posted Feb 7, 2014 15:04 UTC (Fri) by jzbiciak (guest, #5246) [Link] (2 responses)

And on machines whose stack grows down, if you have anything that fills a buffer in reverse (it happens, but it's admittedly far less common), you could do the same there.

"Strong" stack protection for GCC

Posted Feb 9, 2014 19:05 UTC (Sun) by eru (subscriber, #2753) [Link] (1 responses)

if you have anything that fills a buffer in reverse (it happens, but it's admittedly far less common), you could do the same there.

You could have two canaries: the second one is at the opposite end of the stack frame. A colleague actually added this feature to an in-house C compiler. The run-time system then reports which canary got killed. This was done to help catch programming errors, not so much for security.

"Strong" stack protection for GCC

Posted Feb 9, 2014 20:21 UTC (Sun) by jzbiciak (guest, #5246) [Link]

I'm not sure why that didn't occur to me here, as I've actually done the same in my own debugging infrastructure.

For example, in our DSP assembly kernel test benches, we actually went further than keeping a single canary word. We actually kept a sizeable buffer zone ahead of and behind the "live" data in the test bench, since some DSP algorithms write results separated by particular stride. (Think column accesses in a 2-D array for one common class.) I also wrote a "dmalloc" wrapper that put pad at both ends as well. Of course, that was all back in the 90s, when writing large amounts of assembly code for DSPs was much more commonplace.

But, as you said, it was more for catching programming errors than for preventing security exploits.

"Strong" stack protection for GCC

Posted Feb 7, 2014 18:19 UTC (Fri) by deater (subscriber, #11746) [Link] (1 responses)

> On a machine whose stack grows up instead of down (Alpha was one such
> architecture),

I'm pretty sure the stack grows down on Alpha systems.

You might be thinking of HP PA-RISC where the stack grows up.

"Strong" stack protection for GCC

Posted Feb 7, 2014 21:21 UTC (Fri) by jzbiciak (guest, #5246) [Link]

Quite possibly. For some reason I had it cached in my head that Alpha grew upwards. I tried to look it up, and found it's a surprisingly difficult topic to Google! Terms like "alpha", "stack" and "direction" turn up a lot of noise.

I did find a BUGTRAQ posting that indicates that the return value is stored below the other variables in a local stack frame, so the effect would be similar despite the stack itself growing down. If that's the case, that could be what I'm remembering.

The principle holds more generally that the frame you smash to trigger an exploit may not be the frame that holds the original array.

"Strong" stack protection for GCC

Posted Feb 13, 2014 19:37 UTC (Thu) by Pc5Y9sbv (guest, #41328) [Link]

The underlying problem is an unchecked array bounds violation, where the array was allocated on the stack rather than the heap. There is no reason to assume stack-smashing is only on char buffers using bytewise traversal.

These canaries can be untouched by buggy code that happens to stride over the canary when writing, whether due to a per-iteration stride or due to some other constant or computed offset introduced by the buggy, unchecked access logic.

Even much more expensive per-access checks, like Purify, cannot catch 100% of array errors in a language like C using typical ABIs. It is not enough to just test whether an accessed address is a valid structure/variable, but you must reconstitute a triplet of info for the check: base address of pointer, offset from base, and size of object at base address; an access is wrong if the offset takes it beyond the object's size, regardless of whether this takes it to another valid object. But this triplet of information is not readily available in a program when general pointer arithmetic is allowed, and everything is reduced to machine addresses in the ABI.

"Strong" stack protection for GCC

Posted Feb 13, 2014 20:59 UTC (Thu) by fw (subscriber, #26023) [Link] (1 responses)

There are cases where a buffer overflow is exploitable even without overwriting addresses and redirecting execution. A good example is CVE-2001-0797, where you could overwrite the variable which indicated that the user has authenticated successfully.

"Strong" stack protection for GCC

Posted Jun 15, 2014 9:03 UTC (Sun) by mina86 (guest, #68442) [Link]

Another thing that -fstack-protector does is rearrange local variables so that arrays start at higher addresses than non-array variables. I haven't found specifics of CVE-2001-0797, but presumably it would solve the problem.

"Strong" stack protection for GCC

Posted Feb 14, 2014 5:06 UTC (Fri) by ewimberley (guest, #95544) [Link] (16 responses)

Integer overflows/underflows are an example of a write-what-where that allows you to skip the canary completely. If your corruption target is referenced before function return then you can write over the canary without fear of triggering a stack check. In this case the target can be anywhere higher in memory than the overflow (even in another stack frame completely).

If you want a bunch of examples check the code in my github repo:
https://github.com/ewimberley/AdvancedMemoryChallenges

"Strong" stack protection for GCC

Posted Feb 14, 2014 16:31 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (15 responses)

So the Mill CPU has "Not a Result" as a possible result of instructions. Using this, it has 4 variants of, e.g., "add": saturating, wrap-around, sum+overflow flag, and overflow-error (which returns a NaR, *not* a value, which errors upon use (and is tagged with information to help figure out where it came from)). I'd like to see the day where the CPU assists with over and underflow detection.

"Strong" stack protection for GCC

Posted Feb 14, 2014 17:06 UTC (Fri) by mpr22 (subscriber, #60784) [Link] (14 responses)

CPUs have offered some assistance with overflow detection for over half a century (System/360 (1964) had a branch-on-overflow insn). The primary responsibility for arithmetic overflow detection not being ubiquitous in real-world software lies not with CPU designers and implementers, but with the designers and implementers of languages that don't take advantage of that assistance and the programmers who chose not to use, or not to press for the creation of, languages that do.

"Strong" stack protection for GCC

Posted Feb 14, 2014 18:10 UTC (Fri) by mathstuf (subscriber, #69389) [Link]

So the difference here is that the Mill will only cause a failure on *use* of the result, not the *generation* of the result. If you overflow an integer but never use it…who cares? The nice thing about it is that it also doesn't increase code size or extra time, so there's no overhead for the check and you don't need an instrumented build to find the problem(s).

Really, I'd just like for a C compiler to use the error-on-overflow for signed addition and such so that the undefined behavior is able to be caught without extra cost instead of it just being "some random value".

"Strong" stack protection for GCC

Posted Feb 15, 2014 13:30 UTC (Sat) by renox (guest, #23785) [Link] (5 responses)

*some* CPU help for detecting cheaply under/over flow, but unfortunately except for the MIPS they are quite confidential: neither x86 or ARM provide it..

"Strong" stack protection for GCC

Posted Feb 15, 2014 14:23 UTC (Sat) by PaXTeam (guest, #24616) [Link] (4 responses)

so what do into/jo/jno/bvs/bvc do?

"Strong" stack protection for GCC

Posted Feb 15, 2014 22:39 UTC (Sat) by kleptog (subscriber, #1183) [Link] (3 responses)

It's always bugged me that CPUs have all sorts of flags (Carry, Overflow & Zero) yet they're not exposed at the C level. Not even as GCC builtins. There are tricks with pushf but they're workarounds.

To be fair, the C language doesn't make it easy since there's no nice syntax for returning multiple values. So you get alternatives like:

res = check_overflow_sadd(a, b, &overflow)
if(overflow)
    error();
or
if(check_overflow_sadd(a,b))
    error();
res = a+b;
neither of which are really nice. But there are many places where the carry and overflow bits could be used to simplify programs and make them more readable.

"Strong" stack protection for GCC

Posted Feb 16, 2014 1:46 UTC (Sun) by mathstuf (subscriber, #69389) [Link] (2 responses)

The problem with a C abstraction is that on CPU models without instructions or flags for it, your simple addition is now how many instructions even in release and -Os builds.

And this is what Mill offers you…sane behavior with zero code change. If the compiler detects custom overflow detection or whatever, just change the flavor of 'add' to use. There's nothing against such optimizations (and other targets could implement it too, but if you now have to stall the pipeline to fetch a flag, it might not really be worth it overall). *Detecting* them might be hard to Turing-complete, but that never stopped us from parsing Perl or C++, has it ;) .

"Strong" stack protection for GCC

Posted Feb 16, 2014 11:03 UTC (Sun) by paulj (subscriber, #341) [Link] (1 responses)

Well, even on CPUs with flags, adding branching checks for them after every arithmetical instruction would be a noticeable overhead. That said, I think it'd still be very worthwhile on certain lumps of code. Sometimes correctness and helping programmers guard against their own omniscience matters a lot more than performance!

"Strong" stack protection for GCC

Posted Feb 16, 2014 11:08 UTC (Sun) by paulj (subscriber, #341) [Link]

Sigh... guard against their own _lack_ of omniscience.

"Strong" stack protection for GCC

Posted Feb 16, 2014 10:25 UTC (Sun) by paulj (subscriber, #341) [Link] (6 responses)

And of course many popular CPUs (x86, MIPS, ARM), have overflow and carry-out flags in their ISA, which will be set appropriately by integer arithmetical instructions. These could also provide protection.

The problem is in the C implementations. Overflow is undefined behaviour in C, so implementations *could* have chosen to implement some kind of trap or exception (signal, etc) in response. Yet, AFAIK, most don't and silently allow overflow to occur. (I'd be curious to hear about any that do). When C runtimes generally don't provide a way to trap overflows, then it becomes very difficult for any other languages or runtimes to do so, unless they bypass C.

This is probably a lamentable state of affairs. Down to decisions made in the days when performance was king and other factors like correctness and security weren't really a consideration (relative to today). Decisions which, in my view, certainly don't serve us well anymore.

There's a longer argument about whether traps would have been more efficient than flags that have to be checked, and whether traps might have been more likely to be implemented. Still, there is surely lots of code where the security benefits of runtime overflow-checking would outweigh any performance costs? If there were a way to enable error/trap-on-overflow (with unhandled leading to termination), that could be quite useful. If it existed, it might possibly be nice to be able to enable/disable this just on a per-file or even function basis, to limit the overheads.

I'd be curious to read more about the relative costs of the overheads, and the effectiveness of overflow flag checking on current ISAs, if anyone knows.

GCC has an "-ftrapv" argument which I was hoping might do this, and use hardware flags when possible. Though, a trivial test-case (adding command-line arguments) doesn't behave any differently with overflow when compiled with ftrapv on x86-64 and happily runs past an overflow, so maybe I've misunderstood what it's meant to do (the trapv compiled code uses __addvdi3 for the addition, if -O isn't passed). ?? With -O it seems to be using leaq to generate the addition.

"Strong" stack protection for GCC

Posted Feb 16, 2014 10:47 UTC (Sun) by jem (subscriber, #24231) [Link] (1 responses)

"Overflow is undefined behaviour in C."

To be more precise, signed overflow is undefined behaviour in C. Unsigned integers are defined to wrap around.

"Strong" stack protection for GCC

Posted Feb 16, 2014 10:58 UTC (Sun) by paulj (subscriber, #341) [Link]

I wish LWN had +1 buttons, so I wouldn't have to make comments like this. :)

"Strong" stack protection for GCC

Posted Feb 16, 2014 11:06 UTC (Sun) by mchapman (subscriber, #66589) [Link] (3 responses)

> Though, a trivial test-case (adding command-line arguments) doesn't behave any differently with overflow when compiled with ftrapv on x86-64 and happily runs past an overflow

It needs to be a 64-bit signed integer. An int is likely to be only 32 bits.

"Strong" stack protection for GCC

Posted Feb 16, 2014 11:09 UTC (Sun) by mchapman (subscriber, #66589) [Link]

> It needs to be a 64-bit signed integer. An int is likely to be only 32 bits.

On second thoughts, that may be a compiler bug.

At any rate, I could only get it to work on GCC 4.8.2 if I used a 64-bit integer.

"Strong" stack protection for GCC

Posted Feb 16, 2014 11:13 UTC (Sun) by paulj (subscriber, #341) [Link] (1 responses)

Ah. Indeed, with longs instead and a 64bit overflow it core-dumps.

Hmm, that's pretty limited in usefulness so, and buggy with respect to what the documentation suggests it does (the docs don't qualify when overflow checks will actually be done). :(

"Strong" stack protection for GCC

Posted Feb 16, 2014 11:15 UTC (Sun) by mchapman (subscriber, #66589) [Link]

> and buggy with respect to what the documentation suggests it does

Yes, indeed: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52478

"Strong" stack protection for GCC

Posted Mar 11, 2020 14:49 UTC (Wed) by randguy (guest, #137701) [Link] (1 responses)

Hi,

I am using Yocto project for our BSP. I tried adding CONFIG_CC_STACKPROTECTOR_STRONG in the kernel configuration and later added -fstack-protector-strong in makefile of our application too. Still the cfa_problems_report generated from ISAFW (meta-security-isafw) claims that our application does not have stack protector. Please let me know if am missing anything.

More on meta-security-isafw:
https://www.nccgroup.trust/globalassets/our-research/us/w...

please refer
page number: 14,
section:3.2.2

"Strong" stack protection for GCC

Posted Mar 17, 2020 22:21 UTC (Tue) by nix (subscriber, #2304) [Link]

-fstack-protector-strong does not stack-protect every function, only those with char arrays. If you want to stack-protect literally *everything*, use -fstack-protector-all. (This really does have a noticeable performance impact, though, and I'm not sure the kernel has been tested with it. It certainly requires more attention, because things like kernels often have functions in assembler which either assume that they themselves, or that functions they call, do not use the stack-protector -- and if those functions don't use char arrays, they will always be right: so they are suddenly broken by the use of -fstack-protector-all. It took me a while to fix all the glibc bugs in this area. God knows what kernel problems would show up.)


Copyright © 2014, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds