<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    <p><br>
    </p>
    <div class="moz-cite-prefix">On 6/2/26 5:55 PM, Pavel Tikhomirov
      wrote:<br>
    </div>
    <blockquote type="cite" cite="mid:416c98fb-25b2-45f8-b1d1-ef84b093e6ee@virtuozzo.com">
      <pre wrap="" class="moz-quote-pre">

On 6/2/26 16:44, Vasileios Almpanis wrote:
</pre>
      <blockquote type="cite">
        <pre wrap="" class="moz-quote-pre">In legacy mount callpaths, userspace might pass mount options as
flags. These flags escape our checks in ve_devmnt_process allowing
devices to be mounted inside containers with options not specified in
the allowed field. Introduce helpers that take these flags and
already existing tables of flag -&gt; string representation to construct
a comma separated value string from them, and append them to userspace
provided data. Then pass this string to parse_monolithic_mount_data
enforcing the same checks symmetrically in both mount and fsconfig
syscalls.

In the remount path, run legacy_merge_mount_data() before
ve_devmnt_process() so container device mount policy sees MS_* flags
from the legacy mount(2) API, not only the user-supplied option string.
Keep ve_prepare_mount_options() for legacy parsers that do not use
generic_parse_monolithic().

<a class="moz-txt-link-freetext" href="https://virtuozzo.atlassian.net/browse/VSTOR-132330">https://virtuozzo.atlassian.net/browse/VSTOR-132330</a>
Signed-off-by: Vasileios Almpanis <a class="moz-txt-link-rfc2396E" href="mailto:vasileios.almpanis@virtuozzo.com">&lt;vasileios.almpanis@virtuozzo.com&gt;</a>

Feature: ve: ve generic structures
---
Changes since v1:
  - Replace open-coded flag loops with append_entry() helper (pointer-
    advancing style) that unifies the comma-insert-copy pattern across
    all three call sites
  - Rework legacy_merge_mount_data() to allocate the page upfront, write
    user data first then append sb flags via vfs_format_sb_flags(); this
    eliminates the intermediate flags_buf[128], the total size calculation,
    and the +1/+2 arithmetic
  - Fix inconsistent error code: replace -EINVAL with -ENOSPC for the
    buffer-too-small case
  - Add comment on FS_BINARY_MOUNTDATA explaining why those filesystems
    are skipped
  - Add blank line before return in legacy_merge_mount_data()
  - Remove excess blank line after parse_monolithic_mount_data() in
    do_remount()

 fs/fs_context.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++
 fs/internal.h   |   8 ++++
 fs/namespace.c  |  32 +++++++++++---
 3 files changed, 147 insertions(+), 7 deletions(-)

diff --git a/fs/fs_context.c b/fs/fs_context.c
index 76f34f3d468e..ae6ea684d596 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -81,6 +81,120 @@ static int vfs_parse_sb_flag(struct fs_context *fc, const char *key)
         return -ENOPARAM;
 }
 
+#ifdef CONFIG_VE
+/*
+ * Append s to the buffer at dst, preceded by a comma if len &gt; 0.
+ * cap is the total buffer capacity (excluding the null terminator slot).
+ * Updates *len and returns the new write position, or ERR_PTR(-ENOSPC).
+ */
+static char *append_entry(char *dst, size_t *len, size_t cap, const char *s)
+{
+        size_t s_len = strlen(s);
+        size_t write_chars = (*len &gt; 0) + s_len;
+
+        if (*len + write_chars &gt; cap)
+                return ERR_PTR(-ENOSPC);
+        if (*len)
+                *dst++ = ',';
+        memcpy(dst, s, s_len);
+        *len += write_chars;
+        return dst + s_len;
+}
+
+/*
+ * Append fc-&gt;sb_flags as comma-separated option names into [*dstp, cap).
+ * Updates *dstp and *lenp on success.
+ * Matches vfs_parse_sb_flag() / common_{set,clear}_sb_flag tables.
+ */
+static int vfs_format_sb_flags(struct fs_context *fc,
+                               char **dstp, size_t *lenp, size_t cap)
+{
+        const struct constant_table *p;
+        unsigned int flags = fc-&gt;sb_flags;
+        unsigned int mask = fc-&gt;sb_flags_mask;
+        char *dst = *dstp;
+
+        for (p = common_set_sb_flag; p-&gt;name; p++) {
+                if (!(flags &amp; p-&gt;value))
+                        continue;
+                dst = append_entry(dst, lenp, cap, p-&gt;name);
+                if (IS_ERR(dst))
+                        return PTR_ERR(dst);
+        }
</pre>
      </blockquote>
      <pre wrap="" class="moz-quote-pre">
My idea of adding a helper was actually something like:

int __vfs_format_flags(const struct constant_table *p, int flags,
                       char *buff, size_t size, size_t *off)
{
        while (p-&gt;name) {
                if (!(flags &amp; p-&gt;value))
                        continue;</pre>
    </blockquote>
    <font face="monospace">Each for loop has a different flag check. The
      second one also uses&nbsp;<span style="white-space: pre-wrap">fc-&gt;sb_flags_mask</span>.
      Thats why i didn't make it into a function<br>
      Otherwise I need another flag for which check to do, with mask or
      only flag.</font>
    <blockquote type="cite" cite="mid:416c98fb-25b2-45f8-b1d1-ef84b093e6ee@virtuozzo.com">
      <pre wrap="" class="moz-quote-pre">

                if (*off &amp;&amp; *off &lt; size)
                        buff[(*off)++] = ',';

                ret = strscpy(buff + off, p-&gt;name, size - off);
                if (ret &lt; 0)
                        return -E2BIG;
                off += ret;

                p++;
        }

        return 0;
}

/*
 * Append fc-&gt;sb_flags as comma-separated option names into [*dstp, cap).
 * Updates *dstp and *lenp on success.
 * Matches vfs_parse_sb_flag() / common_{set,clear}_sb_flag tables.
 */
static int vfs_format_sb_flags(struct fs_context *fc,
                               char *buff, size_t size, size_t *off)
{
        ret = __vfs_format_flags(common_set_sb_flag, fc-&gt;sb_flags,
                                 buff, size, off);
        if (ret)
                return ret;

        ret = __vfs_format_flags(common_clear_sb_flag, fc-&gt;sb_flags_mask,
                                 buff, size, off);
        if (ret)
                return ret;

        return 0;
}

I don't really like append_entry() design as it has too much things changing,
in the above example only &quot;off&quot; is changing, and there is no err-ptr return.

</pre>
      <blockquote type="cite">
        <pre wrap="" class="moz-quote-pre">+
+        for (p = common_clear_sb_flag; p-&gt;name; p++) {
+                if (!(mask &amp; p-&gt;value) || (flags &amp; p-&gt;value))
+                        continue;
+                dst = append_entry(dst, lenp, cap, p-&gt;name);
+                if (IS_ERR(dst))
+                        return PTR_ERR(dst);
+        }
+
+        *dstp = dst;
+        return 0;
+}
+
+/*
+ * For legacy mount(2), MS_* mount flags are folded into fc-&gt;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.
+ */
+void *legacy_merge_mount_data(struct fs_context *fc, void *data)
+{
+        struct ve_struct *ve = get_exec_env();
+        char *page, *dst;
+        size_t len = 0;
+        int err;
+
+        if (ve_is_super(ve))
+                return data;
+
+        if (!fc-&gt;fs_type || !(fc-&gt;fs_type-&gt;fs_flags &amp; FS_REQUIRES_DEV))
+                return data;
+
+        /*
+         * Filesystems with binary mount data (e.g. btrfs) bypass option
+         * string parsing entirely, so our checks cannot apply here.
+         */
+        if (fc-&gt;fs_type-&gt;fs_flags &amp; FS_BINARY_MOUNTDATA)
+                return data;
+
+        page = (char *)__get_free_page(GFP_KERNEL);
+        if (!page)
+                return ERR_PTR(-ENOMEM);
+
+        dst = page;
+
+        if (data &amp;&amp; *(char *)data) {
+                dst = append_entry(dst, &amp;len, PAGE_SIZE - 1, (char *)data);
+                if (IS_ERR(dst)) {
+                        err = PTR_ERR(dst);
+                        goto err_free;
+                }
+        }
+
+        err = vfs_format_sb_flags(fc, &amp;dst, &amp;len, PAGE_SIZE - 1);
+        if (err)
+                goto err_free;
+
+        if (!len) {
+                free_page((unsigned long)page);
+                return data;
+        }
+
+        *dst = '\0';
+
+        return page;
+
+err_free:
+        free_page((unsigned long)page);
+        return ERR_PTR(err);
+}
+#endif /* CONFIG_VE */
+
 /**
  * vfs_parse_fs_param_source - Handle setting &quot;source&quot; via parameter
  * @fc: The filesystem context to modify
diff --git a/fs/internal.h b/fs/internal.h
index 0064345cb9d0..120c239df394 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -46,6 +46,14 @@ extern void __init chrdev_init(void);
  */
 extern const struct fs_context_operations legacy_fs_context_ops;
 extern int parse_monolithic_mount_data(struct fs_context *, void *);
+#ifdef CONFIG_VE
+void *legacy_merge_mount_data(struct fs_context *fc, void *data);
+#else
+static inline void *legacy_merge_mount_data(struct fs_context *fc, void *data)
+{
+        return data;
+}
+#endif
 extern void vfs_clean_context(struct fs_context *fc);
 extern int finish_clean_context(struct fs_context *fc);
 
diff --git a/fs/namespace.c b/fs/namespace.c
index acd4507e1247..b2defe2006ca 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3295,6 +3295,7 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
         struct super_block *sb = path-&gt;mnt-&gt;mnt_sb;
         struct mount *mnt = real_mount(path-&gt;mnt);
         struct fs_context *fc;
+        void *mnt_data = NULL;
 
         if (!check_mnt(mnt))
                 return -EINVAL;
@@ -3315,13 +3316,17 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
          */
         fc-&gt;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);
         if (!err) {
                 down_write(&amp;sb-&gt;s_umount);
                 err = -EPERM;
@@ -3338,6 +3343,10 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
 
         mnt_warn_timestamp_expiry(path, &amp;mnt-&gt;mnt);
 
+free_mnt_data:
+        if (mnt_data &amp;&amp; mnt_data != data)
+                free_page((unsigned long)mnt_data);
+err:
         put_fs_context(fc);
         return err;
 }
@@ -3774,8 +3783,17 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
                                           subtype, strlen(subtype));
         if (!err &amp;&amp; name)
                 err = vfs_parse_fs_string(fc, &quot;source&quot;, 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);
+                }
+        }
         if (!err &amp;&amp; !mount_capable(fc))
                 err = -EPERM;
         if (!err)
</pre>
      </blockquote>
      <pre wrap="" class="moz-quote-pre">
</pre>
    </blockquote>
    <pre class="moz-signature" cols="72">-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.</pre>
  </body>
</html>