TenForward

技術ブログ。はてなダイアリーから移転しました

マウントプロパゲーション(6)〜 mountinfoファイル(2)〜

これまでの続きです。

これまでと同様に完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。

前回書ききれなかった /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範囲外となった場合などに起こります。

f:id:defiant:20200703221733p:plain
図1 slaveが連鎖している様子

図1のように、②を③にバインドマウントし、③を④にバインドマウントし、②は③のmaster、③は④のmasterとなっているとします(③のようにmasterかつslaveという設定ができます)。

このようにslaveが連鎖した状態で①の/mntchrootすると、④からmasterである③は見えません。③は②のslaveであり、④から②は見えますので、④のエントリにはpropagate_from:②のように書かれます。実際は②はマウントポイントではないので、IDとしては①のIDが書かれます。

文章にするとわかりづらいですね。実際に試してみましょう(実際に試してもわかりづらいですが…)。

まずは後の操作の準備を行います。ここでは次のような操作を行います。

  1. //mntにバインドマウントする
  2. /proc/mnt/procにバインドマウントする
  3. /mntを一度privateにする。これは/mntはsharedにしたいのですが、他と共有を行わない独立したsharedとするためです
  4. /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にも設定します。

  1. ③(`/mnt2/etc')をslaveに設定する
  2. ③を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マウントを行います。

  1. ③(/mnt2/etc)を④(/mnt/tmp/etc)にバインドマウントする
  2. ④(/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ファイル 〜

これまでの続きです。

これまでと同様に完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。

ここまでの説明でマウント情報を確認するために説明もなく/proc/self/mountinfoファイルを使っていました。少しこのファイルについて説明しておきましょう。/proc/selfは次のようにカレントプロセスの情報へのリンクになっています。

$ ls -l /proc/self
lrwxrwxrwx 1 root root 0  623日  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

tenforward.hatenablog.com

tenforward.hatenablog.com

tenforward.hatenablog.com

unbindableは、そのマウント自身が他でバインドマウントできないようにするための指定です。マウントプロパゲーションとは少し違うものに見えます。しかし、これはprivateと同じでさらにバインドマウントを禁止するフラグがついたものと考えることができます。

unbindableはセキュリティ対策的な意味合いがあります。配下に多数のマウントを持ったマウントポイントがあったとします(例えば / なんかは通常はそうですよね)。その多数のマウントを持ったマウントを再帰的にバインドマウントするオプション(--rbind)を指定して何度もバインドマウントするとどうでしょう? 配下には大量のバインドマウントが再帰的に出現することになり、「バインドマウントボム(bomb)攻撃」とも言える状態になってしまいます。これを防ぐためにこのような指定ができるようになっています。

簡単に試してみましょう。まずはトップレベルのマウントとしてtestというディレクトリを作成し、ここをtmpfsでマウントします。このtestというマウント配下にふたつディレクトリ(ab)を作成し、その片方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配下のatest/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 〜

引き続きマウントプロパゲーションについて書いていきます。完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。

tenforward.hatenablog.com

tenforward.hatenablog.com

shared と private を説明すれば、頭の良い皆さんはもう slave がどのような動きになるのかわかったのではないでしょうか。

slaveは、複数のマウントの間に主従関係が生じます。マスター配下のマウント操作はスレーブ配下に伝播しますが、スレーブ配下のマウント操作はマスターには伝播しません。

これまでと同じような例を用いて説明しましょう。まずはorigbindにバインドマウントします。

# 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に、bindmasterと書かれており、/の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/tmporig/tmpを参照するslaveであり、bind/tmp2はprivateでorig/tmp側には伝播していなことが確認できます。

マウントプロパゲーション(2)〜 private mount 〜

昨日のエントリの続きです。引き続き完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。

tenforward.hatenablog.com

それでは今日はprivateを見てみましょう。privateに設定したマウントのサブマウントは、他のマウントには反映されません。

sharedのときと同じディレクトリで試してみましょう。

# tree .
.
├── bind
└── orig
    └── tmp

3 directories, 0 files

まだ何もマウントが行われていない状態です。ここで、sharedと同様にorigbindにバインドマウントしてみましょう。

# 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に設定したマウントのサブマウントは、他のマウント配下には反映されません。