10

I realize how a buffer overflow works but I have a problem understanding the direction in which the overflow is directed. So if the stack grows downwards, that means that the return address is above the variable's reserved space. When that variable is now overflowed, shouldn't it overwrite memory below instead of above?

tl;dr: If the stack grows downwards, how can a buffer overflow overwrite content above the variable?

Address space layout

AdHominem
  • 3,006
  • 1
  • 16
  • 26

4 Answers4

9

If the stack grows downward, functions that are called later get stack frames at lower memory addresses. Also, the return address is pushed to the stack before space for local variables is reserved, so the return address gets a higher address than the local variables. But arrays and buffers are still indexed upwards in memory, so writing past the end of the array will nicely hit the return address next on the stack.

Example, with mandatory ASCII art:

Consider the trivial function that takes input from an untrusted source, and copies it to a local buffer:

void foo(char *s)
{
    char buf[8];
    strcpy(buf, s);
    return;
}

The stack looks a bit like this:

   <---- stack grows to the left
    memory addresses increase to the right -->
  0x8000                        0x8010
  +--------+----------+---------++------------
  + buf[8] | ret addr | char *s ||   ....... 
  +--------+----------+---------++--------------
   <--------- foo() ----------->  <---- caller --

The stack is filled from right to left, starting with the function arguments, then the return address, then the locals of the function. It's easy to see that a simple overflow from buf towards increasing addresses will hit the return address nicely.

So, what if the stack is inverted, and it grows upwards? Then overflowing a buffer will run the same way the stack grows, towards the empty part of the stack.

Sounds nice, but it doesn't help if foo() calls another function to do the copy. Which isn't unusual, I just did that with the strcpy. Now the stack looks like this:

    stack grows to the right -->
    memory addresses increase to the right -->
            0x8000                          0x8010
------------++---------+----------+---------++-----------+-------------+
  ....      || char *s | ret addr | buf[8]  || ret addr  | locals  ... |
------------++---------+----------+---------++-----------+-------------+
 caller --->  <-------- foo() ------------->  <---- strcpy() ---------->

Now, overrunning (to the right) the buffer in foo()'s stack frame will nicely overwrite the return address of strcpy(), not foo(). Doesn't matter, though, we still jump to a location set by the overflowing, attacker-controlled data.

ilkkachu
  • 2,086
  • 1
  • 11
  • 15
  • Question regarding the last diagram. Shouldn't it depend on when the ret address is pushed to the stack? If that happens after the overflow, it is fine/ – Bear Aug 31 '19 at 20:14
  • If the overflow happens before `strcpy` is called, there's no stack frame for `strcpy`, so it can't be overwritten. If it happens after it returns, there's again no stack frame for it (the remnants of it aren't relevant). But the problem is that `strcpy` itself copies data around, and when it does, there's the return address of `strcpy` on the stack, as well as the return address of the calling function. – ilkkachu Aug 31 '19 at 20:34
4

The stack only grows downwards when something is allocated on it. On the other hand, reading off the end of an array means reading upwards in memory.

Let's say the stack pointer says 0x1002. At this address is a return pointer you care about. If you allocate, say, a 2-byte array, the stack grows downward: the stack pointer is changed to 0x1000, and the array's address is set to that new value. Writing the first byte to the array uses address 0x1000, and writing the second uses 0x1001. Writing off the end uses 0x1002, and overwrites the thing that matters.

You can see this in the images you posted, as the grey box representing buffer B grows upward as it is written beyond its bounds.

Reid Rankin
  • 1,062
  • 5
  • 10
3

There is a difference between buffer overflow and stack overflow. Allocated buffers may not use the stack, but the heap. This depends on how they are allocated and what the compiler though would be better/faster/etc.

A stack overflow does indeed override memory below, which may have been assigned to another (prior) call or ultimately the heap. The heap grows upwards and at some point they can collide. This is a great time for the operating system to throw a segmentation fault, halt the program or send signals. The problem with stack overflows are that they can come in contact with other threads from the same process. Now image a thread which has a large buffer for network send/recv, the contents of another thread stack could override this memory area causing memory leaks over the network.

Buffer overflows are more common and they go beyond the bounds of their own memory. As long as the memory accessed is still within the heap region, there is no problem (from the operating system perspective). It gets more dangerous when the heap is too small and additional data needs to be allocated. At this point there is no say what would happen when boundaries are reached. When tying to access one byte beyond the heap border, the operating system should kill the process with a SIGSEGV.

Yorick de Wid
  • 3,346
  • 14
  • 22
2

A stack grows downward by push instruction, but writes and reads upwards. for example, if your stack occupy address 10 to 5 which is a length of 6 usable address, when you have a push instruction, the stack will go down and add an extra memory which will result in your stack occupying address 10 - 4 which is 7 usable addresses. but when you are writing to the stack and your instruction pointer is at 4, it will write from 4 upwards to 10 and when you pass the address 10, then you have a buffer overflow

moski1122
  • 21
  • 1