EDIT: oh heh the thread is almost a month old, didn't notice at the time, sorry
If system calls don't utilize a separate stack, they'll leak tons of internal information such as local variables or function call arguments used inside of the kernel while handling the call. I've decided to come up with an example scenario to illustrate the issue. Imagine a system call named sys_login for associating your process with a specific user account when given the correct username and password:
Code:
struct user { char *username; char *password; struct process *last_process; };
// some function for iterating over the list of users
extern struct user *userdb_getnext(struct user *)
// login as user (system call)
int sys_login(const char *username,const char *password)
{
struct *user = NULL;
while (user = userdb_getnext(user))
{
if (!strcmp_uk(username,user->username))
{
if (!strcmp_uk(password,user->password))
{
current_process->user = user;
user->last_process = current_process;
return SYS_OK;
}
else
{
// since user list is sorted and usernames
// are unique, we can return immediately
return SYS_WRONG_USERNAME_OR_PASSWORD;
}
}
}
return SYS_WRONG_USERNAME_OR_PASSWORD;
}
// some function to read a byte from user space
// assume it returns -1 if the address is kernel-only or unmapped
extern int user_readbyte(const void *);
// compare user-mode string with kernel-mode string
// similar to standard C strcmp
int strcmp_uk(const char *str1,const char *str2)
{
int left;
int right;
do
{
left = user_readbyte(str1++);
right = *(str2++);
if (left == -1)
{
// user code passed a bad pointer, kill the process
// assume this never returns
kill(current_process,KILL_BAD_USER_POINTER));
}
if (left != right)
return left - right;
} while (left && right);
return 0;
}
Right off the bat, sys_login is exploitable to see whether or not a given user account exists. (It's actually already vulnerable to a timing attack, but this one is much more convenient.)
Code:
int main(int argc,char **argv)
{
uintptr_t stack_access[1];
sys_login("admin","garbage");
if (stack_access[-5])
printf("admin account exists\n");
else
printf("admin account doesn't exist\n");
}
This would work if your stack looked something like this:
So by reading out stack_access[-5] you're reading the value held in "user" from the system call when it finished. If the account doesn't exist, user is NULL and the stack beyond sys_login is actually stuff from userdb_getnext, whatever secrets that might hold. But if it does exist, you can look further down to -11 and -7 to see not only which character you got wrong, but also what the correct character was. This means you can pull someone's password and log in as them whenever you please.
Now the example's a bit contrived since nobody would handle passwords this way (and a compiler would likely optimize quite a bit of this to use registers instead), but you get the idea. When you return you should also clear out any necessary registers as well, for the same reason.