マウントプロパゲーション(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
側には伝播していなことが確認できます。
マウントプロパゲーション(2)〜 private mount 〜
昨日のエントリの続きです。引き続き完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。
それでは今日はprivateを見てみましょう。privateに設定したマウントのサブマウントは、他のマウントには反映されません。
sharedのときと同じディレクトリで試してみましょう。
# tree . . ├── bind └── orig └── tmp 3 directories, 0 files
まだ何もマウントが行われていない状態です。ここで、sharedと同様にorig
をbind
にバインドマウントしてみましょう。
# mount --bind orig bind (バインドマウント) # grep bind /proc/self/mountinfo (マウント情報の確認) 613 30 253:0 /root/test/orig /root/test/bind rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw (sharedでマウントされている) # mount --make-private bind (privateに設定) # grep bind /proc/self/mountinfo 613 30 253:0 /root/test/orig /root/test/bind rw,relatime - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw (sharedの文字が消えている)
バインドマウントした状態ではsharedになっていたマウントで、--make-private
でprivateに設定した後はshared
の文字が消えています。
この状態でbind
配下のtmp
ディレクトリにtmpfsをマウントします。
# tree . . ├── bind │ └── tmp └── orig └── tmp 4 directories, 0 files (バインドマウントされているので両方にtmpが存在する) # mount -t tmpfs tmpfs bind/tmp (tmpfsマウント) # egrep "bind.*tmpfs" /proc/self/mountinfo 627 613 0:74 / /root/test/bind/tmp rw,relatime - tmpfs tmpfs rw (マウントされている)
bind/tmp
にtmpfsがマウントされました。このtmpfs上にファイルを置いてみましょう。
# touch bind/tmp/testfile # tree . . ├── bind │ └── tmp │ └── testfile └── orig └── tmp 4 directories, 1 file
bind
以下には作成したファイルが見えていますが、orig
以下ではファイルが見えません。つまりtmpfsはバインドマウント先のbind
配下でだけ有効で、バインドマウント元のorig
では反映されていないことがわかります。
sharedのときと同様にorig
以下で行ったマウントがbind
で行われないことも確認おきましょう。
# mkdir orig/tmp2 (orig以下にtmp2ディレクトリを作成) # tree . . ├── bind │ ├── tmp │ │ └── testfile │ └── tmp2 └── orig ├── tmp └── tmp2 6 directories, 1 file # mount --make-private / (orig側のマウントをprivateに設定) # mount -t tmpfs tmpfs orig/tmp2 (tmp2にtmpfsをマウント) # touch orig/tmp2/testfile2 (ファイルをtmpfs上に置く) # tree . . ├── bind │ ├── tmp │ │ └── testfile │ └── tmp2 └── orig ├── tmp └── tmp2 └── testfile2 6 directories, 2 files (orig側のtmpfsマウントはbind側には反映されていない)
上のようにorig
側でのtmpfsマウントがbind
側では反映されていないことがわかります。つまりtmpfsはバインドマウント元のorig
配下でだけ有効で、バインドマウント先のbind
には反映されていないことがわかります。
このようにprivateに設定したマウントのサブマウントは、他のマウント配下には反映されません。
マウントプロパゲーション(1)〜 shared mount 〜
完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。
2001 年に 2.4 kernel でバインド(bind)マウントが、2002 年の 2.4.19 で mount namespace が導入されました。ただ、ユースケースによっては mount namespace が行う分離は徹底しているので、もう少しマウントが共有されても良いシーンがあったようで、shared マウントなどのマウントプロパゲーションの考え方が 2.6.15(2006 年 1 月)で導入されました。
そのマウントプロパゲーションについてまとめていきます。
説明中の「サブマウント」とは、あるマウントポイントがあったとして、そのツリー配下で行われるマウント処理のことを指すとします。例えば、ホストで/dev/sda1
というデバイスをルート/
にマウントします。その配下の/home
に/dev/sda2
をマウントしたりしますが、この/home
をサブマウントと呼ぶことにします。
プロパゲーションの設定 | 説明 |
---|---|
shared | マウント元のサブマウントはマウント先にも反映される。マウント先配下のサブマウントはマウント元にも反映される |
private | あるマウント配下でのサブマウントは他のマウント配下には公開されない |
slave | マウント元のサブマウントはマウント先にも反映されるが、マウント先のサブマウントはマウント元には反映されない |
unbindable | privateと同様でさらにこのマウントをバインドマウントできない |
上のそれぞれについて、配下のマウントに再帰的に設定を反映させることができます。
プロパゲーションの設定 | 説明 |
---|---|
rshared | 配下で行われるマウントすべてを shared にする |
rprivate | 配下で行われるマウントすべてを private にする |
rslave | 配下で行われるマウントすべてを slave にする |
runbindable | 配下で行われるマウントすべてを unbindable にする |
では、バインドマウントを使ってマウントプロパゲーションの説明をしていきましょう。
shared
まずはsharedです。sharedは、複数のマウント配下で行ったマウントがお互いに伝播します。
まず、バインドマウント用にorig
とbind
というディレクトリをふたつ作ります。そしてバインドマウントを行います。バインドマウント元ディレクトリorig
内にtmp
というディレクトリを作りました。
# mkdir orig bind # mount --bind orig bind (origをbindにバインドマウント) # mkdir orig/tmp (orig内にtmpというディレクトリを作成)
orig
がbind
にもバインドマウントされているので、次のように両方のディレクトリにtmp
ディレクトリが見えます。
# tree . . ├── bind │ └── tmp └── orig └── tmp 4 directories, 0 files
ここでbind
をsharedマウントとしてみましょう。sharedにするにはmountコマンドに--make-shared
を指定します。
# mount --make-shared bind # grep bind /proc/self/mountinfo (マウント情報の確認) 518 30 253:0 /root/test/orig /root/test/bind rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw (shared指定されている)
sharedになっていることを確認するために/proc/self/mountinfo
を確認したところ、きちんと"shared"という文字がみえます。ここでshared指定されたbind
以下でマウント操作を行ってみます。ここではtmp
ディレクトリにtmpfsをマウントしてみましょう。
# mount -t tmpfs tmpfs bind/tmp (bind以下のtmpにtmpfsマウント) # egrep "bind.*tmpfs" /proc/self/mountinfo (マウントされたのを確認) 546 518 0:49 / /root/test/bind/tmp rw,relatime shared:253 - tmpfs tmpfs rw
tmpfsがマウントされていますので、このtmpfs上にファイルを置いてみましょう。そしてorig
とbind
の配下がどうなっているのかを確認してみます。
# touch bind/tmp/testfile (tmpfs上にファイルを置く) # tree . . ├── bind │ └── tmp │ └── testfile └── orig └── tmp └── testfile 4 directories, 2 files
バインドマウント先のbind
内でマウントしたtmpfsがバインドマウント元でもマウントされた状態になっています。
この逆もやってみましょう。orig
ディレクトリはマウントポイントではありませんので、ここではorig
ディレクトリが所属するマウントのマウントポイントであるルート(/
)でsharedを指定します。ここは環境によって違いますので、実際に試す場合は適宜変更してください。
# mount --make-shared /
そしてorig
ディレクトリ以下にtmp2
ディレクトリを作成しtmpfsをマウントし、ファイルを置きます。
# mkdir orig/tmp2 # mount -t tmpfs tmpfs orig/tmp2/ (tmp2にtmpfsをマウント) # touch orig/tmp2/testfile2 (tmpfs上にファイルを置く) # tree . . ├── bind │ ├── tmp │ │ └── testfile │ └── tmp2 │ └── testfile2 └── orig ├── tmp │ └── testfile └── tmp2 └── testfile2 6 directories, 4 files
上のように、orig
以下で行ったマウントがきちんとbind
以下でも見えています。
このようにsharedに設定したマウント配下で行ったマウントは、他のマウント配下にも反映されます。