POSIX1.e capabilities in ou-o-the-box distributions

From: Richard Urwin (notdivulged_at_nowhere.zz)
Date: 09/06/03


Date: Sat, 06 Sep 2003 12:37:33 +0100

I am looking into the Linux Capabilities, which have been part of the
kernel since 2.2. There was a security hole which was patched in
2.2.19, and they are used throughout the kernel. Because of this hole
it is not possible for one process to change the capabilities of a
different process, and calling exec() to load a new application resets
the capabilities. However everything I have read says that if you jump
through the right hoops a setuid-root application should be able to
drop its capabilities and then setuid to a user level and keep the
capabilities that it retained. I am not seeing that behavior on this
stock MDK9.1 installation:

UID=501 EffUID=0
PR_SET_KEEPCAPS=0
Now PR_SET_KEEPCAPS=1
cap CAP_NET_RAW = effective SET, permitted SET, inheritable SET
cap CAP_NET_ADMIN = effective SET, permitted SET, inheritable SET

Removed CAP_NET_RAW.
PR_SET_KEEPCAPS=1
cap CAP_NET_RAW = effective CLEAR, permitted CLEAR, inheritable CLEAR
cap CAP_NET_ADMIN = effective SET, permitted SET, inheritable SET

As user: UID=501 EffUID=501
cap CAP_NET_RAW = effective CLEAR, permitted CLEAR, inheritable CLEAR
cap CAP_NET_ADMIN = effective CLEAR, permitted CLEAR, inheritable CLEAR

I have checked the kernel source and cap_emulate_setxuid() seems to
implement the behavior I expect, cap-bounding is only used on exec(),
and I cannot see why setting anything in CAP_INIT_INH_SET should make a
difference since, as seen above, the bits I am testing are inheritable
now.

What, in MDK9.1 prevents it from working?

Thanks for any help.

-------------------------------------------------------
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <sys/prctl.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;

char * isbit (__u32 word, int bit)
{
  static char buffer[10];
  sprintf (buffer, word & (1<< bit) ?"SET": "CLEAR");
  return buffer;
}

char * describe_cap (capdata_t *data, int cap)
{
  static char buffer[80];
  sprintf (buffer, "effective %s, permitted %s, inheritable %s",
          isbit(data->effective,cap),
          isbit(data->permitted,cap),
          isbit(data->inheritable,cap));
  return buffer;
}

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;

  if (geteuid()) {
    printf("Run me as root please\n");
    exit(1);
  }

  header.version = _LINUX_CAPABILITY_VERSION;
  header.pid = 0;
  data.effective = data.permitted = data.inheritable = 0;

  printf("UID=%d EffUID=%d\n", getuid(), geteuid());
  printf("PR_SET_KEEPCAPS=%d\n", prctl(PR_GET_KEEPCAPS));
  
  if (prctl(PR_SET_KEEPCAPS, 1) < 0)
  {
    perror ("prctl");
    exit(1);
  }
  printf("Now PR_SET_KEEPCAPS=%d\n", prctl(PR_GET_KEEPCAPS));

  cap_get(&header, &data);
  printf ("cap CAP_NET_RAW = %s\n\n", describe_cap (&data, CAP_NET_RAW));
  printf ("cap CAP_NET_ADMIN = %s\n\n", describe_cap (&data, CAP_NET_ADMIN));

  remove_cap(&data, CAP_NET_RAW);
  cap_set(&header, &data);
  printf ("Removed CAP_NET_RAW.\n");
  printf("PR_SET_KEEPCAPS=%d\n", prctl(PR_GET_KEEPCAPS));
  
  cap_get(&header, &data);
  printf ("cap CAP_NET_RAW = %s\n", describe_cap (&data, CAP_NET_RAW));
  printf ("cap CAP_NET_ADMIN = %s\n\n", describe_cap (&data, CAP_NET_ADMIN));

  setuid(getuid());
  printf("As user: UID=%d EffUID=%d\n", getuid(), geteuid());

  cap_get(&header, &data);
  printf ("cap CAP_NET_RAW = %s\n", describe_cap (&data, CAP_NET_RAW));
  printf ("cap CAP_NET_ADMIN = %s\n\n", describe_cap (&data, CAP_NET_ADMIN));
}

-- 
Richard Urwin