On Thu, 2026-07-02 at 16:47 -0400, Zhuoying Cai wrote:
> 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!
Agreed. The flag checks apply regardless of format, which I overlooked
previously.
>
> > > +
> > > + 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.
Works for me. Thanks!
>
> > >
> > > *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;