Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Addressable memory and relation with buffer overflows

Reading about buffer overflows, I came across the sample code given below:-

void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}

void main() {
 function(1,2,3);
}

It's from the famous smashing the stack for fun and profit article I guess. (Reference: http://insecure.org/stf/smashstack.html)

The article says that to allocate space for buffer1 and buffer2, 20 bytes are required (8 bytes for buffer1 and 12 bytes for buffer2) since memory addresses can only be accessed in multiples of word size (1 word = 4 bytes in this case).

But I recall that memory is byte addressable I.e. I can access 1 byte at a time from memory. I related this with processor's bitness. e.g. a 32 bit processor can access 2^32 memory locations and since 1 memory location holds 1 byte(8 bits), the total addressable memory for a 32 bit processor equals (2^32)/(1024*1024*1024) = 4096 MB = 4GB.

Since in the above example, both buffer1 and buffer2 are of type char which let's say requires 1 byte, then why can't we allocate 5 bytes and 10 bytes for buffer1 and buffer2 respectively?

Why is memory access limited to multiples of word size?

like image 216
user720694 Avatar asked Oct 22 '22 09:10

user720694


1 Answers

First thing - memory access is not limited to word size.

As you point out you are free to access memory in the finest granularity your respective CPU supports - in most cases this will be bytes.

However for local variables in C the alignment rules are a little specific.
The whole word-access limitation is related to the fact that the function locals are placed on the so called stack.

The stack is provided by the CPU to temporarily store and retrieve register values in memory and each program is given its own memory to use as stack space. It's common / in some cases required to access the stack in exactly the register size your CPU uses so you don't accidentialy clobber up the push / pop access the CPU uses. On 32bit systems that access-size is 4 byte per register, for 64bit its 8 byte.

So in your example the function's stack may look something like this on an Intel CPU (Depending on the OS):

|---   function's stack bottom  ---|
| 4 byte Code-Segment index        |
| 4 byte return address            |
| 4 byte buffer1[0..3]             |
| 1 byte buffer1[4],    3 byte pad |
| 4 byte buffer2[0..3]             |
| 4 byte buffer2[4..7]             |
| 2 byte buffer2[8..9], 2 byte pad |
|---   function's stack top     ---|

The padding bytes are required so that when your program is running and using the stack from inside your function it'll still by properly aligned (and trust me it will use it a lot ;) ).

E.g. a push / pop would still result in a 4byte aligned address.

Remember: this aligning rules only apply to stack space - global or static variables can be on odd memory locations (unlikely, but possible)

I hope this was not to techie / low-level.

[EDIT]
The relation to buffer overflows here becomes clear if you think about the following thing: If you know the memory and stack layout you can manipulate things like the return address. As you see by over- / underflowing the buffer's you could easily change values located above / below them on the stack.
In most cases this will crash your code but if executed properly you could also put some executable code on the stack / somewhere in memory and alter the function's return address to jump to that code instead back to where it was called.

like image 190
Shirkrin Avatar answered Oct 24 '22 04:10

Shirkrin