-
Linux Usernamespace は namespace ごとに UID / GID をマッピングすることができる
- これはプロセスが namespace の内と外で違う UID / GID を持つことが可能ということを指す
- プロセスは namespace の外では0以外のUIDを持つことができるのと同時に namespace 内では UID を0として持つことができることになる
- つまり、user namespace 外では特権を持たないが、user namespace 内では root 権限を持つことできるということ。
-
user namespace が作成されたあとに行うのは、UID / GID の mapping
- これは
/proc/PID/uid_map
と/proc/PID/gid_map
に書き込まれることで行われる。 - フォーマットは以下のような感じ
- これは
ID-inside-ns ID-outside-ns length
ID-inside-ns
とID-outsides-ns
はそれぞれの名前空間における開始UID。length
はマッピングする範囲ID-inside-ns
… プロセスのユーザー名前空間におけるUIDの範囲の開始値ID-outside-ns
…ID-inside-ns
で指定された UID がマッピングされる先の UID の範囲の開始値- 見える値は読み出したプロセスのユーザー名前空間のユーザー ID マッピングに依存する。
- 2 つのプロセスが異なるユーザー名前空間に属す場合、 2 番目のフィールドは uid_map をオープンしたプロセスのユーザー名前空間におけるユーザー ID の範囲の開始値
- 2 つのプロセスが同じユーザー名前空間に属す場合、 2 番目のフィールドはプロセス pid の親のユーザー名前空間におけるユーザー ID の範囲の開始値
length
… 2 つのユーザー名前空間間でマッピングされるユーザー ID の範囲の長さ
vagrant@ubuntu-bionic:~/shared/uns$ unshare --user
nobody@ubuntu-bionic:~/shared/uns$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
nobody@ubuntu-bionic:~/shared/uns$ cat /proc/$$/uid_map
nobody@ubuntu-bionic:~/shared/uns$ cat /proc/$$/gid_map
nobody@ubuntu-bionic:~/shared/uns$ echo $$
3585
vagrant@ubuntu-bionic:~$ echo '0 1000 1' | sudo tee /proc/3585/uid_map
0 1000 1
vagrant@ubuntu-bionic:~$ echo '0 1000 1' | sudo tee /proc/3585/gid_map
0 1000 1
nobody@ubuntu-bionic:~/shared/uns$ id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
nobody@ubuntu-bionic:~/shared/uns$ cat /proc/$$/uid_map
0 1000 1
nobody@ubuntu-bionic:~/shared/uns$ cat /proc/$$/gid_map
0 1000 1
-
一つの User Namespace につき、一度だけ書き込める
- 一度書き込むと二回目以降は書き込めない
-
user namespace は user namespace をネストすることができる。
-
uid_map
とgid_map
には複数行書き込める- ただし、各行で指定される UID / GID の範囲は他の行が指定する範囲と重なってはならない
- Linux 4.14 以前は5行までだったが、4.15 以降は340行まで
vagrant@ubuntu-bionic:~$ unshare --user
nobody@ubuntu-bionic:~$ echo $$
6132
nobody@ubuntu-bionic:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
root@ubuntu-bionic:/home/vagrant# echo 1|awk '{print "0 1000 1\n1002 1002 1"}' > /proc/6132/uid_map
root@ubuntu-bionic:/home/vagrant# echo 1|awk '{print "0 1000 1\n1002 1002 1"}' > /proc/6132/gid_map
nobody@ubuntu-bionic:~$ bash
root@ubuntu-bionic:~# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
root@ubuntu-bionic:~# su - user
user@ubuntu-bionic:~$ id
uid=1002(user) gid=1002(user) groups=1002(user)
- mapping 処理
[000] 10443.843267: funcgraph_entry: | __vfs_write() {
[000] 10443.843267: funcgraph_entry: | proc_uid_map_write() {
[000] 10443.843267: funcgraph_entry: | map_write() {
[000] 10443.843267: funcgraph_entry: | mutex_lock() {
[000] 10443.843267: funcgraph_entry: | _cond_resched() {
[000] 10443.843268: funcgraph_entry: 0.038 us | rcu_all_qs();
[000] 10443.843268: funcgraph_exit: 0.343 us | }
[000] 10443.843268: funcgraph_exit: 0.673 us | }
[000] 10443.843268: funcgraph_entry: | file_ns_capable() {
[000] 10443.843269: funcgraph_entry: | security_capable() {
[000] 10443.843269: funcgraph_entry: 0.085 us | cap_capable();
[000] 10443.843269: funcgraph_entry: 0.085 us | apparmor_capable();
[000] 10443.843270: funcgraph_exit: 1.169 us | }
- この
map_write()
でuid_map
が正しいフォーマットであるかを検証しているロジックが入ってリウ- ちなみに一度だけ書き込みが許可されるというのは ここ
- 📝
nr_extents
は書き込んだ回数(というか uid_map の行数もかな?)を保持するやつっぽい
- 📝
- ちなみに一度だけ書き込みが許可されるというのは ここ
-
insert_extent
を呼び出しているinsert_extent
はuid_gid_map
構造体に新しい idmap を挿入する関数
- ここ で
lower_first
に親の Namespace の ID が入るっぽい? - ここ で実際に使える UID かチェック
- 続いて 修正コミットで位置が変更された処理 🔥
sort_idmap
という名前のとおり idmap をソートするやつUID_GID_MAP_MAX_BASE_EXTENTS
を超えたときに呼ばれる- これは5ですね
- 4.14以前までの uid_map / gid_map に書き込める行数
- 親の user namespace からカーネルのグローバルID空間に対して lower ids をマップする
- lower ids は直訳すると下位IDだけど、ここでは
lower_first
、つまり、uid_map
の2番目のフィールドかな? - 今回の脆弱性はカーネルから見たUIDがおかしくなる、ということなのでここが重要そう🔥
- lower ids は直訳すると下位IDだけど、ここでは
- lower_first は書き換えられる
-
map_id_range_down
での条件分岐 - これも
uid_map
が5行以上であればmap_id_range_down_max
が呼ばれる - 親の namespace の idmap をバイナリサーチしているが、具体的に何の idmap を探し出しているんだ?一番最初の id ?
-
ここまで読んでようやく extents
の意味( uid_map
, gid_map
の範囲) が理解できる感じだったのかー。