The GCC SSP protects the stack from buffer overflows. If a buffer overflow occurs, you're informed instantly. The way this works is by inserting a "canary" value into the stack frame that, if changed, indicates a buffer overflow or stack corruption. This feature can not only detect buffer overflows, malicious or accidental, but also may help in detecting other stack-related bugs that are often found in kernel code.
Suppose you are writing a program to which data is passed by an external program. You may write a function like the following which accepts external data. Obviously this is a contrived example which no one would write in practice, but it demonstrates the idea.
1 int check_input(const char *input)
2 {
3 char buf[16];
4
5 strcpy(buf, input);
6
7 // do some processing and return a value based on it
8 }
Now the stack frame for the above is:
Return EIP |
Previous EBP |
buf[12-15] |
buf[8-11] |
buf[4-7] |
buf[0-3] |
If someone passed a 24 byte value to this function as the input parameter, they could easily overwrite our return EIP and thus redirect our execution to their own malicious code. SSP is designed to protect against this. What it aims to do is insert a special value called a canary into the stack immediately after the return EIP address. On function exit, this is compared against its original value, and if it has been overwritten then execution halts with an error. It is thus able to protect against accidental and malicious buffer overflows that would affect the return address. Of note it doesn't stop one buffer overflowing into another.
The problem is that the canary value cannot be known to the person passing data to our check_input function, as otherwise they could just inject it into their data at the appropriate offset, along with an altered return EIP address, and the SSP mechanism would be none the wiser. To get around this, the gcc implementation uses a random value which is chosen fresh each time a process starts. The actual gcc implementation uses a canary value the size of a pointer and then reads the required number of bytes from /dev/urandom in the start-up code for a process. If it cannot do this, it chooses the value 0x00000aff which is at least effective protection against some attacks using the standard string copying functions (they will stop copying once they reach the null value) and most accidental overflows.
When you started OS developing, you might have seen that following error:
... undefined reference to __stack_chk_fail
... undefined reference to __stack_chk_guard
That's actually the SSP! You probably just didn't care about it and disabled it.
Now, implementing this feature is dead easy and it is a really handy thing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void
* __stack_chk_guard = NULL;
void
__stack_chk_guard_setup()
{
unsigned
char
* p;
p = (unsigned
char
*) &__stack_chk_guard;
/* If you have the ability to generate random numbers in your kernel then use them,
otherwise for 32-bit code: */
*p = 0x00000aff;
}
void
__attribute__((
noreturn
)) __stack_chk_fail()
{
/* put your panic function or similar in here */
unsigned
char
* vid = (unsigned
char
*)0xB8000;
vid[1] = 7;
for
(;;)
vid[0]++;
}
|
Call __stack_chk_guard_setup at early boot stage, from there on you're protected from most buffer overflows.
Don't forget to add -fstack-protector-all to the gcc flags.