capabilities: introduce per-process capability bounding set
authorSerge E. Hallyn <serue@us.ibm.com>
Tue, 5 Feb 2008 06:29:45 +0000 (22:29 -0800)
committerLinus Torvalds <torvalds@woody.linux-foundation.org>
Tue, 5 Feb 2008 17:44:20 +0000 (09:44 -0800)
The capability bounding set is a set beyond which capabilities cannot grow.
 Currently cap_bset is per-system.  It can be manipulated through sysctl,
but only init can add capabilities.  Root can remove capabilities.  By
default it includes all caps except CAP_SETPCAP.

This patch makes the bounding set per-process when file capabilities are
enabled.  It is inherited at fork from parent.  Noone can add elements,
CAP_SETPCAP is required to remove them.

One example use of this is to start a safer container.  For instance, until
device namespaces or per-container device whitelists are introduced, it is
best to take CAP_MKNOD away from a container.

The bounding set will not affect pP and pE immediately.  It will only
affect pP' and pE' after subsequent exec()s.  It also does not affect pI,
and exec() does not constrain pI'.  So to really start a shell with no way
of regain CAP_MKNOD, you would do

prctl(PR_CAPBSET_DROP, CAP_MKNOD);
cap_t cap = cap_get_proc();
cap_value_t caparray[1];
caparray[0] = CAP_MKNOD;
cap_set_flag(cap, CAP_INHERITABLE, 1, caparray, CAP_DROP);
cap_set_proc(cap);
cap_free(cap);

The following test program will get and set the bounding
set (but not pI).  For instance

./bset get
(lists capabilities in bset)
./bset drop cap_net_raw
(starts shell with new bset)
(use capset, setuid binary, or binary with
file capabilities to try to increase caps)

************************************************************
cap_bound.c
************************************************************
 #include <sys/prctl.h>
 #include <linux/capability.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

 #ifndef PR_CAPBSET_READ
 #define PR_CAPBSET_READ 23
 #endif

 #ifndef PR_CAPBSET_DROP
 #define PR_CAPBSET_DROP 24
 #endif

int usage(char *me)
{
printf("Usage: %s get\n", me);
printf("       %s drop <capability>\n", me);
return 1;
}

 #define numcaps 32
char *captable[numcaps] = {
"cap_chown",
"cap_dac_override",
"cap_dac_read_search",
"cap_fowner",
"cap_fsetid",
"cap_kill",
"cap_setgid",
"cap_setuid",
"cap_setpcap",
"cap_linux_immutable",
"cap_net_bind_service",
"cap_net_broadcast",
"cap_net_admin",
"cap_net_raw",
"cap_ipc_lock",
"cap_ipc_owner",
"cap_sys_module",
"cap_sys_rawio",
"cap_sys_chroot",
"cap_sys_ptrace",
"cap_sys_pacct",
"cap_sys_admin",
"cap_sys_boot",
"cap_sys_nice",
"cap_sys_resource",
"cap_sys_time",
"cap_sys_tty_config",
"cap_mknod",
"cap_lease",
"cap_audit_write",
"cap_audit_control",
"cap_setfcap"
};

int getbcap(void)
{
int comma=0;
unsigned long i;
int ret;

printf("i know of %d capabilities\n", numcaps);
printf("capability bounding set:");
for (i=0; i<numcaps; i++) {
ret = prctl(PR_CAPBSET_READ, i);
if (ret < 0)
perror("prctl");
else if (ret==1)
printf("%s%s", (comma++) ? ", " : " ", captable[i]);
}
printf("\n");
return 0;
}

int capdrop(char *str)
{
unsigned long i;

int found=0;
for (i=0; i<numcaps; i++) {
if (strcmp(captable[i], str) == 0) {
found=1;
break;
}
}
if (!found)
return 1;
if (prctl(PR_CAPBSET_DROP, i)) {
perror("prctl");
return 1;
}
return 0;
}

int main(int argc, char *argv[])
{
if (argc<2)
return usage(argv[0]);
if (strcmp(argv[1], "get")==0)
return getbcap();
if (strcmp(argv[1], "drop")!=0 || argc<3)
return usage(argv[0]);
if (capdrop(argv[2])) {
printf("unknown capability\n");
return 1;
}
return execl("/bin/bash", "/bin/bash", NULL);
}
************************************************************

[serue@us.ibm.com: fix typo]
Signed-off-by: Serge E. Hallyn <serue@us.ibm.com>
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Cc: James Morris <jmorris@namei.org>
Cc: Chris Wright <chrisw@sous-sol.org>
Cc: Casey Schaufler <casey@schaufler-ca.com>a
Signed-off-by: "Serge E. Hallyn" <serue@us.ibm.com>
Tested-by: Jiri Slaby <jirislaby@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
include/linux/capability.h
include/linux/init_task.h
include/linux/prctl.h
include/linux/sched.h
include/linux/security.h
include/linux/sysctl.h
kernel/fork.c
kernel/sys.c
kernel/sysctl.c
kernel/sysctl_check.c
security/commoncap.c

index a1d93da67fe977966a299137ce228d0a44bff739..ffe7bab8c3a0691226aefd99023bd8dd43865d40 100644 (file)
@@ -152,7 +152,9 @@ typedef struct kernel_cap_struct {
  *   Transfer any capability in your permitted set to any pid,
  *   remove any capability in your permitted set from any pid
  * With VFS support for capabilities (neither of above, but)
- *   Add any capability to the current process' inheritable set
+ *   Add any capability from current's capability bounding set
+ *       to the current process' inheritable set
+ *   Allow taking bits out of capability bounding set
  */
 
 #define CAP_SETPCAP          8
@@ -202,7 +204,6 @@ typedef struct kernel_cap_struct {
 #define CAP_IPC_OWNER        15
 
 /* Insert and remove kernel modules - modify kernel without limit */
-/* Modify cap_bset */
 #define CAP_SYS_MODULE       16
 
 /* Allow ioperm/iopl access */
@@ -314,6 +315,10 @@ typedef struct kernel_cap_struct {
 
 #define CAP_SETFCAP         31
 
+#define CAP_LAST_CAP         CAP_SETFCAP
+
+#define cap_valid(x) ((x) >= 0 && (x) <= CAP_LAST_CAP)
+
 /*
  * Bit location of each capability (used by user-space library and kernel)
  */
@@ -465,6 +470,8 @@ extern const kernel_cap_t __cap_init_eff_set;
 int capable(int cap);
 int __capable(struct task_struct *t, int cap);
 
+extern long cap_prctl_drop(unsigned long cap);
+
 #endif /* __KERNEL__ */
 
 #endif /* !_LINUX_CAPABILITY_H */
index f42663eaf655b697a452ed944847c505a1ad0607..1f74e1d7415fe9042e2e71467b40e4a56184ffa8 100644 (file)
@@ -121,6 +121,18 @@ extern struct group_info init_groups;
 #else
 #define INIT_IDS
 #endif
+
+#ifdef CONFIG_SECURITY_FILE_CAPABILITIES
+/*
+ * Because of the reduced scope of CAP_SETPCAP when filesystem
+ * capabilities are in effect, it is safe to allow CAP_SETPCAP to
+ * be available in the default configuration.
+ */
+# define CAP_INIT_BSET  CAP_FULL_SET
+#else
+# define CAP_INIT_BSET  CAP_INIT_EFF_SET
+#endif
+
 /*
  *  INIT_TASK is used to set up the first task table, touch at
  * your own risk!. Base=0, limit=0x1fffff (=2MB)
@@ -156,6 +168,7 @@ extern struct group_info init_groups;
        .cap_effective  = CAP_INIT_EFF_SET,                             \
        .cap_inheritable = CAP_INIT_INH_SET,                            \
        .cap_permitted  = CAP_FULL_SET,                                 \
+       .cap_bset       = CAP_INIT_BSET,                                \
        .keep_capabilities = 0,                                         \
        .user           = INIT_USER,                                    \
        .comm           = "swapper",                                    \
index e2eff9079fe9ff9f02f7e9704479571c02cdd9aa..3800639775aee4706734b1aa9bb3e55104fe4f3b 100644 (file)
@@ -63,4 +63,8 @@
 #define PR_GET_SECCOMP 21
 #define PR_SET_SECCOMP 22
 
+/* Get/set the capability bounding set */
+#define PR_CAPBSET_READ 23
+#define PR_CAPBSET_DROP 24
+
 #endif /* _LINUX_PRCTL_H */
index c30d174a02fa5b6b8343c4f058f9edf7ff0312f9..9c13be3a21e80509100ffb8ea8fda59fa238c7cf 100644 (file)
@@ -1098,7 +1098,7 @@ struct task_struct {
        uid_t uid,euid,suid,fsuid;
        gid_t gid,egid,sgid,fsgid;
        struct group_info *group_info;
-       kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
+       kernel_cap_t   cap_effective, cap_inheritable, cap_permitted, cap_bset;
        unsigned keep_capabilities:1;
        struct user_struct *user;
 #ifdef CONFIG_KEYS
index 9d289e726fd88131fba5d10628e5cedf71135481..fe52cdeab0a67ba5cd1220383b9bd0872efd78e1 100644 (file)
 #define ROOTCONTEXT_MNT                0x04
 #define DEFCONTEXT_MNT         0x08
 
-/*
- * Bounding set
- */
-extern kernel_cap_t cap_bset;
-
 extern unsigned securebits;
 
 struct ctl_table;
index bf4ae4e138f747d79c2903b7a09771f5145112c4..571f01d20a86373454000db25760f7a7aa2572da 100644 (file)
@@ -102,7 +102,6 @@ enum
        KERN_NODENAME=7,
        KERN_DOMAINNAME=8,
 
-       KERN_CAP_BSET=14,       /* int: capability bounding set */
        KERN_PANIC=15,          /* int: panic timeout */
        KERN_REALROOTDEV=16,    /* real root device to mount after initrd */
 
@@ -965,8 +964,6 @@ extern int proc_dostring(struct ctl_table *, int, struct file *,
                         void __user *, size_t *, loff_t *);
 extern int proc_dointvec(struct ctl_table *, int, struct file *,
                         void __user *, size_t *, loff_t *);
-extern int proc_dointvec_bset(struct ctl_table *, int, struct file *,
-                             void __user *, size_t *, loff_t *);
 extern int proc_dointvec_minmax(struct ctl_table *, int, struct file *,
                                void __user *, size_t *, loff_t *);
 extern int proc_dointvec_jiffies(struct ctl_table *, int, struct file *,
index 1160f87ba70063385324be45511638c9f3e9421b..2b55b74cd99999f1eccdedc2158d29945ab16d98 100644 (file)
@@ -1118,6 +1118,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 #ifdef CONFIG_SECURITY
        p->security = NULL;
 #endif
+       p->cap_bset = current->cap_bset;
        p->io_context = NULL;
        p->audit_context = NULL;
        cgroup_fork(p);
index d1fe71eb45469bc99f23cfe9aee83a8cbecc81b1..4162d12390b64a8e94cf20e251118524714a59e9 100644 (file)
@@ -1637,7 +1637,7 @@ asmlinkage long sys_umask(int mask)
        mask = xchg(&current->fs->umask, mask & S_IRWXUGO);
        return mask;
 }
-    
+
 asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3,
                          unsigned long arg4, unsigned long arg5)
 {
@@ -1742,6 +1742,17 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3,
                        error = prctl_set_seccomp(arg2);
                        break;
 
+               case PR_CAPBSET_READ:
+                       if (!cap_valid(arg2))
+                               return -EINVAL;
+                       return !!cap_raised(current->cap_bset, arg2);
+               case PR_CAPBSET_DROP:
+#ifdef CONFIG_SECURITY_FILE_CAPABILITIES
+                       return cap_prctl_drop(arg2);
+#else
+                       return -EINVAL;
+#endif
+
                default:
                        error = -EINVAL;
                        break;
index d0b47b85906731a5cda80d5630ea3dbb11a86810..5e2ad5bf88e280fdbc83b61a380909555e121a71 100644 (file)
@@ -419,15 +419,6 @@ static struct ctl_table kern_table[] = {
                .proc_handler   = &proc_dointvec,
        },
 #endif
-#ifdef CONFIG_SECURITY_CAPABILITIES
-       {
-               .procname       = "cap-bound",
-               .data           = &cap_bset,
-               .maxlen         = sizeof(kernel_cap_t),
-               .mode           = 0600,
-               .proc_handler   = &proc_dointvec_bset,
-       },
-#endif /* def CONFIG_SECURITY_CAPABILITIES */
 #ifdef CONFIG_BLK_DEV_INITRD
        {
                .ctl_name       = KERN_REALROOTDEV,
@@ -2096,26 +2087,6 @@ static int do_proc_dointvec_bset_conv(int *negp, unsigned long *lvalp,
        return 0;
 }
 
-#ifdef CONFIG_SECURITY_CAPABILITIES
-/*
- *     init may raise the set.
- */
-
-int proc_dointvec_bset(struct ctl_table *table, int write, struct file *filp,
-                       void __user *buffer, size_t *lenp, loff_t *ppos)
-{
-       int op;
-
-       if (write && !capable(CAP_SYS_MODULE)) {
-               return -EPERM;
-       }
-
-       op = is_global_init(current) ? OP_SET : OP_AND;
-       return do_proc_dointvec(table,write,filp,buffer,lenp,ppos,
-                               do_proc_dointvec_bset_conv,&op);
-}
-#endif /* def CONFIG_SECURITY_CAPABILITIES */
-
 /*
  *     Taint values can only be increased
  */
@@ -2529,12 +2500,6 @@ int proc_dointvec(struct ctl_table *table, int write, struct file *filp,
        return -ENOSYS;
 }
 
-int proc_dointvec_bset(struct ctl_table *table, int write, struct file *filp,
-                       void __user *buffer, size_t *lenp, loff_t *ppos)
-{
-       return -ENOSYS;
-}
-
 int proc_dointvec_minmax(struct ctl_table *table, int write, struct file *filp,
                    void __user *buffer, size_t *lenp, loff_t *ppos)
 {
index c3206fa50048b8333be64be54a3a52acf8d39bcb..006365b69eafdfcebbaa47ba1370a87d58813e94 100644 (file)
@@ -37,10 +37,6 @@ static struct trans_ctl_table trans_kern_table[] = {
        { KERN_NODENAME,                "hostname" },
        { KERN_DOMAINNAME,              "domainname" },
 
-#ifdef CONFIG_SECURITY_CAPABILITIES
-       { KERN_CAP_BSET,                "cap-bound" },
-#endif /* def CONFIG_SECURITY_CAPABILITIES */
-
        { KERN_PANIC,                   "panic" },
        { KERN_REALROOTDEV,             "real-root-dev" },
 
@@ -1498,9 +1494,6 @@ int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *table)
                            (table->strategy == sysctl_ms_jiffies) ||
                            (table->proc_handler == proc_dostring) ||
                            (table->proc_handler == proc_dointvec) ||
-#ifdef CONFIG_SECURITY_CAPABILITIES
-                           (table->proc_handler == proc_dointvec_bset) ||
-#endif /* def CONFIG_SECURITY_CAPABILITIES */
                            (table->proc_handler == proc_dointvec_minmax) ||
                            (table->proc_handler == proc_dointvec_jiffies) ||
                            (table->proc_handler == proc_dointvec_userhz_jiffies) ||
index 01ab47845dcf55e9fda443a2b9c952e2b356c9f8..5aba82679a0bb9e5506628c9190846a1134707e7 100644 (file)
 #include <linux/mount.h>
 #include <linux/sched.h>
 
-#ifdef CONFIG_SECURITY_FILE_CAPABILITIES
-/*
- * Because of the reduced scope of CAP_SETPCAP when filesystem
- * capabilities are in effect, it is safe to allow this capability to
- * be available in the default configuration.
- */
-# define CAP_INIT_BSET  CAP_FULL_SET
-#else /* ie. ndef CONFIG_SECURITY_FILE_CAPABILITIES */
-# define CAP_INIT_BSET  CAP_INIT_EFF_SET
-#endif /* def CONFIG_SECURITY_FILE_CAPABILITIES */
-
-kernel_cap_t cap_bset = CAP_INIT_BSET;    /* systemwide capability bound */
-EXPORT_SYMBOL(cap_bset);
-
 /* Global security state */
 
 unsigned securebits = SECUREBITS_DEFAULT; /* systemwide security settings */
@@ -140,6 +126,12 @@ int cap_capset_check (struct task_struct *target, kernel_cap_t *effective,
                /* incapable of using this inheritable set */
                return -EPERM;
        }
+       if (!cap_issubset(*inheritable,
+                          cap_combine(target->cap_inheritable,
+                                      current->cap_bset))) {
+               /* no new pI capabilities outside bounding set */
+               return -EPERM;
+       }
 
        /* verify restrictions on target's new Permitted set */
        if (!cap_issubset (*permitted,
@@ -337,10 +329,11 @@ void cap_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
        /* Derived from fs/exec.c:compute_creds. */
        kernel_cap_t new_permitted, working;
 
-       new_permitted = cap_intersect (bprm->cap_permitted, cap_bset);
-       working = cap_intersect (bprm->cap_inheritable,
+       new_permitted = cap_intersect(bprm->cap_permitted,
+                                current->cap_bset);
+       working = cap_intersect(bprm->cap_inheritable,
                                 current->cap_inheritable);
-       new_permitted = cap_combine (new_permitted, working);
+       new_permitted = cap_combine(new_permitted, working);
 
        if (bprm->e_uid != current->uid || bprm->e_gid != current->gid ||
            !cap_issubset (new_permitted, current->cap_permitted)) {
@@ -581,6 +574,23 @@ int cap_task_kill(struct task_struct *p, struct siginfo *info,
 
        return -EPERM;
 }
+
+/*
+ * called from kernel/sys.c for prctl(PR_CABSET_DROP)
+ * done without task_capability_lock() because it introduces
+ * no new races - i.e. only another task doing capget() on
+ * this task could get inconsistent info.  There can be no
+ * racing writer bc a task can only change its own caps.
+ */
+long cap_prctl_drop(unsigned long cap)
+{
+       if (!capable(CAP_SETPCAP))
+               return -EPERM;
+       if (!cap_valid(cap))
+               return -EINVAL;
+       cap_lower(current->cap_bset, cap);
+       return 0;
+}
 #else
 int cap_task_setscheduler (struct task_struct *p, int policy,
                           struct sched_param *lp)