martin carpenter

contents

most popular
2012/05/05, updated 2012/12/15
ubuntu unity lens for vim
2010/04/14
ckwtmpx

solaris issetugid(2) bug

2013/01/15

tags: solaris issetugid(2) vulnerability

summary

Child processes forget that they were once privileged in spite of the fact that they continue to possess differing real and effective user IDs. This could be a security problem for any program that relies on the issetugid(2) system call to return valid results.

Oracle have subsequently fixed this in Solaris 11.1 but not in earlier releases. In particular Solaris 10 has not been fixed and therefore applications should avoid using this system call on this version of the operating system.

bug details

issetugid(2) was a new system call for Solaris 10. It was intended to track the "setuidness" of an invoked program, no matter whether or not it subsequently called setuid() or if it fork()ed or exec()ed another program. The manual page says:

 The result of a call to issetugid() is unaffected  by  calls
 to  setuid(),  setgid(),  or other such calls.  In case of a
 call to fork(2), the child process inherits the same status.

Unfortunately issetugid() fails to preserve its value after forking children from a setuid program when uid != euid in both Solaris 10 and 11.

Here is a simple demonstration program foo.c:

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

int main(int argc, const char *argv[])
{
    pid_t pid = 0;
    int stat_loc = 0;
    const char *execname = getexecname();

    printf("%s: main:   issetugid: %u\n", execname, issetugid());

    pid = fork();
    switch(pid) {
        case -1:
            perror("fork");
            break;
        case 0: 
            printf("%s: child:  issetugid: %u\n", execname, issetugid());
            printf("%s: child:  uid:       %u\n", execname, getuid());
            printf("%s: child:  euid:      %u\n", execname, geteuid());
            break;
        default:
            printf("%s: parent: issetugid: %u\n", execname, issetugid());
            printf("%s: parent: uid:       %u\n", execname, getuid());
            printf("%s: parent: euid:      %u\n", execname, geteuid());
            (void)wait(&stat_loc);
            break;
    }

    return 0;
}

And the results of running it:

alice@sol10:~$ gcc -o foo foo.c
alice@sol10:~$
alice@sol10:~$ ./foo
foo: main:   issetugid: 0
foo: parent: issetugid: 0
foo: parent: uid:       1000
foo: parent: euid:      1000
foo: child:  issetugid: 0
foo: child:  uid:       1000
foo: child:  euid:      1000
alice@sol10:~$
alice@sol10:~$ sudo chown root foo
alice@sol10:~$ sudo chmod 4755 foo
alice@sol10:~$ ./foo
foo: main:   issetugid: 1
foo: parent: issetugid: 1
foo: parent: uid:       1000
foo: parent: euid:      0
foo: child:  issetugid: 0       # oops
foo: child:  uid:       1000
foo: child:  euid:      0
alice@sol10:~$

Since the genealogy of this system call is BSDish, I tested the same program on FreeBSD 9.0 where issetugid() returns 1 in both the parent and child, as expected.

exploitability

Fortunately issetugid() is relatively new and not greatly used. I took a quick tour through OpenSolaris and found it in the following libraries/library calls:

fork()/catopen() occur in, for example, the following setuid root binaries:

None of these look directly exploitable, principally because conditions required are quite strict:

fork()/exec()/catopen() certainly feels like it should be a good lever though, in particular when programs are executed from su(1) or sudo(1).

Since a direct exploit could not be found Oracle fixed this under the Security in Depth programme as noted in the January 2013 Critical Patch Update. A CVE was not allocated.

timeline