[Severity: Low]
This fixes mount options escaping the ve_devmnt_process() checks, which
has been possible since the devmnt option filtering was introduced.
Should this carry a Fixes: tag so it gets backported together with the
feature, e.g.:

Fixes: 50a959cca34e4 ("ve/fs/devmnt: process mount options")

> diff --git a/fs/fs_context.c b/fs/fs_context.c
> index 76f34f3d468ea..fdaefc2276917 100644
> --- a/fs/fs_context.c
> +++ b/fs/fs_context.c

[ ... ]

> +/*
> + * For legacy mount(2), MS_* mount flags are folded into fc->sb_flags and are
> + * not present in the monolithic data string.  Build a page with user data
> + * followed by those flags for ve_devmnt checks in vfs_parse_monolithic_sep.
> + *
> + * Returns @data when nothing needs to be added, a new page otherwise, or
> + * ERR_PTR() on failure.  The caller must free_page() when the result != 
> @data.
> + */

[Severity: Low]
This isn't a bug, but the comment doesn't quite match the code: for a
container mount of a non-binary blockdev filesystem a new page is
returned even when there is nothing to add (no matching flags set,
empty or NULL @data).  The callers only test result != @data, so they
still do the right thing, but the "Returns @data when nothing needs to
be added" wording suggests a narrower contract than the one
implemented.  Returning @data when no flag names were appended would
also keep NULL data NULL, see the last comment below.

> +void *legacy_merge_mount_data(struct fs_context *fc, void *data)
> +{
> +     struct ve_struct *ve = get_exec_env();
> +     size_t off = 0;
> +     char *page;
> +     int err;
> +
> +     if (ve_is_super(ve))
> +             return data;
> +
> +     if (!fc->fs_type || !(fc->fs_type->fs_flags & FS_REQUIRES_DEV))
> +             return data;

[ ... ]

> diff --git a/fs/namespace.c b/fs/namespace.c
> index 566f11a222fcf..0864fbd98131f 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c

[ ... ]

> @@ -3357,13 +3358,17 @@ static int do_remount(struct path *path, int 
> ms_flags, int sb_flags,
>        */
>       fc->oldapi = true;
>
> -     err = ve_prepare_mount_options(fc, data);
> -     if (err) {
> -             put_fs_context(fc);
> -             return err;
> +     mnt_data = legacy_merge_mount_data(fc, data);
> +     if (IS_ERR(mnt_data)) {
> +             err = PTR_ERR(mnt_data);
> +             goto err;
>       }
>
> -     err = parse_monolithic_mount_data(fc, data);
> +     err = ve_prepare_mount_options(fc, mnt_data);
> +     if (err)
> +             goto free_mnt_data;
> +
> +     err = parse_monolithic_mount_data(fc, mnt_data);

[Severity: Medium]
Doesn't this break remount with MS_DIRSYNC inside a container?

MS_DIRSYNC passes through path_mount() into do_remount()'s sb_flags:

        sb_flags = flags & (SB_RDONLY |
                            SB_SYNCHRONOUS |
                            SB_MANDLOCK |
                            SB_DIRSYNC |
        ...

so legacy_merge_mount_data() appends "dirsync" to the options.  Parsing
that string sets the corresponding bit in fc->sb_flags_mask:

fs/fs_context.c:vfs_parse_sb_flag()
        fc->sb_flags |= token;
        fc->sb_flags_mask |= token;

but MS_DIRSYNC is not part of MS_RMT_MASK, so reconfigure_super() now
rejects the whole request:

fs/super.c:reconfigure_super()
        if (fc->sb_flags_mask & ~MS_RMT_MASK)
                return -EINVAL;

Before this change (and still today on the host) MS_DIRSYNC was silently
ignored on remount, which is the historic legacy mount(2) behavior.
util-linux translates a "dirsync" option into MS_DIRSYNC, so remounting
a dirsync-mounted filesystem inside a container now fails with -EINVAL
where it used to succeed.

Would formatting only the remountable subset, e.g.
fc->sb_flags & fc->sb_flags_mask, on the reconfigure path avoid this?

[ ... ]

> @@ -3816,8 +3825,17 @@ static int do_new_mount(struct path *path, const char 
> *fstype, int sb_flags,
>                                         subtype, strlen(subtype));
>       if (!err && name)
>               err = vfs_parse_fs_string(fc, "source", name, strlen(name));
> -     if (!err)
> -             err = parse_monolithic_mount_data(fc, data);
> +     if (!err) {
> +             void *mnt_data = legacy_merge_mount_data(fc, data);
> +
> +             if (IS_ERR(mnt_data)) {
> +                     err = PTR_ERR(mnt_data);
> +             } else {
> +                     err = parse_monolithic_mount_data(fc, mnt_data);
> +                     if (mnt_data != data)
> +                             free_page((unsigned long)mnt_data);
> +             }
> +     }

[Severity: High]
Can this free the page while it is still referenced by the fs_context?

For a filesystem without init_fs_context (legacy mount API),
parse_monolithic_mount_data() resolves to legacy_parse_monolithic(),
which keeps the pointer instead of copying the data:

fs/fs_context.c:legacy_parse_monolithic()
        ctx->legacy_data = data;

so the merged page is freed here and then used again further down the
same path:

do_new_mount()
    parse_monolithic_mount_data(fc, mnt_data)
        legacy_parse_monolithic()          ctx->legacy_data = mnt_data
    free_page(mnt_data)
    vfs_get_tree(fc)
        legacy_get_tree()
            fc->fs_type->mount(..., ctx->legacy_data)
                mount_bdev()
                    ve_devmnt_process()    reads, and ve_devmnt_insert()
                                           memmoves into, the freed page
                    fill_super()           parses the freed page

The remount path is not affected because there legacy_reconfigure()
runs before the free_page() call, but it has a related problem: the
filesystem sees the appended flag names.  Legacy parsers never had to
accept "ro", "sync" etc. as strings (the VFS consumed them as flags),
and e.g. ext2's parse_options() fails on unknown options, so a
container mount/remount with MS_RDONLY would be rejected by the
filesystem even with the lifetime issue fixed.

Every in-tree legacy blockdev filesystem (ext2, f2fs, jfs, ocfs2, hfs,
hfsplus, ufs, ...) is disabled in configs/kernel-6.12.0-x86_64.config,
so neither problem can trigger on the shipped configuration, but
nothing in the code prevents it once such a filesystem is enabled or
loaded from out of tree.

Since filesystems whose parse_monolithic is not generic_parse_monolithic
get their devmnt checks in mount_bdev()/ve_prepare_mount_options()
anyway, should legacy_merge_mount_data() (or its callers) skip
filesystems that use legacy_fs_context_ops?

[ ... ]
_______________________________________________
Devel mailing list
[email protected]
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to