Linux Namespace系列(08):user namespace (CLONE_NEWUSER) (第二部分)

本篇将主要介绍user namespace和其他类型的namespace的关系。

权限涉及的范围非常广,所以导致user namespace比其他的namespace要复杂; 同时权限也是容器安全的基础,所以user namespace非常重要。

本篇所有例子都在ubuntu-server-x86_64 16.04下执行通过

和其他类型的namespace一起使用

除了user namespace外,创建其它类型的namespace都需要CAP_SYS_ADMIN的capability。当新的user namespace创建并映射好uid、gid了之后, 这个user namespace的第一个进程将拥有完整的所有capabilities,意味着它就可以创建新的其它类型namespace

#先记下默认的user namespace编号
dev@ubuntu:~$ readlink /proc/$$/ns/user
user:[4026531837]

#用非root账号创建新的user namespace
dev@ubuntu:~$ unshare --user -r /bin/bash
root@ubuntu:~# readlink /proc/$$/ns/user
user:[4026532463]

#虽然新user namespace的root账号映射到外面的dev账号
#但还是能创建新的ipc namespace,因为当前bash进程拥有全部的capabilities
root@ubuntu:~# cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
root@ubuntu:~# readlink /proc/$$/ns/ipc
ipc:[4026531839]
root@ubuntu:~# unshare --ipc /bin/bash
root@ubuntu:~# readlink /proc/$$/ns/ipc
ipc:[4026532469]

当然我们也可以不用这么一步一步的创建,而是一步到位

dev@ubuntu:~$ readlink /proc/$$/ns/user
user:[4026531837]
dev@ubuntu:~$ readlink /proc/$$/ns/ipc
ipc:[4026531839]
dev@ubuntu:~$ unshare --user -r --ipc /bin/bash
root@ubuntu:~# readlink /proc/$$/ns/user
user:[4026532463]
root@ubuntu:~# readlink /proc/$$/ns/ipc
ipc:[4026532469]

在unshare的实现中,就是传入了CLONE_NEWUSER | CLONE_NEWIPC,大致如下

unshare(CLONE_NEWUSER | CLONE_NEWIPC);

在上面这种情况下,内核会保证CLONE_NEWUSER先被执行,然后执行剩下的其他CLONE_NEW*,这样就使得不用root账号而创建新的容器成为可能,这条规则对于clone函数也同样适用。

和其他类型namespace的关系

Linux下的每个namespace,都有一个user namespace和他关联,这个user namespace就是创建相应namespace时进程所属的user namespace,相当于每个namespace都有一个owner(user namespace),这样保证对任何namespace的操作都受到user namespace权限的控制。这也是上一篇中为什么sethostname失败的原因,因为要修改的uts namespace属于的父user namespace,而新user namespace的进程没有老user namespace的任何capabilities。

这里可以看看uts namespace的结构体,里面有一个指向user namespace的指针,指向他所属于的user namespace,其他类型的namespace也类似。

struct uts_namespace {
  struct kref kref;
  struct new_utsname name;
  struct user_namespace *user_ns;
  struct ns_common ns;
};

不和任何user namespace关联的资源

在系统中,有些需要特权操作的资源没有跟任何user namespace关联,比如修改系统时间(需要CAP_SYS_MODULE)、创建设备(需要CAP_MKNOD),这些操作只能由initial user namespace里有相应权限的进程来操作(这里initial user namespace就是系统启动后的默认user namespace)。

和mount namespace的关系

  • 当和mount namespace一起用时,不能挂载基于块设备的文件系统,但是可以挂载下面这些文件系统

     #摘自user namespaces帮助文件: 
     #http://man7.org/linux/man-pages/man7/user_namespaces.7.html
     * /proc (since Linux 3.8)  
     * /sys (since Linux 3.8) 
     * devpts (since Linux 3.9)
     * tmpfs (since Linux 3.9)
     * ramfs (since Linux 3.9)
     * mqueue (since Linux 3.9)
     * bpf (since Linux 4.4)

示例

#创建新的user和mount namespace
dev@ubuntu:~$ unshare --user -r --mount bash
root@ubuntu:~# mkdir ./mnt
#查找挂载到根目录的设备
root@ubuntu:~#  mount|grep " / "
/dev/mapper/ubuntu--vg-root on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)
#确认这个设备肯定是块设备
root@ubuntu:~# ls -l /dev/mapper/ubuntu--vg-root
lrwxrwxrwx 1 nobody nogroup 7 7月  30 11:22 /dev/mapper/ubuntu--vg-root -> ../dm-0
root@ubuntu:~# file /dev/dm-0
/dev/dm-0: block special (252/0)
#尝试把他挂载到其他目录,结果挂载失败,说明在新的user namespace下没法挂载块设备
root@ubuntu:~# mount /dev/mapper/ubuntu--vg-root ./mnt
mount: /dev/mapper/ubuntu--vg-root is write-protected, mounting read-only
mount: cannot mount /dev/mapper/ubuntu--vg-root read-only
#即使是root账号映射过去也不行(这里mount的错误提示的好像不太准确)
root@ubuntu:~$ exit
exit
dev@ubuntu:~$ sudo unshare --user -r --mount bash
root@ubuntu:~# mount /dev/mapper/ubuntu--vg-root ./mnt
mount: /dev/mapper/ubuntu--vg-root is already mounted or /home/dev/mnt busy
       /dev/mapper/ubuntu--vg-root is already mounted on /

#由于当前pid namespace不属于当前的user namespace,所以挂载/proc失败
root@ubuntu:~# mount -t proc none ./mnt
mount: permission denied
#创建新的pid namespace,然后挂载成功
root@ubuntu:~# unshare --pid --fork bash
root@ubuntu:~# mount -t proc none ./mnt
root@ubuntu:~# mount |grep mnt|grep proc
none on /home/dev/mnt type proc (rw,nodev,relatime)
root@ubuntu:~# exit
exit

#只能通过bind方式挂载devpts,直接mount报错
root@ubuntu:~# mount -t devpts devpts ./mnt
mount: wrong fs type, bad option, bad superblock on devpts,
       missing codepage or helper program, or other error

       In some cases useful info is found in syslog - try
       dmesg | tail or so.
root@ubuntu:~# mount --bind /dev/pts ./mnt
root@ubuntu:~# mount|grep mnt|grep devpts
devpts on /home/dev/mnt type devpts (rw,nosuid,noexec,relatime,mode=600,ptmxmode=000)

#sysfs直接mount和bind mount都不行
root@ubuntu:~# mount -t sysfs sysfs ./mnt
mount: permission denied
root@ubuntu:~# mount --bind /sys ./mnt
mount: wrong fs type, bad option, bad superblock on /sys,
       missing codepage or helper program, or other error

       In some cases useful info is found in syslog - try
       dmesg | tail or so.
#TODO: 对于sysfs和devpts,和帮助文件中描述的对不上,不确定是我理解有问题,还是测试环境有问题,等以后有新的理解后再来更新

# 挂载tmpfs成功
root@ubuntu:~# mount -t tmpfs tmpfs ./mnt
root@ubuntu:~# mount|grep mnt|grep tmpfs
tmpfs on /home/dev/mnt type tmpfs (rw,nodev,relatime,uid=1000,gid=1000)
#ramfs和tmpfs类似,都是内存文件系统,这里就不演示了

#对mqueue和bpf不太熟悉,在这里也不演示了
  • 当mount namespace和user namespace一起用时,就算老mount namespace中的mount point是shared并且用unshare命令时指定了--propagation shared,新mount namespace里面的挂载点的propagation type还是slave。这样就防止了在新user namespace里面mount的东西被外面父user namespace中的进程看到。

#准备目录和disk
dev@ubuntu:~$ mkdir -p disks/disk1
dev@ubuntu:~$ dd if=/dev/zero bs=1M count=32 of=./disks/disk1.img
dev@ubuntu:~$ mkfs.ext2 ./disks/disk1.img

#mount好disk,确认是shared
dev@ubuntu:~$ sudo mount /home/dev/disks/disk1.img /home/dev/disks/disk1
dev@ubuntu:~$ cat /proc/self/mountinfo |grep disk| sed 's/ - .*//'
164 24 7:1 / /home/dev/disks/disk1 rw,relatime shared:105

#先不创建user namespace,看看效果,便于和后面的结果比较,
#当不和user namespace一起用时,新mount namespace中的挂载点为shared
dev@ubuntu:~$ sudo unshare --mount --propagation shared /bin/bash
root@ubuntu:~# cat /proc/self/mountinfo |grep disk| sed 's/ - .*//'
220 174 7:1 / /home/dev/disks/disk1 rw,relatime shared:105
root@ubuntu:~# exit
exit

#创建mount namesapce的同时,创建user namespace,
#可以看出,虽然指定的是--propagation shared,但得到的结果还是slave(master:105)
#由于指定了--propagation shared, 系统为我们新创建了一个peer group(shared:154),
#并让新mount namespace中的挂载点属于它,
#这里同时也说明一个挂载点可以属于多个peer group。
dev@ubuntu:~$ unshare --user -r --mount --propagation shared /bin/bash
root@ubuntu:~# cat /proc/self/mountinfo |grep disk| sed 's/ - .*//'
220 174 7:1 / /home/dev/disks/disk1 rw,relatime shared:154 master:105

其他可以写map文件的情况

  1. 在有一种情况下,没有CAP_SETUID的权限也可以写uid_map和gid_map,那就是在父user namespace中用新user namespace的owner来写,但是限制条件是只能在里面映射自己的账号,不能映射其他的账号。

  2. 在新user namespace中用有CAP_SETUID权限的账号可以来写map文件,但跟上面的情况一样,只能映射自己。细心的朋友可能觉察到了,那就是都还没有映射,新user namespace里的账号怎么有CAP_SETUID的权限呢?关于这个问题请参考下一节(创建新user namespace时capabilities的变迁)的内容。

由于演示第二种情况需要写代码(可以参考unshare的实现),这里就只演示第一种情况:

#--------------------------第一个shell窗口----------------------
#创建新的user namespace并记下当前shell的pid
dev@ubuntu:~$ unshare --user /bin/bash
nobody@ubuntu:~$ echo $$
25430
#--------------------------第二个shell窗口----------------------
#映射多个失败
dev@ubuntu:~$ echo '0 1000 100' > /proc/25430/uid_map
-bash: echo: write error: Operation not permitted
#只映射自己成功,1000是dev账号的ID
dev@ubuntu:~$ echo '0 1000 1' > /proc/25430/uid_map

#设置setgroups为"deny"后,设置gid_map成功
dev@ubuntu:~$ echo '0 1000 1' > /proc/25430/gid_map
-bash: echo: write error: Operation not permitted
dev@ubuntu:~$ cat /proc/25430/setgroups
allow
dev@ubuntu:~$ echo "deny" > /proc/25430/setgroups
dev@ubuntu:~$ cat /proc/25430/setgroups
deny
dev@ubuntu:~$ echo '0 1000 1' > /proc/25430/gid_map
dev@ubuntu:~$

#--------------------------第一个shell窗口----------------------
#回到第一个窗口后重新加载bash,显示当前账号已经是root了
nobody@ubuntu:~$ exec bash
root@ubuntu:~# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)

上面写了"deny"到文件/proc/[pid]/setgroups, 是为了限制在新user namespace里面调用setgroups函数来设置groups,这个主要是基于安全考虑。考虑这样一种情况,一个文件的权限为"rwx---rwx",意味着other group比自己group有更大的权限,当用setgroups(2)去掉自己所属的相应group后,会获得更大的权限,这个在没有user namespace之前不是个问题,因为调用setgroups需要CAP_SETGID权限,但有了user namespace之后,一个普通的账号在新的user namespace中就有了所有的capabilities,于是他可以通过调用setgroups的方式让自己获得更大的权限。

创建新user namespace时capabilities的变迁

              clone函数                              unshare函数
+----------------------------------+    +----------------------------------+
|   父进程        |    子进程       |    |              当前进程             |
|----------------------------------+    |----------------------------------+
|   启动                           |    |               启动                |
|    | ①                           |    |                | ①               |
|    ↓                             |    |                ↓                 |
|  clone  ----------→  启动        |    |              unshare             |
|    |                  | ②        |    |                | ②               |
|    |                  ↓          |    |                ↓                 |
|    | ④               exec        |    |               exec               |
|    |                  | ③        |    |                | ③               |
|    ↓                  ↓          |    |                ↓                 |
|   结束               结束         |    |               结束               |
+----------------------------------+    +----------------------------------+

上面描述了进程在调用不同函数后所处的不同阶段,clone函数会创建新的进程,unshare不会。

  • ①: 处于父user namespace中,进程拥有的capabilities由调用该进程的user决定

  • ②: 处于子user namespace中,这个时候进程拥有所在子user namespace的所有capabilities,所以在这里可以写当前进程的uid/gid map文件,但只能映射当前账号,不能映射任意账号。这里就回答了上一节中为什么没有映射账号但在子user namespace有CAP_SETUID权限的问题。

  • ③: 处于子user namespace中,调用exec后,由于没有映射,系统会去掉当前进程的所有capabilities(这个是exec的机制),所以到这个位置的时候当前进程已经没有任何capabilities了

  • ④: 处于父user namespace中,和①一样。但这里是一个很好的点来设置子user namespace的map文件,如果能在exec执行之前设置好子进程的map文件,exec执行完后当前进程还是有相应的capabilities。但是如果没有在exec执行之前设置好,而是exec之后设置,当前进程还是没有capabilities,就需要再次调用exec后才有。如何在④这个地方设置子进程的map文件需要一点点技巧,可以参考帮助文件最后面的示例代码。

对于unshare来说,由于没有④,所有没法映射任意账号到子user namespace,这也是为什么unshare命令只能映射当前账号的原因。

其它

和pid namespace类似,当在程序中用UNIX domain sokcet将一个user namespace的uid或者gid发送给另一个user namespace中的进程时,内核会自动映射成目的user namespace中对应的uid或者gid。

参考


Linux程序员
专注Linux相关技术
3.7k 声望
1.6k 粉丝
0 条评论
推荐阅读
走进docker(07):docker start命令背后发生了什么?
在上一篇介绍过了docker create之后,这篇来看看docker start是怎么根据create之后的结果运行容器的。 启动容器 在这里我们先启动上一篇中创建的那个容器,然后看看docker都干了些什么。 {代码...} start的大概流...

public08218阅读 14.1k评论 5

工具篇:iTerm与Zsh
iTerm2支持许多的主题配色,可以自己定义,也可以参考网上现成的主题配色。我个人比较喜欢draculatheme配色。支持item,vim,phpstorm , 下方存在主题官网路径,按照教程安装即可。

super白4阅读 4.8k

深入剖析容器网络和 iptables
Docker 能为我们提供很强大和灵活的网络能力,很大程度上要归功于与 iptables 的结合。在使用时,你可能没有太关注到 iptables 的作用,这是因为 Docker 已经帮我们自动完成了相关的配置。

张晋涛3阅读 1.3k

封面图
麒麟操作系统 (kylinos) 从入门到精通 - 常用软件安装 - 第三篇 常用软件安装(windows下的习惯)
本篇内容大部分从应用商店进行安装,部分通过官网下载,少部分通过命令行安装。1.原生应用1.1钉钉1.2飞书1.3 蓝信1.4 腾讯文档1.5 金山文档1.6 搜狗输入法(拼音)1.7 五笔输入法1.8 libreoffice官方也带了WPS,...

码上世界3阅读 7.6k评论 17

封面图
.记一次使用gdb诊断gc问题全过程
上次解决了GC长耗时问题后,系统果然平稳了许多,这是之前的文章《GC耗时高,原因竟是服务流量小?》 然而,过了一段时间,我检查GC日志时,又发现了一个GC问题,如下: 从这个图中可以发现,我们GC有一些尖峰,...

扣钉日记2阅读 675

封面图
麒麟操作系统 (kylinos) 从入门到精通 - 办公环境 - 第十三篇 字体安装与windows字体的利用
正常情况下,系统下的wps或libreoffice用到的字体都已默认安装。但有时候一些ttf字体或者microsoft office下一些字体需要安装。我们可以在安装好office、acrobat之后,将C:\windows\Fonts文件夹中的字体(我是win...

码上世界2阅读 4.1k

封面图
麒麟操作系统 (kylinos) 从入门到精通 - 影音娱乐- 第三十篇 利用PKS观看电视台(IPTV)直播
类别:笔记本型号:中国长城 UF712硬件平台:飞腾处理器(ArmV8 指令集)系统:银河麒麟操作系统 V10 SP1(2203) 关键词:信创,麒麟系统,linux,PKS,银河麒麟,飞腾,arm64,arm,湖南卫视,中央电视台,电视直播,iptv

码上世界1阅读 3.9k

封面图
3.7k 声望
1.6k 粉丝
宣传栏