4 User_ns实现与验证
以上分析的三点都是用户安全管理的基础,user_ns只是结合权能实现了一种局部用户的映射。User_ns的作用简单的说就是一个非特权用户可以创建一个user_ns,然后该用户可以作为该命名空间中的root特权用户,其它的跟在宿主机没有两样。在user_ns中该用户可以创建pid、net_ns等命名空间,并对其有root特权控制属于该命名空间的资源。
4.1 User_ns实现分析
1、 user_ns的虚拟化
设计是基于container中的uid与host中的uid的一一映射,这样的映射关系内核是通过proc文件系统实现的,在proc文件系统添加了两个控制参数,/proc/pid/uid和/proc/pid/gid,系统管理员可以操作这两个proc文件来管理user_ns的全局分配,完成用户的虚拟化。举例如下:
uid = 1000 0 100
gid = 1000 0 100
表示新的user_ns中的uid=0,uid=1的用户分别对应host的uid=1000,uid=1001的用户,其它的类似得出。
static const struct file_operationsproc_uid_map_operations = {
.open = proc_uid_map_open,
.write = proc_uid_map_write,
.read = seq_read,
.llseek = seq_lseek,
.release = proc_id_map_release,
};
以上为proc的注册函数,其中具体代码只是对一些全局变量的解析与复制,详细过程可参见具体代码实现。
2、 user_ns创建初始化
当应用程序新创建一个user_ns,内核会给这个进程的cred安全策略赋予所有的权能,以使得执行创建命令的用户可以成为新user_ns的root用户。未加命名空间之前的代码实现只是将cred完全copy过来。新的代码如下:
intcreate_user_ns(struct cred *new)
{
struct user_namespace *ns, *parent_ns =new->user_ns;
kuid_t owner = new->euid;
kgid_t group = new->egid;
if (!kuid_has_mapping(parent_ns, owner) ||
!kgid_has_mapping(parent_ns, group))
return -EPERM;
ns = kmem_cache_zalloc(user_ns_cachep,GFP_KERNEL);
if (!ns)
return -ENOMEM;
kref_init(&ns->kref);
ns->parent = parent_ns;
ns->owner = owner;
ns->group = group;
/* 将所有的权能赋值给新的进程 */
new->securebits = SECUREBITS_DEFAULT;
new->cap_inheritable = CAP_EMPTY_SET;
new->cap_permitted = CAP_FULL_SET;
new->cap_effective = CAP_FULL_SET;
new->cap_bset = CAP_FULL_SET;
#ifdefCONFIG_KEYS
key_put(new->request_key_auth);
new->request_key_auth = NULL;
#endif
/* tgcred will be cleared in our caller bcCLONE_THREAD won't be set */
/* Leave the new->user_ns referencewith the new user namespace. */
/* Leave the reference to our user_ns withthe new cred. */
new->user_ns = ns;
return 0;
}
该新进程会与execve的执行进行结合组成新命令执行的上下文环境,具体可参见3.3应用程序运行时设置信任值一节。
3、 资源操作时权能检查的关键函数举例
由于现在系统有了user_ns,对用户进行了虚拟化,现在该user_ns中的用户只能访问属于该user_ns中的资源,所以内核在各个访问资源的入口处应该都有相应的函数判断,这样的函数有好几个,但是功能都大同小异,关键看其应用场景,现举例如下:
boolinode_capable(const struct inode *inode, int cap)
{
struct user_namespace *ns =current_user_ns();
return ns_capable(ns, cap) &&kuid_has_mapping(ns, inode->i_uid);
}
该函数的作用检查该user_ns是否具有cap权能,并且通过kuid_has_mapping函数来检查要访问的资源的uid是不是属于该user_ns所对应的uid区域,如果检查通过就返回0,否则返回非0。
4.2 举个实际访问资源例子
1、 访问ext4文件
当在命令行键入如下命令ls –l /home时,shell进程会fork一个新的进程来执行这个命令,当fork新进程时如果设置了user_namespace标志位,则新的进程的安全属性将包括所有的权能位(详见4.1.2 user_ns创建初始化)。接下去该进程会调用execve函数组织新的进程的上下文执行环境,并重新对进程的权能进行计算(详见3.3应用程序运行时权能的设置),如果是user_namespace的root用户则拥有所有的权能。接下去就是文件访问了,首先会进行访问权限的判断对于该命令是读取目录中的文件列表,其访问检查会通过调用generic_permission来检查(详见3.4.3文件系统检查ACL权限),通过检查则返回0,否则不允许访问该资源。
2、 proc文件系统访问
proc文件系统的访问前面过程跟上例一样,只是在调用permission回调函数时有所不同,proc文件系统的注册函数为proc_sys_permission,具体调用了sysctl_perm函数。代码如下:
staticint sysctl_perm(struct ctl_table_root *root, struct ctl_table *table, int op)
{
int mode;
if (root->permissions)
mode =root->permissions(root, current->nsproxy, table);
else
mode = table->mode;
return test_perm(mode, op);
}
对于已经局部化的参数来说,其注册了root->permission函数,其中会检查权能,如果进程有此权能则将mod