Thanks for the review!
On 7/2/26 12:08 PM, Eric Farman wrote:
> 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(-)
>>
[...]
>> +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?
>
Continuing with the flag checks seems reasonable to me, since we're
still dealing with some form of valid SCLAB. Please let me know your
thoughts. Thanks!
>> +
>> + 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?
>
I think we should still add this entry even when global_sclab is not
found, since the comp_entry would still contain the PSW value from the
EXEC entry.
>>
>> *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;