From fd1cd4a8436c77e1f55ffc18e0ec9ef6ed235bf5 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Tue, 23 Jan 2024 13:39:32 +0000 Subject: [PATCH 1/7] path-lookup: add runtime_directory for resolving $RUNTIME_DIRECTORY --- src/basic/path-lookup.c | 38 ++++++++++++++++++++++++++++++++++++++ src/basic/path-lookup.h | 1 + 2 files changed, 39 insertions(+) diff --git a/src/basic/path-lookup.c b/src/basic/path-lookup.c index 068054512b..1b31a4e23a 100644 --- a/src/basic/path-lookup.c +++ b/src/basic/path-lookup.c @@ -91,6 +91,44 @@ int xdg_user_data_dir(char **ret, const char *suffix) { return 1; } +int runtime_directory(char **ret, RuntimeScope scope, const char *suffix) { + _cleanup_free_ char *d = NULL; + int r; + + assert(ret); + assert(suffix); + assert(IN_SET(scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, RUNTIME_SCOPE_GLOBAL)); + + /* Accept $RUNTIME_DIRECTORY as authoritative + * If its missing apply the suffix to /run or $XDG_RUNTIME_DIR + * if we are in a user runtime scope. + * + * Return value indicates whether the suffix was applied or not */ + + const char *e = secure_getenv("RUNTIME_DIRECTORY"); + if (e) { + d = strdup(e); + if (!d) + return -ENOMEM; + + *ret = TAKE_PTR(d); + return false; + } + + if (scope == RUNTIME_SCOPE_USER) { + r = xdg_user_runtime_dir(&d, suffix); + if (r < 0) + return r; + } else { + d = path_join("/run", suffix); + if (!d) + return -ENOMEM; + } + + *ret = TAKE_PTR(d); + return true; +} + static const char* const user_data_unit_paths[] = { "/usr/local/lib/systemd/user", "/usr/local/share/systemd/user", diff --git a/src/basic/path-lookup.h b/src/basic/path-lookup.h index 1601787064..11db264c01 100644 --- a/src/basic/path-lookup.h +++ b/src/basic/path-lookup.h @@ -59,6 +59,7 @@ int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs); int xdg_user_runtime_dir(char **ret, const char *suffix); int xdg_user_config_dir(char **ret, const char *suffix); int xdg_user_data_dir(char **ret, const char *suffix); +int runtime_directory(char **ret, RuntimeScope scope, const char *suffix); bool path_is_user_data_dir(const char *path); bool path_is_user_config_dir(const char *path); From cf3beb273b2b81e0928a177bc0e18cf01e24c392 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Tue, 7 Nov 2023 14:04:11 +0000 Subject: [PATCH 2/7] vmspawn: add swtpm feature --- man/systemd-vmspawn.xml | 15 ++ src/vmspawn/meson.build | 1 + src/vmspawn/vmspawn-scope.c | 302 ++++++++++++++++++++++++++++++++++++ src/vmspawn/vmspawn-scope.h | 22 +++ src/vmspawn/vmspawn-util.h | 6 + src/vmspawn/vmspawn.c | 162 ++++++++++++++++++- 6 files changed, 504 insertions(+), 4 deletions(-) create mode 100644 src/vmspawn/vmspawn-scope.c create mode 100644 src/vmspawn/vmspawn-scope.h diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index d5ece4acea..d5eac5f50e 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -132,6 +132,21 @@ + + BOOL + + + Configure whether to use qemu with a virtual TPM or not. + If the option is not specified vmspawn will detect the presence of + swtpm8 and use it if available. + If yes is specified swtpm8 + is always used, and vice versa if no is set swtpm + 8 is never used. + Note: the virtual TPM used may change in future. + + + + diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index 800d7c362c..a67c66ebe1 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -3,6 +3,7 @@ libvmspawn_core_sources = files( 'vmspawn-settings.c', 'vmspawn-util.c', + 'vmspawn-scope.c', ) libvmspawn_core = static_library( 'vmspawn-core', diff --git a/src/vmspawn/vmspawn-scope.c b/src/vmspawn/vmspawn-scope.c new file mode 100644 index 0000000000..676dee0592 --- /dev/null +++ b/src/vmspawn/vmspawn-scope.c @@ -0,0 +1,302 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "bus-wait-for-jobs.h" +#include "escape.h" +#include "macro.h" +#include "process-util.h" +#include "random-util.h" +#include "socket-util.h" +#include "strv.h" +#include "unit-def.h" +#include "unit-name.h" +#include "vmspawn-scope.h" + +int start_transient_scope(sd_bus *bus, const char *machine_name, bool allow_pidfd, char **ret_scope) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_free_ char *scope = NULL, *description = NULL; + const char *object; + int r; + + assert(bus); + assert(machine_name); + + /* Creates a transient scope unit which tracks the lifetime of the current process */ + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch job: %m"); + + if (asprintf(&scope, "machine-%"PRIu64"-%s.scope", random_u64(), machine_name) < 0) + return log_oom(); + + description = strjoin("Virtual Machine ", machine_name); + if (!description) + return log_oom(); + + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ss", /* name */ scope, /* mode */ "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sa)(sa)(sa)", + "Description", "s", description, + "AddRef", "b", 1, + "CollectMode", "s", "inactive-or-failed"); + + if (allow_pidfd) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_set_pid(&pidref, getpid_cached()); + if (r < 0) + return log_error_errno(r, "Failed to allocate PID reference: %m"); + + r = bus_append_scope_pidref(m, &pidref); + } else + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, getpid_cached()); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* No auxiliary units */ + r = sd_bus_message_append( + m, + "a(sa(sv))", + 0); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + /* If this failed with a property we couldn't write, this is quite likely because the server + * doesn't support PIDFDs yet, let's try without. */ + if (allow_pidfd && + sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY)) + return start_transient_scope(bus, machine_name, false, ret_scope); + + return log_error_errno(r, "Failed to start transient scope unit: %s", bus_error_message(&error, r)); + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, /* quiet */ false, NULL); + if (r < 0) + return r; + + if (ret_scope) + *ret_scope = TAKE_PTR(scope); + + return 0; +} + +static int message_add_commands(sd_bus_message *m, const char *exec_type, char ***commands, size_t n_commands) { + int r; + + assert(m); + assert(exec_type); + assert(commands || n_commands == 0); + + /* A small helper for adding an ExecStart / ExecStopPost / etc.. property to an sd_bus_message */ + + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", exec_type); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', "a(sasb)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sasb)"); + if (r < 0) + return bus_log_create_error(r); + + FOREACH_ARRAY(cmd, commands, n_commands) { + char **cmdline = *cmd; + + r = sd_bus_message_open_container(m, 'r', "sasb"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", cmdline[0]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, cmdline); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "b", 0); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 0; +} + +void socket_service_pair_done(SocketServicePair *p) { + p->exec_start = strv_free(p->exec_start); + p->exec_stop_post = strv_free(p->exec_stop_post); + p->unit_name_prefix = mfree(p->unit_name_prefix); + p->runtime_directory = mfree(p->runtime_directory); + p->listen_address = mfree(p->listen_address); + p->socket_type = 0; +} + +int start_socket_service_pair(sd_bus *bus, const char *scope, SocketServicePair *p) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_free_ char *service_desc = NULL, *service_name = NULL, *socket_name = NULL; + const char *object, *socket_type_str; + int r; + + /* Starts a socket/service unit pair bound to the given scope. */ + + assert(bus); + assert(scope); + assert(p); + assert(p->unit_name_prefix); + assert(p->exec_start); + assert(p->listen_address); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch job: %m"); + + socket_name = strjoin(p->unit_name_prefix, ".socket"); + if (!socket_name) + return log_oom(); + + service_name = strjoin(p->unit_name_prefix, ".service"); + if (!service_name) + return log_oom(); + + service_desc = quote_command_line(p->exec_start, SHELL_ESCAPE_EMPTY); + if (!service_desc) + return log_oom(); + + socket_type_str = socket_address_type_to_string(p->socket_type); + if (!socket_type_str) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Invalid socket type: %d", p->socket_type); + + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ssa(sv)", + /* ss - name, mode */ + socket_name, "fail", + /* a(sv) - Properties */ + 5, + "Description", "s", p->listen_address, + "AddRef", "b", 1, + "BindsTo", "as", 1, scope, + "Listen", "a(ss)", 1, socket_type_str, p->listen_address, + "CollectMode", "s", "inactive-or-failed"); + if (r < 0) + return bus_log_create_error(r); + + /* aux */ + r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'r', "sa(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", service_name); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sv)(sv)(sv)(sv)", + "Description", "s", service_desc, + "AddRef", "b", 1, + "BindsTo", "as", 1, scope, + "CollectMode", "s", "inactive-or-failed"); + if (r < 0) + return bus_log_create_error(r); + + if (p->runtime_directory) { + r = sd_bus_message_append(m, "(sv)", "RuntimeDirectory", "as", 1, p->runtime_directory); + if (r < 0) + return bus_log_create_error(r); + } + + r = message_add_commands(m, "ExecStart", &p->exec_start, 1); + if (r < 0) + return r; + + if (p->exec_stop_post) { + r = message_add_commands(m, "ExecStopPost", &p->exec_stop_post, 1); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start %s as transient unit: %s", p->exec_start[0], bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + return bus_wait_for_jobs_one(w, object, /* quiet */ false, NULL); +} diff --git a/src/vmspawn/vmspawn-scope.h b/src/vmspawn/vmspawn-scope.h new file mode 100644 index 0000000000..b807c3b203 --- /dev/null +++ b/src/vmspawn/vmspawn-scope.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-bus.h" + +#include "macro.h" + +typedef struct SocketServicePair { + char **exec_start; + char **exec_stop_post; + char *unit_name_prefix; + char *runtime_directory; + char *listen_address; + int socket_type; +} SocketServicePair; + +void socket_service_pair_done(SocketServicePair *p); + +int start_transient_scope(sd_bus *bus, const char *machine_name, bool allow_pidfd, char **ret_scope); +int start_socket_service_pair(sd_bus *bus, const char *scope, SocketServicePair *p); diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index f2d1b621e2..c3dbdf205b 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -10,6 +10,12 @@ #define ARCHITECTURE_SUPPORTS_SMBIOS 0 #endif +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +#define ARCHITECTURE_SUPPORTS_TPM 1 +#else +#define ARCHITECTURE_SUPPORTS_TPM 0 +#endif + #if defined(__arm__) || defined(__aarch64__) #define DEFAULT_SERIAL_TTY "ttyAMA0" #elif defined(__s390__) || defined(__s390x__) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index cc21efbf44..8138d142e8 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include "alloc-util.h" @@ -22,17 +21,22 @@ #include "log.h" #include "machine-credential.h" #include "main-func.h" +#include "mkdir.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-lookup.h" #include "path-util.h" #include "pretty-print.h" #include "process-util.h" +#include "rm-rf.h" #include "sd-event.h" #include "signal-util.h" #include "socket-util.h" #include "strv.h" #include "tmpfile-util.h" +#include "unit-name.h" +#include "vmspawn-scope.h" #include "vmspawn-settings.h" #include "vmspawn-util.h" @@ -45,16 +49,21 @@ static uint64_t arg_qemu_mem = UINT64_C(2) * U64_GB; static int arg_qemu_kvm = -1; static int arg_qemu_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; +static int arg_tpm = -1; static bool arg_qemu_gui = false; static int arg_secure_boot = -1; static MachineCredentialContext arg_credentials = {}; static SettingsMask arg_settings_mask = 0; static char **arg_parameters = NULL; static char *arg_firmware = NULL; +static char *arg_runtime_directory = NULL; +static bool arg_runtime_directory_created = false; +static bool arg_privileged = false; STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_machine, freep); STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep); +STATIC_DESTRUCTOR_REGISTER(arg_runtime_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep); @@ -84,6 +93,7 @@ static int help(void) { " --qemu-kvm=BOOL Configure whether to use KVM or not\n" " --qemu-vsock=BOOL Configure whether to use qemu with a vsock or not\n" " --vsock-cid= Specify the CID to use for the qemu guest's vsock\n" + " --tpm=BOOL Configure whether to use a virtual TPM or not\n" " --qemu-gui Start QEMU in graphical mode\n" " --secure-boot=BOOL Configure whether to search for firmware which\n" " supports Secure Boot\n" @@ -117,6 +127,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_QEMU_KVM, ARG_QEMU_VSOCK, ARG_VSOCK_CID, + ARG_TPM, ARG_QEMU_GUI, ARG_SECURE_BOOT, ARG_SET_CREDENTIAL, @@ -136,6 +147,7 @@ static int parse_argv(int argc, char *argv[]) { { "qemu-kvm", required_argument, NULL, ARG_QEMU_KVM }, { "qemu-vsock", required_argument, NULL, ARG_QEMU_VSOCK }, { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, + { "tpm", required_argument, NULL, ARG_TPM }, { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, @@ -228,6 +240,12 @@ static int parse_argv(int argc, char *argv[]) { } break; + case ARG_TPM: + r = parse_tristate(optarg, &arg_tpm); + if (r < 0) + return log_error_errno(r, "Failed to parse --tpm=%s: %m", optarg); + break; + case ARG_QEMU_GUI: arg_qemu_gui = true; break; @@ -482,12 +500,80 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { return 0; } +static int start_tpm(sd_bus *bus, const char *scope, const char *tpm, const char **ret_state_tempdir) { + _cleanup_(rm_rf_physical_and_freep) char *state_dir = NULL; + _cleanup_free_ char *scope_prefix = NULL; + _cleanup_(socket_service_pair_done) SocketServicePair ssp = { + .socket_type = SOCK_STREAM, + }; + int r; + + assert(bus); + assert(scope); + assert(tpm); + assert(ret_state_tempdir); + + r = unit_name_to_prefix(scope, &scope_prefix); + if (r < 0) + return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); + + ssp.unit_name_prefix = strjoin(scope_prefix, "-tpm"); + if (!ssp.unit_name_prefix) + return log_oom(); + + state_dir = path_join(arg_runtime_directory, ssp.unit_name_prefix); + if (!state_dir) + return log_oom(); + + if (arg_runtime_directory_created) { + ssp.runtime_directory = path_join("systemd/vmspawn", ssp.unit_name_prefix); + if (!ssp.runtime_directory) + return log_oom(); + } + + ssp.listen_address = path_join(state_dir, "sock"); + if (!ssp.listen_address) + return log_oom(); + + ssp.exec_start = strv_new(tpm, "socket", "--tpm2", "--tpmstate"); + if (!ssp.exec_start) + return log_oom(); + + r = strv_extendf(&ssp.exec_start, "dir=%s", state_dir); + if (r < 0) + return log_oom(); + + r = strv_extend_many(&ssp.exec_start, "--ctrl", "type=unixio,fd=3"); + if (r < 0) + return log_oom(); + + r = start_socket_service_pair(bus, scope, &ssp); + if (r < 0) + return r; + + *ret_state_tempdir = TAKE_PTR(state_dir); + + return 0; +} + static int run_virtual_machine(void) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; - _cleanup_strv_free_ char **cmdline = NULL; - _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL; - int r; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_close_ int vsock_fd = -EBADF; + _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL; + _cleanup_strv_free_ char **cmdline = NULL; + int r; + + if (arg_privileged) + r = sd_bus_default_system(&bus); + else + r = sd_bus_default_user(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd bus: %m"); + + r = start_transient_scope(bus, arg_machine, /* allow_pidfd= */ true, &trans_scope); + if (r < 0) + return r; bool use_kvm = arg_qemu_kvm > 0; if (arg_qemu_kvm < 0) { @@ -538,6 +624,20 @@ static int run_virtual_machine(void) { if (!cmdline) return log_oom(); + /* if we are going to be starting any units with state then create our runtime dir */ + if (arg_tpm != 0) { + r = runtime_directory(&arg_runtime_directory, arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, "systemd/vmspawn"); + if (r < 0) + return log_error_errno(r, "Failed to lookup runtime directory: %m"); + if (r) { + /* r > 0 means we need to create our own runtime dir */ + r = mkdir_p(arg_runtime_directory, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create runtime directory: %m"); + arg_runtime_directory_created = true; + } + } + bool use_vsock = arg_qemu_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; if (arg_qemu_vsock < 0) { r = qemu_check_vsock_support(); @@ -685,6 +785,58 @@ static int run_virtual_machine(void) { } else log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS"); + /* disable TPM autodetection if the user's hardware doesn't support it */ + if (!ARCHITECTURE_SUPPORTS_TPM) { + if (arg_tpm < 0) { + arg_tpm = 0; + log_debug("TPM not support on %s, disabling tpm autodetection and continuing", architecture_to_string(native_architecture())); + } else if (arg_tpm > 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM not supported on %s, refusing", architecture_to_string(native_architecture())); + } + + _cleanup_free_ char *swtpm = NULL; + if (arg_tpm != 0) { + r = find_executable("swtpm", &swtpm); + if (r < 0) { + /* log if the user asked for swtpm and we cannot find it */ + if (arg_tpm > 0) + return log_error_errno(r, "Failed to find swtpm binary: %m"); + /* also log if we got an error other than ENOENT from find_executable */ + if (r != -ENOENT && arg_tpm < 0) + return log_error_errno(r, "Error detecting swtpm: %m"); + } + } + + _cleanup_free_ const char *tpm_state_tempdir = NULL; + if (swtpm) { + r = start_tpm(bus, trans_scope, swtpm, &tpm_state_tempdir); + if (r < 0) { + /* only bail if the user asked for a tpm */ + if (arg_tpm > 0) + return log_error_errno(r, "Failed to start tpm: %m"); + log_debug_errno(r, "Failed to start tpm, ignoring: %m"); + } + + r = strv_extend(&cmdline, "-chardev"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "socket,id=chrtpm,path=%s/sock", tpm_state_tempdir); + if (r < 0) + return log_oom(); + + r = strv_extend_many(&cmdline, "-tpmdev", "emulator,id=tpm0,chardev=chrtpm"); + if (r < 0) + return log_oom(); + + if (native_architecture() == ARCHITECTURE_X86_64) + r = strv_extend_many(&cmdline, "-device", "tpm-tis,tpmdev=tpm0"); + else if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) + r = strv_extend_many(&cmdline, "-device", "tpm-tis-device,tpmdev=tpm0"); + if (r < 0) + return log_oom(); + } + if (use_vsock) { vsock_fd = open_vsock(); if (vsock_fd < 0) @@ -803,6 +955,8 @@ static int run(int argc, char *argv[]) { log_setup(); + arg_privileged = getuid() == 0; + r = parse_argv(argc, argv); if (r <= 0) return r; From 0f25e3e44387b6faed9a541596794705c8a205dc Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Fri, 10 Nov 2023 11:56:49 +0000 Subject: [PATCH 3/7] vmspawn: add kernel configuration options --- man/systemd-vmspawn.xml | 10 ++++++++ src/vmspawn/vmspawn.c | 52 ++++++++++++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index d5eac5f50e..ce352bc2d0 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -147,6 +147,16 @@ + + PATH + + + Set the linux kernel image to use for direct kernel boot. + If no kernel was installed into the image then the image will fail to boot. + + + + diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8138d142e8..985abd9fbe 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -50,23 +50,25 @@ static int arg_qemu_kvm = -1; static int arg_qemu_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; static int arg_tpm = -1; +static char *arg_linux = NULL; static bool arg_qemu_gui = false; static int arg_secure_boot = -1; static MachineCredentialContext arg_credentials = {}; static SettingsMask arg_settings_mask = 0; -static char **arg_parameters = NULL; static char *arg_firmware = NULL; static char *arg_runtime_directory = NULL; static bool arg_runtime_directory_created = false; static bool arg_privileged = false; +static char **arg_kernel_cmdline_extra = NULL; STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_machine, freep); STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep); STATIC_DESTRUCTOR_REGISTER(arg_runtime_directory, freep); -STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep); +STATIC_DESTRUCTOR_REGISTER(arg_linux, freep); +STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -94,6 +96,7 @@ static int help(void) { " --qemu-vsock=BOOL Configure whether to use qemu with a vsock or not\n" " --vsock-cid= Specify the CID to use for the qemu guest's vsock\n" " --tpm=BOOL Configure whether to use a virtual TPM or not\n" + " --linux=PATH Specify the linux kernel for direct kernel boot\n" " --qemu-gui Start QEMU in graphical mode\n" " --secure-boot=BOOL Configure whether to search for firmware which\n" " supports Secure Boot\n" @@ -128,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_QEMU_VSOCK, ARG_VSOCK_CID, ARG_TPM, + ARG_LINUX, ARG_QEMU_GUI, ARG_SECURE_BOOT, ARG_SET_CREDENTIAL, @@ -148,6 +152,7 @@ static int parse_argv(int argc, char *argv[]) { { "qemu-vsock", required_argument, NULL, ARG_QEMU_VSOCK }, { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, { "tpm", required_argument, NULL, ARG_TPM }, + { "linux", required_argument, NULL, ARG_LINUX }, { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, @@ -246,6 +251,12 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse --tpm=%s: %m", optarg); break; + case ARG_LINUX: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_linux); + if (r < 0) + return r; + break; + case ARG_QEMU_GUI: arg_qemu_gui = true; break; @@ -306,9 +317,8 @@ static int parse_argv(int argc, char *argv[]) { } if (argc > optind) { - strv_free(arg_parameters); - arg_parameters = strv_copy(argv + optind); - if (!arg_parameters) + arg_kernel_cmdline_extra = strv_copy(argv + optind); + if (!arg_kernel_cmdline_extra) return log_oom(); arg_settings_mask |= SETTING_START_MODE; @@ -660,7 +670,6 @@ static int run_virtual_machine(void) { if (r < 0) return log_oom(); - log_debug("vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, child_vsock_fd); r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, child_vsock_fd); if (r < 0) return log_oom(); @@ -751,6 +760,12 @@ static int run_virtual_machine(void) { return log_oom(); } + if (arg_linux) { + r = strv_extend_many(&cmdline, "-kernel", arg_linux); + if (r < 0) + return log_oom(); + } + r = strv_extend(&cmdline, "-drive"); if (r < 0) return log_oom(); @@ -766,22 +781,31 @@ static int run_virtual_machine(void) { if (r < 0) return log_oom(); - r = strv_prepend(&arg_parameters, "console=" DEFAULT_SERIAL_TTY); + r = strv_prepend(&arg_kernel_cmdline_extra, "console=" DEFAULT_SERIAL_TTY); if (r < 0) return log_oom(); if (ARCHITECTURE_SUPPORTS_SMBIOS) { - _cleanup_free_ char *kcl = strv_join(arg_parameters, " "); + _cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " "); if (!kcl) return log_oom(); - r = strv_extend(&cmdline, "-smbios"); - if (r < 0) - return log_oom(); + if (arg_linux) { + r = strv_extend_many(&cmdline, "-append", kcl); + if (r < 0) + return log_oom(); + } else { + if (ARCHITECTURE_SUPPORTS_SMBIOS) { + r = strv_extend(&cmdline, "-smbios"); + if (r < 0) + return log_oom(); - r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl); - if (r < 0) - return log_oom(); + r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl); + if (r < 0) + return log_oom(); + } else + log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring"); + } } else log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS"); From 19301e76e62aa3525c327b879f9d06ffd66cc6c5 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Wed, 20 Dec 2023 09:03:01 +0000 Subject: [PATCH 4/7] vmspawn: synthesise root= argument for direct kernel boot --- src/vmspawn/meson.build | 1 + src/vmspawn/vmspawn.c | 68 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index a67c66ebe1..5f54364fac 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -24,5 +24,6 @@ executables += [ 'conditions': ['ENABLE_VMSPAWN'], 'sources' : files('vmspawn.c'), 'link_with' : vmspawn_libs, + 'dependencies' : [libblkid] } ] diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 985abd9fbe..bc9ee01e77 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -5,17 +5,22 @@ #include #include +#include "sd-event.h" +#include "sd-id128.h" + #include "alloc-util.h" #include "architecture.h" #include "build.h" #include "common-signal.h" #include "copy.h" #include "creds-util.h" +#include "dissect-image.h" #include "escape.h" #include "event-util.h" #include "fileio.h" #include "format-util.h" #include "fs-util.h" +#include "gpt.h" #include "hexdecoct.h" #include "hostname-util.h" #include "log.h" @@ -30,7 +35,6 @@ #include "pretty-print.h" #include "process-util.h" #include "rm-rf.h" -#include "sd-event.h" #include "signal-util.h" #include "socket-util.h" #include "strv.h" @@ -566,6 +570,60 @@ static int start_tpm(sd_bus *bus, const char *scope, const char *tpm, const char return 0; } +static int discover_root(char **ret) { + int r; + _cleanup_(dissected_image_unrefp) DissectedImage *image = NULL; + _cleanup_free_ char *root = NULL; + + assert(ret); + + r = dissect_image_file_and_warn( + arg_image, + /* verity= */ NULL, + /* mount_options= */ NULL, + /* image_policy= */ NULL, + /* flags= */ 0, + &image); + if (r < 0) + return r; + + if (image->partitions[PARTITION_ROOT].found) + root = strjoin("root=PARTUUID=", SD_ID128_TO_UUID_STRING(image->partitions[PARTITION_ROOT].uuid)); + else if (image->partitions[PARTITION_USR].found) + root = strjoin("mount.usr=PARTUUID=", SD_ID128_TO_UUID_STRING(image->partitions[PARTITION_USR].uuid)); + else + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Cannot perform a direct kernel boot without a root or usr partition, refusing"); + + if (!root) + return log_oom(); + + *ret = TAKE_PTR(root); + + return 0; +} + +static int kernel_cmdline_maybe_append_root(void) { + int r; + bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=") + || strv_find_startswith(arg_kernel_cmdline_extra, "mount.usr="); + + if (!cmdline_contains_root) { + _cleanup_free_ char *root = NULL; + + r = discover_root(&root); + if (r < 0) + return r; + + log_debug("Determined root file system %s from dissected image", root); + + r = strv_consume(&arg_kernel_cmdline_extra, TAKE_PTR(root)); + if (r < 0) + return log_oom(); + } + + return 0; +} + static int run_virtual_machine(void) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -764,6 +822,14 @@ static int run_virtual_machine(void) { r = strv_extend_many(&cmdline, "-kernel", arg_linux); if (r < 0) return log_oom(); + + /* We can't rely on gpt-auto-generator when direct kernel booting so synthesize a root= + * kernel argument instead. */ + if (arg_image) { + r = kernel_cmdline_maybe_append_root(); + if (r < 0) + return r; + } } r = strv_extend(&cmdline, "-drive"); From 88af28d1701ab4f4a8c3f5d62ac0019b09cb94f2 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Fri, 8 Dec 2023 13:32:34 +0000 Subject: [PATCH 5/7] vmspawn: add initrd configuration option --- man/systemd-vmspawn.xml | 34 +++++++++++++++++++++++----------- src/vmspawn/vmspawn.c | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index ce352bc2d0..a9011d9106 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -158,20 +158,20 @@ - + PATH - Start QEMU in graphical mode. - - + + Set the initrd to use for direct kernel boot. + If the linux kernel supplied is a UKI then this argument is not required. + If no initrd was installed into the image then the image will fail to boot. + + - BOOL + - Configure whether to search for firmware which supports Secure Boot. - If the option is not specified the first firmware which is detected will be used. - If the option is set to yes then the first firmware with Secure Boot support will be selected. - If no is specified then the first firmware without Secure Boot will be selected. + Start QEMU in graphical mode. @@ -186,9 +186,21 @@ - - + + BOOL + + Configure whether to search for firmware which supports Secure Boot. + If the option is not specified the first firmware which is detected will be used. + If the option is set to yes then the first firmware with Secure Boot support will be selected. + If no is specified then the first firmware without Secure Boot will be selected. + + + + + + + System Identity Options diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index bc9ee01e77..5e5139d574 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "sd-event.h" @@ -23,8 +24,10 @@ #include "gpt.h" #include "hexdecoct.h" #include "hostname-util.h" +#include "kernel-image.h" #include "log.h" #include "machine-credential.h" +#include "macro.h" #include "main-func.h" #include "mkdir.h" #include "pager.h" @@ -37,6 +40,7 @@ #include "rm-rf.h" #include "signal-util.h" #include "socket-util.h" +#include "string-util.h" #include "strv.h" #include "tmpfile-util.h" #include "unit-name.h" @@ -55,6 +59,7 @@ static int arg_qemu_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; static int arg_tpm = -1; static char *arg_linux = NULL; +static char *arg_initrd = NULL; static bool arg_qemu_gui = false; static int arg_secure_boot = -1; static MachineCredentialContext arg_credentials = {}; @@ -72,6 +77,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_runtime_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep); STATIC_DESTRUCTOR_REGISTER(arg_linux, freep); +STATIC_DESTRUCTOR_REGISTER(arg_initrd, freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep); static int help(void) { @@ -101,6 +107,7 @@ static int help(void) { " --vsock-cid= Specify the CID to use for the qemu guest's vsock\n" " --tpm=BOOL Configure whether to use a virtual TPM or not\n" " --linux=PATH Specify the linux kernel for direct kernel boot\n" + " --initrd=PATH Specify the initrd for direct kernel boot\n" " --qemu-gui Start QEMU in graphical mode\n" " --secure-boot=BOOL Configure whether to search for firmware which\n" " supports Secure Boot\n" @@ -136,6 +143,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VSOCK_CID, ARG_TPM, ARG_LINUX, + ARG_INITRD, ARG_QEMU_GUI, ARG_SECURE_BOOT, ARG_SET_CREDENTIAL, @@ -157,6 +165,7 @@ static int parse_argv(int argc, char *argv[]) { { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, { "tpm", required_argument, NULL, ARG_TPM }, { "linux", required_argument, NULL, ARG_LINUX }, + { "initrd", required_argument, NULL, ARG_INITRD }, { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, @@ -261,6 +270,13 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_INITRD: { + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_initrd); + if (r < 0) + return r; + break; + } + case ARG_QEMU_GUI: arg_qemu_gui = true; break; @@ -927,6 +943,12 @@ static int run_virtual_machine(void) { return log_oom(); } + if (arg_initrd) { + r = strv_extend_many(&cmdline, "-initrd", arg_initrd); + if (r < 0) + return log_oom(); + } + if (use_vsock) { vsock_fd = open_vsock(); if (vsock_fd < 0) From 75331bedeb7567e1ecca157818ace92791c31229 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Fri, 10 Nov 2023 17:32:25 +0000 Subject: [PATCH 6/7] vmspawn: add nic configuration --- man/systemd-vmspawn.xml | 25 +++++++++++++ src/vmspawn/vmspawn-util.c | 3 ++ src/vmspawn/vmspawn-util.h | 17 +++++++++ src/vmspawn/vmspawn.c | 77 +++++++++++++++++++++++++++----------- 4 files changed, 100 insertions(+), 22 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index a9011d9106..38f5e3c93e 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -176,6 +176,31 @@ + + + + + + Create a TAP device to network with the virtual machine. + + Note: root privileges are required to use TAP networking. + Additionally requires a correctly setup + systemd-networkd8 + to be running on the host to ensure the host interface is correctly configured. + The relevant .network file can be found at /usr/lib/systemd/network/80-vm-vt.network. + + + + + + + + + Use user mode networking with QEMU. + + + + PATH diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 79e1e5399a..822b102912 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -20,6 +20,7 @@ #include "siphash24.h" #include "socket-util.h" #include "sort-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "vmspawn-util.h" @@ -35,6 +36,8 @@ OvmfConfig* ovmf_config_free(OvmfConfig *config) { return mfree(config); } +DEFINE_STRING_TABLE_LOOKUP(qemu_network_stack, QemuNetworkStack); + int qemu_check_kvm_support(void) { if (access("/dev/kvm", F_OK) >= 0) return true; diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index c3dbdf205b..9c9b1867d3 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -45,6 +45,23 @@ static inline const char *ovmf_config_vars_format(const OvmfConfig *c) { OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config); DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free); +typedef enum QemuNetworkStack { + QEMU_NET_TAP, + QEMU_NET_USER, + QEMU_NET_NONE, + _QEMU_NET_MAX, + _QEMU_NET_INVALID = -EINVAL, +} QemuNetworkStack; + +static const char* const qemu_network_stack_table[_QEMU_NET_MAX] = { + [QEMU_NET_TAP] = "tap", + [QEMU_NET_USER] = "user", + [QEMU_NET_NONE] = "none", +}; + +const char* qemu_network_stack_to_string(QemuNetworkStack type) _const_; +QemuNetworkStack qemu_network_stack_from_string(const char *s) _pure_; + int qemu_check_kvm_support(void); int qemu_check_vsock_support(void); int list_ovmf_config(char ***ret); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 5e5139d574..37c354f0b0 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -61,6 +61,7 @@ static int arg_tpm = -1; static char *arg_linux = NULL; static char *arg_initrd = NULL; static bool arg_qemu_gui = false; +static QemuNetworkStack arg_network_stack = QEMU_NET_NONE; static int arg_secure_boot = -1; static MachineCredentialContext arg_credentials = {}; static SettingsMask arg_settings_mask = 0; @@ -109,6 +110,8 @@ static int help(void) { " --linux=PATH Specify the linux kernel for direct kernel boot\n" " --initrd=PATH Specify the initrd for direct kernel boot\n" " --qemu-gui Start QEMU in graphical mode\n" + " -n --network-tap Create a TAP device for networking with QEMU.\n" + " --network-user-mode Use user mode networking with QEMU.\n" " --secure-boot=BOOL Configure whether to search for firmware which\n" " supports Secure Boot\n" " --firmware=PATH|list Select firmware definition file (or list available)\n" @@ -145,6 +148,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LINUX, ARG_INITRD, ARG_QEMU_GUI, + ARG_NETWORK_USER_MODE, ARG_SECURE_BOOT, ARG_SET_CREDENTIAL, ARG_LOAD_CREDENTIAL, @@ -152,25 +156,27 @@ static int parse_argv(int argc, char *argv[]) { }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "image", required_argument, NULL, 'i' }, - { "machine", required_argument, NULL, 'M' }, - { "qemu-smp", required_argument, NULL, ARG_QEMU_SMP }, - { "qemu-mem", required_argument, NULL, ARG_QEMU_MEM }, - { "qemu-kvm", required_argument, NULL, ARG_QEMU_KVM }, - { "qemu-vsock", required_argument, NULL, ARG_QEMU_VSOCK }, - { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, - { "tpm", required_argument, NULL, ARG_TPM }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, - { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, - { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, - { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, - { "firmware", required_argument, NULL, ARG_FIRMWARE }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "quiet", no_argument, NULL, 'q' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "image", required_argument, NULL, 'i' }, + { "machine", required_argument, NULL, 'M' }, + { "qemu-smp", required_argument, NULL, ARG_QEMU_SMP }, + { "qemu-mem", required_argument, NULL, ARG_QEMU_MEM }, + { "qemu-kvm", required_argument, NULL, ARG_QEMU_KVM }, + { "qemu-vsock", required_argument, NULL, ARG_QEMU_VSOCK }, + { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, + { "tpm", required_argument, NULL, ARG_TPM }, + { "linux", required_argument, NULL, ARG_LINUX }, + { "initrd", required_argument, NULL, ARG_INITRD }, + { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, + { "network-tap", no_argument, NULL, 'n' }, + { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE }, + { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, + { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, + { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, + { "firmware", required_argument, NULL, ARG_FIRMWARE }, {} }; @@ -180,7 +186,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argv); optind = 0; - while ((c = getopt_long(argc, argv, "+hi:M:q", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hi:M:nq", options, NULL)) >= 0) switch (c) { case 'h': return help(); @@ -281,6 +287,14 @@ static int parse_argv(int argc, char *argv[]) { arg_qemu_gui = true; break; + case 'n': + arg_network_stack = QEMU_NET_TAP; + break; + + case ARG_NETWORK_USER_MODE: + arg_network_stack = QEMU_NET_USER; + break; + case ARG_SECURE_BOOT: r = parse_tristate(optarg, &arg_secure_boot); if (r < 0) @@ -702,8 +716,7 @@ static int run_virtual_machine(void) { "-smp", arg_qemu_smp ?: "1", "-m", mem, "-object", "rng-random,filename=/dev/urandom,id=rng0", - "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", - "-nic", "user,model=virtio-net-pci" + "-device", "virtio-rng-pci,rng=rng0,id=rng-device0" ); if (!cmdline) return log_oom(); @@ -722,6 +735,15 @@ static int run_virtual_machine(void) { } } + if (arg_network_stack == QEMU_NET_TAP) + r = strv_extend_many(&cmdline, "-nic", "tap,script=no,model=virtio-net-pci"); + else if (arg_network_stack == QEMU_NET_USER) + r = strv_extend_many(&cmdline, "-nic", "user,model=virtio-net-pci"); + else + r = strv_extend_many(&cmdline, "-nic", "none"); + if (r < 0) + return log_oom(); + bool use_vsock = arg_qemu_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; if (arg_qemu_vsock < 0) { r = qemu_check_vsock_support(); @@ -1062,6 +1084,13 @@ static int determine_names(void) { return 0; } +static int verify_arguments(void) { + if (arg_network_stack == QEMU_NET_TAP && !arg_privileged) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--network-tap requires root privileges, refusing."); + + return 0; +} + static int run(int argc, char *argv[]) { int r; @@ -1077,6 +1106,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = verify_arguments(); + if (r < 0) + return r; + if (!arg_quiet) { _cleanup_free_ char *u = NULL; (void) terminal_urlify_path(arg_image, arg_image, &u); From 51747b3444c82ccae95ead10805b2bb336528e96 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Thu, 18 Jan 2024 12:32:10 +0000 Subject: [PATCH 7/7] vmspawn: accept kvm/vhost-vsock device fds through sd_listen --- man/systemd-vmspawn.xml | 6 +++ src/vmspawn/vmspawn-util.c | 19 ++----- src/vmspawn/vmspawn-util.h | 2 +- src/vmspawn/vmspawn.c | 106 +++++++++++++++++++++++++++++-------- 4 files changed, 96 insertions(+), 37 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 38f5e3c93e..b84cc9c062 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -35,6 +35,12 @@ project='man-pages'>systemd-nspawn1, but it launches a full virtual machine instead of using namespaces. + File descriptors for /dev/kvm and /dev/vhost-vsock can be + passed to systemd-vmspawn via systemd's native socket passing interface (see + sd_listen_fds3 for + details about the precise protocol used and the order in which the file descriptors are passed), these + fds must be passed with the names kvm and vhost-vsock respectively. + Note: on Ubuntu/Debian derivatives systemd-vmspawn requires the user to be in the kvm group to use the VSock options. diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 822b102912..42c7bbfac6 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -367,7 +367,7 @@ int find_qemu_binary(char **ret_qemu_binary) { return find_executable(qemu_arch_specific, ret_qemu_binary); } -int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock) { +int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine) { /* this is an arbitrary value picked from /dev/urandom */ static const uint8_t sip_key[HASH_KEY_SIZE] = { 0x03, 0xad, 0xf0, 0xa4, @@ -376,14 +376,13 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi 0xf5, 0x4c, 0x80, 0x52 }; struct siphash machine_hash_state, state; - _cleanup_close_ int vfd = -EBADF; int r; /* uint64_t is required here for the ioctl call, but valid CIDs are only 32 bits */ uint64_t cid = *ASSERT_PTR(machine_cid); assert(machine); - assert(ret_child_sock); + assert(vhost_device_fd >= 0); /* Fix the CID of the AF_VSOCK socket passed to qemu * @@ -396,16 +395,10 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi * If after another 64 attempts this hasn't worked then give up and return EADDRNOTAVAIL. */ - /* remove O_CLOEXEC before this fd is passed to QEMU */ - vfd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); - if (vfd < 0) - return log_debug_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m"); - if (cid != VMADDR_CID_ANY) { - r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid); + r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid); if (r < 0) return log_debug_errno(errno, "Failed to set CID for child vsock with user provided CID %" PRIu64 ": %m", cid); - *ret_child_sock = TAKE_FD(vfd); return 0; } @@ -417,10 +410,9 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi uint64_t hash = siphash24_finalize(&state); cid = 3 + (hash % (UINT_MAX - 4)); - r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid); + r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid); if (r >= 0) { *machine_cid = cid; - *ret_child_sock = TAKE_FD(vfd); return 0; } if (errno != EADDRINUSE) @@ -429,10 +421,9 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi for (unsigned i = 0; i < 64; i++) { cid = 3 + random_u64_range(UINT_MAX - 4); - r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid); + r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid); if (r >= 0) { *machine_cid = cid; - *ret_child_sock = TAKE_FD(vfd); return 0; } diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 9c9b1867d3..e1ce780671 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -68,4 +68,4 @@ int list_ovmf_config(char ***ret); int load_ovmf_config(const char *path, OvmfConfig **ret); int find_ovmf_config(int search_sb, OvmfConfig **ret); int find_qemu_binary(char **ret_qemu_binary); -int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock); +int vsock_fix_child_cid(int vsock_fd, unsigned *machine_cid, const char *machine); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 37c354f0b0..d5ab2a1a27 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -6,6 +6,7 @@ #include #include +#include "sd-daemon.h" #include "sd-event.h" #include "sd-id128.h" @@ -654,12 +655,15 @@ static int kernel_cmdline_maybe_append_root(void) { return 0; } -static int run_virtual_machine(void) { +static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_close_ int vsock_fd = -EBADF; - _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL; + _cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL; + _cleanup_close_ int notify_sock_fd = -EBADF; _cleanup_strv_free_ char **cmdline = NULL; + _cleanup_free_ int *pass_fds = NULL; + size_t n_pass_fds = 0; + const char *machine, *accel; int r; if (arg_privileged) @@ -693,20 +697,17 @@ static int run_virtual_machine(void) { log_warning("Couldn't find OVMF firmware blob with Secure Boot support, " "falling back to OVMF firmware blobs without Secure Boot support."); - const char *accel = use_kvm ? "kvm" : "tcg"; - if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) - machine = strjoin("type=virt,accel=", accel); - else - machine = strjoin("type=q35,accel=", accel, ",smm=", on_off(ovmf_config->supports_sb)); - if (!machine) - return log_oom(); - r = find_qemu_binary(&qemu_binary); if (r == -EOPNOTSUPP) return log_error_errno(r, "Native architecture is not supported by qemu."); if (r < 0) return log_error_errno(r, "Failed to find QEMU binary: %m"); + if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) + machine = "type=virt"; + else + machine = ovmf_config->supports_sb ? "type=q35,smm=on" : "type=q35,smm=off"; + if (asprintf(&mem, "%" PRIu64, DIV_ROUND_UP(arg_qemu_mem, U64_MB)) < 0) return log_oom(); @@ -744,6 +745,37 @@ static int run_virtual_machine(void) { if (r < 0) return log_oom(); + if (!use_kvm && kvm_device_fd >= 0) { + log_warning("KVM is disabled but fd for /dev/kvm was passed, closing fd and ignoring"); + kvm_device_fd = safe_close(kvm_device_fd); + } + + if (use_kvm && kvm_device_fd >= 0) { + /* /dev/fdset/1 is magic string to tell qemu where to find the fd for /dev/kvm + * we use this so that we can take a fd to /dev/kvm and then give qemu that fd */ + accel = "kvm,device=/dev/fdset/1"; + + r = strv_extend(&cmdline, "--add-fd"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "fd=%d,set=1,opaque=/dev/kvm", kvm_device_fd); + if (r < 0) + return log_oom(); + + if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) + return log_oom(); + + pass_fds[n_pass_fds++] = kvm_device_fd; + } else if (use_kvm) + accel = "kvm"; + else + accel = "tcg"; + + r = strv_extend_many(&cmdline, "-accel", accel); + if (r < 0) + return log_oom(); + bool use_vsock = arg_qemu_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; if (arg_qemu_vsock < 0) { r = qemu_check_vsock_support(); @@ -753,12 +785,20 @@ static int run_virtual_machine(void) { use_vsock = r; } - unsigned child_cid = VMADDR_CID_ANY; _cleanup_close_ int child_vsock_fd = -EBADF; if (use_vsock) { - child_cid = arg_vsock_cid; + int device_fd = vhost_device_fd; + unsigned child_cid = arg_vsock_cid; - r = vsock_fix_child_cid(&child_cid, arg_machine, &child_vsock_fd); + if (device_fd < 0) { + child_vsock_fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); + if (child_vsock_fd < 0) + return log_error_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m"); + + device_fd = child_vsock_fd; + } + + r = vsock_fix_child_cid(device_fd, &child_cid, arg_machine); if (r < 0) return log_error_errno(r, "Failed to fix CID for the guest vsock socket: %m"); @@ -766,9 +806,14 @@ static int run_virtual_machine(void) { if (r < 0) return log_oom(); - r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, child_vsock_fd); + r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, device_fd); if (r < 0) return log_oom(); + + if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) + return log_oom(); + + pass_fds[n_pass_fds++] = device_fd; } r = strv_extend_many(&cmdline, "-cpu", "max"); @@ -972,11 +1017,11 @@ static int run_virtual_machine(void) { } if (use_vsock) { - vsock_fd = open_vsock(); - if (vsock_fd < 0) - return log_error_errno(vsock_fd, "Failed to open vsock: %m"); + notify_sock_fd = open_vsock(); + if (notify_sock_fd < 0) + return log_error_errno(notify_sock_fd, "Failed to open vsock: %m"); - r = cmdline_add_vsock(&cmdline, vsock_fd); + r = cmdline_add_vsock(&cmdline, notify_sock_fd); if (r == -ENOMEM) return log_oom(); if (r < 0) @@ -1027,7 +1072,7 @@ static int run_virtual_machine(void) { int exit_status = INT_MAX; if (use_vsock) { - r = setup_notify_parent(event, vsock_fd, &exit_status, ¬ify_event_source); + r = setup_notify_parent(event, notify_sock_fd, &exit_status, ¬ify_event_source); if (r < 0) return log_error_errno(r, "Failed to setup event loop to handle vsock notify events: %m"); } @@ -1092,7 +1137,8 @@ static int verify_arguments(void) { } static int run(int argc, char *argv[]) { - int r; + int r, kvm_device_fd = -EBADF, vhost_device_fd = -EBADF; + _cleanup_strv_free_ char **names = NULL; log_setup(); @@ -1120,9 +1166,25 @@ static int run(int argc, char *argv[]) { special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal()); } + r = sd_listen_fds_with_names(true, &names); + if (r < 0) + return log_error_errno(r, "Failed to get passed file descriptors: %m"); + + for (int i = 0; i < r; i++) { + int fd = SD_LISTEN_FDS_START + i; + if (streq(names[i], "kvm")) + kvm_device_fd = fd; + else if (streq(names[i], "vhost-vsock")) + vhost_device_fd = fd; + else { + log_notice("Couldn't recognize passed fd %d (%s), closing fd and ignoring...", fd, names[i]); + safe_close(fd); + } + } + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - return run_virtual_machine(); + return run_virtual_machine(kvm_device_fd, vhost_device_fd); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);