Thanks for reviewing!
On 1/24/26 1:13 PM, Collin Walling wrote:
> On 12/8/25 16:32, Zhuoying Cai wrote:
>> Enable secure IPL in audit mode, which performs signature verification,
>> but any error does not terminate the boot process. Only warnings will be
>> logged to the console instead.
>>
>> Add a comp_len variable to store the length of a segment in
>> zipl_load_segment. comp_len variable is necessary to store the
>> calculated segment length and is used during signature verification.
>> Return the length on success, or a negative return code on failure.
>>
>> Secure IPL in audit mode requires at least one certificate provided in
>> the key store along with necessary facilities (Secure IPL Facility,
>> Certificate Store Facility and secure IPL extension support).
>>
>> Note: Secure IPL in audit mode is implemented for the SCSI scheme of
>> virtio-blk/virtio-scsi devices.
>>
>> Signed-off-by: Zhuoying Cai <[email protected]>
>> ---
>> docs/system/s390x/secure-ipl.rst | 34 +++
>> pc-bios/s390-ccw/Makefile | 3 +-
>> pc-bios/s390-ccw/bootmap.c | 44 +++-
>> pc-bios/s390-ccw/bootmap.h | 11 +
>> pc-bios/s390-ccw/main.c | 9 +
>> pc-bios/s390-ccw/s390-ccw.h | 15 ++
>> pc-bios/s390-ccw/sclp.c | 44 ++++
>> pc-bios/s390-ccw/sclp.h | 6 +
>> pc-bios/s390-ccw/secure-ipl.c | 383 +++++++++++++++++++++++++++++++
>> pc-bios/s390-ccw/secure-ipl.h | 94 ++++++++
>> 10 files changed, 638 insertions(+), 5 deletions(-)
>> create mode 100644 pc-bios/s390-ccw/secure-ipl.c
>> create mode 100644 pc-bios/s390-ccw/secure-ipl.h
>>
>> diff --git a/docs/system/s390x/secure-ipl.rst
>> b/docs/system/s390x/secure-ipl.rst
>> index 0a02f171b4..8958a51f0b 100644
>> --- a/docs/system/s390x/secure-ipl.rst
>> +++ b/docs/system/s390x/secure-ipl.rst
>> @@ -18,3 +18,37 @@ Note: certificate files must have a .pem extension.
>> .. code-block:: shell
>>
>> qemu-system-s390x -machine
>> s390-ccw-virtio,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem
>> ...
>> +
>> +
>> +IPL Modes
>> +=========
>> +Multiple IPL modes are available to differentiate between the various IPL
>> +configurations. These modes are mutually exclusive and enabled based on the
>> +``boot-certs`` option on the QEMU command line.
>> +
>> +Normal Mode
>> +-----------
>> +
>> +The absence of certificates will attempt to IPL a guest without secure IPL
>> +operations. No checks are performed, and no warnings/errors are reported.
>> +This is the default mode.
>> +
>> +Configuration:
>> +
>> +.. code-block:: shell
>> +
>> + qemu-system-s390x -machine s390-ccw-virtio ...
>> +
>> +Audit Mode
>> +----------
>> +
>> +With *only* the presence of certificates in the store, it is assumed that
>> secure
>> +boot operations should be performed with errors reported as warnings. As
>> such,
>> +the secure IPL operations will be performed, and any errors that stem from
>> these
>> +operations will result in a warning.
>
> I think both sentences are saying the same thing. How about:
>
> "When the certificate store is populated with at least one certificate
> and no additional secure IPL parameters are provided on the command
> line, then secure IPL will proceed in "audit mode". All secure IPL
> operations will be performed with signature verification errors reported
> as non-disruptive warnings."
>
>> +
>> +Configuration:
>> +
>> +.. code-block:: shell
>> +
>> + qemu-system-s390x -machine
>> s390-ccw-virtio,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem
>> ...
>> diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
>> index a0f24c94a8..603761a857 100644
>> --- a/pc-bios/s390-ccw/Makefile
>> +++ b/pc-bios/s390-ccw/Makefile
>> @@ -34,7 +34,8 @@ QEMU_DGFLAGS = -MMD -MP -MT $@ -MF $(@D)/$(*F).d
>> .PHONY : all clean build-all distclean
>>
>> OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o netmain.o \
>> - virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o
>> + virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o \
>> + secure-ipl.o
>>
>> SLOF_DIR := $(SRC_PATH)/../../roms/SLOF
>>
>> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
>> index 9a03eab6ed..342772860b 100644
>> --- a/pc-bios/s390-ccw/bootmap.c
>> +++ b/pc-bios/s390-ccw/bootmap.c
>> @@ -15,6 +15,7 @@
>> #include "bootmap.h"
>> #include "virtio.h"
>> #include "bswap.h"
>> +#include "secure-ipl.h"
>>
>> #ifdef DEBUG
>> /* #define DEBUG_FALLBACK */
>> @@ -617,7 +618,7 @@ static int ipl_eckd(void)
>> * Returns: length of the segment on success,
>> * negative value on error.
>> */
>> -static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
>> +int zipl_load_segment(ComponentEntry *entry, uint64_t address)
>> {
>> const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));
>> ScsiBlockPtr *bprs = (void *)sec;
>> @@ -736,9 +737,22 @@ static int zipl_run(ScsiBlockPtr *pte)
>> /* Load image(s) into RAM */
>> entry = (ComponentEntry *)(&header[1]);
>>
>> - rc = zipl_run_normal(&entry, tmp_sec);
>> - if (rc) {
>> - return rc;
>> + switch (boot_mode) {
>> + case ZIPL_BOOT_MODE_SECURE_AUDIT:
>> + rc = zipl_run_secure(&entry, tmp_sec);
>> + if (rc) {
>> + return rc;
>> + }
>> + break;
>> + case ZIPL_BOOT_MODE_NORMAL:
>> + rc = zipl_run_normal(&entry, tmp_sec);
>> + if (rc) {
>> + return rc;
>> + }
>> + break;
>> + default:
>> + puts("Unknown boot mode");
>> + return -1;
>> }
>
> Each case has a common pattern. Move the if (rc) check outside the
> switch block.
>
>>
>> if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
>> @@ -1103,17 +1117,35 @@ static int zipl_load_vscsi(void)
>> * IPL starts here
>> */
>>
>> +ZiplBootMode zipl_mode(uint8_t hdr_flags)
>> +{
>> + bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
>> + bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
>> +
>> + if (!sipl_set && iplir_set) {
>> + return ZIPL_BOOT_MODE_SECURE_AUDIT;
>> + }
>> +
>> + return ZIPL_BOOT_MODE_NORMAL;
>> +}
>
> What about making this a void function `set_boot_mode()` which gets
> invoked in main just after the iplb is stored. This would also remove
> the need for the "UNSPECIFIED" mode, since this variable would get
> resolved early on.
>
> Perhaps it makes more sense to default to NORMAL unless the parameters
> specify otherwise?
>
I agree that NORMAL can be the default, so the UNSPECIFIED mode can be
removed. It may be clearer to keep this as a value-returning helper and
rename it to get_boot_mode() (or similar). The function simply derives
the boot mode from the IPLB flags, and main can assign it directly after
the IPLB is stored:
boot_mode = get_boot_mode(iplb->hdr_flags);
>> +
>> void zipl_load(void)
>> {
>> VDev *vdev = virtio_get_device();
>>
>> if (vdev->is_cdrom) {
>> + if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
>> + panic("Secure boot from ISO image is not supported!");
>> + }
>> ipl_iso_el_torito();
>> puts("Failed to IPL this ISO image!");
>> return;
>> }
>>
>> if (virtio_get_device_type() == VIRTIO_ID_NET) {
>> + if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
>> + panic("Virtio net boot device does not support secure boot!");
>> + }
>> netmain();
>> puts("Failed to IPL from this network!");
>> return;
>> @@ -1124,6 +1156,10 @@ void zipl_load(void)
>> return;
>> }
>>
>> + if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
>> + panic("ECKD boot device does not support secure boot!");
>> + }
>> +
>
> For all cases here in zipl_load, an IPL_assert is much cleaner. e.g.
>
> IPL_assert(boot_mode == ZIPL_BOOT_MODE_NORMAL, "message");
>
> Which will also cover the need to check for the other boot modes in a
> future patch.
>
> Additionally, I'm not sure if "ECKD boot device" is correct here.
> Couldn't the IPL devices after this point actually be virtio-blk,
> virtio-scsi, etc? Perhaps a generic "Boot device does not support
> secure IPL" or something is sufficient.
>
Yes, IPL devices after this point are virtio-blk/virtio-scsi using the
ECKD scheme. Maybe we could rephrase the message to something like:
"Secure boot with the ECKD scheme is not supported!"
>> switch (virtio_get_device_type()) {
>> case VIRTIO_ID_BLOCK:
>> zipl_load_vblk();
>> diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h
>> index 95943441d3..dc2783faa2 100644
>> --- a/pc-bios/s390-ccw/bootmap.h
>> +++ b/pc-bios/s390-ccw/bootmap.h
>> @@ -88,9 +88,18 @@ typedef struct BootMapTable {
>> BootMapPointer entry[];
>> } __attribute__ ((packed)) BootMapTable;
>>
>> +#define DER_SIGNATURE_FORMAT 1
>> +
>> +typedef struct SignatureInformation {
>> + uint8_t format;
>> + uint8_t reserved[3];
>> + uint32_t sig_len;
>> +} SignatureInformation;
>> +
>> typedef union ComponentEntryData {
>> uint64_t load_psw;
>> uint64_t load_addr;
>> + SignatureInformation sig_info;
>> } ComponentEntryData;
>>
>> typedef struct ComponentEntry {
>> @@ -113,6 +122,8 @@ typedef struct ScsiMbr {
>> ScsiBlockPtr pt; /* block pointer to program table */
>> } __attribute__ ((packed)) ScsiMbr;
>>
>> +int zipl_load_segment(ComponentEntry *entry, uint64_t address);
>> +
>> #define ZIPL_MAGIC "zIPL"
>> #define ZIPL_MAGIC_EBCDIC "\xa9\xc9\xd7\xd3"
>> #define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */
>> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
>> index c9328f1c51..8aabce115f 100644
>> --- a/pc-bios/s390-ccw/main.c
>> +++ b/pc-bios/s390-ccw/main.c
>> @@ -28,6 +28,7 @@ IplParameterBlock *iplb;
>> bool have_iplb;
>> static uint16_t cutype;
>> LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */
>> +ZiplBootMode boot_mode;
>>
>> #define LOADPARM_PROMPT "PROMPT "
>> #define LOADPARM_EMPTY " "
>> @@ -272,9 +273,17 @@ static int virtio_setup(void)
>>
>> static void ipl_boot_device(void)
>> {
>> + if (boot_mode == ZIPL_BOOT_MODE_UNSPECIFIED) {
>> + boot_mode = zipl_mode(iplb->hdr_flags);
>> + }
>> +
>> switch (cutype) {
>> case CU_TYPE_DASD_3990:
>> case CU_TYPE_DASD_2107:
>> + if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
>> + panic("Passthrough (vfio) CCW device does not support secure
>> boot!");
>> + }
>> +
>
> IPL_assert
>
>> dasd_ipl(blk_schid, cutype);
>> break;
>> case CU_TYPE_VIRTIO:
>> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
>> index b1dc35cded..c2ba40d067 100644
>> --- a/pc-bios/s390-ccw/s390-ccw.h
>> +++ b/pc-bios/s390-ccw/s390-ccw.h
>> @@ -39,6 +39,9 @@ typedef unsigned long long u64;
>> #define MIN_NON_ZERO(a, b) ((a) == 0 ? (b) : \
>> ((b) == 0 ? (a) : (MIN(a, b))))
>> #endif
>> +#ifndef ROUND_UP
>> +#define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d)))
>> +#endif
>>
>> #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
>>
>> @@ -64,6 +67,8 @@ void sclp_print(const char *string);
>> void sclp_set_write_mask(uint32_t receive_mask, uint32_t send_mask);
>> void sclp_setup(void);
>> void sclp_get_loadparm_ascii(char *loadparm);
>> +bool sclp_is_diag320_on(void);
>> +bool sclp_is_sipl_on(void);
>> int sclp_read(char *str, size_t count);
>>
>> /* virtio.c */
>> @@ -76,6 +81,16 @@ int virtio_read(unsigned long sector, void *load_addr);
>> /* bootmap.c */
>> void zipl_load(void);
>>
>> +typedef enum ZiplBootMode {
>> + ZIPL_BOOT_MODE_UNSPECIFIED = 0,
>> + ZIPL_BOOT_MODE_NORMAL = 1,
>> + ZIPL_BOOT_MODE_SECURE_AUDIT = 2,
>> +} ZiplBootMode;
>> +
>> +extern ZiplBootMode boot_mode;
>> +
>> +ZiplBootMode zipl_mode(uint8_t hdr_flags);
>> +
>> /* jump2ipl.c */
>> void write_reset_psw(uint64_t psw);
>> int jump_to_IPL_code(uint64_t address);
>> diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
>> index 4a07de018d..0b03c3164f 100644
>> --- a/pc-bios/s390-ccw/sclp.c
>> +++ b/pc-bios/s390-ccw/sclp.c
>> @@ -113,6 +113,50 @@ void sclp_get_loadparm_ascii(char *loadparm)
>> }
>> }
>>
>> +static void sclp_get_fac134(uint8_t *fac134)
>> +{
>> +
>
> Remove extra newline.
>
>> + ReadInfo *sccb = (void *)_sccb;
>> +
>> + memset((char *)_sccb, 0, sizeof(ReadInfo));
>> + sccb->h.length = SCCB_SIZE;
>> + if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
>> + *fac134 = sccb->fac134;
>> + }
>> +}
>> +
>> +bool sclp_is_diag320_on(void)
>> +{
>> + uint8_t fac134 = 0;
>> +
>> + sclp_get_fac134(&fac134);
>> + return fac134 & SCCB_FAC134_DIAG320_BIT;
>> +}
>> +
>> +/*
>> + * Get fac_ipl (byte 136 and byte 137 of the SCLP Read Info block)
>> + * for IPL device facilities.
>> + */
>> +static void sclp_get_fac_ipl(uint16_t *fac_ipl)
>> +{
>> +
>> + ReadInfo *sccb = (void *)_sccb;
>> +
>> + memset((char *)_sccb, 0, sizeof(ReadInfo));
>> + sccb->h.length = SCCB_SIZE;
>> + if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
>> + *fac_ipl = sccb->fac_ipl;
>> + }
>> +}
>
> Hmm. The number of SCLP service calls could be reduced by doing a
> singular read scp info call early and storing all the fields needed to
> be accessed later.
>
> But maybe that is outside the scope of these patches.
>
>> +
>> +bool sclp_is_sipl_on(void)
>> +{
>> + uint16_t fac_ipl = 0;
>> +
>> + sclp_get_fac_ipl(&fac_ipl);
>> + return fac_ipl & SCCB_FAC_IPL_SIPL_BIT;
>> +}
>> +
>> int sclp_read(char *str, size_t count)
>> {
>> ReadEventData *sccb = (void *)_sccb;
>> diff --git a/pc-bios/s390-ccw/sclp.h b/pc-bios/s390-ccw/sclp.h
>> index 64b53cad29..cf147f4634 100644
>> --- a/pc-bios/s390-ccw/sclp.h
>> +++ b/pc-bios/s390-ccw/sclp.h
>> @@ -50,6 +50,8 @@ typedef struct SCCBHeader {
>> } __attribute__((packed)) SCCBHeader;
>>
>> #define SCCB_DATA_LEN (SCCB_SIZE - sizeof(SCCBHeader))
>> +#define SCCB_FAC134_DIAG320_BIT 0x4
>> +#define SCCB_FAC_IPL_SIPL_BIT 0x4000
>>
>> typedef struct ReadInfo {
>> SCCBHeader h;
>> @@ -57,6 +59,10 @@ typedef struct ReadInfo {
>> uint8_t rnsize;
>> uint8_t reserved[13];
>> uint8_t loadparm[LOADPARM_LEN];
>> + uint8_t reserved1[102];
>> + uint8_t fac134;
>> + uint8_t reserved2;
>> + uint16_t fac_ipl;
>> } __attribute__((packed)) ReadInfo;
>>
>> typedef struct SCCB {
>> diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
>> new file mode 100644
>> index 0000000000..c1c5bc682a
>> --- /dev/null
>> +++ b/pc-bios/s390-ccw/secure-ipl.c
>> @@ -0,0 +1,383 @@
>> +/*
>> + * S/390 Secure IPL
>> + *
>> + * Functions to support IPL in secure boot mode (DIAG 320, DIAG 508,
>> + * signature verification, and certificate handling).
>> + *
>> + * For secure IPL overview: docs/system/s390x/secure-ipl.rst
>> + * For secure IPL technical: docs/specs/s390x-secure-ipl.rst
>> + *
>> + * Copyright 2025 IBM Corp.
>> + * Author(s): Zhuoying Cai <[email protected]>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <stdio.h>
>> +#include "bootmap.h"
>> +#include "s390-ccw.h"
>> +#include "secure-ipl.h"
>> +
>> +uint8_t vcssb_data[VCSSB_MIN_LEN] __attribute__((__aligned__(PAGE_SIZE)));
>> +
>> +VCStorageSizeBlock *zipl_secure_get_vcssb(void)
>> +{
>> + VCStorageSizeBlock *vcssb;
>> + int rc;
>> +
>> + if (!sclp_is_diag320_on() || !is_cert_store_facility_supported()) {
>> + puts("Certificate Store Facility is not supported by the
>> hypervisor!");
>> + return NULL;
>> + }
>> +
>> + vcssb = (VCStorageSizeBlock *)vcssb_data;
>> + /* avoid retrieving vcssb multiple times */
>> + if (vcssb->length >= VCSSB_MIN_LEN) {
>> + return vcssb;
>> + }
>
> Change the order of operations here to check if vcssb has been retrieved
> before checking diag320 stuff. Otherwise, every time
> `zipl_secure_get_vcssb()` is invoked, both an sclp service call and a
> diag320 call is always performed, which is unnecessary if the VCSSB has
> already been stored.
>
>> +
>> + vcssb->length = VCSSB_MIN_LEN;
>> + rc = diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI);
>> + if (rc != DIAG_320_RC_OK) {
>> + return NULL;
>> + }
>
> Can do if (diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI) != DIAG_320_RC_OK)
>
> and then get rid of rc
>
>> +
>> + return vcssb;
>> +}
>> +
>> +static uint32_t get_certs_length(void)
>
> The function name is ambiguous. I had to digest the math below to
> understand the intention. How about something like...
>
> `get_total_certs_length()`
>
> ...to help clarify that this function is returning a value to store all
> certs.
>
>> +{
>> + VCStorageSizeBlock *vcssb;
>> + uint32_t len;
>> +
>> + vcssb = zipl_secure_get_vcssb();
>> + if (vcssb == NULL) {
>> + return 0;
>> + }
>> +
>> + len = vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct *
>> VCE_HEADER_LEN;
>> +
>
> Could return the line above and get rid of len.
>
>> + return len;
>> +}
>> +
>> +static uint32_t request_certificate(uint8_t *cert, uint8_t index)
>> +{
>> + VCStorageSizeBlock *vcssb;
>> + VCBlock *vcb;
>> + VCEntry *vce;
>> + uint64_t rc = 0;
>> + uint32_t cert_len = 0;
>> + uint32_t max_single_vcb_len;
>> +
>> + /* Get Verification Certificate Storage Size block with DIAG320 subcode
>> 1 */
>> + vcssb = zipl_secure_get_vcssb();
>> + if (vcssb == NULL) {
>> + return 0;
>> + }
>> +
>> + /*
>> + * Request single entry
>> + * Fill input fields of single-entry VCB
>> + */
>> + max_single_vcb_len = ROUND_UP(vcssb->max_single_vcb_len, PAGE_SIZE);
>> + vcb = malloc(max_single_vcb_len);
>> + vcb->in_len = max_single_vcb_len;
>> + vcb->first_vc_index = index + 1;
>> + vcb->last_vc_index = index + 1;
>
> There needs to be a better way to account for the index origin
> differences. See my comments below for `verify_signature()`.
>
>> +
>> + rc = diag320(vcb, DIAG_320_SUBC_STORE_VC);
>> + if (rc != DIAG_320_RC_OK) {
>> + goto out;
>> + }
>
> if (!diag320(vcb, DIAG_320_SUBC_STORE_VC))
>
> then get rid of rc
>
>> +
>> + if (vcb->out_len == VCB_HEADER_LEN) {
>> + puts("No certificate entry");
>> + goto out;
>> + }
>> + if (vcb->remain_ct != 0) {
>> + puts("Not enough memory to store all requested certificates");
>> + goto out;
>> + }
>> +
>> + vce = (VCEntry *)vcb->vce_buf;
>> + if (!(vce->flags & DIAG_320_VCE_FLAGS_VALID)) {
>> + puts("Invalid certificate");
>> + goto out;
>> + }
>> +
>> + cert_len = vce->cert_len;
>> + memcpy(cert, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
>> +
>> +out:
>> + free(vcb);
>> + return cert_len;
>> +}
>> +
>> +static void cert_list_add(IplSignatureCertificateList *certs, int
>> cert_index,
>> + uint8_t *cert, uint64_t cert_len)
>
> s/certs/cert_list
> s/cert/cert_addr
>
>> +{
>> + if (cert_index > MAX_CERTIFICATES - 1) {
>> + printf("Warning: Ignoring cert entry [%d] because it's over %d
>> entires\n",
>> + cert_index + 1, MAX_CERTIFICATES);
>> + return;
>> + }
>> +
>> + certs->cert_entries[cert_index].addr = (uint64_t)cert;
>> + certs->cert_entries[cert_index].len = cert_len;
>> + certs->ipl_info_header.len += sizeof(certs->cert_entries[cert_index]);
>> +}
>> +
>> +static void comp_list_add(IplDeviceComponentList *comps, int comp_index,
>> + int cert_index, uint64_t comp_addr,
>> + uint64_t comp_len, uint8_t flags)
>
> s/comps/comp_list
>
>> +{
>> + if (comp_index > MAX_CERTIFICATES - 1) {
>> + printf("Warning: Ignoring comp entry [%d] because it's over %d
>> entires\n",
>> + comp_index + 1, MAX_CERTIFICATES);
>> + return;
>> + }
>> +
>> + comps->device_entries[comp_index].addr = comp_addr;
>> + comps->device_entries[comp_index].len = comp_len;
>> + comps->device_entries[comp_index].flags = flags;
>> + comps->device_entries[comp_index].cert_index = cert_index;
>> + comps->ipl_info_header.len += sizeof(comps->device_entries[comp_index]);
>> +}
>> +
>> +static int update_iirb(IplDeviceComponentList *comps,
>> IplSignatureCertificateList *certs)
>> +{
>> + IplInfoReportBlock *iirb;
>> + IplDeviceComponentList *iirb_comps;
>> + IplSignatureCertificateList *iirb_certs;
>> + uint32_t iirb_hdr_len;
>> + uint32_t comps_len;
>> + uint32_t certs_len;
>> +
>> + if (iplb->len % 8 != 0) {
>> + panic("IPL parameter block length field value is not multiple of 8
>> bytes");
>> + }
>> +
>> + iirb_hdr_len = sizeof(IplInfoReportBlockHeader);
>> + comps_len = comps->ipl_info_header.len;
>> + certs_len = certs->ipl_info_header.len;
>> + if ((comps_len + certs_len + iirb_hdr_len) >
>> sizeof(IplInfoReportBlock)) {
>> + puts("Not enough space to hold all components and certificates in
>> IIRB");
>> + return -1;
>> + }
>
> Shouldn't this also be a panic? If there's not enough space for the
> IIRB, then that should be more serious than just warning in audit mode.
>
>> +
>> + /* IIRB immediately follows IPLB */
>> + iirb = &ipl_data.iirb;
>> + iirb->hdr.len = iirb_hdr_len;
>> +
>> + /* Copy IPL device component list after IIRB Header */
>> + iirb_comps = (IplDeviceComponentList *) iirb->info_blks;
>> + memcpy(iirb_comps, comps, comps_len);
>> +
>> + /* Update IIRB length */
>> + iirb->hdr.len += comps_len;
>> +
>> + /* Copy IPL sig cert list after IPL device component list */
>> + iirb_certs = (IplSignatureCertificateList *) (iirb->info_blks +
>> +
>> iirb_comps->ipl_info_header.len);
>> + memcpy(iirb_certs, certs, certs_len);
>> +
>> + /* Update IIRB length */
>> + iirb->hdr.len += certs_len;
>> +
>> + return 0;
>> +}
>> +
>> +static bool secure_ipl_supported(void)
>> +{
>> + if (!sclp_is_sipl_on()) {
>> + puts("Secure IPL Facility is not supported by the hypervisor!");
>> + return false;
>> + }
>> +
>> + if (!is_secure_ipl_extension_supported()) {
>> + puts("Secure IPL extensions are not supported by the hypervisor!");
>> + return false;
>> + }
>> +
>> + if (!sclp_is_diag320_on() || !is_cert_store_facility_supported()) {
>> + puts("Certificate Store Facility is not supported by the
>> hypervisor!");
>> + return false;
>> + }
>> +
>> + return true;
>> +}
>> +
>> +static void init_lists(IplDeviceComponentList *comps,
>> IplSignatureCertificateList *certs)
>> +{
>> + comps->ipl_info_header.ibt = IPL_IBT_COMPONENTS;
>> + comps->ipl_info_header.len = sizeof(comps->ipl_info_header);
>> +
>> + certs->ipl_info_header.ibt = IPL_IBT_CERTIFICATES;
>> + certs->ipl_info_header.len = sizeof(certs->ipl_info_header);
>> +}
>> +
>> +static uint32_t zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
>> +{
>
> Needs to be type int, otherwise returning -1 below doesn't make sense.
>
>> + uint32_t sig_len;
>> +
>> + if (zipl_load_segment(entry, sig_sec) < 0) {
>> + return -1;
>> + }
>> +
>> + if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {
>> + puts("Signature is not in DER format");
>> + return -1;
>> + }
>> + sig_len = entry->compdat.sig_info.sig_len;
>> +
>> + return sig_len;
>
> Could return entry->compdat.sig_info.sig_len, then sig_len goes away.
>
>> +}
>> +
>> +/*
>> + * Returns: 1 - New certificate requested and added to cert_table and cert
>> list
>> + * 0 - Certificate already exists in the cert_table
>> + * -1 - Error while requesting certificate
>> + */
>> +static int handle_certificate(int *cert_table, uint8_t **cert,
>> + uint64_t cert_len, uint8_t cert_table_idx,
>> + IplSignatureCertificateList *certs, int
>> cert_entry_idx)
>> +{
>> + if (cert_table[cert_table_idx] != -1) {
>> + return 0;
>> + }
>> +
>> + if (!request_certificate(*cert, cert_table_idx)) {
>> + puts("Could not get certificate");
>> + return -1;
>> + }
>> +
>> + cert_list_add(certs, cert_entry_idx, *cert, cert_len);
>> + cert_table[cert_table_idx] = cert_entry_idx;
>> + return 1;
>> +}
>> +
>> +int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>> +{
>> + IplDeviceComponentList comps;
>> + IplSignatureCertificateList certs;
>
> It would read a little easier if these were comp_list and cert_list
>
>> + ComponentEntry *entry = *entry_ptr;
>> + uint8_t *cert = NULL;
>> + uint64_t *sig = NULL;
>> + int cert_entry_idx = 0;
>> + int comp_entry_idx = 0;
>> + uint64_t comp_addr;
>> + int comp_len;
>> + uint32_t sig_len = 0;
>> + uint64_t cert_len = -1;
>> + uint8_t cert_table_idx = -1;
>> + bool verified;
>> + uint32_t certs_len;
>> + /*
>> + * Store indices of cert entry that have already used for signature
>> + * verification to prevent allocating the same certificate multiple
>> times.
>> + * cert_table index (cert_table_idx):
>> + * index of certificate from qemu cert store used for
>> verification
>> + * cert_table value (cert_entry_idx):
>> + * index of cert entry in cert list that contains the
>> certificate
>> + */
>> + int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
>
> If you agree with the suggestion below for verify_signature to adjust
> the returned index, then the cert_table can be used a bit differently:
>
> Make this an array of booleans. Use the index returned by
> verify_signature. Value true means that the cert has already been added
> to the IplSignatureCertificateList. False means it has yet to be added
> to the list.
>
If verify_signature adjusted the returned index, I don’t quite see why
cert_table should become a boolean array. We still need to track the
certificate index in IplSignatureCertificateList, since that index is
used later as the IPL-Signature-Certificate Index (SCI) in
IplDeviceComponentList. That’s what cert_table currently provides.
With a boolean array, that index information would be lost. Am I missing
something?
> Remove the mention of "qemu cert store", as the BIOS technically does
> not know anything about the underlying cert store or its implementation.
>
>> + int rc;
>> + int signed_count = 0;
>> +
>> + if (!secure_ipl_supported()) {
>> + return -1;
>> + }
>> +
>> + init_lists(&comps, &certs);
>> + certs_len = get_certs_length();
>> + cert = malloc(certs_len);
>
> cert = malloc(get_total_certs_length());
>
> Then get rid of the certs_len variable.
>
>> + sig = malloc(MAX_SECTOR_SIZE);
>> +
>> + while (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
>> + switch (entry->component_type) {
>> + case ZIPL_COMP_ENTRY_SIGNATURE:
>> + if (sig_len) {
>> + goto out;
>> + }
>> +
>> + sig_len = zipl_load_signature(entry, (uint64_t)sig);
>> + if (sig_len < 0) {
>> + goto out;
>> + }
>> + break;
>> + case ZIPL_COMP_ENTRY_LOAD:
>> + comp_addr = entry->compdat.load_addr;
>> + comp_len = zipl_load_segment(entry, comp_addr);
>> + if (comp_len < 0) {
>> + goto out;
>> + }
>> +
>> + if (!sig_len) {
>> + break;
>> + }
>> +
>> + verified = verify_signature(comp_len, comp_addr, sig_len,
>> (uint64_t)sig,
>> + &cert_len, &cert_table_idx);
>> +
>> + if (verified) {
>> + rc = handle_certificate(cert_table, &cert, cert_len,
>> + cert_table_idx, &certs,
>> cert_entry_idx);
>
> Check cert_table here before calling handle_certificate. It would
> contain all the cert_table logic to zipl_run_secure and make it a little
> bit easier to understand. Also less variables to pass around.
>
>> + if (rc == -1) {
>> + goto out;
>> + }
>> +
>> + /* increment for the next certificate */
>> + if (rc == 1) {
>> + cert_entry_idx++;
>> + cert += cert_len;
>> + }
>> +
>> + puts("Verified component");
>> + comp_list_add(&comps, comp_entry_idx,
>> cert_table[cert_table_idx],
>> + comp_addr, comp_len,
>> + S390_IPL_COMPONENT_FLAG_SC |
>> S390_IPL_COMPONENT_FLAG_CSV);
>> + } else {
>> + comp_list_add(&comps, comp_entry_idx, -1,
>> + comp_addr, comp_len,
>> + S390_IPL_COMPONENT_FLAG_SC);
>> + zipl_secure_handle("Could not verify component");
>> + }
>
> This can be cleaned up a bit. Set cert_index and flags within the
> if/else block. Call `comp_list_add()` once after the block.
>
>> +
>> + comp_entry_idx++;
>> + signed_count += 1;
>> + /* After a signature is used another new one can be accepted */
>> + sig_len = 0;
>> + break;
>> + default:
>> + puts("Unknown component entry type");
>> + return -1;
>> + }
>> +
>> + entry++;
>> +
>> + if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
>> + puts("Wrong entry value");
>> + return -EINVAL;
>> + }
>> + }
>> +
>> + if (signed_count == 0) {
>> + zipl_secure_handle("Secure boot is on, but components are not
>> signed");
>> + }
>> +
>> + if (update_iirb(&comps, &certs)) {
>> + zipl_secure_handle("Failed to write IPL Information Report Block");
>> + }
>> +
>> + *entry_ptr = entry;
>> + free(sig);
>> +
>> + return 0;
>> +out:
>> + free(cert);
>> + free(sig);
>> +
>> + return -1;
>> +}
>> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
>> new file mode 100644
>> index 0000000000..a6fc1ac8de
>> --- /dev/null
>> +++ b/pc-bios/s390-ccw/secure-ipl.h
>> @@ -0,0 +1,94 @@
>> +/*
>> + * S/390 Secure IPL
>> + *
>> + * Copyright 2025 IBM Corp.
>> + * Author(s): Zhuoying Cai <[email protected]>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef _PC_BIOS_S390_CCW_SECURE_IPL_H
>> +#define _PC_BIOS_S390_CCW_SECURE_IPL_H
>> +
>> +#include <diag320.h>
>> +#include <diag508.h>
>> +
>> +VCStorageSizeBlock *zipl_secure_get_vcssb(void);
>> +int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
>> +
>> +static inline void zipl_secure_handle(const char *message)
>
> This could be implemented like an assert, where the first param is the
> condition.
>
> e.g.
>
> `zipl_secure_handle(signed_count > 0, "Secure boot is on, but components
> are not signed");`
>
> Then some if checks / extra indentation goes away.
>
>> +{
>> + switch (boot_mode) {
>> + case ZIPL_BOOT_MODE_SECURE_AUDIT:
>> + IPL_check(false, message);
>> + break;
>> + default:
>> + break;
>> + }
>> +}
>> +
>> +static inline uint64_t diag320(void *data, unsigned long subcode)
>> +{
>> + register unsigned long addr asm("0") = (unsigned long)data;
>> + register unsigned long rc asm("1") = 0;
>> +
>> + asm volatile ("diag %0,%2,0x320\n"
>> + : "+d" (addr), "+d" (rc)
>> + : "d" (subcode)
>> + : "memory", "cc");
>> + return rc;
>> +}
>> +
>> +static inline bool is_cert_store_facility_supported(void)
>> +{
>> + uint32_t d320_ism;
>> +
>> + diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
>> + return (d320_ism & DIAG_320_ISM_QUERY_SUBCODES) &&
>> + (d320_ism & DIAG_320_ISM_QUERY_VCSI) &&
>> + (d320_ism & DIAG_320_ISM_STORE_VC);
>> +}
>
> Don't need to check if DIAG_320_ISM_QUERY_SUBCODES is supported (implied
> if query ISM returns OK).
>
> Can clean this up by doing `return d320_ism & (DIAG_320_ISM_QUERY_VCSI |
> DIAG_320_ISM_STORE_VC);`
>
> Merge this function with `sclp_is_diag320_on()`, checking the read scp
> facility bit first before calling diag320.
>
>> +
>> +static inline uint64_t _diag508(void *data, unsigned long subcode)
>> +{
>> + register unsigned long addr asm("0") = (unsigned long)data;
>> + register unsigned long rc asm("1") = 0;
>> +
>> + asm volatile ("diag %0,%2,0x508\n"
>> + : "+d" (addr), "+d" (rc)
>> + : "d" (subcode)
>> + : "memory", "cc");
>> + return rc;
>> +}
>> +
>> +static inline bool is_secure_ipl_extension_supported(void)
>
> "is_signature_verif_supported()" or something like that reads better.
>
>> +{
>> + uint64_t d508_subcodes;
>> +
>> + d508_subcodes = _diag508(NULL, DIAG_508_SUBC_QUERY_SUBC);
>> + return d508_subcodes & DIAG_508_SUBC_SIG_VERIF;
>> +}
>> +
>> +static inline bool verify_signature(uint64_t comp_len, uint64_t comp_addr,
>> + uint64_t sig_len, uint64_t sig_addr,
>> + uint64_t *cert_len, uint8_t *cert_idx)
>> +{
>> + Diag508SigVerifBlock svb;
>> +
>> + svb.length = sizeof(Diag508SigVerifBlock);
>> + svb.version = 0;
>> + svb.comp_len = comp_len;
>> + svb.comp_addr = comp_addr;
>> + svb.sig_len = sig_len;
>> + svb.sig_addr = sig_addr;
>> +
>> + if (_diag508(&svb, DIAG_508_SUBC_SIG_VERIF) == DIAG_508_RC_OK) {
>> + *cert_len = svb.cert_len;
>> + *cert_idx = svb.cert_store_index;
>
> Adjust for different indexing origins here, accounting for what DIAG320
> will later need when retrieving certs:
>
> ```
> /*
> * DIAG 508 utilizes an index origin of 0 when indexing the cert store.
> * The cert_idx will be used for DIAG 320 data structures, which expects
> * an index origin of 1. Account for the offset here so it's easier to
> * manage later.
> */
> *cert_idx = svb.cert_store_index + 1;
> ```
>
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> +#endif /* _PC_BIOS_S390_CCW_SECURE_IPL_H */
>