Dan R
Dan R

Reputation: 1494

setuid does not work for non-root

The setuid function from unistd.h successfully changes the real userid (in a program with the proper permissions and ownership) from a normal user to the root user. However, when using the same function in a program owned by a normal user, and with the same permissions, the function call returns 0 for success but in fact does not succeed in changing the real userid from one normal user to another normal user.

What is the reason behind this behavior, and what is the best (most portable, most secure) work-around?

What I tried

Here is try.c:

#include <unistd.h>   /* for the get/set uid functions */
#include <stdio.h>    /* for printf and perror */

int main() {
  int newuid = geteuid(); /* The goal is to set the real UID to this value. */
  printf("Original UID is %d, EUID is %d\n", getuid(), geteuid());

  if (setuid(newuid)) perror("setuid reported failure");
  printf("UID after setuid is %d\n", getuid());

  return 0;
}

I compiled with gcc and changed the permissions to 4711, i.e., -rws--x--x. The owner of this program is username bob, UID 32983.

When user alice, UID 1000, runs the try program, she sees

Original UID is 1000, EUID is 32893
UID after setuid is 1000

In other words, the setuid function call reported success (returned 0), but the real userid did not in fact change.

If I copy this executable to a root-owned directory such as /usr/local/bin, change the ownership to root and keep the permissions at 4711, then it works as expected when alice runs it:

Original UID is 1000, EUID is 0
UID after setuid is 0

So it works in this case!

I have tried this and gotten the same behavior on Ubuntu and Debian; kernel versions 3.2.0-4-amd64, 3.5.0-36-generic, and 3.14-1-amd64; and glibc versions 2.13, 2.15, and 2.19.

Why I want to do this

Most linux programs only use the effective UID to determine permissions, so this shouldn't matter. But some, notably bash, will ignore the effective UID and revert to the real UID. Of course there are some great reasons for that, but writing a C wrapper for a SUID bash script seems to be a standard work-around. (I am properly sanitizing the environment and have a really good reason to do this; namely, my own laziness.)

In any case, I am very curious why this would work to setuid to root but not to any normal user.

Upvotes: 1

Views: 3328

Answers (2)

rici
rici

Reputation: 241921

Posix defines the how [setuid][1] works for privileged and non-privileged users:

If the process has appropriate privileges, setuid() shall set the real user ID, effective user ID, and the saved set-user-ID of the calling process to uid.

If the process does not have appropriate privileges, but uid is equal to the real user ID or the saved set-user-ID, setuid() shall set the effective user ID to uid; the real user ID and saved set-user-ID shall remain unchanged. (emphasis added)

I think that is what you are experiencing.

In the rationale section, it offers something vaguely like an explanation:

The various behaviors of the setuid() and setgid() functions when called by non-privileged processes reflect the behavior of different historical implementations.

And a suggestion:

For portability, it is recommended that new non-privileged applications use the seteuid() and setegid() functions instead.

(The only difference is that seteuid's behaviour is less surprising for non-privileged users, since it does what it's name claims it will do.)

The rest of the rationale section is a long explanation of the various strategies for changing user ids, some of the risks, and the variety of solutions which have been attempted over the years, as well (of course) as the rationale for the path chosen by Posix.

Upvotes: 3

Dan R
Dan R

Reputation: 1494

Using the non-POSIX function setreuid works. In the try.c example above, I changed the line to:

if (setreuid(newuid, geteuid())) perror("setreuid reported failure");

and the result is as expected when alice runs it now:

Original UID is 1000, EUID is 32893
UID after setreuid is 32893

Note that, while setreuid potentially has more power than setuid since it can set the real and effective UIDs at the same time, in this case I am NOT asking to change the effective UID.

I will certainly accept another answer if anyone can explain why setreuid works and setuid does not!

Upvotes: 0

Related Questions