Linux Namespaces in operation记录 - part 5

20 篇文章 0 订阅
14 篇文章 0 订阅


:本文绝大部分内容来自 Linux Namespaces实践part 5,原文系列文章详细描述了Linux Namespace相关内容,英语过关的建议阅读原文,本文内容主要用来记录学习内容,如有不当之处还请评论区指正。

User Namespace隔离了安全相关的属性,即用户ID、组ID、根目录、keyrings以及capabilities。一个进程的用户ID和组ID在User Namespace内外可以是不同的。特别地,一个进程可以在User Namespace外有非零的用户ID(普通用户),而在User Namespace内部有为零的用户ID(root用户)。

在阐述内容之前,先列举几个作者遇到的问题:

  1. sys/capability.h头文件,cap_t, cap_to_text()

    Ubuntu上需要通过sudo apt-get install libcap-dev安装库
    连接时需加上 -lcap 选项: g++ -o a.sh test.cpp -lcap

  2. userns_child_exec.c遇到write: operation not permitted问题

    系列文章写于2013年,之后Linux 3.19更新了gid_map文件的访问规则,可以参考User Namespace手册,文中与样例Example的代码注释都阐述了相关问题及解决方案

创建User Namespace

通过传递CLONE_NEWUSER标志给clone()unshare()函数可以创建一个User Namespace。Linux 3.8之后,创建User Namespace不在需要root权限。值得一提的是,Win10的wsl目前不支持CLONE_NEWUSER标志,即无法创建User Namespace,在即将到来的wsl2(目前预览版可以尝鲜)将内置完整的Linux内核,可以提供该功能。

一个栗子

本例通过demo_userns.c展示了没有特权的进程在新User Namespace中可以有特权。代码如下:

int childFunc(void *arg) {
    cap_t caps;
    for (;;) {
        printf("eUID = %ld; eGID = %ld; \n", (long)geteuid(), (long)getegid());
        caps = cap_get_proc();
        printf("capabilityies: %s\n", cap_to_text(caps, NULL));
        if (arg == NULL)
            break;
        sleep(5);
    }
    return 0;
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
    pid_t pid;
    pid = clone(childFunc, child_stack + STACK_SIZE, CLONE_NEWUSER | SIGCHLD, argv[1]);
    if (pid == -1)
        errExit("clone");
    if (waitpid(pid, NULL, 0) == -1)
        errExit("waitpid");
    return 0;
}

通过传递CLONE_NEWUSER标志给clone()函数创建了新User Namespace,之后子进程childFunc在新创建的User Namespace中打印出自己的用户ID、组ID以及capabilities。有关capability的内容参考Linux 手册 Capability。运行上述程序得到如下结果:

eric@eric_ubuntu_server:~/ns_in_opt/part_5$ id -u
1000
eric@eric_ubuntu_server:~/ns_in_opt/part_5$ id -g
1000
eric@eric_ubuntu_server:~/ns_in_opt/part_5$ ./demo_userns
eUID = 65534; eGID = 65534;
capabilityies: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep

上述输出结果展示了一些有趣的信息:

  1. capabilities: = ...表明子进程拥有所有的capability

    创建一个新User Namespace时,该Namespace中的第一个进程被授予完整的权限capability

  2. 进程的用户和组ID在Namespace内外不同

    需要将User Namespace内的用户与组ID映射到Namespace外部,对于没有映射的Namespacegetuid()getgid()会返回/proc/sys/kernel/overflowuid/proc/sys/kernel/overflowgid的内容,即65534

另外,User Namespace还是可以嵌套的,即除了初始的User Namespace,每一个User Namespace都有一个父Namespace,零个或多个字Namespace

映射用户与组ID

:如前所述,Linux 3.19对gid_map做了些更改,对于3.19及以上版本,需要先向/proc/PID/setgroups文件中写入"deny"才能对gid_map文件进行更改。

创建User Namespace之后,映射用户ID与组ID是必不可少的一步,这些映射可以通过向/proc/PID/uid_map/proc/PID/gid_map文件中写入映射信息实现。映射信息的格式如下:

ID-inside-ns ID-outside-ns length

上述信息将Namespace内从ID-inside-nsID-inside-ns + length映射到了ID-outside-ns同样长度。不妨假设ID为/proc/PID/uid_map中PID的进程为A,正在向/proc/PID/uid_map文件写信息的进程为B。ID-outside-ns取决于进程A和进程B是否在同一个User Namespace

  • 若在同一个Namespace,则ID-outside-ns即为其父Namespace中的值
  • 若不在同一个Namespace,则ID-outside-ns的值为进程B所在的Namespace中的us用户或组ID。

又一个栗子

通过给demo_userns程序传递任意一个参数可让其每隔几秒输出进程的用户、组ID以及cap。

  1. demon_userns程序传递任意参数执行,如下为输出(前两个)

    eric@eric_ubuntu_server:~/ns_in_opt/part_5$ ./demo_userns x
    eUID = 65534; eGID = 65534;
    capabilityies: = cap_chown,...
    eUID = 65534; eGID = 65534;
    capabilityies: = cap_chown,...
    

    可以看到,eUID与eGID(有效用户、组ID)都为65534

  2. 在另一个终端中执行echo '0 1000 1' > /proc/PID/uid_map命令

    eric@eric_ubuntu_server:~$ ps -C demo_userns -o 'pid uid comm'
    PID   UID COMMAND
    4738  1000 demo_userns
    4739  1000 demo_userns
    eric@eric_ubuntu_server:~$ echo '0 1000 1' > /proc/4739/uid_map
    

    即查看当前子进程的PID,之后向/proc/PID/uid_map文件中写如映射信息。

    回到另一个终端我们看到:

    eUID = 0; eGID = 65534;
    capabilityies: = cap_chown,...
    eUID = 0; eGID = 65534;
    capabilityies: = cap_chown,...
    

    子进程在新创建的User Namespace中建立了用户的映射,不再是默认65534

  3. 在上述相同的终端中执行如下命令

    eric@eric_ubuntu_server:~$ echo 'deny' > /proc/4739/setgroups
    eric@eric_ubuntu_server:~$ echo '0 1000 1' > /proc/4739/gid_map
    

    首先向/proc/PID/setgroups文件中写入字符串’deny’,之后将映射信息写入gid_map

    回到另一个终端,看到:

    eUID = 0; eGID = 0;
    capabilityies: = cap_chown,...
    capabilityies: = cap_chown,...
    

    即将子进程的User NamespaceID为0的用户/组映射到了父NamespaceID为1000的用户/组。

映射规则以及深入探索映射

关于uid_mapgid_map的映射规则不在少数,且涉及更深层次的系统细节,本文不详细讨论这些内容,感兴趣的可以参考Linux user_namespace(7),建议运行其后的示例程序以获得更直观的感受。

另外,通过shell命令或其它方式查看用户与组ID的映射时,得到的结果与当前进程所在的User Namespace有关,详细信息参考上述Linux man手册的示例程序,此处不再详细描述。

总结

Linux中User Namespace为非特权用户提供了获得特权的途径,由于其相关细节非常之多,本文仅概括性地描述了一些常见的操作及可能遇到的问题。针对·User Namespace·的运用还要结合其它类型的·Namespace·才可彰显,作者也会在接下来的学习过程中进行记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值