When you add the /gs
argument to microsoft's compiler, it would place a copy of args below the buffer variable.
So, what's the point of keeping a copy of arg?
When you add the /gs
argument to microsoft's compiler, it would place a copy of args below the buffer variable.
So, what's the point of keeping a copy of arg?
The /GS option is a heuristic protection against buffer overflow -- some kinds of buffer overflows, and only if they overflow in the "usual way". Visual C supposes that any variable which "looks array-like" may be subject to array-like treatment, and thus possibly overflow. It wants to detect whether an overflow has occurred, and do that before returning from the function, because traditional buffer overflows try to overwrite the return address. To detect such an occurrence, the compiler writes a "cookie" value between the potentially overflowed variable, and the return address; and the code checks that the cookie is still there when it is about to exit the function.
The compiler will do that for all "local variables", and arguments are such local variables. The detection system works only as long as the cookie is between the return address and the variable, so this may imply moving things around, which is what you talk about.
Consider this:
struct foo { int a; int b; int c; int d; };
void f(struct foo arg)
{
char buf[20];
// ...
}
In this function, both buf
and arg
are considered "array-like" (arg
is deemed array-like because it is larger than 8 bytes). Remember that when you pass a struct
to a C function (the struct
itself, not a pointer to a struct
) then the function receives a copy of the structure to play with. On the stack, you would find things like this:
buf
return address
arg
(increasing addresses in top-to-bottom order; the x86 stack grows towards low addresses, so "up" in the schema above.)
The "/GS" code will want to add a cookie, and put that cookie between any array-like variable and the return address. Since bytes beyond (below) the return address belong to the caller, the function cannot play with them as much as it would want, so it has to do its job in the free stack space. Thus, the code will want this:
buf
arg (copy of the received structure)
cookie
return address
arg (structure passed as parameter by the caller)
This involves copying the arg
structure upon function entry. Since the structure is, semantically, already a copy, that the function can play with at will, the copy does not change the observable behaviour of the function. With this copy, any "normal" overflow on arg
will hit the cookie before it overwrites the return address, and thus be (hopefully) detected when the function exits.
I must of course point out that such a protection is effective only if all of the following hold true:
So you should not put too much credit in the effectiveness of such a system.