PROBLEM: A Capability LSM Module serious bug

From: liangbin01@private
Date: Sun Dec 07 2003 - 04:11:51 PST

  • Next message: Serge E. Hallyn: "Re: PROBLEM: A Capability LSM Module serious bug"

    [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 
    (2.6.0-test11) or security/capability.c (2.5.72-lsm1)): 
    
    	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.5.72-lsm1 (root@LB-Server) (gcc version 3.2.2 20030222 (Red 
    Hat Linux 3.2.2-5)) #10 SMP Sun Nov 30 15:55:38 CST 2003
    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
    2.5.72-lsm1
    #sh scripts/ver_linux
    Linux LB-Server 2.5.72-lsm1 #10 SMP Sun Nov 30 15:55:38 CST 2003 i686 i686 
    i386 GNU/Linux
    
    Gnu C                  3.2.2
    Gnu make               3.79.1
    util-linux             2.11y
    mount                  2.11y
    e2fsprogs              1.32
    jfsutils               1.0.17
    reiserfsprogs          3.6.4
    pcmcia-cs              3.1.31
    PPP                    2.4.1
    isdn4k-utils           3.1pre4
    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 
    
    2.6.0-test11
    #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
    (8.1)patch for security/capability.c in 2.5.72-lsm1 is also availiable: 
    
     --- capability.c	2003-12-07 19:50:57.645799712 +0800
    +++ capability.c.new	2003-12-07 19:50:46.297524912 +0800
    @@ -306,6 +306,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)
    {
    @@ -322,6 +352,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;
    } 
    
    
    (8.2)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@private
    Institute of Software, Chinese Academy of Sciences, Beijing, China 
    
    
    
    --- capability.c	2003-12-07 19:50:57.645799712 +0800
    +++ capability.c.new	2003-12-07 19:50:46.297524912 +0800
    @@ -306,6 +306,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)
     {
    @@ -322,6 +352,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;
     }
     
    
    
    --- 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;
     }
     
    



    This archive was generated by hypermail 2b30 : Sun Dec 07 2003 - 04:20:04 PST