2007-11-26 20:09:24

by Serge E. Hallyn

[permalink] [raw]
Subject: [PATCH] capabilities: introduce per-process capability bounding set (v10)

>From 22da6ccb1a24d1b6fa481d990a26197c6bfdfa77 Mon Sep 17 00:00:00 2001
From: Serge E. Hallyn <[email protected]>
Date: Mon, 19 Nov 2007 13:54:05 -0500
Subject: [PATCH 1/1] capabilities: introduce per-process capability bounding set (v10)

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);
}
************************************************************

Signed-off-by: Serge E. Hallyn <[email protected]>
---
include/linux/capability.h | 11 +++++++++--
include/linux/init_task.h | 12 ++++++++++++
include/linux/prctl.h | 4 ++++
include/linux/sched.h | 2 +-
include/linux/security.h | 5 -----
include/linux/sysctl.h | 3 ---
kernel/fork.c | 1 +
kernel/sys.c | 13 ++++++++++++-
kernel/sysctl.c | 35 -----------------------------------
kernel/sysctl_check.c | 7 -------
security/commoncap.c | 44 +++++++++++++++++++++++++++-----------------
11 files changed, 66 insertions(+), 71 deletions(-)

diff --git a/include/linux/capability.h b/include/linux/capability.h
index a1d93da..ffe7bab 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -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 */
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index cae35b6..83975d9 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -114,6 +114,17 @@ extern struct group_info init_groups;
.pid = &init_struct_pid, \
}

+#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)
@@ -147,6 +158,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", \
diff --git a/include/linux/prctl.h b/include/linux/prctl.h
index e2eff90..3800639 100644
--- a/include/linux/prctl.h
+++ b/include/linux/prctl.h
@@ -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 */
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 1d17f7c..bf51a16 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1041,7 +1041,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
diff --git a/include/linux/security.h b/include/linux/security.h
index f771ad8..04b18f1 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -34,11 +34,6 @@
#include <linux/xfrm.h>
#include <net/flow.h>

-/*
- * Bounding set
- */
-extern kernel_cap_t cap_bset;
-
extern unsigned securebits;

struct ctl_table;
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 4f5047d..fa900cb 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -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 */

@@ -962,8 +961,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 *,
diff --git a/kernel/fork.c b/kernel/fork.c
index 5639b3e..9e4a5e1 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1087,6 +1087,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);
diff --git a/kernel/sys.c b/kernel/sys.c
index 4c77ed2..efc495e 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -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;
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 489b0d1..d858819 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -383,15 +383,6 @@ static struct ctl_table kern_table[] = {
.proc_handler = &proc_dointvec_taint,
},
#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,
@@ -1910,26 +1901,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
*/
@@ -2343,12 +2314,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)
{
diff --git a/kernel/sysctl_check.c b/kernel/sysctl_check.c
index 8f5baac..526fa36 100644
--- a/kernel/sysctl_check.c
+++ b/kernel/sysctl_check.c
@@ -38,10 +38,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" },

@@ -1522,9 +1518,6 @@ int sysctl_check_table(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) ||
diff --git a/security/commoncap.c b/security/commoncap.c
index 3a95990..cb71bb0 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -25,20 +25,6 @@
#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 */
@@ -133,6 +119,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,
@@ -330,10 +322,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)) {
@@ -565,6 +558,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)
--
1.5.1.1.GIT


2007-11-27 03:42:37

by Andrew G. Morgan

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

This looks good to me.

[As you anticipated, there is a potential merge issue with Casey's
recent addition of MAC capabilities - which will make CAP_MAC_ADMIN the
highest allocated capability: ie.,

#define CAP_LAST_CAP CAP_MAC_ADMIN

].

Signed-off-by: Andrew G. Morgan <[email protected]>

Cheers

Andrew

Serge E. Hallyn wrote:
>>From 22da6ccb1a24d1b6fa481d990a26197c6bfdfa77 Mon Sep 17 00:00:00 2001
> From: Serge E. Hallyn <[email protected]>
> Date: Mon, 19 Nov 2007 13:54:05 -0500
> Subject: [PATCH 1/1] capabilities: introduce per-process capability bounding set (v10)
>
> 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);
> }
> ************************************************************
>
> Signed-off-by: Serge E. Hallyn <[email protected]>
> ---
> include/linux/capability.h | 11 +++++++++--
> include/linux/init_task.h | 12 ++++++++++++
> include/linux/prctl.h | 4 ++++
> include/linux/sched.h | 2 +-
> include/linux/security.h | 5 -----
> include/linux/sysctl.h | 3 ---
> kernel/fork.c | 1 +
> kernel/sys.c | 13 ++++++++++++-
> kernel/sysctl.c | 35 -----------------------------------
> kernel/sysctl_check.c | 7 -------
> security/commoncap.c | 44 +++++++++++++++++++++++++++-----------------
> 11 files changed, 66 insertions(+), 71 deletions(-)
>
> diff --git a/include/linux/capability.h b/include/linux/capability.h
> index a1d93da..ffe7bab 100644
> --- a/include/linux/capability.h
> +++ b/include/linux/capability.h
> @@ -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 */
> diff --git a/include/linux/init_task.h b/include/linux/init_task.h
> index cae35b6..83975d9 100644
> --- a/include/linux/init_task.h
> +++ b/include/linux/init_task.h
> @@ -114,6 +114,17 @@ extern struct group_info init_groups;
> .pid = &init_struct_pid, \
> }
>
> +#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)
> @@ -147,6 +158,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", \
> diff --git a/include/linux/prctl.h b/include/linux/prctl.h
> index e2eff90..3800639 100644
> --- a/include/linux/prctl.h
> +++ b/include/linux/prctl.h
> @@ -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 */
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 1d17f7c..bf51a16 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1041,7 +1041,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
> diff --git a/include/linux/security.h b/include/linux/security.h
> index f771ad8..04b18f1 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -34,11 +34,6 @@
> #include <linux/xfrm.h>
> #include <net/flow.h>
>
> -/*
> - * Bounding set
> - */
> -extern kernel_cap_t cap_bset;
> -
> extern unsigned securebits;
>
> struct ctl_table;
> diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
> index 4f5047d..fa900cb 100644
> --- a/include/linux/sysctl.h
> +++ b/include/linux/sysctl.h
> @@ -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 */
>
> @@ -962,8 +961,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 *,
> diff --git a/kernel/fork.c b/kernel/fork.c
> index 5639b3e..9e4a5e1 100644
> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -1087,6 +1087,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);
> diff --git a/kernel/sys.c b/kernel/sys.c
> index 4c77ed2..efc495e 100644
> --- a/kernel/sys.c
> +++ b/kernel/sys.c
> @@ -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;
> diff --git a/kernel/sysctl.c b/kernel/sysctl.c
> index 489b0d1..d858819 100644
> --- a/kernel/sysctl.c
> +++ b/kernel/sysctl.c
> @@ -383,15 +383,6 @@ static struct ctl_table kern_table[] = {
> .proc_handler = &proc_dointvec_taint,
> },
> #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,
> @@ -1910,26 +1901,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
> */
> @@ -2343,12 +2314,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)
> {
> diff --git a/kernel/sysctl_check.c b/kernel/sysctl_check.c
> index 8f5baac..526fa36 100644
> --- a/kernel/sysctl_check.c
> +++ b/kernel/sysctl_check.c
> @@ -38,10 +38,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" },
>
> @@ -1522,9 +1518,6 @@ int sysctl_check_table(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) ||
> diff --git a/security/commoncap.c b/security/commoncap.c
> index 3a95990..cb71bb0 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -25,20 +25,6 @@
> #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 */
> @@ -133,6 +119,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,
> @@ -330,10 +322,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)) {
> @@ -565,6 +558,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)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)

iD8DBQFHS5IeQheEq9QabfIRAu+mAJ9jZRoYVT75lv3hjtfMCArnorIQfwCdFV/J
Z0EkH2TwcGqEO9JaDrnWXNE=
=9LbV
-----END PGP SIGNATURE-----

2007-11-27 18:42:28

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Quoting Andrew Morgan ([email protected]):
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> This looks good to me.
>
> [As you anticipated, there is a potential merge issue with Casey's
> recent addition of MAC capabilities - which will make CAP_MAC_ADMIN the
> highest allocated capability: ie.,
>
> #define CAP_LAST_CAP CAP_MAC_ADMIN
>
> ].
>
> Signed-off-by: Andrew G. Morgan <[email protected]>

Thanks, Andrew.

Yes, the following patch will be needed on top of the previous one:

-serge

>From 97ee046e8075a21b356fb93db0769d440437ef51 Mon Sep 17 00:00:00 2001
From: [email protected] <hallyn@kernel.(none)>
Date: Tue, 27 Nov 2007 10:37:57 -0800
Subject: [PATCH 1/1] capabilities: fix CAP_LAST_CAP to CAP_MAC_ADMIN

A recent SMACK patch introduced two new capabilities. The capability
bounding set patch defined CAP_LAST_CAP erroneously relative to that
patch.

Signed-off-by: Serge Hallyn <[email protected]>
---
include/linux/capability.h | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/include/linux/capability.h b/include/linux/capability.h
index 5fc3fea..7d50ff6 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -332,7 +332,7 @@ typedef struct kernel_cap_struct {

#define CAP_MAC_ADMIN 33

-#define CAP_LAST_CAP CAP_SETFCAP
+#define CAP_LAST_CAP CAP_MAC_ADMIN

#define cap_valid(x) ((x) >= 0 && (x) <= CAP_LAST_CAP)

--
1.5.1

2007-12-01 01:47:32

by KaiGai Kohei

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Serge E. Hallyn wrote:
> 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.

Serge,

This feature makes me being interested in.
I think you intend to apply this feature for the primary process
of security container.
However, it is also worthwhile to apply when a session is starting up.

The following PAM module enables to drop capability bounding bit
specified by the fifth field in /etc/passwd entry.
This code is just an example now, but considerable feature.

build and install:
# gcc -Wall -c pam_cap_drop.c
# gcc -Wall -shared -Xlinker -x -o pam_cap_drop.so pam_cap_drop.o -lpam
# cp pam_cap_drop.so /lib/security

modify /etc/passwd as follows:

tak:x:1004:100:cap_drop=cap_net_raw,cap_chown:/home/tak:/bin/bash
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
example:
[kaigai@masu ~]$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.23 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.02 ms

--- 192.168.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 1.023/1.130/1.237/0.107 ms

[kaigai@masu ~]$ ssh tak@localhost
tak@localhost's password:
Last login: Sat Dec 1 10:09:29 2007 from masu.myhome.cx
[tak@masu ~]$ export LANG=C
[tak@masu ~]$ ping 192.168.1.1
ping: icmp open socket: Operation not permitted

[tak@masu ~]$ su
Password:
pam_cap_bset[6921]: user root does not have 'cap_drop=' property
[root@masu tak]# cat /proc/self/status | grep ^Cap
CapInh: 0000000000000000
CapPrm: 00000000ffffdffe
CapEff: 00000000ffffdffe
[root@masu tak]#

# BTW, I replaced the James's address in the Cc: list,
# because MTA does not accept it.
--
KaiGai Kohei <[email protected]>

************************************************************
pam_cap_drop.c
************************************************************

/*
* pam_cap_drop.c module -- drop capabilities bounding set
*
* Copyright: 2007 KaiGai Kohei <[email protected]>
*/

#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/prctl.h>
#include <sys/types.h>

#include <security/pam_modules.h>

#ifndef PR_CAPBSET_DROP
#define PR_CAPBSET_DROP 24
#endif

static char *captable[] = {
"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",
NULL,
};


PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
struct passwd *pwd;
char *pos, *buf;
char *username = NULL;

/* open system logger */
openlog("pam_cap_bset", LOG_PERROR | LOG_PID, LOG_AUTHPRIV);

/* get the unix username */
if (pam_get_item(pamh, PAM_USER, (void *) &username) != PAM_SUCCESS || !username)
return PAM_USER_UNKNOWN;

/* get the passwd entry */
pwd = getpwnam(username);
if (!pwd)
return PAM_USER_UNKNOWN;

/* Is there "cap_drop=" ? */
pos = strstr(pwd->pw_gecos, "cap_drop=");
if (pos) {
buf = strdup(pos + sizeof("cap_drop=") - 1);
if (!buf)
return PAM_SESSION_ERR;
pos = strtok(buf, ",");
while (pos) {
int rc, i;

for (i=0; captable[i]; i++) {
if (!strcmp(pos, captable[i])) {
rc = prctl(PR_CAPBSET_DROP, i);
if (rc < 0) {
syslog(LOG_NOTICE, "user %s could not drop %s (%s)",
username, captable[i], strerror(errno));
break;
}
syslog(LOG_NOTICE, "user %s drops %s\n", username, captable[i]);
goto next;
}
}
break;
next:
pos = strtok(NULL, ",");
}
free(buf);
} else {
syslog(LOG_NOTICE, "user %s does not have 'cap_drop=' property", username);
}
return PAM_SUCCESS;
}

PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
/* do nothing */
return PAM_SUCCESS;
}

************************************************************

2007-12-01 03:59:33

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Quoting KaiGai Kohei ([email protected]):
> Serge E. Hallyn wrote:
> > 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.
>
> Serge,
>
> This feature makes me being interested in.
> I think you intend to apply this feature for the primary process
> of security container.
> However, it is also worthwhile to apply when a session is starting up.
>
> The following PAM module enables to drop capability bounding bit
> specified by the fifth field in /etc/passwd entry.
> This code is just an example now, but considerable feature.
>
> build and install:
> # gcc -Wall -c pam_cap_drop.c
> # gcc -Wall -shared -Xlinker -x -o pam_cap_drop.so pam_cap_drop.o -lpam
> # cp pam_cap_drop.so /lib/security
>
> modify /etc/passwd as follows:
>
> tak:x:1004:100:cap_drop=cap_net_raw,cap_chown:/home/tak:/bin/bash
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> example:
> [kaigai@masu ~]$ ping 192.168.1.1
> PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
> 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.23 ms
> 64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.02 ms
>
> --- 192.168.1.1 ping statistics ---
> 2 packets transmitted, 2 received, 0% packet loss, time 999ms
> rtt min/avg/max/mdev = 1.023/1.130/1.237/0.107 ms
>
> [kaigai@masu ~]$ ssh tak@localhost
> tak@localhost's password:
> Last login: Sat Dec 1 10:09:29 2007 from masu.myhome.cx
> [tak@masu ~]$ export LANG=C
> [tak@masu ~]$ ping 192.168.1.1
> ping: icmp open socket: Operation not permitted
>
> [tak@masu ~]$ su
> Password:
> pam_cap_bset[6921]: user root does not have 'cap_drop=' property
> [root@masu tak]# cat /proc/self/status | grep ^Cap
> CapInh: 0000000000000000
> CapPrm: 00000000ffffdffe
> CapEff: 00000000ffffdffe
> [root@masu tak]#

Neat. A bigger-stick version of not adding the account to
group wheel. I'll use that.

Is there any reason not to have a separate /etc/login.capbounds
config file, though, so the account can still have a full name?
Did you only use that for convenience of proof of concept, or
is there another reason?

> # BTW, I replaced the James's address in the Cc: list,
> # because MTA does not accept it.

Thanks! I don't know what happened to my alias for him...

thanks,
-serge

> --
> KaiGai Kohei <[email protected]>
>
> ************************************************************
> pam_cap_drop.c
> ************************************************************
>
> /*
> * pam_cap_drop.c module -- drop capabilities bounding set
> *
> * Copyright: 2007 KaiGai Kohei <[email protected]>
> */
>
> #include <errno.h>
> #include <pwd.h>
> #include <stdlib.h>
> #include <stdio.h>
> #include <string.h>
> #include <syslog.h>
> #include <sys/prctl.h>
> #include <sys/types.h>
>
> #include <security/pam_modules.h>
>
> #ifndef PR_CAPBSET_DROP
> #define PR_CAPBSET_DROP 24
> #endif
>
> static char *captable[] = {
> "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",
> NULL,
> };
>
>
> PAM_EXTERN int
> pam_sm_open_session(pam_handle_t *pamh, int flags,
> int argc, const char **argv)
> {
> struct passwd *pwd;
> char *pos, *buf;
> char *username = NULL;
>
> /* open system logger */
> openlog("pam_cap_bset", LOG_PERROR | LOG_PID, LOG_AUTHPRIV);
>
> /* get the unix username */
> if (pam_get_item(pamh, PAM_USER, (void *) &username) != PAM_SUCCESS || !username)
> return PAM_USER_UNKNOWN;
>
> /* get the passwd entry */
> pwd = getpwnam(username);
> if (!pwd)
> return PAM_USER_UNKNOWN;
>
> /* Is there "cap_drop=" ? */
> pos = strstr(pwd->pw_gecos, "cap_drop=");
> if (pos) {
> buf = strdup(pos + sizeof("cap_drop=") - 1);
> if (!buf)
> return PAM_SESSION_ERR;
> pos = strtok(buf, ",");
> while (pos) {
> int rc, i;
>
> for (i=0; captable[i]; i++) {
> if (!strcmp(pos, captable[i])) {
> rc = prctl(PR_CAPBSET_DROP, i);
> if (rc < 0) {
> syslog(LOG_NOTICE, "user %s could not drop %s (%s)",
> username, captable[i], strerror(errno));
> break;
> }
> syslog(LOG_NOTICE, "user %s drops %s\n", username, captable[i]);
> goto next;
> }
> }
> break;
> next:
> pos = strtok(NULL, ",");
> }
> free(buf);
> } else {
> syslog(LOG_NOTICE, "user %s does not have 'cap_drop=' property", username);
> }
> return PAM_SUCCESS;
> }
>
> PAM_EXTERN int
> pam_sm_close_session(pam_handle_t *pamh, int flags,
> int argc, const char **argv)
> {
> /* do nothing */
> return PAM_SUCCESS;
> }
>
> ************************************************************
> -
> To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2007-12-01 19:11:25

by Andrew G. Morgan

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

There is already a pam_cap module in the libcap2 package. Can we merge
this functionality?

Cheers

Andrew

[email protected] wrote:
> Quoting KaiGai Kohei ([email protected]):
>> Serge E. Hallyn wrote:
>>> 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.
>> Serge,
>>
>> This feature makes me being interested in.
>> I think you intend to apply this feature for the primary process
>> of security container.
>> However, it is also worthwhile to apply when a session is starting up.
>>
>> The following PAM module enables to drop capability bounding bit
>> specified by the fifth field in /etc/passwd entry.
>> This code is just an example now, but considerable feature.
>>
>> build and install:
>> # gcc -Wall -c pam_cap_drop.c
>> # gcc -Wall -shared -Xlinker -x -o pam_cap_drop.so pam_cap_drop.o -lpam
>> # cp pam_cap_drop.so /lib/security
>>
>> modify /etc/passwd as follows:
>>
>> tak:x:1004:100:cap_drop=cap_net_raw,cap_chown:/home/tak:/bin/bash
>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>> example:
>> [kaigai@masu ~]$ ping 192.168.1.1
>> PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
>> 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.23 ms
>> 64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.02 ms
>>
>> --- 192.168.1.1 ping statistics ---
>> 2 packets transmitted, 2 received, 0% packet loss, time 999ms
>> rtt min/avg/max/mdev = 1.023/1.130/1.237/0.107 ms
>>
>> [kaigai@masu ~]$ ssh tak@localhost
>> tak@localhost's password:
>> Last login: Sat Dec 1 10:09:29 2007 from masu.myhome.cx
>> [tak@masu ~]$ export LANG=C
>> [tak@masu ~]$ ping 192.168.1.1
>> ping: icmp open socket: Operation not permitted
>>
>> [tak@masu ~]$ su
>> Password:
>> pam_cap_bset[6921]: user root does not have 'cap_drop=' property
>> [root@masu tak]# cat /proc/self/status | grep ^Cap
>> CapInh: 0000000000000000
>> CapPrm: 00000000ffffdffe
>> CapEff: 00000000ffffdffe
>> [root@masu tak]#
>
> Neat. A bigger-stick version of not adding the account to
> group wheel. I'll use that.
>
> Is there any reason not to have a separate /etc/login.capbounds
> config file, though, so the account can still have a full name?
> Did you only use that for convenience of proof of concept, or
> is there another reason?
>
>> # BTW, I replaced the James's address in the Cc: list,
>> # because MTA does not accept it.
>
> Thanks! I don't know what happened to my alias for him...
>
> thanks,
> -serge
>
>> --
>> KaiGai Kohei <[email protected]>
>>
>> ************************************************************
>> pam_cap_drop.c
>> ************************************************************
>>
>> /*
>> * pam_cap_drop.c module -- drop capabilities bounding set
>> *
>> * Copyright: 2007 KaiGai Kohei <[email protected]>
>> */
>>
>> #include <errno.h>
>> #include <pwd.h>
>> #include <stdlib.h>
>> #include <stdio.h>
>> #include <string.h>
>> #include <syslog.h>
>> #include <sys/prctl.h>
>> #include <sys/types.h>
>>
>> #include <security/pam_modules.h>
>>
>> #ifndef PR_CAPBSET_DROP
>> #define PR_CAPBSET_DROP 24
>> #endif
>>
>> static char *captable[] = {
>> "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",
>> NULL,
>> };
>>
>>
>> PAM_EXTERN int
>> pam_sm_open_session(pam_handle_t *pamh, int flags,
>> int argc, const char **argv)
>> {
>> struct passwd *pwd;
>> char *pos, *buf;
>> char *username = NULL;
>>
>> /* open system logger */
>> openlog("pam_cap_bset", LOG_PERROR | LOG_PID, LOG_AUTHPRIV);
>>
>> /* get the unix username */
>> if (pam_get_item(pamh, PAM_USER, (void *) &username) != PAM_SUCCESS || !username)
>> return PAM_USER_UNKNOWN;
>>
>> /* get the passwd entry */
>> pwd = getpwnam(username);
>> if (!pwd)
>> return PAM_USER_UNKNOWN;
>>
>> /* Is there "cap_drop=" ? */
>> pos = strstr(pwd->pw_gecos, "cap_drop=");
>> if (pos) {
>> buf = strdup(pos + sizeof("cap_drop=") - 1);
>> if (!buf)
>> return PAM_SESSION_ERR;
>> pos = strtok(buf, ",");
>> while (pos) {
>> int rc, i;
>>
>> for (i=0; captable[i]; i++) {
>> if (!strcmp(pos, captable[i])) {
>> rc = prctl(PR_CAPBSET_DROP, i);
>> if (rc < 0) {
>> syslog(LOG_NOTICE, "user %s could not drop %s (%s)",
>> username, captable[i], strerror(errno));
>> break;
>> }
>> syslog(LOG_NOTICE, "user %s drops %s\n", username, captable[i]);
>> goto next;
>> }
>> }
>> break;
>> next:
>> pos = strtok(NULL, ",");
>> }
>> free(buf);
>> } else {
>> syslog(LOG_NOTICE, "user %s does not have 'cap_drop=' property", username);
>> }
>> return PAM_SUCCESS;
>> }
>>
>> PAM_EXTERN int
>> pam_sm_close_session(pam_handle_t *pamh, int flags,
>> int argc, const char **argv)
>> {
>> /* do nothing */
>> return PAM_SUCCESS;
>> }
>>
>> ************************************************************
>> -
>> To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHUbHDmwytjiwfWMwRAj5uAJ9+OB8ljQlJAhKW7jxJWrIPa1k2vgCdHFL9
3zXtwGz+cVeThb53/kAmdCs=
=OdM7
-----END PGP SIGNATURE-----

2007-12-02 01:28:43

by KaiGai Kohei

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Serge,

> Is there any reason not to have a separate /etc/login.capbounds
> config file, though, so the account can still have a full name?
> Did you only use that for convenience of proof of concept, or
> is there another reason?

passwd(5) says the fifth field is optional and only used for
informational purpose (like ulimit, umask).

However, using any other separate config file is conservative
and better. One candidate is "/etc/security/capability.conf"
defined as the config file of pam_cap.

Thanks,
--
KaiGai Kohei <[email protected]>

2007-12-02 03:29:59

by KaiGai Kohei

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

> There is already a pam_cap module in the libcap2 package. Can we merge
> this functionality?

I think it is a good idea.

However, this module already have a feature to modify inheritable
capability set.
How does it to be described in the "/etc/security/capability.conf"?

One idea is like a following convention:

# compatible configuration. We can omit "i:" at the head of line
cap_setfcap tak
# It drops any capabilities from b-set except for cap_net_raw and cap_fowner
b:cap_net_raw,cap_fowner ymj
# It drops only cap_dac_override from b-set.
b:-cap_dac_override kaigai
# It drops only cap_sys_admin from b-set of any user within users group.
b:-cap_sys_admin group:users

Thanks,

> Cheers
>
> Andrew
>
> [email protected] wrote:
>> Quoting KaiGai Kohei ([email protected]):
>>> Serge E. Hallyn wrote:
>>>> 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.
>>> Serge,
>>>
>>> This feature makes me being interested in.
>>> I think you intend to apply this feature for the primary process
>>> of security container.
>>> However, it is also worthwhile to apply when a session is starting up.
>>>
>>> The following PAM module enables to drop capability bounding bit
>>> specified by the fifth field in /etc/passwd entry.
>>> This code is just an example now, but considerable feature.
>>>
>>> build and install:
>>> # gcc -Wall -c pam_cap_drop.c
>>> # gcc -Wall -shared -Xlinker -x -o pam_cap_drop.so pam_cap_drop.o -lpam
>>> # cp pam_cap_drop.so /lib/security
>>>
>>> modify /etc/passwd as follows:
>>>
>>> tak:x:1004:100:cap_drop=cap_net_raw,cap_chown:/home/tak:/bin/bash
>>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>> example:
>>> [kaigai@masu ~]$ ping 192.168.1.1
>>> PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
>>> 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.23 ms
>>> 64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.02 ms
>>>
>>> --- 192.168.1.1 ping statistics ---
>>> 2 packets transmitted, 2 received, 0% packet loss, time 999ms
>>> rtt min/avg/max/mdev = 1.023/1.130/1.237/0.107 ms
>>>
>>> [kaigai@masu ~]$ ssh tak@localhost
>>> tak@localhost's password:
>>> Last login: Sat Dec 1 10:09:29 2007 from masu.myhome.cx
>>> [tak@masu ~]$ export LANG=C
>>> [tak@masu ~]$ ping 192.168.1.1
>>> ping: icmp open socket: Operation not permitted
>>>
>>> [tak@masu ~]$ su
>>> Password:
>>> pam_cap_bset[6921]: user root does not have 'cap_drop=' property
>>> [root@masu tak]# cat /proc/self/status | grep ^Cap
>>> CapInh: 0000000000000000
>>> CapPrm: 00000000ffffdffe
>>> CapEff: 00000000ffffdffe
>>> [root@masu tak]#
>> Neat. A bigger-stick version of not adding the account to
>> group wheel. I'll use that.
>>
>> Is there any reason not to have a separate /etc/login.capbounds
>> config file, though, so the account can still have a full name?
>> Did you only use that for convenience of proof of concept, or
>> is there another reason?
>>
>>> # BTW, I replaced the James's address in the Cc: list,
>>> # because MTA does not accept it.
>> Thanks! I don't know what happened to my alias for him...
>>
>> thanks,
>> -serge
>>
>>> --
>>> KaiGai Kohei <[email protected]>
>>>
>>> ************************************************************
>>> pam_cap_drop.c
>>> ************************************************************
>>>
>>> /*
>>> * pam_cap_drop.c module -- drop capabilities bounding set
>>> *
>>> * Copyright: 2007 KaiGai Kohei <[email protected]>
>>> */
>>>
>>> #include <errno.h>
>>> #include <pwd.h>
>>> #include <stdlib.h>
>>> #include <stdio.h>
>>> #include <string.h>
>>> #include <syslog.h>
>>> #include <sys/prctl.h>
>>> #include <sys/types.h>
>>>
>>> #include <security/pam_modules.h>
>>>
>>> #ifndef PR_CAPBSET_DROP
>>> #define PR_CAPBSET_DROP 24
>>> #endif
>>>
>>> static char *captable[] = {
>>> "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",
>>> NULL,
>>> };
>>>
>>>
>>> PAM_EXTERN int
>>> pam_sm_open_session(pam_handle_t *pamh, int flags,
>>> int argc, const char **argv)
>>> {
>>> struct passwd *pwd;
>>> char *pos, *buf;
>>> char *username = NULL;
>>>
>>> /* open system logger */
>>> openlog("pam_cap_bset", LOG_PERROR | LOG_PID, LOG_AUTHPRIV);
>>>
>>> /* get the unix username */
>>> if (pam_get_item(pamh, PAM_USER, (void *) &username) != PAM_SUCCESS || !username)
>>> return PAM_USER_UNKNOWN;
>>>
>>> /* get the passwd entry */
>>> pwd = getpwnam(username);
>>> if (!pwd)
>>> return PAM_USER_UNKNOWN;
>>>
>>> /* Is there "cap_drop=" ? */
>>> pos = strstr(pwd->pw_gecos, "cap_drop=");
>>> if (pos) {
>>> buf = strdup(pos + sizeof("cap_drop=") - 1);
>>> if (!buf)
>>> return PAM_SESSION_ERR;
>>> pos = strtok(buf, ",");
>>> while (pos) {
>>> int rc, i;
>>>
>>> for (i=0; captable[i]; i++) {
>>> if (!strcmp(pos, captable[i])) {
>>> rc = prctl(PR_CAPBSET_DROP, i);
>>> if (rc < 0) {
>>> syslog(LOG_NOTICE, "user %s could not drop %s (%s)",
>>> username, captable[i], strerror(errno));
>>> break;
>>> }
>>> syslog(LOG_NOTICE, "user %s drops %s\n", username, captable[i]);
>>> goto next;
>>> }
>>> }
>>> break;
>>> next:
>>> pos = strtok(NULL, ",");
>>> }
>>> free(buf);
>>> } else {
>>> syslog(LOG_NOTICE, "user %s does not have 'cap_drop=' property", username);
>>> }
>>> return PAM_SUCCESS;
>>> }
>>>
>>> PAM_EXTERN int
>>> pam_sm_close_session(pam_handle_t *pamh, int flags,
>>> int argc, const char **argv)
>>> {
>>> /* do nothing */
>>> return PAM_SUCCESS;
>>> }
>>>
>>> ************************************************************
>>> -
>>> To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
>>> the body of a message to [email protected]
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.7 (Darwin)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>
> iD8DBQFHUbHDmwytjiwfWMwRAj5uAJ9+OB8ljQlJAhKW7jxJWrIPa1k2vgCdHFL9
> 3zXtwGz+cVeThb53/kAmdCs=
> =OdM7
> -----END PGP SIGNATURE-----
> -
> To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>


--
KaiGai Kohei <[email protected]>

2007-12-02 18:15:42

by Andrew G. Morgan

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

KaiGai Kohei wrote:
>> There is already a pam_cap module in the libcap2 package. Can we merge
>> this functionality?
>
> I think it is a good idea.
>
> However, this module already have a feature to modify inheritable
> capability set.
> How does it to be described in the "/etc/security/capability.conf"?
>
> One idea is like a following convention:
>
> # compatible configuration. We can omit "i:" at the head of line
> cap_setfcap tak
> # It drops any capabilities from b-set except for cap_net_raw and
> cap_fowner
> b:cap_net_raw,cap_fowner ymj
> # It drops only cap_dac_override from b-set.
> b:-cap_dac_override kaigai
> # It drops only cap_sys_admin from b-set of any user within users group.
> b:-cap_sys_admin group:users

I like the idea of a separate line for bounds.

For ease of parsing, perhaps '!' or some other symbol prefix to the line
could be used to identify lines that refer to cap_bound?

In other modules, @groupname is used to capture a group association.

Lines like this should be supported:

!cap_net_raw @regularusers # suppress from cap_bset
cap_net_raw @pingers morgan # add to pI

where morgan is not in group @pingers but is in group @regularusers.

Cheers

Andrew

>
> Thanks,
>
>> Cheers
>>
>> Andrew
>>
>> [email protected] wrote:
>>> Quoting KaiGai Kohei ([email protected]):
>>>> Serge E. Hallyn wrote:
>>>>> 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.
>>>> Serge,
>>>>
>>>> This feature makes me being interested in.
>>>> I think you intend to apply this feature for the primary process
>>>> of security container.
>>>> However, it is also worthwhile to apply when a session is starting up.
>>>>
>>>> The following PAM module enables to drop capability bounding bit
>>>> specified by the fifth field in /etc/passwd entry.
>>>> This code is just an example now, but considerable feature.
>>>>
>>>> build and install:
>>>> # gcc -Wall -c pam_cap_drop.c
>>>> # gcc -Wall -shared -Xlinker -x -o pam_cap_drop.so pam_cap_drop.o -lpam
>>>> # cp pam_cap_drop.so /lib/security
>>>>
>>>> modify /etc/passwd as follows:
>>>>
>>>> tak:x:1004:100:cap_drop=cap_net_raw,cap_chown:/home/tak:/bin/bash
>>>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>> example:
>>>> [kaigai@masu ~]$ ping 192.168.1.1
>>>> PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
>>>> 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.23 ms
>>>> 64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.02 ms
>>>>
>>>> --- 192.168.1.1 ping statistics ---
>>>> 2 packets transmitted, 2 received, 0% packet loss, time 999ms
>>>> rtt min/avg/max/mdev = 1.023/1.130/1.237/0.107 ms
>>>>
>>>> [kaigai@masu ~]$ ssh tak@localhost
>>>> tak@localhost's password:
>>>> Last login: Sat Dec 1 10:09:29 2007 from masu.myhome.cx
>>>> [tak@masu ~]$ export LANG=C
>>>> [tak@masu ~]$ ping 192.168.1.1
>>>> ping: icmp open socket: Operation not permitted
>>>>
>>>> [tak@masu ~]$ su
>>>> Password:
>>>> pam_cap_bset[6921]: user root does not have 'cap_drop=' property
>>>> [root@masu tak]# cat /proc/self/status | grep ^Cap
>>>> CapInh: 0000000000000000
>>>> CapPrm: 00000000ffffdffe
>>>> CapEff: 00000000ffffdffe
>>>> [root@masu tak]#
>>> Neat. A bigger-stick version of not adding the account to
>>> group wheel. I'll use that.
>>>
>>> Is there any reason not to have a separate /etc/login.capbounds
>>> config file, though, so the account can still have a full name?
>>> Did you only use that for convenience of proof of concept, or
>>> is there another reason?
>>>
>>>> # BTW, I replaced the James's address in the Cc: list,
>>>> # because MTA does not accept it.
>>> Thanks! I don't know what happened to my alias for him...
>>>
>>> thanks,
>>> -serge
>>>
>>>> --
>>>> KaiGai Kohei <[email protected]>
>>>>
>>>> ************************************************************
>>>> pam_cap_drop.c
>>>> ************************************************************
>>>>
>>>> /*
>>>> * pam_cap_drop.c module -- drop capabilities bounding set
>>>> *
>>>> * Copyright: 2007 KaiGai Kohei <[email protected]>
>>>> */
>>>>
>>>> #include <errno.h>
>>>> #include <pwd.h>
>>>> #include <stdlib.h>
>>>> #include <stdio.h>
>>>> #include <string.h>
>>>> #include <syslog.h>
>>>> #include <sys/prctl.h>
>>>> #include <sys/types.h>
>>>>
>>>> #include <security/pam_modules.h>
>>>>
>>>> #ifndef PR_CAPBSET_DROP
>>>> #define PR_CAPBSET_DROP 24
>>>> #endif
>>>>
>>>> static char *captable[] = {
>>>> "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",
>>>> NULL,
>>>> };
>>>>
>>>>
>>>> PAM_EXTERN int
>>>> pam_sm_open_session(pam_handle_t *pamh, int flags,
>>>> int argc, const char **argv)
>>>> {
>>>> struct passwd *pwd;
>>>> char *pos, *buf;
>>>> char *username = NULL;
>>>>
>>>> /* open system logger */
>>>> openlog("pam_cap_bset", LOG_PERROR | LOG_PID, LOG_AUTHPRIV);
>>>>
>>>> /* get the unix username */
>>>> if (pam_get_item(pamh, PAM_USER, (void *) &username) !=
>>>> PAM_SUCCESS || !username)
>>>> return PAM_USER_UNKNOWN;
>>>>
>>>> /* get the passwd entry */
>>>> pwd = getpwnam(username);
>>>> if (!pwd)
>>>> return PAM_USER_UNKNOWN;
>>>>
>>>> /* Is there "cap_drop=" ? */
>>>> pos = strstr(pwd->pw_gecos, "cap_drop=");
>>>> if (pos) {
>>>> buf = strdup(pos + sizeof("cap_drop=") - 1);
>>>> if (!buf)
>>>> return PAM_SESSION_ERR;
>>>> pos = strtok(buf, ",");
>>>> while (pos) {
>>>> int rc, i;
>>>>
>>>> for (i=0; captable[i]; i++) {
>>>> if (!strcmp(pos, captable[i])) {
>>>> rc = prctl(PR_CAPBSET_DROP, i);
>>>> if (rc < 0) {
>>>> syslog(LOG_NOTICE, "user %s could not drop
>>>> %s (%s)",
>>>> username, captable[i], strerror(errno));
>>>> break;
>>>> }
>>>> syslog(LOG_NOTICE, "user %s drops %s\n",
>>>> username, captable[i]);
>>>> goto next;
>>>> }
>>>> }
>>>> break;
>>>> next:
>>>> pos = strtok(NULL, ",");
>>>> }
>>>> free(buf);
>>>> } else {
>>>> syslog(LOG_NOTICE, "user %s does not have 'cap_drop='
>>>> property", username);
>>>> }
>>>> return PAM_SUCCESS;
>>>> }
>>>>
>>>> PAM_EXTERN int
>>>> pam_sm_close_session(pam_handle_t *pamh, int flags,
>>>> int argc, const char **argv)
>>>> {
>>>> /* do nothing */
>>>> return PAM_SUCCESS;
>>>> }
>>>>
>>>> ************************************************************
>>>> -
>>>> To unsubscribe from this list: send the line "unsubscribe
>>>> linux-security-module" in
>>>> the body of a message to [email protected]
>>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>> -----BEGIN PGP SIGNATURE-----
>> Version: GnuPG v1.4.7 (Darwin)
>> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>>
>> iD8DBQFHUbHDmwytjiwfWMwRAj5uAJ9+OB8ljQlJAhKW7jxJWrIPa1k2vgCdHFL9
>> 3zXtwGz+cVeThb53/kAmdCs=
>> =OdM7
>> -----END PGP SIGNATURE-----
>> -
>> To unsubscribe from this list: send the line "unsubscribe
>> linux-security-module" in
>> the body of a message to [email protected]
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
>

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHUvY5+bHCR3gb8jsRAvKaAJ9Upbi+vcSoiQnf64qtubbNow4wmgCdGMBb
UneHzcT8FDexDcFhN5c6OK4=
=0XeM
-----END PGP SIGNATURE-----

2007-12-03 06:24:14

by Kohei KaiGai

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Andrew Morgan wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> KaiGai Kohei wrote:
>>> There is already a pam_cap module in the libcap2 package. Can we merge
>>> this functionality?
>> I think it is a good idea.
>>
>> However, this module already have a feature to modify inheritable
>> capability set.
>> How does it to be described in the "/etc/security/capability.conf"?
>>
>> One idea is like a following convention:
>>
>> # compatible configuration. We can omit "i:" at the head of line
>> cap_setfcap tak
>> # It drops any capabilities from b-set except for cap_net_raw and
>> cap_fowner
>> b:cap_net_raw,cap_fowner ymj
>> # It drops only cap_dac_override from b-set.
>> b:-cap_dac_override kaigai
>> # It drops only cap_sys_admin from b-set of any user within users group.
>> b:-cap_sys_admin group:users
>
> I like the idea of a separate line for bounds.
>
> For ease of parsing, perhaps '!' or some other symbol prefix to the line
> could be used to identify lines that refer to cap_bound?
>
> In other modules, @groupname is used to capture a group association.
>
> Lines like this should be supported:
>
> !cap_net_raw @regularusers # suppress from cap_bset
> cap_net_raw @pingers morgan # add to pI
>
> where morgan is not in group @pingers but is in group @regularusers.

The "@groupname" is intuitive convention. I also think it is good idea.

But !cap_xxx is a bit misunderstandable for me. Someone may misunderstand
this line means any capabilities except for cap_xxx.
Thus, I think that using "b:" and omittable "i:" prefix is better than "!".
In addition, what is your opinion about using "-b:" and "-i:" to represent
dropping capabilities currently they have?

There is one more uncertain case.
When a user belongs to several groups with capabilities configuration,
what capabilities are to be attached for the user?

e.g) When kaigai belong to @pingers and @paccters

b:cap_sys_pacct @paccters
b:cap_net_raw @pingers
-b:cap_dac_override,cap_net_raw kaigai

If we apply "OR" policy, kaigai get only cap_sys_pacct, because
he got cap_sys_pacct and cap_net_raw came from @paccters and @pingers
but cap_dac_override and cap_net_raw are dropped by the third line.

Thanks,

> Cheers
>
> Andrew
>
>> Thanks,
>>
>>> Cheers
>>>
>>> Andrew
>>>
>>> [email protected] wrote:
>>>> Quoting KaiGai Kohei ([email protected]):
>>>>> Serge E. Hallyn wrote:
>>>>>> 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.
>>>>> Serge,
>>>>>
>>>>> This feature makes me being interested in.
>>>>> I think you intend to apply this feature for the primary process
>>>>> of security container.
>>>>> However, it is also worthwhile to apply when a session is starting up.
>>>>>
>>>>> The following PAM module enables to drop capability bounding bit
>>>>> specified by the fifth field in /etc/passwd entry.
>>>>> This code is just an example now, but considerable feature.
>>>>>
>>>>> build and install:
>>>>> # gcc -Wall -c pam_cap_drop.c
>>>>> # gcc -Wall -shared -Xlinker -x -o pam_cap_drop.so pam_cap_drop.o -lpam
>>>>> # cp pam_cap_drop.so /lib/security
>>>>>
>>>>> modify /etc/passwd as follows:
>>>>>
>>>>> tak:x:1004:100:cap_drop=cap_net_raw,cap_chown:/home/tak:/bin/bash
>>>>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>>> example:
>>>>> [kaigai@masu ~]$ ping 192.168.1.1
>>>>> PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
>>>>> 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.23 ms
>>>>> 64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.02 ms
>>>>>
>>>>> --- 192.168.1.1 ping statistics ---
>>>>> 2 packets transmitted, 2 received, 0% packet loss, time 999ms
>>>>> rtt min/avg/max/mdev = 1.023/1.130/1.237/0.107 ms
>>>>>
>>>>> [kaigai@masu ~]$ ssh tak@localhost
>>>>> tak@localhost's password:
>>>>> Last login: Sat Dec 1 10:09:29 2007 from masu.myhome.cx
>>>>> [tak@masu ~]$ export LANG=C
>>>>> [tak@masu ~]$ ping 192.168.1.1
>>>>> ping: icmp open socket: Operation not permitted
>>>>>
>>>>> [tak@masu ~]$ su
>>>>> Password:
>>>>> pam_cap_bset[6921]: user root does not have 'cap_drop=' property
>>>>> [root@masu tak]# cat /proc/self/status | grep ^Cap
>>>>> CapInh: 0000000000000000
>>>>> CapPrm: 00000000ffffdffe
>>>>> CapEff: 00000000ffffdffe
>>>>> [root@masu tak]#
>>>> Neat. A bigger-stick version of not adding the account to
>>>> group wheel. I'll use that.
>>>>
>>>> Is there any reason not to have a separate /etc/login.capbounds
>>>> config file, though, so the account can still have a full name?
>>>> Did you only use that for convenience of proof of concept, or
>>>> is there another reason?
>>>>
>>>>> # BTW, I replaced the James's address in the Cc: list,
>>>>> # because MTA does not accept it.
>>>> Thanks! I don't know what happened to my alias for him...
>>>>
>>>> thanks,
>>>> -serge
>>>>
>>>>> --
>>>>> KaiGai Kohei <[email protected]>
>>>>>
>>>>> ************************************************************
>>>>> pam_cap_drop.c
>>>>> ************************************************************
>>>>>
>>>>> /*
>>>>> * pam_cap_drop.c module -- drop capabilities bounding set
>>>>> *
>>>>> * Copyright: 2007 KaiGai Kohei <[email protected]>
>>>>> */
>>>>>
>>>>> #include <errno.h>
>>>>> #include <pwd.h>
>>>>> #include <stdlib.h>
>>>>> #include <stdio.h>
>>>>> #include <string.h>
>>>>> #include <syslog.h>
>>>>> #include <sys/prctl.h>
>>>>> #include <sys/types.h>
>>>>>
>>>>> #include <security/pam_modules.h>
>>>>>
>>>>> #ifndef PR_CAPBSET_DROP
>>>>> #define PR_CAPBSET_DROP 24
>>>>> #endif
>>>>>
>>>>> static char *captable[] = {
>>>>> "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",
>>>>> NULL,
>>>>> };
>>>>>
>>>>>
>>>>> PAM_EXTERN int
>>>>> pam_sm_open_session(pam_handle_t *pamh, int flags,
>>>>> int argc, const char **argv)
>>>>> {
>>>>> struct passwd *pwd;
>>>>> char *pos, *buf;
>>>>> char *username = NULL;
>>>>>
>>>>> /* open system logger */
>>>>> openlog("pam_cap_bset", LOG_PERROR | LOG_PID, LOG_AUTHPRIV);
>>>>>
>>>>> /* get the unix username */
>>>>> if (pam_get_item(pamh, PAM_USER, (void *) &username) !=
>>>>> PAM_SUCCESS || !username)
>>>>> return PAM_USER_UNKNOWN;
>>>>>
>>>>> /* get the passwd entry */
>>>>> pwd = getpwnam(username);
>>>>> if (!pwd)
>>>>> return PAM_USER_UNKNOWN;
>>>>>
>>>>> /* Is there "cap_drop=" ? */
>>>>> pos = strstr(pwd->pw_gecos, "cap_drop=");
>>>>> if (pos) {
>>>>> buf = strdup(pos + sizeof("cap_drop=") - 1);
>>>>> if (!buf)
>>>>> return PAM_SESSION_ERR;
>>>>> pos = strtok(buf, ",");
>>>>> while (pos) {
>>>>> int rc, i;
>>>>>
>>>>> for (i=0; captable[i]; i++) {
>>>>> if (!strcmp(pos, captable[i])) {
>>>>> rc = prctl(PR_CAPBSET_DROP, i);
>>>>> if (rc < 0) {
>>>>> syslog(LOG_NOTICE, "user %s could not drop
>>>>> %s (%s)",
>>>>> username, captable[i], strerror(errno));
>>>>> break;
>>>>> }
>>>>> syslog(LOG_NOTICE, "user %s drops %s\n",
>>>>> username, captable[i]);
>>>>> goto next;
>>>>> }
>>>>> }
>>>>> break;
>>>>> next:
>>>>> pos = strtok(NULL, ",");
>>>>> }
>>>>> free(buf);
>>>>> } else {
>>>>> syslog(LOG_NOTICE, "user %s does not have 'cap_drop='
>>>>> property", username);
>>>>> }
>>>>> return PAM_SUCCESS;
>>>>> }
>>>>>
>>>>> PAM_EXTERN int
>>>>> pam_sm_close_session(pam_handle_t *pamh, int flags,
>>>>> int argc, const char **argv)
>>>>> {
>>>>> /* do nothing */
>>>>> return PAM_SUCCESS;
>>>>> }
>>>>>
>>>>> ************************************************************
>>>>> -
>>>>> To unsubscribe from this list: send the line "unsubscribe
>>>>> linux-security-module" in
>>>>> the body of a message to [email protected]
>>>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>> -----BEGIN PGP SIGNATURE-----
>>> Version: GnuPG v1.4.7 (Darwin)
>>> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>>>
>>> iD8DBQFHUbHDmwytjiwfWMwRAj5uAJ9+OB8ljQlJAhKW7jxJWrIPa1k2vgCdHFL9
>>> 3zXtwGz+cVeThb53/kAmdCs=
>>> =OdM7
>>> -----END PGP SIGNATURE-----
>>> -
>>> To unsubscribe from this list: send the line "unsubscribe
>>> linux-security-module" in
>>> the body of a message to [email protected]
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>>
>>
>
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.7 (Darwin)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>
> iD8DBQFHUvY5+bHCR3gb8jsRAvKaAJ9Upbi+vcSoiQnf64qtubbNow4wmgCdGMBb
> UneHzcT8FDexDcFhN5c6OK4=
> =0XeM
> -----END PGP SIGNATURE-----
> -
> To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
>


--
OSS Platform Development Division, NEC
KaiGai Kohei <[email protected]>

2007-12-04 04:29:18

by Kohei KaiGai

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Serge,

Please tell me the meanings of the following condition.

> diff --git a/security/commoncap.c b/security/commoncap.c
> index 3a95990..cb71bb0 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -133,6 +119,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,

It seems to me this condition requires the new inheritable capability
set must have a capability more than bounding set, at least.
What is the purpose of this checking?

In the initial state, any process have no inheritable capability set
and full bounding set. Thus, we cannot do capset() always.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <[email protected]>

2007-12-04 06:15:01

by Andrew G. Morgan

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

KaiGai Kohei wrote:
> Serge,
>
> Please tell me the meanings of the following condition.
>
>> diff --git a/security/commoncap.c b/security/commoncap.c
>> index 3a95990..cb71bb0 100644
>> --- a/security/commoncap.c
>> +++ b/security/commoncap.c
>> @@ -133,6 +119,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,
>
> It seems to me this condition requires the new inheritable capability
> set must have a capability more than bounding set, at least.
> What is the purpose of this checking?

Yes, the !! was a bug. The correct check is a single !.

(Thus, the correct check says no 'new' pI bits can be outside cap_bset.)

Cheers

Andrew

>
> In the initial state, any process have no inheritable capability set
> and full bounding set. Thus, we cannot do capset() always.
>
> Thanks,

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHVPBS+bHCR3gb8jsRAnxQAJ0Vna82bl9M11OL/uuEe21nF5+9TACfSzGi
aY0SUvMmLZCIF0KovBTpihE=
=wT9N
-----END PGP SIGNATURE-----

2007-12-04 15:47:29

by Kohei KaiGai

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Andrew Morgan wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> KaiGai Kohei wrote:
>> Serge,
>>
>> Please tell me the meanings of the following condition.
>>
>>> diff --git a/security/commoncap.c b/security/commoncap.c
>>> index 3a95990..cb71bb0 100644
>>> --- a/security/commoncap.c
>>> +++ b/security/commoncap.c
>>> @@ -133,6 +119,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,
>> It seems to me this condition requires the new inheritable capability
>> set must have a capability more than bounding set, at least.
>> What is the purpose of this checking?
>
> Yes, the !! was a bug. The correct check is a single !.

I was in trouble with getting -EPERM at pam_cap.so :-)

> (Thus, the correct check says no 'new' pI bits can be outside cap_bset.)

If this condition intends to dominate 'new' pI bits by 'old' pI bits masked
with bounding set, we should not apply cap_combine() here.
I think applying cap_intersect() is correct for the purpose.

Thanks,
--
KaiGai Kohei

2007-12-04 20:18:21

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Quoting KaiGai Kohei ([email protected]):
> Andrew Morgan wrote:
>> -----BEGIN PGP SIGNED MESSAGE-----
>> Hash: SHA1
>> KaiGai Kohei wrote:
>>> Serge,
>>>
>>> Please tell me the meanings of the following condition.
>>>
>>>> diff --git a/security/commoncap.c b/security/commoncap.c
>>>> index 3a95990..cb71bb0 100644
>>>> --- a/security/commoncap.c
>>>> +++ b/security/commoncap.c
>>>> @@ -133,6 +119,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,
>>> It seems to me this condition requires the new inheritable capability
>>> set must have a capability more than bounding set, at least.
>>> What is the purpose of this checking?
>> Yes, the !! was a bug. The correct check is a single !.
>
> I was in trouble with getting -EPERM at pam_cap.so :-)
>
>> (Thus, the correct check says no 'new' pI bits can be outside cap_bset.)
>
> If this condition intends to dominate 'new' pI bits by 'old' pI bits masked
> with bounding set, we should not apply cap_combine() here.
> I think applying cap_intersect() is correct for the purpose.

That would have been my first inclination, but Andrew actually
wanted to be able to keep a pI with bits not in the capability
bounding set. And it's really not a big problem, since

1. you can never grow cap_bset
2. the capbound.c program just makes sure to call capset
to take the bit being removed from cap_bset out of
pI'
3. It could be advantageous for some daemon to keep a bit
in pI which can never be gained through fP but can be
gained by a child through (fI&pI).

Does that seem reasonable to you?

(On the one hand man 7 capabilities shows cap_bset affecting
fP and not pP', on the other hand we're definately getting into
the territory where we'll have to rewrite the manpages anyway)

thanks,
-serge

2007-12-05 15:31:36

by Andrew G. Morgan

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

KaiGai Kohei wrote:
> Andrew Morgan wrote:
>> -----BEGIN PGP SIGNED MESSAGE-----
>> Hash: SHA1
>>
>> KaiGai Kohei wrote:
>>>> + if (!!cap_issubset(*inheritable,
>>>> + cap_combine(target->cap_inheritable,
>>>> + current->cap_bset))) {
>>>> + /* no new pI capabilities outside bounding set */
>>>> + return -EPERM;
>>>> + }
>>>>

>> Yes, the !! was a bug. The correct check is a single !.
>
> I was in trouble with getting -EPERM at pam_cap.so :-)
>
>> (Thus, the correct check says no 'new' pI bits can be outside cap_bset.)
>
> If this condition intends to dominate 'new' pI bits by 'old' pI bits masked
> with bounding set, we should not apply cap_combine() here.
> I think applying cap_intersect() is correct for the purpose.

The check is not meant to limit existing pI bits.

The check is meant to limit what new bits can be 'added' to pI (in the
case that pE & CAP_SETPCAP is true).

Cheers

Andrew
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHVsQ2mwytjiwfWMwRAs9RAKCUyjsjONVhRXooG5I2b+1zU/HLGwCfQIyh
tjdDI9QxJ1DWLCm2Ee29qYA=
=Gwwt
-----END PGP SIGNATURE-----

2007-12-06 02:02:17

by Kohei KaiGai

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

>>> (Thus, the correct check says no 'new' pI bits can be outside cap_bset.)
>> If this condition intends to dominate 'new' pI bits by 'old' pI bits masked
>> with bounding set, we should not apply cap_combine() here.
>> I think applying cap_intersect() is correct for the purpose.
>
> That would have been my first inclination, but Andrew actually
> wanted to be able to keep a pI with bits not in the capability
> bounding set. And it's really not a big problem, since
>
> 1. you can never grow cap_bset
> 2. the capbound.c program just makes sure to call capset
> to take the bit being removed from cap_bset out of
> pI'
> 3. It could be advantageous for some daemon to keep a bit
> in pI which can never be gained through fP but can be
> gained by a child through (fI&pI).
>
> Does that seem reasonable to you?

OK, I got understood the intention of the condition.
It seems to me reasonable policy.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <[email protected]>


This patch fixes incorrect condition added by per-process capability
bounding set patch.
It intends to limit no new pI capabilities outside bounding set.

Signed-off-by: KaiGai Kohei <[email protected]>

commoncap.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

--- linux-2.6.24-rc3/security/commoncap.c.old 2007-12-06 10:51:48.000000000 +0900
+++ linux-2.6.24-rc3/security/commoncap.c 2007-12-06 10:52:15.000000000 +0900
@@ -119,9 +119,9 @@ int cap_capset_check (struct task_struct
/* incapable of using this inheritable set */
return -EPERM;
}
- if (!!cap_issubset(*inheritable,
- cap_combine(target->cap_inheritable,
- current->cap_bset))) {
+ if (!cap_issubset(*inheritable,
+ cap_combine(target->cap_inheritable,
+ current->cap_bset))) {
/* no new pI capabilities outside bounding set */
return -EPERM;
}

2007-12-06 02:15:44

by Kohei KaiGai

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Andrew Morgan wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> KaiGai Kohei wrote:
>> Andrew Morgan wrote:
>>> -----BEGIN PGP SIGNED MESSAGE-----
>>> Hash: SHA1
>>>
>>> KaiGai Kohei wrote:
>>>>> + if (!!cap_issubset(*inheritable,
>>>>> + cap_combine(target->cap_inheritable,
>>>>> + current->cap_bset))) {
>>>>> + /* no new pI capabilities outside bounding set */
>>>>> + return -EPERM;
>>>>> + }
>>>>>
>
>>> Yes, the !! was a bug. The correct check is a single !.
>> I was in trouble with getting -EPERM at pam_cap.so :-)
>>
>>> (Thus, the correct check says no 'new' pI bits can be outside cap_bset.)
>> If this condition intends to dominate 'new' pI bits by 'old' pI bits masked
>> with bounding set, we should not apply cap_combine() here.
>> I think applying cap_intersect() is correct for the purpose.
>
> The check is not meant to limit existing pI bits.
>
> The check is meant to limit what new bits can be 'added' to pI (in the
> case that pE & CAP_SETPCAP is true).

Thanks, I got understood as I wrote in the previous reply.

BTW, could you tell me your intention about pam_cap.c is implemented
with pam_sm_authenticate() and pam_sm_setcred()?
I think it can be done with pam_sm_open_session(), and this approach
enables to reduce the iteration of reading /etc/security/capability.conf.

How do you think the idea?
--
OSS Platform Development Division, NEC
KaiGai Kohei <[email protected]>

2007-12-06 05:39:25

by Andrew G. Morgan

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

KaiGai Kohei wrote:
> BTW, could you tell me your intention about pam_cap.c is implemented
> with pam_sm_authenticate() and pam_sm_setcred()?
> I think it can be done with pam_sm_open_session(), and this approach
> enables to reduce the iteration of reading /etc/security/capability.conf.
>
> How do you think the idea?

Good question! If you want to add session support you can. I'd prefer it
if you retained support for the auth/cred API too: admin choice and all
that. To remove the second read of the file, you can use a PAM data item
to cache the desired capability info after the first read of the file.

I implemented it as a credential module (which has to get the
authentication return code right to make the credential stack execute
correctly) because I think of capabilities as credentials.

That being said, the credentials vs. session thing is not well
delineated by many applications, so it is arguably useful to provide
both interfaces for the admin to make use of on a per application basis.

Cheers

Andrew
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHV4r8mwytjiwfWMwRAlOsAJ9MQQN0cLhH2lhx9gwvwHsMhQ72ggCfcKWt
/krnNdiAisfcbcXDfssdbLE=
=+0r1
-----END PGP SIGNATURE-----

2007-12-06 08:38:12

by Kohei KaiGai

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Andrew Morgan wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> KaiGai Kohei wrote:
>> BTW, could you tell me your intention about pam_cap.c is implemented
>> with pam_sm_authenticate() and pam_sm_setcred()?
>> I think it can be done with pam_sm_open_session(), and this approach
>> enables to reduce the iteration of reading /etc/security/capability.conf.
>>
>> How do you think the idea?
>
> Good question! If you want to add session support you can. I'd prefer it
> if you retained support for the auth/cred API too: admin choice and all
> that. To remove the second read of the file, you can use a PAM data item
> to cache the desired capability info after the first read of the file.

I added session API support with retaining auth/cred API, and
PAM data item caching feature using pam_(get|set)_data. It enables to
kill the seconf read of the configuration file.

Thanks for your suggestion.
Followings are detailed explanation and the patch.

> I implemented it as a credential module (which has to get the
> authentication return code right to make the credential stack execute
> correctly) because I think of capabilities as credentials.
>
> That being said, the credentials vs. session thing is not well
> delineated by many applications, so it is arguably useful to provide
> both interfaces for the admin to make use of on a per application basis.

The attached patch provides several improvement for pam_cap module.
1. It enables pam_cap to drop capabilities from process'es capability
bounding set.
2. It enables to specify allowing inheritable capability set or dropping
bounding capability set for groups, not only users.
3. It provide pam_sm_session() method, not only pam_sm_authenticate()
and pam_sm_setcred(). A system administrator can select more
appropriate mode for his purpose.
4. In the auth/cred mode, it enables to cache the configuration file,
to avoid read and analyze it twice.
(Therefore, most of the part in the original one got replaced....)

The default configuration file is "/etc/security/capability.conf".
You can describe as follows:
--------
# kaigai get cap_net_raw and cap_kill, tak get cap_sys_pacct pI.
# We can omit "i:" in the head of each line.
i:cap_net_raw,cap_kill kaigai
cap_sys_pacct tak

# ymj and tak lost cap_sys_chroot from cap_bset
b:cap_sys_chroot ymj tak

# Any user within webadm group get cap_net_bind_service pI.
i:cap_net_bind_service @webadm

# Any user within users group lost cap_sys_module from cap_bset
b:cap_sys_module @users
--------

When a user or groups he belongs is on several lines, all configurations
are simplly compounded.

In the above example, if tak belongs to webadm and users group,
he will get cap_sys_pacct and cap_net_bind_service pI, and lost
cap_sys_chroot and cap_sys_module from his cap_bset.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <[email protected]>


Signed-off-by: KaiGai Kohei <[email protected]>
--
pam_cap/capability.conf | 6 +
pam_cap/pam_cap.c | 495 ++++++++++++++++++++++++++++-------------------
2 files changed, 305 insertions(+), 196 deletions(-)

diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index b543142..707cdc3 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -24,6 +24,12 @@ cap_setfcap morgan
## 'everyone else' gets no inheritable capabilities
none *

+# user 'kaigai' lost CAP_NET_RAW capability from bounding set
+b:cap_net_raw kaigai
+
+# group 'acctadm' get CAP_SYS_PACCT inheritable capability
+i:cap_sys_pacct @acctadm
+
## if there is no '*' entry, all users not explicitly mentioned will
## get all available capabilities. This is a permissive default, and
## probably not what you want...
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 94c5ebc..a917d5c 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -1,5 +1,6 @@
/*
* Copyright (c) 1999,2007 Andrew G. Morgan <[email protected]>
+ * Copyright (c) 2007 KaiGai Kohei <[email protected]>
*
* The purpose of this module is to enforce inheritable capability sets
* for a specified user.
@@ -13,298 +14,400 @@
#include <stdarg.h>
#include <stdlib.h>
#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>

#include <sys/capability.h>
+#include <sys/prctl.h>

#include <security/pam_modules.h>
#include <security/_pam_macros.h>

+#define MODULE_NAME "pam_cap"
#define USER_CAP_FILE "/etc/security/capability.conf"
#define CAP_FILE_BUFFER_SIZE 4096
#define CAP_FILE_DELIMITERS " \t\n"
-#define CAP_COMBINED_FORMAT "%s all-i %s+i"
-#define CAP_DROP_ALL "%s all-i"
+
+#ifndef PR_CAPBSET_DROP
+#define PR_CAPBSET_DROP 24
+#endif
+
+extern char const *_cap_names[];

struct pam_cap_s {
int debug;
const char *user;
const char *conf_filename;
+ /* set in read_capabilities_for_user() */
+ cap_t result;
+ int do_set_inh : 1;
+ int do_set_bset : 1;
};

-/* obtain the inheritable capabilities for the current user */
-
-static char *read_capabilities_for_user(const char *user, const char *source)
+/* obtain the inheritable/bounding capabilities for the current user */
+static int read_capabilities_for_user(struct pam_cap_s *pcs)
{
- char *cap_string = NULL;
- char buffer[CAP_FILE_BUFFER_SIZE], *line;
+ char buffer[CAP_FILE_BUFFER_SIZE];
FILE *cap_file;
+ struct passwd *pwd;
+ int line_num = 0;
+ int rc = -1; /* PAM_(AUTH|CRED|SESSION)_ERR */
+
+ pwd = getpwnam(pcs->user);
+ if (!pwd) {
+ syslog(LOG_ERR, "user %s not in passwd entries", pcs->user);
+ return PAM_AUTH_ERR;
+ }

- cap_file = fopen(source, "r");
- if (cap_file == NULL) {
- D(("failed to open capability file"));
- return NULL;
+ cap_file = fopen(pcs->conf_filename, "r");
+ if (!cap_file) {
+ if (errno == ENOENT) {
+ syslog(LOG_NOTICE, "%s is not found",
+ pcs->conf_filename);
+ return PAM_IGNORE;
+ } else {
+ syslog(LOG_ERR, "unable to open '%s' (%s)",
+ pcs->conf_filename, strerror(errno));
+ return rc;
+ }
}

- while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
- int found_one = 0;
- const char *cap_text;
+ pcs->result = NULL;
+ while (fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file) != NULL) {
+ char *pos, *cap_text;
+ int matched = 0;
+ int line_ops = CAP_INHERITABLE;

- cap_text = strtok(line, CAP_FILE_DELIMITERS);
+ line_num++;

- if (cap_text == NULL) {
- D(("empty line"));
- continue;
- }
- if (*cap_text == '#') {
- D(("comment line"));
+ /* remove comment */
+ pos = strchr(buffer, '#');
+ if (pos)
+ *pos = '\0';
+
+ cap_text = strtok(buffer, CAP_FILE_DELIMITERS);
+ /* empty line */
+ if (!cap_text)
continue;
+
+ if (!strncmp(cap_text, "b:", 2)) {
+ /* permitted field is used to store bounding set */
+ line_ops = CAP_PERMITTED;
+ cap_text += 2;
+ } else if (!strncmp(cap_text, "i:", 2)) {
+ cap_text += 2;
}

- while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
+ /* check members */
+ while ((pos = strtok(NULL, CAP_FILE_DELIMITERS)) != NULL) {
+ /* wildcard */
+ if (!strcmp("*", pos)) {
+ matched = 1;
+ break;
+ }

- if (strcmp("*", line) == 0) {
- D(("wildcard matched"));
- found_one = 1;
- cap_string = strdup(cap_text);
+ /* It it group name? */
+ if (*pos == '@') {
+ struct group *grp;
+ int i;
+
+ pos++;
+ grp = getgrnam(pos);
+ if (!grp) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "group '%s' not found at line:%d",
+ pos, line_num);
+ continue;
+ }
+
+ if (pwd->pw_gid == grp->gr_gid) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
+ pcs->user, pos, line_num);
+ matched = 1;
+ break;
+ }
+
+ for (i=0; grp->gr_mem[i]; i++) {
+ if (!strcmp(pcs->user, grp->gr_mem[i])) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
+ pcs->user, pos, line_num);
+ matched = 1;
+ break;
+ }
+ }
+ syslog(LOG_ERR, "no matching %s", pos);
+ } else if (!strcmp(pcs->user, pos)) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "user '%s' matched at line:%d",
+ pos, line_num);
+ matched = 1;
break;
}
+ }
+
+ if (matched) {
+ char tmpbuf[CAP_FILE_BUFFER_SIZE];
+ cap_t tmp;
+ cap_value_t value;
+ cap_flag_value_t code;
+
+ if (!pcs->result) {
+ pcs->result = cap_init();
+ if (!pcs->result) {
+ syslog(LOG_ERR, "unable to allocate cap_t object (%s)",
+ strerror(errno));
+ goto out;
+ }
+ }

- if (strcmp(user, line) == 0) {
- D(("exact match for user"));
- found_one = 1;
- cap_string = strdup(cap_text);
+ switch (line_ops) {
+ case CAP_INHERITABLE:
+ pcs->do_set_inh = 1;
+ break;
+ case CAP_PERMITTED:
+ pcs->do_set_bset = 1;
break;
}

- D(("user is not [%s] - skipping", line));
+ if (!strcmp(cap_text, "none"))
+ continue;
+
+ snprintf(tmpbuf, sizeof(tmpbuf), "%s=p", cap_text);
+ tmp = cap_from_text(tmpbuf);
+ if (!tmp) {
+ syslog(LOG_ERR, "unable to convert '%s' (%s)",
+ tmpbuf, strerror(errno));
+ cap_free(pcs->result);
+ pcs->result = NULL;
+ goto out;
+ }
+
+ for (value=0; ;value++) {
+ if (cap_get_flag(tmp, value, CAP_PERMITTED, &code) < 0)
+ break; /* If value == __CAP_BITS, we get EINVAL */
+ if (code == CAP_SET)
+ cap_set_flag(pcs->result, line_ops, 1, &value, CAP_SET);
+ }
+ cap_free(tmp);
}
+ }

- cap_text = NULL;
- line = NULL;
+ if (pcs->debug) {
+ char *tmp = cap_to_text(pcs->result, NULL);

- if (found_one) {
- D(("user [%s] matched - caps are [%s]", user, cap_string));
- break;
- }
+ syslog(LOG_DEBUG, "configuration for user %s is %s",
+ pcs->user, tmp);
+ cap_free(tmp);
}
+ rc = PAM_SUCCESS;

+ out:
fclose(cap_file);

- memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
-
- return cap_string;
+ return rc;
}

/*
* Set capabilities for current process to match the current
* permitted+executable sets combined with the configured inheritable
- * set.
+ * and bounding set.
*/

-static int set_capabilities(struct pam_cap_s *cs)
+static int set_capabilities(struct pam_cap_s *pcs)
{
- cap_t cap_s;
- ssize_t length = 0;
- char *conf_icaps;
- char *proc_epcaps;
- char *combined_caps;
- int ok = 0;
-
- cap_s = cap_get_proc();
- if (cap_s == NULL) {
- D(("your kernel is capability challenged - upgrade: %s",
- strerror(errno)));
- return 0;
- }
-
- conf_icaps =
- read_capabilities_for_user(cs->user,
- cs->conf_filename
- ? cs->conf_filename:USER_CAP_FILE );
- if (conf_icaps == NULL) {
- D(("no capabilities found for user [%s]", cs->user));
- goto cleanup_cap_s;
- }
-
- proc_epcaps = cap_to_text(cap_s, &length);
- if (proc_epcaps == NULL) {
- D(("unable to convert process capabilities to text"));
- goto cleanup_icaps;
- }
-
- /*
- * This is a pretty inefficient way to combine
- * capabilities. However, it seems to be the most straightforward
- * one, given the limitations of the POSIX.1e draft spec. The spec
- * is optimized for applications that know the capabilities they
- * want to manipulate at compile time.
- */
-
- combined_caps = malloc(1+strlen(CAP_COMBINED_FORMAT)
- +strlen(proc_epcaps)+strlen(conf_icaps));
- if (combined_caps == NULL) {
- D(("unable to combine capabilities into one string - no memory"));
- goto cleanup_epcaps;
- }
-
- if (!strcmp(conf_icaps, "none")) {
- sprintf(combined_caps, CAP_DROP_ALL, proc_epcaps);
- } else if (!strcmp(conf_icaps, "all")) {
- /* no change */
- sprintf(combined_caps, "%s", proc_epcaps);
- } else {
- sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps);
- }
- D(("combined_caps=[%s]", combined_caps));
-
- cap_free(cap_s);
- cap_s = cap_from_text(combined_caps);
- _pam_overwrite(combined_caps);
- _pam_drop(combined_caps);
-
-#ifdef DEBUG
- {
- char *temp = cap_to_text(cap_s, NULL);
- D(("abbreviated caps for process will be [%s]", temp));
- cap_free(temp);
- }
-#endif /* DEBUG */
-
- if (cap_s == NULL) {
- D(("no capabilies to set"));
- } else if (cap_set_proc(cap_s) == 0) {
- D(("capabilities were set correctly"));
- ok = 1;
- } else {
- D(("failed to set specified capabilities: %s", strerror(errno)));
- }
-
-cleanup_epcaps:
- cap_free(proc_epcaps);
-
-cleanup_icaps:
- _pam_overwrite(conf_icaps);
- _pam_drop(conf_icaps);
+ cap_value_t value;
+ cap_flag_value_t code;
+ int rc = -1; /* PAM_(AUTH|CRED|SESSION)_ERR */
+
+ /* set inheritable capability set */
+ if (pcs->do_set_inh) {
+ cap_t cap_s = cap_get_proc();
+ if (!cap_s) {
+ syslog(LOG_ERR, "your kernel is capability challenged - upgrade: %s",
+ strerror(errno));
+ goto out;
+ }
+ for (value=0; ;value++) {
+ if (cap_get_flag(pcs->result, value, CAP_INHERITABLE, &code))
+ break;
+ cap_set_flag(cap_s, CAP_INHERITABLE, 1, &value, code);
+ }
+ if (cap_set_proc(cap_s) < 0) {
+ if (errno == EPERM)
+ rc = PAM_PERM_DENIED;
+ syslog(LOG_ERR, "unable to set inheritable capabilities (%s)",
+ strerror(errno));
+ cap_free(cap_s);
+ goto out;
+ }
+ if (pcs->debug) {
+ char *tmp = cap_to_text(cap_s, NULL);

-cleanup_cap_s:
- if (cap_s) {
+ syslog(LOG_DEBUG, "user %s new capabilities: %s",
+ pcs->user, tmp);
+ cap_free(tmp);
+ }
cap_free(cap_s);
- cap_s = NULL;
}
+ /* drop capability bounding set */
+ if (pcs->do_set_bset) {
+ for (value=0; ;value++) {
+ if (cap_get_flag(pcs->result, value, CAP_PERMITTED, &code))
+ break;
+ if (code == CAP_SET) {
+ if (prctl(PR_CAPBSET_DROP, value) < 0) {
+ syslog(LOG_ERR, "unable to drop capability b-set %u (%s)",
+ value, strerror(errno));
+ goto out;
+ }
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "%s drops capability %s from bounding set",
+ pcs->user, _cap_names[value]);
+ }
+ }
+ }
+ rc = PAM_SUCCESS;

- return ok;
-}
-
-/* log errors */
-
-static void _pam_log(int err, const char *format, ...)
-{
- va_list args;
+ out:
+ cap_free(pcs->result);

- va_start(args, format);
- openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
- vsyslog(err, format, args);
- va_end(args);
- closelog();
+ return rc;
}

-static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
+static int init_pam_cap(pam_handle_t *pamh, int argc, const char **argv,
+ struct pam_cap_s *pcs)
{
- int ctrl=0;
+ int ctrl, rc;
+
+ /* Initialization */
+ memset(pcs, 0, sizeof(struct pam_cap_s));
+ pcs->conf_filename = USER_CAP_FILE;
+ rc = pam_get_user(pamh, &pcs->user, NULL);
+ if (rc == PAM_CONV_AGAIN) {
+ syslog(LOG_INFO, "user conversation is not available yet");
+ return PAM_INCOMPLETE;
+ }
+ if (rc != PAM_SUCCESS) {
+ syslog(LOG_INFO, "pam_get_user failed: %s", pam_strerror(pamh, rc));
+ return -1;
+ }

/* step through arguments */
for (ctrl=0; argc-- > 0; ++argv) {
-
if (!strcmp(*argv, "debug")) {
pcs->debug = 1;
} else if (!strcmp(*argv, "config=")) {
pcs->conf_filename = strlen("config=") + *argv;
} else {
- _pam_log(LOG_ERR, "unknown option; %s", *argv);
+ syslog(LOG_ERR, "unknown option: %s", *argv);
+ return -1;
}
+ }
+ return PAM_SUCCESS;
+}

+static void cleanup_pam_cap(pam_handle_t *pamh, void *data, int error_status)
+{
+ struct pam_cap_s *pcs = (struct pam_cap_s *) data;
+
+ if (pcs) {
+ if (pcs->result)
+ cap_free(pcs->result);
+ free(pcs);
}
}

int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
- int retval;
- struct pam_cap_s pcs;
- char *conf_icaps;
+ struct pam_cap_s *pcs = NULL;
+ int rc = PAM_BUF_ERR;

- memset(&pcs, 0, sizeof(pcs));
+ openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

- parse_args(argc, argv, &pcs);
+ pcs = malloc(sizeof(struct pam_cap_s));
+ if (!pcs)
+ goto error;

- retval = pam_get_user(pamh, &pcs.user, NULL);
+ rc = init_pam_cap(pamh, argc, argv, pcs);
+ if (rc != PAM_SUCCESS)
+ goto error;

- if (retval == PAM_CONV_AGAIN) {
- D(("user conversation is not available yet"));
- memset(&pcs, 0, sizeof(pcs));
- return PAM_INCOMPLETE;
- }
+ rc = read_capabilities_for_user(pcs);
+ if (rc != PAM_SUCCESS)
+ goto error;

- if (retval != PAM_SUCCESS) {
- D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
- memset(&pcs, 0, sizeof(pcs));
- return PAM_AUTH_ERR;
+ rc = pam_set_data(pamh, MODULE_NAME, pcs, cleanup_pam_cap);
+ if (rc == PAM_SUCCESS) {
+ /* OK, pam_sm_setcred() will be called next */
+ closelog();
+ return rc;
}

- conf_icaps =
- read_capabilities_for_user(pcs.user,
- pcs.conf_filename
- ? pcs.conf_filename:USER_CAP_FILE );
-
- memset(&pcs, 0, sizeof(pcs));
-
- if (conf_icaps) {
- D(("it appears that there are capabilities for this user [%s]",
- conf_icaps));
+ error:
+ cleanup_pam_cap(pamh, pcs, rc);
+ closelog();
+ return rc < 0 ? PAM_AUTH_ERR : rc;
+}

- /* We could also store this as a pam_[gs]et_data item for use
- by the setcred call to follow. As it is, there is a small
- race associated with a redundant read. Oh well, if you
- care, send me a patch.. */
+int pam_sm_setcred(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ struct pam_cap_s *pcs = NULL;
+ int rc = PAM_IGNORE;

- _pam_overwrite(conf_icaps);
- _pam_drop(conf_icaps);
+ openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

- return PAM_SUCCESS;
+ if (!(flags & PAM_ESTABLISH_CRED))
+ goto out;

- } else {
+ rc = pam_get_data(pamh, MODULE_NAME, (void *)&pcs);
+ if (rc != PAM_SUCCESS)
+ return rc;

- D(("there are no capabilities restrctions on this user"));
- return PAM_IGNORE;
+ rc = set_capabilities(pcs);

- }
+ out:
+ closelog();
+ return rc < 0 ? PAM_CRED_ERR : rc;
}

-int pam_sm_setcred(pam_handle_t *pamh, int flags,
- int argc, const char **argv)
+int pam_sm_open_session(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
{
- int retval;
struct pam_cap_s pcs;
+ int rc;

- if (!(flags & PAM_ESTABLISH_CRED)) {
- D(("we don't handle much in the way of credentials"));
- return PAM_IGNORE;
- }
+ openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

- memset(&pcs, 0, sizeof(pcs));
+ rc = init_pam_cap(pamh, argc, argv, &pcs);
+ if (rc != PAM_SUCCESS)
+ goto out;

- parse_args(argc, argv, &pcs);
+ rc = read_capabilities_for_user(&pcs);
+ if (rc != PAM_SUCCESS)
+ goto out;

- retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
- if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
+ rc = set_capabilities(&pcs);

- D(("user's name is not set"));
- return PAM_AUTH_ERR;
+ if (rc == PAM_SUCCESS) {
+ rc = set_capabilities(&pcs);
+ if (pcs.result)
+ cap_free(pcs.result);
}

- retval = set_capabilities(&pcs);
+ out:
+ if (pcs.result)
+ cap_free(pcs.result);
+ closelog();

- memset(&pcs, 0, sizeof(pcs));
+ return rc < 0 ? PAM_SESSION_ERR : rc;
+}

- return (retval ? PAM_SUCCESS:PAM_IGNORE );
+int pam_sm_close_session(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ return PAM_SUCCESS; /* do nothing */
}

2007-12-07 00:52:15

by Kohei KaiGai

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

Sorry, any TABs are replaced by MUA.
I'll send the patch again.

> The attached patch provides several improvement for pam_cap module.
> 1. It enables pam_cap to drop capabilities from process'es capability
> bounding set.
> 2. It enables to specify allowing inheritable capability set or dropping
> bounding capability set for groups, not only users.
> 3. It provide pam_sm_session() method, not only pam_sm_authenticate()
> and pam_sm_setcred(). A system administrator can select more
> appropriate mode for his purpose.
> 4. In the auth/cred mode, it enables to cache the configuration file,
> to avoid read and analyze it twice.
> (Therefore, most of the part in the original one got replaced....)
>
> The default configuration file is "/etc/security/capability.conf".
> You can describe as follows:
> --------
> # kaigai get cap_net_raw and cap_kill, tak get cap_sys_pacct pI.
> # We can omit "i:" in the head of each line.
> i:cap_net_raw,cap_kill kaigai
> cap_sys_pacct tak
>
> # ymj and tak lost cap_sys_chroot from cap_bset
> b:cap_sys_chroot ymj tak
>
> # Any user within webadm group get cap_net_bind_service pI.
> i:cap_net_bind_service @webadm
>
> # Any user within users group lost cap_sys_module from cap_bset
> b:cap_sys_module @users
> --------
>
> When a user or groups he belongs is on several lines, all configurations
> are simplly compounded.
>
> In the above example, if tak belongs to webadm and users group,
> he will get cap_sys_pacct and cap_net_bind_service pI, and lost
> cap_sys_chroot and cap_sys_module from his cap_bset.
>
> Thanks,

Signed-off-by: KaiGai Kohei <[email protected]>
--
pam_cap/capability.conf | 6 +
pam_cap/pam_cap.c | 495 ++++++++++++++++++++++++++++-------------------
2 files changed, 305 insertions(+), 196 deletions(-)

diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index b543142..707cdc3 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -24,6 +24,12 @@ cap_setfcap morgan
## 'everyone else' gets no inheritable capabilities
none *

+# user 'kaigai' lost CAP_NET_RAW capability from bounding set
+b:cap_net_raw kaigai
+
+# group 'acctadm' get CAP_SYS_PACCT inheritable capability
+i:cap_sys_pacct @acctadm
+
## if there is no '*' entry, all users not explicitly mentioned will
## get all available capabilities. This is a permissive default, and
## probably not what you want...
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 94c5ebc..a917d5c 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -1,5 +1,6 @@
/*
* Copyright (c) 1999,2007 Andrew G. Morgan <[email protected]>
+ * Copyright (c) 2007 KaiGai Kohei <[email protected]>
*
* The purpose of this module is to enforce inheritable capability sets
* for a specified user.
@@ -13,298 +14,400 @@
#include <stdarg.h>
#include <stdlib.h>
#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>

#include <sys/capability.h>
+#include <sys/prctl.h>

#include <security/pam_modules.h>
#include <security/_pam_macros.h>

+#define MODULE_NAME "pam_cap"
#define USER_CAP_FILE "/etc/security/capability.conf"
#define CAP_FILE_BUFFER_SIZE 4096
#define CAP_FILE_DELIMITERS " \t\n"
-#define CAP_COMBINED_FORMAT "%s all-i %s+i"
-#define CAP_DROP_ALL "%s all-i"
+
+#ifndef PR_CAPBSET_DROP
+#define PR_CAPBSET_DROP 24
+#endif
+
+extern char const *_cap_names[];

struct pam_cap_s {
int debug;
const char *user;
const char *conf_filename;
+ /* set in read_capabilities_for_user() */
+ cap_t result;
+ int do_set_inh : 1;
+ int do_set_bset : 1;
};

-/* obtain the inheritable capabilities for the current user */
-
-static char *read_capabilities_for_user(const char *user, const char *source)
+/* obtain the inheritable/bounding capabilities for the current user */
+static int read_capabilities_for_user(struct pam_cap_s *pcs)
{
- char *cap_string = NULL;
- char buffer[CAP_FILE_BUFFER_SIZE], *line;
+ char buffer[CAP_FILE_BUFFER_SIZE];
FILE *cap_file;
+ struct passwd *pwd;
+ int line_num = 0;
+ int rc = -1; /* PAM_(AUTH|CRED|SESSION)_ERR */
+
+ pwd = getpwnam(pcs->user);
+ if (!pwd) {
+ syslog(LOG_ERR, "user %s not in passwd entries", pcs->user);
+ return PAM_AUTH_ERR;
+ }

- cap_file = fopen(source, "r");
- if (cap_file == NULL) {
- D(("failed to open capability file"));
- return NULL;
+ cap_file = fopen(pcs->conf_filename, "r");
+ if (!cap_file) {
+ if (errno == ENOENT) {
+ syslog(LOG_NOTICE, "%s is not found",
+ pcs->conf_filename);
+ return PAM_IGNORE;
+ } else {
+ syslog(LOG_ERR, "unable to open '%s' (%s)",
+ pcs->conf_filename, strerror(errno));
+ return rc;
+ }
}

- while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
- int found_one = 0;
- const char *cap_text;
+ pcs->result = NULL;
+ while (fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file) != NULL) {
+ char *pos, *cap_text;
+ int matched = 0;
+ int line_ops = CAP_INHERITABLE;

- cap_text = strtok(line, CAP_FILE_DELIMITERS);
+ line_num++;

- if (cap_text == NULL) {
- D(("empty line"));
- continue;
- }
- if (*cap_text == '#') {
- D(("comment line"));
+ /* remove comment */
+ pos = strchr(buffer, '#');
+ if (pos)
+ *pos = '\0';
+
+ cap_text = strtok(buffer, CAP_FILE_DELIMITERS);
+ /* empty line */
+ if (!cap_text)
continue;
+
+ if (!strncmp(cap_text, "b:", 2)) {
+ /* permitted field is used to store bounding set */
+ line_ops = CAP_PERMITTED;
+ cap_text += 2;
+ } else if (!strncmp(cap_text, "i:", 2)) {
+ cap_text += 2;
}

- while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
+ /* check members */
+ while ((pos = strtok(NULL, CAP_FILE_DELIMITERS)) != NULL) {
+ /* wildcard */
+ if (!strcmp("*", pos)) {
+ matched = 1;
+ break;
+ }

- if (strcmp("*", line) == 0) {
- D(("wildcard matched"));
- found_one = 1;
- cap_string = strdup(cap_text);
+ /* It it group name? */
+ if (*pos == '@') {
+ struct group *grp;
+ int i;
+
+ pos++;
+ grp = getgrnam(pos);
+ if (!grp) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "group '%s' not found at line:%d",
+ pos, line_num);
+ continue;
+ }
+
+ if (pwd->pw_gid == grp->gr_gid) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
+ pcs->user, pos, line_num);
+ matched = 1;
+ break;
+ }
+
+ for (i=0; grp->gr_mem[i]; i++) {
+ if (!strcmp(pcs->user, grp->gr_mem[i])) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
+ pcs->user, pos, line_num);
+ matched = 1;
+ break;
+ }
+ }
+ syslog(LOG_ERR, "no matching %s", pos);
+ } else if (!strcmp(pcs->user, pos)) {
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "user '%s' matched at line:%d",
+ pos, line_num);
+ matched = 1;
break;
}
+ }
+
+ if (matched) {
+ char tmpbuf[CAP_FILE_BUFFER_SIZE];
+ cap_t tmp;
+ cap_value_t value;
+ cap_flag_value_t code;
+
+ if (!pcs->result) {
+ pcs->result = cap_init();
+ if (!pcs->result) {
+ syslog(LOG_ERR, "unable to allocate cap_t object (%s)",
+ strerror(errno));
+ goto out;
+ }
+ }

- if (strcmp(user, line) == 0) {
- D(("exact match for user"));
- found_one = 1;
- cap_string = strdup(cap_text);
+ switch (line_ops) {
+ case CAP_INHERITABLE:
+ pcs->do_set_inh = 1;
+ break;
+ case CAP_PERMITTED:
+ pcs->do_set_bset = 1;
break;
}

- D(("user is not [%s] - skipping", line));
+ if (!strcmp(cap_text, "none"))
+ continue;
+
+ snprintf(tmpbuf, sizeof(tmpbuf), "%s=p", cap_text);
+ tmp = cap_from_text(tmpbuf);
+ if (!tmp) {
+ syslog(LOG_ERR, "unable to convert '%s' (%s)",
+ tmpbuf, strerror(errno));
+ cap_free(pcs->result);
+ pcs->result = NULL;
+ goto out;
+ }
+
+ for (value=0; ;value++) {
+ if (cap_get_flag(tmp, value, CAP_PERMITTED, &code) < 0)
+ break; /* If value == __CAP_BITS, we get EINVAL */
+ if (code == CAP_SET)
+ cap_set_flag(pcs->result, line_ops, 1, &value, CAP_SET);
+ }
+ cap_free(tmp);
}
+ }

- cap_text = NULL;
- line = NULL;
+ if (pcs->debug) {
+ char *tmp = cap_to_text(pcs->result, NULL);

- if (found_one) {
- D(("user [%s] matched - caps are [%s]", user, cap_string));
- break;
- }
+ syslog(LOG_DEBUG, "configuration for user %s is %s",
+ pcs->user, tmp);
+ cap_free(tmp);
}
+ rc = PAM_SUCCESS;

+ out:
fclose(cap_file);

- memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
-
- return cap_string;
+ return rc;
}

/*
* Set capabilities for current process to match the current
* permitted+executable sets combined with the configured inheritable
- * set.
+ * and bounding set.
*/

-static int set_capabilities(struct pam_cap_s *cs)
+static int set_capabilities(struct pam_cap_s *pcs)
{
- cap_t cap_s;
- ssize_t length = 0;
- char *conf_icaps;
- char *proc_epcaps;
- char *combined_caps;
- int ok = 0;
-
- cap_s = cap_get_proc();
- if (cap_s == NULL) {
- D(("your kernel is capability challenged - upgrade: %s",
- strerror(errno)));
- return 0;
- }
-
- conf_icaps =
- read_capabilities_for_user(cs->user,
- cs->conf_filename
- ? cs->conf_filename:USER_CAP_FILE );
- if (conf_icaps == NULL) {
- D(("no capabilities found for user [%s]", cs->user));
- goto cleanup_cap_s;
- }
-
- proc_epcaps = cap_to_text(cap_s, &length);
- if (proc_epcaps == NULL) {
- D(("unable to convert process capabilities to text"));
- goto cleanup_icaps;
- }
-
- /*
- * This is a pretty inefficient way to combine
- * capabilities. However, it seems to be the most straightforward
- * one, given the limitations of the POSIX.1e draft spec. The spec
- * is optimized for applications that know the capabilities they
- * want to manipulate at compile time.
- */
-
- combined_caps = malloc(1+strlen(CAP_COMBINED_FORMAT)
- +strlen(proc_epcaps)+strlen(conf_icaps));
- if (combined_caps == NULL) {
- D(("unable to combine capabilities into one string - no memory"));
- goto cleanup_epcaps;
- }
-
- if (!strcmp(conf_icaps, "none")) {
- sprintf(combined_caps, CAP_DROP_ALL, proc_epcaps);
- } else if (!strcmp(conf_icaps, "all")) {
- /* no change */
- sprintf(combined_caps, "%s", proc_epcaps);
- } else {
- sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps);
- }
- D(("combined_caps=[%s]", combined_caps));
-
- cap_free(cap_s);
- cap_s = cap_from_text(combined_caps);
- _pam_overwrite(combined_caps);
- _pam_drop(combined_caps);
-
-#ifdef DEBUG
- {
- char *temp = cap_to_text(cap_s, NULL);
- D(("abbreviated caps for process will be [%s]", temp));
- cap_free(temp);
- }
-#endif /* DEBUG */
-
- if (cap_s == NULL) {
- D(("no capabilies to set"));
- } else if (cap_set_proc(cap_s) == 0) {
- D(("capabilities were set correctly"));
- ok = 1;
- } else {
- D(("failed to set specified capabilities: %s", strerror(errno)));
- }
-
-cleanup_epcaps:
- cap_free(proc_epcaps);
-
-cleanup_icaps:
- _pam_overwrite(conf_icaps);
- _pam_drop(conf_icaps);
+ cap_value_t value;
+ cap_flag_value_t code;
+ int rc = -1; /* PAM_(AUTH|CRED|SESSION)_ERR */
+
+ /* set inheritable capability set */
+ if (pcs->do_set_inh) {
+ cap_t cap_s = cap_get_proc();
+ if (!cap_s) {
+ syslog(LOG_ERR, "your kernel is capability challenged - upgrade: %s",
+ strerror(errno));
+ goto out;
+ }
+ for (value=0; ;value++) {
+ if (cap_get_flag(pcs->result, value, CAP_INHERITABLE, &code))
+ break;
+ cap_set_flag(cap_s, CAP_INHERITABLE, 1, &value, code);
+ }
+ if (cap_set_proc(cap_s) < 0) {
+ if (errno == EPERM)
+ rc = PAM_PERM_DENIED;
+ syslog(LOG_ERR, "unable to set inheritable capabilities (%s)",
+ strerror(errno));
+ cap_free(cap_s);
+ goto out;
+ }
+ if (pcs->debug) {
+ char *tmp = cap_to_text(cap_s, NULL);

-cleanup_cap_s:
- if (cap_s) {
+ syslog(LOG_DEBUG, "user %s new capabilities: %s",
+ pcs->user, tmp);
+ cap_free(tmp);
+ }
cap_free(cap_s);
- cap_s = NULL;
}
+ /* drop capability bounding set */
+ if (pcs->do_set_bset) {
+ for (value=0; ;value++) {
+ if (cap_get_flag(pcs->result, value, CAP_PERMITTED, &code))
+ break;
+ if (code == CAP_SET) {
+ if (prctl(PR_CAPBSET_DROP, value) < 0) {
+ syslog(LOG_ERR, "unable to drop capability b-set %u (%s)",
+ value, strerror(errno));
+ goto out;
+ }
+ if (pcs->debug)
+ syslog(LOG_DEBUG, "%s drops capability %s from bounding set",
+ pcs->user, _cap_names[value]);
+ }
+ }
+ }
+ rc = PAM_SUCCESS;

- return ok;
-}
-
-/* log errors */
-
-static void _pam_log(int err, const char *format, ...)
-{
- va_list args;
+ out:
+ cap_free(pcs->result);

- va_start(args, format);
- openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
- vsyslog(err, format, args);
- va_end(args);
- closelog();
+ return rc;
}

-static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
+static int init_pam_cap(pam_handle_t *pamh, int argc, const char **argv,
+ struct pam_cap_s *pcs)
{
- int ctrl=0;
+ int ctrl, rc;
+
+ /* Initialization */
+ memset(pcs, 0, sizeof(struct pam_cap_s));
+ pcs->conf_filename = USER_CAP_FILE;
+ rc = pam_get_user(pamh, &pcs->user, NULL);
+ if (rc == PAM_CONV_AGAIN) {
+ syslog(LOG_INFO, "user conversation is not available yet");
+ return PAM_INCOMPLETE;
+ }
+ if (rc != PAM_SUCCESS) {
+ syslog(LOG_INFO, "pam_get_user failed: %s", pam_strerror(pamh, rc));
+ return -1;
+ }

/* step through arguments */
for (ctrl=0; argc-- > 0; ++argv) {
-
if (!strcmp(*argv, "debug")) {
pcs->debug = 1;
} else if (!strcmp(*argv, "config=")) {
pcs->conf_filename = strlen("config=") + *argv;
} else {
- _pam_log(LOG_ERR, "unknown option; %s", *argv);
+ syslog(LOG_ERR, "unknown option: %s", *argv);
+ return -1;
}
+ }
+ return PAM_SUCCESS;
+}

+static void cleanup_pam_cap(pam_handle_t *pamh, void *data, int error_status)
+{
+ struct pam_cap_s *pcs = (struct pam_cap_s *) data;
+
+ if (pcs) {
+ if (pcs->result)
+ cap_free(pcs->result);
+ free(pcs);
}
}

int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
- int retval;
- struct pam_cap_s pcs;
- char *conf_icaps;
+ struct pam_cap_s *pcs = NULL;
+ int rc = PAM_BUF_ERR;

- memset(&pcs, 0, sizeof(pcs));
+ openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

- parse_args(argc, argv, &pcs);
+ pcs = malloc(sizeof(struct pam_cap_s));
+ if (!pcs)
+ goto error;

- retval = pam_get_user(pamh, &pcs.user, NULL);
+ rc = init_pam_cap(pamh, argc, argv, pcs);
+ if (rc != PAM_SUCCESS)
+ goto error;

- if (retval == PAM_CONV_AGAIN) {
- D(("user conversation is not available yet"));
- memset(&pcs, 0, sizeof(pcs));
- return PAM_INCOMPLETE;
- }
+ rc = read_capabilities_for_user(pcs);
+ if (rc != PAM_SUCCESS)
+ goto error;

- if (retval != PAM_SUCCESS) {
- D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
- memset(&pcs, 0, sizeof(pcs));
- return PAM_AUTH_ERR;
+ rc = pam_set_data(pamh, MODULE_NAME, pcs, cleanup_pam_cap);
+ if (rc == PAM_SUCCESS) {
+ /* OK, pam_sm_setcred() will be called next */
+ closelog();
+ return rc;
}

- conf_icaps =
- read_capabilities_for_user(pcs.user,
- pcs.conf_filename
- ? pcs.conf_filename:USER_CAP_FILE );
-
- memset(&pcs, 0, sizeof(pcs));
-
- if (conf_icaps) {
- D(("it appears that there are capabilities for this user [%s]",
- conf_icaps));
+ error:
+ cleanup_pam_cap(pamh, pcs, rc);
+ closelog();
+ return rc < 0 ? PAM_AUTH_ERR : rc;
+}

- /* We could also store this as a pam_[gs]et_data item for use
- by the setcred call to follow. As it is, there is a small
- race associated with a redundant read. Oh well, if you
- care, send me a patch.. */
+int pam_sm_setcred(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ struct pam_cap_s *pcs = NULL;
+ int rc = PAM_IGNORE;

- _pam_overwrite(conf_icaps);
- _pam_drop(conf_icaps);
+ openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

- return PAM_SUCCESS;
+ if (!(flags & PAM_ESTABLISH_CRED))
+ goto out;

- } else {
+ rc = pam_get_data(pamh, MODULE_NAME, (void *)&pcs);
+ if (rc != PAM_SUCCESS)
+ return rc;

- D(("there are no capabilities restrctions on this user"));
- return PAM_IGNORE;
+ rc = set_capabilities(pcs);

- }
+ out:
+ closelog();
+ return rc < 0 ? PAM_CRED_ERR : rc;
}

-int pam_sm_setcred(pam_handle_t *pamh, int flags,
- int argc, const char **argv)
+int pam_sm_open_session(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
{
- int retval;
struct pam_cap_s pcs;
+ int rc;

- if (!(flags & PAM_ESTABLISH_CRED)) {
- D(("we don't handle much in the way of credentials"));
- return PAM_IGNORE;
- }
+ openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

- memset(&pcs, 0, sizeof(pcs));
+ rc = init_pam_cap(pamh, argc, argv, &pcs);
+ if (rc != PAM_SUCCESS)
+ goto out;

- parse_args(argc, argv, &pcs);
+ rc = read_capabilities_for_user(&pcs);
+ if (rc != PAM_SUCCESS)
+ goto out;

- retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
- if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
+ rc = set_capabilities(&pcs);

- D(("user's name is not set"));
- return PAM_AUTH_ERR;
+ if (rc == PAM_SUCCESS) {
+ rc = set_capabilities(&pcs);
+ if (pcs.result)
+ cap_free(pcs.result);
}

- retval = set_capabilities(&pcs);
+ out:
+ if (pcs.result)
+ cap_free(pcs.result);
+ closelog();

- memset(&pcs, 0, sizeof(pcs));
+ return rc < 0 ? PAM_SESSION_ERR : rc;
+}

- return (retval ? PAM_SUCCESS:PAM_IGNORE );
+int pam_sm_close_session(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ return PAM_SUCCESS; /* do nothing */
}

2007-12-07 06:14:24

by Andrew G. Morgan

[permalink] [raw]
Subject: Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I've pushed it to a pamcap-enhancements branch and I'll will try to
review it quickly.

Thanks

Andrew

KaiGai Kohei wrote:
> Sorry, any TABs are replaced by MUA.
> I'll send the patch again.
>
>> The attached patch provides several improvement for pam_cap module.
>> 1. It enables pam_cap to drop capabilities from process'es capability
>> bounding set.
>> 2. It enables to specify allowing inheritable capability set or dropping
>> bounding capability set for groups, not only users.
>> 3. It provide pam_sm_session() method, not only pam_sm_authenticate()
>> and pam_sm_setcred(). A system administrator can select more
>> appropriate mode for his purpose.
>> 4. In the auth/cred mode, it enables to cache the configuration file,
>> to avoid read and analyze it twice.
>> (Therefore, most of the part in the original one got replaced....)
>>
>> The default configuration file is "/etc/security/capability.conf".
>> You can describe as follows:
>> --------
>> # kaigai get cap_net_raw and cap_kill, tak get cap_sys_pacct pI.
>> # We can omit "i:" in the head of each line.
>> i:cap_net_raw,cap_kill kaigai
>> cap_sys_pacct tak
>>
>> # ymj and tak lost cap_sys_chroot from cap_bset
>> b:cap_sys_chroot ymj tak
>>
>> # Any user within webadm group get cap_net_bind_service pI.
>> i:cap_net_bind_service @webadm
>>
>> # Any user within users group lost cap_sys_module from cap_bset
>> b:cap_sys_module @users
>> --------
>>
>> When a user or groups he belongs is on several lines, all configurations
>> are simplly compounded.
>>
>> In the above example, if tak belongs to webadm and users group,
>> he will get cap_sys_pacct and cap_net_bind_service pI, and lost
>> cap_sys_chroot and cap_sys_module from his cap_bset.
>>
>> Thanks,
>
> Signed-off-by: KaiGai Kohei <[email protected]>
> --
> pam_cap/capability.conf | 6 +
> pam_cap/pam_cap.c | 495 ++++++++++++++++++++++++++++-------------------
> 2 files changed, 305 insertions(+), 196 deletions(-)
>
> diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
> index b543142..707cdc3 100644
> --- a/pam_cap/capability.conf
> +++ b/pam_cap/capability.conf
> @@ -24,6 +24,12 @@ cap_setfcap morgan
> ## 'everyone else' gets no inheritable capabilities
> none *
>
> +# user 'kaigai' lost CAP_NET_RAW capability from bounding set
> +b:cap_net_raw kaigai
> +
> +# group 'acctadm' get CAP_SYS_PACCT inheritable capability
> +i:cap_sys_pacct @acctadm
> +
> ## if there is no '*' entry, all users not explicitly mentioned will
> ## get all available capabilities. This is a permissive default, and
> ## probably not what you want...
> diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
> index 94c5ebc..a917d5c 100644
> --- a/pam_cap/pam_cap.c
> +++ b/pam_cap/pam_cap.c
> @@ -1,5 +1,6 @@
> /*
> * Copyright (c) 1999,2007 Andrew G. Morgan <[email protected]>
> + * Copyright (c) 2007 KaiGai Kohei <[email protected]>
> *
> * The purpose of this module is to enforce inheritable capability sets
> * for a specified user.
> @@ -13,298 +14,400 @@
> #include <stdarg.h>
> #include <stdlib.h>
> #include <syslog.h>
> +#include <pwd.h>
> +#include <grp.h>
>
> #include <sys/capability.h>
> +#include <sys/prctl.h>
>
> #include <security/pam_modules.h>
> #include <security/_pam_macros.h>
>
> +#define MODULE_NAME "pam_cap"
> #define USER_CAP_FILE "/etc/security/capability.conf"
> #define CAP_FILE_BUFFER_SIZE 4096
> #define CAP_FILE_DELIMITERS " \t\n"
> -#define CAP_COMBINED_FORMAT "%s all-i %s+i"
> -#define CAP_DROP_ALL "%s all-i"
> +
> +#ifndef PR_CAPBSET_DROP
> +#define PR_CAPBSET_DROP 24
> +#endif
> +
> +extern char const *_cap_names[];
>
> struct pam_cap_s {
> int debug;
> const char *user;
> const char *conf_filename;
> + /* set in read_capabilities_for_user() */
> + cap_t result;
> + int do_set_inh : 1;
> + int do_set_bset : 1;
> };
>
> -/* obtain the inheritable capabilities for the current user */
> -
> -static char *read_capabilities_for_user(const char *user, const char *source)
> +/* obtain the inheritable/bounding capabilities for the current user */
> +static int read_capabilities_for_user(struct pam_cap_s *pcs)
> {
> - char *cap_string = NULL;
> - char buffer[CAP_FILE_BUFFER_SIZE], *line;
> + char buffer[CAP_FILE_BUFFER_SIZE];
> FILE *cap_file;
> + struct passwd *pwd;
> + int line_num = 0;
> + int rc = -1; /* PAM_(AUTH|CRED|SESSION)_ERR */
> +
> + pwd = getpwnam(pcs->user);
> + if (!pwd) {
> + syslog(LOG_ERR, "user %s not in passwd entries", pcs->user);
> + return PAM_AUTH_ERR;
> + }
>
> - cap_file = fopen(source, "r");
> - if (cap_file == NULL) {
> - D(("failed to open capability file"));
> - return NULL;
> + cap_file = fopen(pcs->conf_filename, "r");
> + if (!cap_file) {
> + if (errno == ENOENT) {
> + syslog(LOG_NOTICE, "%s is not found",
> + pcs->conf_filename);
> + return PAM_IGNORE;
> + } else {
> + syslog(LOG_ERR, "unable to open '%s' (%s)",
> + pcs->conf_filename, strerror(errno));
> + return rc;
> + }
> }
>
> - while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
> - int found_one = 0;
> - const char *cap_text;
> + pcs->result = NULL;
> + while (fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file) != NULL) {
> + char *pos, *cap_text;
> + int matched = 0;
> + int line_ops = CAP_INHERITABLE;
>
> - cap_text = strtok(line, CAP_FILE_DELIMITERS);
> + line_num++;
>
> - if (cap_text == NULL) {
> - D(("empty line"));
> - continue;
> - }
> - if (*cap_text == '#') {
> - D(("comment line"));
> + /* remove comment */
> + pos = strchr(buffer, '#');
> + if (pos)
> + *pos = '\0';
> +
> + cap_text = strtok(buffer, CAP_FILE_DELIMITERS);
> + /* empty line */
> + if (!cap_text)
> continue;
> +
> + if (!strncmp(cap_text, "b:", 2)) {
> + /* permitted field is used to store bounding set */
> + line_ops = CAP_PERMITTED;
> + cap_text += 2;
> + } else if (!strncmp(cap_text, "i:", 2)) {
> + cap_text += 2;
> }
>
> - while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
> + /* check members */
> + while ((pos = strtok(NULL, CAP_FILE_DELIMITERS)) != NULL) {
> + /* wildcard */
> + if (!strcmp("*", pos)) {
> + matched = 1;
> + break;
> + }
>
> - if (strcmp("*", line) == 0) {
> - D(("wildcard matched"));
> - found_one = 1;
> - cap_string = strdup(cap_text);
> + /* It it group name? */
> + if (*pos == '@') {
> + struct group *grp;
> + int i;
> +
> + pos++;
> + grp = getgrnam(pos);
> + if (!grp) {
> + if (pcs->debug)
> + syslog(LOG_DEBUG, "group '%s' not found at line:%d",
> + pos, line_num);
> + continue;
> + }
> +
> + if (pwd->pw_gid == grp->gr_gid) {
> + if (pcs->debug)
> + syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
> + pcs->user, pos, line_num);
> + matched = 1;
> + break;
> + }
> +
> + for (i=0; grp->gr_mem[i]; i++) {
> + if (!strcmp(pcs->user, grp->gr_mem[i])) {
> + if (pcs->debug)
> + syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
> + pcs->user, pos, line_num);
> + matched = 1;
> + break;
> + }
> + }
> + syslog(LOG_ERR, "no matching %s", pos);
> + } else if (!strcmp(pcs->user, pos)) {
> + if (pcs->debug)
> + syslog(LOG_DEBUG, "user '%s' matched at line:%d",
> + pos, line_num);
> + matched = 1;
> break;
> }
> + }
> +
> + if (matched) {
> + char tmpbuf[CAP_FILE_BUFFER_SIZE];
> + cap_t tmp;
> + cap_value_t value;
> + cap_flag_value_t code;
> +
> + if (!pcs->result) {
> + pcs->result = cap_init();
> + if (!pcs->result) {
> + syslog(LOG_ERR, "unable to allocate cap_t object (%s)",
> + strerror(errno));
> + goto out;
> + }
> + }
>
> - if (strcmp(user, line) == 0) {
> - D(("exact match for user"));
> - found_one = 1;
> - cap_string = strdup(cap_text);
> + switch (line_ops) {
> + case CAP_INHERITABLE:
> + pcs->do_set_inh = 1;
> + break;
> + case CAP_PERMITTED:
> + pcs->do_set_bset = 1;
> break;
> }
>
> - D(("user is not [%s] - skipping", line));
> + if (!strcmp(cap_text, "none"))
> + continue;
> +
> + snprintf(tmpbuf, sizeof(tmpbuf), "%s=p", cap_text);
> + tmp = cap_from_text(tmpbuf);
> + if (!tmp) {
> + syslog(LOG_ERR, "unable to convert '%s' (%s)",
> + tmpbuf, strerror(errno));
> + cap_free(pcs->result);
> + pcs->result = NULL;
> + goto out;
> + }
> +
> + for (value=0; ;value++) {
> + if (cap_get_flag(tmp, value, CAP_PERMITTED, &code) < 0)
> + break; /* If value == __CAP_BITS, we get EINVAL */
> + if (code == CAP_SET)
> + cap_set_flag(pcs->result, line_ops, 1, &value, CAP_SET);
> + }
> + cap_free(tmp);
> }
> + }
>
> - cap_text = NULL;
> - line = NULL;
> + if (pcs->debug) {
> + char *tmp = cap_to_text(pcs->result, NULL);
>
> - if (found_one) {
> - D(("user [%s] matched - caps are [%s]", user, cap_string));
> - break;
> - }
> + syslog(LOG_DEBUG, "configuration for user %s is %s",
> + pcs->user, tmp);
> + cap_free(tmp);
> }
> + rc = PAM_SUCCESS;
>
> + out:
> fclose(cap_file);
>
> - memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
> -
> - return cap_string;
> + return rc;
> }
>
> /*
> * Set capabilities for current process to match the current
> * permitted+executable sets combined with the configured inheritable
> - * set.
> + * and bounding set.
> */
>
> -static int set_capabilities(struct pam_cap_s *cs)
> +static int set_capabilities(struct pam_cap_s *pcs)
> {
> - cap_t cap_s;
> - ssize_t length = 0;
> - char *conf_icaps;
> - char *proc_epcaps;
> - char *combined_caps;
> - int ok = 0;
> -
> - cap_s = cap_get_proc();
> - if (cap_s == NULL) {
> - D(("your kernel is capability challenged - upgrade: %s",
> - strerror(errno)));
> - return 0;
> - }
> -
> - conf_icaps =
> - read_capabilities_for_user(cs->user,
> - cs->conf_filename
> - ? cs->conf_filename:USER_CAP_FILE );
> - if (conf_icaps == NULL) {
> - D(("no capabilities found for user [%s]", cs->user));
> - goto cleanup_cap_s;
> - }
> -
> - proc_epcaps = cap_to_text(cap_s, &length);
> - if (proc_epcaps == NULL) {
> - D(("unable to convert process capabilities to text"));
> - goto cleanup_icaps;
> - }
> -
> - /*
> - * This is a pretty inefficient way to combine
> - * capabilities. However, it seems to be the most straightforward
> - * one, given the limitations of the POSIX.1e draft spec. The spec
> - * is optimized for applications that know the capabilities they
> - * want to manipulate at compile time.
> - */
> -
> - combined_caps = malloc(1+strlen(CAP_COMBINED_FORMAT)
> - +strlen(proc_epcaps)+strlen(conf_icaps));
> - if (combined_caps == NULL) {
> - D(("unable to combine capabilities into one string - no memory"));
> - goto cleanup_epcaps;
> - }
> -
> - if (!strcmp(conf_icaps, "none")) {
> - sprintf(combined_caps, CAP_DROP_ALL, proc_epcaps);
> - } else if (!strcmp(conf_icaps, "all")) {
> - /* no change */
> - sprintf(combined_caps, "%s", proc_epcaps);
> - } else {
> - sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps);
> - }
> - D(("combined_caps=[%s]", combined_caps));
> -
> - cap_free(cap_s);
> - cap_s = cap_from_text(combined_caps);
> - _pam_overwrite(combined_caps);
> - _pam_drop(combined_caps);
> -
> -#ifdef DEBUG
> - {
> - char *temp = cap_to_text(cap_s, NULL);
> - D(("abbreviated caps for process will be [%s]", temp));
> - cap_free(temp);
> - }
> -#endif /* DEBUG */
> -
> - if (cap_s == NULL) {
> - D(("no capabilies to set"));
> - } else if (cap_set_proc(cap_s) == 0) {
> - D(("capabilities were set correctly"));
> - ok = 1;
> - } else {
> - D(("failed to set specified capabilities: %s", strerror(errno)));
> - }
> -
> -cleanup_epcaps:
> - cap_free(proc_epcaps);
> -
> -cleanup_icaps:
> - _pam_overwrite(conf_icaps);
> - _pam_drop(conf_icaps);
> + cap_value_t value;
> + cap_flag_value_t code;
> + int rc = -1; /* PAM_(AUTH|CRED|SESSION)_ERR */
> +
> + /* set inheritable capability set */
> + if (pcs->do_set_inh) {
> + cap_t cap_s = cap_get_proc();
> + if (!cap_s) {
> + syslog(LOG_ERR, "your kernel is capability challenged - upgrade: %s",
> + strerror(errno));
> + goto out;
> + }
> + for (value=0; ;value++) {
> + if (cap_get_flag(pcs->result, value, CAP_INHERITABLE, &code))
> + break;
> + cap_set_flag(cap_s, CAP_INHERITABLE, 1, &value, code);
> + }
> + if (cap_set_proc(cap_s) < 0) {
> + if (errno == EPERM)
> + rc = PAM_PERM_DENIED;
> + syslog(LOG_ERR, "unable to set inheritable capabilities (%s)",
> + strerror(errno));
> + cap_free(cap_s);
> + goto out;
> + }
> + if (pcs->debug) {
> + char *tmp = cap_to_text(cap_s, NULL);
>
> -cleanup_cap_s:
> - if (cap_s) {
> + syslog(LOG_DEBUG, "user %s new capabilities: %s",
> + pcs->user, tmp);
> + cap_free(tmp);
> + }
> cap_free(cap_s);
> - cap_s = NULL;
> }
> + /* drop capability bounding set */
> + if (pcs->do_set_bset) {
> + for (value=0; ;value++) {
> + if (cap_get_flag(pcs->result, value, CAP_PERMITTED, &code))
> + break;
> + if (code == CAP_SET) {
> + if (prctl(PR_CAPBSET_DROP, value) < 0) {
> + syslog(LOG_ERR, "unable to drop capability b-set %u (%s)",
> + value, strerror(errno));
> + goto out;
> + }
> + if (pcs->debug)
> + syslog(LOG_DEBUG, "%s drops capability %s from bounding set",
> + pcs->user, _cap_names[value]);
> + }
> + }
> + }
> + rc = PAM_SUCCESS;
>
> - return ok;
> -}
> -
> -/* log errors */
> -
> -static void _pam_log(int err, const char *format, ...)
> -{
> - va_list args;
> + out:
> + cap_free(pcs->result);
>
> - va_start(args, format);
> - openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
> - vsyslog(err, format, args);
> - va_end(args);
> - closelog();
> + return rc;
> }
>
> -static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
> +static int init_pam_cap(pam_handle_t *pamh, int argc, const char **argv,
> + struct pam_cap_s *pcs)
> {
> - int ctrl=0;
> + int ctrl, rc;
> +
> + /* Initialization */
> + memset(pcs, 0, sizeof(struct pam_cap_s));
> + pcs->conf_filename = USER_CAP_FILE;
> + rc = pam_get_user(pamh, &pcs->user, NULL);
> + if (rc == PAM_CONV_AGAIN) {
> + syslog(LOG_INFO, "user conversation is not available yet");
> + return PAM_INCOMPLETE;
> + }
> + if (rc != PAM_SUCCESS) {
> + syslog(LOG_INFO, "pam_get_user failed: %s", pam_strerror(pamh, rc));
> + return -1;
> + }
>
> /* step through arguments */
> for (ctrl=0; argc-- > 0; ++argv) {
> -
> if (!strcmp(*argv, "debug")) {
> pcs->debug = 1;
> } else if (!strcmp(*argv, "config=")) {
> pcs->conf_filename = strlen("config=") + *argv;
> } else {
> - _pam_log(LOG_ERR, "unknown option; %s", *argv);
> + syslog(LOG_ERR, "unknown option: %s", *argv);
> + return -1;
> }
> + }
> + return PAM_SUCCESS;
> +}
>
> +static void cleanup_pam_cap(pam_handle_t *pamh, void *data, int error_status)
> +{
> + struct pam_cap_s *pcs = (struct pam_cap_s *) data;
> +
> + if (pcs) {
> + if (pcs->result)
> + cap_free(pcs->result);
> + free(pcs);
> }
> }
>
> int pam_sm_authenticate(pam_handle_t *pamh, int flags,
> int argc, const char **argv)
> {
> - int retval;
> - struct pam_cap_s pcs;
> - char *conf_icaps;
> + struct pam_cap_s *pcs = NULL;
> + int rc = PAM_BUF_ERR;
>
> - memset(&pcs, 0, sizeof(pcs));
> + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);
>
> - parse_args(argc, argv, &pcs);
> + pcs = malloc(sizeof(struct pam_cap_s));
> + if (!pcs)
> + goto error;
>
> - retval = pam_get_user(pamh, &pcs.user, NULL);
> + rc = init_pam_cap(pamh, argc, argv, pcs);
> + if (rc != PAM_SUCCESS)
> + goto error;
>
> - if (retval == PAM_CONV_AGAIN) {
> - D(("user conversation is not available yet"));
> - memset(&pcs, 0, sizeof(pcs));
> - return PAM_INCOMPLETE;
> - }
> + rc = read_capabilities_for_user(pcs);
> + if (rc != PAM_SUCCESS)
> + goto error;
>
> - if (retval != PAM_SUCCESS) {
> - D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
> - memset(&pcs, 0, sizeof(pcs));
> - return PAM_AUTH_ERR;
> + rc = pam_set_data(pamh, MODULE_NAME, pcs, cleanup_pam_cap);
> + if (rc == PAM_SUCCESS) {
> + /* OK, pam_sm_setcred() will be called next */
> + closelog();
> + return rc;
> }
>
> - conf_icaps =
> - read_capabilities_for_user(pcs.user,
> - pcs.conf_filename
> - ? pcs.conf_filename:USER_CAP_FILE );
> -
> - memset(&pcs, 0, sizeof(pcs));
> -
> - if (conf_icaps) {
> - D(("it appears that there are capabilities for this user [%s]",
> - conf_icaps));
> + error:
> + cleanup_pam_cap(pamh, pcs, rc);
> + closelog();
> + return rc < 0 ? PAM_AUTH_ERR : rc;
> +}
>
> - /* We could also store this as a pam_[gs]et_data item for use
> - by the setcred call to follow. As it is, there is a small
> - race associated with a redundant read. Oh well, if you
> - care, send me a patch.. */
> +int pam_sm_setcred(pam_handle_t *pamh, int flags,
> + int argc, const char **argv)
> +{
> + struct pam_cap_s *pcs = NULL;
> + int rc = PAM_IGNORE;
>
> - _pam_overwrite(conf_icaps);
> - _pam_drop(conf_icaps);
> + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);
>
> - return PAM_SUCCESS;
> + if (!(flags & PAM_ESTABLISH_CRED))
> + goto out;
>
> - } else {
> + rc = pam_get_data(pamh, MODULE_NAME, (void *)&pcs);
> + if (rc != PAM_SUCCESS)
> + return rc;
>
> - D(("there are no capabilities restrctions on this user"));
> - return PAM_IGNORE;
> + rc = set_capabilities(pcs);
>
> - }
> + out:
> + closelog();
> + return rc < 0 ? PAM_CRED_ERR : rc;
> }
>
> -int pam_sm_setcred(pam_handle_t *pamh, int flags,
> - int argc, const char **argv)
> +int pam_sm_open_session(pam_handle_t *pamh, int flags,
> + int argc, const char **argv)
> {
> - int retval;
> struct pam_cap_s pcs;
> + int rc;
>
> - if (!(flags & PAM_ESTABLISH_CRED)) {
> - D(("we don't handle much in the way of credentials"));
> - return PAM_IGNORE;
> - }
> + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);
>
> - memset(&pcs, 0, sizeof(pcs));
> + rc = init_pam_cap(pamh, argc, argv, &pcs);
> + if (rc != PAM_SUCCESS)
> + goto out;
>
> - parse_args(argc, argv, &pcs);
> + rc = read_capabilities_for_user(&pcs);
> + if (rc != PAM_SUCCESS)
> + goto out;
>
> - retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
> - if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
> + rc = set_capabilities(&pcs);
>
> - D(("user's name is not set"));
> - return PAM_AUTH_ERR;
> + if (rc == PAM_SUCCESS) {
> + rc = set_capabilities(&pcs);
> + if (pcs.result)
> + cap_free(pcs.result);
> }
>
> - retval = set_capabilities(&pcs);
> + out:
> + if (pcs.result)
> + cap_free(pcs.result);
> + closelog();
>
> - memset(&pcs, 0, sizeof(pcs));
> + return rc < 0 ? PAM_SESSION_ERR : rc;
> +}
>
> - return (retval ? PAM_SUCCESS:PAM_IGNORE );
> +int pam_sm_close_session(pam_handle_t *pamh, int flags,
> + int argc, const char **argv)
> +{
> + return PAM_SUCCESS; /* do nothing */
> }
>
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHWOSqmwytjiwfWMwRAnSIAJ0ea1HisHTLBfeApmdoHx+aSRbQ9wCbBC9C
I8mLshEVleoPG9OkJVUHTo0=
=WZO6
-----END PGP SIGNATURE-----