diff options
Diffstat (limited to 'src/vmspawn/vmspawn-util.c')
-rw-r--r-- | src/vmspawn/vmspawn-util.c | 369 |
1 files changed, 272 insertions, 97 deletions
diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index b5b5eaf..472dd92 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -7,6 +7,7 @@ #include "architecture.h" #include "conf-files.h" #include "errno-util.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "json.h" @@ -20,19 +21,53 @@ #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" +static const char* const architecture_to_qemu_table[_ARCHITECTURE_MAX] = { + [ARCHITECTURE_ARM64] = "aarch64", /* differs from our name */ + [ARCHITECTURE_ARM] = "arm", + [ARCHITECTURE_ALPHA] = "alpha", + [ARCHITECTURE_X86_64] = "x86_64", /* differs from our name */ + [ARCHITECTURE_X86] = "i386", /* differs from our name */ + [ARCHITECTURE_LOONGARCH64] = "loongarch64", + [ARCHITECTURE_MIPS64_LE] = "mips", /* differs from our name */ + [ARCHITECTURE_MIPS_LE] = "mips", /* differs from our name */ + [ARCHITECTURE_PARISC] = "hppa", /* differs from our name */ + [ARCHITECTURE_PPC64_LE] = "ppc", /* differs from our name */ + [ARCHITECTURE_PPC64] = "ppc", /* differs from our name */ + [ARCHITECTURE_PPC] = "ppc", + [ARCHITECTURE_RISCV32] = "riscv32", + [ARCHITECTURE_RISCV64] = "riscv64", + [ARCHITECTURE_S390X] = "s390x", +}; + +static int native_arch_as_qemu(const char **ret) { + const char *s = architecture_to_qemu_table[native_architecture()]; + if (!s) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Architecture %s not supported by qemu", architecture_to_string(native_architecture())); + + if (ret) + *ret = s; + + return 0; +} + OvmfConfig* ovmf_config_free(OvmfConfig *config) { if (!config) return NULL; free(config->path); + free(config->format); free(config->vars); + free(config->vars_format); return mfree(config); } +DEFINE_STRING_TABLE_LOOKUP(network_stack, NetworkStack); + int qemu_check_kvm_support(void) { if (access("/dev/kvm", F_OK) >= 0) return true; @@ -40,7 +75,7 @@ int qemu_check_kvm_support(void) { log_debug_errno(errno, "/dev/kvm not found. Not using KVM acceleration."); return false; } - if (errno == EPERM) { + if (ERRNO_IS_PRIVILEGE(errno)) { log_debug_errno(errno, "Permission denied to access /dev/kvm. Not using KVM acceleration."); return false; } @@ -62,11 +97,11 @@ int qemu_check_vsock_support(void) { fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); if (fd >= 0) return true; - if (errno == ENODEV) { + if (ERRNO_IS_DEVICE_ABSENT(errno)) { log_debug_errno(errno, "/dev/vhost-vsock device doesn't exist. Not adding a vsock device to the virtual machine."); return false; } - if (errno == EPERM) { + if (ERRNO_IS_PRIVILEGE(errno)) { log_debug_errno(errno, "Permission denied to access /dev/vhost-vsock. Not adding a vsock device to the virtual machine."); return false; } @@ -78,16 +113,28 @@ int qemu_check_vsock_support(void) { typedef struct FirmwareData { char **features; char *firmware; + char *firmware_format; char *vars; + char *vars_format; + char **architectures; } FirmwareData; +static bool firmware_data_supports_sb(const FirmwareData *fwd) { + assert(fwd); + + return strv_contains(fwd->features, "secure-boot"); +} + static FirmwareData* firmware_data_free(FirmwareData *fwd) { if (!fwd) return NULL; - fwd->features = strv_free(fwd->features); - fwd->firmware = mfree(fwd->firmware); - fwd->vars = mfree(fwd->vars); + strv_free(fwd->features); + free(fwd->firmware); + free(fwd->firmware_format); + free(fwd->vars); + free(fwd->vars_format); + strv_free(fwd->architectures); return mfree(fwd); } @@ -95,22 +142,22 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FirmwareData*, firmware_data_free); static int firmware_executable(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch table[] = { - { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY }, - { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY }, + { "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware_format), JSON_MANDATORY }, {} }; - return json_dispatch(v, table, 0, userdata); + return json_dispatch(v, table, flags, userdata); } static int firmware_nvram_template(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch table[] = { - { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY }, - { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY }, + { "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars_format), JSON_MANDATORY }, {} }; - return json_dispatch(v, table, 0, userdata); + return json_dispatch(v, table, flags, userdata); } static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { @@ -121,15 +168,170 @@ static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags {} }; - return json_dispatch(v, table, 0, userdata); + return json_dispatch(v, table, flags, userdata); +} + +static int target_architecture(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { + int r; + JsonVariant *e; + char ***supported_architectures = ASSERT_PTR(userdata); + + static const JsonDispatch table[] = { + { "architecture", JSON_VARIANT_STRING, json_dispatch_string, 0, JSON_MANDATORY }, + { "machines", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + {} + }; + + JSON_VARIANT_ARRAY_FOREACH(e, v) { + _cleanup_free_ char *arch = NULL; + + r = json_dispatch(e, table, flags, &arch); + if (r < 0) + return r; + + r = strv_consume(supported_architectures, TAKE_PTR(arch)); + if (r < 0) + return r; + } + + return 0; +} + +static int get_firmware_search_dirs(char ***ret) { + int r; + + assert(ret); + + /* Search in: + * - $XDG_CONFIG_HOME/qemu/firmware + * - /etc/qemu/firmware + * - /usr/share/qemu/firmware + * + * Prioritising entries in "more specific" directories */ + + _cleanup_free_ char *user_firmware_dir = NULL; + r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware"); + if (r < 0) + return r; + + _cleanup_strv_free_ char **l = NULL; + l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware"); + if (!l) + return log_oom_debug(); + + *ret = TAKE_PTR(l); + return 0; +} + +int list_ovmf_config(char ***ret) { + _cleanup_strv_free_ char **search_dirs = NULL; + int r; + + assert(ret); + + r = get_firmware_search_dirs(&search_dirs); + if (r < 0) + return r; + + r = conf_files_list_strv( + ret, + ".json", + /* root= */ NULL, + CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR, + (const char *const*) search_dirs); + if (r < 0) + return log_debug_errno(r, "Failed to list firmware files: %m"); + + return 0; +} + +static int load_firmware_data(const char *path, FirmwareData **ret) { + int r; + + assert(path); + assert(ret); + + _cleanup_(json_variant_unrefp) JsonVariant *json = NULL; + r = json_parse_file( + /* f= */ NULL, + path, + /* flags= */ 0, + &json, + /* ret_line= */ NULL, + /* ret_column= */ NULL); + if (r < 0) + return r; + + static const JsonDispatch table[] = { + { "description", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "interface-types", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + { "mapping", JSON_VARIANT_OBJECT, firmware_mapping, 0, JSON_MANDATORY }, + { "targets", JSON_VARIANT_ARRAY, target_architecture, offsetof(FirmwareData, architectures), JSON_MANDATORY }, + { "features", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY }, + { "tags", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + {} + }; + + _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + fwd = new0(FirmwareData, 1); + if (!fwd) + return -ENOMEM; + + r = json_dispatch(json, table, JSON_ALLOW_EXTENSIONS, fwd); + if (r < 0) + return r; + + *ret = TAKE_PTR(fwd); + return 0; +} + +static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) { + assert(fwd); + assert(ret); + + _cleanup_free_ OvmfConfig *config = NULL; + config = new(OvmfConfig, 1); + if (!config) + return -ENOMEM; + + *config = (OvmfConfig) { + .path = TAKE_PTR(fwd->firmware), + .format = TAKE_PTR(fwd->firmware_format), + .vars = TAKE_PTR(fwd->vars), + .vars_format = TAKE_PTR(fwd->vars_format), + .supports_sb = firmware_data_supports_sb(fwd), + }; + + *ret = TAKE_PTR(config); + return 0; +} + +int load_ovmf_config(const char *path, OvmfConfig **ret) { + _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + int r; + + assert(path); + assert(ret); + + r = load_firmware_data(path, &fwd); + if (r < 0) + return r; + + return ovmf_config_make(fwd, ret); } int find_ovmf_config(int search_sb, OvmfConfig **ret) { _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL; - _cleanup_free_ char *user_firmware_dir = NULL; _cleanup_strv_free_ char **conf_files = NULL; + const char* native_arch_qemu; int r; + assert(ret); + + r = native_arch_as_qemu(&native_arch_qemu); + if (r < 0) + return r; + /* Search in: * - $XDG_CONFIG_HOME/qemu/firmware * - /etc/qemu/firmware @@ -138,74 +340,40 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) { * Prioritising entries in "more specific" directories */ - r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware"); + r = list_ovmf_config(&conf_files); if (r < 0) return r; - r = conf_files_list_strv(&conf_files, ".json", NULL, CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR, - STRV_MAKE_CONST(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware")); - if (r < 0) - return log_debug_errno(r, "Failed to list config files: %m"); - STRV_FOREACH(file, conf_files) { _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; - _cleanup_(json_variant_unrefp) JsonVariant *config_json = NULL; - _cleanup_free_ char *contents = NULL; - size_t contents_sz = 0; - r = read_full_file(*file, &contents, &contents_sz); - if (r == -ENOMEM) - return r; + r = load_firmware_data(*file, &fwd); if (r < 0) { - log_debug_errno(r, "Failed to read contents of %s - ignoring: %m", *file); + log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file); continue; } - r = json_parse(contents, 0, &config_json, NULL, NULL); - if (r == -ENOMEM) - return r; - if (r < 0) { - log_debug_errno(r, "Failed to parse the JSON in %s - ignoring: %m", *file); + if (strv_contains(fwd->features, "enrolled-keys")) { + log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file); continue; } - static const JsonDispatch table[] = { - { "description", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, - { "interface-types", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, - { "mapping", JSON_VARIANT_OBJECT, firmware_mapping, 0, JSON_MANDATORY }, - { "targets", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, - { "features", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY }, - { "tags", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, - {} - }; - - fwd = new0(FirmwareData, 1); - if (!fwd) - return -ENOMEM; - - r = json_dispatch(config_json, table, 0, fwd); - if (r == -ENOMEM) - return r; - if (r < 0) { - log_debug_errno(r, "Failed to extract the required fields from the JSON in %s - ignoring: %m", *file); + if (!strv_contains(fwd->architectures, native_arch_qemu)) { + log_debug("Skipping %s, firmware doesn't support the native architecture.", *file); continue; } - int sb_present = !!strv_find(fwd->features, "secure-boot"); - /* exclude firmware which doesn't match our Secure Boot requirements */ - if (search_sb >= 0 && search_sb != sb_present) { - log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration", *file); + if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) { + log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file); continue; } - config = new0(OvmfConfig, 1); - if (!config) - return -ENOMEM; + r = ovmf_config_make(fwd, &config); + if (r < 0) + return r; - config->path = TAKE_PTR(fwd->firmware); - config->vars = TAKE_PTR(fwd->vars); - config->supports_sb = sb_present; + log_debug("Selected firmware definition %s.", *file); break; } @@ -219,6 +387,7 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) { } int find_qemu_binary(char **ret_qemu_binary) { + const char *native_arch_qemu; int r; /* @@ -228,24 +397,6 @@ int find_qemu_binary(char **ret_qemu_binary) { * If the native architecture is not supported by qemu -EOPNOTSUPP will be returned; */ - static const char *architecture_to_qemu_table[_ARCHITECTURE_MAX] = { - [ARCHITECTURE_ARM64] = "aarch64", /* differs from our name */ - [ARCHITECTURE_ARM] = "arm", - [ARCHITECTURE_ALPHA] = "alpha", - [ARCHITECTURE_X86_64] = "x86_64", /* differs from our name */ - [ARCHITECTURE_X86] = "i386", /* differs from our name */ - [ARCHITECTURE_LOONGARCH64] = "loongarch64", - [ARCHITECTURE_MIPS64_LE] = "mips", /* differs from our name */ - [ARCHITECTURE_MIPS_LE] = "mips", /* differs from our name */ - [ARCHITECTURE_PARISC] = "hppa", /* differs from our name */ - [ARCHITECTURE_PPC64_LE] = "ppc", /* differs from our name */ - [ARCHITECTURE_PPC64] = "ppc", /* differs from our name */ - [ARCHITECTURE_PPC] = "ppc", - [ARCHITECTURE_RISCV32] = "riscv32", - [ARCHITECTURE_RISCV64] = "riscv64", - [ARCHITECTURE_S390X] = "s390x", - }; - FOREACH_STRING(s, "qemu", "qemu-kvm") { r = find_executable(s, ret_qemu_binary); if (r == 0) @@ -255,19 +406,19 @@ int find_qemu_binary(char **ret_qemu_binary) { return r; } - const char *arch_qemu = architecture_to_qemu_table[native_architecture()]; - if (!arch_qemu) - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Architecture %s not supported by qemu", architecture_to_string(native_architecture())); + r = native_arch_as_qemu(&native_arch_qemu); + if (r < 0) + return r; _cleanup_free_ char *qemu_arch_specific = NULL; - qemu_arch_specific = strjoin("qemu-system-", arch_qemu); + qemu_arch_specific = strjoin("qemu-system-", native_arch_qemu); if (!qemu_arch_specific) return -ENOMEM; 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, @@ -276,14 +427,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 * @@ -296,16 +446,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; } @@ -317,10 +461,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) @@ -329,10 +472,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; } @@ -342,3 +484,36 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Failed to assign a CID to the guest vsock"); } + +char* escape_qemu_value(const char *s) { + const char *f; + char *e, *t; + size_t n; + + assert(s); + + /* QEMU requires that commas in arguments to be escaped by doubling up the commas. See + * https://www.qemu.org/docs/master/system/qemu-manpage.html#options for more information. + * + * This function performs this escaping, returning an allocated string with the escaped value, or + * NULL if allocation failed. */ + + n = strlen(s); + + if (n > (SIZE_MAX - 1) / 2) + return NULL; + + e = new(char, n*2 + 1); + if (!e) + return NULL; + + for (f = s, t = e; f < s + n; f++) { + *t++ = *f; + if (*f == ',') + *t++ = ','; + } + + *t = 0; + + return e; +} |