Re: Syscall changes registers beyond %eax, on linux-i386

Richard B. Johnson
2002-09-19T17:15:09Z

On Thu, 19 Sep 2002, Brian Gerst wrote:

> Richard B. Johnson wrote:
> > On Thu, 19 Sep 2002, dvorak wrote:
> >
> >
> >>Hi,
> >>
> >>recently i came across a situation were on linux-i386 not only %eax was
> >>altered after a syscall but also %ebx. I tracked this problem down, to
> >>gcc re-using a variable passed to a function.
> >>
> >>This was found on a debian system with a 2.4.17 kernel compiled with gcc
> >>2.95.2 and verified on another system, kernel 2.4.18 compiled with 2.95.4
> >>Attached is small program to test for this 'bug'
> >>
> >>a syscall gets his data off the stack, the stack looks like:
> >>
> >>saved(edx)
> >>saved(ecx)
> >>saved(ebx)
> >>return_addres (somewhere in entry.S)
> >>
> >>When the syscall is called.
> >>
> >>the register came there through use of 'SAVE_ALL'.
> >>
> >>After the syscall returns these registers are restored using RESTORE_ALL
> >>and execution is transferred to userland again.
> >>
> >>A short snippet of sys_poll, with irrelavant data removed.
> >>
> >>sys_poll(struct pollfd *ufds, .. , ..) {
> >> ...
> >> ufds++;
> >> ...
> >>}
> >>
> >>It seems that gcc in certain cases optimizes in such a way that it changes
> >>the variable ufds as placed on the stack directly. Which results in saved(ebx)
> >>being overwritten and thus in a changed %ebx on return from the system call.
> >>
> >
> >
> > The 'C' compiler must make room on the stack for any local
> > variables except register types. If it was doing as you state, you
> > couldn't even execute a "hello world" program. Further, the local
> > variables are after the return address. It would screw up the return
> > address and you'd go off into hyper-space upon return.
> >
> >
> >
> >>I don't know if this is considered a bug, and if it is, from whom.
> >>If it's not a bug it means low-level userland programs need to be rewritten
> >>to store all registers on a syscall and restore them on return.
> >>
> >
> >
> > No. Various 'C' implementers have standardized calling methods even
> > though it's not part of the 'C' standard. gcc and others assume that
> > a called procedure is not going to change any segments or index registers.
> > There are various optimization things, like "-fcaller-saves" where the
> > called procedure can destroy anything. You may be using something that
> > was wrongly compiled using that switch.
> >
> >
> >
> >>It shouldn't be a bug in gcc, since the C-standard doesn't talk about how to
> >>pass variables and stuff. So it seems like a kernel(-gcc-interaction) bug.
> >>
> >>To solve this issue 2 solutions spring to mind
> >>1) add a flag to gcc to tell it that it shouldn't do this optimization, this
> >> won't work with the gcc's already out there.
> >>2) When calling a syscall explicitly push all variable an extra time, since
> >> the code in entry.S doesn't know the amount of variables to a syscall it
> >> needs to push all theoretical 6 parameters every time, a not so nice
> >> overhead.
> >>
> >>
> >
> >
> > There is a bug in some other code. Try this. It will show
> > that ebx is not being killed in a syscall. You can prove
> > that this code works by changing ebx to eax, which will
> > get destroyed and print "Broken" before exit.
>
> The bug is only with _some_ syscalls, and getpid() is not one of them,
> so your example is flawed. It happens when a syscall modifies one of
> it's parameter values. The solution is to assign the parameter to a
> local variable before modifying it.
>

Well which one? Here is an ioctl(). It certainly modifies one
of its parameter values.

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <termios.h>

void barf(void);
void barf()
{
puts("Broken\n");
exit(0);
}
int main()
{
struct termios t;

__asm__ __volatile__("movl $0xdeadface, %ebx\n");
(void)ioctl(0, TCGETS, &t);
(void)getpid();
__asm__ __volatile__("cmpl $0xdeadface, %ebx\n"
"jnz barf\n");

return 0;
}

Until you can show the syscall that doesn't follow the correct
rules, then my example is not flawed. In fact a modified example can
be used to find any broken calls.

Cheers,
Dick Johnson
Penguin : Linux version 2.4.18 on an i686 machine (797.90 BogoMips).
The US military has given us many words, FUBAR, SNAFU, now ENRON.
Yes, top management were graduates of West Point and Annapolis.