From 097b6d3f662c9c2917360ea779bc531039ba96f1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 30 Oct 2025 22:20:36 +0100 Subject: [PATCH 1/6] nspawn: Fix docs --- man/systemd-nspawn.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index d11f7ed934..4e019ee732 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1601,7 +1601,7 @@ After=sys-subsystem-net-devices-ens1.device Binds the home directory of the specified user on the host into the container. Takes the name of an existing user on the host as argument. May be used multiple times to bind multiple - users into the container. This does three things: + users into the container. This does two things: The user's home directory is bind mounted from the host into @@ -1616,7 +1616,7 @@ After=sys-subsystem-net-devices-ens1.device user/group databases. - The combination of the three operations above ensures that it is possible to log into the + The combination of the two operations above ensures that it is possible to log into the container using the same account information as on the host. The user is only mapped transiently, while the container is running, and the mapping itself does not result in persistent changes to the container (except maybe for log messages generated at login time, and similar). Note that in From 472161f3688d8b2bf14d0ef0d3c2585e4ac0f40d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 30 Oct 2025 22:21:46 +0100 Subject: [PATCH 2/6] userdb: Add missing .membership extension to membership files Follow up for fe0342edf4693ac14c8cb9a977afa09e4acd4daf This also drops the mkosi testuser from the wheel and systemd-journal groups as the integration tests rely on the testuser not being to read the full journal. --- mkosi/mkosi.credentials/userdb.user.testuser | 4 ---- src/userdb/userdbctl.c | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mkosi/mkosi.credentials/userdb.user.testuser b/mkosi/mkosi.credentials/userdb.user.testuser index 6a2ce88324..69a1bfd21d 100644 --- a/mkosi/mkosi.credentials/userdb.user.testuser +++ b/mkosi/mkosi.credentials/userdb.user.testuser @@ -3,10 +3,6 @@ "uid": 4711, "disposition": "regular", "enforcePasswordPolicy": false, - "memberOf": [ - "wheel", - "systemd-journal" - ], "shell": "/bin/bash", "privileged": { "hashedPassword": ["$1$kqp7NF1f$tNnQcshPX53CSfRKTQD0R1"] diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 8efdc7c133..ca698e7345 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1430,7 +1430,7 @@ static int load_credential_one( if (ur) STRV_FOREACH(g, ur->member_of) { - _cleanup_free_ char *membership = strjoin(ur->user_name, ":", *g); + _cleanup_free_ char *membership = strjoin(ur->user_name, ":", *g, ".membership"); if (!membership) return log_oom(); @@ -1442,7 +1442,7 @@ static int load_credential_one( } else STRV_FOREACH(u, gr->members) { - _cleanup_free_ char *membership = strjoin(*u, ":", gr->group_name); + _cleanup_free_ char *membership = strjoin(*u, ":", gr->group_name, ".membership"); if (!membership) return log_oom(); From cfabf3eb3b27f26175b54bbc562003404e80fa1e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 31 Oct 2025 08:44:16 +0100 Subject: [PATCH 3/6] TEST-87-AUX-UTILS-VM: Propagate SYSTEMD_PAGER at one more place --- test/units/TEST-87-AUX-UTILS-VM.coredump.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index 4f2bbf40c8..19f0fa280e 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -162,7 +162,7 @@ rm -f /tmp/core.{output,redirected} # Unprivileged stuff # Related issue: https://github.com/systemd/systemd/issues/26912 -UNPRIV_CMD=(systemd-run --user --wait --pipe -M "testuser@.host" --) +UNPRIV_CMD=(systemd-run --user --wait --pipe -M "testuser@.host" -E SYSTEMD_PAGER --) # Trigger a couple of coredumps as an unprivileged user "${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP" "${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT" From 3fbf4ac24bc2deea57c5e55b2349e52f5a45df1b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 30 Oct 2025 22:49:03 +0100 Subject: [PATCH 4/6] userdbctl: Write empty JSON object into membership files --- src/userdb/userdbctl.c | 50 +++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index ca698e7345..823b68e7d9 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -18,6 +18,7 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" +#include "io-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" @@ -1196,6 +1197,31 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) { return r; } +static int write_membership(int dir_fd, const char *dir, const char *user, const char *group) { + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir); + assert(user); + assert(group); + + _cleanup_free_ char *membership = strjoin(user, ":", group, ".membership"); + if (!membership) + return log_oom(); + + _cleanup_close_ int fd = openat(dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644); + if (fd < 0) + return log_error_errno(errno, "Failed to create %s/%s: %m", dir, membership); + + r = loop_write(fd, "{}\n", SIZE_MAX); + if (r < 0) + return log_error_errno(r, "Failed to write empty JSON object into %s/%s: %m", dir, membership); + + log_info("Installed %s/%s from credential.", dir, membership); + + return 0; +} + static int load_credential_one( int credential_dir_fd, const char *name, @@ -1430,27 +1456,15 @@ static int load_credential_one( if (ur) STRV_FOREACH(g, ur->member_of) { - _cleanup_free_ char *membership = strjoin(ur->user_name, ":", *g, ".membership"); - if (!membership) - return log_oom(); - - _cleanup_close_ int fd = openat(*userdb_dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644); - if (fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", membership); - - log_info("Installed %s/%s from credential.", userdb_dir, membership); + r = write_membership(*userdb_dir_fd, userdb_dir, ur->user_name, *g); + if (r < 0) + return r; } else STRV_FOREACH(u, gr->members) { - _cleanup_free_ char *membership = strjoin(*u, ":", gr->group_name, ".membership"); - if (!membership) - return log_oom(); - - _cleanup_close_ int fd = openat(*userdb_dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644); - if (fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", membership); - - log_info("Installed %s/%s from credential.", userdb_dir, membership); + r = write_membership(*userdb_dir_fd, userdb_dir, *u, gr->group_name); + if (r < 0) + return r; } if (ur && user_record_disposition(ur) == USER_REGULAR) { From b430f2bc94922b313296ef0112ab582b96bdefa4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 30 Oct 2025 22:20:59 +0100 Subject: [PATCH 5/6] nspawn-bind-user: Write membership records --- src/nspawn/nspawn-bind-user.c | 50 +++++++++++++++++++++++++++++++++++ src/nspawn/nspawn.c | 5 +++- src/nspawn/nspawn.h | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/nspawn/nspawn-bind-user.c b/src/nspawn/nspawn-bind-user.c index d9a06e2c03..0211cfe23c 100644 --- a/src/nspawn/nspawn-bind-user.c +++ b/src/nspawn/nspawn-bind-user.c @@ -5,12 +5,16 @@ #include "sd-json.h" #include "alloc-util.h" +#include "chase.h" +#include "fd-util.h" #include "fileio.h" #include "format-util.h" +#include "io-util.h" #include "log.h" #include "nspawn.h" #include "machine-bind-user.h" #include "nspawn-bind-user.h" +#include "strv.h" #include "user-record.h" #include "group-record.h" #include "path-util.h" @@ -67,6 +71,40 @@ static int write_and_symlink( return 0; } +static int write_membership(const char *root, const char *user, const char *group) { + int r; + + assert(user); + assert(group); + + _cleanup_free_ char *membership = strjoin(user, ":", group, ".membership"); + if (!membership) + return log_oom(); + + _cleanup_free_ char *p = path_join("/run/host/userdb/", membership); + if (!p) + return log_oom(); + + _cleanup_close_ int fd = chase_and_open( + p, + root, + CHASE_PREFIX_ROOT|CHASE_NO_AUTOFS, + O_WRONLY|O_CREAT|O_CLOEXEC, + /* ret_path= */ NULL); + if (fd < 0) + return log_error_errno(errno, "Failed to create %s: %m", p); + + r = userns_chown_at(fd, /* fname= */ NULL, /* uid= */ 0, /* gid= */ 0, /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to adjust access mode of '%s': %m", p); + + r = loop_write(fd, "{}\n", SIZE_MAX); + if (r < 0) + return log_error_errno(r, "Failed to write empty JSON object into %s: %m", p); + + return 0; +} + int bind_user_setup(const MachineBindUserContext *c, const char *root) { static const UserRecordLoadFlags strip_flags = /* Removes privileged info */ USER_RECORD_LOAD_MASK_PRIVILEGED| @@ -130,6 +168,12 @@ int bind_user_setup(const MachineBindUserContext *c, const char *root) { if (r < 0) return r; + STRV_FOREACH(u, stripped_group->members) { + r = write_membership(root, *u, stripped_group->group_name); + if (r < 0) + return r; + } + /* Third, write out user shadow data. i.e. extract privileged info from user record */ r = user_record_clone(d->payload_user, shadow_flags, &shadow_user); if (r < 0) @@ -161,6 +205,12 @@ int bind_user_setup(const MachineBindUserContext *c, const char *root) { 0); if (r < 0) return r; + + STRV_FOREACH(g, stripped_user->member_of) { + r = write_membership(root, stripped_user->user_name, *g); + if (r < 0) + return r; + } } return 1; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 88d2346d00..814895b781 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1733,7 +1733,7 @@ static int in_child_chown(void) { return IN_SET(arg_userns_mode, USER_NAMESPACE_PICK, USER_NAMESPACE_FIXED); } -static int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int flags) { +int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int flags) { assert(fd >= 0 || fd == AT_FDCWD); if (!in_child_chown()) @@ -1756,6 +1756,9 @@ static int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int return -EOVERFLOW; } + if (isempty(fname)) + flags |= AT_EMPTY_PATH; + return RET_NERRNO(fchownat(fd, strempty(fname), uid, gid, flags)); } diff --git a/src/nspawn/nspawn.h b/src/nspawn/nspawn.h index 943b22db2d..192110c4d4 100644 --- a/src/nspawn/nspawn.h +++ b/src/nspawn/nspawn.h @@ -3,6 +3,7 @@ #include "shared-forward.h" +int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int flags); int userns_lchown(const char *p, uid_t uid, gid_t gid); int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid); int make_run_host(const char *root); From def01c7efeb2d0af216b692f543dc1c64df26e2b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 30 Oct 2025 22:23:20 +0100 Subject: [PATCH 6/6] nspawn/vmspawn: Add --bind-user-group= option Useful to add the bound users to the wheel group. --- man/systemd-nspawn.xml | 13 +++++++++++++ man/systemd-vmspawn.xml | 14 ++++++++++++++ src/nspawn/nspawn.c | 22 +++++++++++++++++++++- src/shared/machine-bind-user.c | 4 ++++ src/shared/machine-bind-user.h | 1 + src/vmspawn/vmspawn.c | 22 +++++++++++++++++++++- 6 files changed, 74 insertions(+), 2 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 4e019ee732..ed0acc6937 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1667,6 +1667,19 @@ After=sys-subsystem-net-devices-ens1.device + + + + When used with , includes the specified group as an + auxiliary group in the user records of users bound into the container. Takes a group name. + + Note: This will not check whether the specified groups exist in the container. + + This operation is only supported in combination with . + + + + diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 6d677cf106..7b26bec067 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -526,6 +526,20 @@ + + + + + When used with , includes the specified group as an + auxiliary group in the user records of users bound into the virtual machine. Takes a group name. + + Note: This will not check whether the specified groups exist in the virtual machine. + + This operation is only supported in combination with . + + + + diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 814895b781..4cd16d9bc0 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -240,6 +240,7 @@ static MachineCredentialContext arg_credentials = {}; static char **arg_bind_user = NULL; static char *arg_bind_user_shell = NULL; static bool arg_bind_user_shell_copy = false; +static char **arg_bind_user_groups = NULL; static bool arg_suppress_sync = false; static char *arg_settings_filename = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; @@ -283,6 +284,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_cpu_set, cpu_set_done); STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); +STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); @@ -429,6 +431,8 @@ static int help(void) { " --bind-user=NAME Bind user from host to container\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" + " --bind-user-group=GROUP\n" + " Add an auxiliary group to --bind-user= users\n" "\n%3$sInput/Output:%4$s\n" " --console=MODE Select how stdin/stdout/stderr and /dev/console are\n" " set up for the container.\n" @@ -660,6 +664,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOAD_CREDENTIAL, ARG_BIND_USER, ARG_BIND_USER_SHELL, + ARG_BIND_USER_GROUP, ARG_SUPPRESS_SYNC, ARG_IMAGE_POLICY, ARG_BACKGROUND, @@ -738,6 +743,7 @@ static int parse_argv(int argc, char *argv[]) { { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, { "bind-user", required_argument, NULL, ARG_BIND_USER }, { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, + { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, { "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "background", required_argument, NULL, ARG_BACKGROUND }, @@ -1514,6 +1520,15 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_BIND_USER_GROUP: + if (!valid_user_group_name(optarg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); + + if (strv_extend(&arg_bind_user_groups, optarg) < 0) + return log_oom(); + + break; + case ARG_SUPPRESS_SYNC: r = parse_boolean_argument("--suppress-sync=", optarg, &arg_suppress_sync); if (r < 0) @@ -1689,12 +1704,16 @@ static int verify_arguments(void) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "AmbientCapability= setting is not useful for boot mode."); } - /* Drop duplicate --bind-user= entries */ + /* Drop duplicate --bind-user= and --bind-user-group= entries */ strv_uniq(arg_bind_user); + strv_uniq(arg_bind_user_groups); if (arg_bind_user_shell && strv_isempty(arg_bind_user)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-shell= without --bind-user="); + if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + r = custom_mount_check_all(); if (r < 0) return r; @@ -4023,6 +4042,7 @@ static int outer_child( arg_bind_user_shell, arg_bind_user_shell_copy, "/run/host/home", + arg_bind_user_groups, &bind_user_context); if (r < 0) return r; diff --git a/src/shared/machine-bind-user.c b/src/shared/machine-bind-user.c index 476536ea28..c0fd1a96d0 100644 --- a/src/shared/machine-bind-user.c +++ b/src/shared/machine-bind-user.c @@ -94,6 +94,7 @@ static int convert_user( const char *shell, bool shell_copy, const char *home_mount_directory, + char **groups, UserRecord **ret_converted_user, GroupRecord **ret_converted_group) { @@ -145,6 +146,7 @@ static int convert_user( SD_JSON_BUILD_PAIR("homeDirectory", SD_JSON_BUILD_STRING(h)), SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NSpawn")), JSON_BUILD_PAIR_STRING_NON_EMPTY("shell", shell), + SD_JSON_BUILD_PAIR_STRV("memberOf", groups), SD_JSON_BUILD_PAIR("privileged", SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(u->hashed_password), "hashedPassword", SD_JSON_BUILD_VARIANT(hp)), SD_JSON_BUILD_PAIR_CONDITION(!!ssh, "sshAuthorizedKeys", SD_JSON_BUILD_VARIANT(ssh)))))); @@ -212,6 +214,7 @@ int machine_bind_user_prepare( const char *bind_user_shell, bool bind_user_shell_copy, const char *bind_user_home_mount_directory, + char **bind_user_groups, MachineBindUserContext **ret) { _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *c = NULL; @@ -288,6 +291,7 @@ int machine_bind_user_prepare( bind_user_shell, bind_user_shell_copy, bind_user_home_mount_directory, + bind_user_groups, &cu, &cg); if (r < 0) return r; diff --git a/src/shared/machine-bind-user.h b/src/shared/machine-bind-user.h index c5537d34d2..265fe13727 100644 --- a/src/shared/machine-bind-user.h +++ b/src/shared/machine-bind-user.h @@ -28,4 +28,5 @@ int machine_bind_user_prepare( const char *bind_user_shell, bool bind_user_shell_copy, const char *bind_user_home_mount_directory, + char **bind_user_groups, MachineBindUserContext **ret); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index f3ee2c28e7..14ae20fbe3 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -143,6 +143,7 @@ static bool arg_notify_ready = true; static char **arg_bind_user = NULL; static char *arg_bind_user_shell = NULL; static bool arg_bind_user_shell_copy = false; +static char **arg_bind_user_groups = NULL; STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -164,6 +165,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm_state_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep); +STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -227,6 +229,8 @@ static int help(void) { " --bind-user=NAME Bind user from host to virtual machine\n" " --bind-user-shell=BOOL|PATH\n" " Configure the shell to use for --bind-user= users\n" + " --bind-user-group=GROUP\n" + " Add an auxiliary group to --bind-user= users\n" "\n%3$sIntegration:%4$s\n" " --forward-journal=FILE|DIR\n" " Forward the VM's journal to the host\n" @@ -303,6 +307,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NOTIFY_READY, ARG_BIND_USER, ARG_BIND_USER_SHELL, + ARG_BIND_USER_GROUP, }; static const struct option options[] = { @@ -354,6 +359,7 @@ static int parse_argv(int argc, char *argv[]) { { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, { "bind-user", required_argument, NULL, ARG_BIND_USER }, { "bind-user-shell", required_argument, NULL, ARG_BIND_USER_SHELL }, + { "bind-user-group", required_argument, NULL, ARG_BIND_USER_GROUP }, {} }; @@ -715,6 +721,15 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_BIND_USER_GROUP: + if (!valid_user_group_name(optarg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg); + + if (strv_extend(&arg_bind_user_groups, optarg) < 0) + return log_oom(); + + break; + case '?': return -EINVAL; @@ -722,12 +737,16 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } - /* Drop duplicate --bind-user= entries */ + /* Drop duplicate --bind-user= and --bind-user-group= entries */ strv_uniq(arg_bind_user); + strv_uniq(arg_bind_user_groups); if (arg_bind_user_shell && strv_isempty(arg_bind_user)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-shell= without --bind-user="); + if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + if (argc > optind) { arg_kernel_cmdline_extra = strv_copy(argv + optind); if (!arg_kernel_cmdline_extra) @@ -1840,6 +1859,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_bind_user_shell, arg_bind_user_shell_copy, "/run/vmhost/home", + arg_bind_user_groups, &bind_user_context); if (r < 0) return r;