From: Michael Brown Date: Tue, 20 May 2025 13:14:26 +0000 (+0100) Subject: [lkrn] Add support for EFI zboot compressed kernel images X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e2f4dba2b734190bb69667a16844dec3473b1f4d;p=thirdparty%2Fipxe.git [lkrn] Add support for EFI zboot compressed kernel images Current RISC-V and AArch64 kernels found in the wild tend not to be in the documented kernel format, but are instead "EFI zboot" kernels comprising a small EFI executable that decompresses and executes the inner payload (which is a kernel in the expected format). The EFI zboot header includes a recognisable magic value "zimg" along with two fields describing the offset and length of the compressed payload. We can therefore treat this as an archive image format, extracting the payload as-is and then relying on our existing ability to execute compressed images. This is sufficient to allow iPXE to execute the Fedora 42 RISC-V kernel binary as currently published. Signed-off-by: Michael Brown --- diff --git a/src/image/lkrn.c b/src/image/lkrn.c index 37e1485d0..f8929f3b3 100644 --- a/src/image/lkrn.c +++ b/src/image/lkrn.c @@ -252,3 +252,105 @@ struct image_type lkrn_image_type __image_type ( PROBE_NORMAL ) = { .probe = lkrn_probe, .exec = lkrn_exec, }; + +/** + * Parse compressed kernel image + * + * @v image Compressed kernel image + * @v zctx Compressed kernel image context + * @ret rc Return status code + */ +static int zimg_parse ( struct image *image, struct zimg_context *zctx ) { + const struct zimg_header *zhdr; + + /* Initialise context */ + memset ( zctx, 0, sizeof ( *zctx ) ); + + /* Parse header */ + if ( image->len < sizeof ( *zhdr ) ) { + DBGC ( image, "ZIMG %s too short for header\n", + image->name ); + return -ENOEXEC; + } + zhdr = image->data; + + /* Check magic value */ + if ( zhdr->magic != cpu_to_le32 ( ZIMG_MAGIC ) ) { + DBGC ( image, "ZIMG %s bad magic value %#08x\n", + image->name, le32_to_cpu ( zhdr->magic ) ); + return -ENOEXEC; + } + + /* Record and check offset and length */ + zctx->offset = le32_to_cpu ( zhdr->offset ); + zctx->len = le32_to_cpu ( zhdr->len ); + if ( ( zctx->offset > image->len ) || + ( zctx->len > ( image->len - zctx->offset ) ) ) { + DBGC ( image, "ZIMG %s bad range [+%#zx,+%#zx)/%#zx\n", + image->name, zctx->offset, + (zctx->offset + zctx->len ), image->len ); + return -ENOEXEC; + } + + /* Record compression type */ + zctx->type.raw = zhdr->type; + + return 0; +} + +/** + * Extract compresed kernel image + * + * @v image Compressed kernel image + * @v extracted Extracted image + * @ret rc Return status code + */ +static int zimg_extract ( struct image *image, struct image *extracted ) { + struct zimg_context zctx; + const void *payload; + int rc; + + /* Parse header */ + if ( ( rc = zimg_parse ( image, &zctx ) ) != 0 ) + return rc; + DBGC ( image, "ZIMG %s has %s-compressed payload at [+%#zx,+%#zx)\n", + image->name, zctx.type.string, zctx.offset, + ( zctx.offset + zctx.len ) ); + + /* Extract compressed payload */ + payload = ( image->data + zctx.offset ); + if ( ( rc = image_set_data ( extracted, payload, zctx.len ) ) != 0 ) { + DBGC ( image, "ZIMG %s could not extract: %s\n", + image->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Probe compressed kernel image + * + * @v image Compressed kernel image + * @ret rc Return status code + */ +static int zimg_probe ( struct image *image ) { + struct zimg_context zctx; + int rc; + + /* Parse header */ + if ( ( rc = zimg_parse ( image, &zctx ) ) != 0 ) + return rc; + + DBGC ( image, "ZIMG %s is a %s-compressed Linux kernel\n", + image->name, zctx.type.string ); + return 0; +} + +/** Linux kernel compressed image type */ +struct image_type zimg_image_type __image_type ( PROBE_NORMAL ) = { + .name = "zimg", + .probe = zimg_probe, + .extract = zimg_extract, + .exec = image_extract_exec, +}; diff --git a/src/include/ipxe/lkrn.h b/src/include/ipxe/lkrn.h index c3a781b44..59aabdd4b 100644 --- a/src/include/ipxe/lkrn.h +++ b/src/include/ipxe/lkrn.h @@ -56,6 +56,40 @@ struct lkrn_context { physaddr_t fdt; }; +/** Compressed kernel image header */ +struct zimg_header { + /** Reserved */ + uint8_t reserved_a[4]; + /** Magic */ + uint32_t magic; + /** Offset to payload */ + uint32_t offset; + /** Length of payload */ + uint32_t len; + /** Reserved */ + uint8_t reserved_b[8]; + /** Compression type */ + uint32_t type; +} __attribute__ (( packed )); + +/** Compressed kernel image magic value */ +#define ZIMG_MAGIC LKRN_MAGIC ( 'z', 'i', 'm', 'g' ) + +/** Compressed kernel image context */ +struct zimg_context { + /** Offset to compressed data */ + size_t offset; + /** Length of compressed data */ + size_t len; + /** Compression type */ + union { + /** Raw type */ + uint32_t raw; + /** Printable string */ + char string[5]; + } type; +}; + #include /**