[PATCH 2/2] capabilities: capability evolution

From: Andy Lutomirski
Date: Tue May 11 2004 - 22:30:23 EST


Change capability evolution to make capabilities useful.

include/linux/capability.h | 2
include/linux/init_task.h | 4 -
kernel/capability.c | 2
security/commoncap.c | 137 +++++++++++++++++++++++++++------------------ 4 files changed, 87 insertions(+), 58 deletions(-)

--- linux-2.6.6-mm1/security/commoncap.c~cap_2_evolution 2004-05-11 00:28:02.000000000 -0700
+++ linux-2.6.6-mm1/security/commoncap.c 2004-05-11 19:49:24.091175930 -0700
@@ -58,9 +58,7 @@
{
/* Derived from kernel/capability.c:sys_capset. */
/* verify restrictions on target's new Inheritable set */
- if (!cap_issubset (*inheritable,
- cap_combine (target->cap_inheritable,
- current->cap_permitted))) {
+ if (!cap_issubset (*inheritable, target->cap_inheritable)) {
return -EPERM;
}

@@ -76,6 +74,11 @@
return -EPERM;
}

+ /* verify the _new_Permitted_ is a subset of the _new_Inheritable_ */
+ if (!cap_issubset (*permitted, *inheritable)) {
+ return -EPERM;
+ }
+
return 0;
}

@@ -96,69 +99,96 @@
return 0;
}

+/*
+ *
+ * What the masks mean:
+ * pI = capabilities that this process or its children may have
+ * pP = capabilities that this process has
+ * pE = capabilities that this process has and are enabled
+ * (so pE <= pP <= pI)
+ *
+ * The capability evolution rules are:
+ *
+ * pI' = pI & fI
+ * pP' = ((fP & cap_bset) | (pP & Y)) & pI'
+ * pE' = (euid changed ? pP' : pE)
+ *
+ * Caveat: if (fP & ~pI'), there is no _theoretical_ problem, but
+ * this could introduce exploits in buggy programs. Since programs
+ * that aren't capability-aware are insecure _anyway_ if pP!=0, this
+ * is OK.
+ *
+ * To allow pI != ~0 to be secure in the presence of buggy programs,
+ * we require full pI for setuid.
+ *
+ * The moral is that, if file capabilities are introduced, programs
+ * that are granted fP > 0 need to be aware of how to deal with it.
+ * Because the effective set is left alone on non-setuid fP>0,
+ * such a program should drop capabilities that were not in its initial
+ * effective set before running untrusted code.
+ *
+ */
void cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
{
- /* Derived from fs/exec.c:compute_creds. */
- kernel_cap_t new_permitted, working;
+ kernel_cap_t new_pI, new_pP;

- /* This code is clearly broken. I think it's equivalent
- * to the old code...
- */
- int euid, egid;
- euid = bprm->set_uid;
- egid = bprm->set_gid;
- if (euid != 0 && current->uid != 0)
- cap_clear(bprm->cap_inheritable);
-
- new_permitted = cap_intersect (bprm->cap_permitted, cap_bset);
- working = cap_intersect (bprm->cap_inheritable,
- current->cap_inheritable);
- new_permitted = cap_combine (new_permitted, working);
-
- if (euid != current->uid || egid != current->gid ||
-
- !cap_issubset (new_permitted, current->cap_permitted)) {
- current->mm->dumpable = 0;
-
- if (unsafe & ~LSM_UNSAFE_PTRACE_CAP) {
- if (!capable(CAP_SETUID)) {
- euid = current->uid;
- egid = current->gid;
- }
- if (!capable (CAP_SETPCAP)) {
- new_permitted = cap_intersect (new_permitted,
- current->cap_permitted);
- }
+ new_pI = cap_intersect(current->cap_inheritable, bprm->cap_inheritable);
+
+ /* Check if setuid/setgid is safe */
+ if (bprm->secflags & (BINPRM_SEC_SETUID | BINPRM_SEC_SETGID)) {
+ int revoke_setid = 0;
+ if (unsafe & ~LSM_UNSAFE_PTRACE_CAP) revoke_setid = 1;
+
+ /* FIXME: Is this overly harsh on setgid? */
+ if (new_pI != CAP_FULL_SET) revoke_setid = 1;
+
+ /* Don't break root. */
+ if (revoke_setid && capable(CAP_SETUID)) revoke_setid = 0;
+
+ if (revoke_setid) {
+ bprm->secflags &=
+ ~(BINPRM_SEC_SETUID | BINPRM_SEC_SETGID);
+ cap_clear(bprm->cap_permitted);
}
}

- current->suid = current->euid = current->fsuid = euid;
- current->sgid = current->egid = current->fsgid = egid;
-
- /* For init, we want to retain the capabilities set
- * in the init_task struct. Thus we skip the usual
- * capability rules */
- if (current->pid != 1) {
- current->cap_permitted = new_permitted;
- current->cap_effective = new_permitted;
- if (euid != 0)
- cap_clear(current->cap_effective);
+ /* Enforce unsafeness (a noop without VFS caps) */
+ if(unsafe & ~LSM_UNSAFE_PTRACE_CAP)
+ cap_clear(bprm->cap_permitted);
+
+
+ new_pP = cap_intersect(bprm->cap_permitted, cap_bset);
+ new_pP = cap_combine(new_pP, current->cap_permitted);
+ new_pP = cap_intersect(new_pP, new_pI);
+
+ /* setuid-nonroot is special */
+ if ((bprm->secflags & BINPRM_SEC_SETUID) && bprm->set_uid != 0 &&
+ current->uid != 0 && current->euid == 0)
+ cap_clear(new_pP);
+
+ if(!cap_issubset(new_pP, current->cap_permitted))
+ bprm->secflags |= BINPRM_SEC_SECUREEXEC;
+
+ /* Apply new security state */
+ if (bprm->secflags & BINPRM_SEC_SETUID) {
+ current->suid = current->euid = current->fsuid = bprm->set_uid;
+ current->cap_effective = new_pP;
}
+ if (bprm->secflags & BINPRM_SEC_SETGID)
+ current->sgid = current->egid = current->fsgid = bprm->set_gid;

- /* AUD: Audit candidate if current->cap_effective is set */
+ current->cap_inheritable = new_pI;
+ current->cap_permitted = new_pP;
+ cap_mask(current->cap_effective, new_pP);

current->keep_capabilities = 0;
}

int cap_bprm_secureexec (struct linux_binprm *bprm)
{
- /* If/when this module is enhanced to incorporate capability
- bits on files, the test below should be extended to also perform a
- test between the old and new capability sets. For now,
- it simply preserves the legacy decision algorithm used by
- the old userland. */
return (current->euid != current->uid ||
- current->egid != current->gid);
+ current->egid != current->gid ||
+ (bprm->secflags & BINPRM_SEC_SECUREEXEC));
}

int cap_inode_setxattr(struct dentry *dentry, char *name, void *value,
@@ -272,8 +302,9 @@

void cap_task_reparent_to_init (struct task_struct *p)
{
- p->cap_effective = CAP_INIT_EFF_SET;
- p->cap_inheritable = CAP_INIT_INH_SET;
+ /* It's a kernel thread. Don't worry about enforcing inheritable. */
+ p->cap_effective = CAP_FULL_SET;
+ p->cap_inheritable = CAP_FULL_SET;
p->cap_permitted = CAP_FULL_SET;
p->keep_capabilities = 0;
return;
--- linux-2.6.6-mm1/kernel/capability.c~cap_2_evolution 2004-05-11 12:14:40.000000000 -0700
+++ linux-2.6.6-mm1/kernel/capability.c 2004-05-11 12:17:22.000000000 -0700
@@ -13,7 +13,7 @@
#include <asm/uaccess.h>

unsigned securebits = SECUREBITS_DEFAULT; /* systemwide security settings */
-kernel_cap_t cap_bset = CAP_INIT_EFF_SET;
+kernel_cap_t cap_bset = CAP_FULL_SET;
int sysctl_mlock_group;

EXPORT_SYMBOL(securebits);
--- linux-2.6.6-mm1/include/linux/capability.h~cap_2_evolution 2004-05-11 01:43:50.000000000 -0700
+++ linux-2.6.6-mm1/include/linux/capability.h 2004-05-11 01:44:03.000000000 -0700
@@ -308,8 +308,6 @@

#define CAP_EMPTY_SET to_cap_t(0)
#define CAP_FULL_SET to_cap_t(~0)
-#define CAP_INIT_EFF_SET to_cap_t(~0 & ~CAP_TO_MASK(CAP_SETPCAP))
-#define CAP_INIT_INH_SET to_cap_t(0)

#define CAP_TO_MASK(x) (1 << (x))
#define cap_raise(c, flag) (cap_t(c) |= CAP_TO_MASK(flag))
--- linux-2.6.6-mm1/include/linux/init_task.h~cap_2_evolution 2004-05-11 01:44:13.000000000 -0700
+++ linux-2.6.6-mm1/include/linux/init_task.h 2004-05-11 01:44:31.000000000 -0700
@@ -92,8 +92,8 @@
.function = it_real_fn \
}, \
.group_info = &init_groups, \
- .cap_effective = CAP_INIT_EFF_SET, \
- .cap_inheritable = CAP_INIT_INH_SET, \
+ .cap_effective = CAP_FULL_SET, \
+ .cap_inheritable = CAP_FULL_SET, \
.cap_permitted = CAP_FULL_SET, \
.keep_capabilities = 0, \
.rlim = INIT_RLIMITS, \


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