Re: StackGuard: Automatic Protection From Stack-smashing Attacks

Tim Newsham (newsham@LAVA.NET)
Fri, 19 Dec 1997 08:55:31 -1000

> StackGuard: Automatic Detection and Prevention of Buffer-Overflow Attacks

>From the online paper:
> StackGuard detects and defeats stack smashing attacks by protecting the
> return address on the stack from being altered. StackGuard has two
> mechanisms to protect the return address: one provides greater assurance,
> and the other provides greater performance.

You are only protecting the return address. This means many programs
will still be vulnerable to overflow attacks. In particular you
don't protect the overflow of locals in a procedure, nor the overflow
of globals in the data segment or heap. While it does stop the
"cookbook" stack overflow attacks, it does not really put an end
to the problem. Consider for example the following (contrived but
not entirely fictional) examples:

int save_uid;
char buf[10];

save_uid = getuid();
setuid(0);
fp = fopen("input", "r");
fscanf(fp, "%s", buf);
setuid(save_uid);

overflowing the buffer will allow the user to increase his priveledge
for the duration of the program execution, which may be a very bad thing.

int x, *ptr;
char buf[10];

ptr = get_some_pointer();
x = compute_some_value();
strcpy(buf, getenv("SOME_ENV"));
*ptr = x;

overflowing the buffer allows the user to overwrite any 4 bytes
in the entire program. Clearly not good.

char cmdbuf[100], username[10];

cmdbuf[0] = 0;
strcat(cmdbuf, "/bin/cat ");
strcat(cmdbuf, filename);
getusername(username);
if(strcmp(username, "foobar") == 0)
return;
system(cmdbuf);

overflowing the username buffer allows an attacker to overwrite the
buffer that gets past to system(). Not good.

These are just examples of attacks involving locals. Attackers can
also overwrite globals that are in the data and bss segments as
well as globals allocated dynamically on the heap. The end result
is that an attacker can overwrite data in a security critical program
that was never intended to be user changeable. This changes the
entire security dynamics of the program. The program may still
be secure after the change, or it may not be. The attackers job
is then to find out how to manipulate the program given the
constraints of the buffer he can overflow.

Further in the paper you discuss two techniques that can be used:

> The higher performance StackGuard mechanism is to instrument the stack
> frame with a "canary" word laid right next to the return address on
> the stack. If an attacker attempts to smash the stack and change the
> return address, the attacker will necessarily overwrite the canary word as
> well. The code emitted by the compiler to do function returns is enhanced
> to check the integrity of the stack by looking for the canary word: if
> the canary word has been changed, the program aborts instead of yielding
> control to the attacker. The canary word is selected at random when
> the process starts, to make it difficult for the attacker to guess the
> canary value.

Obviously this technique is not totally secure (which you note by
calling the second technique "high assurance"). By adjusting the
size of the "canary" you can probably reduce the chances of effective
guessing of the canary pretty low (assuming you have a good source
of true randomness). Obviously if the canary is a small value of
say 16 bits, an attacker can trivially brute force it (setting off
alarms along the way.. but hey, he can clean up afterwards).
I assume you picked an integer value that fits nicely on the stack
or a 64 bit quantity for extra assurance.

A clever attacker may be able to trick the program into disclosing
the canary value though. For example if he has more than one
overflow, or an overflow that is repeated several times with different
input. In this case he can overflow the buffer right up to the
value before the canary, watch the resulting behavior of the program
to infer the canary, and then construct a second overflow with the
proper canary value in it. As an example:

char filename[100], username[10];

strcpy(filename, queuename);
strcat(filename, ".dat");
getusername(username);
if(!authorized_user(userlist, username))
return 0;
fd = open(filename, O_READ);
if(fd == -1) {
perror(filename);
return -1;
}

An attacker can cause "filename" to extend right up to the
canary value (and then continue running till the next nul byte on
the stack) to get the canary value.

> The higher assurance StackGuard mechanism utilizes a tool from the
> Synthetix project called MemGuard. MemGuard provides fine-grained
> memory protection, down to the size of a single word. The MemGuard
> variant of StackGuard applies MemGuard's protect() to the return
> address when a function starts, and releases the protection of the return
> address when the function finishes. If any part of the program ever tries
> to alter the return address (which is not normal behavior) then the MemGuard
> memory protection mechanism detects the write to protected
> data, and aborts the program.

A much safer technique than the canary technique, but it comes at
a considerable performance penalty. I may be willing to let some
small utilities on my system run with this loss of performance.
I doubt many people would make all the security critical programs
on their system run with this degraded performance, especially
large performance critical daemons like sendmail and httpd.

So in summary: You reduced, but did not eliminate, the number
of buffer overflow problems that allow a user to hijack the program.
The first of your techniques does not eliminate as many of these
problems as the second technique, but the second technique is
unattractive due to its performance impact. Even with the best
of these techniques implemented, there will still be a number
of security problems left in the system due to buffer overflows.

Comments: If you fix the root of the problem, you can eliminate
all the problems. If you make a fix that combats one of the
common attacks against a problem, you may make quicker progress,
but you cannot address the entire problem. In addition you may
affect performance (memory protection) or functionality (allowing
execution of code on the stack).

My personal feelings on the recent proposals for fixing
"the overflow problem" is that I don't like them. They all
seem hacky to me, and all claim to be a silver bullet to finally
put an end to the problem. I much rather see the original problems
fixed, a solution that is much more aesthetically pleasing to
me. On the other hand the proposals do reduce the number of
attacks, and buy time until attackers get more sophisticated
in their exploits.

> Crispin Cowan, Research Assistant Professor of Computer Science
> PO Box 91000 | digital: crispin@cse.ogi.edu

Tim N.