2006-05-01 13:45:47

by Jan Engelhardt

[permalink] [raw]
Subject: [PATCH 0/4] MultiAdmin LSM

Subject: [PATCH 0/4] MultiAdmin LSM
(was: Re: Time to remove LSM
(was: Re: [RESEND][RFC][PATCH 2/7] implementation of LSM hooks))


0. Preface
==========
Thanks to Greg who, requiring me to post more-split patches, made me
reconsider the code. I did nothing less than to simplified the whole patch
cruft (shrunk by factor 10) and removed what seemed unreasonable. This
thread posts MultiAdmin *1.0.5*.



1. Super-short description
==========================
Three user classes exist (determined by user-defined UID ranges),
- superadmin, the usual "root"
- subadmin
- normal users

A usual (non-multiadm,non-selinux) system has only one superadmin (UID 0)
and a number of normal users, and the superadmin can operate on
everything.

The "subadmin" can read in some superadmin-only places, and is allowed to
fully operate on processes/files/ipc/etc. of normal users. The full list
(possibly incomplete) of permissions is available in the README.txt
(includes short description) in the out-of-tree tarball.
[http://freshmeat.net/p/multiadm/]



2. A small problem
==================
As cool as it may sound, I think the implementation is not as clean as
possible.

Let's pick a random starting point: The subadmin is allowed to call
drivers/char/lp.c:lp_ioctl():LPGETSTATS. Or
fs/quota.c:generic_quotactl_valid():Q_GET*/Q_XGET*. For that to work
without too much code changes, CAP_SYS_ADMIN must be given to the
subadmin.

However, CAP_SYS_ADMIN (others are affected too, but this is the main one)
is used for other things too (mostly write or ioctl operations), which is
actually something that should not be granted to the subadmin.

This poses a problem. Currently, it is solved by adding an extra LSM hook,
security_cap_extra(), called from capable(). The hooked function then
looks at current->*uid/*gid and returns 1 or 0, depending on whether an
action is allowed or not. For more details see patch #1.



3. Conclusion
=============
You might have noticed: MultiAdmin's concept is based on UIDs/GIDs, not
capabilities. This interferes with the capability framework, which is
currently... hardcoded.

At best I would want that capabilities get out of the core functions (e.g.
{kernel,fs,etc}/*.c) and have them get their place in security/*, so that
in case you load a security module that is not based around POSIX
capabilities, they don't get used. I will see if I can put this idea into
a working idea. But for now...

...the multiadm patch series, based upon giving CAP_almost_anything
to subadmin and 'reducing' permissions within the LSM.



4. Patches
==========
Compile-tested (defconfig + enabling SECURIY,SECURITY_NETWORK).
A small overview for pleasure:

[01] security_cap_extra() and more
[02] Use of capable_light()
[03] task_post_setgid()
[04] MultiAdmin module


Jan Engelhardt
--


2006-05-01 13:49:12

by Jan Engelhardt

[permalink] [raw]
Subject: [PATCH 1/4] security_cap_extra() and more


[PATCH 1/4] security_cap_extra() and more

- Renames capable() to capable_light().
This function is used if only a capability is to be checked.

- Implement a new capable that calls security_cap_extra().
Since a subadmin has almost the same capabilities as a
superadmin, an extra helper is needed to decide whether an
action is allowed, based on the philosophy of the LSM.

- implement the .cap_extra LSM hook


Signed-off-by: Jan Engelhardt <[email protected]>

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/include/linux/capability.h linux-2.6.17-rc3+/include/linux/capability.h
--- linux-2.6.17-rc3~/include/linux/capability.h 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/include/linux/capability.h 2006-04-30 23:25:25.233048000 +0200
@@ -357,6 +357,8 @@ static inline kernel_cap_t cap_invert(ke

#define cap_is_fs_cap(c) (CAP_TO_MASK(c) & CAP_FS_MASK)

+int capable_light(int);
+int __capable_light(struct task_struct *, int);
int capable(int cap);
int __capable(struct task_struct *t, int cap);

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/include/linux/security.h linux-2.6.17-rc3+/include/linux/security.h
--- linux-2.6.17-rc3~/include/linux/security.h 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/include/linux/security.h 2006-04-30 23:25:35.893048000 +0200
@@ -1319,6 +1319,7 @@ struct security_operations {

#endif /* CONFIG_KEYS */

+ int (*cap_extra)(int);
};

/* global variables */
@@ -2018,6 +2019,11 @@ static inline int security_netlink_recv(
return security_ops->netlink_recv(skb);
}

+static inline int security_cap_extra(int cap)
+{
+ return security_ops->cap_extra(cap);
+}
+
/* prototypes */
extern int security_init (void);
extern int register_security (struct security_operations *ops);
@@ -2651,6 +2657,12 @@ static inline int security_netlink_recv
return cap_netlink_recv (skb);
}

+static inline int security_cap_extra(int cap);
+{
+ /* Capability test already passed. No more checks. => Allow. */
+ return 1;
+}
+
static inline struct dentry *securityfs_create_dir(const char *name,
struct dentry *parent)
{
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/kernel/capability.c linux-2.6.17-rc3+/kernel/capability.c
--- linux-2.6.17-rc3~/kernel/capability.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/kernel/capability.c 2006-04-30 23:30:06.143048000 +0200
@@ -238,7 +238,7 @@ int __capable(struct task_struct *t, int
{
if (security_capable(t, cap) == 0) {
t->flags |= PF_SUPERPRIV;
- return 1;
+ return security_cap_extra(cap);
}
return 0;
}
@@ -249,3 +249,20 @@ int capable(int cap)
return __capable(current, cap);
}
EXPORT_SYMBOL(capable);
+
+int __capable_light(struct task_struct *t, int cap)
+{
+ if (security_capable(t, cap) == 0) {
+ t->flags |= PF_SUPERPRIV;
+ return 1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(__capable_light);
+
+int capable_light(int cap)
+{
+ return __capable_light(current, cap);
+}
+EXPORT_SYMBOL(capable_light);
+
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/dummy.c linux-2.6.17-rc3+/security/dummy.c
--- linux-2.6.17-rc3~/security/dummy.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/security/dummy.c 2006-04-30 23:30:24.763048000 +0200
@@ -677,6 +677,11 @@ static int dummy_netlink_recv (struct sk
return 0;
}

+static int dummy_cap_extra(int cap)
+{
+ return 1; /* allow */
+}
+
#ifdef CONFIG_SECURITY_NETWORK
static int dummy_unix_stream_connect (struct socket *sock,
struct socket *other,
@@ -1040,5 +1045,6 @@ void security_fixup_ops (struct security
set_to_dummy_if_null(ops, key_permission);
#endif /* CONFIG_KEYS */

+ set_to_dummy_if_null(ops, cap_extra);
}

#<<eof>>


Jan Engelhardt
--

2006-05-01 13:49:42

by Jan Engelhardt

[permalink] [raw]
Subject: [PATCH 2/4] Use of capable_light()


[PATCH 2/4] Use of capable_light()

capable() now behaves like (capable_light() && is_superadm). Since some
operations are allowed by subadmins too, it suffices to use
capable_light().


Signed-off-by: Jan Engelhardt <[email protected]>

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/arch/alpha/kernel/pci-noop.c linux-2.6.17-rc3+/arch/alpha/kernel/pci-noop.c
--- linux-2.6.17-rc3~/arch/alpha/kernel/pci-noop.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/arch/alpha/kernel/pci-noop.c 2006-04-30 22:05:33.263048000 +0200
@@ -89,7 +89,7 @@ asmlinkage long
sys_pciconfig_read(unsigned long bus, unsigned long dfn,
unsigned long off, unsigned long len, void *buf)
{
- if (!capable(CAP_SYS_ADMIN))
+ if (!capable_light(CAP_SYS_ADMIN))
return -EPERM;
else
return -ENODEV;
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/drivers/char/lp.c linux-2.6.17-rc3+/drivers/char/lp.c
--- linux-2.6.17-rc3~/drivers/char/lp.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/drivers/char/lp.c 2006-04-30 22:28:53.433048000 +0200
@@ -633,7 +633,7 @@ static int lp_ioctl(struct inode *inode,
if (copy_to_user(argp, &LP_STAT(minor),
sizeof(struct lp_stats)))
return -EFAULT;
- if (capable(CAP_SYS_ADMIN))
+ if (capable_light(CAP_SYS_ADMIN))
memset(&LP_STAT(minor), 0,
sizeof(struct lp_stats));
break;
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/drivers/firmware/efivars.c linux-2.6.17-rc3+/drivers/firmware/efivars.c
--- linux-2.6.17-rc3~/drivers/firmware/efivars.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/drivers/firmware/efivars.c 2006-04-30 22:29:38.913048000 +0200
@@ -354,7 +354,7 @@ static ssize_t efivar_attr_show(struct k
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
ssize_t ret = -EIO;

- if (!capable(CAP_SYS_ADMIN))
+ if (!capable_light(CAP_SYS_ADMIN))
return -EACCES;

if (efivar_attr->show) {
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/drivers/pci/pci-sysfs.c linux-2.6.17-rc3+/drivers/pci/pci-sysfs.c
--- linux-2.6.17-rc3~/drivers/pci/pci-sysfs.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/drivers/pci/pci-sysfs.c 2006-04-30 22:31:33.873048000 +0200
@@ -113,7 +113,7 @@ pci_read_config(struct kobject *kobj, ch
u8 *data = (u8*) buf;

/* Several chips lock up trying to read undefined config space */
- if (capable(CAP_SYS_ADMIN)) {
+ if (capable_light(CAP_SYS_ADMIN)) {
size = dev->cfg_size;
} else if (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) {
size = 128;
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/drivers/pci/proc.c linux-2.6.17-rc3+/drivers/pci/proc.c
--- linux-2.6.17-rc3~/drivers/pci/proc.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/drivers/pci/proc.c 2006-04-30 22:31:42.213048000 +0200
@@ -60,7 +60,7 @@ proc_bus_pci_read(struct file *file, cha
* undefined locations (think of Intel PIIX4 as a typical example).
*/

- if (capable(CAP_SYS_ADMIN))
+ if (capable_light(CAP_SYS_ADMIN))
size = dev->cfg_size;
else if (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
size = 128;
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/drivers/pci/syscall.c linux-2.6.17-rc3+/drivers/pci/syscall.c
--- linux-2.6.17-rc3~/drivers/pci/syscall.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/drivers/pci/syscall.c 2006-04-30 22:31:51.863048000 +0200
@@ -27,7 +27,7 @@ sys_pciconfig_read(unsigned long bus, un
long err, cfg_ret;

err = -EPERM;
- if (!capable(CAP_SYS_ADMIN))
+ if (!capable_light(CAP_SYS_ADMIN))
goto error;

err = -ENODEV;
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/fs/quota.c linux-2.6.17-rc3+/fs/quota.c
--- linux-2.6.17-rc3~/fs/quota.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/fs/quota.c 2006-04-30 22:40:03.483048000 +0200
@@ -81,11 +81,11 @@ static int generic_quotactl_valid(struct
if (cmd == Q_GETQUOTA) {
if (((type == USRQUOTA && current->euid != id) ||
(type == GRPQUOTA && !in_egroup_p(id))) &&
- !capable(CAP_SYS_ADMIN))
+ !capable_light(CAP_SYS_ADMIN))
return -EPERM;
}
else if (cmd != Q_GETFMT && cmd != Q_SYNC && cmd != Q_GETINFO)
- if (!capable(CAP_SYS_ADMIN))
+ if (!capable_light(CAP_SYS_ADMIN))
return -EPERM;

return 0;
@@ -132,10 +132,10 @@ static int xqm_quotactl_valid(struct sup
if (cmd == Q_XGETQUOTA) {
if (((type == XQM_USRQUOTA && current->euid != id) ||
(type == XQM_GRPQUOTA && !in_egroup_p(id))) &&
- !capable(CAP_SYS_ADMIN))
+ !capable_light(CAP_SYS_ADMIN))
return -EPERM;
} else if (cmd != Q_XGETQSTAT && cmd != Q_XQUOTASYNC) {
- if (!capable(CAP_SYS_ADMIN))
+ if (!capable_light(CAP_SYS_ADMIN))
return -EPERM;
}

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/ipc/msg.c linux-2.6.17-rc3+/ipc/msg.c
--- linux-2.6.17-rc3~/ipc/msg.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/ipc/msg.c 2006-04-30 22:52:53.383048000 +0200
@@ -449,7 +449,7 @@ asmlinkage long sys_msgctl (int msqid, i
ipcp = &msq->q_perm;
err = -EPERM;
if (current->euid != ipcp->cuid &&
- current->euid != ipcp->uid && !capable(CAP_SYS_ADMIN))
+ current->euid != ipcp->uid && !capable_light(CAP_SYS_ADMIN))
/* We _could_ check for CAP_CHOWN above, but we don't */
goto out_unlock_up;

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/ipc/sem.c linux-2.6.17-rc3+/ipc/sem.c
--- linux-2.6.17-rc3~/ipc/sem.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/ipc/sem.c 2006-04-30 22:54:15.703048000 +0200
@@ -821,7 +821,7 @@ static int semctl_down(int semid, int se
}
ipcp = &sma->sem_perm;
if (current->euid != ipcp->cuid &&
- current->euid != ipcp->uid && !capable(CAP_SYS_ADMIN)) {
+ current->euid != ipcp->uid && !capable_light(CAP_SYS_ADMIN)) {
err=-EPERM;
goto out_unlock;
}
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/ipc/shm.c linux-2.6.17-rc3+/ipc/shm.c
--- linux-2.6.17-rc3~/ipc/shm.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/ipc/shm.c 2006-04-30 22:55:10.413048000 +0200
@@ -596,7 +596,7 @@ asmlinkage long sys_shmctl (int shmid, i

if (current->euid != shp->shm_perm.uid &&
current->euid != shp->shm_perm.cuid &&
- !capable(CAP_SYS_ADMIN)) {
+ !capable_light(CAP_SYS_ADMIN)) {
err=-EPERM;
goto out_unlock_up;
}
@@ -636,7 +636,7 @@ asmlinkage long sys_shmctl (int shmid, i
err=-EPERM;
if (current->euid != shp->shm_perm.uid &&
current->euid != shp->shm_perm.cuid &&
- !capable(CAP_SYS_ADMIN)) {
+ !capable_light(CAP_SYS_ADMIN)) {
goto out_unlock_up;
}

#<<eof>>


Jan Engelhardt
--

2006-05-01 13:49:59

by Jan Engelhardt

[permalink] [raw]
Subject: [PATCH 3/4] task_post_setgid()


[PATCH 3/4] task_post_setgid()

- Implement the task_post_setgid() LSM hook which is required by
MultiAdmin to switch between classes.
(task_post_setuid also switches between classes -- and already exists)


Signed-off-by: Jan Engelhardt <[email protected]>

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/include/linux/security.h linux-2.6.17-rc3+/include/linux/security.h
--- linux-2.6.17-rc3~/include/linux/security.h 2006-05-01 09:57:13.112832000 +0200
+++ linux-2.6.17-rc3+/include/linux/security.h 2006-05-01 09:57:38.182832000 +0200
@@ -1320,6 +1320,7 @@ struct security_operations {
#endif /* CONFIG_KEYS */

int (*cap_extra)(int);
+ int (*task_post_setgid)(gid_t, gid_t, gid_t, int);
};

/* global variables */
@@ -2024,6 +2025,11 @@ static inline int security_cap_extra(int
return security_ops->cap_extra(cap);
}

+static inline int security_task_post_setgid(gid_t r, gid_t e, gid_t s, int t)
+{
+ return security_ops->task_post_setgid(r, e, s, t);
+}
+
/* prototypes */
extern int security_init (void);
extern int register_security (struct security_operations *ops);
@@ -2663,6 +2669,11 @@ static inline int security_cap_extra(int
return 1;
}

+static inline int security_task_post_setgid(gid_t r, gid_t e, gid_t s, int t)
+{
+ return 0;
+}
+
static inline struct dentry *securityfs_create_dir(const char *name,
struct dentry *parent)
{
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/kernel/sys.c linux-2.6.17-rc3+/kernel/sys.c
--- linux-2.6.17-rc3~/kernel/sys.c 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/kernel/sys.c 2006-05-01 09:57:38.192832000 +0200
@@ -836,7 +836,7 @@ asmlinkage long sys_setregid(gid_t rgid,
current->gid = new_rgid;
key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
- return 0;
+ return security_task_post_setgid(old_rgid, old_egid, (gid_t)-1, LSM_SETID_RE);
}

/*
@@ -846,6 +846,7 @@ asmlinkage long sys_setregid(gid_t rgid,
*/
asmlinkage long sys_setgid(gid_t gid)
{
+ gid_t old_rgid = current->gid;
int old_egid = current->egid;
int retval;

@@ -876,7 +877,7 @@ asmlinkage long sys_setgid(gid_t gid)

key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
- return 0;
+ return security_task_post_setgid(old_rgid, (gid_t)-1, (gid_t)-1, LSM_SETID_ID);
}

static int set_user(uid_t new_ruid, int dumpclear)
@@ -1083,6 +1084,8 @@ asmlinkage long sys_getresuid(uid_t __us
*/
asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
{
+ gid_t old_rgid = current->gid, old_egid = current->egid,
+ old_sgid = current->sgid;
int retval;

retval = security_task_setgid(rgid, egid, sgid, LSM_SETID_RES);
@@ -1116,7 +1119,7 @@ asmlinkage long sys_setresgid(gid_t rgid

key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
- return 0;
+ return security_task_post_setgid(old_rgid, old_egid, old_sgid, LSM_SETID_RES);
}

asmlinkage long sys_getresgid(gid_t __user *rgid, gid_t __user *egid, gid_t __user *sgid)
@@ -1189,6 +1192,7 @@ asmlinkage long sys_setfsgid(gid_t gid)
key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
}
+ security_task_post_setgid(old_fsgid, (gid_t)-1, (gid_t)-1, LSM_SETID_FS);
return old_fsgid;
}

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/dummy.c linux-2.6.17-rc3+/security/dummy.c
--- linux-2.6.17-rc3~/security/dummy.c 2006-05-01 09:57:13.112832000 +0200
+++ linux-2.6.17-rc3+/security/dummy.c 2006-05-01 09:57:38.192832000 +0200
@@ -682,6 +682,11 @@ static int dummy_cap_extra(int cap)
return 1; /* allow */
}

+static int dummy_task_post_setgid(gid_t r, gid_t e, gid_t s, int t)
+{
+ return 0;
+}
+
#ifdef CONFIG_SECURITY_NETWORK
static int dummy_unix_stream_connect (struct socket *sock,
struct socket *other,
@@ -1046,5 +1051,6 @@ void security_fixup_ops (struct security
#endif /* CONFIG_KEYS */

set_to_dummy_if_null(ops, cap_extra);
+ set_to_dummy_if_null(ops, task_post_setgid);
}

#<<eof>>


Jan Engelhardt
--

2006-05-01 13:50:48

by Jan Engelhardt

[permalink] [raw]
Subject: [PATCH 4/4] MultiAdmin module


[PATCH 4/4] MultiAdmin module

- Add the MultiAdmin to the mainline tree.
I hope the rest is self-explanatory.

Please do not mention CodingStyle for multiadm.c. I already know it. :)
And I will get to it should it really be merged.


Signed-off-by: Jan Engelhardt <[email protected]>

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/Kconfig linux-2.6.17-rc3+/security/Kconfig
--- linux-2.6.17-rc3~/security/Kconfig 2006-05-01 12:47:01.382832000 +0200
+++ linux-2.6.17-rc3+/security/Kconfig 2006-05-01 15:04:08.482832000 +0200
@@ -99,6 +99,22 @@ config SECURITY_SECLVL

If you are unsure how to answer this question, answer N.

+config SECURITY_MULTIADM
+ tristate "MultiAdmin secuirty module"
+ depends on SECURITY
+ ---help---
+ The MultiAdmin security kernel module provides means to have multiple
+ "root" users with unique UIDs. This fixes collation order problems
+ which for example appear with NSCD, allows to have files with
+ determinable owner and allows to track the quota usage for every
+ user, since they now have a unique uid.
+
+ It also implements a "sub-admin", a partially restricted root user
+ (or enhanced normal user, depending on the way you see it), who has
+ full read-only access to most subsystems, and additional write rights
+ only to a limited subset, e.g. writing to files or killing processes
+ only of certain users.
+
source security/selinux/Kconfig

endmenu
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/Makefile linux-2.6.17-rc3+/security/Makefile
--- linux-2.6.17-rc3~/security/Makefile 2006-05-01 12:47:01.382832000 +0200
+++ linux-2.6.17-rc3+/security/Makefile 2006-05-01 15:04:08.482832000 +0200
@@ -17,3 +17,4 @@ obj-$(CONFIG_SECURITY_SELINUX) += selin
obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o
obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o
obj-$(CONFIG_SECURITY_SECLVL) += seclvl.o
+obj-$(CONFIG_SECURITY_MULTIADM) += commoncap.o multiadm.o
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/loadme linux-2.6.17-rc3+/security/loadme
--- linux-2.6.17-rc3~/security/loadme 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.17-rc3+/security/loadme 2006-05-01 15:16:27.662832000 +0200
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+rmmod multiadm;
+modprobe commoncap;
+insmod ./multiadm.ko Supergid=0 Subuid_start=4000 \
+ Subuid_end=4000 Wrtuid_start=4003 Wrtuid_end=4004;
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/multiadm.c linux-2.6.17-rc3+/security/multiadm.c
--- linux-2.6.17-rc3~/security/multiadm.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.17-rc3+/security/multiadm.c 2006-05-01 15:39:13.042832000 +0200
@@ -0,0 +1,618 @@
+/*=============================================================================
+| MultiAdmin Security Module |
+| Copyright © Jan Engelhardt <jengelh [at] gmx de>, 2005 - 2006 |
+| v1.0.5, May 2006 |
+| http://alphagate.hopto.org/ |
+`-----------------------------------------------------------------------------'
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2 as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program kit; if not, write to:
+ Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ Boston, MA 02110-1301 USA
+=============================================================================*/
+#include <asm/siginfo.h>
+#include <linux/binfmts.h>
+#include <linux/capability.h>
+#include <linux/config.h>
+#include <linux/dcache.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ipc.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/namei.h>
+#include <linux/sched.h>
+#include <linux/securebits.h>
+#include <linux/security.h>
+#include <linux/sem.h>
+#include <linux/types.h>
+
+#if !defined(CONFIG_SECURITY_CAPABILITIES) && \
+ !defined(CONFIG_SECURITY_CAPABILITIES_MODULE)
+# error You need to have CONFIG_SECURITY_CAPABILITIES=y or =m \
+ for MultiAdmin to compile successfully.
+#endif
+
+#define BASENAME "multiadm"
+#define PREFIX BASENAME ": "
+
+/*
+This typedef is used to mark functions a littile without interfering
+with the ABI:
+ bool for 0=failure, !0=success
+ int for 0=success, <0=failure, >0=failure or some val
+ int for 0=eof, <0=failure, >0=value
+*/
+typedef int bool;
+
+static int mt_bprm_set_security(struct linux_binprm *);
+static int mt_cap_extra(int);
+static int mt_inode_permission(struct inode *, int, struct nameidata *);
+static int mt_inode_setattr(struct dentry *, struct iattr *);
+static int mt_ipc_permission(struct kern_ipc_perm *, short);
+static int mt_msq_msgctl(struct msg_queue *, int);
+static int mt_ptrace(task_t *, task_t *);
+static int mt_quotactl(int, int, int, struct super_block *);
+static int mt_sem_semctl(struct sem_array *, int);
+static int mt_shm_shmctl(struct shmid_kernel *, int);
+static int mt_task_kill(task_t *, struct siginfo *, int);
+static int mt_task_post_setuid(uid_t, uid_t, uid_t, int);
+static int mt_task_post_setgid(gid_t, gid_t, gid_t, int);
+static int mt_task_setnice(task_t *, int);
+static int mt_task_setscheduler(task_t *, int, struct sched_param *);
+static int mt_task_setuid(uid_t, uid_t, uid_t, int);
+
+static inline void chg2_superadm(kernel_cap_t *);
+static inline void chg2_subadm(kernel_cap_t *);
+static inline void chg2_netadm(kernel_cap_t *);
+static inline bool is_any_superadm(uid_t, gid_t);
+static inline bool is_uid_superadm(uid_t);
+static inline bool is_gid_superadm(gid_t);
+static inline bool is_any_subadm(uid_t, gid_t);
+static inline bool is_uid_subadm(uid_t);
+static inline bool is_gid_subadm(gid_t);
+static inline bool is_uid_netadm(uid_t);
+static inline bool is_uid_user(uid_t);
+static inline bool is_task1_user(const task_t *);
+static inline bool is_task_user(const task_t *);
+static inline bool range_intersect(uid_t, uid_t, uid_t, uid_t);
+static inline bool range_intersect_wrt(uid_t, uid_t, uid_t, uid_t);
+
+static struct security_operations mt_secops = {
+ .bprm_apply_creds = cap_bprm_apply_creds,
+ .bprm_set_security = mt_bprm_set_security,
+ .cap_extra = mt_cap_extra,
+ .capable = cap_capable,
+ .capget = cap_capget,
+ .capset_check = cap_capset_check,
+ .capset_set = cap_capset_set,
+ .inode_permission = mt_inode_permission,
+ .inode_setattr = mt_inode_setattr,
+ .ipc_permission = mt_ipc_permission,
+ .msg_queue_msgctl = mt_msq_msgctl,
+ .ptrace = mt_ptrace,
+ .quotactl = mt_quotactl,
+ .sem_semctl = mt_sem_semctl,
+ .shm_shmctl = mt_shm_shmctl,
+ .task_kill = mt_task_kill,
+ .task_post_setuid = mt_task_post_setuid,
+ .task_post_setgid = mt_task_post_setgid,
+ .task_setnice = mt_task_setnice,
+ .task_setscheduler = mt_task_setscheduler,
+ .task_setuid = mt_task_setuid,
+};
+static gid_t Supergid = -1, Subgid = -1;
+static uid_t Superuid_start = 0, Superuid_end = 0,
+ Subuid_start = -1, Subuid_end = -1,
+ Netuid = -1,
+ Wrtuid_start = -1, Wrtuid_end = -1;
+static int Secondary = 0;
+
+MODULE_DESCRIPTION("MultiAdmin Security Module; http://alphagate.hopto.org/");
+MODULE_AUTHOR("Jan Engelhardt <jengelh [at] gmx de>");
+MODULE_LICENSE("GPL");
+module_param(Supergid, int, S_IRUSR | S_IWUSR);
+module_param(Superuid_start, int, S_IRUSR | S_IWUSR);
+module_param(Superuid_end, int, S_IRUSR | S_IWUSR);
+module_param(Subuid_start, int, S_IRUSR | S_IWUSR);
+module_param(Subuid_end, int, S_IRUSR | S_IWUSR);
+module_param(Subgid, int, S_IRUSR | S_IWUSR);
+module_param(Netuid, int, S_IRUSR | S_IWUSR);
+module_param(Wrtuid_start, int, S_IRUGO | S_IWUSR);
+module_param(Wrtuid_end, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(Wrtuid_start, "First UID of the write-enabled user range");
+MODULE_PARM_DESC(Wrtuid_end, "Last UID of the write-enabled user range");
+MODULE_PARM_DESC(Superuid_start, "First UIDs of the superadmin range");
+MODULE_PARM_DESC(Superuid_end, "Last UID of the superadmin range");
+MODULE_PARM_DESC(Supergid, "Superadmin GID");
+MODULE_PARM_DESC(Subuid_start, "First UIDs of the subadmin range");
+MODULE_PARM_DESC(Subuid_end, "Last UID of the subadmin range");
+MODULE_PARM_DESC(Subgid, "Subadmin GID");
+MODULE_PARM_DESC(Netuid, "Netadmin UID");
+
+//-----------------------------------------------------------------------------
+__init static int multiadm_init(void) {
+ int eax = 0, ebx = 0;
+ if((eax = register_security(&mt_secops)) != 0) {
+ if((ebx = mod_reg_security(BASENAME, &mt_secops)) != 0) {
+ printk(KERN_WARNING PREFIX
+ "Could not register with kernel: %d, %d\n", eax, ebx);
+ return ebx;
+ }
+ Secondary = 1;
+ }
+
+ if(range_intersect(Superuid_start, Superuid_end, Subuid_start, Subuid_end))
+ printk(KERN_WARNING PREFIX
+ "Superadmin and Subadmin ranges intersect! Unpredictable behavior"
+ " may result: some operations may classify you as a superadmin,"
+ " others as a subadmin. Security leak: subadmin could possibly"
+ " change into superadmin!\n"
+ );
+ if(range_intersect(Superuid_start, Superuid_end, Netuid, Netuid))
+ printk(KERN_WARNING PREFIX "Netuid within superadmin range! -Has more "
+ "powers than intended!\n");
+ if(range_intersect(Superuid_start, Superuid_end, Wrtuid_start, Wrtuid_end))
+ printk(KERN_WARNING PREFIX "Superadmin and write-enabled user range "
+ "intersect! A subadmin could setuid() into a superadmin!\n");
+ if(range_intersect(Subuid_start, Subuid_end, Netuid, Netuid))
+ printk(KERN_WARNING PREFIX "Netuid within subadmin range! -Has more "
+ "powers than intended!\n");
+ if(range_intersect_wrt(Subuid_start, Subuid_end, Wrtuid_start, Wrtuid_end))
+ printk(KERN_WARNING PREFIX "Subadmin and write-enabled user range "
+ "intersect! Subadmins are able to poke on other subadmins!\n");
+ if(range_intersect_wrt(Netuid, Netuid, Wrtuid_start, Wrtuid_end))
+ printk(KERN_WARNING PREFIX "Netuid within write-enabled user range! "
+ "Subadmin may gain CAP_NET_ADMIN!\n");
+ printk(KERN_INFO "MultiAdmin loaded\n");
+ return 0;
+}
+
+__exit static void multiadm_exit(void) {
+ int ret = 0;
+
+ if(Secondary)
+ ret = mod_unreg_security(BASENAME, &mt_secops);
+ else
+ ret = unregister_security(&mt_secops);
+
+ if(ret != 0)
+ printk(KERN_WARNING PREFIX
+ "Could not unregister with kernel: %d\n", ret);
+
+ return;
+}
+
+module_init(multiadm_init);
+module_exit(multiadm_exit);
+
+//-----------------------------------------------------------------------------
+static int mt_bprm_set_security(struct linux_binprm *bp) {
+ /* In the function chain of exec(), we eventually get here, which is the
+ place to set up new privileges. */
+ cap_bprm_set_security(bp);
+
+ /* All of the following is nicely inlined. The capability raising is
+ resolved to only one instruction for each set. */
+ if(is_any_superadm(bp->e_uid, bp->e_gid)) {
+ chg2_superadm(&bp->cap_permitted);
+ chg2_superadm(&bp->cap_effective);
+ } else if(is_any_superadm(current->uid, current->gid)) {
+ chg2_superadm(&bp->cap_permitted);
+ } else if(is_any_subadm(bp->e_uid, bp->e_gid)) {
+ chg2_subadm(&bp->cap_permitted);
+ chg2_subadm(&bp->cap_effective);
+ } else if(is_any_subadm(current->uid, current->gid)) {
+ chg2_subadm(&bp->cap_permitted);
+ } else if(is_uid_netadm(bp->e_uid)) {
+ chg2_netadm(&bp->cap_permitted);
+ chg2_netadm(&bp->cap_effective);
+ } else if(is_uid_netadm(current->uid)) {
+ chg2_netadm(&bp->cap_permitted);
+ }
+ return 0;
+}
+
+static int mt_cap_extra(int capability) {
+ if(capability == CAP_SYS_ADMIN)
+ /* Subadmin also has CAP_SYS_ADMIN, but if we get here, we did so
+ by capable() -- not capable_light(). */
+ return is_any_superadm(current->euid, current->egid);
+ else
+ /* Subadmin/Netadmin also has other capabilities, but they
+ are -- I hope -- ok. */
+ return 1;
+}
+
+static int mt_inode_permission(struct inode *inode, int mask,
+ struct nameidata *nd)
+{
+ /* Check for superadmin is not done, since the only users that can get
+ here is either superadmin or subadmin. By omitting the check for
+ superadmin, only two comparisons need to be done for the subadmin case.
+ This method is done almost throughout the entire module. */
+
+ if(is_any_subadm(current->euid, current->egid) && (mask & MAY_WRITE)) {
+ int ret;
+ if(inode->i_uid == current->fsuid || is_uid_user(inode->i_uid))
+ return 0;
+
+ /* Since we practically jumped over the checks to get here (because of
+ CAP_DAC_OVERRIDE), we need to do it again. Without CAP_DAC_OVERRIDE
+ this time. Temporarily drop it. */
+ cap_lower(current->cap_effective, CAP_DAC_OVERRIDE);
+
+ // Copied from fs/namei.c
+ if(inode->i_op != NULL && inode->i_op->permission != NULL)
+ ret = inode->i_op->permission(inode, mask & ~MAY_APPEND, nd);
+ else
+ ret = generic_permission(inode, mask & ~MAY_APPEND, NULL);
+
+ cap_raise(current->cap_effective, CAP_DAC_OVERRIDE);
+ return ret;
+ }
+ return 0;
+}
+
+static int mt_inode_setattr(struct dentry *dentry, struct iattr *attr) {
+ if(is_any_subadm(current->euid, current->egid)) {
+ /* Change is only allowed if either the inode belongs to us, or does
+ belond, _and_ will belong in case of ATTR_UID, to a WRT user. */
+ const struct inode *inode = dentry->d_inode;
+ if(inode->i_uid != current->fsuid && !is_uid_user(inode->i_uid))
+ return -EPERM;
+
+ if((attr->ia_valid & ATTR_UID) && attr->ia_uid != current->fsuid &&
+ !is_uid_user(attr->ia_uid))
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_ipc_permission(struct kern_ipc_perm *perm, short flag) {
+ if(is_any_subadm(current->euid, current->egid)) {
+ int req, grant;
+
+ if(perm->uid == current->euid || perm->cuid == current->euid ||
+ is_uid_user(perm->uid) || is_uid_user(perm->cuid))
+ return 0;
+
+ /* Copied and modified from ipc/util.c. Subadmin always has read
+ permission so add S_IRUGO to granted. Checking the owner permission
+ part is not done anymore, because it is done above. */
+ req = (flag >> 6) | (flag >> 3) | flag;
+ grant = (perm->mode | S_IRUGO) >> 3;
+ if(in_group_p(perm->gid) || in_group_p(perm->cgid))
+ grant >>= 3;
+ if(req & ~grant & 0007)
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_msq_msgctl(struct msg_queue *msq, int cmd) {
+ if(is_any_subadm(current->euid, current->egid)) {
+ if(cmd == MSG_INFO || cmd == MSG_STAT || cmd == IPC_INFO ||
+ cmd == IPC_STAT)
+ return 0;
+
+ // UID or CUID (creator UID) must fit
+ if(msq != NULL && msq->q_perm.uid != current->euid &&
+ msq->q_perm.cuid != current->euid && !is_uid_user(msq->q_perm.uid) &&
+ !is_uid_user(msq->q_perm.cuid))
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_ptrace(task_t *tracer, task_t *task) {
+ if(is_any_subadm(tracer->euid, tracer->egid)) {
+ /* Ownership check according to kernel/ptrace.c:
+ all of [RES][UG]ID must match the tracer's R[UG]ID. */
+ if(task->euid == tracer->uid && task->uid == tracer->uid &&
+ task->suid == tracer->uid && task->egid == tracer->gid &&
+ task->gid == tracer->gid && task->sgid == tracer->gid)
+ return 0;
+
+ // ...or all [RES]UIDs must match a WRT user
+ if(!is_task_user(task))
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_quotactl(int cmd, int type, int id, struct super_block *sb) {
+ if(is_any_subadm(current->euid, current->egid))
+ switch(cmd) {
+ case Q_SYNC:
+ case Q_GETFMT:
+ case Q_GETINFO:
+ case Q_GETQUOTA:
+ case Q_XGETQUOTA:
+ case Q_XGETQSTAT:
+ case Q_XQUOTASYNC:
+ return 0;
+ default:
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_sem_semctl(struct sem_array *sem, int cmd) {
+ if(is_any_subadm(current->euid, current->euid)) {
+ if(cmd == SEM_INFO || cmd == IPC_INFO || cmd == SEM_STAT)
+ return 0;
+ if(sem != NULL) {
+ const struct kern_ipc_perm *perm = &sem->sem_perm;
+ if(perm->uid != current->euid && perm->cuid != current->euid &&
+ !is_uid_user(perm->uid) && !is_uid_user(perm->cuid))
+ return -EPERM;
+ }
+ }
+ return 0;
+}
+
+static int mt_shm_shmctl(struct shmid_kernel *shp, int cmd) {
+ if(is_any_subadm(current->euid, current->egid)) {
+ if(cmd == SHM_INFO || cmd == SHM_STAT ||
+ cmd == IPC_INFO || cmd == IPC_STAT)
+ return 0;
+ if(shp != NULL) {
+ const struct kern_ipc_perm *perm = &shp->shm_perm;
+ if(perm->uid != current->euid && perm->cuid != current->euid &&
+ !is_uid_user(perm->uid) && !is_uid_user(perm->cuid))
+ return -EPERM;
+ }
+ }
+ return 0;
+}
+
+static int mt_task_kill(task_t *task, struct siginfo *si, int sig) {
+ if(is_any_subadm(current->euid, current->egid)) {
+ // As tricky as the ptrace() permission net.
+ if(is_uid_user(task->uid) || is_uid_user(task->suid))
+ return 0;
+
+ // Subadmin's own process
+ if(task->uid == current->euid || task->suid == current->euid ||
+ task->uid == current->uid || task->suid == current->uid)
+ return 0;
+
+ // SIG_IGN or a kernel-generated signal
+ if(si != NULL && ((long)si == 1 || (long)si == 2 || !SI_FROMUSER(si)))
+ return 0;
+
+ // For the case of a privileged subshell, but with the same tty
+ if(sig == SIGCONT && task->signal->session == current->signal->session)
+ return 0;
+
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_task_post_setuid(uid_t old_ruid, uid_t old_euid,
+ uid_t old_suid, int flags)
+{
+ int ret = cap_task_post_setuid(old_ruid, old_euid, old_suid, flags);
+ if(ret != 0)
+ return ret;
+
+ switch(flags) {
+ case LSM_SETID_ID:
+ case LSM_SETID_RE:
+ case LSM_SETID_RES:
+ // Unlike bprm_set_security(), effective must be set independently.
+ if(is_uid_superadm(current->uid))
+ chg2_superadm(&current->cap_permitted);
+ else if(is_uid_subadm(current->uid))
+ chg2_subadm(&current->cap_permitted);
+ else if(is_uid_netadm(current->uid))
+ chg2_netadm(&current->cap_permitted);
+
+ if(is_uid_superadm(current->euid))
+ chg2_superadm(&current->cap_effective);
+ else if(is_uid_subadm(current->euid))
+ chg2_subadm(&current->cap_effective);
+ else if(is_uid_netadm(current->euid))
+ chg2_netadm(&current->cap_effective);
+ break;
+ }
+ return 0;
+}
+
+static int mt_task_post_setgid(gid_t old_rgid, gid_t old_egid,
+ gid_t old_sgid, int flags)
+{
+ switch(flags) {
+ case LSM_SETID_ID:
+ case LSM_SETID_RE:
+ case LSM_SETID_RES:
+ if(is_gid_superadm(current->gid))
+ chg2_superadm(&current->cap_permitted);
+ else if(is_gid_subadm(current->gid))
+ chg2_subadm(&current->cap_permitted);
+
+ if(is_gid_superadm(current->egid))
+ chg2_superadm(&current->cap_effective);
+ else if(is_gid_subadm(current->egid))
+ chg2_subadm(&current->cap_effective);
+ break;
+ }
+ return 0;
+}
+
+static int mt_task_setuid(uid_t ruid, uid_t euid, uid_t suid, int flags) {
+ if(is_any_superadm(current->euid, current->egid))
+ return 0;
+
+ if(is_any_subadm(current->euid, current->egid))
+ if((ruid == -1 || is_uid_user(ruid)) && (euid == -1 ||
+ is_uid_user(euid)) && (suid == -1 || is_uid_user(suid)))
+ return 0;
+
+ switch(flags) {
+ case LSM_SETID_ID:
+ if(current->uid == ruid || current->suid == ruid)
+ return 0;
+ break;
+ case LSM_SETID_RE:
+ if(current->euid == ruid || current->euid == euid ||
+ current->uid == ruid || current->uid == euid ||
+ current->suid == euid)
+ return 0;
+ break;
+ case LSM_SETID_RES:
+ if(current->euid == ruid || current->euid == euid ||
+ current->euid == suid || current->uid == ruid ||
+ current->uid == euid || current->uid == suid ||
+ current->suid == ruid || current->suid == euid ||
+ current->suid == suid)
+ return 0;
+ break;
+ case LSM_SETID_FS:
+ if(current->euid == ruid)
+ return 0;
+ break;
+ default:
+ printk(KERN_WARNING PREFIX "Unsupported case %d in %s\n",
+ flags, __FUNCTION__);
+ break;
+ }
+ return -EIO;
+}
+
+static int mt_task_setnice(task_t *task, int nice) {
+ if(is_any_subadm(current->euid, current->egid)) {
+ if(task->euid != current->euid && task->uid != current->euid &&
+ !is_task1_user(task))
+ return -EPERM;
+ if(nice < 0)
+ return -EACCES;
+ }
+ return 0;
+}
+
+static int mt_task_setscheduler(task_t *task, int policy,
+ struct sched_param *param)
+{
+ /* Return 0 for superuser and normal users. The latters' checks are
+ performed in sched.c. */
+ if(is_any_subadm(current->euid, current->egid)) {
+ // Copied from kernel/sched.c:sched_setscheduler()
+ if(task->policy != policy)
+ return -EPERM;
+
+ if(policy != SCHED_NORMAL && param->sched_priority > task->rt_priority &&
+ param->sched_priority > task->signal->rlim[RLIMIT_RTPRIO].rlim_cur)
+ return -EPERM;
+
+ if(task->uid != current->euid && task->suid != current->euid &&
+ !is_task1_user(task))
+ return -EPERM;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+static inline void chg2_superadm(kernel_cap_t *c) {
+ cap_set_full(*c);
+ cap_lower(*c, CAP_SETPCAP);
+ cap_lower(*c, 31); // currently unused
+ return;
+}
+
+static inline void chg2_subadm(kernel_cap_t *c) {
+ cap_clear(*c);
+ cap_raise(*c, CAP_CHOWN);
+ cap_raise(*c, CAP_DAC_OVERRIDE);
+ cap_raise(*c, CAP_DAC_READ_SEARCH);
+ cap_raise(*c, CAP_FOWNER);
+ cap_raise(*c, CAP_KILL);
+ cap_raise(*c, CAP_SETUID);
+ cap_raise(*c, CAP_IPC_OWNER);
+ cap_raise(*c, CAP_SYS_PTRACE);
+ cap_raise(*c, CAP_SYS_ADMIN);
+ cap_raise(*c, CAP_SYS_NICE);
+ return;
+}
+
+static inline void chg2_netadm(kernel_cap_t *c) {
+ cap_clear(*c);
+ cap_raise(*c, CAP_NET_ADMIN);
+ return;
+}
+
+static inline bool is_any_superadm(uid_t u, gid_t g) {
+ return is_uid_superadm(u) || is_gid_superadm(g);
+}
+
+static inline bool is_uid_superadm(uid_t u) {
+ return
+ (!issecure(SECURE_NOROOT) && u == 0) ||
+ (Superuid_start != -1 && Superuid_end != -1 &&
+ u >= Superuid_start && u <= Superuid_end);
+}
+
+static inline bool is_gid_superadm(gid_t g) {
+ return Supergid != -1 && g == Supergid;
+}
+
+static inline bool is_any_subadm(uid_t u, gid_t g) {
+ return is_uid_subadm(u) || is_gid_subadm(g);
+}
+
+static inline bool is_uid_subadm(uid_t u) {
+ return Subuid_start != -1 && Subuid_end != -1 &&
+ u >= Subuid_start && u <= Subuid_end;
+}
+
+static inline bool is_gid_subadm(gid_t g) {
+ return Subgid != -1 && g == Subgid;
+}
+
+static inline bool is_uid_netadm(uid_t u) {
+ return Netuid != -1 && u == Netuid;
+}
+
+static inline bool is_uid_user(uid_t u) {
+ /* Special case Wrtuid_end == (unsigned) -1 means what it means: everything
+ until the end. This is why there is no Wrtuid_end != -1 check. */
+ return Wrtuid_start != -1 && u >= Wrtuid_start && u <= Wrtuid_end;
+}
+
+static inline bool is_task1_user(const task_t *task) {
+ return is_uid_user(task->uid) || is_uid_user(task->suid);
+}
+
+static inline bool is_task_user(const task_t *task) {
+ return is_uid_user(task->euid) && is_uid_user(task->uid) &&
+ is_uid_user(task->suid);
+}
+
+static inline bool range_intersect(uid_t as, uid_t ae, uid_t bs, uid_t be) {
+ if(as == -1 || ae == -1 || bs == -1 || be == -1)
+ return 0;
+ return (long)ae >= (long)bs && (long)as <= (long)be;
+}
+
+static inline bool range_intersect_wrt(uid_t as, uid_t ae,
+ uid_t bs, uid_t be)
+{
+ if(as == -1 || ae == -1 || bs == -1)
+ return 0;
+ return (long)ae >= (long)bs && (long)as <= (long)be;
+}
+
+//=============================================================================
#<<eof>>


Jan Engelhardt
--

2006-05-01 13:50:59

by Arjan van de Ven

[permalink] [raw]
Subject: Re: [PATCH 0/4] MultiAdmin LSM


> 2. A small problem
> ==================
> As cool as it may sound, I think the implementation is not as clean as
> possible.
>
> Let's pick a random starting point: The subadmin is allowed to call
> drivers/char/lp.c:lp_ioctl():LPGETSTATS. Or
> fs/quota.c:generic_quotactl_valid():Q_GET*/Q_XGET*. For that to work
> without too much code changes, CAP_SYS_ADMIN must be given to the
> subadmin.
>
> However, CAP_SYS_ADMIN (others are affected too, but this is the main one)
> is used for other things too (mostly write or ioctl operations), which is
> actually something that should not be granted to the subadmin.
>
> This poses a problem. Currently, it is solved by adding an extra LSM hook,
> security_cap_extra(), called from capable(). The hooked function then
> looks at current->*uid/*gid and returns 1 or 0, depending on whether an
> action is allowed or not. For more details see patch #1.
>
>


I wonder if we should just split up CAP_SYS_ADMIN then...
that might end up being the most simple solution...


2006-05-01 14:56:36

by James Morris

[permalink] [raw]
Subject: Re: [PATCH 4/4] MultiAdmin module

On Mon, 1 May 2006, Jan Engelhardt wrote:

> +#if !defined(CONFIG_SECURITY_CAPABILITIES) && \
> + !defined(CONFIG_SECURITY_CAPABILITIES_MODULE)
> +# error You need to have CONFIG_SECURITY_CAPABILITIES=y or =m \
> + for MultiAdmin to compile successfully.
> +#endif

This is what Kconfig language is for.

> +typedef int bool;

You won't get much worthwile review of this code until you clean it up and
make it conform to kernel coding style.


- James
--
James Morris
<[email protected]>

2006-05-01 15:07:45

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 4/4] MultiAdmin module

On Mon, May 01, 2006 at 03:50:21PM +0200, Jan Engelhardt wrote:
>
> [PATCH 4/4] MultiAdmin module
>
> - Add the MultiAdmin to the mainline tree.
> I hope the rest is self-explanatory.
>
> Please do not mention CodingStyle for multiadm.c. I already know it. :)
> And I will get to it should it really be merged.

No one will review it if it isn't in the proper CodingStyle.

We have a coding style for a reason, it makes it a very simple thing for
anyone to review the code as the patterns are all the same. It turns
out that people's brains get trained to ignore the patterns and see the
details instead. Lots of research backs this up.

So switch to the common coding style, otherwise no one will look at your
code (or if they do, odds are they will miss a lot...)

thanks,

greg k-h

2006-05-01 16:03:43

by Jan Engelhardt

[permalink] [raw]
Subject: [PATCH 4a/4] MultiAdmin LSM (LKCS'ed)



Does Lindented suffice?

Signed-off-by: Jan Engelhardt <[email protected]>

diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/Kconfig linux-2.6.17-rc3+/security/Kconfig
--- linux-2.6.17-rc3~/security/Kconfig 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/security/Kconfig 2006-05-01 18:00:26.692832000 +0200
@@ -99,6 +99,22 @@ config SECURITY_SECLVL

If you are unsure how to answer this question, answer N.

+config SECURITY_MULTIADM
+ tristate "MultiAdmin secuirty module"
+ depends on SECURITY
+ ---help---
+ The MultiAdmin security kernel module provides means to have multiple
+ "root" users with unique UIDs. This fixes collation order problems
+ which for example appear with NSCD, allows to have files with
+ determinable owner and allows to track the quota usage for every
+ user, since they now have a unique uid.
+
+ It also implements a "sub-admin", a partially restricted root user
+ (or enhanced normal user, depending on the way you see it), who has
+ full read-only access to most subsystems, and additional write rights
+ only to a limited subset, e.g. writing to files or killing processes
+ only of certain users.
+
source security/selinux/Kconfig

endmenu
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/Makefile linux-2.6.17-rc3+/security/Makefile
--- linux-2.6.17-rc3~/security/Makefile 2006-04-27 04:19:25.000000000 +0200
+++ linux-2.6.17-rc3+/security/Makefile 2006-05-01 18:00:26.692832000 +0200
@@ -17,3 +17,4 @@ obj-$(CONFIG_SECURITY_SELINUX) += selin
obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o
obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o
obj-$(CONFIG_SECURITY_SECLVL) += seclvl.o
+obj-$(CONFIG_SECURITY_MULTIADM) += commoncap.o multiadm.o
diff --fast -Ndpru -X dontdiff linux-2.6.17-rc3~/security/multiadm.c linux-2.6.17-rc3+/security/multiadm.c
--- linux-2.6.17-rc3~/security/multiadm.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.17-rc3+/security/multiadm.c 2006-05-01 18:01:15.142832000 +0200
@@ -0,0 +1,652 @@
+/*=============================================================================
+| MultiAdmin Security Module |
+| Copyright © Jan Engelhardt <jengelh [at] gmx de>, 2005 - 2006 |
+| v1.0.5, May 2006 |
+| http://alphagate.hopto.org/ |
+`-----------------------------------------------------------------------------'
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2 as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program kit; if not, write to:
+ Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ Boston, MA 02110-1301 USA
+=============================================================================*/
+#include <asm/siginfo.h>
+#include <linux/binfmts.h>
+#include <linux/capability.h>
+#include <linux/config.h>
+#include <linux/dcache.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ipc.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/namei.h>
+#include <linux/sched.h>
+#include <linux/securebits.h>
+#include <linux/security.h>
+#include <linux/sem.h>
+#include <linux/types.h>
+
+#define BASENAME "multiadm"
+#define PREFIX BASENAME ": "
+
+static int mt_bprm_set_security(struct linux_binprm *);
+static int mt_cap_extra(int);
+static int mt_inode_permission(struct inode *, int, struct nameidata *);
+static int mt_inode_setattr(struct dentry *, struct iattr *);
+static int mt_ipc_permission(struct kern_ipc_perm *, short);
+static int mt_msq_msgctl(struct msg_queue *, int);
+static int mt_ptrace(task_t *, task_t *);
+static int mt_quotactl(int, int, int, struct super_block *);
+static int mt_sem_semctl(struct sem_array *, int);
+static int mt_shm_shmctl(struct shmid_kernel *, int);
+static int mt_task_kill(task_t *, struct siginfo *, int);
+static int mt_task_post_setuid(uid_t, uid_t, uid_t, int);
+static int mt_task_post_setgid(gid_t, gid_t, gid_t, int);
+static int mt_task_setnice(task_t *, int);
+static int mt_task_setscheduler(task_t *, int, struct sched_param *);
+static int mt_task_setuid(uid_t, uid_t, uid_t, int);
+
+static inline void chg2_superadm(kernel_cap_t *);
+static inline void chg2_subadm(kernel_cap_t *);
+static inline void chg2_netadm(kernel_cap_t *);
+static inline int is_any_superadm(uid_t, gid_t);
+static inline int is_uid_superadm(uid_t);
+static inline int is_gid_superadm(gid_t);
+static inline int is_any_subadm(uid_t, gid_t);
+static inline int is_uid_subadm(uid_t);
+static inline int is_gid_subadm(gid_t);
+static inline int is_uid_netadm(uid_t);
+static inline int is_uid_user(uid_t);
+static inline int is_task1_user(const task_t *);
+static inline int is_task_user(const task_t *);
+static inline int range_intersect(uid_t, uid_t, uid_t, uid_t);
+static inline int range_intersect_wrt(uid_t, uid_t, uid_t, uid_t);
+
+static struct security_operations mt_secops = {
+ .bprm_apply_creds = cap_bprm_apply_creds,
+ .bprm_set_security = mt_bprm_set_security,
+ .cap_extra = mt_cap_extra,
+ .capable = cap_capable,
+ .capget = cap_capget,
+ .capset_check = cap_capset_check,
+ .capset_set = cap_capset_set,
+ .inode_permission = mt_inode_permission,
+ .inode_setattr = mt_inode_setattr,
+ .ipc_permission = mt_ipc_permission,
+ .msg_queue_msgctl = mt_msq_msgctl,
+ .ptrace = mt_ptrace,
+ .quotactl = mt_quotactl,
+ .sem_semctl = mt_sem_semctl,
+ .shm_shmctl = mt_shm_shmctl,
+ .task_kill = mt_task_kill,
+ .task_post_setuid = mt_task_post_setuid,
+ .task_post_setgid = mt_task_post_setgid,
+ .task_setnice = mt_task_setnice,
+ .task_setscheduler = mt_task_setscheduler,
+ .task_setuid = mt_task_setuid,
+};
+static gid_t Supergid = -1, Subgid = -1;
+static uid_t Superuid_start = 0, Superuid_end = 0,
+ Subuid_start = -1, Subuid_end = -1,
+ Netuid = -1, Wrtuid_start = -1, Wrtuid_end = -1;
+static int Secondary = 0;
+
+MODULE_DESCRIPTION("MultiAdmin Security Module; http://alphagate.hopto.org/");
+MODULE_AUTHOR("Jan Engelhardt <jengelh [at] gmx de>");
+MODULE_LICENSE("GPL");
+module_param(Supergid, int, S_IRUSR | S_IWUSR);
+module_param(Superuid_start, int, S_IRUSR | S_IWUSR);
+module_param(Superuid_end, int, S_IRUSR | S_IWUSR);
+module_param(Subuid_start, int, S_IRUSR | S_IWUSR);
+module_param(Subuid_end, int, S_IRUSR | S_IWUSR);
+module_param(Subgid, int, S_IRUSR | S_IWUSR);
+module_param(Netuid, int, S_IRUSR | S_IWUSR);
+module_param(Wrtuid_start, int, S_IRUGO | S_IWUSR);
+module_param(Wrtuid_end, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(Wrtuid_start, "First UID of the write-enabled user range");
+MODULE_PARM_DESC(Wrtuid_end, "Last UID of the write-enabled user range");
+MODULE_PARM_DESC(Superuid_start, "First UIDs of the superadmin range");
+MODULE_PARM_DESC(Superuid_end, "Last UID of the superadmin range");
+MODULE_PARM_DESC(Supergid, "Superadmin GID");
+MODULE_PARM_DESC(Subuid_start, "First UIDs of the subadmin range");
+MODULE_PARM_DESC(Subuid_end, "Last UID of the subadmin range");
+MODULE_PARM_DESC(Subgid, "Subadmin GID");
+MODULE_PARM_DESC(Netuid, "Netadmin UID");
+
+//-----------------------------------------------------------------------------
+__init static int multiadm_init(void)
+{
+ int eax = 0, ebx = 0;
+ if ((eax = register_security(&mt_secops)) != 0) {
+ if ((ebx = mod_reg_security(BASENAME, &mt_secops)) != 0) {
+ printk(KERN_WARNING PREFIX
+ "Could not register with kernel: %d, %d\n", eax,
+ ebx);
+ return ebx;
+ }
+ Secondary = 1;
+ }
+
+ if (range_intersect
+ (Superuid_start, Superuid_end, Subuid_start, Subuid_end))
+ printk(KERN_WARNING PREFIX
+ "Superadmin and Subadmin ranges intersect! Unpredictable behavior"
+ " may result: some operations may classify you as a superadmin,"
+ " others as a subadmin. Security leak: subadmin could possibly"
+ " change into superadmin!\n");
+ if (range_intersect(Superuid_start, Superuid_end, Netuid, Netuid))
+ printk(KERN_WARNING PREFIX
+ "Netuid within superadmin range! -Has more "
+ "powers than intended!\n");
+ if (range_intersect
+ (Superuid_start, Superuid_end, Wrtuid_start, Wrtuid_end))
+ printk(KERN_WARNING PREFIX
+ "Superadmin and write-enabled user range "
+ "intersect! A subadmin could setuid() into a superadmin!\n");
+ if (range_intersect(Subuid_start, Subuid_end, Netuid, Netuid))
+ printk(KERN_WARNING PREFIX
+ "Netuid within subadmin range! -Has more "
+ "powers than intended!\n");
+ if (range_intersect_wrt
+ (Subuid_start, Subuid_end, Wrtuid_start, Wrtuid_end))
+ printk(KERN_WARNING PREFIX
+ "Subadmin and write-enabled user range "
+ "intersect! Subadmins are able to poke on other subadmins!\n");
+ if (range_intersect_wrt(Netuid, Netuid, Wrtuid_start, Wrtuid_end))
+ printk(KERN_WARNING PREFIX
+ "Netuid within write-enabled user range! "
+ "Subadmin may gain CAP_NET_ADMIN!\n");
+ printk(KERN_INFO "MultiAdmin loaded\n");
+ return 0;
+}
+
+__exit static void multiadm_exit(void)
+{
+ int ret = 0;
+
+ if (Secondary)
+ ret = mod_unreg_security(BASENAME, &mt_secops);
+ else
+ ret = unregister_security(&mt_secops);
+
+ if (ret != 0)
+ printk(KERN_WARNING PREFIX
+ "Could not unregister with kernel: %d\n", ret);
+
+ return;
+}
+
+module_init(multiadm_init);
+module_exit(multiadm_exit);
+
+//-----------------------------------------------------------------------------
+static int mt_bprm_set_security(struct linux_binprm *bp)
+{
+ /* In the function chain of exec(), we eventually get here, which is the
+ place to set up new privileges. */
+ cap_bprm_set_security(bp);
+
+ /* All of the following is nicely inlined. The capability raising is
+ resolved to only one instruction for each set. */
+ if (is_any_superadm(bp->e_uid, bp->e_gid)) {
+ chg2_superadm(&bp->cap_permitted);
+ chg2_superadm(&bp->cap_effective);
+ } else if (is_any_superadm(current->uid, current->gid)) {
+ chg2_superadm(&bp->cap_permitted);
+ } else if (is_any_subadm(bp->e_uid, bp->e_gid)) {
+ chg2_subadm(&bp->cap_permitted);
+ chg2_subadm(&bp->cap_effective);
+ } else if (is_any_subadm(current->uid, current->gid)) {
+ chg2_subadm(&bp->cap_permitted);
+ } else if (is_uid_netadm(bp->e_uid)) {
+ chg2_netadm(&bp->cap_permitted);
+ chg2_netadm(&bp->cap_effective);
+ } else if (is_uid_netadm(current->uid)) {
+ chg2_netadm(&bp->cap_permitted);
+ }
+ return 0;
+}
+
+static int mt_cap_extra(int capability)
+{
+ if (capability == CAP_SYS_ADMIN)
+ /* Subadmin also has CAP_SYS_ADMIN, but if we get here, we did so
+ by capable() -- not capable_light(). */
+ return is_any_superadm(current->euid, current->egid);
+ else
+ /* Subadmin/Netadmin also has other capabilities, but they
+ are -- I hope -- ok. */
+ return 1;
+}
+
+static int mt_inode_permission(struct inode *inode, int mask,
+ struct nameidata *nd)
+{
+ /* Check for superadmin is not done, since the only users that can get
+ here is either superadmin or subadmin. By omitting the check for
+ superadmin, only two comparisons need to be done for the subadmin case.
+ This method is done almost throughout the entire module. */
+
+ if (is_any_subadm(current->euid, current->egid) && (mask & MAY_WRITE)) {
+ int ret;
+ if (inode->i_uid == current->fsuid || is_uid_user(inode->i_uid))
+ return 0;
+
+ /* Since we practically jumped over the checks to get here (because of
+ CAP_DAC_OVERRIDE), we need to do it again. Without CAP_DAC_OVERRIDE
+ this time. Temporarily drop it. */
+ cap_lower(current->cap_effective, CAP_DAC_OVERRIDE);
+
+ // Copied from fs/namei.c
+ if (inode->i_op != NULL && inode->i_op->permission != NULL)
+ ret =
+ inode->i_op->permission(inode, mask & ~MAY_APPEND,
+ nd);
+ else
+ ret =
+ generic_permission(inode, mask & ~MAY_APPEND, NULL);
+
+ cap_raise(current->cap_effective, CAP_DAC_OVERRIDE);
+ return ret;
+ }
+ return 0;
+}
+
+static int mt_inode_setattr(struct dentry *dentry, struct iattr *attr)
+{
+ if (is_any_subadm(current->euid, current->egid)) {
+ /* Change is only allowed if either the inode belongs to us, or does
+ belond, _and_ will belong in case of ATTR_UID, to a WRT user. */
+ const struct inode *inode = dentry->d_inode;
+ if (inode->i_uid != current->fsuid
+ && !is_uid_user(inode->i_uid))
+ return -EPERM;
+
+ if ((attr->ia_valid & ATTR_UID)
+ && attr->ia_uid != current->fsuid
+ && !is_uid_user(attr->ia_uid))
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_ipc_permission(struct kern_ipc_perm *perm, short flag)
+{
+ if (is_any_subadm(current->euid, current->egid)) {
+ int req, grant;
+
+ if (perm->uid == current->euid || perm->cuid == current->euid ||
+ is_uid_user(perm->uid) || is_uid_user(perm->cuid))
+ return 0;
+
+ /* Copied and modified from ipc/util.c. Subadmin always has read
+ permission so add S_IRUGO to granted. Checking the owner permission
+ part is not done anymore, because it is done above. */
+ req = (flag >> 6) | (flag >> 3) | flag;
+ grant = (perm->mode | S_IRUGO) >> 3;
+ if (in_group_p(perm->gid) || in_group_p(perm->cgid))
+ grant >>= 3;
+ if (req & ~grant & 0007)
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_msq_msgctl(struct msg_queue *msq, int cmd)
+{
+ if (is_any_subadm(current->euid, current->egid)) {
+ if (cmd == MSG_INFO || cmd == MSG_STAT || cmd == IPC_INFO ||
+ cmd == IPC_STAT)
+ return 0;
+
+ // UID or CUID (creator UID) must fit
+ if (msq != NULL && msq->q_perm.uid != current->euid &&
+ msq->q_perm.cuid != current->euid
+ && !is_uid_user(msq->q_perm.uid)
+ && !is_uid_user(msq->q_perm.cuid))
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_ptrace(task_t * tracer, task_t * task)
+{
+ if (is_any_subadm(tracer->euid, tracer->egid)) {
+ /* Ownership check according to kernel/ptrace.c:
+ all of [RES][UG]ID must match the tracer's R[UG]ID. */
+ if (task->euid == tracer->uid && task->uid == tracer->uid &&
+ task->suid == tracer->uid && task->egid == tracer->gid &&
+ task->gid == tracer->gid && task->sgid == tracer->gid)
+ return 0;
+
+ // ...or all [RES]UIDs must match a WRT user
+ if (!is_task_user(task))
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_quotactl(int cmd, int type, int id, struct super_block *sb)
+{
+ if (is_any_subadm(current->euid, current->egid))
+ switch (cmd) {
+ case Q_SYNC:
+ case Q_GETFMT:
+ case Q_GETINFO:
+ case Q_GETQUOTA:
+ case Q_XGETQUOTA:
+ case Q_XGETQSTAT:
+ case Q_XQUOTASYNC:
+ return 0;
+ default:
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_sem_semctl(struct sem_array *sem, int cmd)
+{
+ if (is_any_subadm(current->euid, current->euid)) {
+ if (cmd == SEM_INFO || cmd == IPC_INFO || cmd == SEM_STAT)
+ return 0;
+ if (sem != NULL) {
+ const struct kern_ipc_perm *perm = &sem->sem_perm;
+ if (perm->uid != current->euid
+ && perm->cuid != current->euid
+ && !is_uid_user(perm->uid)
+ && !is_uid_user(perm->cuid))
+ return -EPERM;
+ }
+ }
+ return 0;
+}
+
+static int mt_shm_shmctl(struct shmid_kernel *shp, int cmd)
+{
+ if (is_any_subadm(current->euid, current->egid)) {
+ if (cmd == SHM_INFO || cmd == SHM_STAT ||
+ cmd == IPC_INFO || cmd == IPC_STAT)
+ return 0;
+ if (shp != NULL) {
+ const struct kern_ipc_perm *perm = &shp->shm_perm;
+ if (perm->uid != current->euid
+ && perm->cuid != current->euid
+ && !is_uid_user(perm->uid)
+ && !is_uid_user(perm->cuid))
+ return -EPERM;
+ }
+ }
+ return 0;
+}
+
+static int mt_task_kill(task_t * task, struct siginfo *si, int sig)
+{
+ if (is_any_subadm(current->euid, current->egid)) {
+ // As tricky as the ptrace() permission net.
+ if (is_uid_user(task->uid) || is_uid_user(task->suid))
+ return 0;
+
+ // Subadmin's own process
+ if (task->uid == current->euid || task->suid == current->euid ||
+ task->uid == current->uid || task->suid == current->uid)
+ return 0;
+
+ // SIG_IGN or a kernel-generated signal
+ if (si != NULL
+ && ((long)si == 1 || (long)si == 2 || !SI_FROMUSER(si)))
+ return 0;
+
+ // For the case of a privileged subshell, but with the same tty
+ if (sig == SIGCONT
+ && task->signal->session == current->signal->session)
+ return 0;
+
+ return -EPERM;
+ }
+ return 0;
+}
+
+static int mt_task_post_setuid(uid_t old_ruid, uid_t old_euid,
+ uid_t old_suid, int flags)
+{
+ int ret = cap_task_post_setuid(old_ruid, old_euid, old_suid, flags);
+ if (ret != 0)
+ return ret;
+
+ switch (flags) {
+ case LSM_SETID_ID:
+ case LSM_SETID_RE:
+ case LSM_SETID_RES:
+ // Unlike bprm_set_security(), effective must be set independently.
+ if (is_uid_superadm(current->uid))
+ chg2_superadm(&current->cap_permitted);
+ else if (is_uid_subadm(current->uid))
+ chg2_subadm(&current->cap_permitted);
+ else if (is_uid_netadm(current->uid))
+ chg2_netadm(&current->cap_permitted);
+
+ if (is_uid_superadm(current->euid))
+ chg2_superadm(&current->cap_effective);
+ else if (is_uid_subadm(current->euid))
+ chg2_subadm(&current->cap_effective);
+ else if (is_uid_netadm(current->euid))
+ chg2_netadm(&current->cap_effective);
+ break;
+ }
+ return 0;
+}
+
+static int mt_task_post_setgid(gid_t old_rgid, gid_t old_egid,
+ gid_t old_sgid, int flags)
+{
+ switch (flags) {
+ case LSM_SETID_ID:
+ case LSM_SETID_RE:
+ case LSM_SETID_RES:
+ if (is_gid_superadm(current->gid))
+ chg2_superadm(&current->cap_permitted);
+ else if (is_gid_subadm(current->gid))
+ chg2_subadm(&current->cap_permitted);
+
+ if (is_gid_superadm(current->egid))
+ chg2_superadm(&current->cap_effective);
+ else if (is_gid_subadm(current->egid))
+ chg2_subadm(&current->cap_effective);
+ break;
+ }
+ return 0;
+}
+
+static int mt_task_setuid(uid_t ruid, uid_t euid, uid_t suid, int flags)
+{
+ if (is_any_superadm(current->euid, current->egid))
+ return 0;
+
+ if (is_any_subadm(current->euid, current->egid))
+ if ((ruid == -1 || is_uid_user(ruid)) && (euid == -1 ||
+ is_uid_user(euid))
+ && (suid == -1 || is_uid_user(suid)))
+ return 0;
+
+ switch (flags) {
+ case LSM_SETID_ID:
+ if (current->uid == ruid || current->suid == ruid)
+ return 0;
+ break;
+ case LSM_SETID_RE:
+ if (current->euid == ruid || current->euid == euid ||
+ current->uid == ruid || current->uid == euid ||
+ current->suid == euid)
+ return 0;
+ break;
+ case LSM_SETID_RES:
+ if (current->euid == ruid || current->euid == euid ||
+ current->euid == suid || current->uid == ruid ||
+ current->uid == euid || current->uid == suid ||
+ current->suid == ruid || current->suid == euid ||
+ current->suid == suid)
+ return 0;
+ break;
+ case LSM_SETID_FS:
+ if (current->euid == ruid)
+ return 0;
+ break;
+ default:
+ printk(KERN_WARNING PREFIX "Unsupported case %d in %s\n",
+ flags, __FUNCTION__);
+ break;
+ }
+ return -EIO;
+}
+
+static int mt_task_setnice(task_t * task, int nice)
+{
+ if (is_any_subadm(current->euid, current->egid)) {
+ if (task->euid != current->euid && task->uid != current->euid &&
+ !is_task1_user(task))
+ return -EPERM;
+ if (nice < 0)
+ return -EACCES;
+ }
+ return 0;
+}
+
+static int mt_task_setscheduler(task_t * task, int policy,
+ struct sched_param *param)
+{
+ /* Return 0 for superuser and normal users. The latters' checks are
+ performed in sched.c. */
+ if (is_any_subadm(current->euid, current->egid)) {
+ // Copied from kernel/sched.c:sched_setscheduler()
+ if (task->policy != policy)
+ return -EPERM;
+
+ if (policy != SCHED_NORMAL
+ && param->sched_priority > task->rt_priority
+ && param->sched_priority >
+ task->signal->rlim[RLIMIT_RTPRIO].rlim_cur)
+ return -EPERM;
+
+ if (task->uid != current->euid && task->suid != current->euid &&
+ !is_task1_user(task))
+ return -EPERM;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+static inline void chg2_superadm(kernel_cap_t * c)
+{
+ cap_set_full(*c);
+ cap_lower(*c, CAP_SETPCAP);
+ cap_lower(*c, 31); // currently unused
+ return;
+}
+
+static inline void chg2_subadm(kernel_cap_t * c)
+{
+ cap_clear(*c);
+ cap_raise(*c, CAP_CHOWN);
+ cap_raise(*c, CAP_DAC_OVERRIDE);
+ cap_raise(*c, CAP_DAC_READ_SEARCH);
+ cap_raise(*c, CAP_FOWNER);
+ cap_raise(*c, CAP_KILL);
+ cap_raise(*c, CAP_SETUID);
+ cap_raise(*c, CAP_IPC_OWNER);
+ cap_raise(*c, CAP_SYS_PTRACE);
+ cap_raise(*c, CAP_SYS_ADMIN);
+ cap_raise(*c, CAP_SYS_NICE);
+ return;
+}
+
+static inline void chg2_netadm(kernel_cap_t * c)
+{
+ cap_clear(*c);
+ cap_raise(*c, CAP_NET_ADMIN);
+ return;
+}
+
+static inline int is_any_superadm(uid_t u, gid_t g)
+{
+ return is_uid_superadm(u) || is_gid_superadm(g);
+}
+
+static inline int is_uid_superadm(uid_t u)
+{
+ return
+ (!issecure(SECURE_NOROOT) && u == 0) ||
+ (Superuid_start != -1 && Superuid_end != -1 &&
+ u >= Superuid_start && u <= Superuid_end);
+}
+
+static inline int is_gid_superadm(gid_t g)
+{
+ return Supergid != -1 && g == Supergid;
+}
+
+static inline int is_any_subadm(uid_t u, gid_t g)
+{
+ return is_uid_subadm(u) || is_gid_subadm(g);
+}
+
+static inline int is_uid_subadm(uid_t u)
+{
+ return Subuid_start != -1 && Subuid_end != -1 &&
+ u >= Subuid_start && u <= Subuid_end;
+}
+
+static inline int is_gid_subadm(gid_t g)
+{
+ return Subgid != -1 && g == Subgid;
+}
+
+static inline int is_uid_netadm(uid_t u)
+{
+ return Netuid != -1 && u == Netuid;
+}
+
+static inline int is_uid_user(uid_t u)
+{
+ /* Special case Wrtuid_end == (unsigned) -1 means what it means: everything
+ until the end. This is why there is no Wrtuid_end != -1 check. */
+ return Wrtuid_start != -1 && u >= Wrtuid_start && u <= Wrtuid_end;
+}
+
+static inline int is_task1_user(const task_t * task)
+{
+ return is_uid_user(task->uid) || is_uid_user(task->suid);
+}
+
+static inline int is_task_user(const task_t * task)
+{
+ return is_uid_user(task->euid) && is_uid_user(task->uid) &&
+ is_uid_user(task->suid);
+}
+
+static inline int range_intersect(uid_t as, uid_t ae, uid_t bs, uid_t be)
+{
+ if (as == -1 || ae == -1 || bs == -1 || be == -1)
+ return 0;
+ return (long)ae >= (long)bs && (long)as <= (long)be;
+}
+
+static inline int range_intersect_wrt(uid_t as, uid_t ae, uid_t bs, uid_t be)
+{
+ if (as == -1 || ae == -1 || bs == -1)
+ return 0;
+ return (long)ae >= (long)bs && (long)as <= (long)be;
+}
+
+//=============================================================================


Jan Engelhardt
--

2006-05-01 16:49:26

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 4a/4] MultiAdmin LSM (LKCS'ed)

On Mon, May 01, 2006 at 06:03:09PM +0200, Jan Engelhardt wrote:
>
>
> Does Lindented suffice?

It's a good start, but you still need to fix things like:

> +#include <asm/siginfo.h>
> +#include <linux/binfmts.h>
> +#include <linux/capability.h>
> +#include <linux/config.h>
> +#include <linux/dcache.h>
> +#include <linux/file.h>
> +#include <linux/fs.h>
> +#include <linux/init.h>
> +#include <linux/ipc.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/namei.h>
> +#include <linux/sched.h>
> +#include <linux/securebits.h>
> +#include <linux/security.h>
> +#include <linux/sem.h>
> +#include <linux/types.h>

asm #include goes last.

> +static inline void chg2_superadm(kernel_cap_t *);
> +static inline void chg2_subadm(kernel_cap_t *);
> +static inline void chg2_netadm(kernel_cap_t *);
> +static inline int is_any_superadm(uid_t, gid_t);
> +static inline int is_uid_superadm(uid_t);
> +static inline int is_gid_superadm(gid_t);
> +static inline int is_any_subadm(uid_t, gid_t);
> +static inline int is_uid_subadm(uid_t);
> +static inline int is_gid_subadm(gid_t);
> +static inline int is_uid_netadm(uid_t);
> +static inline int is_uid_user(uid_t);
> +static inline int is_task1_user(const task_t *);
> +static inline int is_task_user(const task_t *);
> +static inline int range_intersect(uid_t, uid_t, uid_t, uid_t);
> +static inline int range_intersect_wrt(uid_t, uid_t, uid_t, uid_t);

inline functions don't need definitions like this.

> +static gid_t Supergid = -1, Subgid = -1;
> +static uid_t Superuid_start = 0, Superuid_end = 0,
> + Subuid_start = -1, Subuid_end = -1,
> + Netuid = -1, Wrtuid_start = -1, Wrtuid_end = -1;
> +static int Secondary = 0;

Variables do not have capital letters.

thanks,

greg k-h

2006-05-01 17:43:03

by Jan Engelhardt

[permalink] [raw]
Subject: Re: [PATCH 4a/4] MultiAdmin LSM (LKCS'ed)


>asm #include goes last.

How come?

>> +static inline int range_intersect_wrt(uid_t, uid_t, uid_t, uid_t);
>
>inline functions don't need definitions like this.

If memory serves right, callees mentioned below their callers need a
prototype.

>> +static gid_t Supergid = -1, Subgid = -1;
>> +static uid_t Superuid_start = 0, Superuid_end = 0,
>> + Subuid_start = -1, Subuid_end = -1,
>> + Netuid = -1, Wrtuid_start = -1, Wrtuid_end = -1;
>> +static int Secondary = 0;
>
>Variables do not have capital letters.

Who has, besides macros, if anything?



Jan Engelhardt
--

2006-05-01 18:09:20

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 4a/4] MultiAdmin LSM (LKCS'ed)

On Mon, May 01, 2006 at 07:42:33PM +0200, Jan Engelhardt wrote:
>
> >asm #include goes last.
>
> How come?

Just the standard style.

> >> +static inline int range_intersect_wrt(uid_t, uid_t, uid_t, uid_t);
> >
> >inline functions don't need definitions like this.
>
> If memory serves right, callees mentioned below their callers need a
> prototype.

You can't have a inline function with a prototype :)

> >> +static gid_t Supergid = -1, Subgid = -1;
> >> +static uid_t Superuid_start = 0, Superuid_end = 0,
> >> + Subuid_start = -1, Subuid_end = -1,
> >> + Netuid = -1, Wrtuid_start = -1, Wrtuid_end = -1;
> >> +static int Secondary = 0;
> >
> >Variables do not have capital letters.
>
> Who has, besides macros, if anything?

nothing.

thanks,

greg k-h

2006-05-01 20:19:43

by Jan Engelhardt

[permalink] [raw]
Subject: Re: [PATCH 4a/4] MultiAdmin LSM (LKCS'ed)

>
>> >> +static inline int range_intersect_wrt(uid_t, uid_t, uid_t, uid_t);
>> >
>> >inline functions don't need definitions like this.
>>
>> If memory serves right, callees mentioned below their callers need a
>> prototype.
>
>You can't have a inline function with a prototype :)

Says who? The file is the best example that one can X-].
(Of course, it requires -funit-at-a-time.)


Jan Engelhardt
--

2006-05-01 21:47:15

by Adrian Bunk

[permalink] [raw]
Subject: Re: [PATCH 4a/4] MultiAdmin LSM (LKCS'ed)

On Mon, May 01, 2006 at 10:19:14PM +0200, Jan Engelhardt wrote:
> >
> >> >> +static inline int range_intersect_wrt(uid_t, uid_t, uid_t, uid_t);
> >> >
> >> >inline functions don't need definitions like this.
> >>
> >> If memory serves right, callees mentioned below their callers need a
> >> prototype.
> >
> >You can't have a inline function with a prototype :)
>
> Says who? The file is the best example that one can X-].
> (Of course, it requires -funit-at-a-time.)

IOW, you know that your code will not compile in the kernel on i386 with
gcc 3.3 or 3.4.

> Jan Engelhardt

cu
Adrian

--

"Is there not promise of rain?" Ling Tan asked suddenly out
of the darkness. There had been need of rain for many days.
"Only a promise," Lao Er said.
Pearl S. Buck - Dragon Seed

2006-05-02 04:22:50

by James Morris

[permalink] [raw]
Subject: Re: [PATCH 0/4] MultiAdmin LSM

This module implements two distinct ideas:

(1) Multiple superusers with distinct UIDs.

More than one root on a system I think is generally regarded as a bad
idea. I'm not sure why you'd use a scheme like this instead of, say, sudo
or custom setuid helpers for specific tasks -- whatever the case, I think
such issues can be addressed entirely in userspace.


(2) Partially decomposing the superuser and protecting some users from
some decomposed superusers, and decomposing CAP_SYS_ADMIN.

This is a special-case security policy hard-coded into the kernel. It
lacks a clear design rationale, and does not seem amenable to analysis, as
its access control coverage is incomplete.

As already suggested, it may be worth looking at just decomposing
CAP_SYS_ADMIN, although it's not clear how do to this correctly for the
general case.


- James
--
James Morris
<[email protected]>

2006-05-03 09:25:25

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH 0/4] MultiAdmin LSM

Hi!

> Subject: [PATCH 0/4] MultiAdmin LSM
> (was: Re: Time to remove LSM
> (was: Re: [RESEND][RFC][PATCH 2/7] implementation of LSM hooks))
>
>
> 0. Preface
> ==========
> Thanks to Greg who, requiring me to post more-split patches, made me
> reconsider the code. I did nothing less than to simplified the whole patch
> cruft (shrunk by factor 10) and removed what seemed unreasonable. This
> thread posts MultiAdmin *1.0.5*.
>
>
>
> 1. Super-short description
> ==========================
> Three user classes exist (determined by user-defined UID ranges),
> - superadmin, the usual "root"
> - subadmin
> - normal users
>
> A usual (non-multiadm,non-selinux) system has only one superadmin (UID 0)
> and a number of normal users, and the superadmin can operate on
> everything.
>
> The "subadmin" can read in some superadmin-only places, and is allowed to
> fully operate on processes/files/ipc/etc. of normal users. The full list
> (possibly incomplete) of permissions is available in the README.txt
> (includes short description) in the out-of-tree tarball.
> [http://freshmeat.net/p/multiadm/]

I guess you should really split CAP_SYS_ADMIN into some subsets that
make sense... along with explanation why subsets are 'right'.

Pavel
--
Thanks, Sharp!