背景情報
private
/ tmpは、カーネルのデフォルト値(システムデフォルトではない)に応じて、伝播を介して別々のファイルシステムにマウントされていると仮定します。必要にunshare -m
応じてコマンドを実行するか、またはを使用してそれを確認できますmount --bind /tmp /tmp
。
# findmnt -n -o propagation /tmp
private
次のコマンドはエラーを返します。
# touch /tmp/a
# mount --bind /proc/self/ns/mnt /tmp/a
mount: /tmp/a: wrong fs type, bad option, bad superblock on /proc/self/ns/mnt, missing codepage or helper program, or other error.
これは、カーネルコード(以下の抜粋を参照)が単純なマウントネームスペースループを防止するためです。コードコメントでは、これが許可されない理由を説明します。マウントネームスペースのライフサイクルは、単純な参照計算によって追跡されます。マウントネームスペースAとBの両方が異なるネームスペースを参照するループがある場合、AとBは常に少なくとも1つの参照を持ちます。いいえリリースされた。割り当てられたメモリは、システム全体が再起動されるまで失われます。
比較のために、カーネルはループではなく次のようなケースを受け入れます。
# unshare -m
# echo $$
8456
# kill -STOP $$
[1]+ Stopped unshare -m
# touch /tmp/a
# mount --bind /proc/8456/ns/mnt /tmp/a
#
# umount /tmp/a # now clean up
# kill %1; echo
#
質問
カーネルコードは、次の2つのケースをどこで区別しますか?
インストール伝播を使用してループを作成しようとすると失敗します。
# mount --make-shared /tmp
# unshare -m --propagation shared
# echo $$
8456
# kill -STOP $$
[1]+ Stopped unshare -m
# mount --bind /proc/8456/ns/mnt /tmp/a
mount: /tmp/a: wrong fs type, bad option, bad superblock on /proc/9061/ns/mnt, missing codepage or helper program, or other error.
ただし、マウント伝播を削除すると、ループは生成されずに成功します。
# unshare -m --propagation private
# echo $$
8456
# kill -STOP $$
[1]+ Stopped unshare -m
# mount --bind /proc/8456/ns/mnt /tmp/a
#
# umount /tmp/a # cleanup
簡単なケースを処理するカーネルコード
https://elixir.bootlin.com/linux/v4.18/source/fs/namespace.c
static bool mnt_ns_loop(struct dentry *dentry)
{
/* Could bind mounting the mount namespace inode cause a
* mount namespace loop?
*/
struct mnt_namespace *mnt_ns;
if (!is_mnt_ns_file(dentry))
return false;
mnt_ns = to_mnt_ns(get_proc_ns(dentry->d_inode));
return current->nsproxy->mnt_ns->seq >= mnt_ns->seq;
}
...
err = -EINVAL;
if (mnt_ns_loop(old_path.dentry))
goto out;
...
* Assign a sequence number so we can detect when we attempt to bind
* mount a reference to an older mount namespace into the current
* mount namespace, preventing reference counting loops. A 64bit
* number incrementing at 10Ghz will take 12,427 years to wrap which
* is effectively never, so we can ignore the possibility.
*/
static atomic64_t mnt_ns_seq = ATOMIC64_INIT(1);
static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *user_ns)
答え1
コミット4ce5d2b1a8fdを参照してください。/proc/<pid>/ns/mnt
vfs:名前空間間でマウントバインドマウントをコピーしないでください。
propagate_one()
copy_tree()
なしで呼び出されますCL_COPY_MNT_NS_FILE
。根copy_tree()
エラーのために失敗したのはNSファイルのマウントですEINVAL
。 「NSファイル」という用語は、次のいずれかのファイルを表します/proc/*/ns/mnt
。
もっと読むと、ツリーのルートがNSファイルではなく、サブマウントの1つがNSファイルの場合、伝播から除外されます(バインドできないマウントと同じ方法)。
伝播中に自動的にスキップするNSファイルの例
# mount --make-shared /tmp
# cd /tmp
# mkdir private_mnt
# mount --bind private_mnt private_mnt
# mount --make-private private_mnt
# touch private_mnt/child_ns
# unshare --mount=private_mnt/child_ns --propagation=shared ls -l /proc/self/ns/mnt
lrwxrwxrwx. 1 root root 0 Oct 7 18:25 /proc/self/ns/mnt -> 'mnt:[4026532807]'
# findmnt | grep /tmp
├─/tmp tmpfs tmpfs ...
│ ├─/tmp/private_mnt tmpfs[/private_mnt] tmpfs ...
│ │ └─/tmp/private_mnt/child_ns nsfs[mnt:[4026532807]] nsfs ...
比較のために通常のインストールを作成しましょう。
# mkdir private_mnt/child_mnt
# mount --bind private_mnt/child_mnt private_mnt/child_mnt
今、すべてを広げてみてください。 (private_mnt
内部再帰バインドマウントを作成します/tmp
。 /tmp
共有マウントです。)
# mkdir shared_mnt
# mount --rbind private_mnt shared_mnt
# findmnt | grep /tmp/shared_mnt
│ └─/tmp/shared_mnt tmpfs[/private_mnt] tmpfs ...
│ ├─/tmp/shared_mnt/child_ns nsfs[mnt:[4026532809]] nsfs ...
│ └─/tmp/shared_mnt/child_mnt tmpfs[/private_mnt/child_mnt] tmpfs ...
# nsenter --mount=/tmp/private_mnt/child_ns findmnt|grep /tmp/shared_mnt
│ └─/tmp/shared_mnt tmpfs[/private_mnt] tmpfs ...
│ └─/tmp/shared_mnt/child_mnt tmpfs[/private_mnt/child_mnt] tmpfs ...
カーネルコード
以下は、上記のリンクされたコミットに追加された現在のバージョンのコードからの抜粋です。
https://elixir.bootlin.com/linux/v4.18/source/fs/pnode.c#L226
static int propagate_one(struct mount *m)
{
...
/* Notice when we are propagating across user namespaces */
if (m->mnt_ns->user_ns != user_ns)
type |= CL_UNPRIVILEGED;
child = copy_tree(last_source, last_source->mnt.mnt_root, type);
if (IS_ERR(child))
return PTR_ERR(child);
https://elixir.bootlin.com/linux/v4.18/source/fs/namespace.c#L1790
struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
int flag)
{
struct mount *res, *p, *q, *r, *parent;
if (!(flag & CL_COPY_UNBINDABLE) && IS_MNT_UNBINDABLE(mnt))
return ERR_PTR(-EINVAL);
if (!(flag & CL_COPY_MNT_NS_FILE) && is_mnt_ns_file(dentry))
return ERR_PTR(-EINVAL);