[PATCH] linux-user: implement mount_setattr(2)

Matt Turner <[email protected]> Wed, 5 Aug 2026 13:16:05 -0400
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
mount_setattr() was in the syscall tables but had no implementation, so
guests always got -ENOSYS. systemd uses it when setting up per-unit
credential mounts, which fails the affected units with EXIT_CREDENTIALS.

struct mount_attr is an extensible struct like open_how, so handle it the
same way openat2() does: reject sizes smaller than the ver0 struct and
require any unknown trailing bytes to be zero. All of its fields are
64-bit, and the MOUNT_ATTR_* and MS_* propagation values are identical on
every target, so only the byte order needs fixing up.

Signed-off-by: Matt Turner <[email protected]>
---
 linux-user/strace.list    |  3 +++
 linux-user/syscall.c      | 44 +++++++++++++++++++++++++++++++++++++++
 linux-user/syscall_defs.h | 13 ++++++++++++
 3 files changed, 60 insertions(+)

diff --git ./linux-user/strace.list ./linux-user/strace.list
index 25ace05187..e952f15d20 100644
--- ./linux-user/strace.list
+++ ./linux-user/strace.list
@@ -1737,3 +1737,6 @@
 #ifdef TARGET_NR_fspick
 { TARGET_NR_fspick, "fspick", "%s(%d,%s,%d)", NULL, NULL },
 #endif
+#ifdef TARGET_NR_mount_setattr
+{ TARGET_NR_mount_setattr, "mount_setattr", "%s(%d,%s,%d,%p,%d)", NULL, NULL },
+#endif
diff --git ./linux-user/syscall.c ./linux-user/syscall.c
index dc028686f4..cfa68dfbdb 100644
--- ./linux-user/syscall.c
+++ ./linux-user/syscall.c
@@ -9735,6 +9735,13 @@ _syscall5(int, sys_move_mount, int, __from_dfd, const char *, __from_pathname,
            int, __to_dfd, const char *, __to_pathname, unsigned int, flag)
 #endif
 
+#if defined(TARGET_NR_mount_setattr) && defined(__NR_mount_setattr)
+#define __NR_sys_mount_setattr __NR_mount_setattr
+_syscall5(int, sys_mount_setattr, int, dfd, const char *, path,
+          unsigned int, flags, struct mount_attr_ver0 *, uattr,
+          size_t, usize)
+#endif
+
 #if defined(TARGET_NR_fsopen) && defined(__NR_fsopen)
 #define __NR_sys_fsopen __NR_fsopen
 _syscall2(int, sys_fsopen, const char *, fs_name, unsigned int, flags);
@@ -14480,6 +14487,43 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,
         return do_map_shadow_stack(cpu_env, arg1, arg2, arg3);
 #endif
 
+#if defined(TARGET_NR_mount_setattr) && defined(__NR_mount_setattr)
+    case TARGET_NR_mount_setattr:
+        {
+            struct mount_attr_ver0 attr = {};
+            abi_ulong usize = arg5;
+
+            if (usize < sizeof(struct target_mount_attr_ver0)) {
+                return -TARGET_EINVAL;
+            }
+            ret = copy_struct_from_user(&attr, sizeof(attr), arg4, usize);
+            if (ret) {
+                if (ret == -TARGET_E2BIG) {
+                    qemu_log_mask(LOG_UNIMP,
+                                  "Unimplemented mount_setattr mount_attr "
+                                  "size: " TARGET_ABI_FMT_lu "\n", usize);
+                }
+                return ret;
+            }
+            /*
+             * MOUNT_ATTR_* and the MS_* propagation flags have the same
+             * values on all targets, so only byte order needs fixing up.
+             */
+            attr.attr_set = tswap64(attr.attr_set);
+            attr.attr_clr = tswap64(attr.attr_clr);
+            attr.propagation = tswap64(attr.propagation);
+            attr.userns_fd = tswap64(attr.userns_fd);
+
+            p = lock_user_string(arg2);
+            if (!p) {
+                return -TARGET_EFAULT;
+            }
+            ret = get_errno(sys_mount_setattr(arg1, p, arg3, &attr,
+                                              sizeof(attr)));
+            unlock_user(p, arg2, 0);
+        }
+        return ret;
+#endif
 #if defined(TARGET_NR_fsopen) && defined(__NR_fsopen)
     case TARGET_NR_fsopen:
         {
diff --git ./linux-user/syscall_defs.h ./linux-user/syscall_defs.h
index e033c7db34..e28853c93b 100644
--- ./linux-user/syscall_defs.h
+++ ./linux-user/syscall_defs.h
@@ -2770,6 +2770,19 @@ struct target_open_how_ver0 {
     abi_ullong mode;
     abi_ullong resolve;
 };
+/* from kernel's include/uapi/linux/mount.h */
+struct mount_attr_ver0 {
+    uint64_t attr_set;
+    uint64_t attr_clr;
+    uint64_t propagation;
+    uint64_t userns_fd;
+};
+struct target_mount_attr_ver0 {
+    abi_ullong attr_set;
+    abi_ullong attr_clr;
+    abi_ullong propagation;
+    abi_ullong userns_fd;
+};
 #ifndef RESOLVE_NO_MAGICLINKS
 #define RESOLVE_NO_MAGICLINKS   0x02
 #endif
-- 
2.54.0