Buffer overflows occur because of a programming bug: due to circumstances unforeseen by the developer, but triggered by the attacker, the code is writing data beyond the end of the buffer. What end ? Well, there are two: toward high addresses, and toward low addresses.
The traditional way to write a loop looks like this:
for (i = 0; i < n; i ++)
and not like this:
for (i = n - 1; i >= 0; i --)
which means that developers tend to apply algorithms in the low-to-high order. There is no mathematical reason for that, but developers are trained to think like that; and hardware's evolution has followed (pre-fetching strategies in RAM chips rely on this pattern, too). Usual suspects for string manipulation (
sprintf()...) also overflow on the high end for the same reason: they operate low-to-high.
This implies that most buffer overflows also occur on the high end of the buffer, not on the low end. On a stack which grows downwards, this allows these overflows to target the return address of the current function: the best a buffer overflow exploit can hope for is redirecting execution into attacker-chosen code, and for that the overflow shall overwrite a memory slot which contains a pointer to code, which the code will follow at some ulterior point. The "return address" slot on the stack is convenient for that. But that's not the only pointer to code.
In particular, when using C++, any object with virtual methods must contain some sort of reference to a structure containing pointer to these methods (the vtable). This gives a lot of extra targets which may be in range of a buffer overflow. C++ objects can be stack allocated.
Another point is that when a buffer is overflown, it could be located in another stack frame. For instance, this:
void foo(char *s)
bar(buf, "x", s);
void bar(char *dst, char *s1, char *s2)
sprintf(dst, "%s/%s", s1, s2);
In this example, if the attacker can put an oversized string as parameter to
foo(), the buffer allocated on the stack frame for
foo() will be overflowed in
bar(). If the stack grows upwards, the return address for
foo() will be undamaged, but the one for
bar() will be overwritten.
To sum up:
- An upward-growing stack means that the function return address of the function which allocated the buffer will usually be unharmed.
- But other function return address slots for other functions may be in range (and actually be more in range than if the stack had grown downwards).
- And though they are rarer, buffer overflows on the low end (sometimes called "underflows") also occur in practice.
- And the return address slot is just one of the juicy targets for an attacker.
So we cannot really think of upwards-growing stacks as a protection against exploitation of buffer overflows. It changes the attack conditions, but not necessarily toward safer grounds. Experience shows that buffer overflows have been exploited with success on architectures with upwards-growing stacks.
(Note: the PA-RISC CPU, by itself, knows of no stack. The stack is part of the calling convention, which says that GR 30 is to be conventionally used as stack pointer, and that the stack grows upwards. This is how HP defined it but any operating system could decide otherwise. It is a generic trend for most RISC CPU: the hardware knows of no stack, so the position and direction of the stack are a matter of convention between OS and applications.)