首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是什么代码阻止挂载命名空间循环?在更复杂的情况下涉及挂载传播

是什么代码阻止挂载命名空间循环?在更复杂的情况下涉及挂载传播
EN

Unix & Linux用户
提问于 2018-10-07 00:43:35
回答 1查看 498关注 0票数 3

背景信息

假设/tmp是按照内核缺省值(但不是systemd默认值)与private传播一起挂载的,并且是一个单独的文件系统。如果有必要,您可以通过在unshare -m中运行命令和/或使用mount --bind /tmp /tmp来确保这一点。

代码语言:javascript
复制
# findmnt -n -o propagation /tmp
private

注意,以下命令返回一个错误:

代码语言:javascript
复制
# 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始终至少有一个引用,而且它们永远不会被释放。分配的内存将丢失,直到重新启动整个系统。

比较而言,内核允许以下内容,这不是一个循环:

代码语言:javascript
复制
# 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
#

问题

内核代码在以下两种情况之间有何区别?

如果我试图使用挂载传播创建一个循环,它将失败:

代码语言:javascript
复制
# 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.

但是,如果删除挂载传播,则不会创建任何循环,并且它会成功:

代码语言:javascript
复制
# 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

代码语言:javascript
复制
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;
}

..。

代码语言:javascript
复制
    err = -EINVAL;
    if (mnt_ns_loop(old_path.dentry))
        goto out;

..。

代码语言:javascript
复制
 * 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)
EN

回答 1

Unix & Linux用户

回答已采纳

发布于 2018-10-07 17:13:36

参见提交4ce5d2b1a8fd,vfs:不要复制/proc//ns/mnt在命名空间之间

propagate_one()调用没有CL_COPY_MNT_NS_FILEcopy_tree()。在这种情况下,如果树根是NS文件的挂载,则copy_tree()将因错误EINVAL而失败。术语“NS文件”是指其中一个文件/proc/*/ns/mnt

进一步阅读时,我注意到如果树根不是NS文件,但是其中一个子挂载是,那么它就被排除在传播之外(就像不可绑定的挂载一样)。

一个NS文件在传播过程中被悄悄跳过的

示例

代码语言:javascript
复制
# 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            ...

让我们创建一个用于比较的普通挂载

代码语言:javascript
复制
# mkdir private_mnt/child_mnt
# mount --bind private_mnt/child_mnt private_mnt/child_mnt

现在试着传播一切。(在private_mnt/tmp中创建一个递归绑定挂载。/tmp是一个共享挂载)。

代码语言:javascript
复制
# 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

代码语言:javascript
复制
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

代码语言:javascript
复制
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);
票数 1
EN
页面原文内容由Unix & Linux提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://unix.stackexchange.com/questions/473717

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档