#include <xen/xen.h>
#include "errno-util.h"
-#include "fd-util.h"
#endif
#include "alloc-util.h"
+#include "compress.h"
+#include "copy.h"
+#include "fd-util.h"
#include "fileio.h"
+#include "io-util.h"
#include "log.h"
+#include "memfd-util.h"
+#include "pe-binary.h"
#include "proc-cmdline.h"
#include "reboot-util.h"
+#include "sparse-endian.h"
+#include "stat-util.h"
#include "string-util.h"
#include "umask-util.h"
#include "utf8.h"
#include "virt.h"
+/* ZBOOT header layout — see linux/drivers/firmware/efi/libstub/zboot-header.S */
+struct zboot_header {
+ le16_t mz_magic; /* 0x00: "MZ" DOS signature */
+ le16_t _pad0;
+ uint8_t zimg_magic[4]; /* 0x04: "zimg" identifier */
+ le32_t payload_offset; /* 0x08: offset to compressed payload */
+ le32_t payload_size; /* 0x0C: size of compressed payload */
+ uint8_t _pad1[8];
+ char comp_type[6]; /* 0x18: NUL-terminated compression type (e.g. "gzip", "zstd") */
+ uint8_t _pad2[2];
+} _packed_;
+assert_cc(sizeof(struct zboot_header) == 0x20);
+assert_cc(offsetof(struct zboot_header, comp_type) == 0x18);
+
int raw_reboot(int cmd, const void *arg) {
return syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, arg);
}
return 0;
}
+static int decompress_to_memfd(Compression compression, int fd) {
+ int r;
+
+ _cleanup_close_ int memfd = memfd_new("kexec-kernel");
+ if (memfd < 0)
+ return log_error_errno(memfd, "Failed to create memfd: %m");
+
+ r = decompress_stream(compression, fd, memfd, UINT64_MAX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decompress kernel: %m");
+
+ if (lseek(memfd, 0, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek memfd: %m");
+
+ return TAKE_FD(memfd);
+}
+
+static int decompress_zboot_to_memfd(int fd, uint32_t payload_offset, uint32_t payload_size, const char *comp_type) {
+ int r;
+
+ Compression c = compression_from_string(comp_type);
+ if (c < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Unsupported ZBOOT compression type '%s'.", comp_type);
+
+ struct stat st;
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat ZBOOT image: %m");
+
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return log_error_errno(r, "Kernel image is not a regular file: %m");
+
+ if (payload_offset < 0x20 ||
+ payload_size == 0 ||
+ payload_offset > (uint64_t) st.st_size ||
+ payload_size > (uint64_t) st.st_size - payload_offset)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "ZBOOT payload offset/size invalid.");
+
+ if (payload_size > 256 * 1024 * 1024) /* generous for any compressed kernel */
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "ZBOOT payload unreasonably large.");
+
+ _cleanup_free_ void *payload = malloc(payload_size);
+ if (!payload)
+ return log_oom();
+
+ ssize_t n = pread(fd, payload, payload_size, payload_offset);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to read ZBOOT payload: %m");
+ if ((uint32_t) n < payload_size)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read of ZBOOT payload.");
+
+ _cleanup_free_ void *decompressed = NULL;
+ size_t decompressed_size;
+ r = decompress_blob(c, payload, payload_size, &decompressed, &decompressed_size, /* dst_max= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decompress ZBOOT payload: %m");
+
+ payload = mfree(payload);
+
+ _cleanup_close_ int memfd = memfd_new("kexec-kernel");
+ if (memfd < 0)
+ return log_error_errno(memfd, "Failed to create memfd: %m");
+
+ r = loop_write(memfd, decompressed, decompressed_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write decompressed kernel to memfd: %m");
+
+ if (lseek(memfd, 0, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek memfd: %m");
+
+ return TAKE_FD(memfd);
+}
+
+static int pe_section_to_memfd(int fd, const IMAGE_SECTION_HEADER *section, const char *name) {
+ int r;
+
+ assert(fd >= 0);
+ assert(section);
+
+ uint32_t offset = le32toh(section->PointerToRawData),
+ size = MIN(le32toh(section->VirtualSize), le32toh(section->SizeOfRawData));
+
+ _cleanup_close_ int memfd = memfd_new(name);
+ if (memfd < 0)
+ return log_error_errno(memfd, "Failed to create memfd for PE section '%s': %m", name);
+
+ if (lseek(fd, offset, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek to PE section '%s': %m", name);
+
+ r = copy_bytes(fd, memfd, size, /* copy_flags= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy PE section '%s': %m", name);
+
+ if (lseek(memfd, 0, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek memfd: %m");
+
+ return TAKE_FD(memfd);
+}
+
+static int extract_uki(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd) {
+ int r;
+
+ assert(fd >= 0);
+ assert(ret_kernel_fd);
+
+ _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
+ _cleanup_free_ PeHeader *pe_header = NULL;
+ r = pe_load_headers(fd, &dos_header, &pe_header);
+ if (r < 0)
+ return log_debug_errno(r, "Not a valid PE file '%s': %m", path);
+
+ _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
+ r = pe_load_sections(fd, dos_header, pe_header, §ions);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to load PE sections from '%s': %m", path);
+
+ if (!pe_is_uki(pe_header, sections))
+ return 0; /* Not a UKI */
+
+ /* FIXME: we currently only extract .linux and .initrd, but sd-stub does a lot more:
+ * profiles, .cmdline, .dtb/.dtbauto, .ucode, .pcrsig/.pcrpkey, sidecar addons,
+ * credentials, sysexts/confexts, and TPM PCR measurements. */
+
+ const IMAGE_SECTION_HEADER *linux_section = pe_header_find_section(pe_header, sections, ".linux");
+ if (!linux_section)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "UKI '%s' has no .linux section.", path);
+
+ log_debug("Detected UKI image '%s', extracting .linux section.", path);
+
+ _cleanup_close_ int kernel_memfd = pe_section_to_memfd(fd, linux_section, "kexec-uki-kernel");
+ if (kernel_memfd < 0)
+ return kernel_memfd;
+
+ _cleanup_close_ int initrd_memfd = -EBADF;
+ if (ret_initrd_fd) {
+ const IMAGE_SECTION_HEADER *initrd_section = pe_header_find_section(pe_header, sections, ".initrd");
+ if (initrd_section) {
+ log_debug("Extracting .initrd section from UKI '%s'.", path);
+
+ initrd_memfd = pe_section_to_memfd(fd, initrd_section, "kexec-uki-initrd");
+ if (initrd_memfd < 0)
+ return initrd_memfd;
+ }
+ }
+
+ *ret_kernel_fd = TAKE_FD(kernel_memfd);
+ if (ret_initrd_fd)
+ *ret_initrd_fd = TAKE_FD(initrd_memfd);
+
+ return 1;
+}
+
+int kexec_maybe_decompress_kernel(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd) {
+ uint8_t magic[8];
+ ssize_t n;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret_kernel_fd);
+
+ n = pread(fd, magic, sizeof(magic), 0);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to read kernel magic from '%s': %m", path);
+ if ((size_t) n < sizeof(magic))
+ /* Too small to detect, pass through as-is */
+ return 0;
+
+ if (magic[0] == 'M' && magic[1] == 'Z') {
+
+ if (magic[4] == 'z' && magic[5] == 'i' && magic[6] == 'm' && magic[7] == 'g') {
+ struct zboot_header h;
+
+ n = pread(fd, &h, sizeof(h), 0);
+ if (n < 0)
+ return log_error_errno(errno, "Failed to read ZBOOT header from '%s': %m", path);
+ if ((size_t) n < sizeof(h))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+ "Short read of ZBOOT header from '%s'.", path);
+
+ char comp_type[sizeof(h.comp_type) + 1];
+ memcpy(comp_type, h.comp_type, sizeof(h.comp_type));
+ comp_type[sizeof(h.comp_type)] = '\0';
+
+ uint32_t payload_offset = le32toh(h.payload_offset),
+ payload_size = le32toh(h.payload_size);
+
+ log_debug("Detected ZBOOT image '%s' (compression=%s, offset=%"PRIu32", size=%"PRIu32")",
+ path, comp_type, payload_offset, payload_size);
+
+ r = decompress_zboot_to_memfd(fd, payload_offset, payload_size, comp_type);
+ if (r < 0)
+ return r;
+
+ *ret_kernel_fd = r;
+ return 1;
+ }
+
+ /* MZ but not ZBOOT — check if it's a UKI */
+ return extract_uki(path, fd, ret_kernel_fd, ret_initrd_fd);
+ }
+
+ Compression c = compression_detect_from_magic(magic);
+ if (c < 0)
+ /* Not a recognized compressed format, pass through as-is */
+ return 0;
+
+ log_debug("Detected %s-compressed kernel '%s', decompressing.", compression_to_string(c), path);
+
+ /* Seek back to start before decompression */
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek kernel fd: %m");
+
+ r = decompress_to_memfd(c, fd);
+ if (r < 0)
+ return r;
+
+ *ret_kernel_fd = r;
+ return 1;
+}
+
int create_shutdown_run_nologin_or_warn(void) {
int r;
bool kexec_loaded(void);
int kexec(void);
+int kexec_maybe_decompress_kernel(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd);
+
int create_shutdown_run_nologin_or_warn(void);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <sys/kexec.h>
#include <unistd.h>
#include "sd-bus.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "efivars.h"
+#include "fd-util.h"
#include "log.h"
#include "parse-util.h"
#include "path-util.h"
#include "systemctl-util.h"
static int load_kexec_kernel(void) {
- _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
- _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL;
- const BootEntry *e;
int r;
if (kexec_loaded()) {
return 0;
}
- if (access(KEXEC, X_OK) < 0)
- return log_error_errno(errno, KEXEC" is not available: %m");
-
+ _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
r = boot_config_load_auto(&config, NULL, NULL);
if (r == -ENOKEY)
/* The call doesn't log about ENOKEY, let's do so here. */
if (r < 0)
return r;
- e = boot_config_default_entry(&config);
+ const BootEntry *e = boot_config_default_entry(&config);
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No boot loader entry suitable as default, refusing to guess.");
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Boot entry specifies multiple initrds, which is not supported currently.");
+ _cleanup_free_ char *kernel = NULL;
kernel = path_join(e->root, e->kernel);
if (!kernel)
return log_oom();
+ _cleanup_free_ char *initrd = NULL;
if (!strv_isempty(e->initrd)) {
initrd = path_join(e->root, e->initrd[0]);
if (!initrd)
return log_oom();
}
- options = strv_join(e->options, " ");
+ _cleanup_free_ char *options = strv_join(e->options, " ");
if (!options)
return log_oom();
log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
- "%s "KEXEC" --load \"%s\" --append \"%s\"%s%s%s",
- arg_dry_run ? "Would run" : "Running",
+ "%s %s kernel=\"%s\" cmdline=\"%s\"%s%s%s",
+ arg_dry_run ? "Would call" : "Calling",
+ HAVE_KEXEC_FILE_LOAD_SYSCALL ? "kexec_file_load()" : "kexec",
kernel,
options,
- initrd ? " --initrd \"" : NULL, strempty(initrd), initrd ? "\"" : "");
+ initrd ? " initrd=\"" : "", strempty(initrd), initrd ? "\"" : "");
if (arg_dry_run)
return 0;
+#if HAVE_KEXEC_FILE_LOAD_SYSCALL
+ _cleanup_close_ int kernel_fd = open(kernel, O_RDONLY|O_CLOEXEC);
+ if (kernel_fd < 0)
+ return log_error_errno(errno, "Failed to open kernel '%s': %m", kernel);
+
+ _cleanup_close_ int initrd_fd = -EBADF;
+ if (initrd) {
+ initrd_fd = open(initrd, O_RDONLY|O_CLOEXEC);
+ if (initrd_fd < 0)
+ return log_error_errno(errno, "Failed to open initrd '%s': %m", initrd);
+ }
+
+ unsigned long flags = initrd ? 0 : KEXEC_FILE_NO_INITRAMFS;
+
+ if (kexec_file_load(kernel_fd, initrd_fd, strlen(options) + 1, options, flags) >= 0)
+ return 0;
+
+ int saved_errno = errno;
+
+ if (saved_errno == ENOEXEC) {
+ /* The kernel didn't recognize the image format. Try decompressing or extracting the
+ * kernel (e.g. compressed Image, ZBOOT PE, or UKI) and loading again. */
+ log_debug_errno(saved_errno, "Kernel rejected image, trying decompression/extraction: %m");
+
+ _cleanup_close_ int extracted_kernel_fd = -EBADF, extracted_initrd_fd = -EBADF;
+ r = kexec_maybe_decompress_kernel(
+ kernel, kernel_fd, &extracted_kernel_fd,
+ initrd_fd >= 0 ? NULL : &extracted_initrd_fd);
+ if (r < 0)
+ log_debug_errno(r, "Failed to decompress/extract kernel image, ignoring: %m");
+ else if (r > 0) {
+ int final_initrd_fd = initrd_fd >= 0 ? initrd_fd : extracted_initrd_fd;
+ unsigned long final_flags = final_initrd_fd >= 0 ? 0 : KEXEC_FILE_NO_INITRAMFS;
+
+ if (kexec_file_load(extracted_kernel_fd, final_initrd_fd, strlen(options) + 1, options, final_flags) >= 0)
+ return 0;
+
+ saved_errno = errno;
+ }
+ }
+
+ log_debug_errno(saved_errno, "kexec_file_load() failed, falling back to " KEXEC " binary: %m");
+#endif
+
+ /* Fall back to kexec binary for architectures without kexec_file_load() or when the
+ * syscall fails (e.g. the kernel's kexec handler doesn't support this image format
+ * but kexec-tools might via the older kexec_load() code path). */
+ if (access(KEXEC, X_OK) < 0)
+ return log_error_errno(errno, KEXEC " is not available: %m");
+
r = pidref_safe_fork(
"(kexec)",
FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG,
'sources' : files('test-json.c'),
'dependencies' : libm,
},
+ test_template + {
+ 'sources' : files('test-kexec.c'),
+ 'link_with' : [libshared],
+ },
test_template + {
'sources' : files('test-libcrypt-util.c'),
'conditions' : ['HAVE_LIBCRYPT'],
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "compress.h"
+#include "fd-util.h"
+#include "io-util.h"
+#include "reboot-util.h"
+#include "string-util.h"
+#include "tests.h"
+#include "tmpfile-util.h"
+#include "unaligned.h"
+
+static int find_kernel_image(char **ret) {
+ struct utsname u;
+
+ ASSERT_OK_ERRNO(uname(&u));
+
+ /* Kernel image names vary across architectures and distributions:
+ * vmlinuz — compressed Linux kernel (x86, most distros)
+ * vmlinux — uncompressed ELF kernel (ppc64, s390)
+ * Image — uncompressed flat binary (arm64, riscv)
+ * Image.gz — gzip-compressed Image (arm64)
+ * zImage — compressed kernel (arm 32-bit)
+ * vmlinuz.efi — EFI ZBOOT PE wrapper (arm64 with CONFIG_EFI_ZBOOT) */
+ static const char *const names[] = {
+ "vmlinuz",
+ "vmlinux",
+ "Image",
+ "Image.gz",
+ "zImage",
+ "vmlinuz.efi",
+ };
+
+ /* Try /usr/lib/modules/<version>/<name> first (kernel-install convention),
+ * then /boot/<name>-<version>, then /boot/<name> */
+ for (size_t i = 0; i < ELEMENTSOF(names); i++) {
+ _cleanup_free_ char *path = NULL;
+
+ path = strjoin("/usr/lib/modules/", u.release, "/", names[i]);
+ if (!path)
+ return -ENOMEM;
+
+ if (access(path, R_OK) >= 0) {
+ *ret = TAKE_PTR(path);
+ return 0;
+ }
+ }
+
+ /* /boot may not be accessible without root, skip gracefully */
+ if (access("/boot", R_OK) >= 0) {
+ for (size_t i = 0; i < ELEMENTSOF(names); i++) {
+ _cleanup_free_ char *path = NULL;
+
+ path = strjoin("/boot/", names[i], "-", u.release);
+ if (!path)
+ return -ENOMEM;
+
+ if (access(path, R_OK) >= 0) {
+ *ret = TAKE_PTR(path);
+ return 0;
+ }
+ }
+
+ for (size_t i = 0; i < ELEMENTSOF(names); i++) {
+ _cleanup_free_ char *path = NULL;
+
+ path = strjoin("/boot/", names[i]);
+ if (!path)
+ return -ENOMEM;
+
+ if (access(path, R_OK) >= 0) {
+ *ret = TAKE_PTR(path);
+ return 0;
+ }
+ }
+ }
+
+ return -ENOENT;
+}
+
+TEST(passthrough_unrecognized) {
+ /* A file with unrecognized magic should pass through as-is (return 0) */
+ _cleanup_close_ int fd = -EBADF;
+ _cleanup_(unlink_tempfilep) char path[] = "/tmp/test-kexec.XXXXXX";
+
+ ASSERT_OK(fd = mkostemp_safe(path));
+ ASSERT_OK_EQ_ERRNO(write(fd, "HELLO WORLD\0", 12), 12);
+ ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET));
+
+ _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF;
+ ASSERT_OK_ZERO(kexec_maybe_decompress_kernel(path, fd, &kernel_fd, &initrd_fd));
+ ASSERT_EQ(kernel_fd, -EBADF);
+ ASSERT_EQ(initrd_fd, -EBADF);
+}
+
+TEST(gzip_round_trip) {
+ _cleanup_close_ int src_fd = -EBADF, gz_fd = -EBADF;
+ _cleanup_(unlink_tempfilep) char
+ src_path[] = "/tmp/test-kexec-src.XXXXXX",
+ gz_path[] = "/tmp/test-kexec-gz.XXXXXX";
+ int r;
+
+ r = dlopen_zlib();
+ if (r < 0) {
+ log_tests_skipped("zlib not available");
+ return;
+ }
+
+ /* Create a source file with known content */
+ ASSERT_OK(src_fd = mkostemp_safe(src_path));
+ char buf[4096];
+ memset(buf, 'A', sizeof(buf));
+ ASSERT_OK(loop_write(src_fd, buf, sizeof(buf)));
+
+ /* Compress it with gzip */
+ ASSERT_OK_ERRNO(lseek(src_fd, 0, SEEK_SET));
+ ASSERT_OK(gz_fd = mkostemp_safe(gz_path));
+ ASSERT_OK(compress_stream(COMPRESSION_GZIP, src_fd, gz_fd, UINT64_MAX, NULL));
+
+ /* Feed the gzip file to kexec_maybe_decompress_kernel */
+ ASSERT_OK_ERRNO(lseek(gz_fd, 0, SEEK_SET));
+
+ _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF;
+ ASSERT_OK_POSITIVE(kexec_maybe_decompress_kernel(gz_path, gz_fd, &kernel_fd, &initrd_fd));
+ ASSERT_GE(kernel_fd, 0);
+ ASSERT_EQ(initrd_fd, -EBADF);
+
+ /* Verify the decompressed content matches the original */
+ char result[4096];
+ ASSERT_OK_EQ_ERRNO(pread(kernel_fd, result, sizeof(result), 0), (ssize_t) sizeof(result));
+ ASSERT_EQ(memcmp(buf, result, sizeof(buf)), 0);
+}
+
+TEST(zboot_synthetic) {
+ /* Construct a minimal ZBOOT header with a gzip-compressed payload */
+ _cleanup_close_ int src_fd = -EBADF, gz_fd = -EBADF, zboot_fd = -EBADF;
+ _cleanup_(unlink_tempfilep) char
+ src_path[] = "/tmp/test-kexec-zboot-src.XXXXXX",
+ gz_path[] = "/tmp/test-kexec-zboot-gz.XXXXXX",
+ zboot_path[] = "/tmp/test-kexec-zboot.XXXXXX";
+ int r;
+
+ r = dlopen_zlib();
+ if (r < 0) {
+ log_tests_skipped("zlib not available");
+ return;
+ }
+
+ /* Create and compress a payload */
+ char payload[512];
+ memset(payload, 'K', sizeof(payload));
+
+ ASSERT_OK(src_fd = mkostemp_safe(src_path));
+ ASSERT_OK(loop_write(src_fd, payload, sizeof(payload)));
+ ASSERT_OK_ERRNO(lseek(src_fd, 0, SEEK_SET));
+
+ ASSERT_OK(gz_fd = mkostemp_safe(gz_path));
+ ASSERT_OK(compress_stream(COMPRESSION_GZIP, src_fd, gz_fd, UINT64_MAX, NULL));
+
+ /* Read the compressed data */
+ struct stat st;
+ ASSERT_OK_ERRNO(fstat(gz_fd, &st));
+ size_t compressed_size = st.st_size;
+ _cleanup_free_ void *compressed = malloc(compressed_size);
+ ASSERT_NOT_NULL(compressed);
+ ASSERT_OK_EQ_ERRNO(pread(gz_fd, compressed, compressed_size, 0), (ssize_t) compressed_size);
+
+ /* Build the ZBOOT header:
+ * 0x00: "MZ"
+ * 0x04: "zimg"
+ * 0x08: payload offset (LE32)
+ * 0x0C: payload size (LE32)
+ * 0x18: "gzip\0" */
+ uint8_t header[0x40] = {};
+ uint32_t payload_offset = sizeof(header);
+
+ header[0] = 'M';
+ header[1] = 'Z';
+ memcpy(header + 0x04, "zimg", 4);
+ unaligned_write_le32(header + 0x08, payload_offset);
+ unaligned_write_le32(header + 0x0C, (uint32_t) compressed_size);
+ memcpy(header + 0x18, "gzip", 5);
+
+ ASSERT_OK(zboot_fd = mkostemp_safe(zboot_path));
+ ASSERT_OK(loop_write(zboot_fd, header, sizeof(header)));
+ ASSERT_OK(loop_write(zboot_fd, compressed, compressed_size));
+ ASSERT_OK_ERRNO(lseek(zboot_fd, 0, SEEK_SET));
+
+ /* Test extraction */
+ _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF;
+ ASSERT_OK_POSITIVE(kexec_maybe_decompress_kernel(zboot_path, zboot_fd, &kernel_fd, &initrd_fd));
+ ASSERT_GE(kernel_fd, 0);
+
+ /* Verify decompressed content matches original payload */
+ char result[512];
+ ASSERT_OK_EQ_ERRNO(pread(kernel_fd, result, sizeof(result), 0), (ssize_t) sizeof(result));
+ ASSERT_EQ(memcmp(payload, result, sizeof(payload)), 0);
+}
+
+TEST(system_kernel) {
+ _cleanup_free_ char *path = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ r = find_kernel_image(&path);
+ if (r < 0) {
+ log_tests_skipped_errno(r, "No kernel image found on this system");
+ return;
+ }
+
+ log_info("Found kernel image: %s", path);
+
+ fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ log_tests_skipped_errno(errno, "Cannot open kernel image '%s'", path);
+ return;
+ }
+
+ _cleanup_close_ int kernel_fd = -EBADF, initrd_fd = -EBADF;
+ ASSERT_OK(r = kexec_maybe_decompress_kernel(path, fd, &kernel_fd, &initrd_fd));
+
+ if (r == 0) {
+ log_info("Kernel image was not compressed (passed through as-is).");
+ return;
+ }
+
+ log_info("Kernel image was decompressed/extracted successfully.");
+ ASSERT_GE(kernel_fd, 0);
+
+ /* Verify the decompressed result is non-empty and looks plausible */
+ struct stat st;
+ ASSERT_OK_ERRNO(fstat(kernel_fd, &st));
+ ASSERT_GT(st.st_size, 0);
+ log_info("Decompressed kernel size: %zu bytes", (size_t) st.st_size);
+
+ /* Read the first bytes and check for known kernel magic */
+ uint8_t magic[8];
+ ASSERT_OK_EQ_ERRNO(pread(kernel_fd, magic, sizeof(magic), 0), (ssize_t) sizeof(magic));
+
+ if (magic[0] == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F')
+ log_info("Decompressed kernel is an ELF image.");
+ else if (magic[0] == 'M' && magic[1] == 'Z')
+ log_info("Decompressed kernel is a PE image.");
+ else
+ log_info("Decompressed kernel magic: %02x %02x %02x %02x %02x %02x %02x %02x",
+ magic[0], magic[1], magic[2], magic[3],
+ magic[4], magic[5], magic[6], magic[7]);
+
+ /* If a UKI initrd was extracted, verify it too */
+ if (initrd_fd >= 0) {
+ ASSERT_OK_ERRNO(fstat(initrd_fd, &st));
+ ASSERT_GT(st.st_size, 0);
+ log_info("Extracted initrd size: %zu bytes", (size_t) st.st_size);
+ }
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);