From 93de2a6a4b91b72607136dd656edf03fb399d27f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 23 Sep 2026 09:37:21 -0700 Subject: KVM: SEV: Do cache maintenance on the source VM during intra-host migration Manually perform cache maintenance on the source VM during intra-host migration to ensure no stale data is left in CPU caches after the VM is destroyed. Because the source VM is "converted" to a non-SEV VM, KVM's memory reclaim flows won't trigger cache maintenance, e.g. when all guest memory is reclaimed in response to detaching from the mmu_notifier. Note, relying on the destination VM to do cache maintenance isn't an option as KVM doesn't require identical guest memory configurations, i.e. the source VM may have access to memory that the destination VM does not. Enforcing equivalent memory configurations is infeasible, as it would require a *deep* comparison of memslots, e.g. to verify that not only are the memslot identical, but what the memslots point at is also identical. Fixes: b56639318bb2 ("KVM: SEV: Add support for SEV intra host migration") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Signed-off-by: Sean Christopherson Message-ID: <20260923163721.1584779-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- tools/bootconfig/main.c | 593 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 593 insertions(+) create mode 100644 tools/bootconfig/main.c (limited to 'tools/bootconfig/main.c') diff --git a/tools/bootconfig/main.c b/tools/bootconfig/main.c new file mode 100644 index 000000000..17d971d47 --- /dev/null +++ b/tools/bootconfig/main.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Boot config tool for initrd image + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) + +/* Bootconfig footer is [size][csum][BOOTCONFIG_MAGIC]. */ +#define BOOTCONFIG_FOOTER_SIZE \ + (sizeof(uint32_t) * 2 + BOOTCONFIG_MAGIC_LEN) + +static int xbc_show_value(struct xbc_node *node, bool semicolon) +{ + const char *val, *eol; + char q; + int i = 0; + + eol = semicolon ? ";\n" : "\n"; + xbc_array_for_each_value(node, val) { + if (strchr(val, '"')) + q = '\''; + else + q = '"'; + printf("%c%s%c%s", q, val, q, xbc_node_is_array(node) ? ", " : eol); + i++; + } + return i; +} + +static void xbc_show_compact_tree(void) +{ + struct xbc_node *node, *cnode = NULL, *vnode; + int depth = 0, i; + + node = xbc_root_node(); + while (node && xbc_node_is_key(node)) { + for (i = 0; i < depth; i++) + printf("\t"); + if (!cnode) + cnode = xbc_node_get_child(node); + while (cnode && xbc_node_is_key(cnode) && !cnode->next) { + vnode = xbc_node_get_child(cnode); + /* + * If @cnode has value and subkeys, this + * should show it as below. + * + * key(@node) { + * key(@cnode) = value; + * key(@cnode) { + * subkeys; + * } + * } + */ + if (vnode && xbc_node_is_value(vnode) && vnode->next) + break; + printf("%s.", xbc_node_get_data(node)); + node = cnode; + cnode = vnode; + } + if (cnode && xbc_node_is_key(cnode)) { + printf("%s {\n", xbc_node_get_data(node)); + depth++; + node = cnode; + cnode = NULL; + continue; + } else if (cnode && xbc_node_is_value(cnode)) { + printf("%s = ", xbc_node_get_data(node)); + xbc_show_value(cnode, true); + /* + * If @node has value and subkeys, continue + * looping on subkeys with same node. + */ + if (cnode->next) { + cnode = xbc_node_get_next(cnode); + continue; + } + } else { + printf("%s;\n", xbc_node_get_data(node)); + } + cnode = NULL; + + if (node->next) { + node = xbc_node_get_next(node); + continue; + } + while (!node->next) { + node = xbc_node_get_parent(node); + if (!node) + return; + if (!xbc_node_get_child(node)->next) + continue; + if (depth) { + depth--; + for (i = 0; i < depth; i++) + printf("\t"); + printf("}\n"); + } + } + node = xbc_node_get_next(node); + } +} + +static void xbc_show_list(void) +{ + char key[XBC_KEYLEN_MAX]; + struct xbc_node *leaf; + const char *val; + int ret; + + xbc_for_each_key_value(leaf, val) { + ret = xbc_node_compose_key(leaf, key, XBC_KEYLEN_MAX); + if (ret < 0) { + fprintf(stderr, "Failed to compose key %d\n", ret); + break; + } + printf("%s = ", key); + if (!val || val[0] == '\0') { + printf("\"\"\n"); + continue; + } + xbc_show_value(xbc_node_get_child(leaf), false); + } +} + +#define PAGE_SIZE 4096 + +static int load_xbc_fd(int fd, char **buf, int size) +{ + int ret; + + if (size < 0 || size > XBC_DATA_MAX) + return -EINVAL; + + *buf = malloc(size + 1); + if (!*buf) + return -ENOMEM; + + ret = read(fd, *buf, size); + if (ret < 0) + return -errno; + (*buf)[size] = '\0'; + + return ret; +} + +/* Return the read size or -errno */ +static int load_xbc_file(const char *path, char **buf) +{ + struct stat stat; + int fd, ret; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + ret = fstat(fd, &stat); + if (ret < 0) { + ret = -errno; + close(fd); + return ret; + } + + if (stat.st_size > XBC_DATA_MAX) { + pr_err("%s size is too big\n", path); + ret = -E2BIG; + close(fd); + return ret; + } + + ret = load_xbc_fd(fd, buf, stat.st_size); + + close(fd); + + return ret; +} + +static int pr_errno(const char *msg, int err) +{ + pr_err("%s: %d\n", msg, err); + return err; +} + +static int load_xbc_from_initrd(int fd, char **buf) +{ + struct stat stat; + int ret; + uint32_t size = 0, csum = 0, rcsum; + char magic[BOOTCONFIG_MAGIC_LEN]; + const char *msg; + + ret = fstat(fd, &stat); + if (ret < 0) + return -errno; + + if (stat.st_size < BOOTCONFIG_FOOTER_SIZE) + return 0; + + if (lseek(fd, -(off_t)BOOTCONFIG_MAGIC_LEN, SEEK_END) < 0) + return pr_errno("Failed to lseek for magic", -errno); + + if (read(fd, magic, BOOTCONFIG_MAGIC_LEN) < 0) + return pr_errno("Failed to read", -errno); + + /* Check the bootconfig magic bytes */ + if (memcmp(magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN) != 0) + return 0; + + if (lseek(fd, -(off_t)BOOTCONFIG_FOOTER_SIZE, SEEK_END) < 0) + return pr_errno("Failed to lseek for size", -errno); + + if (read(fd, &size, sizeof(uint32_t)) < 0) + return pr_errno("Failed to read size", -errno); + size = le32toh(size); + + if (read(fd, &csum, sizeof(uint32_t)) < 0) + return pr_errno("Failed to read checksum", -errno); + csum = le32toh(csum); + + /* Wrong size error */ + if (size > XBC_DATA_MAX || + size > stat.st_size - BOOTCONFIG_FOOTER_SIZE) { + pr_err("bootconfig size is too big\n"); + return -E2BIG; + } + + if (lseek(fd, stat.st_size - (size + BOOTCONFIG_FOOTER_SIZE), + SEEK_SET) < 0) + return pr_errno("Failed to lseek", -errno); + + ret = load_xbc_fd(fd, buf, size); + if (ret < 0) + return ret; + + /* Wrong Checksum */ + rcsum = xbc_calc_checksum(*buf, size); + if (csum != rcsum) { + pr_err("checksum error: %u != %u\n", csum, rcsum); + return -EINVAL; + } + + ret = xbc_init(*buf, size, &msg, NULL); + /* Wrong data */ + if (ret < 0) { + pr_err("parse error: %s.\n", msg); + return ret; + } + + return size; +} + +static void show_xbc_error(const char *data, const char *msg, int pos) +{ + int lin = 1, col, i; + + if (pos < 0) { + pr_err("Error: %s.\n", msg); + return; + } + + /* Note that pos starts from 0 but lin and col should start from 1. */ + col = pos + 1; + for (i = 0; i < pos; i++) { + if (data[i] == '\n') { + lin++; + col = pos - i; + } + } + pr_err("Parse Error: %s at %d:%d\n", msg, lin, col); + +} + +static int init_xbc_with_error(char *buf, int len) +{ + char *copy = strdup(buf); + const char *msg; + int ret, pos; + + if (!copy) + return -ENOMEM; + + ret = xbc_init(buf, len, &msg, &pos); + if (ret < 0) + show_xbc_error(copy, msg, pos); + free(copy); + + return ret; +} + +static int show_xbc_kernel_cmdline(void) +{ + struct xbc_node *root; + char *buf = NULL; + int len, ret; + + root = xbc_find_node("kernel"); + if (!root) + return 0; /* no kernel.* keys: emit empty output */ + + len = xbc_snprint_cmdline(NULL, 0, root); + if (len < 0) { + pr_err("Failed to size cmdline output: %d\n", len); + return len; + } + if (len == 0) + return 0; + + buf = malloc(len + 1); + if (!buf) + return -ENOMEM; + + ret = xbc_snprint_cmdline(buf, len + 1, root); + if (ret < 0) { + pr_err("Failed to render cmdline output: %d\n", ret); + free(buf); + return ret; + } + + fputs(buf, stdout); + free(buf); + return 0; +} + +static int show_xbc(const char *path, bool list, bool render_cmdline) +{ + int ret, fd; + char *buf = NULL; + struct stat st; + + ret = stat(path, &st); + if (ret < 0) { + ret = -errno; + pr_err("Failed to stat %s: %d\n", path, ret); + return ret; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + pr_err("Failed to open initrd %s: %d\n", path, ret); + return ret; + } + + ret = load_xbc_from_initrd(fd, &buf); + close(fd); + if (ret < 0) { + pr_err("Failed to load a boot config from initrd: %d\n", ret); + goto out; + } + /* Assume a bootconfig file if it is enough small */ + if (ret == 0 && st.st_size <= XBC_DATA_MAX) { + ret = load_xbc_file(path, &buf); + if (ret < 0) { + pr_err("Failed to load a boot config: %d\n", ret); + goto out; + } + if (init_xbc_with_error(buf, ret) < 0) + goto out; + } + if (render_cmdline) + ret = show_xbc_kernel_cmdline(); + else if (list) + xbc_show_list(); + else + xbc_show_compact_tree(); + if (ret > 0) + ret = 0; +out: + free(buf); + + return ret; +} + +static int delete_xbc(const char *path) +{ + struct stat stat; + int ret = 0, fd, size; + char *buf = NULL; + + fd = open(path, O_RDWR); + if (fd < 0) { + ret = -errno; + pr_err("Failed to open initrd %s: %d\n", path, ret); + return ret; + } + + size = load_xbc_from_initrd(fd, &buf); + if (size < 0) { + ret = size; + pr_err("Failed to load a boot config from initrd: %d\n", ret); + } else if (size > 0) { + ret = fstat(fd, &stat); + if (!ret) + ret = ftruncate(fd, stat.st_size + - size - BOOTCONFIG_FOOTER_SIZE); + if (ret) + ret = -errno; + } /* Ignore if there is no boot config in initrd */ + + close(fd); + free(buf); + + return ret; +} + +static int apply_xbc(const char *path, const char *xbc_path) +{ + struct { + uint32_t size; + uint32_t csum; + char magic[BOOTCONFIG_MAGIC_LEN]; + } footer; + char *buf, *data; + size_t total_size; + struct stat stat; + const char *msg; + uint32_t size, csum; + int pos, pad; + int ret, fd; + + ret = load_xbc_file(xbc_path, &buf); + if (ret < 0) { + pr_err("Failed to load %s : %d\n", xbc_path, ret); + return ret; + } + size = strlen(buf) + 1; + csum = xbc_calc_checksum(buf, size); + + /* Backup the bootconfig data */ + data = calloc(size + BOOTCONFIG_ALIGN + BOOTCONFIG_FOOTER_SIZE, 1); + if (!data) { + free(buf); + return -ENOMEM; + } + memcpy(data, buf, size); + + /* Check the data format */ + ret = xbc_init(buf, size, &msg, &pos); + if (ret < 0) { + show_xbc_error(data, msg, pos); + free(data); + free(buf); + + return ret; + } + printf("Apply %s to %s\n", xbc_path, path); + xbc_get_info(&ret, NULL); + printf("\tNumber of nodes: %d\n", ret); + printf("\tSize: %u bytes\n", (unsigned int)size); + printf("\tChecksum: %u\n", (unsigned int)csum); + + /* TODO: Check the options by schema */ + xbc_exit(); + free(buf); + + /* Remove old boot config if exists */ + ret = delete_xbc(path); + if (ret < 0) { + pr_err("Failed to delete previous boot config: %d\n", ret); + free(data); + return ret; + } + + /* Apply new one */ + fd = open(path, O_RDWR | O_APPEND); + if (fd < 0) { + ret = -errno; + pr_err("Failed to open %s: %d\n", path, ret); + free(data); + return ret; + } + /* TODO: Ensure the @path is initramfs/initrd image */ + if (fstat(fd, &stat) < 0) { + ret = -errno; + pr_err("Failed to get the size of %s\n", path); + goto out; + } + + /* To align up the total size to BOOTCONFIG_ALIGN, get padding size */ + total_size = stat.st_size + size + BOOTCONFIG_FOOTER_SIZE; + pad = ((total_size + BOOTCONFIG_ALIGN - 1) & (~BOOTCONFIG_ALIGN_MASK)) - total_size; + size += pad; + + /* Add a footer */ + footer.size = htole32(size); + footer.csum = htole32(csum); + memcpy(footer.magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN); + static_assert(sizeof(footer) == BOOTCONFIG_FOOTER_SIZE); + memcpy(data + size, &footer, BOOTCONFIG_FOOTER_SIZE); + + total_size = size + BOOTCONFIG_FOOTER_SIZE; + + ret = write(fd, data, total_size); + if (ret < total_size) { + if (ret < 0) + ret = -errno; + pr_err("Failed to apply a boot config: %d\n", ret); + if (ret >= 0) + goto out_rollback; + } else + ret = 0; + +out: + close(fd); + free(data); + + return ret; + +out_rollback: + /* Map the partial write to -ENOSPC */ + if (ret >= 0) + ret = -ENOSPC; + if (ftruncate(fd, stat.st_size) < 0) { + ret = -errno; + pr_err("Failed to rollback the write error: %d\n", ret); + pr_err("The initrd %s may be corrupted. Recommend to rebuild.\n", path); + } + goto out; +} + +static int usage(void) +{ + printf("Usage: bootconfig [OPTIONS] \n" + "Or bootconfig \n" + " Apply, delete or show boot config to initrd.\n" + " Options:\n" + " -a : Apply boot config to initrd\n" + " -d : Delete boot config file from initrd\n" + " -l : list boot config in initrd or file\n" + " -C : render the kernel.* subtree as a flat cmdline\n" + " string (suitable for embedding in a kernel image)\n" + " and print it to stdout\n\n" + " If no option is given, show the bootconfig in the given file.\n"); + return -1; +} + +int main(int argc, char **argv) +{ + char *path = NULL; + char *apply = NULL; + bool render_cmdline = false; + bool delete = false, list = false; + int opt; + + while ((opt = getopt(argc, argv, "hda:lC")) != -1) { + switch (opt) { + case 'd': + delete = true; + break; + case 'a': + apply = optarg; + break; + case 'l': + list = true; + break; + case 'C': + render_cmdline = true; + break; + case 'h': + default: + return usage(); + } + } + + if ((!!apply + !!delete + !!list + !!render_cmdline) > 1) { + pr_err("Error: You can give one of -a, -d, -l or -C at once.\n"); + return usage(); + } + + if (optind >= argc) { + pr_err("Error: No initrd is specified.\n"); + return usage(); + } + + path = argv[optind]; + + if (apply) + return apply_xbc(path, apply); + else if (delete) + return delete_xbc(path); + + return show_xbc(path, list, render_cmdline); +} -- cgit v1.3.1