Question

This is a set-root-uid program

$ls -l
-rwsr-sr-x 1 root root 7406 2011-12-13 22:37 ./x*

The source code:

int main(void) {
    printf(
        "         UID           GID  \n"
        "Real      %d  Real      %d  \n"
        "Effective %d  Effective %d  \n",
             getuid (),     getgid (),
             geteuid(),     getegid()
    );

seteuid(600);
    printf(
        "         UID           GID  \n"
        "Real      %d  Real      %d  \n"
        "Effective %d  Effective %d  \n",
             getuid (),     getgid (),
             geteuid(),     getegid()
    );

setuid(1000);

    printf(
        "         UID           GID  \n"
        "Real      %d  Real      %d  \n"
        "Effective %d  Effective %d  \n",
             getuid (),     getgid (),
             geteuid(),     getegid()
    );

setuid(0); // HOW DOES THIS SUCCEED IN SETTING THE EUID BACK TO 0
    printf(
        "         UID           GID  \n"
        "Real      %d  Real      %d  \n"
        "Effective %d  Effective %d  \n",
             getuid (),     getgid (),
             geteuid(),     getegid()
    );

    return 0 ;       
}

OUTPUT

         UID           GID  
Real      1000  Real      1000  
Effective 0  Effective 0  
         UID           GID  
Real      1000  Real      1000  
Effective 600  Effective 0  
         UID           GID  
Real      1000  Real      1000  
Effective 1000  Effective 1000  
         UID           GID  
Real      1000  Real      1000  
Effective 0  Effective 1000  

My question

The man page states that setuid will change the real,saved and effective uid. So after the calling setuid(1000), all three change to 1000. How is that setuid(0) let's me change euid to 0?

Was it helpful?

Solution

There are two cases,

  1. You want to temporarily drop root privilege while executing setuid program
  2. You want to permanently drop root privilege while executing setuid program...
  • You can temporarily do it by setting the euid to the real user id and then changing the uid to anything you want.And later when you need the root privilege back you can setuid to root and the effective userid will change back to root. This is because the saved user id is not changed.
  • You can drop privilege permanently by changing the uid straight away to a lesser privileged user id. After this no matter what you cannot get back the root privilege.

Case 1:

After a setuid program starts executing

1.seteuid(600);
2.setuid(1000);
3.setuid(0);

For this case the root privilege can be gained back again.

              +----+------+------------+
              | uid|euid  |saved-uid   |
              |----|------|------------|
            1.|1000| 0    | 0          |
            2.|1000| 600  | 0          |
            3.|1000| 1000 | 0          |
            4.|1000|  0   | 0          |
              |    |      |            |
              +------------------------+

Case 2:

After a setuid program starts executing,

1.setuid(1000);
2.setuid(0);



               +----+------+------------+
               | uid|euid  |saved-uid   |
               |----|------|------------|
             1.|1000|0     | 0          |
             2.|1000|1000  | 1000       |
               |    |      |            |
               +------------------------+

In this case you cannot get back the root privilege. This can be verified by the following command,

cat /proc/PROCID/task/PROCID/status | less

Uid:    1000    0       0       0
Gid:    1000    0       0       0

This command will display a Uid and Gid and it will have 4 fields( the first three fields are the one we are concerned with). Something like the above

The three fields represent uid,euid and saved-user-id. You can introduce a pause (an input from user) in your setuid program and check for each step the cat /proc/PROCID/task/PROCID/status | less command. During each step you can check the saved uid getting changed as mentioned.

If you're euid is root and you change the uid, the privileges gets dropped permanently.If effective user id is not root then saved user id is never touched and you can regain the root privilege back anytime you want in your program.

OTHER TIPS

DESCRIPTION setuid() sets the effective user ID of the calling process. If the effective UID of the caller is root, the real UID and saved set-user-ID are also set.

Under Linux, setuid() is implemented like the POSIX version with the _POSIX_SAVED_IDS feature. This allows a set-user-ID (other than root) program to drop all of its user privileges, do some un-privileged work, and then reengage the original effective user ID in a secure manner.

If the user is root or the program is set-user-ID-root, special care must be taken. The setuid() function checks the effective user ID of the caller and if it is the superuser, all process-related user ID's are set to uid. After this has occurred, it is impossible for the program to regain root privileges.

Thus, a set-user-ID-root program wishing to temporarily drop root privileges, assume the identity of an unprivileged user, and then regain root privileges afterward cannot use setuid(). You can accomplish this with seteuid(2).

(from the Linux Programmers' Manual, 2014-09-21, page setuid.2)

O! These functions are difficult to use correctly.

The man page states that setuid will change the real,saved and effective uid. So after the calling setuid(1000), all three change to 1000.

That is the case if and only if you are euid 0. At the time you call setuid(0), however, you are euid 1000 and saved uid 0 (check getresuid(2), for example). That's why you're able to regain privileges.

Code:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>

void print_uid(char *str, int ret)
{
    uid_t ruid;
    uid_t euid;
    uid_t suid;
    getresuid(&ruid, &euid, &suid);

    printf("%s ret:%d\n"
           "Real:%4d  Effective:%4d  Saved:%4d\n",
           str, ret, ruid, euid, suid);
}

int main(void)
{
    int ret = 0;
    print_uid("init", ret);            /* Real:1000  Effective:   0  Saved:   0 */

    ret = seteuid(600);
    print_uid("seteuid(600)", ret);    /* Real:1000  Effective: 600  Saved:   0 */

    ret = setuid(1000);
    print_uid("setuid(1000)", ret);    /* Real:1000  Effective:1000  Saved:   0 */

    ret = setuid(0);
    print_uid("setuid(0)", ret);       /* Real:1000  Effective:   0  Saved:   0 */

    ret = setuid(1000);
    print_uid("setuid(1000)", ret);    /* Real:1000  Effective:1000  Saved:1000 */

    ret = setuid(0);
    print_uid("setuid(0)", ret);       /* Real:1000  Effective:1000  Saved:1000 */

    return 0 ;       
}

sudo chown root setuid_feature
sudo chmod +s setuid_feature

There are three uids for a process in Linux: REAL uid, EFFECTIVE uid, SAVED uid.
Cond 1. When euid is root, setuid or seteuid can be set to any uid, but there is a side effect, when using setuid(not seteuid), all of the three can be set to the same uid which is not ROOT, and then the process can't regain ROOT privilege.
Cond 2. When euid is not root, setuid or seteuid can be set to ruid or suid, and only euid is changed.

                       |      seteuid             |          setuid  
Cond 1. (euid == root) | set euid to any uid      | set all three uids to any uid  
Cond 2. (euid != root) | set euid to ruid or suid | set euid to ruid or suid  

so, there are 5 setuid or seteuid process there in code, let me classify them:
1. seteuid(600): Cond 1, set euid to 600
2. setuid(1000): Cond 2, set euid to 1000
3. setuid(0) : Cond 2, set euid to 0(suid)
4. setuid(1000): Cond 1, set all three uids to 1000
5. setuid(0) : Cond 2, all three uids is not equal 0, so can't set to 0, failed with ret = -1

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top