PROBLEM: A 2.6.0-test11 Capability LSM Module serious bug

From: ??
Date: Mon Dec 08 2003 - 03:49:47 EST


[1.] Abstract

When POSIX Capability LSM module isn't compiled into kernel, after inserting Capability module into kernel, all existed normal users processes will have total Capability privileges of superuser (root).

[2.] Full description of the problem

POSIX.1e Capability is a very important component of Linux kernel. In original Linux Kernel, system security relies on it and DAC mainly. In new kernel version, Linux Security Modules (LSM) framework is introduced to provide a lightweight, general-purpose framework for access control. Some Linux security projects are ported to LSM and accepted by kernel source, such as POSIX.1e Capability and SE-Linux. Users can compile Capability as a Linux Loadable Kernel Module, and insert it into kernel at any time he wants to. Under this situation, after inserting Capability module, due to error creds of existed processes, normal user processes have total privileges of root and can perform any thing.

When the privileged operations are controlled by Capability modules, it mediate privileged operation base on the creds of process, the creds consists of three fields of task_struct, namely, cap_permitted, cap_inheritable and cap_effective. Before user process carry out privileged operations (such as sethostname, override DAC, raw IO etc.), system check cap_effective field (in cap_capable hooks funcation of security/commoncap.c):

if (cap_raised (tsk->cap_effective, cap)))
return 0;
else
return -EPERM;

The computation of creds are so important that it should be computed by system according to *uid properties of process correctly, the computation is perform by Capability Module too. In general, only root processes can take on Capability privileges.

When Capability don't run in kernel and no other LSM security modules are loaded, kernel uses default security function ops (security/dummy.c) to mediate privilege operation. The check logic of dummy ops is very simple: if a process want to perform a privileged operation, the euid property of it must be zero (root), or fsuid property must be zero when this privileged operation involved with file system. However, dummy ops do nothing about creds of processes, the creds of any process is a clone of its parent process. As results, the creds of all process, even normal user process, are as same as that of Init process. Init process is a privileged process, it is assigned total Capability privileges in the creds of it when system initiate.

Unfortunately, after Capability LSM module is loaded, it don't recomputed the creds of processes that are existed before inserting Capability module. Before inserting, only root processes can perform privileged operations controlled by dummy ops based *uids correctly. After inserting, the control of privileged operations is switch from dummy ops to Capability module based on creds. As a result, all existed processes have privileges as same as init, even they are normal user processes. Normal user (maybe a malicious user) can perform any operations through these processes!

[3.] Keywords

Security, Capability, LSM, creds

[4.] kernel version

Linux version 2.6.0-test11 (root@LB-Server) (gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)) #2 SMP Sun Dec 7 16:32:14 CST 2003

[5.] An example Let's review a simple example:
Before loading Capability module, run a vim editor as a normal user. In vim, enter command ":r /root/.viminfo", vim response "Can't open file /root/.viminfo" the request to access a root file of normal user is denied.

Don't close vim, switch to another console and login as root, insert Capability module into kernel.
#modprobe capability

After inserting, back to vim and try to open file /root/.viminfo again, you will find you can read, edit and save(w!) this file as a normal user! The reason for this wrong access control is error creds of vim so as to it has Capability privilege CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH.

Let's view the creds with a shell command.
$cat /proc/pid of vim/status.

Name: vim
State: S (sleeping)
SleepAVG: 91%
Tgid: 2454
Pid: 2454
PPid: 1552
TracerPid: 0
Uid: 500 500 500 500
Gid: 500 500 500 500
FDSize: 256
Groups: 500
VmSize: 9356 kB
VmLck: 0 kB
VmRSS: 2728 kB
VmData: 856 kB
VmStk: 16 kB
VmExe: 1676 kB
VmLib: 3256 kB
Threads: 1
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 8000000000003000
SigCgt: 00000000ef824eff
CapInh: 0000000000000000
CapPrm: 00000000ffffffff
CapEff: 00000000fffffeff

The last three lines are the creds of vim, it has all Capability privileges besides CAP_SETPCAP.

Above test is perform in 2.6.0-test11 and 2.5.72-lsm1, I think the same result will occur in all 2.6.0-test*.

[6.] The version of some important subsystems
#sh scripts/ver_linux
Linux LB-Server 2.6.0-test11 #2 SMP Sun Dec 7 16:32:14 CST 2003 i686 i686 i386 GNU/Linux

Gnu C 3.2.2
Gnu make 3.79.1
util-linux 2.11y
mount 2.11y
module-init-tools 0.9.14
e2fsprogs 1.32
jfsutils 1.0.17
reiserfsprogs 3.6.4
pcmcia-cs 3.1.31
quota-tools 3.06.
PPP 2.4.1
isdn4k-utils 3.1pre4
nfs-utils 1.0.1
Linux C Library 2.3.2
Dynamic linker (ldd) 2.3.2
Procps 2.0.11
Net-tools 1.60
Kbd 1.08
Sh-utils 4.5.3
Modules Loaded capability commoncap

[7.]How to fix this bug

In order to fix this bug, we may have two methods. One is moving the computation of creds from Capability module to kernel. The other is adding some code in Capability module to re-compute creds of existed process. I choose the second method, add following code in security/capability.c:

static void recompute_capability_creds(struct task_struct *task)
{
if(task->pid <= 1)
return;

task_lock(task);
task->keep_capabilities = 0;

if ((task->uid && task->euid && task->suid) && !task->keep_capabilities)
cap_clear (task->cap_permitted);
else
task->cap_permitted = CAP_INIT_EFF_SET;


if (task->euid != 0){
cap_clear (task->cap_effective);
}
else{
task->cap_effective = CAP_INIT_EFF_SET;
}

if(task->fsuid)
task->cap_effective &= ~CAP_FS_MASK;
else
task->cap_effective |= CAP_FS_MASK;

task_unlock(task);

return;
}


and add following code in Capability init function capability_init before it return:

read_lock(&tasklist_lock);
for_each_process(task){
recompute_capability_creds(task);
}
read_unlock(&tasklist_lock);

return 0;

After modifing capability.c, we need "make" and "make modules_install" again. Unload Capability module (rmmod capability; rmmod commoncap) and retry the above example, all the accesses to a root file by normal user existed process are always denied before and after inserting Capability module.

Let's view the creds again.

$cat /proc/pid of vim/status.

Name: vim
State: S (sleeping)
SleepAVG: 91%
Tgid: 2864
Pid: 2864
PPid: 1552
TracerPid: 0
Uid: 500 500 500 500
Gid: 500 500 500 500
FDSize: 256
Groups: 500
VmSize: 9360 kB
VmLck: 0 kB
VmRSS: 2816 kB
VmData: 860 kB
VmStk: 16 kB
VmExe: 1676 kB
VmLib: 3256 kB
Threads: 1
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 8000000000003000
SigCgt: 00000000ef824eff
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000

[8] Patch

patch for security/capability.c in 2.6.0-test11 is also availiable:

--- capability.c 2003-12-07 17:06:40.228670848 +0800
+++ capability.c.new 2003-12-07 17:06:51.908895184 +0800
@@ -56,6 +56,36 @@
/* flag to keep track of how we were registered */
static int secondary;

+static void recompute_capability_creds(struct task_struct *task)
+{
+ if(task->pid <= 1)
+ return;
+
+ task_lock(task);
+ task->keep_capabilities = 0;
+
+ if ((task->uid && task->euid && task->suid) && !task->keep_capabilities)
+ cap_clear (task->cap_permitted);
+ else
+ task->cap_permitted = CAP_INIT_EFF_SET;
+
+
+ if (task->euid != 0){
+ cap_clear (task->cap_effective);
+ }
+ else{
+ task->cap_effective = CAP_INIT_EFF_SET;
+ }
+
+ if(task->fsuid)
+ task->cap_effective &= ~CAP_FS_MASK;
+ else
+ task->cap_effective |= CAP_FS_MASK;
+
+ task_unlock(task);
+
+ return;
+}

static int __init capability_init (void)
{
@@ -72,6 +102,15 @@
secondary = 1;
}
printk (KERN_INFO "Capability LSM initialized\n");
+
+ struct task_struct *task;
+
+ read_lock(&tasklist_lock);
+ for_each_process(task){
+ recompute_capability_creds(task);
+ }
+ read_unlock(&tasklist_lock);
+
return 0;
}


[9.] My address

Bin Liang
liangbin01@xxxxxxxx
Institute of Software, Chinese Academy of Sciences, Beijing, China

Attachment: capability.2.6.0-test11.patch
Description: Binary data