setreuid() bug

The following program demonstrates what appears
to be a bug in the implementation of setreuid().
(I haven’t a clue whether the bug is in the library
or the OS.) To test:

make testuid
su -c “chown root testuid; chmod u+s testuid”
…/testuid

I get the following output under 6.2.0:

(UID,EUID)=(101,0) => setreuid(0,101) OK
(UID,EUID)=(0,101) => setreuid(0,0) ERROR: Operation not permitted
Workaround…
(UID,EUID)=(0,101) => setreuid(101,0) OK
(UID,EUID)=(101,0) => setreuid(0,0) OK

According to the man page, if the real UID is 0,
setreuid should be able to set the effective UID
to 0 as well. I came up with the workaround of
swapping real/effective before setreuid(0,0).


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

void testreuid( uid_t uid, uid_t euid ) {
printf("(UID,EUID)=(%d,%d) => setreuid(%d,%d)",
getuid(), geteuid(), uid, euid );
if ( setreuid( uid, euid ) == -1 ) {
printf( " ERROR: %s\n", strerror(errno) );
} else {
printf( " OK\n" );
}
}
int main( int argc, char **argv ) {
testreuid(geteuid(), getuid());
testreuid(0, 0);
printf(“Workaround…\n”);
testreuid(geteuid(), getuid());
testreuid(0, 0);
return 0;
}

Norton Allen <allen@huarp.harvard.edu> wrote:

The following program demonstrates what appears
to be a bug in the implementation of setreuid().
(I haven’t a clue whether the bug is in the library
or the OS.) To test:

make testuid
su -c “chown root testuid; chmod u+s testuid”
./testuid

I get the following output under 6.2.0:

(UID,EUID)=(101,0) => setreuid(0,101) OK

Before the call:
uid=101 euid=0

So, you are the superuser, you can do whatever you want. So, the
call returns 0.

(UID,EUID)=(0,101) => setreuid(0,0) ERROR: Operation not permitted

Before the call:
uid=0 euid=101 savedid=101

You are no longer the superuser because your euid is not zero. Your
choices are restricted. You can change uid to 101 or the euid to 101 or 0.
So, trying to “change” the uid to 0 will fail with EPERM.

This is a “new” restriction added by UNIX98, so you may see different
behaviour on other OSes. If you want the uid to remain unchanged, pass “-1”:
setreuid(-1, 0);

Workaround…
(UID,EUID)=(0,101) => setreuid(101,0) OK

Before the call:
uid=0 euid=101 savedid=101

You are no longer the superuser because your euid is not zero. Your
choices are restricted. You can change uid to 101 or the euid to 101 or 0.
The call returns 0.

(UID,EUID)=(101,0) => setreuid(0,0) OK

Before the call:
uid=101 euid=0 savedid=0

You are the superuser again, you can do whaterver you want. So, the
call returns 0.

According to the man page, if the real UID is 0,
setreuid should be able to set the effective UID
to 0 as well. I came up with the workaround of
swapping real/effective before setreuid(0,0).

That depends on the current value of the effective user id, with the UNIX98
restriction:
http://www.opennc.org/onlinepubs/7908799/xsh/setreuid.html

I would guess that using setreuid(-1, 0), will work with a BSD and UNIX98
system. Or am I missing something, I find all these uids confusing…

#include <stdio.h
#include <stdlib.h
#include <sys/types.h
#include <unistd.h
#include <errno.h

void testreuid( uid_t uid, uid_t euid ) {
printf("(UID,EUID)=(%d,%d) => setreuid(%d,%d)",
getuid(), geteuid(), uid, euid );
if ( setreuid( uid, euid ) == -1 ) {
printf( " ERROR: %s\n", strerror(errno) );
} else {
printf( " OK\n" );
}
}
int main( int argc, char **argv ) {
testreuid(geteuid(), getuid());
testreuid(0, 0);
printf(“Workaround…\n”);
testreuid(geteuid(), getuid());
testreuid(0, 0);
return 0;
}


Kirk Russell Bridlewood Software Testers Guild

kirk wrote:

Norton Allen <> allen@huarp.harvard.edu> > wrote:

The following program demonstrates what appears
to be a bug in the implementation of setreuid().
(I haven’t a clue whether the bug is in the library
or the OS.) To test:


make testuid
su -c “chown root testuid; chmod u+s testuid”
./testuid


I get the following output under 6.2.0:


(UID,EUID)=(101,0) => setreuid(0,101) OK


Before the call:
uid=101 euid=0

So, you are the superuser, you can do whatever you want. So, the
call returns 0.


(UID,EUID)=(0,101) => setreuid(0,0) ERROR: Operation not permitted


Before the call:
uid=0 euid=101 savedid=101

You are no longer the superuser because your euid is not zero. Your
choices are restricted. You can change uid to 101 or the euid to 101 or 0.
So, trying to “change” the uid to 0 will fail with EPERM.

This is a “new” restriction added by UNIX98, so you may see different
behaviour on other OSes. If you want the uid to remain unchanged, pass “-1”:
setreuid(-1, 0);

Ah, subtle! I read the man page closely and assumed it was complaining
about changing the euid to 0, not about “changing” the uid. Sounds like
this could be spelled out a little more explicitly. Thanks for the
analysis.

-Norton