배경 정보
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는 항상 최소한 하나의 참조를 갖게 되며안 돼요출시된. 할당된 메모리는 전체 시스템을 다시 시작할 때까지 손실됩니다.
비교를 위해 커널은 루프가 아닌 다음과 같은 경우를 허용합니다.
# 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
#
질문
커널 코드는 다음 두 경우를 어디에서 구별합니까?
설치 전파를 사용하여 루프를 만들려고 하면 실패합니다.
# 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 파일이 아니고 하위 마운트 중 하나가 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);