A security bug w.r.t. fsuid?

From: Hao Chen (hchen@cs.berkeley.edu)
Date: Fri Apr 26 2002 - 14:25:50 EST


We came across a possible security bug with regard to fsuid while doing
research on Linux's user ID model. In linux/kernel/sys.c, in the comment
just before the body of cap_emulate_setxuid(), it says:

   * fsuid is handled elsewhere. fsuid == 0 and {r,e,s}uid!= 0 should
   * never happen.

However, the following program shows that a process can enter the state
where ruid=euid=suid!=0 and fsuid=0. The problem is with setresuid().
While setuid() and setreuid() always set fsuid to euid, setresuid(ruid,
euid, suid) fails to do the same when the euid parameter is -1.

Normally, a programmer expects that if none of the ruid, euid, and suid of a
process is zero, the process has no root privileges. This seems to be the
reason behind the above comment from linux/kernel/sys.c. If this invariant
is not satisfied, i.e. if a process can get ruid=euid=suid!=0 and fsuid=0,
an unwary programmer may allow the process to create a file whose data are
supplied by an untrusted user. Since fsuid=0, the file will be owned by
root. If the file happens to be setuid-root, then the user can gain root
access (by asking the process to write shell command into the file and then
running the file).

We are not sure if this invariant is a true invariant or just a
documentation error, and how much kernel code rely on this invariant. Could
anyone point out?

Thanks,

- Hao

Run the following program as user root
-------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setfsuid(uid_t);

int getfsuid(uid_t *fsuid)
{
    pid_t pid;
    char filename[1024], str[1024], *str2;
    FILE *fp;
    int i;

    if ((pid = getpid()) < 0)
    {
      perror("getpid()");
      exit(1);
    }
    sprintf(filename, "/proc/%u/status", pid);
    if ((fp = fopen(filename, "r")) == NULL)
    {
      perror("fopen()");
      exit(1);
    }
    while (fgets(str, 1023, fp) != NULL)
    {
      if ((str2 = strstr(str, "Uid:")) == str)
      {
        strtok(str, "\t");
        for (i = 0; i < 4; i++)
          str2 = strtok(NULL, "\t");
        *fsuid = atoi(str2);
        fclose(fp);
        return 0;
      }
    }

    return -1;
}

int main()
{
    uid_t alice = 100;
    uid_t ruid, euid, suid, fsuid;

    // Now ruid==0, euid==0, suid==0, fsuid==0
    if (setresuid(alice, alice, -1) < 0)
    {
      perror("setresuid");
      exit(1);
    }
    // Now ruid==alice, euid==alice, suid==0, fsuid==alice
    setfsuid(0);
    // Now ruid==alice, euid==alice, suid==0, fsuid==0
    if (setresuid(-1, -1, alice) < 0)
    {
      perror("setresuid");
      exit(1);
    }
    // Now ruid==alice, euid==alice, suid==alice, fsuid==0
    // But according to linux/kernel/sys.c:
    // "fsuid == 0 and {r,e,s}uid!= 0 should never happen."
    if (getresuid(&ruid, &euid, &suid) < 0)
    {
      perror("getresuid");
      exit(1);
    }
    if (getfsuid(&fsuid) < 0)
    {
      fprintf(stderr, "getfsuid failed\n");
    }
    printf("ruid=%u euid=%u suid=%d fsuid=%d\n", ruid, euid, suid, fsuid);
}

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Tue Apr 30 2002 - 22:00:13 EST