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

本篇將主要介紹user namespace和其餘類型的namespace的關係。html

權限涉及的範圍很是廣,因此致使user namespace比其餘的namespace要複雜; 同時權限也是容器安全的基礎,因此user namespace很是重要。node

本篇全部例子都在ubuntu-server-x86_64 16.04下執行經過linux

和其餘類型的namespace一塊兒使用

除了user namespace外,建立其它類型的namespace都須要CAP_SYS_ADMIN的capability。當新的user namespace建立並映射好uid、gid了以後, 這個user namespace的第一個進程將擁有完整的全部capabilities,意味着它就能夠建立新的其它類型namespaceshell

#先記下默認的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]

固然咱們也能夠不用這麼一步一步的建立,而是一步到位ubuntu

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函數也一樣適用。bash

和其餘類型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。app

這裏能夠看看uts namespace的結構體,裏面有一個指向user namespace的指針,指向他所屬於的user namespace,其餘類型的namespace也相似。dom

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。

參考

相關文章
相關標籤/搜索