From 64d5bb4d53a5d002b15d9ff6aa1a0a09597e2074 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Oct 2025 21:31:29 +0200 Subject: [PATCH 1/5] nspawn: Add --bind-user-shell= to --help --- src/nspawn/nspawn.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 5de826c44a..220ae7ec9f 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -427,6 +427,8 @@ static int help(void) { " --overlay-ro=PATH[:PATH...]:PATH\n" " Similar, but creates a read-only overlay mount\n" " --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" "\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" From 9aa6c30bbdd43f62a322516f82ba9ac1780a492b Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Fri, 22 Aug 2025 10:52:17 +0200 Subject: [PATCH 2/5] machine-bind-user: Use machine in log messages instead of container --- src/shared/machine-bind-user.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/shared/machine-bind-user.c b/src/shared/machine-bind-user.c index e4b0e12194..947be1ba95 100644 --- a/src/shared/machine-bind-user.c +++ b/src/shared/machine-bind-user.c @@ -33,14 +33,14 @@ static int check_etc_passwd_collisions( if (r == -ENOENT) return 0; /* no user database? then no user, hence no collision */ if (r < 0) - return log_error_errno(r, "Failed to open /etc/passwd of container: %m"); + return log_error_errno(r, "Failed to open /etc/passwd of machine: %m"); for (;;) { struct passwd *pw; r = fgetpwent_sane(f, &pw); if (r < 0) - return log_error_errno(r, "Failed to iterate through /etc/passwd of container: %m"); + return log_error_errno(r, "Failed to iterate through /etc/passwd of machine: %m"); if (r == 0) /* EOF */ return 0; /* no collision */ @@ -68,14 +68,14 @@ static int check_etc_group_collisions( if (r == -ENOENT) return 0; /* no group database? then no group, hence no collision */ if (r < 0) - return log_error_errno(r, "Failed to open /etc/group of container: %m"); + return log_error_errno(r, "Failed to open /etc/group of machine: %m"); for (;;) { struct group *gr; r = fgetgrent_sane(f, &gr); if (r < 0) - return log_error_errno(r, "Failed to iterate through /etc/group of container: %m"); + return log_error_errno(r, "Failed to iterate through /etc/group of machine: %m"); if (r == 0) return 0; /* no collision */ @@ -114,14 +114,14 @@ static int convert_user( return r; if (r > 0) return log_error_errno(SYNTHETIC_ERRNO(EBUSY), - "Sorry, the user '%s' already exists in the container.", u->user_name); + "Sorry, the user '%s' already exists in the machine.", u->user_name); r = check_etc_group_collisions(directory, g->group_name, GID_INVALID); if (r < 0) return r; if (r > 0) return log_error_errno(SYNTHETIC_ERRNO(EBUSY), - "Sorry, the group '%s' already exists in the container.", g->group_name); + "Sorry, the group '%s' already exists in the machine.", g->group_name); h = path_join("/run/host/home/", u->user_name); if (!h) @@ -148,7 +148,7 @@ static int convert_user( 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)))))); if (r < 0) - return log_error_errno(r, "Failed to build container user record: %m"); + return log_error_errno(r, "Failed to build machine user record: %m"); r = group_record_build( &converted_group, @@ -158,7 +158,7 @@ static int convert_user( SD_JSON_BUILD_PAIR_CONDITION(g->disposition >= 0, "disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(g->disposition))), SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NSpawn")))); if (r < 0) - return log_error_errno(r, "Failed to build container group record: %m"); + return log_error_errno(r, "Failed to build machine group record: %m"); *ret_converted_user = TAKE_PTR(converted_user); *ret_converted_group = TAKE_PTR(converted_group); @@ -175,7 +175,7 @@ static int find_free_uid(const char *directory, uid_t *current_uid) { if (*current_uid > MAP_UID_MAX) return log_error_errno( SYNTHETIC_ERRNO(EBUSY), - "No suitable available UID in range " UID_FMT "…" UID_FMT " in container detected, can't map user.", + "No suitable available UID in range " UID_FMT "…" UID_FMT " in machine detected, can't map user.", MAP_UID_MIN, MAP_UID_MAX); r = check_etc_passwd_collisions(directory, NULL, *current_uid); @@ -219,7 +219,7 @@ int machine_bind_user_prepare( assert(ret); /* This resolves the users specified in 'bind_user', generates a minimalized JSON user + group record - * for it to stick in the container, allocates a UID/GID for it, and updates the custom mount table, + * for it to stick in the machine, allocates a UID/GID for it, and updates the custom mount table, * to include an appropriate bind mount mapping. * * This extends the passed custom_mounts/n_custom_mounts with the home directories, and allocates a @@ -264,13 +264,13 @@ int machine_bind_user_prepare( if (r < 0) return log_error_errno(r, "Failed to resolve group of user '%s': %m", u->user_name); - /* We want to synthesize exactly one user + group from the host into the container. This only + /* We want to synthesize exactly one user + group from the host into the machine. This only * makes sense if the user on the host has its own private group. We can't reasonably check * this, so we just check of the name of user and group match. * * One of these days we might want to support users in a shared/common group too, but it's * not clear to me how this would have to be mapped, precisely given that the common group - * probably already exists in the container. */ + * probably already exists in the machine. */ if (!streq(u->user_name, g->group_name)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, mapping users without private groups is currently not supported."); From af0ae29714310f6739d079b3938fef48b33c8768 Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Fri, 22 Aug 2025 11:02:29 +0200 Subject: [PATCH 3/5] machine-bind-user: Make home mount directory configurable --- src/nspawn/nspawn.c | 1 + src/shared/machine-bind-user.c | 13 +++++++++++-- src/shared/machine-bind-user.h | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 220ae7ec9f..a47fcb2294 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -4019,6 +4019,7 @@ static int outer_child( arg_bind_user, arg_bind_user_shell, arg_bind_user_shell_copy, + "/run/host/home", &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 947be1ba95..476536ea28 100644 --- a/src/shared/machine-bind-user.c +++ b/src/shared/machine-bind-user.c @@ -93,6 +93,7 @@ static int convert_user( uid_t allocate_uid, const char *shell, bool shell_copy, + const char *home_mount_directory, UserRecord **ret_converted_user, GroupRecord **ret_converted_group) { @@ -123,7 +124,7 @@ static int convert_user( return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Sorry, the group '%s' already exists in the machine.", g->group_name); - h = path_join("/run/host/home/", u->user_name); + h = path_join(home_mount_directory, u->user_name); if (!h) return log_oom(); @@ -210,6 +211,7 @@ int machine_bind_user_prepare( char **bind_user, const char *bind_user_shell, bool bind_user_shell_copy, + const char *bind_user_home_mount_directory, MachineBindUserContext **ret) { _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *c = NULL; @@ -279,7 +281,14 @@ int machine_bind_user_prepare( if (r < 0) return r; - r = convert_user(directory, u, g, current_uid, bind_user_shell, bind_user_shell_copy, &cu, &cg); + r = convert_user( + directory, + u, g, + current_uid, + bind_user_shell, + bind_user_shell_copy, + bind_user_home_mount_directory, + &cu, &cg); if (r < 0) return r; diff --git a/src/shared/machine-bind-user.h b/src/shared/machine-bind-user.h index 2751bc315b..c5537d34d2 100644 --- a/src/shared/machine-bind-user.h +++ b/src/shared/machine-bind-user.h @@ -27,4 +27,5 @@ int machine_bind_user_prepare( char **bind_user, const char *bind_user_shell, bool bind_user_shell_copy, + const char *bind_user_home_mount_directory, MachineBindUserContext **ret); From ac0a248ee63ec049c53a220767ad5f10f1d2c6df Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Oct 2025 21:38:18 +0200 Subject: [PATCH 4/5] vmspawn: Use machine_credential_add() --- src/vmspawn/vmspawn.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index c6d52cccb7..735d733f24 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2441,7 +2441,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (arg_forward_journal) { - _cleanup_free_ char *listen_address = NULL, *cred = NULL; + _cleanup_free_ char *listen_address = NULL; if (!GREEDY_REALLOC(children, n_children + 1)) return log_oom(); @@ -2459,11 +2459,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { pidref_done(&child); children[n_children++] = TAKE_PTR(source); - cred = strjoin("journal.forward_to_socket:", listen_address); - if (!cred) - return log_oom(); - - r = machine_credential_set(&arg_credentials, cred); + r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", listen_address, SIZE_MAX); if (r < 0) return r; } @@ -2509,13 +2505,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { /* on distros that provide their own sshd@.service file we need to provide a dropin which * picks up our public key credential */ - r = machine_credential_set( + r = machine_credential_add( &arg_credentials, - "systemd.unit-dropin.sshd-vsock@.service:" + "systemd.unit-dropin.sshd-vsock@.service", "[Service]\n" "ExecStart=\n" "ExecStart=-sshd -i -o 'AuthorizedKeysFile=%d/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys'\n" - "ImportCredential=ssh.ephemeral-authorized_keys-all\n"); + "ImportCredential=ssh.ephemeral-authorized_keys-all\n", + SIZE_MAX); if (r < 0) return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m"); } From b1681f981b51bd684ec170e46bf0f94b9b550c17 Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Mon, 14 Jul 2025 10:24:53 +0200 Subject: [PATCH 5/5] vmspawn: Add --bind-user= and --bind-user-shell= We use virtiofsd ID translation to mimick idmapped mounts and the transient userdb credentials to provision the mapped user in the VM. --- man/systemd-vmspawn.xml | 67 +++++++++++++++++ src/vmspawn/vmspawn-mount.c | 8 +- src/vmspawn/vmspawn-mount.h | 3 + src/vmspawn/vmspawn.c | 142 ++++++++++++++++++++++++++++++++++-- 4 files changed, 210 insertions(+), 10 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index e52903ae65..6d677cf106 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -459,6 +459,73 @@ + + + + Binds the home directory of the specified user on the host into the virtual + machine. Takes the name of an existing user on the host as argument. May be used multiple times to + bind multiple users into the virtual machine. This does two things: + + + The user's home directory is made available from the host into + /run/vmhost/home/ using virtiofs. virtiofsd id translation to map the host + user's UID/GID to its assigned UID/GID in the virtual machine. + + JSON user and group records are generated in that describes the mapped user which + are passed into the virtual machine using userdb.transient.* credentials. + They contain a minimized representation of the host's user record, adjusted to the UID/GID and + home directory path assigned to the user in the virtual machine. The + nss-systemd8 + glibc NSS module will pick up these records from there and make them available in the virtual + machine's user/group databases. + + + The combination of the two operations above ensures that it is possible to log into the + virtual machine using the same account information as on the host. The user is only mapped + transiently, while the virtual machine is running, and the mapping itself does not result in + persistent changes to the virtual machine (except maybe for log messages generated at login time, + and similar). Note that in particular the UID/GID assignment in the virtual machine is not made + persistently. If the user is mapped transiently, it is best to not allow the user to make + persistent changes to the virtual machine. If the user leaves files or directories owned by the + user, and those UIDs/GIDs are reused during later virtual machine invocations (possibly with a + different mapping), those files and directories will be accessible to + the "new" user. + + The user/group record mapping only works if the virtual machine contains systemd 258 or + newer, with nss-systemd properly configured in + nsswitch.conf. See + nss-systemd8 for + details. + + Note that the user record propagated from the host into the virtual machine will contain the + UNIX password hash of the user, so that seamless logins in the virtual machine are possible. If the + virtual machine is less trusted than the host it is hence important to use a strong UNIX password + hash function (e.g. yescrypt or similar, with the $y$ hash prefix). + + + + + + + + When used with , includes the specified shell in the + user records of users bound into the virtual machine. Takes either a boolean or an absolute path. + + + If false (the default), no shell is passed in the user records for users bound into + the virtual machine. This causes bound users to the use the virtual machine's default shell. + + If true, the shells specified by the host user records are included in the user records of all users bound into the virtual machine. + + If passed an absolute path, sets that path as the shell for user records of all users bound into the virtual machine. + + + Note: This will not check whether the specified shells exist in the virtual machine. + + This operation is only supported in combination with . + + + diff --git a/src/vmspawn/vmspawn-mount.c b/src/vmspawn/vmspawn-mount.c index 9f03a21655..feff5c8f4a 100644 --- a/src/vmspawn/vmspawn-mount.c +++ b/src/vmspawn/vmspawn-mount.c @@ -7,7 +7,7 @@ #include "string-util.h" #include "vmspawn-mount.h" -static void runtime_mount_done(RuntimeMount *mount) { +void runtime_mount_done(RuntimeMount *mount) { assert(mount); mount->source = mfree(mount->source); @@ -24,7 +24,11 @@ void runtime_mount_context_done(RuntimeMountContext *ctx) { } int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only) { - _cleanup_(runtime_mount_done) RuntimeMount mount = { .read_only = read_only }; + _cleanup_(runtime_mount_done) RuntimeMount mount = { + .read_only = read_only, + .source_uid = UID_INVALID, + .target_uid = UID_INVALID, + }; _cleanup_free_ char *source_rel = NULL; int r; diff --git a/src/vmspawn/vmspawn-mount.h b/src/vmspawn/vmspawn-mount.h index 9221519018..4647affb99 100644 --- a/src/vmspawn/vmspawn-mount.h +++ b/src/vmspawn/vmspawn-mount.h @@ -6,7 +6,9 @@ typedef struct RuntimeMount { bool read_only; char *source; + uid_t source_uid; char *target; + uid_t target_uid; } RuntimeMount; typedef struct RuntimeMountContext { @@ -14,5 +16,6 @@ typedef struct RuntimeMountContext { size_t n_mounts; } RuntimeMountContext; +void runtime_mount_done(RuntimeMount *mount); void runtime_mount_context_done(RuntimeMountContext *ctx); int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 735d733f24..3a2cd919b6 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -35,11 +35,13 @@ #include "format-util.h" #include "fs-util.h" #include "gpt.h" +#include "group-record.h" #include "hexdecoct.h" #include "hostname-setup.h" #include "hostname-util.h" #include "id128-util.h" #include "log.h" +#include "machine-bind-user.h" #include "machine-credential.h" #include "main-func.h" #include "mkdir.h" @@ -68,6 +70,8 @@ #include "terminal-util.h" #include "tmpfile-util.h" #include "unit-name.h" +#include "user-record.h" +#include "user-util.h" #include "utf8.h" #include "vmspawn-mount.h" #include "vmspawn-register.h" @@ -136,6 +140,9 @@ static char *arg_tpm_state_path = NULL; static TpmStateMode arg_tpm_state_mode = TPM_STATE_AUTO; static bool arg_ask_password = true; 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_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -155,6 +162,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep); STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep); 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 int help(void) { _cleanup_free_ char *link = NULL; @@ -215,6 +224,9 @@ static int help(void) { " --bind-ro=SOURCE[:TARGET]\n" " Mount a file or directory, but read-only\n" " --extra-drive=PATH Adds an additional disk to the virtual machine\n" + " --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" "\n%3$sIntegration:%4$s\n" " --forward-journal=FILE|DIR\n" " Forward the VM's journal to the host\n" @@ -289,6 +301,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_ASK_PASSWORD, ARG_PROPERTY, ARG_NOTIFY_READY, + ARG_BIND_USER, + ARG_BIND_USER_SHELL, }; static const struct option options[] = { @@ -338,6 +352,8 @@ static int parse_argv(int argc, char *argv[]) { { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { "property", required_argument, NULL, ARG_PROPERTY }, { "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 }, {} }; @@ -675,6 +691,30 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_BIND_USER: + if (!valid_user_group_name(optarg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg); + + if (strv_extend(&arg_bind_user, optarg) < 0) + return log_oom(); + + break; + + case ARG_BIND_USER_SHELL: { + bool copy = false; + char *sh = NULL; + r = parse_user_shell(optarg, &sh, ©); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(r, "Invalid user shell to bind: %s", optarg); + + free_and_replace(arg_bind_user_shell, sh); + arg_bind_user_shell_copy = copy; + + break; + } + case '?': return -EINVAL; @@ -682,6 +722,12 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + /* Drop duplicate --bind-user= entries */ + strv_uniq(arg_bind_user); + + 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 (argc > optind) { arg_kernel_cmdline_extra = strv_copy(argv + optind); if (!arg_kernel_cmdline_extra) @@ -1359,7 +1405,9 @@ static int find_virtiofsd(char **ret) { static int start_virtiofsd( const char *scope, const char *directory, - bool uidmap, + uid_t source_uid, + uid_t target_uid, + uid_t uid_range, const char *runtime_dir, const char *sd_socket_activate, char **ret_listen_address, @@ -1397,20 +1445,20 @@ static int start_virtiofsd( if (!argv) return log_oom(); - if (uidmap && arg_uid_shift != UID_INVALID) { - r = strv_extend(&argv, "--uid-map"); + if (source_uid != UID_INVALID && target_uid != UID_INVALID && uid_range != UID_INVALID) { + r = strv_extend(&argv, "--translate-uid"); if (r < 0) return log_oom(); - r = strv_extendf(&argv, ":0:" UID_FMT ":" UID_FMT ":", arg_uid_shift, arg_uid_range); + r = strv_extendf(&argv, "map:" UID_FMT ":" UID_FMT ":" UID_FMT, target_uid, source_uid, uid_range); if (r < 0) return log_oom(); - r = strv_extend(&argv, "--gid-map"); + r = strv_extend(&argv, "--translate-gid"); if (r < 0) return log_oom(); - r = strv_extendf(&argv, ":0:" GID_FMT ":" GID_FMT ":", arg_uid_shift, arg_uid_range); + r = strv_extendf(&argv, "map:" GID_FMT ":" GID_FMT ":" GID_FMT, target_uid, source_uid, uid_range); if (r < 0) return log_oom(); } @@ -1425,6 +1473,65 @@ static int start_virtiofsd( return 0; } +static int bind_user_setup( + const MachineBindUserContext *context, + MachineCredentialContext *credentials, + RuntimeMountContext *mounts) { + + int r; + + assert(credentials); + assert(mounts); + + if (!context) + return 0; + + FOREACH_ARRAY(bind_user, context->data, context->n_data) { + _cleanup_free_ char *formatted = NULL; + r = sd_json_variant_format(bind_user->payload_user->json, SD_JSON_FORMAT_NEWLINE, &formatted); + if (r < 0) + return log_error_errno(r, "Failed to format JSON user record: %m"); + + _cleanup_free_ char *cred = strjoin("userdb.transient.user.", bind_user->payload_user->user_name); + if (!cred) + return log_oom(); + + r = machine_credential_add(credentials, cred, formatted, SIZE_MAX); + if (r < 0) + return r; + + formatted = mfree(formatted); + r = sd_json_variant_format(bind_user->payload_group->json, SD_JSON_FORMAT_NEWLINE, &formatted); + if (r < 0) + return log_error_errno(r, "Failed to format JSON group record: %m"); + + free(cred); + cred = strjoin("userdb.transient.group.", bind_user->payload_group->group_name); + if (!cred) + return log_oom(); + + r = machine_credential_add(credentials, cred, formatted, SIZE_MAX); + if (r < 0) + return r; + + _cleanup_(runtime_mount_done) RuntimeMount mount = { + .source = strdup(user_record_home_directory(bind_user->host_user)), + .source_uid = bind_user->host_user->uid, + .target = strdup(user_record_home_directory(bind_user->payload_user)), + .target_uid = bind_user->payload_user->uid, + }; + if (!mount.source || !mount.target) + return log_oom(); + + if (!GREEDY_REALLOC(mounts->mounts, mounts->n_mounts + 1)) + return log_oom(); + + mounts->mounts[mounts->n_mounts++] = TAKE_STRUCT(mount); + } + + return 0; +} + static int kernel_cmdline_maybe_append_root(void) { int r; bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=") @@ -1726,6 +1833,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return log_error_errno(r, "Failed to find OVMF config: %m"); + _cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL; + r = machine_bind_user_prepare( + /* directory= */ NULL, + arg_bind_user, + arg_bind_user_shell, + arg_bind_user_shell_copy, + "/run/vmhost/home", + &bind_user_context); + if (r < 0) + return r; + + r = bind_user_setup(bind_user_context, &arg_credentials, &arg_runtime_mounts); + if (r < 0) + return r; + /* only warn if the user hasn't disabled secureboot */ if (!ovmf_config->supports_sb && arg_secure_boot) log_warning("Couldn't find OVMF firmware blob with Secure Boot support, " @@ -2177,7 +2299,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { r = start_virtiofsd( unit, arg_directory, - /* uidmap= */ true, + /* source_uid= */ arg_uid_shift, + /* target_uid= */ 0, + /* uid_range= */ arg_uid_range, runtime_dir, sd_socket_activate, &listen_address, @@ -2267,7 +2391,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { r = start_virtiofsd( unit, mount->source, - /* uidmap= */ false, + /* source_uid= */ mount->source_uid, + /* target_uid= */ mount->target_uid, + /* uid_range= */ 1U, runtime_dir, sd_socket_activate, &listen_address,