マウントプロパゲーション(7)〜 Namespaceとプロパゲーション(1)〜
これまでの続きです。まだまだ続きます。
- マウントプロパゲーション(1)〜 shared mount 〜 - TenForward
- マウントプロパゲーション(2)〜 private mount 〜 - TenForward
- マウントプロパゲーション(3)〜 slave mount 〜 - TenForward
- マウントプロパゲーション(4)〜 unbindable mount 〜 - TenForward
- マウントプロパゲーション(5)〜 mountinfoファイル 〜 - TenForward
- マウントプロパゲーション(6)〜 mountinfoファイル(2)〜 - TenForward
これまでと同様に完全に私個人が理解するための資料です(man 7 mount_namespaceみれば書いてますし)。間違いの指摘は大歓迎です。
マウントプロパゲーションとMount Namespaceの関係も見ておきましょう。
今回はprivateとsharedの場合にMount Namespaceが別になるとどうなるかを見てみます。
次のようにprivateなマウントである/mnt_priv
とsharedな/mnt_shrd
を準備します。以下の実行例ではふたつのシェルを使いますので、shell_1
とshell_2
とプロンプトを変えています。
shell_1# mkdir /mnt_priv shell_1# mkdir /mnt_shrd shell_1# mount -t tmpfs tmpfs /mnt_priv/ shell_1# mount -t tmpfs tmpfs /mnt_shrd/ shell_1# mount --make-private /mnt_priv/ (privateに設定) shell_1# mount --make-shared /mnt_shrd/ (sharedに設定)
ここでmountinfo
ファイルを確認しておきましょう。
shell_1# grep /mnt /proc/self/mountinfo 325 31 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw 382 31 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw
この例ではmnt_shrd
の方はIDが242のsharedとなっています。いっぽうで/mnt_priv
の方はエントリーがありませんのでprivateであることがわかります。
ここで別のシェルを開いてMount Namespaceを作成してみましょう。
shell_2# unshare --mount --propagation unchanged /bin/bash (Mount Namespaceを作成) shell_2# grep /mnt /proc/self/mountinfo 735 666 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw 736 666 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw
Mount Namespaceを作成するとき親のNamespaceのプロパゲーション設定を変更しないように--propagation unchanged
を指定しています。親のNamespaceとmountinfo
ファイルを比較すると、親のマウントの状態がそのままコピーされていることがわかります(ID:242が同じ)。
ここでこの名前空間内で新たにマウント操作を行ってみましょう。
shell_2# mount -t tmpfs tmpfs /mnt_priv/a shell_2# mount -t tmpfs tmpfs /mnt_shrd/b shell_2# grep /mnt /proc/self/mountinfo 735 666 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw 736 666 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw 737 735 0:56 / /mnt_priv/a rw,relatime - tmpfs tmpfs rw 738 736 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw
共有マウント下の/mnt_shrd/b
はsharedとして新たにIDが割り当たっています。いっぽうでprivateの/mnt_priv/a
はprivateです。ここで最初のシェルに戻ってみましょう。
shell_1# grep /mnt /proc/self/mountinfo 325 31 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw 382 31 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw 739 382 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw (2番目のシェルでsharedだったマウント)
2番目のシェルでsharedとなっていたID:222のマウントが共有されています。
ここでさらに最初のシェルでマウント操作を行ってみます。
shell_1# mount -t tmpfs tmpfs mnt_priv/c shell_1# mount -t tmpfs tmpfs mnt_shrd/d shell_1# grep /mnt /proc/self/mountinfo 325 31 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw 382 31 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw 739 382 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw 753 325 0:58 / /mnt_priv/c rw,relatime - tmpfs tmpfs rw 754 382 0:59 / /mnt_shrd/d rw,relatime shared:255 - tmpfs tmpfs rw
新たに行ったマウントで、private以下のマウントはprivate、shared以下のマウントにはあらたにID:255が割り当たったsharedとなっています。
ここで再度2番目のシェルを確認してみると、次のようにID:255のマウントも共有されていることがわかります。
shell_2# grep /mnt /proc/self/mountinfo 735 666 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw 736 666 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw 737 735 0:56 / /mnt_priv/a rw,relatime - tmpfs tmpfs rw 738 736 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw 755 736 0:59 / /mnt_shrd/d rw,relatime shared:255 - tmpfs tmpfs rw (最初のシェルでsharedだったマウント)
このようにsharedであればMount Namespaceをまたがっていてもマウントが共有されます。
マウントプロパゲーション(6)〜 mountinfoファイル(2)〜
これまでの続きです。
- マウントプロパゲーション(1)〜 shared mount 〜 - TenForward
- マウントプロパゲーション(2)〜 private mount 〜 - TenForward
- マウントプロパゲーション(3)〜 slave mount 〜 - TenForward
- マウントプロパゲーション(4)〜 unbindable mount 〜 - TenForward
- マウントプロパゲーション(5)〜 mountinfoファイル 〜 - TenForward
これまでと同様に完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。
前回書ききれなかった /proc/[PID]/mountinfo
ファイルのエントリーのお話です。
unbindableの場合
unbindableを指定した場合は次のようなエントリになります。
369 31 252:2 /root/test/test/orig /root/test/test/bind rw,relatime unbindable - ext4 /dev/vda2 rw
propagate_fromの場合
propagete_from
はslaveマウントなのですが、slaveとなっているマウントからmasterが見えないときに表示されます。これは、例えばchroot
したあと、masterとなるマウントがroot範囲外となった場合などに起こります。
図1のように、②を③にバインドマウントし、③を④にバインドマウントし、②は③のmaster、③は④のmasterとなっているとします(③のようにmasterかつslaveという設定ができます)。
このようにslaveが連鎖した状態で①の/mnt
にchroot
すると、④からmasterである③は見えません。③は②のslaveであり、④から②は見えますので、④のエントリにはpropagate_from:②
のように書かれます。実際は②はマウントポイントではないので、IDとしては①のIDが書かれます。
文章にするとわかりづらいですね。実際に試してみましょう(実際に試してもわかりづらいですが…)。
まずは後の操作の準備を行います。ここでは次のような操作を行います。
/
を/mnt
にバインドマウントする/proc
を/mnt/proc
にバインドマウントする/mnt
を一度privateにする。これは/mnt
はsharedにしたいのですが、他と共有を行わない独立したsharedとするためです/mnt
をsharedにする
これで/mnt
は独自のIDを持つsharedマウントとなります。
# mount --bind / /mnt ... (1) # mount --bind /proc /mnt/proc ... (2) # mount --make-private /mnt ... (3) # mount --make-shared /mnt ... (4) # cat /proc/self/mountinfo | grep "/mnt" 231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered 238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw
ここでmountinfo
ファイルを確認すると上のようになります。ここで/mnt
に割り当たっているIDは124となっていますが、これは他とマウントは共有していないマウントです。
次にひとつめのslaveマウントを作成します。図1の②を③にバインドマウントします。
# mkdir /mnt2/etc # mount --bind /mnt/etc /mnt2/etc # cat /proc/self/mountinfo | grep "/mnt" 231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered 238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw 252 25 8:1 /etc /mnt2/etc rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
これだけでは上のように③(/mnt2/etc
)は②(/mnt/etc
、つまりは①の/mnt
)とマウントを共有するsharedマウントになっていますので、slaveに設定した上で、あとで行うバインドマウントのためにsharedにも設定します。
- ③(`/mnt2/etc')をslaveに設定する
- ③をsharedに設定する
# mount --make-slave /mnt2/etc ... (1) # mount --make-shared /mnt2/etc ... (2) # cat /proc/self/mountinfo | grep "/mnt" 231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered 238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw 252 25 8:1 /etc /mnt2/etc rw,relatime shared:131 master:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
mountinfo
を確認すると③(/mnt2/etc
)は独自のID(131)を持つshared、かつ①のマウント(②のマウントポイント)を参照するslaveとなっています(masterが①のIDである124)。
さらに次のslaveマウントを行います。
- ③(
/mnt2/etc
)を④(/mnt/tmp/etc
)にバインドマウントする - ④(
/mnt/tmp/etc
)をslaveに設定する
# mkdir /mnt/tmp/etc # mount --bind /mnt2/etc /mnt/tmp/etc ... (1) # mount --make-slave /mnt/tmp/etc ... (2) # cat /proc/self/mountinfo | grep "/mnt" 231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered 238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw 252 25 8:1 /etc /mnt2/etc rw,relatime shared:131 master:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered 265 231 8:1 /etc /mnt/tmp/etc rw,relatime master:131 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
mountinfo
を確認すると④(/mnt/tmp/etc
)は③(/mnt/etc
)をmasterとするslaveになっていることがわかります。
これで図1の状態になりましたので、①(/mnt
)にchroot
してみましょう。
# chroot /mnt # cat /proc/self/mountinfo 231 25 8:1 / / rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered 238 231 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw 265 231 8:1 /etc /tmp/etc rw,relatime master:131 propagate_from:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
③(/mnt2/etc
)が④(/mnt/tmp/etc
)から見えなくなりましたので、④のエントリーには②(つまりは①)のIDである124がpropagate_from
として表示されています。
マウントプロパゲーション(5)〜 mountinfoファイル 〜
これまでの続きです。
- マウントプロパゲーション(1)〜 shared mount 〜 - TenForward
- マウントプロパゲーション(2)〜 private mount 〜 - TenForward
- マウントプロパゲーション(3)〜 slave mount 〜 - TenForward
- マウントプロパゲーション(4)〜 unbindable mount 〜 - TenForward
これまでと同様に完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。
ここまでの説明でマウント情報を確認するために説明もなく/proc/self/mountinfo
ファイルを使っていました。少しこのファイルについて説明しておきましょう。/proc/self
は次のようにカレントプロセスの情報へのリンクになっています。
$ ls -l /proc/self lrwxrwxrwx 1 root root 0 6月 23日 21:55 /proc/self -> 5771/
/proc/[PID]
以下にはそのPIDを持つプロセスの情報が収められています。/proc/[PID]/mountinfo
は、そのPIDのプロセスから見えるマウント情報が含まれています。1行がひとつのマウントポイントの情報を表しています。
先の説明で使ったmountinfo
の一行を見てみましょう。
714 30 0:74 / /root/test/orig/tmp rw,relatime shared:346 - tmpfs tmpfs rw
1行には11のフィールドが含まれています。このうち7番目がマウントプロパゲーションに関係するエントリです。この7番目のフィールドはオプショナルフィールドであり、7番目に上の例で8番目に表示されている-
が表示されていれば、7番目のフィールドは存在しないということです(8番目のフィールドは7番目がオプショナルですので区切りのために存在します)。
この7番目のオプショナルフィールドには4種類の文字列が入ります。
表1 オプショナルフィールドの文字列の説明
文字列 | 説明 |
---|---|
shared:X | マウントはピアグループ"X"で共有されているsharedマウント |
master:X | マウントはピアグループ"X"のslaveマウント |
propagate_from:X | マウントはslaveマウント |
unbindable | マウントはバインドできない(unbindable) |
ここでピアグループとは、同じマウントが伝播するグループで、ピアグループにはカーネルが割り当てる一意なIDが割り当てられます。
sharedの場合
「マウントプロパゲーション(1)〜 shared mount 〜 - TenForward」のsharedマウントと同様の操作をした状態でmountinfoファイルを見ると次のようになります。
31 1 252:2 / / rw,relatime shared:1 - ext4 /dev/vda2 rw 131 31 252:2 /root/test/test/orig /root/test/test/bind rw,relatime shared:1 - ext4 /dev/vda2 rw
先の例ではバインドマウントした側と、元の/
のマウント側の両方でお互いにマウント操作が伝播していました。上を見ると双方ともshared:1
となっており、両方ともsharedでかつIDが1
で同じですのでマウントが相互に伝播したわけです。
private場合
次にprivateの説明(マウントプロパゲーション(2)〜 private mount 〜 - TenForward)で--make-private
した際のmountinfo
の該当行は次のようになっていました。
613 30 253:0 /root/test/orig /root/test/bind rw,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
7番目のフィールドが区切りとなる-
となっています。他とマウントが共有されないのでオプショナルフィールドが存在しないということです。
slave場合
slaveの場合(マウントプロパゲーション(3)〜 slave mount 〜 - TenForward)は次のようなエントリとなっていました。
30 1 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw 133 30 253:0 /root/test/orig /root/test/bind rw,relatime master:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
sharedに設定されている/
ではshared
となっており、IDは1
です。それに対するslaveマウントの方はmaster:1
となっており、sharedでマウントされている/
のID=1のマウントの伝播を受け取ることがわかります。
残りは次回に。
マウントプロパゲーション(4)〜 unbindable mount 〜
引き続きマウントプロパゲーションについて書いていきます。完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。そもそも、このシリーズ、カーネル付属ドキュメントと mount_namespaces(7) に書いてあるんですよねw
unbindableは、そのマウント自身が他でバインドマウントできないようにするための指定です。マウントプロパゲーションとは少し違うものに見えます。しかし、これはprivateと同じでさらにバインドマウントを禁止するフラグがついたものと考えることができます。
unbindableはセキュリティ対策的な意味合いがあります。配下に多数のマウントを持ったマウントポイントがあったとします(例えば /
なんかは通常はそうですよね)。その多数のマウントを持ったマウントを再帰的にバインドマウントするオプション(--rbind
)を指定して何度もバインドマウントするとどうでしょう? 配下には大量のバインドマウントが再帰的に出現することになり、「バインドマウントボム(bomb)攻撃」とも言える状態になってしまいます。これを防ぐためにこのような指定ができるようになっています。
簡単に試してみましょう。まずはトップレベルのマウントとしてtest
というディレクトリを作成し、ここをtmpfsでマウントします。このtest
というマウント配下にふたつディレクトリ(a
、b
)を作成し、その片方a
もtmpfsをマウントします。
# mkdir test # mount -t tmpfs tmpfs test (ディレクトリtestをtmpfsマウントする) # mkdir test/a # mount -t tmpfs tmpfs test/a (test/aディレクトリを作成しtmpfsマウントする) # mkdir test/b # tree . . └── test (←tmpfsマウント) ├── a (←tmpfsマウント) └── b (←ディレクトリ) 3 directories, 0 files
上のようにtmpfs配下にtmpfsとディレクトリがひとつずつある状態になります。
ここでディレクトリb
に親ディレクトリであるtest
をバインドマウントします。この際、再帰的にマウントするように--rbind
を指定します。
# mount --rbind test test/b # tree . . └── test ├── a (←tmpfsマウント) └── b (←バインドマウント) ├── a (←tmpfsマウント) └── b 5 directories, 0 files
再帰的にマウントしていますので、b
配下のa
(test/b/a
)はtmpfsとしてマウントされたまま見えています。
ここでtest
配下にc
というディレクトリを作成し、ここも上で行ったb
のように再帰的にマウントします。
# mkdir test/c # mount --rbind test test/c
再帰的にマウントしていますので、c
配下にはさきほど再帰的にマウントしたb
配下のマウントもすべて再帰的にマウントされています。そして、先に行ったバインドマウントb
以下にもc
が出現し、その配下でもb
配下のマウントが見えています。
# tree . . └── test ├── a (←tmpfsマウント) ├── b (←バインドマウント) │ ├── a (←tmpfsマウント) │ ├── b │ └── c (←バインドマウント) │ ├── a │ ├── b (←バインドマウント) │ │ ├── a (←tmpfsマウント) │ │ ├── b │ │ └── c │ └── c └── c (←バインドマウント) ├── a (←tmpfsマウント) ├── b (←バインドマウント) │ ├── a │ ├── b │ └── c └── c 19 directories, 0 files
これが繰り返されると、延々と再帰的なマウントが出現することになり、リソースを食いつぶすDoS攻撃が成り立ちます。このようなことを防ぐためにunbindableが存在します。
# mount -t tmpfs tmpfs test # mkdir test/{a,b} # mount -t tmpfs tmpfs test/a
ここまでは先程の例と同じです。さきほどの例では--rbind
とだけ指定したところで同時に--make-unbindable
を指定してバインドマウントします。
# mount --rbind --make-unbindable test test/b # tree . . └── test ├── a (←tmpfsマウント) └── b (←バインドマウント) ├── a (←tmpfsマウント,unbindable) └── b 5 directories, 0 files
ここでさきほどと同様にtest/c
を作成し、test
を--make-unbindable
でバインドマウントします。
# mkdir test/c # mount --rbind --make-unbindable test test/c # tree . . └── test ├── a (←tmpfsマウント) ├── b (←バインドマウント) │ ├── a (←tmpfsマウント,unbindable) │ ├── b │ └── c └── c (←バインドマウント) ├── a (←tmpfsマウント) ├── b └── c 10 directories, 0 files
unbindableを指定した以外は同じ操作を行っていますが、test/b
配下もtest/c
配下でもバインドマウントは再帰的に行われていません(tmpfsはマウントされています)。
このようにunbindableを指定すると爆発的にバインドマウントが増殖することを防げます。
マウントプロパゲーション(3)〜 slave mount 〜
引き続きマウントプロパゲーションについて書いていきます。完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。
shared と private を説明すれば、頭の良い皆さんはもう slave がどのような動きになるのかわかったのではないでしょうか。
slaveは、複数のマウントの間に主従関係が生じます。マスター配下のマウント操作はスレーブ配下に伝播しますが、スレーブ配下のマウント操作はマスターには伝播しません。
これまでと同じような例を用いて説明しましょう。まずはorig
をbind
にバインドマウントします。
# mount --bind orig bind # cat /proc/self/mountinfo | grep bind 133 30 253:0 /root/test/orig /root/test/bind rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
そしてorig
側のマウントをsharedに、bind
側のマウントをslaveに設定します。
# mount --make-shared / # mount --make-slave bind # cat /proc/self/mountinfo :(略) 30 1 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw :(略) 133 30 253:0 /root/test/orig /root/test/bind rw,relatime master:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw # tree . . ├── bind │ ├── tmp │ └── tmp2 └── orig ├── tmp └── tmp2 6 directories, 0 files
/proc/self/mountinfo
を確認すると、orig
側のマウントポイントである/
はsharedに、bind
はmaster
と書かれており、/
のsharedのあとに書かれた番号(1
)と同じ番号が書かれています。これはbind
は/
(つまりorig
側)のslaveであることを示しています。orig
にはこれまでの例のようにあらかじめディレクトリがふたつ作成されています。
ここで、これまでの例と同様にorig/tmp
にtmpfsをマウントしてファイルを作成しましょう。
# mount -t tmpfs tmpfs orig/tmp # touch orig/tmp/testfile # tree . . ├── bind │ ├── tmp │ │ └── testfile │ └── tmp2 └── orig ├── tmp │ └── testfile └── tmp2 6 directories, 2 files
orig
側はsharedですのでbind
側にも同じマウントが伝播し、同じようなツリーが見えます。
それでは逆にbind/tmp2
にtmpfsをマウントし、ファイルを作成します。
# mount -t tmpfs tmpfs bind/tmp2 # touch bind/tmp2/testfile2 # tree . . ├── bind │ ├── tmp │ │ └── testfile │ └── tmp2 │ └── testfile2 └── orig ├── tmp │ └── testfile └── tmp2 6 directories, 3 files
確認してみると、bind/tmp2
で行ったtmpfsのマウントはorig
では見えていないことがわかります。つまりorig
側のマウントはbind
側に伝播しますが、逆は伝播しないということです。
マウント情報も確認しておきましょう。
# cat /proc/self/mountinfo :(略) 714 30 0:74 / /root/test/orig/tmp rw,relatime shared:346 - tmpfs tmpfs rw 729 133 0:74 / /root/test/bind/tmp rw,relatime master:346 - tmpfs tmpfs rw 742 133 0:75 / /root/test/bind/tmp2 rw,relatime - tmpfs tmpfs rw :(略)
orig/tmp
はsharedで、bind/tmp
はorig/tmp
を参照するslaveであり、bind/tmp2
はprivateでorig/tmp
側には伝播していなことが確認できます。