On Wed, 2026-07-01 at 16:49 -0400, Zhuoying Cai wrote:
> Add additional checks to ensure that components do not overlap with
> signed components when loaded into memory.
>
> Add additional checks to ensure the load addresses of unsigned components
> are greater than or equal to 0x2000.
>
> When the secure IPL code loading attributes facility (SCLAF) is installed,
> all signed components must contain a secure code loading attributes block
> (SCLAB).
>
> The SCLAB provides further validation of information on where to load the
> signed binary code from the load device, and where to start the execution
> of the loaded OS code.
>
> When SCLAF is installed, its content must be evaluated during secure IPL.
>
> Add IPL Information Error Indicators (IIEI) and Component Error
> Indicators (CEI) for IPL Information Report Block (IIRB).
>
> When SCLAF is installed, additional secure boot checks are performed
> during zipl and store results of verification into IIRB.
>
> Signed-off-by: Zhuoying Cai <[email protected]>
> ---
> include/hw/s390x/ipl/qipl.h | 29 +++++-
> pc-bios/s390-ccw/sclp.h | 1 +
> pc-bios/s390-ccw/secure-ipl.c | 170 +++++++++++++++++++++++++++++++++-
> pc-bios/s390-ccw/secure-ipl.h | 51 ++++++++++
> 4 files changed, 246 insertions(+), 5 deletions(-)
>
> diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
> index 2b29dfa8c5..f05c4a7206 100644
> --- a/include/hw/s390x/ipl/qipl.h
> +++ b/include/hw/s390x/ipl/qipl.h
> @@ -167,10 +167,20 @@ struct IplInfoReportBlockHeader {
> };
> typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
>
> +/* IPL Info Error Indicators */
> +#define S390_IIEI_NO_SIGNED_COMP 0x8000 /* bit 0 */
> +#define S390_IIEI_NO_SCLAB 0x4000 /* bit 1 */
> +#define S390_IIEI_NO_GLOBAL_SCLAB 0x2000 /* bit 2 */
> +#define S390_IIEI_MORE_GLOBAL_SCLAB 0x1000 /* bit 3 */
> +#define S390_IIEI_FOUND_UNSIGNED_COMP 0x800 /* bit 4 */
> +#define S390_IIEI_MORE_SIGNED_COMP 0x400 /* bit 5 */
> +
> struct IplInfoBlockHeader {
> uint32_t len;
> uint8_t type;
> - uint8_t reserved1[11];
> + uint8_t reserved1[3];
> + uint16_t iiei;
> + uint8_t reserved2[6];
> };
> typedef struct IplInfoBlockHeader IplInfoBlockHeader;
>
> @@ -194,13 +204,28 @@ typedef struct IplSignatureCertificateList
> IplSignatureCertificateList;
> #define S390_IPL_DEV_COMP_FLAG_SC 0x80
> #define S390_IPL_DEV_COMP_FLAG_CSV 0x40
>
> +/* IPL Device Component Error Indicators */
> +#define S390_CEI_INVALID_SCLAB 0x80000000 /* bit 0 */
> +#define S390_CEI_INVALID_SCLAB_LEN 0x40000000 /* bit 1 */
> +#define S390_CEI_INVALID_SCLAB_FORMAT 0x20000000 /* bit 2 */
> +#define S390_CEI_UNMATCHED_SCLAB_LOAD_ADDR 0x10000000 /* bit 3 */
> +#define S390_CEI_UNMATCHED_SCLAB_LOAD_PSW 0x8000000 /* bit 4 */
> +#define S390_CEI_INVALID_LOAD_PSW 0x4000000 /* bit 5 */
> +#define S390_CEI_NUC_NOT_IN_GLOBAL_SCLAB 0x2000000 /* bit 6 */
> +#define S390_CEI_SCLAB_OLA_NOT_ONE 0x1000000 /* bit 7 */
> +#define S390_CEI_SC_NOT_IN_GLOBAL_SCLAB 0x800000 /* bit 8 */
> +#define S390_CEI_SCLAB_LOAD_ADDR_NOT_ZERO 0x400000 /* bit 9 */
> +#define S390_CEI_SCLAB_LOAD_PSW_NOT_ZERO 0x200000 /* bit 10 */
> +#define S390_CEI_INVALID_UNSIGNED_ADDR 0x100000 /* bit 11 */
> +
> struct IplDeviceComponentEntry {
> uint64_t addr;
> uint64_t len;
> uint8_t flags;
> uint8_t reserved1[5];
> uint16_t cert_index;
> - uint8_t reserved2[8];
> + uint32_t cei;
> + uint8_t reserved2[4];
> };
> typedef struct IplDeviceComponentEntry IplDeviceComponentEntry;
>
> diff --git a/pc-bios/s390-ccw/sclp.h b/pc-bios/s390-ccw/sclp.h
> index a8a41cd004..cae65b29b5 100644
> --- a/pc-bios/s390-ccw/sclp.h
> +++ b/pc-bios/s390-ccw/sclp.h
> @@ -52,6 +52,7 @@ typedef struct SCCBHeader {
> #define SCCB_DATA_LEN (SCCB_SIZE - sizeof(SCCBHeader))
> #define SCCB_FAC134_DIAG320_BIT 0x4
> #define SCCB_FAC_IPL_SIPL_BIT 0x4000
> +#define SCCB_FAC_IPL_SCLAF_BIT 0x1000
>
> typedef struct ReadInfo {
> SCCBHeader h;
> diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
> index af98881aa5..c92d8e2aff 100644
> --- a/pc-bios/s390-ccw/secure-ipl.c
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -180,6 +180,12 @@ bool secure_ipl_supported(void)
> return false;
> }
>
> + if (!sclp_is_fac_ipl_flag_on(SCCB_FAC_IPL_SCLAF_BIT)) {
> + puts("Secure IPL Code Loading Attributes Facility is not supported
> by"
> + " the hypervisor!");
> + return false;
> + }
> +
> return true;
> }
>
> @@ -210,6 +216,147 @@ static void check_comp_overlap(IplDeviceComponentList
> *comp_list,
> }
> }
>
> +static bool is_psw_valid(uint64_t psw, IplDeviceComponentEntry *comp)
> +{
> + uint32_t addr = psw & 0x7fffffff;
> +
> + /*
> + * PSW points within a signed binary code component
> + *
> + * Check addr falls within [comp->addr, comp->addr + comp->len - 2],
> + * ensuring at least 2 bytes (minimum instruction length) remain.
> + */
> + return intersects(addr, 1, comp->addr, comp->len - 1);
> +}
> +
> +void check_global_sclab(const SclaBlock *global_sclab,
> + IplDeviceComponentEntry *comp_entry,
> + IplDeviceComponentList *comp_list)
> +{
> + bool psw_valid = false;
> + bool global_psw_valid = false;
> + int signed_count = 0;
> + int unsigned_count = 0;
> + IplDeviceComponentEntry *comp;
> +
> + if (!global_sclab) {
> + comp_list->ipl_info_header.iiei |= S390_IIEI_NO_GLOBAL_SCLAB;
> + zipl_secure_error("Global SCLAB does not exist");
> + return;
> + }
> +
> + for_each_rb_entry(comp, comp_list) {
> + if (comp->flags & S390_IPL_DEV_COMP_FLAG_SC) {
> + psw_valid |= is_psw_valid(comp_entry->addr, comp);
> + global_psw_valid |= is_psw_valid(global_sclab->load_psw, comp);
> + signed_count += 1;
> + } else {
> + unsigned_count += 1;
> + }
> + }
> +
> + /* validate load PSW with PSW specified in the final entry */
> + zipl_secure_validate(psw_valid && global_psw_valid, &comp_entry->cei,
> + S390_CEI_INVALID_LOAD_PSW, "Invalid PSW");
> +
> + /* compare load PSW with the PSW specified in component */
> + zipl_secure_validate(global_sclab->load_psw == comp_entry->addr,
> + &comp_entry->cei, S390_CEI_UNMATCHED_SCLAB_LOAD_PSW,
> + "Load PSW does not match with PSW in component");
> +
> + /* Unsigned components are not allowed if NUC flag is set in the global
> SCLAB */
> + if ((global_sclab->flags & S390_SCLAB_NUC) && unsigned_count > 0) {
> + comp_list->ipl_info_header.iiei |= S390_IIEI_FOUND_UNSIGNED_COMP;
> + zipl_secure_error("Unsigned components are not allowed");
> + }
> +
> + /*
> + * Only one signed component is allowed if SC flag is set in the global
> SCLAB
> + * More than one component in the component table is not allowed
> + */
> + if ((global_sclab->flags & S390_SCLAB_SC) &&
> + (signed_count != 1 || unsigned_count != 0)) {
> + comp_list->ipl_info_header.iiei |= S390_IIEI_MORE_SIGNED_COMP;
> + zipl_secure_error("Only one signed component is allowed");
> + }
> +}
> +
> +static void check_sclab(SclaBlock **global_sclab,
> + IplDeviceComponentEntry *comp_entry,
> + IplInfoBlockHeader *comp_list_hdr)
> +{
> + SclabOriginLocator *sclab_locator;
> + SclaBlock *sclab;
> +
> + /* sclab locator is located at the last 8 bytes of the signed comp */
> + sclab_locator = (SclabOriginLocator *)(comp_entry->addr +
> + comp_entry->len - 8);
Need to verify that len >=8, otherwise who knows what gets read.
> +
> + /* return early if sclab does not exist */
> + zipl_secure_validate(magic_match(sclab_locator->magic, ZIPL_MAGIC),
> + &comp_entry->cei, S390_CEI_INVALID_SCLAB,
> + "Magic does not match. SCLAB does not exist");
> +
> + if (comp_entry->cei & S390_CEI_INVALID_SCLAB) {
> + return;
> + }
> +
> + zipl_secure_validate(sclab_locator->len >= S390_SCLAB_MIN_LEN,
> &comp_entry->cei,
> + S390_CEI_INVALID_SCLAB_LEN | S390_CEI_INVALID_SCLAB,
> + "Invalid SCLAB length");
> +
> + /* return early if sclab is invalid */
> + if (comp_entry->cei & S390_CEI_INVALID_SCLAB) {
> + return;
> + }
I get why these two conditions (missing/invalid SCLAB) cause us to stop
processing when in audit
mode...
> +
> + sclab = (SclaBlock *)(comp_entry->addr + comp_entry->len -
> + sclab_locator->len);
> +
> + zipl_secure_validate(sclab->format == 0, &comp_entry->cei,
> + S390_CEI_INVALID_SCLAB_FORMAT,
> + "Format-0 SCLAB is not being used");
...but why doesn't this one (SCLAB format)? Do the subsequent flags checks all
apply regardless of
format?
> +
> + if (!(sclab->flags & S390_SCLAB_OPSW)) {
> + /* OPSW = 0 - Load PSW field in SCLAB must contain zeros */
> + zipl_secure_validate(sclab->load_psw == 0, &comp_entry->cei,
> + S390_CEI_SCLAB_LOAD_PSW_NOT_ZERO,
> + "Load PSW is not zero when Override PSW bit is
> zero");
> + } else {
> + /* OPSW = 1 indicating global SCLAB */
> + if (*global_sclab) {
> + comp_list_hdr->iiei |= S390_IIEI_MORE_GLOBAL_SCLAB;
> + zipl_secure_error("More than one global SCLAB");
> + }
> + *global_sclab = sclab;
> +
> + /* override load address flag must set to one */
> + zipl_secure_validate(sclab->flags & S390_SCLAB_OLA, &comp_entry->cei,
> + S390_CEI_SCLAB_OLA_NOT_ONE,
> + "OLA flag is not set to one in the global
> SCLAB");
> + }
> +
> + if (!(sclab->flags & S390_SCLAB_OLA)) {
> + /* OLA = 0 - Load address field in SCLAB must contain zeros */
> + zipl_secure_validate(sclab->load_addr == 0, &comp_entry->cei,
> + S390_CEI_SCLAB_LOAD_ADDR_NOT_ZERO,
> + "Load Address is not zero when OLA flag is
> zero");
> + } else {
> + /* OLA = 1 - Load address field must match storage address of the
> component */
> + zipl_secure_validate(sclab->load_addr == comp_entry->addr,
> &comp_entry->cei,
> + S390_CEI_UNMATCHED_SCLAB_LOAD_ADDR,
> + "Load Address does not match with component
> load address");
> + }
> +
> + zipl_secure_validate(~sclab->flags & S390_SCLAB_NUC || sclab->flags &
> S390_SCLAB_OPSW,
> + &comp_entry->cei, S390_CEI_NUC_NOT_IN_GLOBAL_SCLAB,
> + "NUC bit is set, but not in the global SCLAB");
> +
> + zipl_secure_validate(~sclab->flags & S390_SCLAB_SC || sclab->flags &
> S390_SCLAB_OPSW,
> + &comp_entry->cei, S390_CEI_SC_NOT_IN_GLOBAL_SCLAB,
> + "SC bit is set, but not in the global SCLAB");
> +}
> +
> static int zipl_load_signature(ComponentEntry *entry, uint64_t sig)
> {
> if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {
> @@ -268,6 +415,8 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t
> *tmp_sec,
> uint8_t *tmp_buf;
> bool verified;
> bool signed_found = false;
> + bool sclab_found = false;
> + SclaBlock *global_sclab = NULL;
>
> if ((MAX_SIGNED_COMP * CERT_BUF_MAX_LEN) > (CERT_BUF_SIZE)) {
> panic("Not enough memory to store certificates");
> @@ -308,6 +457,10 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t
> *tmp_sec,
>
> /* no signature present (unsigned component) */
> if (!sig_entry.len) {
> + zipl_secure_validate(comp_entry.addr >=
> S390_UNSIGNED_MIN_ADDR,
> + &comp_entry.cei, S390_CEI_INVALID_UNSIGNED_ADDR,
> + "Load address for unsigned component is less
> than 0x2000");
> +
> comp_list_add(comp_list, comp_entry);
> break;
> }
> @@ -319,6 +472,9 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t
> *tmp_sec,
> comp_entry.flags = S390_IPL_DEV_COMP_FLAG_SC;
> signed_found = true;
>
> + check_sclab(&global_sclab, &comp_entry,
> &comp_list->ipl_info_header);
> + sclab_found |= !(comp_entry.cei & S390_CEI_INVALID_SCLAB);
> +
> cert_entry = (IplSignatureCertificateEntry) { 0 };
> verified = verify_signature(comp_entry, sig_entry,
> &cert_entry.len, &cert_table_idx);
> @@ -364,9 +520,17 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t
> *tmp_sec,
> }
> }
>
> - if (!signed_found) {
> - zipl_secure_error("Secure boot is on, but components are not
> signed");
> - }
> + zipl_secure_validate(signed_found, &comp_list->ipl_info_header.iiei,
> + S390_IIEI_NO_SIGNED_COMP,
> + "Secure boot is on, but components are not signed");
> +
> + zipl_secure_validate(sclab_found, &comp_list->ipl_info_header.iiei,
> + S390_IIEI_NO_SCLAB, "No recognizable SCLAB");
> +
> + comp_entry = (IplDeviceComponentEntry){ 0 };
> + comp_entry.addr = entry->compdat.load_psw;
> + check_global_sclab(global_sclab, &comp_entry, comp_list);
> + comp_list_add(comp_list, comp_entry);
Do we actually need to add an (empty) entry when global_sclab wasn't found?
>
> *entry_ptr = entry;
> free((void *)sig_entry.addr);
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> index 1b1287858b..1bacb4987a 100644
> --- a/pc-bios/s390-ccw/secure-ipl.h
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -27,6 +27,33 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t
> *tmp_sec,
> IplSignatureCertificateList *cert_list,
> uint8_t **tmp_cert_buf);
>
> +#define S390_SCLAB_OPSW 0x8000 /* override PSW flag */
> +#define S390_SCLAB_OLA 0x4000 /* override load address flag */
> +#define S390_SCLAB_NUC 0x2000 /* no unsigned components flag */
> +#define S390_SCLAB_SC 0x1000 /* single component flag */
> +
> +#define S390_SCLAB_MIN_LEN 32
> +#define S390_UNSIGNED_MIN_ADDR 0x2000
> +
> +/* Secure Code Loading Attributes Block */
> +struct SclaBlock {
> + uint8_t format;
> + uint8_t reserved1;
> + uint16_t flags;
> + uint8_t reserved2[4];
> + uint64_t load_psw;
> + uint64_t load_addr;
> + uint64_t reserved3[];
> +} __attribute__ ((packed));
> +typedef struct SclaBlock SclaBlock;
> +
> +struct SclabOriginLocator {
> + uint8_t reserved[2];
> + uint16_t len;
> + uint8_t magic[4];
> +} __attribute__ ((packed));
> +typedef struct SclabOriginLocator SclabOriginLocator;
> +
> static inline void zipl_secure_error(const char *message)
> {
> switch (boot_mode) {
> @@ -38,6 +65,30 @@ static inline void zipl_secure_error(const char *message)
> }
> }
>
> +static inline void zipl_secure_validate_u16(bool condition, uint16_t *flags,
> + uint16_t flag, const char
> *message)
> +{
> + if (!condition) {
> + *flags |= flag;
> + zipl_secure_error(message);
> + }
> +}
> +
> +static inline void zipl_secure_validate_u32(bool condition, uint32_t *flags,
> + uint32_t flag, const char
> *message)
> +{
> + if (!condition) {
> + *flags |= flag;
> + zipl_secure_error(message);
> + }
> +}
> +
> +#define zipl_secure_validate(condition, flags, flag, message) \
> + _Generic((flags), \
> + uint16_t * : zipl_secure_validate_u16, \
> + uint32_t * : zipl_secure_validate_u32 \
> + )(condition, flags, flag, message)
> +
> static inline uint64_t _diag320(void *data, unsigned long subcode)
> {
> register unsigned long addr asm("0") = (unsigned long)data;