summaryrefslogtreecommitdiffstats
path: root/src/vmspawn/vmspawn-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vmspawn/vmspawn-util.c')
-rw-r--r--src/vmspawn/vmspawn-util.c369
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;
+}