public inbox for qemu-devel@nongnu.org
 help / color / mirror / Atom feed
* [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices
@ 2026-03-05 22:41 Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 01/30] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
                   ` (29 more replies)
  0 siblings, 30 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Documentation
- Replaced "userspace" with "guest code".

[PATCH v8 01/30] Add boot-certs to s390-ccw-virtio machine type
option
- Fixed typo and formatting issues in documentation.

[PATCH v8 04/30] hw/s390x/ipl: Create certificate store
- Fixed coding-style issues.
- Corrected the maximum certificate count check.
- Removed QEMU_BUILD_BUG_MSG from cert-store.h.
- Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v8 05/30] s390x/diag: Introduce DIAG 320 for Certificate
Store Facility
- Updated documentation.
- Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v8 06/30] s390x/diag: Refactor address validation check from
diag308_parm_check
- Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v8 07/30] s390x/diag: Implement DIAG 320 subcode 1
- Adjusted return code in handle_diag320_query_vcsi().
- Changed double-word boundary handling to use a specification
  interrupt rather than an address interrupt.

[PATCH v8 09/30] s390x/diag: Implement DIAG 320 subcode 2
- Fixed a build error caused by a missing include.
- Added a comment documenting VCE_INVALID_LEN usage.
- Enforced 4 KB alignment for the subcode 2 address.

[PATCH v8 12/30] s390x/diag: Implement DIAG 508 subcode 1 for
signature verification
- Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v8 13/30] s390x/ipl: Introduce IPL Information Report Block
(IIRB)
- Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v8 14/30] pc-bios/s390-ccw: Define memory for IPLB and convert
IPLB to pointers
- Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v8 16/30] s390x: Guest support for Secure-IPL Facility
- Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v8 19/30] pc-bios/s390-ccw: Add signature verification for
secure IPL in audit mode
- Corrected ROUND_UP usage and added a remark similar to the one in
  osdep.h.
- Merged sclp_is_diag320_on() and sclp_get_fac134().
- Declared vcssb_data as static.
- Ensured vcssb_data is double-word aligned as required by DIAG 320
  subcode 1.
- Reset vcssb->length on errors (e.g., set to 0).
- Defined vcb_data as a static array with 4 KB alignment, as required
  by DIAG 320 subcode 2.
- Ensured the boot process exits when required facilities are missing
  in secure/audit mode.

[PATCH v8 20/30] pc-bios/s390-ccw: Add signed component address
overlap checks
- Handled out-of-range component address range indices according to
  the active boot mode.

[PATCH v8 21/30] s390x: Guest support for Secure-IPL Code Loading
Attributes Facility (SCLAF)
- Fixed a documentation build issue.

[PATCH v8 22/30] pc-bios/s390-ccw: Add additional security checks for
secure boot
- Fixed coding-style issues.

[PATCH v8 24/30] hw/s390x/ipl: Set IPIB flags for secure IPL
- Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v8 25/30] pc-bios/s390-ccw: Handle true secure IPL mode
- Removed ZIPL_BOOT_MODE_INVALID case from zipl_run() and allowed the
  default case to handle it.

[PATCH v8 26/30] hw/s390x/ipl: Handle secure boot with multiple boot
devices
- Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v8 28/30] tests/functional/s390x: Add secure IPL functional
test
- Made the test executable (chmod a+x) to allow direct execution
  outside the Meson harness.
- Updated the test to operate on a copied asset using qemu-img -b.

------------------------------------------------------------------------

# Description

This patch series is an external requirement by Linux distribution
partners to verify secure IPL process. Additional secure IPL checks are
also included in this series to address security holes in the original
secure IPL design to prevent malicious actors to boot modified or
unsigned code despite secure IPL being enforced.

Secure IPL is enabled when the QEMU options for secure IPL are specified
in the command line.

During this process, additional security checks are performed to ensure
system integrity.

As components are loaded from disk, DIAG 508 subcode 2 performs
signature verification if a signature entry is identified. Upon
successful verification, DIAG 320 subcode 2 will request the
corresponding certificate from QEMU key store to the BIOS.

Secure IPL will continue until all the components are loaded if no error
occurs during True secure IPL mode or in Audit mode (see explanation below).

After that, an IPL Information report block (IIRB) is initialized
immediately following an IPL Parameter Information Block. The IIRB is
populated with information about the components, verification results
and certificate data.

Finally, the guest system proceeds to boot.

Only List-Directed-IPL contains the relevant zIPL data structures to
perform secure IPL. This patch series only adds support for the SCSI
scheme of virtio-blk/virtio-scsi devices. Secure IPL for other device
types will be considered as follow-up work at a later date.

** Note: "secure IPL" and "secure boot" are used interchangeably
throughout the design. **

# True Secure IPL Mode and Audit Mode

## True Secure IPL Mode

When secure IPL is enabled and certificates are provided, all the secure
IPL checks will performed. The boot process will abort if any error
occurs during the secure IPL checks.

## Audit Mode

When the secure IPL option is not selected and certificates are
provided, all the secure IPL checks will still be performed. However,
the boot process will continue if any errors occur, with messages logged
to the console during the secure IPL checks.

The audit mode is also considered as simulated secure IPL because it is
less pervasive, and allows the guest to boot regardless of the secure
checking results.

# How to Enable Secure IPL

## QEMU Build Notes

When building QEMU, enable the cryptographic libraries.

Run configure script in QEMU repository with either parameter:

	./configure … --enable-gnutls

## Create Certificates via certtool

Generate and use an RSA private key for signing.

	certtool --generate-privkey > key.pem

A self-signed certificate requires the organization name. Use the cert.info
template to pre-fill values and avoid interactive prompts from certtool.

	cat > cert.info <<EOF
	cn = "My Name"
	expiration_days = 36500
	cert_signing_key
	EOF

	certtool --generate-self-signed \
		 --load-privkey key.pem \
		 --template cert.info \
		 --hash=SHA256 \
		 --outfile cert.pem

It is recommended to store the certificate(s) in the /…/qemu/certs
directory for easy identification.

## Sign Kernel and Prepare zipl

All actions must be performed on a guest.

Copy the sign-file script (located in Linux source repository),
generated private key(s), and certificate(s) to guest's file system.

Sign guest image(s) and stage3 binary:

	./sign-file sha256 key.pem cert.pem /boot/vmlinuz-…

	./sign-file sha256 key.pem cert.pem /usr/lib/s390-tools/stage3.bin

Run zipl with secure boot enabled.

	zipl --secure 1 -V

Guest image(s) are now signed, stored on disk, and can be verified.

## New QEMU Command Options for Secure IPL

New parameters have been added to the s390-ccw-virtio machine type to
enable Secure IPL and provide certificates for signature verification.

This parameter enables or disables Secure IPL/boot. If not specified, it
defaults to off.

	qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on|off

This parameter specifies one or more paths to boot certificates, used
for signature verification. You can provide a single certificate file or
a directory.

	qemu-system-s390x -machine s390-ccw-virtio, \
                                   boot-certs.0.path=/.../qemu/certs, \
                                   boot-certs.1.path=/another/path/cert.pem

Example:
	qemu-system-s390x -machine s390-ccw-virtio,
	                           secure-boot=on, \
	                           boot-certs.0.path=/.../qemu/certs, \
                                   boot-certs.1.path=/another/path/cert.pem

Secure IPL command options overview:

If neither the -secure-boot nor the -boot-certs options are
specified, the guest will boot in normal mode, and no security checks
will be conducted.

If the -secure-boot option is not specified or is set to off, and the
-boot-certs option is provided, the guest will boot in audit mode.
In this mode, all security checks are performed; however, any
errors encountered will not interrupt the boot process.

If the -secure-boot option is set to on and the -boot-certs option is
provided, the guest will boot in true secure IPL mode. In this mode,
all security checks are performed, and any errors encountered will
terminate the boot process.
  - If the -boot-certs option is not provided in true secure IPL
    mode, the boot process will fail for the corresponding device.

## Constraints

- z16 or "qemu" CPU model

- certificates must be in X.509 PEM format

- only support for SCSI scheme of virtio-blk/virtio-scsi devices
    - The boot process will terminate if secure boot is enabled without
specifying a boot device.
    - If enabling secure boot with multiple boot devices, any
unsupported devices or non-eligible devices will cause the entire boot
process terminating early with an error logged to the console.

- attempting to perform secure IPL outside of these constraints will
result in a failure.

# DIAGNOSE 508 - IPL Extensions

Signature verification is performed during IPL via DIAG 508. Component
address, component length, signature address and signature length are
obtained in the BIOS and pass to DIAG 508 subcode 2 to perform signature
verification in QEMU. If verification succeeds, DIAG 508 subcode 2
(signature verification) will return the length and index of the
certificate in the QemuCertificateStore that was used for verification.

## Data Structures

Diag508SignatureVerificationBlock (SVB) — stores addresses and
lengths of the component and signature to be used for signature
verification. Upon verification, an index and the length of the
certificate used is stored.

------------------------------------------------------------------------

Changelog v7->v8

- Edited documentation

[PATCH v7 01/29] Add boot-certs to s390-ccw-virtio machine type
option
- Updated version number from 10.2 to 11.0

[PATCH v7 02/29] crypto/x509-utils: Refactor with GNUTLS fallback
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 03/29] crypto/x509-utils: Add helper functions for
certificate store
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 04/29] hw/s390x/ipl: Create certificate store
- Merged init_cert_x509() with init_cert()
- Simplified certificate path handling; removed redundant NULL
  checks in get_cert_paths()
- Added warnings for empty certificate directories and non-.pem
  files
- Renamed variables: VC_* → CERT_* and max_cert_size →
  largest_cert_size
- Made certificate store initialization independent of kernel/IPLB
  state
- Fixed memory leak
- Added comments explaining why DER data is not stored in
  S390IPLCertificate

[PATCH v7 05/29] s390x/diag: Introduce DIAG 320 for Certificate Store
Facility
- Simplified control flow in handle_diag_320()
- Removed the word "Provide" from description text

[PATCH v7 06/29] s390x/diag: Refactor address validation check from
diag308_parm_check()
- Added Reviewed-by: Collin Walling <walling@linux.ibm.com>
- Added Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>

[PATCH v7 07/29] s390x/diag: Implement DIAG 320 subcode 1
- Added upper-bound validation for vcssb->length
- Added Reviewed-by: Collin Walling <walling@linux.ibm.com>

[PATCH v7 08/29] crypto/x509-utils: Add helper functions for DIAG 320
subcode 2
- Simplified ECC curve validation logic
- Moved public-key algorithm detection to internal helpers
- Removed unused QCRYPTO_PK_ALGO enum
- Fixed commit message

[PATCH v7 09/29] s390x/diag: Implement DIAG 320 subcode 2
- Added sanity checks for key ID retrieval; mark certificates
  invalid if extraction fails
- Reworked VCE construction logic
- Pass S390IPLCertificate as const pointers instead of copying
  structs

[PATCH v7 10/29] s390x/diag: Introduce DIAG 508 for secure IPL
operations
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 11/29] crypto/x509-utils: Add helper functions for DIAG 508
subcode 1
- Fixed typos
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>
- Added Reviewed-by: Farhan Ali <alialifm@linux.ibm.com>

[PATCH v7 12/29] s390x/diag: Implement DIAG 508 subcode 1 for signature
verification
- Added comments on maximum component and signature lengths
- Added warning indicating failures due to oversize signature or
  component
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 13/29] pc-bios/s390-ccw: Introduce IPL Information Report
Block (IIRB)
- Updated documentation describing guest kernel usage of certificate
  data in the IIRB
- Simplified and renamed flags and type definitions
- Merged unused and reserved fields
- Moved IIRB definitions from pc-bios/s390-ccw/iplb.h to
  include/hw/s390x/ipl/qipl.h

[PATCH v7 14/29] pc-bios/s390-ccw: Define memory for IPLB and convert
IPLB to pointers
- Moved IplBlocks struct from pc-bios/s390-ccw/iplb.h to
  include/hw/s390x/ipl/qipl.h
- Moved attribute annotations to the ipl_data declaration in main.c

  * Note: checkpatch.pl flags the following:
          ERROR: externs should be avoided in .c files
          #88: FILE: pc-bios/s390-ccw/main.c:26:
          +IplBlocks ipl_data __attribute__((__aligned__(PAGE_SIZE)));

This appears to be a false positive, as IplBlocks is not declared as an
extern in a .c file, and checkpatch.pl reports the same warning for the
existing IplParameterBlock declaration in main.c even before this patch.

[PATCH v7 15/29] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 16/29] s390x: Guest support for Secure-IPL Facility
- Fixed reserved-field off-by-one error

[PATCH v7 17/29] pc-bios/s390-ccw: Refactor zipl_run()
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 19/29] pc-bios/s390-ccw: Add signature verification for
secure IPL in audit mode
- Updated warning message
- Simplified control flow and error handling in zipl_run()
- Removed UNSPECIFIED boot mode and defaulted to NORMAL
- Refactored zipl_run_secure()
  - Renamed variables and functions
- Adjusted index handling in verify_signature(), accounting for
  DIAG 320 when retrieving certificates

[PATCH v7 21/29] pc-bios/s390-ccw: Add additional security checks for
secure boot
- Renamed constants:
    S390_IPL_INFO_IIEI_* → S390_IIEI_*
    S390_IPL_COMPONENT_CEI_* → S390_CEI_*
- Improved readability by reducing function parameter counts
- Fixed check_sc() to allow only a single signed binary component
- Moved address-range/overlap checks to a separate patch

[PATCH v7 22/29] Add secure-boot to s390-ccw-virtio machine type option
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 23/29] hw/s390x/ipl: Set IPIB flags for secure IPL
- Fixed commit message

[PATCH v7 24/29] pc-bios/s390-ccw: Handle true secure IPL mode
- Updated documentation
- Fixed typo

[PATCH v7 25/29] pc-bios/s390-ccw: Handle secure boot with multiple
boot devices
- Handled support for multiple secure boot devices on the QEMU side

[PATCH v7 26/29] hw/s390x/ipl: Handle secure boot without a specified
boot device
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v7 27/29] tests/functional/s390x: Add secure IPL
- Cleaned up test code
- Switched from time.sleep() to wait_for_console_pattern()

Changelog v6->v7

Add functional test for secure IPL

Secure IPL documentation
- Merged command line into one line
- Limited line length to 80 columns

[PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
- Renamed BootCertificate to BootCertificates
- Added Acked-by: Markus Armbruster <armbru@redhat.com>

[PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback
- Edited commit message
- Added Acked-by: Daniel P. Berrangé <berrange@redhat.com>
        Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
        Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate
store
- Added Acked-by: Daniel P. Berrangé <berrange@redhat.com>
        Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
        Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v6 04/28] hw/s390x/ipl: Create certificate store
- Removed QEMU_PAKCED
- Inlined cert2buf wrapper function to reduce lines of code
- Reported error and handled exit in s390_ipl_create_cert_store()

[PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store
Facility
- Fixed typos
- Added Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v6 06/28] s390x/diag: Refactor address validation check from
diag308_parm_check
- Moved diag_parm_addr_valid() to target/s390x/diag.c
- Added Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1
- Added Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320
subcode 2
- Edited commit message
- Handled negative return value from gnutls_x509_crt_get_pk_algorithm()
in qcrypto_x509_get_pk_algorithm
- Checked boundary before using hash_alg in qcrypto_x509_get_cert_key_id()
- Rename qcrypto_x509_is_ecc_curve_p521() to
qcrypto_x509_check_ecc_curve_p521()

[PATCH v6 09/28] s390x/diag: Implement DIAG 320 subcode 2
- Removed redundant set vce->len
- Added check to make sure data doesn't exceed buffer bounds

[PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations
- Reworded document to avoid the term “KVM”
- Added Reviewed-by: Farhan Ali <alifm@linux.ibm.com>

[PATCH v6 11/28] crypto/x509-utils: Add helper functions for DIAG 508
subcode 1
- Fixed indentation
- Replaced g_new0() and memcpy() with g_memdup2()

[PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature
verification
- Edit commit message and documentation in handle_diag508_sig_verif()
- Changed diag_508_verify_sig function return type from int to bool
- Set upper limit for comp_len and sig_len to avoid malicious memory
allocation
- Fixed coding style - eliminate unnecessary variables

[PATCH v6 13/28] pc-bios/s390-ccw: Introduce IPL Information Report
Block (IIRB)
- Removed bios structs packing

[PATCH v6 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert
IPLB to pointers
- specify ipl_data attribute in the pc-bios/s390-ccw/main.c

[PATCH v6 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
- Moved the setting of iplb->len to when the DIAG308_IPIB_FLAGS_IPLIR is set

[PATCH v6 17/28] pc-bios/s390-ccw: Refactor zipl_run()
- Preserved return error value from zipl_run_normal()

[PATCH v6 18/28] pc-bios/s390-ccw: Rework zipl_load_segment function
- Fixed typoes
- Added Reviewed-by: Thomas Huth <thuth@redhat.com>

[PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure
IPL in audit mode
- Edited documentation
- Removed bios structs packing
- Be more specific in the error message and mention Passthrough (vfio)
CCW device does not support secure boot
- Allocated memory for vcb using max_single_vcb_len instead of
MAX_SECTOR_SIZE in request_certificate()
- Rename variables in zipl_run_secure()
cert_index → cert_entry_idx
comp_index → comp_entry_idx
cert_idx → cert_table_idx
- Refactored handle_certificate(): only handles certificate related
operations and moved the increment of cert_entry_idx and cert to
zipl_run_secure()

[PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for
secure boot
- Fixed coding style
- Added out of range warning in comp_addr_range_add()
- Fixed end address issue in is_psw_valid() and is_comp_overlap()
- Inlined comparison functions

[PATCH v6 22/28] Add secure-boot to s390-ccw-virtio machine type option
- Moved bool secure_boot next to other bool variables to avoid
additional padding

[PATCH v6 24/28] pc-bios/s390-ccw: Handle true secure IPL mode
- Edited documentation
- Used panic instead of IPL_assert() in zipl_secure_handle()

[PATCH v6 25/28] pc-bios/s390-ccw: Handle secure boot with multiple boot
devices
- Used a stack variable for QemuIplParameters instead of a malloc in
check_secure_boot_support()

Changelog v5->v6

Add boot-certs to s390-ccw-virtio machine type option
- Renamed struct BootCertPath to BootCertificate
- Defined DummyBootCertificates struct for QMP to use
BootCertificateList internally

crypto/x509-utils
- Used gnutls_x509_crt_export2() in qcrypto_x509_convert_cert_der()
- Allocated buffers with g_new for results in
qcrypto_x509_convert_cert_der() and qcrypto_pkcs7_convert_sig_pem()
- Added check for qcrypto_to_gnutls_hash_alg_map bounds in
qcrypto_x509_get_cert_key_id()
- Added qcrypto_x509_is_ecc_curve_p521() function to determine if an ECC
public key algorithm uses P521 curve
- Fixed typo

hw/s390x/ipl: Create certificate store
- Made any configuration errors fatal when loading certificates into the
cert store
- Removed key_id_size and hash_size from S390IPLCertificate and defined
them as constants
- Do not reinitialize the cert store if it already exists
- Handled memory cleanup

s390x/diag: Implement DIAG 320 subcode 1
- Added QEMU_BUILD_BUG_MSG

s390x/diag: Implement DIAG 320 subcode 2
- Refactored build_vce_header() and created a new function for getting
key type
- Added QEMU_BUILD_BUG_MSG

s390x/diag: Implement DIAG 508 subcode 1 for signature verification
- Updated Diag508SigVerifBlock

hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
- Override IPLB length only when DIAG308_IPIB_FLAGS_IPLIR flag is set
- Dropped hw/s390x/ipl: Set iplb->len to maximum length of IPL Parameter
Block patch

pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
- Added validation for VCB and VCE before retrieving information in
request_certificate()
- Replaced uint64_t *cert with uint8_t *cert in zipl_run_secure()

Changelog v4->v5

- Segmented documentation by introducing them alongside the patches that
add the relevant functionality
- Removed hash-type restriction from S390IPLCertificateStore

Add boot-certs to s390-ccw-virtio machine type option
- Added boot-certs property to the s390-ccw-virtio machine type
    - Changed to use an array property for passing certificate instead
      of colo-delimited strings.
    e.g. boot-certs.0.path=/path/to/dir,
         boot-certs.1.path=/to/other/dir,
         boot-certs.2.path=/some/...

crypto/x509-utils
- Refactored fallback handling into a separated commit
- Removed QCryptoKeyidFlags and qcrypto_x509_get_keyid_len() function
- Removed qcrypto_x509_get_cert_version() and
qcrypto_x509_get_signature_algorithm()
- Passed QCryptoHashAlgo to qcrypto_x509_get_cert_key_id() instead of
QCryptoKeyidFlags
- Improved memory cleanup

hw/s390x/ipl: Create certificate store
- Fixed error handling and memory management
- Added condition to only accept files with ".pem" extension

s390x: Guest support for Certificate Store Facility (CS)
- Renamed "S390_FEAT_DIAG_320" to "S390_FEAT_CERT_STORE"
- Merged this patch with s390x/diag: Introduce DIAG 320 for certificate
store facility patch

s390x/diag: Introduce DIAG 320 for certificate store facility
- Added bit zero to indicate subcode 0 is supported
- Set response code to 0x0102 when the subcode is not supported
- Added check to ensure subcode fits within one byte

s390x/diag: Implement DIAG 320 subcode 1
- Refactored and moved implementation to a helper function
- Added check to ensure VCSSB length is set to a minimum of 128 bytes
from the userspace
- Removed max_vce_len field from the VCSSB data structure

s390x/diag: Implement DIAG 320 subcode 2
- Refactored and split functions for extracting certificate information
- Set VCE length to 72 if certificate is invalid
- Returned VCE even if the certificate is invalid

pc-bios/s390-ccw: Refactor zipl_run()
- Refactor zipl_run_normal() to reduce duplicate code

pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
- Moved secure-boot related functions into secure-ipl.c/h files
- Rename zipl_handle_sig_entry() to zipl_load_signature()
- Refactored zipl_run_secure() to use switch cases for different entry
types instead of a while loop
- Renamed ZiplBootMode enumerations with "ZIPL_BOOT_MODE" prefix
- Added meaningful file header to secure-ipl.c
- Renamed zipl_secure_print() to zipl_secure_handle()

Changelog v3->v4:

cyrpto/x590-utils
- Updated patch descriptions to summarize what each patch introduces.
- Restricted accepted certificate format on the QEMU command line to PEM
only.
    - Added internal conversion routines to handle PEM-to-DER and
DER-to-PEM as needed.
- Renamed crypto functions using the qcrypto_x509_ prefix.
- Replaced QAPI enums with plain C enums.
- Improved GNUTLS error propagation throughout the crypto API.
- Standardized error returns to -1 (instead of using errno values) for
consistency.
- Removed unused helpers: qcrypto_check_x509_cert_fmt() and
qcrypto_get_x509_hash_len().
- Added bounds checking before converting GNUTLS enums to internal
crypto enums.
- Modified qcrypto_get_x509_cert_key_id() to dynamically allocate the
result.
- Fixed a typo in the crypto API documentation.

hw/s390x/ipl: Create certificate store
- Removed max_size check when loading certificate.
- Removed check_path_type() and use the stat() & S_ISXXX checks inline
where needed.
- Initialized all g_autofree variables.
- Refactored get_cert_paths()
    - Used g_auto(GStrv) and g_autoptr(GPtrArray) for automatic cleanup.
    - Renamed misleading variables and dropped unnecessary intermediates.
    - Replaced check_path_type() with stat(), and treated failures as fatal.
    - Treated empty path components as fatal configuration errors.
    - Used GError with g_dir_open() and handled errors properly.
    - Removed redundant casts and manual cleanup.
- Treated providing 65 or more certificates as a fatal error.
- Modified S390IPLCertificate
    - Changed raw from char * to uint8_t * for API consistency.
    - Added cert_der field to calculate certificate data buffer size for
DIAG 320 and format conversion.

s390x/diag
- Handled error propagation from crypto API correctly.
- Added g_autofree to variables to ensure proper memory cleanup.
- Initialized all g_autofree variables.

pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
- Allocated certificate buffer based on calculated size instead of
static MAX_CERT_SIZE.

pc-bios/s390-ccw: Add additional security checks for secure boot
- Refactored to improve readability.
- Handled SCLAB Single Component/No Unsigned Component flags.

docs: Add secure IPL documentation
- Updated documentation.
- Illustrated with gnutls ‘certtool’ instead of ‘openssl’ for
consistency with other certificate creation docs.

- Updated cover letter with corresponding changes.

Changelog v2->v3:

- Fixed typos in patches
- Edited and corrected patch description
- Enabled secure IPL feature in TCG
- Split crypto subsystem changes from s390x subsystem changes
    - Added API documentation for each helper function
    - Removed qcrypto_get_x509_cert_fmt()
    - Reworked qcrypto_check_x509_cert_fmt() to return 0 on success and
negative error code on errors
    - Rephrased error messages when GNUTLS is not enabled
    - Changed dev cycle reference from 9.2 to 10.1

hw/s390x/ipl: Create certificate store
- Passed *cert_buf to g_file_get_contents() directly in cert2buf()
- Returned NULL for early returns
- Added check to only allow maximum of 64 certificates in the certificate
store

s390x: Guest support for Certificate Store Facility (CS)
- Renamed “diag320” to “cstore”

s390x/diag: Implement DIAG 320 subcode 1
- Removed QEMU_PACKED from VCStorageSizeBlock
- Removed unnecessary error check from subcode 1 implementation

s390x/diag: Implement DIAG 320 subcode 2
- Removed QEMU_PACKED from VCBlock and VCEngtry
- Reworked diag_320_is_cert_valid() to return 0 on success and negative
error code on errors
- Set *key_id_data and *hash_data to NULL after g_free()
- Moved DIAG_320_SUBC_STORE_VC case block implementation to a separate
function

s390x/diag: Implement DIAG 508 subcode 1 for signature verification
- Added a reserved field to Diag508CertificateStoreInfo
- Removed QEMU_PACKED from Diag508CertificateStoreInfo and
Diag508SignatureVerificationBlock
- Applied g_autofree to svb, svb_comp and svb_sig varaibles
- Moved DIAG_508_SUBC_SIG_VERIF case block implementation to a separate
function

pc-bios/s390-ccw: Refactor zipl_load_segment function
- Removed casting when calculating comp_len

pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
- Used malloc() to allocate certificate buffer instead of statically
allocating
- Reworked zipl_secure_print()

s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
- Added check to make sure SIPL is enabled when SCLAF is enabled

docs: Add secure IPL documentation
- Split document into two parts:
    - docs/specs/s390x-secure-ipl.rst (developer reference)
    - docs/system/s390x/secure-ipl.rst (user guide)

Changelog v1->v2:

- Fixed typos in patches
- Edited cover letter
- Added secure IPL documentation

QEMU Command-Line Interface:
- Moved boot-certificates under the machine-type option for s390x-virtio-ccw
- Moved secure-boot under the machine-type option for s390x-virtio-ccw

hw/s390x/ipl: Create Certificate Store
- Defined internal GNUTLS-related APIs
- Added check to only accept certificates using SHA-256 hashing
- Recalculated data_buf_size to ensure word alignment
- Cleaned up memory allocation
- Refactored functions for clarity

s390x: Guest Support for Certificate Store Facility (CS)
- Updated patch description to clarify:
    - Why Secure IPL is not available with Secure Execution
    - Why this feature requires S390_FEAT_EXTENDED_LENGTH_SCCB
-Restricted features to z16 due to additional layers requiring z16

s390x/diag: Implement DIAG 320 Subcode 1
- Renamed VerificationCertificate prefix to VC
- Byte-swap written values for endianness correctness

s390x/diag: Implement DIAG 320 Subcode 2
- Edited commit message for clarity
- Defined internal GNUTLS-related APIs
- Renamed data structure variables
- Ensured length fields in VCE are word-aligned
- Handled the VC index 0 case
- General refactoring

s390x/diag: Implement DIAG 508 Subcode 2 for Signature Verification
- Defined subcode from 2 to 1
- Removed unused error codes
- Defined internal GNUTLS-related APIs
- Byte-swap read values

hw/s390x/ipl: Add IPIB Flags to IPL Parameter Block
- Moved DIAG308 flags to a new header file

s390x: Guest Support for Secure-IPL Facility
- Renamed SCLP variable from cbl to fac_ipl

pc-bios/s390-ccw: Add Signature Verification for Secure Boot (Audit Mode)
- Moved Secure IPL-related functions to pc-bios/s390-ccw/secure-ip.c|h
- Refactored code for clarity

Collin L. Walling (2):
  s390x/diag: Introduce DIAG 508 for secure IPL operations
  s390x/diag: Implement DIAG 508 subcode 1 for signature verification

Zhuoying Cai (28):
  Add boot-certs to s390-ccw-virtio machine type option
  crypto/x509-utils: Refactor with GNUTLS fallback
  crypto/x509-utils: Add helper functions for certificate store
  hw/s390x/ipl: Create certificate store
  s390x/diag: Introduce DIAG 320 for Certificate Store Facility
  s390x/diag: Refactor address validation check from diag308_parm_check
  s390x/diag: Implement DIAG 320 subcode 1
  crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
  s390x/diag: Implement DIAG 320 subcode 2
  crypto/x509-utils: Add helper functions for DIAG 508 subcode 1
  s390x/ipl: Introduce IPL Information Report Block (IIRB)
  pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
  hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
  s390x: Guest support for Secure-IPL Facility
  pc-bios/s390-ccw: Refactor zipl_run()
  pc-bios/s390-ccw: Rework zipl_load_segment function
  pc-bios/s390-ccw: Add signature verification for secure IPL in audit
    mode
  pc-bios/s390-ccw: Add signed component address overlap checks
  s390x: Guest support for Secure-IPL Code Loading Attributes Facility
    (SCLAF)
  pc-bios/s390-ccw: Add additional security checks for secure boot
  Add secure-boot to s390-ccw-virtio machine type option
  hw/s390x/ipl: Set IPIB flags for secure IPL
  pc-bios/s390-ccw: Handle true secure IPL mode
  hw/s390x/ipl: Handle secure boot with multiple boot devices
  hw/s390x/ipl: Handle secure boot without specifying a boot device
  tests/functional/s390x: Add secure IPL functional test
  docs/specs: Add secure IPL documentation
  docs/system/s390x: Add secure IPL documentation

 crypto/meson.build                        |   2 +-
 crypto/x509-utils.c                       | 409 ++++++++++++
 docs/specs/index.rst                      |   1 +
 docs/specs/s390x-secure-ipl.rst           | 185 ++++++
 docs/system/s390x/secure-ipl.rst          | 177 ++++++
 docs/system/target-s390x.rst              |   1 +
 hw/s390x/cert-store.c                     | 221 +++++++
 hw/s390x/cert-store.h                     |  38 ++
 hw/s390x/ipl.c                            |  87 +++
 hw/s390x/ipl.h                            |  21 +-
 hw/s390x/meson.build                      |   1 +
 hw/s390x/s390-virtio-ccw.c                |  52 ++
 hw/s390x/sclp.c                           |   2 +
 include/crypto/x509-utils.h               | 113 ++++
 include/hw/s390x/ipl/diag308.h            |  34 +
 include/hw/s390x/ipl/diag320.h            |  97 +++
 include/hw/s390x/ipl/diag508.h            |  45 ++
 include/hw/s390x/ipl/qipl.h               |  97 ++-
 include/hw/s390x/s390-virtio-ccw.h        |   3 +
 include/hw/s390x/sclp.h                   |   4 +-
 pc-bios/s390-ccw/Makefile                 |   3 +-
 pc-bios/s390-ccw/bootmap.c                | 100 ++-
 pc-bios/s390-ccw/bootmap.h                |  11 +
 pc-bios/s390-ccw/iplb.h                   |   5 +-
 pc-bios/s390-ccw/jump2ipl.c               |   6 +-
 pc-bios/s390-ccw/main.c                   |  40 +-
 pc-bios/s390-ccw/netmain.c                |   8 +-
 pc-bios/s390-ccw/s390-ccw.h               |  30 +
 pc-bios/s390-ccw/sclp.c                   |  46 ++
 pc-bios/s390-ccw/sclp.h                   |   7 +
 pc-bios/s390-ccw/secure-ipl.c             | 723 ++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h             | 153 +++++
 qapi/machine-s390x.json                   |  23 +
 qapi/pragma.json                          |   1 +
 qemu-options.hx                           |  10 +-
 target/s390x/cpu_features.c               |   7 +
 target/s390x/cpu_features.h               |   1 +
 target/s390x/cpu_features_def.h.inc       |   5 +
 target/s390x/cpu_models.c                 |   7 +
 target/s390x/diag.c                       | 590 +++++++++++++++++-
 target/s390x/gen-features.c               |   7 +
 target/s390x/kvm/kvm.c                    |  34 +
 target/s390x/s390x-internal.h             |   4 +
 target/s390x/tcg/misc_helper.c            |  14 +
 tests/functional/s390x/meson.build        |   2 +
 tests/functional/s390x/test_secure_ipl.py | 148 +++++
 46 files changed, 3506 insertions(+), 69 deletions(-)
 create mode 100644 docs/specs/s390x-secure-ipl.rst
 create mode 100644 docs/system/s390x/secure-ipl.rst
 create mode 100644 hw/s390x/cert-store.c
 create mode 100644 hw/s390x/cert-store.h
 create mode 100644 include/hw/s390x/ipl/diag308.h
 create mode 100644 include/hw/s390x/ipl/diag320.h
 create mode 100644 include/hw/s390x/ipl/diag508.h
 create mode 100644 pc-bios/s390-ccw/secure-ipl.c
 create mode 100644 pc-bios/s390-ccw/secure-ipl.h
 create mode 100755 tests/functional/s390x/test_secure_ipl.py

-- 
2.53.0



^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH v9 01/30] Add boot-certs to s390-ccw-virtio machine type option
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 02/30] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
                   ` (28 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
machine. This allows users to specify one or more certificate file paths
or directories to be used during secure boot.

Each entry is specified using the syntax:
	boot-certs.<index>.path=/path/to/cert.pem

Multiple paths can be specify using array properties:
	boot-certs.0.path=/path/to/cert.pem,
	boot-certs.1.path=/path/to/cert-dir,
	boot-certs.2.path=/path/to/another-dir...

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Acked-by: Markus Armbruster <armbru@redhat.com>
---
 docs/system/s390x/secure-ipl.rst   | 20 ++++++++++++++++++++
 docs/system/target-s390x.rst       |  1 +
 hw/s390x/s390-virtio-ccw.c         | 30 ++++++++++++++++++++++++++++++
 include/hw/s390x/s390-virtio-ccw.h |  2 ++
 qapi/machine-s390x.json            | 23 +++++++++++++++++++++++
 qapi/pragma.json                   |  1 +
 qemu-options.hx                    |  6 +++++-
 7 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100644 docs/system/s390x/secure-ipl.rst

diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
new file mode 100644
index 0000000000..0a02f171b4
--- /dev/null
+++ b/docs/system/s390x/secure-ipl.rst
@@ -0,0 +1,20 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Secure IPL Command Line Options
+===============================
+
+The s390-ccw-virtio machine type supports secure IPL. These parameters allow
+users to provide certificates and enable secure IPL directly via the command
+line.
+
+Providing Certificates
+----------------------
+
+The certificate store can be populated by supplying a list of X.509 certificate
+file paths or directories containing certificate files on the command-line:
+
+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 ...
diff --git a/docs/system/target-s390x.rst b/docs/system/target-s390x.rst
index 94c981e732..8938a13d10 100644
--- a/docs/system/target-s390x.rst
+++ b/docs/system/target-s390x.rst
@@ -35,3 +35,4 @@ Architectural features
    s390x/bootdevices
    s390x/protvirt
    s390x/cpu-topology
+   s390x/secure-ipl
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index 3ef009463d..a6f0fc4e00 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -44,6 +44,7 @@
 #include "target/s390x/kvm/pv.h"
 #include "migration/blocker.h"
 #include "qapi/visitor.h"
+#include "qapi/qapi-visit-machine-s390x.h"
 #include "hw/s390x/cpu-topology.h"
 #include "kvm/kvm_s390x.h"
 #include "hw/virtio/virtio-md-pci.h"
@@ -788,6 +789,30 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
     g_free(val);
 }
 
+static void machine_get_boot_certs(Object *obj, Visitor *v,
+                                   const char *name, void *opaque,
+                                   Error **errp)
+{
+    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+    BootCertificatesList **certs = &ms->boot_certs;
+
+    visit_type_BootCertificatesList(v, name, certs, errp);
+}
+
+static void machine_set_boot_certs(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+    BootCertificatesList *cert_list = NULL;
+
+    visit_type_BootCertificatesList(v, name, &cert_list, errp);
+    if (!cert_list) {
+        return;
+    }
+
+    ms->boot_certs = cert_list;
+}
+
 static void ccw_machine_class_init(ObjectClass *oc, const void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
@@ -841,6 +866,11 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
             "Up to 8 chars in set of [A-Za-z0-9. ] (lower case chars converted"
             " to upper case) to pass to machine loader, boot manager,"
             " and guest kernel");
+
+    object_class_property_add(oc, "boot-certs", "BootCertificatesList",
+                              machine_get_boot_certs, machine_set_boot_certs, NULL, NULL);
+    object_class_property_set_description(oc, "boot-certs",
+            "provide paths to a directory and/or a certificate file for secure boot");
 }
 
 static inline void s390_machine_initfn(Object *obj)
diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
index f1f06119d6..5ad1ea2f24 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -14,6 +14,7 @@
 #include "hw/core/boards.h"
 #include "qom/object.h"
 #include "hw/s390x/sclp.h"
+#include "qapi/qapi-types-machine-s390x.h"
 
 #define TYPE_S390_CCW_MACHINE               "s390-ccw-machine"
 
@@ -31,6 +32,7 @@ struct S390CcwMachineState {
     uint8_t loadparm[8];
     uint64_t memory_limit;
     uint64_t max_pagesize;
+    BootCertificatesList *boot_certs;
 
     SCLPDevice *sclp;
 };
diff --git a/qapi/machine-s390x.json b/qapi/machine-s390x.json
index ea430e1b88..53936c2554 100644
--- a/qapi/machine-s390x.json
+++ b/qapi/machine-s390x.json
@@ -140,3 +140,26 @@
 { 'event': 'SCLP_CPI_INFO_AVAILABLE',
   'features': [ 'unstable' ]
 }
+
+##
+# @BootCertificates:
+#
+# Boot certificates for secure IPL.
+#
+# @path: path to an X.509 certificate file or a directory containing
+#      certificate files.
+#
+# Since: 11.0
+##
+{ 'struct': 'BootCertificates',
+  'data': {'path': 'str'} }
+
+##
+# @DummyBootCertificates:
+#
+# Not used by QMP; hack to let us use BootCertificatesList internally.
+#
+# Since: 11.0
+##
+{ 'struct': 'DummyBootCertificates',
+  'data': {'unused-boot-certs': ['BootCertificates'] } }
diff --git a/qapi/pragma.json b/qapi/pragma.json
index 193bc39059..aad270402f 100644
--- a/qapi/pragma.json
+++ b/qapi/pragma.json
@@ -49,6 +49,7 @@
         'DisplayProtocol',
         'DriveBackupWrapper',
         'DummyBlockCoreForceArrays',
+        'DummyBootCertificates',
         'DummyForceArrays',
         'DummyVirtioForceArrays',
         'HotKeyMod',
diff --git a/qemu-options.hx b/qemu-options.hx
index 0da2b4d034..8873083792 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -45,7 +45,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
     "                memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n"
     "                cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n"
     "                sgx-epc.0.memdev=memid,sgx-epc.0.node=numaid\n"
-    "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n",
+    "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
+    "                boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file provides paths to a directory and/or a certificate file\n",
     QEMU_ARCH_ALL)
 SRST
 ``-machine [type=]name[,prop=value[,...]]``
@@ -209,6 +210,9 @@ SRST
         ::
 
             -machine smp-cache.0.cache=l1d,smp-cache.0.topology=core,smp-cache.1.cache=l1i,smp-cache.1.topology=core
+
+    ``boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file``
+        Provide paths to a directory and/or a certificate file on the host [s390x only].
 ERST
 
 DEF("M", HAS_ARG, QEMU_OPTION_M,
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 02/30] crypto/x509-utils: Refactor with GNUTLS fallback
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 01/30] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 03/30] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
                   ` (27 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Always compile x509-utils.c and add a fallback when GNUTLS is
unavailable.

These functions will be needed in the s390x code regardless of whether
GNUTLS is available.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 crypto/meson.build  |  2 +-
 crypto/x509-utils.c | 16 ++++++++++++++++
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/crypto/meson.build b/crypto/meson.build
index b51597a879..fda85543de 100644
--- a/crypto/meson.build
+++ b/crypto/meson.build
@@ -22,12 +22,12 @@ crypto_ss.add(files(
   'tlscredsx509.c',
   'tlssession.c',
   'rsakey.c',
+  'x509-utils.c',
 ))
 
 if gnutls.found()
   crypto_ss.add(files(
     'tlscredsbox.c',
-    'x509-utils.c',
   ))
 endif
 
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 39bb6d4d8c..6176a88653 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -11,6 +11,8 @@
 #include "qemu/osdep.h"
 #include "qapi/error.h"
 #include "crypto/x509-utils.h"
+
+#ifdef CONFIG_GNUTLS
 #include <gnutls/gnutls.h>
 #include <gnutls/crypto.h>
 #include <gnutls/x509.h>
@@ -78,3 +80,17 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
     gnutls_x509_crt_deinit(crt);
     return ret;
 }
+
+#else /* ! CONFIG_GNUTLS */
+
+int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
+                                      QCryptoHashAlgo hash,
+                                      uint8_t *result,
+                                      size_t *resultlen,
+                                      Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get fingerprint");
+    return -1;
+}
+
+#endif /* ! CONFIG_GNUTLS */
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 03/30] crypto/x509-utils: Add helper functions for certificate store
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 01/30] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 02/30] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 04/30] hw/s390x/ipl: Create " Zhuoying Cai
                   ` (26 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Introduce new helper functions for x509 certificate, which will be used
by the certificate store:

qcrypto_x509_convert_cert_der() - converts a certificate from PEM to DER format

These functions provide support for certificate format conversion.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 crypto/x509-utils.c         | 49 +++++++++++++++++++++++++++++++++++++
 include/crypto/x509-utils.h | 21 ++++++++++++++++
 2 files changed, 70 insertions(+)

diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 6176a88653..2696d48155 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -81,6 +81,46 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
     return ret;
 }
 
+int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
+                                  uint8_t **result, size_t *resultlen,
+                                  Error **errp)
+{
+    int ret = -1;
+    int rc;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    gnutls_datum_t datum_der = {.data = NULL, .size = 0};
+
+    rc = gnutls_x509_crt_init(&crt);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+        return ret;
+    }
+
+    rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &datum_der);
+    if (rc != 0) {
+        error_setg(errp, "Failed to convert certificate to DER format: %s",
+                   gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    *resultlen = datum_der.size;
+    *result = g_memdup2(datum_der.data, datum_der.size);
+
+    ret = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    gnutls_free(datum_der.data);
+    return ret;
+}
+
 #else /* ! CONFIG_GNUTLS */
 
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
@@ -93,4 +133,13 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
     return -1;
 }
 
+int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
+                                  uint8_t **result,
+                                  size_t *resultlen,
+                                  Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to export X.509 certificate");
+    return -1;
+}
+
 #endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 1e99661a71..91ae79fb03 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -19,4 +19,25 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
                                       size_t *resultlen,
                                       Error **errp);
 
+/**
+ * qcrypto_x509_convert_cert_der
+ * @cert: pointer to the raw certificate data in PEM format
+ * @size: size of the certificate
+ * @result: output location for the allocated buffer for the certificate
+ *          in DER format
+ *          (the function allocates memory which must be freed by the caller)
+ * @resultlen: pointer to the size of the buffer (will be updated with the
+ *             actual size of the DER-encoded certificate)
+ * @errp: error pointer
+ *
+ * Convert the given @cert from PEM to DER format.
+ *
+ * Returns: 0 on success,
+ *         -1 on error.
+ */
+int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
+                                  uint8_t **result,
+                                  size_t *resultlen,
+                                  Error **errp);
+
 #endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 04/30] hw/s390x/ipl: Create certificate store
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (2 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 03/30] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 05/30] s390x/diag: Introduce DIAG 320 for Certificate Store Facility Zhuoying Cai
                   ` (25 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Create a certificate store for boot certificates used for secure IPL.

Load certificates from the `boot-certs` parameter of s390-ccw-virtio
machine type option into the cert store.

Currently, only X.509 certificates in PEM format are supported, as the
QEMU command line accepts certificates in PEM format only.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Farhan Ali<alifm@linux.ibm.com>
---
 docs/specs/index.rst            |   1 +
 docs/specs/s390x-secure-ipl.rst |  16 +++
 hw/s390x/cert-store.c           | 221 ++++++++++++++++++++++++++++++++
 hw/s390x/cert-store.h           |  39 ++++++
 hw/s390x/ipl.c                  |  10 ++
 hw/s390x/ipl.h                  |   3 +
 hw/s390x/meson.build            |   1 +
 include/hw/s390x/ipl/qipl.h     |   2 +
 8 files changed, 293 insertions(+)
 create mode 100644 docs/specs/s390x-secure-ipl.rst
 create mode 100644 hw/s390x/cert-store.c
 create mode 100644 hw/s390x/cert-store.h

diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index b7909a108a..76d439782c 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -40,3 +40,4 @@ guest hardware that is specific to QEMU.
    riscv-aia
    aspeed-intc
    iommu-testdev
+   s390x-secure-ipl
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
new file mode 100644
index 0000000000..7ddac98a37
--- /dev/null
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -0,0 +1,16 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+s390 Certificate Store and Functions
+====================================
+
+s390 Certificate Store
+----------------------
+
+A certificate store is implemented for s390-ccw guests to retain within
+memory all certificates provided by the user via the command-line, which
+are expected to be stored somewhere on the host's file system. The store
+will keep track of the number of certificates, their respective size,
+and a summation of the sizes.
+
+Note: A maximum of 64 certificates are allowed to be stored in the certificate
+store.
diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
new file mode 100644
index 0000000000..a4f15627e9
--- /dev/null
+++ b/hw/s390x/cert-store.c
@@ -0,0 +1,221 @@
+/*
+ * S390 certificate store implementation
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "cert-store.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "hw/s390x/ebcdic.h"
+#include "hw/s390x/s390-virtio-ccw.h"
+#include "qemu/cutils.h"
+#include "crypto/x509-utils.h"
+#include "qapi/qapi-types-machine-s390x.h"
+
+static BootCertificatesList *s390_get_boot_certs(void)
+{
+    return S390_CCW_MACHINE(qdev_get_machine())->boot_certs;
+}
+
+static S390IPLCertificate *init_cert(char *path, Error **errp)
+{
+    int rc;
+    char *buf;
+    size_t size;
+    size_t der_len;
+    char name[CERT_NAME_MAX_LEN];
+    g_autofree gchar *filename = NULL;
+    S390IPLCertificate *cert = NULL;
+    g_autofree uint8_t *cert_der = NULL;
+    Error *local_err = NULL;
+
+    filename = g_path_get_basename(path);
+
+    if (!g_file_get_contents(path, &buf, &size, NULL)) {
+        error_setg(errp, "Failed to load certificate: %s", path);
+        return NULL;
+    }
+
+    rc = qcrypto_x509_convert_cert_der((uint8_t *)buf, size,
+                                       &cert_der, &der_len, &local_err);
+    if (rc != 0) {
+        error_propagate_prepend(errp, local_err,
+                                "Failed to initialize certificate: %s: ", path);
+        g_free(buf);
+        return NULL;
+    }
+
+    cert = g_new0(S390IPLCertificate, 1);
+    cert->size = size;
+    /*
+     * Store DER length only - reused for size calculation.
+     * cert_der is discarded because DER certificate data will be used once
+     * and can be regenerated from cert->raw.
+     */
+    cert->der_size = der_len;
+    /* store raw pointer - ownership transfers to cert */
+    cert->raw = (uint8_t *)buf;
+
+    /*
+     * Left justified certificate name with padding on the right with blanks.
+     * Convert certificate name to EBCDIC.
+     */
+    strpadcpy(name, CERT_NAME_MAX_LEN, filename, ' ');
+    ebcdic_put(cert->name, name, CERT_NAME_MAX_LEN);
+
+    return cert;
+}
+
+static void update_cert_store(S390IPLCertificateStore *cert_store,
+                              S390IPLCertificate *cert)
+{
+    size_t data_buf_size;
+    size_t keyid_buf_size;
+    size_t hash_buf_size;
+    size_t cert_buf_size;
+
+    /* length field is word aligned for later DIAG use */
+    keyid_buf_size = ROUND_UP(CERT_KEY_ID_LEN, 4);
+    hash_buf_size = ROUND_UP(CERT_HASH_LEN, 4);
+    cert_buf_size = ROUND_UP(cert->der_size, 4);
+    data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
+
+    if (cert_store->largest_cert_size < data_buf_size) {
+        cert_store->largest_cert_size = data_buf_size;
+    }
+
+    g_assert(cert_store->count < MAX_CERTIFICATES);
+
+    cert_store->certs[cert_store->count] = *cert;
+    cert_store->total_bytes += data_buf_size;
+    cert_store->count++;
+}
+
+static GPtrArray *get_cert_paths(Error **errp)
+{
+    struct stat st;
+    BootCertificatesList *path_list = NULL;
+    BootCertificatesList *list = NULL;
+    gchar *cert_path;
+    GDir *dir = NULL;
+    const gchar *filename;
+    bool is_empty;
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_full(0, g_free);
+
+    path_list = s390_get_boot_certs();
+
+    for (list = path_list; list; list = list->next) {
+        cert_path = list->value->path;
+
+        if (g_strcmp0(cert_path, "") == 0) {
+            error_setg(errp, "Empty path in certificate path list is not allowed");
+            goto fail;
+        }
+
+        if (stat(cert_path, &st) != 0) {
+            error_setg(errp, "Failed to stat path '%s': %s",
+                       cert_path, g_strerror(errno));
+            goto fail;
+        }
+
+        if (S_ISREG(st.st_mode)) {
+            if (!g_str_has_suffix(cert_path, ".pem")) {
+                error_setg(errp, "Certificate file '%s' must have a .pem extension",
+                           cert_path);
+                goto fail;
+            }
+
+            g_ptr_array_add(cert_path_builder, g_strdup(cert_path));
+        } else if (S_ISDIR(st.st_mode)) {
+            dir = g_dir_open(cert_path, 0, &err);
+            if (dir == NULL) {
+                error_setg(errp, "Failed to open directory '%s': %s",
+                           cert_path, err->message);
+
+                goto fail;
+            }
+
+            is_empty = true;
+            while ((filename = g_dir_read_name(dir))) {
+                is_empty = false;
+
+                if (g_str_has_suffix(filename, ".pem")) {
+                    g_ptr_array_add(cert_path_builder,
+                                    g_build_filename(cert_path, filename, NULL));
+                } else {
+                    warn_report("skipping '%s': not a .pem file", filename);
+                }
+            }
+
+            if (is_empty) {
+                warn_report("'%s' directory is empty", cert_path);
+            }
+
+            g_dir_close(dir);
+        } else {
+            error_setg(errp, "Path '%s' is neither a file nor a directory", cert_path);
+            goto fail;
+        }
+    }
+
+    qapi_free_BootCertificatesList(path_list);
+    return g_steal_pointer(&cert_path_builder);
+
+fail:
+    qapi_free_BootCertificatesList(path_list);
+    return NULL;
+}
+
+void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store)
+{
+    GPtrArray *cert_path_builder;
+    Error *err = NULL;
+
+    /* If cert store is already populated, then no work to do */
+    if (cert_store->count) {
+        return;
+    }
+
+    cert_path_builder = get_cert_paths(&err);
+    if (cert_path_builder == NULL) {
+        error_report_err(err);
+        exit(1);
+    }
+
+    if (cert_path_builder->len == 0) {
+        g_ptr_array_free(cert_path_builder, TRUE);
+        return;
+    }
+
+    if (cert_path_builder->len > MAX_CERTIFICATES) {
+        error_report("Cert store exceeds maximum of %d certificates", MAX_CERTIFICATES);
+        g_ptr_array_free(cert_path_builder, TRUE);
+        exit(1);
+    }
+
+    cert_store->largest_cert_size = 0;
+    cert_store->total_bytes = 0;
+
+    for (int i = 0; i < cert_path_builder->len; i++) {
+        g_autofree S390IPLCertificate *cert =
+                    init_cert((char *) cert_path_builder->pdata[i],
+                              &err);
+        if (!cert) {
+            error_report_err(err);
+            g_ptr_array_free(cert_path_builder, TRUE);
+            exit(1);
+        }
+
+        update_cert_store(cert_store, cert);
+    }
+
+    g_ptr_array_free(cert_path_builder, TRUE);
+}
diff --git a/hw/s390x/cert-store.h b/hw/s390x/cert-store.h
new file mode 100644
index 0000000000..7fc9503cb9
--- /dev/null
+++ b/hw/s390x/cert-store.h
@@ -0,0 +1,39 @@
+/*
+ * S390 certificate store
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_S390_CERT_STORE_H
+#define HW_S390_CERT_STORE_H
+
+#include "hw/s390x/ipl/qipl.h"
+#include "crypto/x509-utils.h"
+
+#define CERT_NAME_MAX_LEN  64
+
+#define CERT_KEY_ID_LEN    QCRYPTO_HASH_DIGEST_LEN_SHA256
+#define CERT_HASH_LEN      QCRYPTO_HASH_DIGEST_LEN_SHA256
+
+struct S390IPLCertificate {
+    uint8_t name[CERT_NAME_MAX_LEN];
+    size_t  size;
+    size_t  der_size;
+    uint8_t *raw;
+};
+typedef struct S390IPLCertificate S390IPLCertificate;
+
+struct S390IPLCertificateStore {
+    uint16_t count;
+    size_t   largest_cert_size;
+    size_t   total_bytes;
+    S390IPLCertificate certs[MAX_CERTIFICATES];
+};
+typedef struct S390IPLCertificateStore S390IPLCertificateStore;
+
+void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store);
+
+#endif
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index d34adb5522..ea108fe370 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -36,6 +36,7 @@
 #include "qemu/option.h"
 #include "qemu/ctype.h"
 #include "standard-headers/linux/virtio_ids.h"
+#include "cert-store.h"
 
 #define KERN_IMAGE_START                0x010000UL
 #define LINUX_MAGIC_ADDR                0x010008UL
@@ -425,6 +426,13 @@ void s390_ipl_convert_loadparm(char *ascii_lp, uint8_t *ebcdic_lp)
     }
 }
 
+S390IPLCertificateStore *s390_ipl_get_certificate_store(void)
+{
+    S390IPLState *ipl = get_ipl_device();
+
+    return &ipl->cert_store;
+}
+
 static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
 {
     CcwDevice *ccw_dev = NULL;
@@ -718,6 +726,8 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
     cpu->env.psw.addr = ipl->start_addr;
     cpu->env.psw.mask = IPL_PSW_MASK;
 
+    s390_ipl_create_cert_store(&ipl->cert_store);
+
     if (!ipl->kernel || ipl->iplb_valid) {
         cpu->env.psw.addr = ipl->bios_start_addr;
         if (!ipl->iplb_valid) {
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 086e57681c..37f311474d 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -13,6 +13,7 @@
 #ifndef HW_S390_IPL_H
 #define HW_S390_IPL_H
 
+#include "cert-store.h"
 #include "cpu.h"
 #include "exec/target_page.h"
 #include "system/address-spaces.h"
@@ -35,6 +36,7 @@ int s390_ipl_pv_unpack(struct S390PVResponse *pv_resp);
 void s390_ipl_prepare_cpu(S390CPU *cpu);
 IplParameterBlock *s390_ipl_get_iplb(void);
 IplParameterBlock *s390_ipl_get_iplb_pv(void);
+S390IPLCertificateStore *s390_ipl_get_certificate_store(void);
 
 enum s390_reset {
     /* default is a reset not triggered by a CPU e.g. issued by QMP */
@@ -63,6 +65,7 @@ struct S390IPLState {
     IplParameterBlock iplb;
     IplParameterBlock iplb_pv;
     QemuIplParameters qipl;
+    S390IPLCertificateStore cert_store;
     uint64_t start_addr;
     uint64_t compat_start_addr;
     uint64_t bios_start_addr;
diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build
index 1bc8583799..62884fc99c 100644
--- a/hw/s390x/meson.build
+++ b/hw/s390x/meson.build
@@ -17,6 +17,7 @@ s390x_ss.add(files(
   'sclpcpu.c',
   'sclpquiesce.c',
   'tod.c',
+  'cert-store.c',
 ))
 s390x_ss.add(when: 'CONFIG_KVM', if_true: files(
   'tod-kvm.c',
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 6824391111..e505f44020 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -20,6 +20,8 @@
 #define LOADPARM_LEN    8
 #define NO_LOADPARM "\0\0\0\0\0\0\0\0"
 
+#define MAX_CERTIFICATES  64
+
 /*
  * The QEMU IPL Parameters will be stored at absolute address
  * 204 (0xcc) which means it is 32-bit word aligned but not
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 05/30] s390x/diag: Introduce DIAG 320 for Certificate Store Facility
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (3 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 04/30] hw/s390x/ipl: Create " Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 06/30] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
                   ` (24 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

DIAGNOSE 320 is introduced to support Certificate Store (CS)
Facility, which includes operations such as query certificate
storage information and provide certificates in the certificate
store.

Currently, only subcode 0 is supported with this patch, which is
used to query the Installed Subcodes Mask (ISM).

This subcode is only supported when the CS facility is enabled.

Availability of CS facility is determined by byte 134 bit 5 of the
SCLP Read Info block. Byte 134's facilities cannot be represented
without the availability of the extended-length-SCCB, so add it as
a check for consistency.

Note: secure IPL is not available for Secure Execution (SE) guests,
as their images are already integrity protected, and an additional
protection of the kernel by secure IPL is not necessary.

This feature is available starting with the gen16 CPU model.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 docs/specs/s390x-secure-ipl.rst     | 12 +++++++++
 include/hw/s390x/ipl/diag320.h      | 20 ++++++++++++++
 target/s390x/cpu_features.c         |  1 +
 target/s390x/cpu_features_def.h.inc |  1 +
 target/s390x/cpu_models.c           |  2 ++
 target/s390x/diag.c                 | 42 +++++++++++++++++++++++++++++
 target/s390x/gen-features.c         |  3 +++
 target/s390x/kvm/kvm.c              | 16 +++++++++++
 target/s390x/s390x-internal.h       |  2 ++
 target/s390x/tcg/misc_helper.c      |  7 +++++
 10 files changed, 106 insertions(+)
 create mode 100644 include/hw/s390x/ipl/diag320.h

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 7ddac98a37..96a8d0fb83 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -14,3 +14,15 @@ and a summation of the sizes.
 
 Note: A maximum of 64 certificates are allowed to be stored in the certificate
 store.
+
+DIAGNOSE function code 'X'320' - Certificate Store Facility
+-----------------------------------------------------------
+
+DIAGNOSE 'X'320' is used to provide support for guest code to directly
+query the s390 certificate store. Guest code may be the s390-ccw BIOS or
+the guest kernel.
+
+Subcode 0 - query installed subcodes
+    Returns a 256-bit installed subcodes mask (ISM) stored in the installed
+    subcodes block (ISB). This mask indicates which subcodes are currently
+    installed and available for use.
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
new file mode 100644
index 0000000000..aa04b699c6
--- /dev/null
+++ b/include/hw/s390x/ipl/diag320.h
@@ -0,0 +1,20 @@
+/*
+ * S/390 DIAGNOSE 320 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG320_H
+#define S390X_DIAG320_H
+
+#define DIAG_320_SUBC_QUERY_ISM     0
+
+#define DIAG_320_RC_OK              0x0001
+#define DIAG_320_RC_NOT_SUPPORTED   0x0102
+
+#define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
+
+#endif
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 4b5be6798e..436471f4b4 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -147,6 +147,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
         break;
     case S390_FEAT_TYPE_SCLP_FAC134:
         clear_be_bit(s390_feat_def(S390_FEAT_DIAG_318)->bit, data);
+        clear_be_bit(s390_feat_def(S390_FEAT_CERT_STORE)->bit, data);
         break;
     default:
         return;
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index c017bffcdc..2976ecd0ee 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -138,6 +138,7 @@ DEF_FEAT(SIE_IBS, "ibs", SCLP_CONF_CHAR_EXT, 10, "SIE: Interlock-and-broadcast-s
 
 /* Features exposed via SCLP SCCB Facilities byte 134 (bit numbers relative to byte-134) */
 DEF_FEAT(DIAG_318, "diag318", SCLP_FAC134, 0, "Control program name and version codes")
+DEF_FEAT(CERT_STORE, "cstore", SCLP_FAC134, 5, "Certificate Store functions")
 
 /* Features exposed via SCLP CPU info. */
 DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index 954a7a99a9..6b8471700e 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -248,6 +248,7 @@ bool s390_has_feat(S390Feat feat)
     if (s390_is_pv()) {
         switch (feat) {
         case S390_FEAT_DIAG_318:
+        case S390_FEAT_CERT_STORE:
         case S390_FEAT_HPMA2:
         case S390_FEAT_SIE_F2:
         case S390_FEAT_SIE_SKEY:
@@ -505,6 +506,7 @@ static void check_consistency(const S390CPUModel *model)
         { S390_FEAT_PTFF_STOUE, S390_FEAT_MULTIPLE_EPOCH },
         { S390_FEAT_AP_QUEUE_INTERRUPT_CONTROL, S390_FEAT_AP },
         { S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
+        { S390_FEAT_CERT_STORE, S390_FEAT_EXTENDED_LENGTH_SCCB },
         { S390_FEAT_NNPA, S390_FEAT_VECTOR },
         { S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
         { S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index da44b0133e..6373544bb2 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -18,6 +18,7 @@
 #include "hw/watchdog/wdt_diag288.h"
 #include "system/cpus.h"
 #include "hw/s390x/ipl.h"
+#include "hw/s390x/ipl/diag320.h"
 #include "hw/s390x/s390-virtio-ccw.h"
 #include "system/kvm.h"
 #include "kvm/kvm_s390x.h"
@@ -192,3 +193,44 @@ out:
         break;
     }
 }
+
+void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
+{
+    S390CPU *cpu = env_archcpu(env);
+    uint64_t subcode = env->regs[r3];
+    uint64_t addr = env->regs[r1];
+
+    if (env->psw.mask & PSW_MASK_PSTATE) {
+        s390_program_interrupt(env, PGM_PRIVILEGED, ra);
+        return;
+    }
+
+    if (!s390_has_feat(S390_FEAT_CERT_STORE) ||
+        (subcode & ~0x000ffULL) ||
+        (r1 & 1)) {
+        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+        return;
+    }
+
+
+    switch (subcode) {
+    case DIAG_320_SUBC_QUERY_ISM:
+        /*
+         * The Installed Subcode Block (ISB) can be up 8 words in size,
+         * but the current set of subcodes can fit within a single word
+         * for now.
+         */
+        uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES);
+
+        if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
+            s390_cpu_virt_mem_handle_exc(cpu, ra);
+            return;
+        }
+
+        env->regs[r1 + 1] = DIAG_320_RC_OK;
+        break;
+    default:
+        env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
+        break;
+    }
+}
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index 8218e6470e..6c20c3a862 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -720,6 +720,7 @@ static uint16_t full_GEN16_GA1[] = {
     S390_FEAT_PAIE,
     S390_FEAT_UV_FEAT_AP,
     S390_FEAT_UV_FEAT_AP_INTR,
+    S390_FEAT_CERT_STORE,
 };
 
 static uint16_t full_GEN17_GA1[] = {
@@ -919,6 +920,8 @@ static uint16_t qemu_MAX[] = {
     S390_FEAT_KIMD_SHA_512,
     S390_FEAT_KLMD_SHA_512,
     S390_FEAT_PRNO_TRNG,
+    S390_FEAT_EXTENDED_LENGTH_SCCB,
+    S390_FEAT_CERT_STORE,
 };
 
 /****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 54d28e37d4..fb7a99f380 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -98,6 +98,7 @@
 #define DIAG_TIMEREVENT                 0x288
 #define DIAG_IPL                        0x308
 #define DIAG_SET_CONTROL_PROGRAM_CODES  0x318
+#define DIAG_CERT_STORE                 0x320
 #define DIAG_KVM_HYPERCALL              0x500
 #define DIAG_KVM_BREAKPOINT             0x501
 
@@ -1560,6 +1561,16 @@ static void handle_diag_318(S390CPU *cpu, struct kvm_run *run)
     }
 }
 
+static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
+{
+    uint64_t r1, r3;
+
+    r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
+    r3 = run->s390_sieic.ipa & 0x000f;
+
+    handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
+}
+
 #define DIAG_KVM_CODE_MASK 0x000000000000ffff
 
 static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
@@ -1590,6 +1601,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
     case DIAG_KVM_BREAKPOINT:
         r = handle_sw_breakpoint(cpu, run);
         break;
+    case DIAG_CERT_STORE:
+        kvm_handle_diag_320(cpu, run);
+        break;
     default:
         trace_kvm_insn_diag(func_code);
         kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
@@ -2488,6 +2502,8 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
         set_bit(S390_FEAT_DIAG_318, model->features);
     }
 
+    set_bit(S390_FEAT_CERT_STORE, model->features);
+
     /* Test for Ultravisor features that influence secure guest behavior */
     query_uv_feat_guest(model->features);
 
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index 40850bcdc4..b16490bce6 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -388,6 +388,8 @@ int mmu_translate_real(CPUS390XState *env, hwaddr raddr, int rw,
 int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3);
 void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
                      uintptr_t ra);
+void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
+                     uintptr_t ra);
 
 
 /* translate.c */
diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
index 1fd900fbbf..4d73475d95 100644
--- a/target/s390x/tcg/misc_helper.c
+++ b/target/s390x/tcg/misc_helper.c
@@ -142,6 +142,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
         /* time bomb (watchdog) */
         r = handle_diag_288(env, r1, r3);
         break;
+    case 0x320:
+        /* cert store */
+        bql_lock();
+        handle_diag_320(env, r1, r3, GETPC());
+        bql_unlock();
+        r = 0;
+        break;
     default:
         r = -1;
         break;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 06/30] s390x/diag: Refactor address validation check from diag308_parm_check
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (4 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 05/30] s390x/diag: Introduce DIAG 320 for Certificate Store Facility Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 07/30] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
                   ` (23 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Create a function to validate the address parameter of DIAGNOSE.

Refactor the function for reuse in the next patch, which allows address
validation in read or write operation of DIAGNOSE.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
Reviewed-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 target/s390x/diag.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 6373544bb2..8ab40437a2 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -26,6 +26,12 @@
 #include "qemu/error-report.h"
 
 
+static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
+{
+    return address_space_access_valid(&address_space_memory, addr,
+                                      size, write, MEMTXATTRS_UNSPECIFIED);
+}
+
 int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
 {
     uint64_t func = env->regs[r1];
@@ -65,9 +71,7 @@ static int diag308_parm_check(CPUS390XState *env, uint64_t r1, uint64_t addr,
         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
         return -1;
     }
-    if (!address_space_access_valid(&address_space_memory, addr,
-                                    sizeof(IplParameterBlock), write,
-                                    MEMTXATTRS_UNSPECIFIED)) {
+    if (!diag_parm_addr_valid(addr, sizeof(IplParameterBlock), write)) {
         s390_program_interrupt(env, PGM_ADDRESSING, ra);
         return -1;
     }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 07/30] s390x/diag: Implement DIAG 320 subcode 1
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (5 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 06/30] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 08/30] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
                   ` (22 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

DIAG 320 subcode 1 provides information needed to determine
the amount of storage to store one or more certificates from the
certificate store.

Upon successful completion, this subcode returns information of the current
cert store, such as the number of certificates stored and allowed in the cert
store, amount of space may need to be allocate to store a certificate,
etc for verification-certificate blocks (VCBs).

The subcode value is denoted by setting the left-most bit
of an 8-byte field.

The verification-certificate-storage-size block (VCSSB) contains
the output data when the operation completes successfully. A VCSSB
length of 4 indicates that no certificate are available in the cert
store.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
---
 docs/specs/s390x-secure-ipl.rst | 12 +++++++
 include/hw/s390x/ipl/diag320.h  | 22 ++++++++++++
 target/s390x/diag.c             | 63 ++++++++++++++++++++++++++++++++-
 3 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 96a8d0fb83..52661fab00 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -26,3 +26,15 @@ Subcode 0 - query installed subcodes
     Returns a 256-bit installed subcodes mask (ISM) stored in the installed
     subcodes block (ISB). This mask indicates which subcodes are currently
     installed and available for use.
+
+Subcode 1 - query verification certificate storage information
+    Provides the information required to determine the amount of memory needed
+    to store one or more verification-certificates (VCs) from the certificate
+    store (CS).
+
+    Upon successful completion, this subcode returns various storage size values
+    for verification-certificate blocks (VCBs).
+
+    The output is returned in the verification-certificate-storage-size block
+    (VCSSB). A VCSSB length of 4 indicates that no certificates are available
+    in the CS.
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index aa04b699c6..6e4779c699 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -11,10 +11,32 @@
 #define S390X_DIAG320_H
 
 #define DIAG_320_SUBC_QUERY_ISM     0
+#define DIAG_320_SUBC_QUERY_VCSI    1
 
 #define DIAG_320_RC_OK              0x0001
 #define DIAG_320_RC_NOT_SUPPORTED   0x0102
+#define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
 
 #define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
+#define DIAG_320_ISM_QUERY_VCSI     0x40000000
+
+#define VCSSB_NO_VC     4
+#define VCSSB_MIN_LEN   128
+#define VCE_HEADER_LEN  128
+#define VCB_HEADER_LEN  64
+
+struct VCStorageSizeBlock {
+    uint32_t length;
+    uint8_t reserved0[3];
+    uint8_t version;
+    uint32_t reserved1[6];
+    uint16_t total_vc_ct;
+    uint16_t max_vc_ct;
+    uint32_t reserved3[11];
+    uint32_t max_single_vcb_len;
+    uint32_t total_vcb_len;
+    uint32_t reserved4[10];
+};
+typedef struct VCStorageSizeBlock VCStorageSizeBlock;
 
 #endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 8ab40437a2..c44624e1e6 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -198,11 +198,54 @@ out:
     }
 }
 
+static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
+                                     uintptr_t ra, S390IPLCertificateStore *cs)
+{
+    g_autofree VCStorageSizeBlock *vcssb = NULL;
+
+    vcssb = g_new0(VCStorageSizeBlock, 1);
+    if (s390_cpu_virt_mem_read(cpu, addr, r1, vcssb, sizeof(*vcssb))) {
+        s390_cpu_virt_mem_handle_exc(cpu, ra);
+        return -1;
+    }
+
+    if (be32_to_cpu(vcssb->length) > sizeof(*vcssb)) {
+        return DIAG_320_RC_INVAL_VCSSB_LEN;
+    }
+
+    if (be32_to_cpu(vcssb->length) < VCSSB_MIN_LEN) {
+        return DIAG_320_RC_INVAL_VCSSB_LEN;
+    }
+
+    if (!cs->count) {
+        vcssb->length = cpu_to_be32(VCSSB_NO_VC);
+    } else {
+        vcssb->version = 0;
+        vcssb->total_vc_ct = cpu_to_be16(cs->count);
+        vcssb->max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
+        vcssb->max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
+                                                cs->largest_cert_size);
+        vcssb->total_vcb_len = cpu_to_be32(VCB_HEADER_LEN + cs->count * VCE_HEADER_LEN +
+                                           cs->total_bytes);
+    }
+
+    if (s390_cpu_virt_mem_write(cpu, addr, r1, vcssb, be32_to_cpu(vcssb->length))) {
+        s390_cpu_virt_mem_handle_exc(cpu, ra);
+        return -1;
+    }
+    return DIAG_320_RC_OK;
+}
+
+QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
+                   "size of VCStorageSizeBlock is wrong");
+
 void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 {
     S390CPU *cpu = env_archcpu(env);
+    S390IPLCertificateStore *cs = s390_ipl_get_certificate_store();
     uint64_t subcode = env->regs[r3];
     uint64_t addr = env->regs[r1];
+    int rc;
 
     if (env->psw.mask & PSW_MASK_PSTATE) {
         s390_program_interrupt(env, PGM_PRIVILEGED, ra);
@@ -224,7 +267,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
          * but the current set of subcodes can fit within a single word
          * for now.
          */
-        uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES);
+        uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
+                                         DIAG_320_ISM_QUERY_VCSI);
 
         if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
             s390_cpu_virt_mem_handle_exc(cpu, ra);
@@ -233,6 +277,23 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 
         env->regs[r1 + 1] = DIAG_320_RC_OK;
         break;
+    case DIAG_320_SUBC_QUERY_VCSI:
+        if (addr & 0x7) {
+            s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+            return;
+        }
+
+        if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock), true)) {
+            s390_program_interrupt(env, PGM_ADDRESSING, ra);
+            return;
+        }
+
+        rc = handle_diag320_query_vcsi(cpu, addr, r1, ra, cs);
+        if (rc == -1) {
+            return;
+        }
+        env->regs[r1 + 1] = rc;
+        break;
     default:
         env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
         break;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 08/30] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (6 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 07/30] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 09/30] s390x/diag: Implement " Zhuoying Cai
                   ` (21 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Introduce new helper functions to extract certificate metadata:

qcrypto_x509_check_cert_times() - validates the certificate's validity period against the current time
qcrypto_x509_get_pk_algorithm() - returns the public key algorithm used in the certificate
qcrypto_x509_get_cert_key_id() - extracts the key ID from the certificate
qcrypto_x509_check_ecc_curve_p521() - determines the ECC public key algorithm uses P-521 curve

These functions provide support for metadata extraction and validity checking
for X.509 certificates.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Farhan Ali<alifm@linux.ibm.com>
---
 crypto/x509-utils.c         | 236 ++++++++++++++++++++++++++++++++++++
 include/crypto/x509-utils.h |  51 ++++++++
 2 files changed, 287 insertions(+)

diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 2696d48155..906d5e5e87 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -27,6 +27,16 @@ static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
     [QCRYPTO_HASH_ALGO_RIPEMD160] = GNUTLS_DIG_RMD160,
 };
 
+static const int qcrypto_to_gnutls_keyid_flags_map[] = {
+    [QCRYPTO_HASH_ALGO_MD5] = -1,
+    [QCRYPTO_HASH_ALGO_SHA1] = GNUTLS_KEYID_USE_SHA1,
+    [QCRYPTO_HASH_ALGO_SHA224] = -1,
+    [QCRYPTO_HASH_ALGO_SHA256] = GNUTLS_KEYID_USE_SHA256,
+    [QCRYPTO_HASH_ALGO_SHA384] = -1,
+    [QCRYPTO_HASH_ALGO_SHA512] = GNUTLS_KEYID_USE_SHA512,
+    [QCRYPTO_HASH_ALGO_RIPEMD160] = -1,
+};
+
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
                                       QCryptoHashAlgo alg,
                                       uint8_t *result,
@@ -121,6 +131,210 @@ cleanup:
     return ret;
 }
 
+int qcrypto_x509_check_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+    int rc;
+    int ret = -1;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    time_t now = time(NULL);
+    time_t exp_time;
+    time_t act_time;
+
+    if (now == ((time_t)-1)) {
+        error_setg_errno(errp, errno, "Cannot get current time");
+        return ret;
+    }
+
+    rc = gnutls_x509_crt_init(&crt);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+        return ret;
+    }
+
+    rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    exp_time = gnutls_x509_crt_get_expiration_time(crt);
+    if (exp_time == ((time_t)-1)) {
+        error_setg(errp, "Failed to get certificate expiration time");
+        goto cleanup;
+    }
+    if (exp_time < now) {
+        error_setg(errp, "The certificate has expired");
+        goto cleanup;
+    }
+
+    act_time = gnutls_x509_crt_get_activation_time(crt);
+    if (act_time == ((time_t)-1)) {
+        error_setg(errp, "Failed to get certificate activation time");
+        goto cleanup;
+    }
+    if (act_time > now) {
+        error_setg(errp, "The certificate is not yet active");
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
+static int qcrypto_x509_get_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+    int rc;
+    int ret = -1;
+    unsigned int bits;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+
+    rc = gnutls_x509_crt_init(&crt);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+        return ret;
+    }
+
+    rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
+    if (rc < 0) {
+        error_setg(errp, "Unknown public key algorithm %d", rc);
+        goto cleanup;
+    }
+
+    ret = rc;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoHashAlgo hash_alg,
+                                 uint8_t **result,
+                                 size_t *resultlen,
+                                 Error **errp)
+{
+    int rc;
+    int ret = -1;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+
+    if (hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
+        error_setg(errp, "Unknown hash algorithm %d", hash_alg);
+        return ret;
+    }
+
+    if (hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map) ||
+        qcrypto_to_gnutls_keyid_flags_map[hash_alg] == -1) {
+        error_setg(errp, "Unsupported key id flag %d", hash_alg);
+        return ret;
+    }
+
+    rc = gnutls_x509_crt_init(&crt);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+        return ret;
+    }
+
+    rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    *resultlen = gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[hash_alg]);
+    if (*resultlen == 0) {
+        error_setg(errp, "Failed to get hash algorithn length: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    *result = g_malloc0(*resultlen);
+    if (gnutls_x509_crt_get_key_id(crt,
+                                   qcrypto_to_gnutls_keyid_flags_map[hash_alg],
+                                   *result, resultlen) != 0) {
+        error_setg(errp, "Failed to get key ID from certificate");
+        g_clear_pointer(result, g_free);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
+static int qcrypto_x509_get_ecc_curve(uint8_t *cert, size_t size, Error **errp)
+{
+    int rc;
+    int ret = -1;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    gnutls_ecc_curve_t curve_id;
+    gnutls_datum_t x = {.data = NULL, .size = 0};
+    gnutls_datum_t y = {.data = NULL, .size = 0};
+
+    rc = gnutls_x509_crt_init(&crt);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+        return ret;
+    }
+
+    rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_get_pk_ecc_raw(crt, &curve_id, &x, &y);
+    if (rc != 0) {
+        error_setg(errp, "Failed to get ECC public key curve: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    ret = curve_id;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    gnutls_free(x.data);
+    gnutls_free(y.data);
+    return ret;
+}
+
+int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
+{
+    int algo;
+    int curve_id;
+
+    algo = qcrypto_x509_get_pk_algorithm(cert, size, errp);
+    if (algo != GNUTLS_PK_ECDSA) {
+        return 0;
+    }
+
+    curve_id = qcrypto_x509_get_ecc_curve(cert, size, errp);
+    if (curve_id == -1) {
+        error_setg(errp, "Failed to get ECC curve");
+        return -1;
+    }
+
+    if (curve_id == GNUTLS_ECC_CURVE_INVALID) {
+        error_setg(errp, "Invalid ECC curve");
+        return -1;
+    }
+
+    return curve_id == GNUTLS_ECC_CURVE_SECP521R1;
+}
+
 #else /* ! CONFIG_GNUTLS */
 
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
@@ -142,4 +356,26 @@ int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
     return -1;
 }
 
+int qcrypto_x509_check_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get certificate times");
+    return -1;
+}
+
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoHashAlgo hash_alg,
+                                 uint8_t **result,
+                                 size_t *resultlen,
+                                 Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get key ID");
+    return -1;
+}
+
+int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to determine ecc curve");
+    return -1;
+}
+
 #endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 91ae79fb03..6040894a46 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -40,4 +40,55 @@ int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
                                   size_t *resultlen,
                                   Error **errp);
 
+/**
+ * qcrypto_x509_check_cert_times
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Check whether the activation and expiration times of @cert
+ * are valid at the current time.
+ *
+ * Returns: 0 if the certificate times are valid,
+ *         -1 on error.
+ */
+int qcrypto_x509_check_cert_times(uint8_t *cert, size_t size, Error **errp);
+
+/**
+ * qcrypto_x509_get_cert_key_id
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @hash_alg: the hash algorithm flag
+ * @result: output location for the allocated buffer for key ID
+ *          (the function allocates memory which must be freed by the caller)
+ * @resultlen: pointer to the size of the buffer
+ *             (will be updated with the actual size of key id)
+ * @errp: error pointer
+ *
+ * Retrieve the key ID from the @cert based on the specified @flag.
+ *
+ * Returns: 0 if key ID was successfully stored in @result,
+ *         -1 on error.
+ */
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoHashAlgo hash_alg,
+                                 uint8_t **result,
+                                 size_t *resultlen,
+                                 Error **errp);
+
+/**
+ * qcrypto_x509_check_ecc_curve_p521
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine whether the ECC public key in the given certificate uses the P-521
+ * curve.
+ *
+ * Returns: 0 if ECC public key does not use P521 curve.
+ *          1 if ECC public key uses P521 curve.
+ *         -1 on error.
+ */
+int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp);
+
 #endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 09/30] s390x/diag: Implement DIAG 320 subcode 2
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (7 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 08/30] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-13 19:58   ` Collin Walling
  2026-03-05 22:41 ` [PATCH v9 10/30] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
                   ` (20 subsequent siblings)
  29 siblings, 1 reply; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
certificate store. Only X509 certificates in DER format and SHA-256 hash
type are recognized.

The subcode value is denoted by setting the second-left-most bit
of an 8-byte field.

The Verification Certificate Block (VCB) contains the output data
when the operation completes successfully. It includes a common
header followed by zero or more Verification Certificate Entries (VCEs),
depending on the VCB input length and the VC range (from the first VC
index to the last VC index) in the certificate store.

Each VCE contains information about a certificate retrieved from
the S390IPLCertificateStore, such as the certificate name, key type,
key ID length, hash length, and the raw certificate data.
The key ID and hash are extracted from the raw certificate by the crypto API.

Note: SHA2-256 VC hash type is required for retrieving the hash
(fingerprint) of the certificate.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 docs/specs/s390x-secure-ipl.rst |  22 ++
 hw/s390x/cert-store.h           |   3 +-
 include/hw/s390x/ipl/diag320.h  |  55 +++++
 target/s390x/diag.c             | 343 +++++++++++++++++++++++++++++++-
 4 files changed, 420 insertions(+), 3 deletions(-)

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 52661fab00..708253ac91 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -38,3 +38,25 @@ Subcode 1 - query verification certificate storage information
     The output is returned in the verification-certificate-storage-size block
     (VCSSB). A VCSSB length of 4 indicates that no certificates are available
     in the CS.
+
+Subcode 2 - store verification certificates
+    Provides VCs that are in the certificate store.
+
+    The output is provided in a VCB, which includes a common header followed by
+    zero or more verification-certificate entries (VCEs).
+
+    The instruction expects the cert store to
+    maintain an origin of 1 for the index (i.e. a retrieval of the first
+    certificate in the store should be denoted by setting first-VC to 1).
+
+    The first-VC index and last-VC index fields of VCB specify the range of VCs
+    to be stored by subcode 2. Stored count and remained count fields specify
+    the number of VCs stored and could not be stored in the VCB due to
+    insufficient storage specified in the VCB input length field.
+
+    Each VCE contains a header followed by information extracted from a
+    certificate within the certificate store. The information includes:
+    key-id, hash, and certificate data. This information is stored
+    contiguously in a VCE (with zero-padding). Following the header, the
+    key-id is immediately stored. The hash and certificate data follow and
+    may be accessed via the respective offset fields stored in the VCE.
diff --git a/hw/s390x/cert-store.h b/hw/s390x/cert-store.h
index 7fc9503cb9..6f5ee63177 100644
--- a/hw/s390x/cert-store.h
+++ b/hw/s390x/cert-store.h
@@ -11,10 +11,9 @@
 #define HW_S390_CERT_STORE_H
 
 #include "hw/s390x/ipl/qipl.h"
+#include "hw/s390x/ipl/diag320.h"
 #include "crypto/x509-utils.h"
 
-#define CERT_NAME_MAX_LEN  64
-
 #define CERT_KEY_ID_LEN    QCRYPTO_HASH_DIGEST_LEN_SHA256
 #define CERT_HASH_LEN      QCRYPTO_HASH_DIGEST_LEN_SHA256
 
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index 6e4779c699..bfd6385b40 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -12,19 +12,37 @@
 
 #define DIAG_320_SUBC_QUERY_ISM     0
 #define DIAG_320_SUBC_QUERY_VCSI    1
+#define DIAG_320_SUBC_STORE_VC      2
 
 #define DIAG_320_RC_OK              0x0001
 #define DIAG_320_RC_NOT_SUPPORTED   0x0102
 #define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
+#define DIAG_320_RC_INVAL_VCB_LEN   0x0204
+#define DIAG_320_RC_BAD_RANGE       0x0302
 
 #define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
 #define DIAG_320_ISM_QUERY_VCSI     0x40000000
+#define DIAG_320_ISM_STORE_VC       0x20000000
 
 #define VCSSB_NO_VC     4
 #define VCSSB_MIN_LEN   128
 #define VCE_HEADER_LEN  128
+/*
+ * If the VCE flags indicate an invalid certificate,
+ * the VCE length is set to 72, containing only the
+ * first five fields of VCEntry.
+ */
+#define VCE_INVALID_LEN 72
 #define VCB_HEADER_LEN  64
 
+#define CERT_NAME_MAX_LEN  64
+
+#define DIAG_320_VCE_FLAGS_VALID                0x80
+#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING    0
+#define DIAG_320_VCE_KEYTYPE_ECDSA_P521         1
+#define DIAG_320_VCE_FORMAT_X509_DER            1
+#define DIAG_320_VCE_HASHTYPE_SHA2_256          1
+
 struct VCStorageSizeBlock {
     uint32_t length;
     uint8_t reserved0[3];
@@ -39,4 +57,41 @@ struct VCStorageSizeBlock {
 };
 typedef struct VCStorageSizeBlock VCStorageSizeBlock;
 
+struct VCBlock {
+    uint32_t in_len;
+    uint32_t reserved0;
+    uint16_t first_vc_index;
+    uint16_t last_vc_index;
+    uint32_t reserved1[5];
+    uint32_t out_len;
+    uint8_t reserved2[4];
+    uint16_t stored_ct;
+    uint16_t remain_ct;
+    uint32_t reserved3[5];
+    uint8_t vce_buf[];
+};
+typedef struct VCBlock VCBlock;
+
+struct VCEntry {
+    uint32_t len;
+    uint8_t flags;
+    uint8_t key_type;
+    uint16_t cert_idx;
+    uint8_t name[CERT_NAME_MAX_LEN];
+    uint8_t format;
+    uint8_t reserved0;
+    uint16_t keyid_len;
+    uint8_t reserved1;
+    uint8_t hash_type;
+    uint16_t hash_len;
+    uint32_t reserved2;
+    uint32_t cert_len;
+    uint32_t reserved3[2];
+    uint16_t hash_offset;
+    uint16_t cert_offset;
+    uint32_t reserved4[7];
+    uint8_t cert_buf[];
+};
+typedef struct VCEntry VCEntry;
+
 #endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index c44624e1e6..5326522fda 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -17,13 +17,16 @@
 #include "s390x-internal.h"
 #include "hw/watchdog/wdt_diag288.h"
 #include "system/cpus.h"
+#include "hw/s390x/cert-store.h"
 #include "hw/s390x/ipl.h"
 #include "hw/s390x/ipl/diag320.h"
 #include "hw/s390x/s390-virtio-ccw.h"
 #include "system/kvm.h"
 #include "kvm/kvm_s390x.h"
 #include "target/s390x/kvm/pv.h"
+#include "qapi/error.h"
 #include "qemu/error-report.h"
+#include "crypto/x509-utils.h"
 
 
 static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
@@ -236,8 +239,333 @@ static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
     return DIAG_320_RC_OK;
 }
 
+static bool is_cert_valid(const S390IPLCertificate *cert)
+{
+    int rc;
+    Error *err = NULL;
+
+    rc = qcrypto_x509_check_cert_times(cert->raw, cert->size, &err);
+    if (rc != 0) {
+        error_report_err(err);
+        return false;
+    }
+
+    return true;
+}
+
+static int handle_key_id(VCEntry *vce, const S390IPLCertificate *cert)
+{
+    int rc;
+    g_autofree unsigned char *key_id_data = NULL;
+    size_t key_id_len;
+    Error *err = NULL;
+
+    rc = qcrypto_x509_get_cert_key_id(cert->raw, cert->size,
+                                      QCRYPTO_HASH_ALGO_SHA256,
+                                      &key_id_data, &key_id_len, &err);
+    if (rc < 0) {
+        error_report_err(err);
+        return -1;
+    }
+
+    if (VCE_HEADER_LEN + key_id_len > be32_to_cpu(vce->len)) {
+        error_report("Unable to write key ID: exceeds buffer bounds");
+        return -1;
+    }
+
+    vce->keyid_len = cpu_to_be16(key_id_len);
+
+    memcpy(vce->cert_buf, key_id_data, key_id_len);
+
+    return 0;
+}
+
+static int handle_hash(VCEntry *vce, const S390IPLCertificate *cert,
+                       uint16_t keyid_field_len)
+{
+    int rc;
+    uint16_t hash_offset;
+    g_autofree void *hash_data = NULL;
+    size_t hash_len;
+    Error *err = NULL;
+
+    hash_len = CERT_HASH_LEN;
+    hash_data = g_malloc0(hash_len);
+    rc = qcrypto_get_x509_cert_fingerprint(cert->raw, cert->size,
+                                           QCRYPTO_HASH_ALGO_SHA256,
+                                           hash_data, &hash_len, &err);
+    if (rc < 0) {
+        error_report_err(err);
+        return -1;
+    }
+
+    hash_offset = VCE_HEADER_LEN + keyid_field_len;
+    if (hash_offset + hash_len > be32_to_cpu(vce->len)) {
+        error_report("Unable to write hash: exceeds buffer bounds");
+        return -1;
+    }
+
+    vce->hash_len = cpu_to_be16(hash_len);
+    vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
+    vce->hash_offset = cpu_to_be16(hash_offset);
+
+    memcpy((uint8_t *)vce + hash_offset, hash_data, hash_len);
+
+    return 0;
+}
+
+static int handle_cert(VCEntry *vce, const S390IPLCertificate *cert,
+                       uint16_t hash_field_len)
+{
+    int rc;
+    uint16_t cert_offset;
+    g_autofree uint8_t *cert_der = NULL;
+    size_t der_size;
+    Error *err = NULL;
+
+    rc = qcrypto_x509_convert_cert_der(cert->raw, cert->size,
+                                       &cert_der, &der_size, &err);
+    if (rc < 0) {
+        error_report_err(err);
+        return -1;
+    }
+
+    cert_offset = be16_to_cpu(vce->hash_offset) + hash_field_len;
+    if (cert_offset + der_size > be32_to_cpu(vce->len)) {
+        error_report("Unable to write certificate: exceeds buffer bounds");
+        return -1;
+    }
+
+    vce->format = DIAG_320_VCE_FORMAT_X509_DER;
+    vce->cert_len = cpu_to_be32(der_size);
+    vce->cert_offset = cpu_to_be16(cert_offset);
+
+    memcpy((uint8_t *)vce + cert_offset, cert_der, der_size);
+
+    return 0;
+}
+
+static int get_key_type(const S390IPLCertificate *cert)
+{
+    int rc;
+    Error *err = NULL;
+
+    rc = qcrypto_x509_check_ecc_curve_p521(cert->raw, cert->size, &err);
+    if (rc == -1) {
+        error_report_err(err);
+        return -1;
+    }
+
+    return (rc == 1) ? DIAG_320_VCE_KEYTYPE_ECDSA_P521 :
+                        DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
+}
+
+static int build_vce_header(VCEntry *vce, const S390IPLCertificate *cert, int idx)
+{
+    int key_type;
+
+    vce->len = cpu_to_be32(VCE_HEADER_LEN);
+    vce->cert_idx = cpu_to_be16(idx + 1);
+    memcpy(vce->name, cert->name, CERT_NAME_MAX_LEN);
+
+    key_type = get_key_type(cert);
+    if (key_type == -1) {
+        return -1;
+    }
+    vce->key_type = key_type;
+
+    return 0;
+}
+
+static int build_vce_data(VCEntry *vce, const S390IPLCertificate *cert)
+{
+    uint16_t keyid_field_len;
+    uint16_t hash_field_len;
+    uint32_t cert_field_len;
+    uint32_t vce_len;
+    int rc;
+
+    rc = handle_key_id(vce, cert);
+    if (rc) {
+        return -1;
+    }
+    keyid_field_len = ROUND_UP(be16_to_cpu(vce->keyid_len), 4);
+
+    rc = handle_hash(vce, cert, keyid_field_len);
+    if (rc) {
+        return -1;
+    }
+    hash_field_len = ROUND_UP(be16_to_cpu(vce->hash_len), 4);
+
+    rc = handle_cert(vce, cert, hash_field_len);
+    if (rc || !is_cert_valid(cert)) {
+        return -1;
+    }
+    cert_field_len = ROUND_UP(be32_to_cpu(vce->cert_len), 4);
+
+    vce_len = VCE_HEADER_LEN + keyid_field_len + hash_field_len + cert_field_len;
+    if (vce_len > be32_to_cpu(vce->len)) {
+        return -1;
+    }
+
+    vce->flags |= DIAG_320_VCE_FLAGS_VALID;
+
+    /* Update vce length to reflect the actual size used by vce */
+    vce->len = cpu_to_be32(vce_len);
+
+    return 0;
+}
+
+static VCEntry *diag_320_build_vce(const S390IPLCertificate *cert, int idx)
+{
+    g_autofree VCEntry *vce = NULL;
+    uint32_t vce_max_size;
+    int rc;
+
+    /*
+     * Each field of the VCE is word-aligned.
+     * Allocate enough space for the largest possible size for this VCE.
+     * As the certificate fields (key-id, hash, data) are parsed, the
+     * VCE's length field will be updated accordingly.
+     */
+    vce_max_size = VCE_HEADER_LEN +
+                   ROUND_UP(CERT_KEY_ID_LEN, 4) +
+                   ROUND_UP(CERT_HASH_LEN, 4) +
+                   ROUND_UP(cert->der_size, 4);
+
+    vce = g_malloc0(vce_max_size);
+    rc = build_vce_header(vce, cert, idx);
+    if (rc) {
+        /*
+         * Error occurs - VCE does not contain a valid certificate.
+         * Bit 0 of the VCE flags is 0 and the VCE length is set.
+         */
+        vce->len = cpu_to_be32(VCE_INVALID_LEN);
+        goto out;
+    }
+
+    vce->len = cpu_to_be32(vce_max_size);
+    rc = build_vce_data(vce, cert);
+    if (rc) {
+        vce->len = cpu_to_be32(VCE_INVALID_LEN);
+    }
+
+out:
+    return g_steal_pointer(&vce);
+}
+
+static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
+                                   S390IPLCertificateStore *cs)
+{
+    g_autofree VCBlock *vcb = NULL;
+    size_t entry_offset;
+    size_t remaining_space;
+    uint32_t vce_len;
+    uint16_t first_vc_index;
+    uint16_t last_vc_index;
+    int cs_start_index;
+    int cs_end_index;
+    uint32_t in_len;
+
+    vcb = g_new0(VCBlock, 1);
+    if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
+        s390_cpu_virt_mem_handle_exc(cpu, ra);
+        return -1;
+    }
+
+    in_len = be32_to_cpu(vcb->in_len);
+    first_vc_index = be16_to_cpu(vcb->first_vc_index);
+    last_vc_index = be16_to_cpu(vcb->last_vc_index);
+
+    if (in_len % TARGET_PAGE_SIZE != 0) {
+        return DIAG_320_RC_INVAL_VCB_LEN;
+    }
+
+    if (first_vc_index > last_vc_index) {
+        return DIAG_320_RC_BAD_RANGE;
+    }
+
+    vcb->out_len = VCB_HEADER_LEN;
+
+    /*
+     * DIAG 320 subcode 2 expects to query a certificate store that
+     * maintains an index origin of 1. However, the S390IPLCertificateStore
+     * maintains an index origin of 0. Thus, the indices must be adjusted
+     * for correct access into the cert store. A couple of special cases
+     * must also be accounted for.
+     */
+
+    /* Both indices are 0; return header with no certs */
+    if (first_vc_index == 0 && last_vc_index == 0) {
+        goto out;
+    }
+
+    /* Normalize indices */
+    cs_start_index = (first_vc_index == 0) ? 0 : first_vc_index - 1;
+    cs_end_index = last_vc_index - 1;
+
+    /* Requested range is outside the cert store; return header with no certs */
+    if (cs_start_index >= cs->count || cs_end_index >= cs->count) {
+        goto out;
+    }
+
+    entry_offset = VCB_HEADER_LEN;
+    remaining_space = in_len - VCB_HEADER_LEN;
+
+    for (int i = cs_start_index; i <= cs_end_index; i++) {
+        VCEntry *vce;
+        const S390IPLCertificate *cert = &cs->certs[i];
+
+        /*
+         * Bit 0 of the VCE flags indicates whether the certificate is valid.
+         * The caller of DIAG320 subcode 2 is responsible for verifying that
+         * the VCE contains a valid certificate.
+         */
+        vce = diag_320_build_vce(cert, i);
+        vce_len = be32_to_cpu(vce->len);
+
+        /*
+         * If there is no more space to store the cert,
+         * set the remaining verification cert count and
+         * break early.
+         */
+        if (remaining_space < vce_len) {
+            vcb->remain_ct = cpu_to_be16(last_vc_index - i);
+            g_free(vce);
+            break;
+        }
+
+        /* Write VCE */
+        if (s390_cpu_virt_mem_write(cpu, addr + entry_offset, r1, vce, vce_len)) {
+            s390_cpu_virt_mem_handle_exc(cpu, ra);
+            g_free(vce);
+            return -1;
+        }
+
+        entry_offset += vce_len;
+        vcb->out_len += vce_len;
+        remaining_space -= vce_len;
+        vcb->stored_ct++;
+
+        g_free(vce);
+    }
+    vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
+
+out:
+    vcb->out_len = cpu_to_be32(vcb->out_len);
+
+    if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
+        s390_cpu_virt_mem_handle_exc(cpu, ra);
+        return -1;
+    }
+
+    return DIAG_320_RC_OK;
+}
+
 QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
                    "size of VCStorageSizeBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != VCB_HEADER_LEN, "size of VCBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != VCE_HEADER_LEN, "size of VCEntry is wrong");
 
 void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 {
@@ -268,7 +596,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
          * for now.
          */
         uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
-                                         DIAG_320_ISM_QUERY_VCSI);
+                                         DIAG_320_ISM_QUERY_VCSI |
+                                         DIAG_320_ISM_STORE_VC);
 
         if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
             s390_cpu_virt_mem_handle_exc(cpu, ra);
@@ -294,6 +623,18 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
         }
         env->regs[r1 + 1] = rc;
         break;
+    case DIAG_320_SUBC_STORE_VC:
+        if (addr & ~TARGET_PAGE_MASK) {
+            s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+            return;
+        }
+
+        rc = handle_diag320_store_vc(cpu, addr, r1, ra, cs);
+        if (rc == -1) {
+            return;
+        }
+        env->regs[r1 + 1] = rc;
+        break;
     default:
         env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
         break;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 10/30] s390x/diag: Introduce DIAG 508 for secure IPL operations
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (8 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 09/30] s390x/diag: Implement " Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 11/30] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
                   ` (19 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

From: Collin Walling <walling@linux.ibm.com>

In order to support secure IPL (aka secure boot) for the s390-ccw BIOS,
a new s390 DIAGNOSE instruction is introduced to leverage QEMU for
handling operations such as signature verification and certificate
retrieval.

Currently, only subcode 0 is supported with this patch, which is used to
query a bitmap of which subcodes are supported.

Signed-off-by: Collin Walling <walling@linux.ibm.com>
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 docs/specs/s390x-secure-ipl.rst | 18 ++++++++++++++++++
 include/hw/s390x/ipl/diag508.h  | 15 +++++++++++++++
 target/s390x/diag.c             | 27 +++++++++++++++++++++++++++
 target/s390x/kvm/kvm.c          | 14 ++++++++++++++
 target/s390x/s390x-internal.h   |  2 ++
 target/s390x/tcg/misc_helper.c  |  7 +++++++
 6 files changed, 83 insertions(+)
 create mode 100644 include/hw/s390x/ipl/diag508.h

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 708253ac91..9a3decef69 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -60,3 +60,21 @@ Subcode 2 - store verification certificates
     contiguously in a VCE (with zero-padding). Following the header, the
     key-id is immediately stored. The hash and certificate data follow and
     may be accessed via the respective offset fields stored in the VCE.
+
+
+Secure IPL Data Structures, Facilities, and Functions
+=====================================================
+
+DIAGNOSE function code 'X'508' - IPL extensions
+---------------------------------------------------
+
+DIAGNOSE 'X'508' is reserved for guest use in order to facilitate communication
+of additional IPL operations that cannot be handled by guest code, such as
+signature verification for secure IPL.
+
+If the function code specifies 0x508, IPL extension functions are performed.
+These functions are meant to provide extended functionality for s390 guest boot
+that requires assistance from QEMU.
+
+Subcode 0 - query installed subcodes
+    Returns a 64-bit mask indicating which subcodes are supported.
diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
new file mode 100644
index 0000000000..6281ad8299
--- /dev/null
+++ b/include/hw/s390x/ipl/diag508.h
@@ -0,0 +1,15 @@
+/*
+ * S/390 DIAGNOSE 508 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Collin Walling <walling@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG508_H
+#define S390X_DIAG508_H
+
+#define DIAG_508_SUBC_QUERY_SUBC    0x0000
+
+#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 5326522fda..6d9bdee7e3 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -20,6 +20,7 @@
 #include "hw/s390x/cert-store.h"
 #include "hw/s390x/ipl.h"
 #include "hw/s390x/ipl/diag320.h"
+#include "hw/s390x/ipl/diag508.h"
 #include "hw/s390x/s390-virtio-ccw.h"
 #include "system/kvm.h"
 #include "kvm/kvm_s390x.h"
@@ -640,3 +641,29 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
         break;
     }
 }
+
+void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
+{
+    uint64_t subcode = env->regs[r3];
+    int rc;
+
+    if (env->psw.mask & PSW_MASK_PSTATE) {
+        s390_program_interrupt(env, PGM_PRIVILEGED, ra);
+        return;
+    }
+
+    if ((subcode & ~0x0ffffULL) || (r1 & 1)) {
+        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+        return;
+    }
+
+    switch (subcode) {
+    case DIAG_508_SUBC_QUERY_SUBC:
+        rc = 0;
+        break;
+    default:
+        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+        return;
+    }
+    env->regs[r1 + 1] = rc;
+}
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index fb7a99f380..cba431688b 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -101,6 +101,7 @@
 #define DIAG_CERT_STORE                 0x320
 #define DIAG_KVM_HYPERCALL              0x500
 #define DIAG_KVM_BREAKPOINT             0x501
+#define DIAG_SECURE_IPL                 0x508
 
 #define ICPT_INSTRUCTION                0x04
 #define ICPT_PROGRAM                    0x08
@@ -1571,6 +1572,16 @@ static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
     handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
 }
 
+static void kvm_handle_diag_508(S390CPU *cpu, struct kvm_run *run)
+{
+    uint64_t r1, r3;
+
+    r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
+    r3 = run->s390_sieic.ipa & 0x000f;
+
+    handle_diag_508(&cpu->env, r1, r3, RA_IGNORED);
+}
+
 #define DIAG_KVM_CODE_MASK 0x000000000000ffff
 
 static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
@@ -1604,6 +1615,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
     case DIAG_CERT_STORE:
         kvm_handle_diag_320(cpu, run);
         break;
+    case DIAG_SECURE_IPL:
+        kvm_handle_diag_508(cpu, run);
+        break;
     default:
         trace_kvm_insn_diag(func_code);
         kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index b16490bce6..367df65970 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -390,6 +390,8 @@ void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
                      uintptr_t ra);
 void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
                      uintptr_t ra);
+void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3,
+                     uintptr_t ra);
 
 
 /* translate.c */
diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
index 4d73475d95..562dde9cb3 100644
--- a/target/s390x/tcg/misc_helper.c
+++ b/target/s390x/tcg/misc_helper.c
@@ -149,6 +149,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
         bql_unlock();
         r = 0;
         break;
+    case 0x508:
+        /* secure ipl operations */
+        bql_lock();
+        handle_diag_508(env, r1, r3, GETPC());
+        bql_unlock();
+        r = 0;
+        break;
     default:
         r = -1;
         break;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 11/30] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (9 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 10/30] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 12/30] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
                   ` (18 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Introduce helper functions to support signature verification required by
DIAG 508 subcode 1:

qcrypto_pkcs7_convert_sig_pem() – converts a signature from DER to PEM format
qcrypto_x509_verify_sig() – verifies the provided data against the given signature

These functions enable basic signature verification support.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Farhan Ali<alifm@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 crypto/x509-utils.c         | 108 ++++++++++++++++++++++++++++++++++++
 include/crypto/x509-utils.h |  41 ++++++++++++++
 2 files changed, 149 insertions(+)

diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 906d5e5e87..2b991ff9ac 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -16,6 +16,7 @@
 #include <gnutls/gnutls.h>
 #include <gnutls/crypto.h>
 #include <gnutls/x509.h>
+#include <gnutls/pkcs7.h>
 
 static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
     [QCRYPTO_HASH_ALGO_MD5] = GNUTLS_DIG_MD5,
@@ -335,6 +336,96 @@ int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
     return curve_id == GNUTLS_ECC_CURVE_SECP521R1;
 }
 
+int qcrypto_pkcs7_convert_sig_pem(uint8_t *sig, size_t sig_size,
+                                  uint8_t **result, size_t *resultlen,
+                                  Error **errp)
+{
+    int ret = -1;
+    int rc;
+    gnutls_pkcs7_t signature;
+    gnutls_datum_t sig_datum_der = {.data = sig, .size = sig_size};
+    gnutls_datum_t sig_datum_pem = {.data = NULL, .size = 0};
+
+    rc = gnutls_pkcs7_init(&signature);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize pkcs7 data: %s", gnutls_strerror(rc));
+        return ret;
+    }
+
+    rc = gnutls_pkcs7_import(signature, &sig_datum_der, GNUTLS_X509_FMT_DER);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import signature: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = gnutls_pkcs7_export2(signature, GNUTLS_X509_FMT_PEM, &sig_datum_pem);
+    if (rc != 0) {
+        error_setg(errp, "Failed to convert signature to PEM format: %s",
+                   gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    *resultlen = sig_datum_pem.size;
+    *result = g_memdup2(sig_datum_pem.data, sig_datum_pem.size);
+
+    ret = 0;
+
+cleanup:
+    gnutls_pkcs7_deinit(signature);
+    gnutls_free(sig_datum_pem.data);
+    return ret;
+}
+
+int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
+                            uint8_t *comp, size_t comp_size,
+                            uint8_t *sig, size_t sig_size, Error **errp)
+{
+    int rc;
+    int ret = -1;
+    gnutls_x509_crt_t crt = NULL;
+    gnutls_pkcs7_t signature = NULL;
+    gnutls_datum_t cert_datum = {.data = cert, .size = cert_size};
+    gnutls_datum_t data_datum = {.data = comp, .size = comp_size};
+    gnutls_datum_t sig_datum = {.data = sig, .size = sig_size};
+
+    rc = gnutls_x509_crt_init(&crt);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_import(crt, &cert_datum, GNUTLS_X509_FMT_PEM);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = gnutls_pkcs7_init(&signature);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initialize pkcs7 data: %s", gnutls_strerror(rc));
+        goto cleanup;
+     }
+
+    rc = gnutls_pkcs7_import(signature, &sig_datum , GNUTLS_X509_FMT_PEM);
+    if (rc != 0) {
+        error_setg(errp, "Failed to import signature: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
+    if (rc != 0) {
+        error_setg(errp, "Failed to verify signature: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    gnutls_pkcs7_deinit(signature);
+    return ret;
+}
+
 #else /* ! CONFIG_GNUTLS */
 
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
@@ -378,4 +469,21 @@ int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
     return -1;
 }
 
+int qcrypto_pkcs7_convert_sig_pem(uint8_t *sig, size_t sig_size,
+                                  uint8_t **result,
+                                  size_t *resultlen,
+                                  Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to export pkcs7 signature");
+    return -1;
+}
+
+int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
+                            uint8_t *comp, size_t comp_size,
+                            uint8_t *sig, size_t sig_size, Error **errp)
+{
+    error_setg(errp, "GNUTLS is required for signature-verification support");
+    return -1;
+}
+
 #endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 6040894a46..02e937b14a 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -91,4 +91,45 @@ int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
  */
 int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp);
 
+/**
+ * qcrypto_pkcs7_convert_sig_pem
+ * @sig: pointer to the PKCS#7 signature in DER format
+ * @sig_size: size of the signature
+ * @result: output location for the allocated buffer for the signature in
+ *          PEM format
+ *          (the function allocates memory which must be freed by the caller)
+ * @resultlen: pointer to the size of the buffer
+ *              (will be updated with the actual size of the PEM-encoded
+ *               signature)
+ * @errp: error pointer
+ *
+ * Convert given PKCS#7 @sig from DER to PEM format.
+ *
+ * Returns: 0 if PEM-encoded signature was successfully stored in @result,
+ *         -1 on error.
+ */
+int qcrypto_pkcs7_convert_sig_pem(uint8_t *sig, size_t sig_size,
+                                  uint8_t **result,
+                                  size_t *resultlen,
+                                  Error **errp);
+
+/**
+ * qcrypto_x509_verify_sig
+ * @cert: pointer to the raw certificate data
+ * @cert_size: size of the certificate
+ * @comp: pointer to the component to be verified
+ * @comp_size: size of the component
+ * @sig: pointer to the signature
+ * @sig_size: size of the signature
+ * @errp: error pointer
+ *
+ * Verify the provided @comp against the @sig and @cert.
+ *
+ * Returns: 0 on success,
+ *         -1 on error.
+ */
+int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
+                            uint8_t *comp, size_t comp_size,
+                            uint8_t *sig, size_t sig_size, Error **errp);
+
 #endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 12/30] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (10 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 11/30] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 13/30] s390x/ipl: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
                   ` (17 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

From: Collin Walling <walling@linux.ibm.com>

DIAG 508 subcode 1 performs signature-verification on signed components.
A signed component may be a Linux kernel image, or any other signed
binary. **Verification of initrd is not supported.**

The instruction call expects two item-pairs: an address of a device
component, an address of the analogous signature file (in PKCS#7 DER format),
and their respective lengths. All of this data should be encapsulated
within a Diag508SigVerifBlock.

The DIAG handler will read from the provided addresses
to retrieve the necessary data, parse the signature file, then
perform the signature-verification. Because there is no way to
correlate a specific certificate to a component, each certificate
in the store is tried until either verification succeeds, or all
certs have been exhausted.

A return code of 1 indicates success, and the index and length of the
corresponding certificate will be set in the Diag508SigVerifBlock.
The following values indicate failure:

	0x0102: no certificates are available in the store
	0x0202: component data is invalid
	0x0302: PKCS#7 format signature is invalid
	0x0402: signature-verification failed
	0x0502: length of Diag508SigVerifBlock is invalid

Signed-off-by: Collin Walling <walling@linux.ibm.com>
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Reviewed-by: Farhan Ali<alifm@linux.ibm.com>
---
 docs/specs/s390x-secure-ipl.rst |  17 +++++
 include/hw/s390x/ipl/diag508.h  |  30 +++++++++
 target/s390x/diag.c             | 111 +++++++++++++++++++++++++++++++-
 3 files changed, 157 insertions(+), 1 deletion(-)

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 9a3decef69..32add09dc1 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -78,3 +78,20 @@ that requires assistance from QEMU.
 
 Subcode 0 - query installed subcodes
     Returns a 64-bit mask indicating which subcodes are supported.
+
+Subcode 1 - perform signature verification
+    Perform signature-verification on a signed component, using certificates
+    from the certificate store and leveraging qcrypto libraries to perform
+    this operation.
+
+    Note: verification of initrd is not supported.
+
+    A return code of 1 indicates success, and the index and length of the
+    corresponding certificate will be set in the Diag508SigVerifBlock.
+    The following values indicate failure:
+
+    * ``0x0102``: no certificates are available in the store
+    * ``0x0202``: component data is invalid
+    * ``0x0302``: PKCS#7 format signature is invalid
+    * ``0x0402``: signature-verification failed
+    * ``0x0502``: length of Diag508SigVerifBlock is invalid
diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
index 6281ad8299..8a147f32a0 100644
--- a/include/hw/s390x/ipl/diag508.h
+++ b/include/hw/s390x/ipl/diag508.h
@@ -11,5 +11,35 @@
 #define S390X_DIAG508_H
 
 #define DIAG_508_SUBC_QUERY_SUBC    0x0000
+#define DIAG_508_SUBC_SIG_VERIF     0x8000
+
+#define DIAG_508_RC_OK              0x0001
+#define DIAG_508_RC_NO_CERTS        0x0102
+#define DIAG_508_RC_INVAL_COMP_DATA 0x0202
+#define DIAG_508_RC_INVAL_PKCS7_SIG 0x0302
+#define DIAG_508_RC_FAIL_VERIF      0x0402
+#define DIAG_508_RC_INVAL_LEN       0x0502
+
+/*
+ * Maximum componenet and signature sizes for current secure boot implementation
+ * Not architecturally defined and may need to revisit if increased
+ */
+#define DIAG_508_MAX_COMP_LEN      0x10000000
+#define DIAG_508_MAX_SIG_LEN       4096
+
+struct Diag508SigVerifBlock {
+    uint32_t length;
+    uint8_t reserved0[3];
+    uint8_t version;
+    uint32_t reserved[2];
+    uint8_t cert_store_index;
+    uint8_t reserved1[7];
+    uint64_t cert_len;
+    uint64_t comp_len;
+    uint64_t comp_addr;
+    uint64_t sig_len;
+    uint64_t sig_addr;
+};
+typedef struct Diag508SigVerifBlock Diag508SigVerifBlock;
 
 #endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 6d9bdee7e3..1dae4ada07 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -642,9 +642,110 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
     }
 }
 
+static bool diag_508_verify_sig(uint8_t *cert, size_t cert_size,
+                                uint8_t *comp, size_t comp_size,
+                                uint8_t *sig, size_t sig_size)
+{
+    g_autofree uint8_t *sig_pem = NULL;
+    size_t sig_size_pem;
+    int rc;
+
+    /*
+     * PKCS#7 signature with DER format
+     * Convert to PEM format for signature verification
+     *
+     * Ignore errors during qcrypto signature format conversion and verification
+     * Return false on any error, treating it as a verification failure
+     */
+    rc = qcrypto_pkcs7_convert_sig_pem(sig, sig_size, &sig_pem, &sig_size_pem, NULL);
+    if (rc < 0) {
+        return false;
+    }
+
+    rc = qcrypto_x509_verify_sig(cert, cert_size,
+                                 comp, comp_size,
+                                 sig_pem, sig_size_pem, NULL);
+    if (rc < 0) {
+        return false;
+    }
+
+    return true;
+}
+
+static int handle_diag508_sig_verif(uint64_t addr)
+{
+    int verified;
+    uint32_t svb_len;
+    uint64_t comp_len, comp_addr;
+    uint64_t sig_len, sig_addr;
+    g_autofree uint8_t *comp = NULL;
+    g_autofree uint8_t *sig = NULL;
+    g_autofree Diag508SigVerifBlock *svb = NULL;
+    size_t svb_size = sizeof(Diag508SigVerifBlock);
+    S390IPLCertificateStore *cs = s390_ipl_get_certificate_store();
+
+    if (!cs->count) {
+        return DIAG_508_RC_NO_CERTS;
+    }
+
+    svb = g_new0(Diag508SigVerifBlock, 1);
+    cpu_physical_memory_read(addr, svb, svb_size);
+
+    svb_len = be32_to_cpu(svb->length);
+    if (svb_len != svb_size) {
+        return DIAG_508_RC_INVAL_LEN;
+    }
+
+    comp_len = be64_to_cpu(svb->comp_len);
+    comp_addr = be64_to_cpu(svb->comp_addr);
+    sig_len = be64_to_cpu(svb->sig_len);
+    sig_addr = be64_to_cpu(svb->sig_addr);
+
+    if (!comp_len || !comp_addr || comp_len > DIAG_508_MAX_COMP_LEN) {
+        if (comp_len > DIAG_508_MAX_COMP_LEN) {
+            warn_report("DIAG 0x508: component length %lu exceeds current maximum %u",
+                        comp_len, DIAG_508_MAX_COMP_LEN);
+        }
+        return DIAG_508_RC_INVAL_COMP_DATA;
+    }
+
+    if (!sig_len || !sig_addr || sig_len > DIAG_508_MAX_SIG_LEN) {
+        if (sig_len > DIAG_508_MAX_SIG_LEN) {
+            warn_report("DIAG 0x508: signature length %lu exceeds current maximum %u",
+                        sig_len, DIAG_508_MAX_SIG_LEN);
+        }
+        return DIAG_508_RC_INVAL_PKCS7_SIG;
+    }
+
+    comp = g_malloc0(comp_len);
+    cpu_physical_memory_read(comp_addr, comp, comp_len);
+
+    sig = g_malloc0(sig_len);
+    cpu_physical_memory_read(sig_addr, sig, sig_len);
+
+    for (int i = 0; i < cs->count; i++) {
+        verified = diag_508_verify_sig(cs->certs[i].raw,
+                                       cs->certs[i].size,
+                                       comp, comp_len,
+                                       sig, sig_len);
+        if (verified) {
+            svb->cert_store_index = i;
+            svb->cert_len = cpu_to_be64(cs->certs[i].der_size);
+            cpu_physical_memory_write(addr, svb, svb_size);
+            return DIAG_508_RC_OK;
+       }
+    }
+
+    return DIAG_508_RC_FAIL_VERIF;
+}
+
+QEMU_BUILD_BUG_MSG(sizeof(Diag508SigVerifBlock) != 64,
+                   "size of Diag508SigVerifBlock is wrong");
+
 void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 {
     uint64_t subcode = env->regs[r3];
+    uint64_t addr = env->regs[r1];
     int rc;
 
     if (env->psw.mask & PSW_MASK_PSTATE) {
@@ -659,7 +760,15 @@ void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 
     switch (subcode) {
     case DIAG_508_SUBC_QUERY_SUBC:
-        rc = 0;
+        rc = DIAG_508_SUBC_SIG_VERIF;
+        break;
+    case DIAG_508_SUBC_SIG_VERIF:
+        if (!diag_parm_addr_valid(addr, sizeof(Diag508SigVerifBlock), true)) {
+            s390_program_interrupt(env, PGM_ADDRESSING, ra);
+            return;
+        }
+
+        rc = handle_diag508_sig_verif(addr);
         break;
     default:
         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 13/30] s390x/ipl: Introduce IPL Information Report Block (IIRB)
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (11 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 12/30] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-13 20:00   ` Collin Walling
  2026-03-05 22:41 ` [PATCH v9 14/30] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
                   ` (16 subsequent siblings)
  29 siblings, 1 reply; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

The IPL information report block (IIRB) contains information used
to locate IPL records and to report the results of signature verification
of one or more secure components of the load device.

IIRB is stored immediately following the IPL Parameter Block. Results on
component verification in any case (failure or success) are stored.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Farhan Ali<alifm@linux.ibm.com>
---
 docs/specs/s390x-secure-ipl.rst | 14 ++++++++
 include/hw/s390x/ipl/qipl.h     | 59 +++++++++++++++++++++++++++++++++
 2 files changed, 73 insertions(+)

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 32add09dc1..fc37de52b9 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -95,3 +95,17 @@ Subcode 1 - perform signature verification
     * ``0x0302``: PKCS#7 format signature is invalid
     * ``0x0402``: signature-verification failed
     * ``0x0502``: length of Diag508SigVerifBlock is invalid
+
+IPL Information Report Block
+----------------------------
+
+The IPL Parameter Block (IPLPB), utilized for IPL operation, is extended with an
+IPL Information Report Block (IIRB), which contains the results from secure IPL
+operations such as:
+
+* component data
+* verification results
+* certificate data
+
+The guest's kernel inspects the IIRB and uses the certificate data it contains
+to build the keyring.
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index e505f44020..0f1f55c428 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -126,4 +126,63 @@ union IplParameterBlock {
 } QEMU_PACKED;
 typedef union IplParameterBlock IplParameterBlock;
 
+struct IplInfoReportBlockHeader {
+    uint32_t len;
+    uint8_t  flags;
+    uint8_t  reserved1[11];
+};
+typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
+
+struct IplInfoBlockHeader {
+    uint32_t len;
+    uint8_t  type;
+    uint8_t  reserved1[11];
+};
+typedef struct IplInfoBlockHeader IplInfoBlockHeader;
+
+enum IplInfoBlockType {
+    IPL_INFO_BLOCK_TYPE_CERTIFICATES = 1,
+    IPL_INFO_BLOCK_TYPE_COMPONENTS = 2,
+};
+
+struct IplSignatureCertificateEntry {
+    uint64_t addr;
+    uint64_t len;
+};
+typedef struct IplSignatureCertificateEntry IplSignatureCertificateEntry;
+
+struct IplSignatureCertificateList {
+    IplInfoBlockHeader            ipl_info_header;
+    IplSignatureCertificateEntry  cert_entries[MAX_CERTIFICATES];
+};
+typedef struct IplSignatureCertificateList IplSignatureCertificateList;
+
+#define S390_IPL_DEV_COMP_FLAG_SC  0x80
+#define S390_IPL_DEV_COMP_FLAG_CSV 0x40
+
+struct IplDeviceComponentEntry {
+    uint64_t addr;
+    uint64_t len;
+    uint8_t  flags;
+    uint8_t  reserved1[5];
+    uint16_t cert_index;
+    uint8_t  reserved2[8];
+};
+typedef struct IplDeviceComponentEntry IplDeviceComponentEntry;
+
+struct IplDeviceComponentList {
+    IplInfoBlockHeader       ipl_info_header;
+    IplDeviceComponentEntry  device_entries[MAX_CERTIFICATES];
+};
+typedef struct IplDeviceComponentList IplDeviceComponentList;
+
+#define COMP_LIST_MAX   sizeof(IplDeviceComponentList)
+#define CERT_LIST_MAX   sizeof(IplSignatureCertificateList)
+
+struct IplInfoReportBlock {
+    IplInfoReportBlockHeader     hdr;
+    uint8_t                      info_blks[COMP_LIST_MAX + CERT_LIST_MAX];
+};
+typedef struct IplInfoReportBlock IplInfoReportBlock;
+
 #endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 14/30] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (12 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 13/30] s390x/ipl: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 15/30] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
                   ` (15 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Define a memory space for both IPL Parameter Block (IPLB) and
IPL Information Report Block (IIRB) since IIRB is stored immediately
following IPLB.

Convert IPLB to pointer and it points to the start of the defined memory space.
IIRB points to the end of IPLB.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 include/hw/s390x/ipl/qipl.h |  6 ++++++
 pc-bios/s390-ccw/iplb.h     |  5 +++--
 pc-bios/s390-ccw/jump2ipl.c |  6 +++---
 pc-bios/s390-ccw/main.c     | 34 +++++++++++++++++++---------------
 pc-bios/s390-ccw/netmain.c  |  8 ++++----
 5 files changed, 35 insertions(+), 24 deletions(-)

diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 0f1f55c428..f5e63a2fdb 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -185,4 +185,10 @@ struct IplInfoReportBlock {
 };
 typedef struct IplInfoReportBlock IplInfoReportBlock;
 
+struct IplBlocks {
+    IplParameterBlock   iplb;
+    IplInfoReportBlock  iirb;
+};
+typedef struct IplBlocks IplBlocks;
+
 #endif
diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index 08f259ff31..fefca65ac6 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -20,8 +20,9 @@
 #include <string.h>
 
 extern QemuIplParameters qipl;
-extern IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE)));
+extern IplParameterBlock *iplb;
 extern bool have_iplb;
+extern IplBlocks ipl_data;
 
 #define S390_IPL_TYPE_FCP 0x00
 #define S390_IPL_TYPE_CCW 0x02
@@ -65,7 +66,7 @@ static inline bool load_next_iplb(void)
 
     qipl.index++;
     next_iplb = (IplParameterBlock *) qipl.next_iplb;
-    memcpy(&iplb, next_iplb, sizeof(IplParameterBlock));
+    memcpy(iplb, next_iplb, sizeof(IplParameterBlock));
 
     qipl.chain_len--;
     qipl.next_iplb = qipl.next_iplb + sizeof(IplParameterBlock);
diff --git a/pc-bios/s390-ccw/jump2ipl.c b/pc-bios/s390-ccw/jump2ipl.c
index 86321d0f46..fa2ca5cbe1 100644
--- a/pc-bios/s390-ccw/jump2ipl.c
+++ b/pc-bios/s390-ccw/jump2ipl.c
@@ -43,11 +43,11 @@ int jump_to_IPL_code(uint64_t address)
      * The IPLB for QEMU SCSI type devices must be rebuilt during re-ipl. The
      * iplb.devno is set to the boot position of the target SCSI device.
      */
-    if (iplb.pbt == S390_IPL_TYPE_QEMU_SCSI) {
-        iplb.devno = qipl.index;
+    if (iplb->pbt == S390_IPL_TYPE_QEMU_SCSI) {
+        iplb->devno = qipl.index;
     }
 
-    if (have_iplb && !set_iplb(&iplb)) {
+    if (have_iplb && !set_iplb(iplb)) {
         panic("Failed to set IPLB");
     }
 
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 76bf743900..819f053009 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -22,7 +22,9 @@
 static SubChannelId blk_schid = { .one = 1 };
 static char loadparm_str[LOADPARM_LEN + 1];
 QemuIplParameters qipl;
-IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE)));
+/* Ensure that IPLB and IIRB are page aligned and sequential in memory */
+IplBlocks ipl_data __attribute__((__aligned__(PAGE_SIZE)));
+IplParameterBlock *iplb;
 bool have_iplb;
 static uint16_t cutype;
 LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */
@@ -51,7 +53,7 @@ void write_subsystem_identification(void)
 void write_iplb_location(void)
 {
     if (cutype == CU_TYPE_VIRTIO && virtio_get_device_type() != VIRTIO_ID_NET) {
-        lowcore->ptr_iplb = ptr2u32(&iplb);
+        lowcore->ptr_iplb = ptr2u32(iplb);
     }
 }
 
@@ -162,7 +164,7 @@ static void menu_setup(void)
         return;
     }
 
-    switch (iplb.pbt) {
+    switch (iplb->pbt) {
     case S390_IPL_TYPE_CCW:
     case S390_IPL_TYPE_QEMU_SCSI:
         menu_set_parms(qipl.qipl_flags & BOOT_MENU_FLAG_MASK,
@@ -191,8 +193,8 @@ static void boot_setup(void)
 {
     char lpmsg[] = "LOADPARM=[________]\n";
 
-    if (have_iplb && memcmp(iplb.loadparm, NO_LOADPARM, LOADPARM_LEN) != 0) {
-        ebcdic_to_ascii((char *) iplb.loadparm, loadparm_str, LOADPARM_LEN);
+    if (have_iplb && memcmp(iplb->loadparm, NO_LOADPARM, LOADPARM_LEN) != 0) {
+        ebcdic_to_ascii((char *) iplb->loadparm, loadparm_str, LOADPARM_LEN);
     } else {
         sclp_get_loadparm_ascii(loadparm_str);
     }
@@ -216,21 +218,21 @@ static bool find_boot_device(void)
     VDev *vdev = virtio_get_device();
     bool found = false;
 
-    switch (iplb.pbt) {
+    switch (iplb->pbt) {
     case S390_IPL_TYPE_CCW:
         vdev->scsi_device_selected = false;
-        debug_print_int("device no. ", iplb.ccw.devno);
-        blk_schid.ssid = iplb.ccw.ssid & 0x3;
+        debug_print_int("device no. ", iplb->ccw.devno);
+        blk_schid.ssid = iplb->ccw.ssid & 0x3;
         debug_print_int("ssid ", blk_schid.ssid);
-        found = find_subch(iplb.ccw.devno);
+        found = find_subch(iplb->ccw.devno);
         break;
     case S390_IPL_TYPE_QEMU_SCSI:
         vdev->scsi_device_selected = true;
-        vdev->selected_scsi_device.channel = iplb.scsi.channel;
-        vdev->selected_scsi_device.target = iplb.scsi.target;
-        vdev->selected_scsi_device.lun = iplb.scsi.lun;
-        blk_schid.ssid = iplb.scsi.ssid & 0x3;
-        found = find_subch(iplb.scsi.devno);
+        vdev->selected_scsi_device.channel = iplb->scsi.channel;
+        vdev->selected_scsi_device.target = iplb->scsi.target;
+        vdev->selected_scsi_device.lun = iplb->scsi.lun;
+        blk_schid.ssid = iplb->scsi.ssid & 0x3;
+        found = find_subch(iplb->scsi.devno);
         break;
     default:
         puts("Unsupported IPLB");
@@ -311,10 +313,12 @@ static void probe_boot_device(void)
 
 void main(void)
 {
+    iplb = &ipl_data.iplb;
+
     copy_qipl();
     sclp_setup();
     css_setup();
-    have_iplb = store_iplb(&iplb);
+    have_iplb = store_iplb(iplb);
     if (!have_iplb) {
         boot_setup();
         probe_boot_device();
diff --git a/pc-bios/s390-ccw/netmain.c b/pc-bios/s390-ccw/netmain.c
index a9521dff41..457fbc3095 100644
--- a/pc-bios/s390-ccw/netmain.c
+++ b/pc-bios/s390-ccw/netmain.c
@@ -528,11 +528,11 @@ static bool virtio_setup(void)
      */
     enable_mss_facility();
 
-    if (have_iplb || store_iplb(&iplb)) {
-        IPL_assert(iplb.pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
-        dev_no = iplb.ccw.devno;
+    if (have_iplb || store_iplb(iplb)) {
+        IPL_assert(iplb->pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
+        dev_no = iplb->ccw.devno;
         debug_print_int("device no. ", dev_no);
-        net_schid.ssid = iplb.ccw.ssid & 0x3;
+        net_schid.ssid = iplb->ccw.ssid & 0x3;
         debug_print_int("ssid ", net_schid.ssid);
         found = find_net_dev(&schib, dev_no);
     } else {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 15/30] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (13 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 14/30] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 16/30] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
                   ` (14 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Add IPIB flags to IPL Parameter Block to determine if IPL needs to
perform securely and if IPL Information Report Block (IIRB) exists.

Move DIAG308 flags to a separated header file and add flags for secure IPL.

Secure boot in audit mode will perform if certificate(s) exist in the
key store. IIRB will exist and results of verification will be stored in
IIRB.

To ensure proper alignment of the IIRB and prevent overlap, set
iplb->len to the maximum length of the IPLB, allowing alignment
constraints to be determined based on its size.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 hw/s390x/ipl.c                 | 21 +++++++++++++++++++++
 hw/s390x/ipl.h                 | 18 +-----------------
 include/hw/s390x/ipl/diag308.h | 34 ++++++++++++++++++++++++++++++++++
 include/hw/s390x/ipl/qipl.h    |  5 ++++-
 4 files changed, 60 insertions(+), 18 deletions(-)
 create mode 100644 include/hw/s390x/ipl/diag308.h

diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index ea108fe370..b66dfd06bd 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -433,6 +433,13 @@ S390IPLCertificateStore *s390_ipl_get_certificate_store(void)
     return &ipl->cert_store;
 }
 
+static bool s390_has_certificate(void)
+{
+    S390IPLState *ipl = get_ipl_device();
+
+    return ipl->cert_store.count > 0;
+}
+
 static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
 {
     CcwDevice *ccw_dev = NULL;
@@ -490,6 +497,20 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
         s390_ipl_convert_loadparm((char *)lp, iplb->loadparm);
         iplb->flags |= DIAG308_FLAGS_LP_VALID;
 
+        /*
+         * Secure boot in audit mode will perform
+         * if certificate(s) exist in the key store.
+         *
+         * IPL Information Report Block (IIRB) will exist
+         * for secure boot in audit mode.
+         *
+         * Results of secure boot will be stored in IIRB.
+         */
+        if (s390_has_certificate()) {
+            iplb->hdr_flags |= DIAG308_IPIB_FLAGS_IPLIR;
+            iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
+        }
+
         return true;
     }
 
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 37f311474d..f0d44a87dd 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -23,7 +23,6 @@
 #include "qom/object.h"
 #include "target/s390x/kvm/pv.h"
 
-#define DIAG308_FLAGS_LP_VALID 0x80
 #define MAX_BOOT_DEVS 8 /* Max number of devices that may have a bootindex */
 
 void s390_ipl_convert_loadparm(char *ascii_lp, uint8_t *ebcdic_lp);
@@ -90,22 +89,6 @@ struct S390IPLState {
 };
 QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
 
-#define DIAG_308_RC_OK              0x0001
-#define DIAG_308_RC_NO_CONF         0x0102
-#define DIAG_308_RC_INVALID         0x0402
-#define DIAG_308_RC_NO_PV_CONF      0x0902
-#define DIAG_308_RC_INVAL_FOR_PV    0x0a02
-
-#define DIAG308_RESET_MOD_CLR       0
-#define DIAG308_RESET_LOAD_NORM     1
-#define DIAG308_LOAD_CLEAR          3
-#define DIAG308_LOAD_NORMAL_DUMP    4
-#define DIAG308_SET                 5
-#define DIAG308_STORE               6
-#define DIAG308_PV_SET              8
-#define DIAG308_PV_STORE            9
-#define DIAG308_PV_START            10
-
 #define S390_IPL_TYPE_FCP 0x00
 #define S390_IPL_TYPE_CCW 0x02
 #define S390_IPL_TYPE_PV 0x05
@@ -116,6 +99,7 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
 #define S390_IPLB_MIN_CCW_LEN 200
 #define S390_IPLB_MIN_FCP_LEN 384
 #define S390_IPLB_MIN_QEMU_SCSI_LEN 200
+#define S390_IPLB_MAX_LEN 4096
 
 static inline bool iplb_valid_len(IplParameterBlock *iplb)
 {
diff --git a/include/hw/s390x/ipl/diag308.h b/include/hw/s390x/ipl/diag308.h
new file mode 100644
index 0000000000..6e62f29215
--- /dev/null
+++ b/include/hw/s390x/ipl/diag308.h
@@ -0,0 +1,34 @@
+/*
+ * S/390 DIAGNOSE 308 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG308_H
+#define S390X_DIAG308_H
+
+#define DIAG_308_RC_OK              0x0001
+#define DIAG_308_RC_NO_CONF         0x0102
+#define DIAG_308_RC_INVALID         0x0402
+#define DIAG_308_RC_NO_PV_CONF      0x0902
+#define DIAG_308_RC_INVAL_FOR_PV    0x0a02
+
+#define DIAG308_RESET_MOD_CLR       0
+#define DIAG308_RESET_LOAD_NORM     1
+#define DIAG308_LOAD_CLEAR          3
+#define DIAG308_LOAD_NORMAL_DUMP    4
+#define DIAG308_SET                 5
+#define DIAG308_STORE               6
+#define DIAG308_PV_SET              8
+#define DIAG308_PV_STORE            9
+#define DIAG308_PV_START            10
+
+#define DIAG308_FLAGS_LP_VALID 0x80
+
+#define DIAG308_IPIB_FLAGS_SIPL 0x40
+#define DIAG308_IPIB_FLAGS_IPLIR 0x20
+
+#endif
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index f5e63a2fdb..1b6cb3231d 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -12,6 +12,8 @@
 #ifndef S390X_QIPL_H
 #define S390X_QIPL_H
 
+#include "diag308.h"
+
 /* Boot Menu flags */
 #define QIPL_FLAG_BM_OPTS_CMD   0x80
 #define QIPL_FLAG_BM_OPTS_ZIPL  0x40
@@ -103,7 +105,8 @@ typedef struct IplBlockQemuScsi IplBlockQemuScsi;
 union IplParameterBlock {
     struct {
         uint32_t len;
-        uint8_t  reserved0[3];
+        uint8_t  hdr_flags;
+        uint8_t  reserved0[2];
         uint8_t  version;
         uint32_t blk0_len;
         uint8_t  pbt;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 16/30] s390x: Guest support for Secure-IPL Facility
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (14 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 15/30] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 17/30] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
                   ` (13 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Introduce Secure-IPL (SIPL) facility.

Use fac_ipl to represent bytes 136 and 137 for IPL device facilities
of the SCLP Read Info block.

Availability of SIPL facility is determined by byte 136 bit 1 of the
SCLP Read Info block. Byte 136's facilities cannot be represented
without the availability of the extended-length-SCCB, so add it as a
check for consistency.

Secure IPL is not available for guests under protected virtualization.

This feature is available starting with the gen16 CPU model.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 hw/s390x/sclp.c                     | 2 ++
 include/hw/s390x/sclp.h             | 4 +++-
 target/s390x/cpu_features.c         | 4 ++++
 target/s390x/cpu_features.h         | 1 +
 target/s390x/cpu_features_def.h.inc | 3 +++
 target/s390x/cpu_models.c           | 2 ++
 target/s390x/gen-features.c         | 2 ++
 target/s390x/kvm/kvm.c              | 3 +++
 8 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
index b9c3983df1..666bae33f0 100644
--- a/hw/s390x/sclp.c
+++ b/hw/s390x/sclp.c
@@ -146,6 +146,8 @@ static void read_SCP_info(SCLPDevice *sclp, SCCB *sccb)
     if (s390_has_feat(S390_FEAT_EXTENDED_LENGTH_SCCB)) {
         s390_get_feat_block(S390_FEAT_TYPE_SCLP_FAC134,
                             &read_info->fac134);
+        s390_get_feat_block(S390_FEAT_TYPE_SCLP_FAC_IPL,
+                            read_info->fac_ipl);
     }
 
     read_info->facilities = cpu_to_be64(SCLP_HAS_CPU_INFO |
diff --git a/include/hw/s390x/sclp.h b/include/hw/s390x/sclp.h
index ddc61f1c21..a9595d8007 100644
--- a/include/hw/s390x/sclp.h
+++ b/include/hw/s390x/sclp.h
@@ -136,7 +136,9 @@ typedef struct ReadInfo {
     uint32_t hmfai;
     uint8_t  _reserved7[134 - 128];     /* 128-133 */
     uint8_t  fac134;
-    uint8_t  _reserved8[144 - 135];     /* 135-143 */
+    uint8_t  _reserved8;
+    uint8_t  fac_ipl[2];                /* 136-137 */
+    uint8_t  _reserved9[144 - 138];     /* 138-143 */
     struct CPUEntry entries[];
     /*
      * When the Extended-Length SCCB (ELS) feature is enabled the
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 436471f4b4..200bd8c15b 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -119,6 +119,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
      * Some facilities are not available for CPUs in protected mode:
      * - All SIE facilities because SIE is not available
      * - DIAG318
+     * - Secure IPL Facility
      *
      * As VMs can move in and out of protected mode the CPU model
      * doesn't protect us from that problem because it is only
@@ -149,6 +150,9 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
         clear_be_bit(s390_feat_def(S390_FEAT_DIAG_318)->bit, data);
         clear_be_bit(s390_feat_def(S390_FEAT_CERT_STORE)->bit, data);
         break;
+    case S390_FEAT_TYPE_SCLP_FAC_IPL:
+        clear_be_bit(s390_feat_def(S390_FEAT_SIPL)->bit, data);
+        break;
     default:
         return;
     }
diff --git a/target/s390x/cpu_features.h b/target/s390x/cpu_features.h
index 5635839d03..b038198555 100644
--- a/target/s390x/cpu_features.h
+++ b/target/s390x/cpu_features.h
@@ -24,6 +24,7 @@ typedef enum {
     S390_FEAT_TYPE_SCLP_CONF_CHAR,
     S390_FEAT_TYPE_SCLP_CONF_CHAR_EXT,
     S390_FEAT_TYPE_SCLP_FAC134,
+    S390_FEAT_TYPE_SCLP_FAC_IPL,
     S390_FEAT_TYPE_SCLP_CPU,
     S390_FEAT_TYPE_MISC,
     S390_FEAT_TYPE_PLO,
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index 2976ecd0ee..bcf8a666e4 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -140,6 +140,9 @@ DEF_FEAT(SIE_IBS, "ibs", SCLP_CONF_CHAR_EXT, 10, "SIE: Interlock-and-broadcast-s
 DEF_FEAT(DIAG_318, "diag318", SCLP_FAC134, 0, "Control program name and version codes")
 DEF_FEAT(CERT_STORE, "cstore", SCLP_FAC134, 5, "Certificate Store functions")
 
+/* Features exposed via SCLP SCCB Facilities byte 136 - 137 (bit numbers relative to byte-136) */
+DEF_FEAT(SIPL, "sipl", SCLP_FAC_IPL, 1, "Secure-IPL facility")
+
 /* Features exposed via SCLP CPU info. */
 DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
 DEF_FEAT(SIE_SKEY, "skey", SCLP_CPU, 5, "SIE: Storage-key facility")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index 6b8471700e..f99536ef9a 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -263,6 +263,7 @@ bool s390_has_feat(S390Feat feat)
         case S390_FEAT_SIE_CMMA:
         case S390_FEAT_SIE_PFMFI:
         case S390_FEAT_SIE_IBS:
+        case S390_FEAT_SIPL:
         case S390_FEAT_CONFIGURATION_TOPOLOGY:
             return false;
             break;
@@ -507,6 +508,7 @@ static void check_consistency(const S390CPUModel *model)
         { S390_FEAT_AP_QUEUE_INTERRUPT_CONTROL, S390_FEAT_AP },
         { S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
         { S390_FEAT_CERT_STORE, S390_FEAT_EXTENDED_LENGTH_SCCB },
+        { S390_FEAT_SIPL, S390_FEAT_EXTENDED_LENGTH_SCCB },
         { S390_FEAT_NNPA, S390_FEAT_VECTOR },
         { S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
         { S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index 6c20c3a862..bd2060ab93 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -721,6 +721,7 @@ static uint16_t full_GEN16_GA1[] = {
     S390_FEAT_UV_FEAT_AP,
     S390_FEAT_UV_FEAT_AP_INTR,
     S390_FEAT_CERT_STORE,
+    S390_FEAT_SIPL,
 };
 
 static uint16_t full_GEN17_GA1[] = {
@@ -922,6 +923,7 @@ static uint16_t qemu_MAX[] = {
     S390_FEAT_PRNO_TRNG,
     S390_FEAT_EXTENDED_LENGTH_SCCB,
     S390_FEAT_CERT_STORE,
+    S390_FEAT_SIPL,
 };
 
 /****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index cba431688b..40197cca7a 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2518,6 +2518,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
 
     set_bit(S390_FEAT_CERT_STORE, model->features);
 
+    /* Some Secure IPL facilities are emulated by QEMU */
+    set_bit(S390_FEAT_SIPL, model->features);
+
     /* Test for Ultravisor features that influence secure guest behavior */
     query_uv_feat_guest(model->features);
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 17/30] pc-bios/s390-ccw: Refactor zipl_run()
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (15 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 16/30] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 18/30] pc-bios/s390-ccw: Rework zipl_load_segment function Zhuoying Cai
                   ` (12 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Refactor to enhance readability before enabling secure IPL in later
patches.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 pc-bios/s390-ccw/bootmap.c | 51 ++++++++++++++++++++++++--------------
 1 file changed, 33 insertions(+), 18 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 0f8baa0198..22801ca746 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -674,12 +674,42 @@ static int zipl_load_segment(ComponentEntry *entry)
     return 0;
 }
 
+static int zipl_run_normal(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
+{
+    ComponentEntry *entry = *entry_ptr;
+
+    while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
+           entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+
+        /* Secure boot is off, so we skip signature entries */
+        if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+            entry++;
+            continue;
+        }
+
+        if (zipl_load_segment(entry)) {
+            return -1;
+        }
+
+        entry++;
+
+        if ((uint8_t *)&entry[1] > tmp_sec + MAX_SECTOR_SIZE) {
+            puts("Wrong entry value");
+            return -EINVAL;
+        }
+    }
+
+    *entry_ptr = entry;
+    return 0;
+}
+
 /* Run a zipl program */
 static int zipl_run(ScsiBlockPtr *pte)
 {
     ComponentHeader *header;
     ComponentEntry *entry;
     uint8_t tmp_sec[MAX_SECTOR_SIZE];
+    int rc;
 
     if (virtio_read(pte->blockno, tmp_sec)) {
         puts("Cannot read header");
@@ -700,25 +730,10 @@ static int zipl_run(ScsiBlockPtr *pte)
 
     /* Load image(s) into RAM */
     entry = (ComponentEntry *)(&header[1]);
-    while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
-           entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
-
-        /* We don't support secure boot yet, so we skip signature entries */
-        if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
-            entry++;
-            continue;
-        }
-
-        if (zipl_load_segment(entry)) {
-            return -1;
-        }
 
-        entry++;
-
-        if ((uint8_t *)(&entry[1]) > (tmp_sec + MAX_SECTOR_SIZE)) {
-            puts("Wrong entry value");
-            return -EINVAL;
-        }
+    rc = zipl_run_normal(&entry, tmp_sec);
+    if (rc) {
+        return rc;
     }
 
     if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 18/30] pc-bios/s390-ccw: Rework zipl_load_segment function
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (16 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 17/30] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
                   ` (11 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Make the address variable a parameter of zipl_load_segment and return
segment length.

Modify this function to allow the caller to specify a memory address
where segment data should be loaded into.

seg_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.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 pc-bios/s390-ccw/bootmap.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 22801ca746..9a03eab6ed 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -613,19 +613,22 @@ static int ipl_eckd(void)
  * IPL a SCSI disk
  */
 
-static int zipl_load_segment(ComponentEntry *entry)
+/*
+ * Returns: length of the segment on success,
+ *          negative value on error.
+ */
+static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
 {
     const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));
     ScsiBlockPtr *bprs = (void *)sec;
     const int bprs_size = sizeof(sec);
     block_number_t blockno;
-    uint64_t address;
     int i;
     char err_msg[] = "zIPL failed to read BPRS at 0xZZZZZZZZZZZZZZZZ";
     char *blk_no = &err_msg[30]; /* where to print blockno in (those ZZs) */
+    int seg_len = 0;
 
     blockno = entry->data.blockno;
-    address = entry->compdat.load_addr;
 
     debug_print_int("loading segment at block", blockno);
     debug_print_int("addr", address);
@@ -668,10 +671,12 @@ static int zipl_load_segment(ComponentEntry *entry)
                 puts("zIPL load segment failed");
                 return -EIO;
             }
+
+            seg_len += bprs->size * (bprs[i].blockct + 1);
         }
     } while (blockno);
 
-    return 0;
+    return seg_len;
 }
 
 static int zipl_run_normal(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
@@ -687,7 +692,7 @@ static int zipl_run_normal(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
             continue;
         }
 
-        if (zipl_load_segment(entry)) {
+        if (zipl_load_segment(entry, entry->compdat.load_addr) < 0) {
             return -1;
         }
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (17 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 18/30] pc-bios/s390-ccw: Rework zipl_load_segment function Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-17  3:41   ` Collin Walling
  2026-03-05 22:41 ` [PATCH v9 20/30] pc-bios/s390-ccw: Add signed component address overlap checks Zhuoying Cai
                   ` (10 subsequent siblings)
  29 siblings, 1 reply; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

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 <zycai@linux.ibm.com>
---
 docs/system/s390x/secure-ipl.rst |  35 +++
 pc-bios/s390-ccw/Makefile        |   3 +-
 pc-bios/s390-ccw/bootmap.c       |  36 +++-
 pc-bios/s390-ccw/bootmap.h       |  11 +
 pc-bios/s390-ccw/main.c          |   6 +
 pc-bios/s390-ccw/s390-ccw.h      |  27 +++
 pc-bios/s390-ccw/sclp.c          |  38 ++++
 pc-bios/s390-ccw/sclp.h          |   6 +
 pc-bios/s390-ccw/secure-ipl.c    | 357 +++++++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h    | 102 +++++++++
 10 files changed, 618 insertions(+), 3 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..3a19b72085 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -18,3 +18,38 @@ 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
+----------
+
+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..43a661325f 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,7 +737,19 @@ static int zipl_run(ScsiBlockPtr *pte)
     /* Load image(s) into RAM */
     entry = (ComponentEntry *)(&header[1]);
 
-    rc = zipl_run_normal(&entry, tmp_sec);
+    switch (boot_mode) {
+    case ZIPL_BOOT_MODE_SECURE_AUDIT:
+        rc = zipl_run_secure(&entry, tmp_sec);
+        break;
+    case ZIPL_BOOT_MODE_NORMAL:
+        rc = zipl_run_normal(&entry, tmp_sec);
+        break;
+    default:
+        puts("Unknown boot mode");
+        rc = -1;
+        break;
+    }
+
     if (rc) {
         return rc;
     }
@@ -1103,17 +1116,33 @@ static int zipl_load_vscsi(void)
  * IPL starts here
  */
 
+ZiplBootMode get_boot_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;
+}
+
 void zipl_load(void)
 {
     VDev *vdev = virtio_get_device();
 
     if (vdev->is_cdrom) {
+        IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
+                   "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) {
+        IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
+                    "Virtio net boot device does not support secure boot!");
         netmain();
         puts("Failed to IPL from this network!");
         return;
@@ -1124,6 +1153,9 @@ void zipl_load(void)
         return;
     }
 
+    IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
+                "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 819f053009..106cdf9dec 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  "        "
@@ -275,6 +276,9 @@ static void ipl_boot_device(void)
     switch (cutype) {
     case CU_TYPE_DASD_3990:
     case CU_TYPE_DASD_2107:
+        IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
+                    "Passthrough (vfio) CCW device does not support secure boot!");
+
         dasd_ipl(blk_schid, cutype);
         break;
     case CU_TYPE_VIRTIO:
@@ -324,6 +328,8 @@ void main(void)
         probe_boot_device();
     }
 
+    boot_mode = get_boot_mode(iplb->hdr_flags);
+
     while (have_iplb) {
         boot_setup();
         if (have_iplb && find_boot_device()) {
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index b1dc35cded..a0d568696a 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -40,6 +40,22 @@ typedef unsigned long long u64;
                             ((b) == 0 ? (a) : (MIN(a, b))))
 #endif
 
+/*
+ * Round number down to multiple. Requires that d be a power of 2.
+ * Works even if d is a smaller type than n.
+ */
+#ifndef ROUND_DOWN
+#define ROUND_DOWN(n, d) ((n) & -(0 ? (n) : (d)))
+#endif
+
+/*
+ * Round number up to multiple. Requires that d be a power of 2.
+ * Works even if d is a smaller type than n.
+ */
+#ifndef ROUND_UP
+#define ROUND_UP(n, d) ROUND_DOWN((n) + (d) - 1, (d))
+#endif
+
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
 
 #include "cio.h"
@@ -64,6 +80,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 +94,15 @@ int virtio_read(unsigned long sector, void *load_addr);
 /* bootmap.c */
 void zipl_load(void);
 
+typedef enum ZiplBootMode {
+    ZIPL_BOOT_MODE_NORMAL = 0,
+    ZIPL_BOOT_MODE_SECURE_AUDIT = 1,
+} ZiplBootMode;
+
+extern ZiplBootMode boot_mode;
+
+ZiplBootMode get_boot_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..e3b6a1f07e 100644
--- a/pc-bios/s390-ccw/sclp.c
+++ b/pc-bios/s390-ccw/sclp.c
@@ -113,6 +113,44 @@ void sclp_get_loadparm_ascii(char *loadparm)
     }
 }
 
+bool sclp_is_diag320_on(void)
+{
+    ReadInfo *sccb = (void *)_sccb;
+    uint8_t fac134 = 0;
+
+    memset((char *)_sccb, 0, sizeof(ReadInfo));
+    sccb->h.length = SCCB_SIZE;
+    if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
+        fac134 = sccb->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;
+    }
+}
+
+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..8d281c1cea
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -0,0 +1,357 @@
+/*
+ * 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 <zycai@linux.ibm.com>
+ *
+ * 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"
+
+static uint8_t vcb_data[MAX_SECTOR_SIZE * 4] __attribute__((__aligned__(PAGE_SIZE)));
+static uint8_t vcssb_data[VCSSB_MIN_LEN] __attribute__((__aligned__(8)));
+
+VCStorageSizeBlock *zipl_secure_get_vcssb(void)
+{
+    VCStorageSizeBlock *vcssb;
+
+    vcssb = (VCStorageSizeBlock *)vcssb_data;
+    /* avoid retrieving vcssb multiple times */
+    if (vcssb->length >= VCSSB_MIN_LEN) {
+        return vcssb;
+    }
+
+    if (!is_cert_store_facility_supported()) {
+        puts("Certificate Store Facility is not supported by the hypervisor!");
+        return NULL;
+    }
+
+    vcssb->length = VCSSB_MIN_LEN;
+    if (diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI) != DIAG_320_RC_OK) {
+        vcssb->length = 0;
+        return NULL;
+    }
+
+    return vcssb;
+}
+
+static uint32_t get_total_certs_length(void)
+{
+    VCStorageSizeBlock *vcssb;
+
+    vcssb = zipl_secure_get_vcssb();
+    if (vcssb == NULL) {
+        return 0;
+    }
+
+    return vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct * VCE_HEADER_LEN;
+}
+
+static uint32_t request_certificate(uint8_t *cert_addr, uint8_t index)
+{
+    VCStorageSizeBlock *vcssb;
+    VCBlock *vcb;
+    VCEntry *vce;
+    uint32_t cert_len = 0;
+
+    /* 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
+     */
+    vcb = (VCBlock *)vcb_data;
+    vcb->in_len = ROUND_UP(vcssb->max_single_vcb_len, PAGE_SIZE);
+    vcb->first_vc_index = index;
+    vcb->last_vc_index = index;
+
+    if (diag320(vcb, DIAG_320_SUBC_STORE_VC) != DIAG_320_RC_OK) {
+        goto out;
+    }
+
+    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_addr, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
+    memcpy(vcb_data, 0, sizeof(vcb_data));
+
+out:
+    return cert_len;
+}
+
+static void cert_list_add(IplSignatureCertificateList *cert_list, int cert_index,
+                          uint8_t *cert_addr, uint64_t cert_len)
+{
+    if (cert_index > MAX_CERTIFICATES - 1) {
+        printf("Warning: Ignoring cert entry #%d because only %d entries are supported\n",
+                cert_index + 1, MAX_CERTIFICATES);
+        return;
+    }
+
+    cert_list->cert_entries[cert_index].addr = (uint64_t)cert_addr;
+    cert_list->cert_entries[cert_index].len = cert_len;
+    cert_list->ipl_info_header.len += sizeof(cert_list->cert_entries[cert_index]);
+}
+
+static void comp_list_add(IplDeviceComponentList *comp_list, int comp_index,
+                          int cert_index, uint64_t comp_addr,
+                          uint64_t comp_len, uint8_t flags)
+{
+    if (comp_index > MAX_CERTIFICATES - 1) {
+        printf("Warning: Ignoring comp entry #%d because only %d entries are supported\n",
+                comp_index + 1, MAX_CERTIFICATES);
+        return;
+    }
+
+    comp_list->device_entries[comp_index].addr = comp_addr;
+    comp_list->device_entries[comp_index].len = comp_len;
+    comp_list->device_entries[comp_index].flags = flags;
+    comp_list->device_entries[comp_index].cert_index = cert_index;
+    comp_list->ipl_info_header.len += sizeof(comp_list->device_entries[comp_index]);
+}
+
+static void update_iirb(IplDeviceComponentList *comp_list,
+                        IplSignatureCertificateList *cert_list)
+{
+    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 = comp_list->ipl_info_header.len;
+    certs_len = cert_list->ipl_info_header.len;
+    if ((comps_len + certs_len + iirb_hdr_len) > sizeof(IplInfoReportBlock)) {
+        panic("Not enough space to hold all components and certificates in IIRB");
+    }
+
+    /* 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, comp_list, 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, cert_list, certs_len);
+
+    /* Update IIRB length */
+    iirb->hdr.len += certs_len;
+}
+
+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_signature_verif_supported()) {
+        puts("Secure IPL extensions are not supported by the hypervisor!");
+        return false;
+    }
+
+    if (!is_cert_store_facility_supported()) {
+        puts("Certificate Store Facility is not supported by the hypervisor!");
+        return false;
+    }
+
+    return true;
+}
+
+static void init_lists(IplDeviceComponentList *comp_list,
+                       IplSignatureCertificateList *cert_list)
+{
+    comp_list->ipl_info_header.type = IPL_INFO_BLOCK_TYPE_COMPONENTS;
+    comp_list->ipl_info_header.len = sizeof(comp_list->ipl_info_header);
+
+    cert_list->ipl_info_header.type = IPL_INFO_BLOCK_TYPE_CERTIFICATES;
+    cert_list->ipl_info_header.len = sizeof(cert_list->ipl_info_header);
+}
+
+static int zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
+{
+    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;
+    }
+
+    return entry->compdat.sig_info.sig_len;
+}
+
+int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
+{
+    IplDeviceComponentList comp_list = { 0 };
+    IplSignatureCertificateList cert_list = { 0 };
+    ComponentEntry *entry = *entry_ptr;
+    uint8_t *cert_addr = 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;
+    int cert_index;
+    uint8_t flags;
+    bool verified;
+    /*
+     * Keep track of which certificate store indices correspond to the
+     * certificate data entries within the IplSignatureCertificateList to
+     * prevent allocating space for the same certificate multiple times.
+     *
+     * The array index corresponds to the certificate's cert-store index.
+     *
+     * The array value corresponds to the certificate's entry within the
+     * IplSignatureCertificateList (with a value of -1 denoting no entry
+     * exists for the certificate).
+     */
+    int cert_list_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1 };
+    int signed_count = 0;
+
+    if (!secure_ipl_supported()) {
+        panic("Unable to boot in secure/audit mode");
+    }
+
+    init_lists(&comp_list, &cert_list);
+    cert_addr = malloc(get_total_certs_length());
+    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);
+
+            /* default cert index and flags for unverified component */
+            cert_index = -1;
+            flags = S390_IPL_DEV_COMP_FLAG_SC;
+
+            if (verified) {
+                if (cert_list_table[cert_table_idx] == -1) {
+                    if (!request_certificate(cert_addr, cert_table_idx)) {
+                        puts("Could not get certificate");
+                        goto out;
+                    }
+
+                    cert_list_table[cert_table_idx] = cert_entry_idx;
+                    cert_list_add(&cert_list, cert_entry_idx, cert_addr, cert_len);
+
+                    /* increment for the next certificate */
+                    cert_entry_idx++;
+                    cert_addr += cert_len;
+                }
+
+                puts("Verified component");
+                cert_index = cert_list_table[cert_table_idx];
+                flags |= S390_IPL_DEV_COMP_FLAG_CSV;
+            }
+
+            comp_list_add(&comp_list, comp_entry_idx, cert_index,
+                          comp_addr, comp_len, flags);
+
+            if (!verified) {
+                zipl_secure_handle("Could not verify component");
+            }
+
+            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");
+    }
+
+    update_iirb(&comp_list, &cert_list);
+
+    *entry_ptr = entry;
+    free(sig);
+
+    return 0;
+out:
+    free(cert_addr);
+    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..eb5ba0ed47
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -0,0 +1,102 @@
+/*
+ * S/390 Secure IPL
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * 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)
+{
+    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;
+
+    if (!sclp_is_diag320_on()) {
+        return false;
+    }
+
+    diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
+    return d320_ism & (DIAG_320_ISM_QUERY_VCSI | DIAG_320_ISM_STORE_VC);
+}
+
+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_signature_verif_supported(void)
+{
+    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;
+        /*
+         * 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 */
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 20/30] pc-bios/s390-ccw: Add signed component address overlap checks
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (18 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-17  4:25   ` Collin Walling
  2026-03-05 22:41 ` [PATCH v9 21/30] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
                   ` (9 subsequent siblings)
  29 siblings, 1 reply; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Add address range tracking and overlap checks to ensure that no
component overlaps with a signed component during secure IPL.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 pc-bios/s390-ccw/secure-ipl.c | 52 +++++++++++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h |  6 ++++
 2 files changed, 58 insertions(+)

diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
index 8d281c1cea..68596491c5 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -211,6 +211,53 @@ static void init_lists(IplDeviceComponentList *comp_list,
     cert_list->ipl_info_header.len = sizeof(cert_list->ipl_info_header);
 }
 
+static bool is_comp_overlap(SecureIplCompAddrRange *comp_addr_range,
+                            int addr_range_index,
+                            uint64_t start_addr, uint64_t end_addr)
+{
+    /* neither a signed nor an unsigned component can overlap with a signed component */
+    for (int i = 0; i < addr_range_index; i++) {
+        if ((comp_addr_range[i].start_addr < end_addr &&
+            start_addr < comp_addr_range[i].end_addr) &&
+            comp_addr_range[i].is_signed) {
+            return true;
+       }
+    }
+
+    return false;
+}
+
+static void comp_addr_range_add(SecureIplCompAddrRange *comp_addr_range,
+                                int addr_range_index, bool is_signed,
+                                uint64_t start_addr, uint64_t end_addr)
+{
+    if (addr_range_index >= MAX_CERTIFICATES) {
+        zipl_secure_handle("Component address range update failed due to out-of-range"
+                           " index; Overlapping validation cannot be guaranteed");
+    }
+
+    comp_addr_range[addr_range_index].is_signed = is_signed;
+    comp_addr_range[addr_range_index].start_addr = start_addr;
+    comp_addr_range[addr_range_index].end_addr = end_addr;
+}
+
+static void addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
+                               int *addr_range_index,
+                               uint64_t start_addr, uint64_t end_addr, bool is_signed)
+{
+    bool overlap;
+
+    overlap = is_comp_overlap(comp_addr_range, *addr_range_index,
+                              start_addr, end_addr);
+    if (overlap) {
+        zipl_secure_handle("Component addresses overlap");
+    }
+
+    comp_addr_range_add(comp_addr_range, *addr_range_index, is_signed,
+                        start_addr, end_addr);
+    *addr_range_index += 1;
+}
+
 static int zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
 {
     if (zipl_load_segment(entry, sig_sec) < 0) {
@@ -254,6 +301,8 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
      * exists for the certificate).
      */
     int cert_list_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1 };
+    SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
+    int addr_range_index = 0;
     int signed_count = 0;
 
     if (!secure_ipl_supported()) {
@@ -283,6 +332,9 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
                 goto out;
             }
 
+            addr_overlap_check(comp_addr_range, &addr_range_index,
+                               comp_addr, comp_addr + comp_len, sig_len > 0);
+
             if (!sig_len) {
                 break;
             }
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index eb5ba0ed47..69edfce241 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -16,6 +16,12 @@
 VCStorageSizeBlock *zipl_secure_get_vcssb(void);
 int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
 
+typedef struct SecureIplCompAddrRange {
+    bool is_signed;
+    uint64_t start_addr;
+    uint64_t end_addr;
+} SecureIplCompAddrRange;
+
 static inline void zipl_secure_handle(const char *message)
 {
     switch (boot_mode) {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 21/30] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (19 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 20/30] pc-bios/s390-ccw: Add signed component address overlap checks Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 22/30] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
                   ` (8 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

The secure-IPL-code-loading-attributes facility (SCLAF)
provides additional security during secure IPL.

Availability of SCLAF is determined by byte 136 bit 3 of the
SCLP Read Info block.

This feature is available starting with the gen16 CPU model.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
---
 docs/specs/s390x-secure-ipl.rst     | 19 +++++++++++++++++++
 target/s390x/cpu_features.c         |  2 ++
 target/s390x/cpu_features_def.h.inc |  1 +
 target/s390x/cpu_models.c           |  3 +++
 target/s390x/gen-features.c         |  2 ++
 target/s390x/kvm/kvm.c              |  1 +
 6 files changed, 28 insertions(+)

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index fc37de52b9..9903b9dcf2 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -109,3 +109,22 @@ operations such as:
 
 The guest's kernel inspects the IIRB and uses the certificate data it contains
 to build the keyring.
+
+
+Secure Code Loading Attributes Facility
+---------------------------------------
+
+The Secure Code Loading Attributes Facility (SCLAF) enhances system security
+during the IPL by enforcing additional verification rules.
+
+When SCLAF is available, its behavior depends on the IPL mode. It introduces
+verification of both signed and unsigned components to help ensure that only
+authorized code is loaded during the IPL process. Any errors detected by SCLAF
+are reported in the IIRB.
+
+Unsigned components are restricted to load addresses at or above absolute
+storage address ``0x2000``.
+
+Signed components must include a Secure Code Loading Attribute Block (SCLAB),
+which is appended at the very end of the component. The SCLAB defines security
+attributes for handling the signed code.
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 200bd8c15b..29ea3bfec2 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -120,6 +120,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
      * - All SIE facilities because SIE is not available
      * - DIAG318
      * - Secure IPL Facility
+     * - Secure IPL Code Loading Attributes Facility
      *
      * As VMs can move in and out of protected mode the CPU model
      * doesn't protect us from that problem because it is only
@@ -152,6 +153,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
         break;
     case S390_FEAT_TYPE_SCLP_FAC_IPL:
         clear_be_bit(s390_feat_def(S390_FEAT_SIPL)->bit, data);
+        clear_be_bit(s390_feat_def(S390_FEAT_SCLAF)->bit, data);
         break;
     default:
         return;
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index bcf8a666e4..f6ba9e87e1 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -142,6 +142,7 @@ DEF_FEAT(CERT_STORE, "cstore", SCLP_FAC134, 5, "Certificate Store functions")
 
 /* Features exposed via SCLP SCCB Facilities byte 136 - 137 (bit numbers relative to byte-136) */
 DEF_FEAT(SIPL, "sipl", SCLP_FAC_IPL, 1, "Secure-IPL facility")
+DEF_FEAT(SCLAF, "sclaf", SCLP_FAC_IPL, 3, "Secure-IPL-code-loading-attributes facility")
 
 /* Features exposed via SCLP CPU info. */
 DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index f99536ef9a..7d214b5f72 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -264,6 +264,7 @@ bool s390_has_feat(S390Feat feat)
         case S390_FEAT_SIE_PFMFI:
         case S390_FEAT_SIE_IBS:
         case S390_FEAT_SIPL:
+        case S390_FEAT_SCLAF:
         case S390_FEAT_CONFIGURATION_TOPOLOGY:
             return false;
             break;
@@ -509,6 +510,8 @@ static void check_consistency(const S390CPUModel *model)
         { S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
         { S390_FEAT_CERT_STORE, S390_FEAT_EXTENDED_LENGTH_SCCB },
         { S390_FEAT_SIPL, S390_FEAT_EXTENDED_LENGTH_SCCB },
+        { S390_FEAT_SCLAF, S390_FEAT_EXTENDED_LENGTH_SCCB },
+        { S390_FEAT_SCLAF, S390_FEAT_SIPL },
         { S390_FEAT_NNPA, S390_FEAT_VECTOR },
         { S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
         { S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index bd2060ab93..c3e0c6ceff 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -722,6 +722,7 @@ static uint16_t full_GEN16_GA1[] = {
     S390_FEAT_UV_FEAT_AP_INTR,
     S390_FEAT_CERT_STORE,
     S390_FEAT_SIPL,
+    S390_FEAT_SCLAF,
 };
 
 static uint16_t full_GEN17_GA1[] = {
@@ -924,6 +925,7 @@ static uint16_t qemu_MAX[] = {
     S390_FEAT_EXTENDED_LENGTH_SCCB,
     S390_FEAT_CERT_STORE,
     S390_FEAT_SIPL,
+    S390_FEAT_SCLAF,
 };
 
 /****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 40197cca7a..6b7c606742 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2520,6 +2520,7 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
 
     /* Some Secure IPL facilities are emulated by QEMU */
     set_bit(S390_FEAT_SIPL, model->features);
+    set_bit(S390_FEAT_SCLAF, model->features);
 
     /* Test for Ultravisor features that influence secure guest behavior */
     query_uv_feat_guest(model->features);
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 22/30] pc-bios/s390-ccw: Add additional security checks for secure boot
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (20 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 21/30] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-17  6:54   ` Collin Walling
  2026-03-05 22:41 ` [PATCH v9 23/30] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
                   ` (7 subsequent siblings)
  29 siblings, 1 reply; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

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.
However, a missing SCLAB will not be reported in audit mode. The SCALB
checking will be skipped in this case.

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 <zycai@linux.ibm.com>
---
 include/hw/s390x/ipl/qipl.h   |  29 +++-
 pc-bios/s390-ccw/s390-ccw.h   |   1 +
 pc-bios/s390-ccw/sclp.c       |   8 +
 pc-bios/s390-ccw/sclp.h       |   1 +
 pc-bios/s390-ccw/secure-ipl.c | 314 +++++++++++++++++++++++++++++++++-
 pc-bios/s390-ccw/secure-ipl.h |  42 +++++
 6 files changed, 391 insertions(+), 4 deletions(-)

diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 1b6cb3231d..9518fcb1dc 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -136,10 +136,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;
 
@@ -163,13 +173,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_SCLA    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/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index a0d568696a..7d1a9d4acc 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -82,6 +82,7 @@ void sclp_setup(void);
 void sclp_get_loadparm_ascii(char *loadparm);
 bool sclp_is_diag320_on(void);
 bool sclp_is_sipl_on(void);
+bool sclp_is_sclaf_on(void);
 int sclp_read(char *str, size_t count);
 
 /* virtio.c */
diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
index e3b6a1f07e..5dbddff9ae 100644
--- a/pc-bios/s390-ccw/sclp.c
+++ b/pc-bios/s390-ccw/sclp.c
@@ -151,6 +151,14 @@ bool sclp_is_sipl_on(void)
     return fac_ipl & SCCB_FAC_IPL_SIPL_BIT;
 }
 
+bool sclp_is_sclaf_on(void)
+{
+    uint16_t fac_ipl = 0;
+
+    sclp_get_fac_ipl(&fac_ipl);
+    return fac_ipl & SCCB_FAC_IPL_SCLAF_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 cf147f4634..3441020d6b 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 68596491c5..840b88a699 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -198,6 +198,12 @@ static bool secure_ipl_supported(void)
         return false;
     }
 
+    if (!sclp_is_sclaf_on()) {
+        puts("Secure IPL Code Loading Attributes Facility is not supported by"
+             " the hypervisor!");
+        return false;
+    }
+
     return true;
 }
 
@@ -258,6 +264,286 @@ static void addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
     *addr_range_index += 1;
 }
 
+static void check_unsigned_addr(uint64_t load_addr, IplDeviceComponentEntry *comp_entry)
+{
+    /* unsigned load address must be greater than or equal to 0x2000 */
+    if (load_addr >= 0x2000) {
+        return;
+    }
+
+    set_comp_cei_with_log(comp_entry, S390_CEI_INVALID_UNSIGNED_ADDR,
+                          "Load address is less than 0x2000");
+}
+
+static bool check_sclab_presence(uint8_t *sclab_magic,
+                                 IplDeviceComponentEntry *comp_entry)
+{
+    /* identifies the presence of SCLAB */
+    if (magic_match(sclab_magic, ZIPL_MAGIC)) {
+        return true;
+    }
+
+    if (comp_entry) {
+        comp_entry->cei |= S390_CEI_INVALID_SCLAB;
+    }
+
+    /* a missing SCLAB will not be reported in audit mode */
+    return false;
+}
+
+static void check_sclab_length(uint16_t sclab_len, IplDeviceComponentEntry *comp_entry)
+{
+    if (sclab_len >= S390_SECURE_IPL_SCLAB_MIN_LEN) {
+        return;
+    }
+
+    set_comp_cei_with_log(comp_entry,
+                          S390_CEI_INVALID_SCLAB_LEN | S390_CEI_INVALID_SCLAB,
+                          "Invalid SCLAB length");
+}
+
+static void check_sclab_format(uint8_t sclab_format, IplDeviceComponentEntry *comp_entry)
+{
+    /* SCLAB format must set to zero, indicating a format-0 SCLAB being used */
+    if (sclab_format == 0) {
+        return;
+    }
+
+    set_comp_cei_with_log(comp_entry, S390_CEI_INVALID_SCLAB_FORMAT,
+                          "Format-0 SCLAB is not being used");
+}
+
+static void check_sclab_opsw(SecureCodeLoadingAttributesBlock *sclab,
+                             SecureIplSclabInfo *sclab_info,
+                             IplDeviceComponentEntry *comp_entry)
+{
+    const char *msg;
+    uint32_t cei_flag = 0;
+
+    if (!(sclab->flags & S390_SECURE_IPL_SCLAB_FLAG_OPSW)) {
+        /* OPSW = 0 - Load PSW field in SCLAB must contain zeros */
+        if (sclab->load_psw != 0) {
+            cei_flag |= S390_CEI_SCLAB_LOAD_PSW_NOT_ZERO;
+            msg = "Load PSW is not zero when Override PSW bit is zero";
+        }
+    } else {
+        /* OPSW = 1 indicating global SCLAB */
+        sclab_info->global_count += 1;
+        if (sclab_info->global_count == 1) {
+            sclab_info->load_psw = sclab->load_psw;
+            sclab_info->flags = sclab->flags;
+        }
+
+        /* OLA must set to one */
+        if (!(sclab->flags & S390_SECURE_IPL_SCLAB_FLAG_OLA)) {
+            cei_flag |= S390_CEI_SCLAB_OLA_NOT_ONE;
+            msg = "Override Load Address bit is not set to one in the global SCLAB";
+        }
+    }
+
+    if (cei_flag) {
+        set_comp_cei_with_log(comp_entry, cei_flag, msg);
+    }
+}
+
+static void check_sclab_ola(SecureCodeLoadingAttributesBlock *sclab, uint64_t load_addr,
+                            IplDeviceComponentEntry *comp_entry)
+{
+    const char *msg;
+    uint32_t cei_flag = 0;
+
+    if (!(sclab->flags & S390_SECURE_IPL_SCLAB_FLAG_OLA)) {
+        /* OLA = 0 - Load address field in SCLAB must contain zeros */
+        if (sclab->load_addr != 0) {
+            cei_flag |= S390_CEI_SCLAB_LOAD_ADDR_NOT_ZERO;
+            msg = "Load Address is not zero when Override Load Address bit is zero";
+        }
+    } else {
+        /* OLA = 1 - Load address field must match storage address of the component */
+        if (sclab->load_addr != load_addr) {
+            cei_flag |= S390_CEI_UNMATCHED_SCLAB_LOAD_ADDR;
+            msg = "Load Address does not match with component load address";
+        }
+    }
+
+    if (cei_flag) {
+        set_comp_cei_with_log(comp_entry, cei_flag, msg);
+    }
+}
+
+static void check_sclab_nuc(uint16_t sclab_flags, IplDeviceComponentEntry *comp_entry)
+{
+    const char *msg;
+    bool is_nuc_set;
+    bool is_global_sclab;
+
+    is_nuc_set = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_NUC;
+    is_global_sclab = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_OPSW;
+    if (is_nuc_set && !is_global_sclab) {
+        msg = "No Unsigned Components bit is set, but not in the global SCLAB";
+        set_comp_cei_with_log(comp_entry, S390_CEI_NUC_NOT_IN_GLOBAL_SCLA, msg);
+    }
+}
+
+static void check_sclab_sc(uint16_t sclab_flags, IplDeviceComponentEntry *comp_entry)
+{
+    const char *msg;
+    bool is_sc_set;
+    bool is_global_sclab;
+
+    is_sc_set = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_SC;
+    is_global_sclab = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_OPSW;
+    if (is_sc_set && !is_global_sclab) {
+        msg = "Single Component bit is set, but not in the global SCLAB";
+        set_comp_cei_with_log(comp_entry, S390_CEI_SC_NOT_IN_GLOBAL_SCLAB, msg);
+    }
+}
+
+static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
+                         int range_index)
+{
+    uint32_t addr = psw & 0x7fffffff;
+
+    /* PSW points within a signed binary code component */
+    for (int i = 0; i < range_index; i++) {
+        if (comp_addr_range[i].is_signed &&
+            addr >= comp_addr_range[i].start_addr &&
+            addr <= comp_addr_range[i].end_addr - 2) {
+            return true;
+       }
+    }
+
+    return false;
+}
+
+static void check_load_psw(SecureIplCompAddrRange *comp_addr_range,
+                           int addr_range_index, uint64_t sclab_load_psw,
+                           uint64_t load_psw, IplDeviceComponentEntry *comp_entry)
+{
+    bool valid;
+
+    valid = is_psw_valid(sclab_load_psw, comp_addr_range, addr_range_index) &&
+            is_psw_valid(load_psw, comp_addr_range, addr_range_index);
+    if (!valid) {
+        set_comp_cei_with_log(comp_entry, S390_CEI_INVALID_LOAD_PSW, "Invalid PSW");
+    }
+
+    /* compare load PSW with the PSW specified in component */
+    if (sclab_load_psw != load_psw) {
+        set_comp_cei_with_log(comp_entry, S390_CEI_UNMATCHED_SCLAB_LOAD_PSW,
+                              "Load PSW does not match with PSW in component");
+    }
+}
+
+static void check_nuc(uint16_t global_sclab_flags, int unsigned_count,
+                      IplDeviceComponentList *comp_list)
+{
+    bool is_nuc_set;
+
+    is_nuc_set = global_sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_NUC;
+    if (is_nuc_set && unsigned_count > 0) {
+        comp_list->ipl_info_header.iiei |= S390_IIEI_FOUND_UNSIGNED_COMP;
+        zipl_secure_handle("Unsigned components are not allowed");
+    }
+}
+
+static void check_sc(uint16_t global_sclab_flags,
+                     int signed_count, int unsigned_count,
+                     IplDeviceComponentList *comp_list)
+{
+    bool is_sc_set;
+
+    is_sc_set = global_sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_SC;
+    if (is_sc_set && signed_count != 1 && unsigned_count >= 0) {
+        comp_list->ipl_info_header.iiei |= S390_IIEI_MORE_SIGNED_COMP;
+        zipl_secure_handle("Only one signed component is allowed");
+    }
+}
+
+void check_global_sclab(SecureIplSclabInfo sclab_info,
+                        int unsigned_count, int signed_count,
+                        IplDeviceComponentList *comp_list)
+{
+    if (sclab_info.count == 0) {
+        return;
+    }
+
+    if (sclab_info.global_count == 0) {
+        comp_list->ipl_info_header.iiei |= S390_IIEI_NO_GLOBAL_SCLAB;
+        zipl_secure_handle("Global SCLAB does not exists");
+        return;
+    }
+
+    if (sclab_info.global_count > 1) {
+        comp_list->ipl_info_header.iiei |= S390_IIEI_MORE_GLOBAL_SCLAB;
+        zipl_secure_handle("More than one global SCLAB");
+        return;
+    }
+
+    if (sclab_info.flags) {
+        /* Unsigned components are not allowed if NUC flag is set in the global SCLAB */
+        check_nuc(sclab_info.flags, unsigned_count, comp_list);
+
+        /* Only one signed component is allowed is SC flag is set in the global SCLAB */
+        check_sc(sclab_info.flags, signed_count, unsigned_count, comp_list);
+    }
+}
+
+static void check_signed_comp(int signed_count, IplDeviceComponentList *comp_list)
+{
+    if (signed_count > 0) {
+        return;
+    }
+
+    comp_list->ipl_info_header.iiei |= S390_IIEI_NO_SIGNED_COMP;
+    zipl_secure_handle("Secure boot is on, but components are not signed");
+}
+
+static void check_sclab_count(int count, IplDeviceComponentList *comp_list)
+{
+    if (count > 0) {
+        return;
+    }
+
+    comp_list->ipl_info_header.iiei |= S390_IIEI_NO_SCLAB;
+    zipl_secure_handle("No recognizable SCLAB");
+}
+
+static void check_sclab(uint64_t comp_addr, uint64_t comp_len,
+                        IplDeviceComponentEntry *comp_entry,
+                        SecureIplSclabInfo *sclab_info)
+{
+    SclabOriginLocator *sclab_locator;
+    SecureCodeLoadingAttributesBlock *sclab;
+    bool exist;
+
+    /* sclab locator is located at the last 8 bytes of the signed comp */
+    sclab_locator = (SclabOriginLocator *)(comp_addr + comp_len - 8);
+
+    /* return early if sclab does not exist */
+    exist = check_sclab_presence(sclab_locator->magic, comp_entry);
+    if (!exist) {
+        return;
+    }
+
+    check_sclab_length(sclab_locator->len, comp_entry);
+
+    /* return early if sclab is invalid */
+    if (comp_entry && (comp_entry->cei & S390_CEI_INVALID_SCLAB)) {
+        return;
+    }
+
+    sclab_info->count += 1;
+    sclab = (SecureCodeLoadingAttributesBlock *)(comp_addr + comp_len -
+                                                 sclab_locator->len);
+
+    check_sclab_format(sclab->format, comp_entry);
+    check_sclab_opsw(sclab, sclab_info, comp_entry);
+    check_sclab_ola(sclab, comp_addr, comp_entry);
+    check_sclab_nuc(sclab->flags, comp_entry);
+    check_sclab_sc(sclab->flags, comp_entry);
+}
+
 static int zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
 {
     if (zipl_load_segment(entry, sig_sec) < 0) {
@@ -304,6 +590,9 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
     SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
     int addr_range_index = 0;
     int signed_count = 0;
+    int unsigned_count = 0;
+    SecureIplSclabInfo sclab_info = { 0 };
+    IplDeviceComponentEntry *comp_entry;
 
     if (!secure_ipl_supported()) {
         panic("Unable to boot in secure/audit mode");
@@ -335,10 +624,21 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
             addr_overlap_check(comp_addr_range, &addr_range_index,
                                comp_addr, comp_addr + comp_len, sig_len > 0);
 
+            comp_entry = (comp_entry_idx < MAX_CERTIFICATES) ?
+                         &comp_list.device_entries[comp_entry_idx] : NULL;
+
             if (!sig_len) {
+                check_unsigned_addr(comp_addr, comp_entry);
+                comp_list_add(&comp_list, comp_entry_idx, cert_entry_idx,
+                              comp_addr, comp_len, 0x00);
+
+                unsigned_count += 1;
+                comp_entry_idx++;
                 break;
             }
 
+            check_sclab(comp_addr, comp_len,
+                        &comp_list.device_entries[comp_entry_idx], &sclab_info);
             verified = verify_signature(comp_len, comp_addr, sig_len, (uint64_t)sig,
                                         &cert_len, &cert_table_idx);
 
@@ -391,10 +691,20 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
         }
     }
 
-    if (signed_count == 0) {
-        zipl_secure_handle("Secure boot is on, but components are not signed");
+    /* validate load PSW with PSW specified in the final entry */
+    if (sclab_info.load_psw) {
+        comp_entry = (comp_entry_idx < MAX_CERTIFICATES) ?
+                     &comp_list.device_entries[comp_entry_idx] : NULL;
+        check_load_psw(comp_addr_range, addr_range_index,
+                       sclab_info.load_psw, entry->compdat.load_psw, comp_entry);
+        comp_list_add(&comp_list, comp_entry_idx, -1,
+                      entry->compdat.load_psw, 0, 0x00);
     }
 
+    check_signed_comp(signed_count, &comp_list);
+    check_sclab_count(sclab_info.count, &comp_list);
+    check_global_sclab(sclab_info, unsigned_count, signed_count, &comp_list);
+
     update_iirb(&comp_list, &cert_list);
 
     *entry_ptr = entry;
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index 69edfce241..4e9f4f08b9 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -16,6 +16,38 @@
 VCStorageSizeBlock *zipl_secure_get_vcssb(void);
 int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
 
+#define S390_SECURE_IPL_SCLAB_FLAG_OPSW    0x8000
+#define S390_SECURE_IPL_SCLAB_FLAG_OLA     0x4000
+#define S390_SECURE_IPL_SCLAB_FLAG_NUC     0x2000
+#define S390_SECURE_IPL_SCLAB_FLAG_SC      0x1000
+
+#define S390_SECURE_IPL_SCLAB_MIN_LEN      32
+
+struct SecureCodeLoadingAttributesBlock {
+    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 SecureCodeLoadingAttributesBlock SecureCodeLoadingAttributesBlock;
+
+struct SclabOriginLocator {
+    uint8_t reserved[2];
+    uint16_t len;
+    uint8_t magic[4];
+} __attribute__ ((packed));
+typedef struct SclabOriginLocator SclabOriginLocator;
+
+typedef struct SecureIplSclabInfo {
+    int count;
+    int global_count;
+    uint64_t load_psw;
+    uint16_t flags;
+} SecureIplSclabInfo;
+
 typedef struct SecureIplCompAddrRange {
     bool is_signed;
     uint64_t start_addr;
@@ -33,6 +65,16 @@ static inline void zipl_secure_handle(const char *message)
     }
 }
 
+static inline void set_comp_cei_with_log(IplDeviceComponentEntry *comp_entry,
+                                         uint32_t flag, const char *message)
+{
+    if (comp_entry) {
+        comp_entry->cei |= flag;
+    }
+
+    zipl_secure_handle(message);
+}
+
 static inline uint64_t diag320(void *data, unsigned long subcode)
 {
     register unsigned long addr asm("0") = (unsigned long)data;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 23/30] Add secure-boot to s390-ccw-virtio machine type option
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (21 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 22/30] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 24/30] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
                   ` (6 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Add secure-boot as a parameter of s390-ccw-virtio machine type option.

The `secure-boot=on|off` parameter is implemented to enable secure IPL.

By default, secure-boot is set to false if not specified in
the command line.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 docs/system/s390x/secure-ipl.rst   | 22 +++++++++++++++++-----
 hw/s390x/s390-virtio-ccw.c         | 22 ++++++++++++++++++++++
 include/hw/s390x/s390-virtio-ccw.h |  1 +
 qemu-options.hx                    |  6 +++++-
 4 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
index 3a19b72085..2465f8b26d 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -19,19 +19,31 @@ Note: certificate files must have a .pem extension.
 
     qemu-system-s390x -machine s390-ccw-virtio,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem ...
 
+Enabling Secure IPL
+-------------------
+
+Secure IPL is enabled by explicitly setting ``secure-boot=on``; if not
+specified, secure boot is considered off.
+
+.. code-block:: shell
+
+    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on|off
+
 
 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.
+configurations. These modes are mutually exclusive and enabled based on specific
+combinations of the ``secure-boot`` and ``boot-certs`` options 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.
+The absence of both certificates and the ``secure-boot`` option 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, and can be explicitly
+enabled with ``secure-boot=off``.
 
 Configuration:
 
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index a6f0fc4e00..a24cc14906 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -813,6 +813,21 @@ static void machine_set_boot_certs(Object *obj, Visitor *v, const char *name,
     ms->boot_certs = cert_list;
 }
 
+static inline bool machine_get_secure_boot(Object *obj, Error **errp)
+{
+    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+    return ms->secure_boot;
+}
+
+static inline void machine_set_secure_boot(Object *obj, bool value,
+                                            Error **errp)
+{
+    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+    ms->secure_boot = value;
+}
+
 static void ccw_machine_class_init(ObjectClass *oc, const void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
@@ -871,6 +886,13 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
                               machine_get_boot_certs, machine_set_boot_certs, NULL, NULL);
     object_class_property_set_description(oc, "boot-certs",
             "provide paths to a directory and/or a certificate file for secure boot");
+
+    object_class_property_add_bool(oc, "secure-boot",
+                                   machine_get_secure_boot,
+                                   machine_set_secure_boot);
+    object_class_property_set_description(oc, "secure-boot",
+            "enable/disable secure boot");
+
 }
 
 static inline void s390_machine_initfn(Object *obj)
diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
index 5ad1ea2f24..93a4c0ccad 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -29,6 +29,7 @@ struct S390CcwMachineState {
     bool aes_key_wrap;
     bool dea_key_wrap;
     bool pv;
+    bool secure_boot;
     uint8_t loadparm[8];
     uint64_t memory_limit;
     uint64_t max_pagesize;
diff --git a/qemu-options.hx b/qemu-options.hx
index 8873083792..7f58d129de 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -46,7 +46,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
     "                cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n"
     "                sgx-epc.0.memdev=memid,sgx-epc.0.node=numaid\n"
     "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
-    "                boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file provides paths to a directory and/or a certificate file\n",
+    "                boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file provides paths to a directory and/or a certificate file\n"
+    "                secure-boot=on|off enable/disable secure boot (default=off) \n",
     QEMU_ARCH_ALL)
 SRST
 ``-machine [type=]name[,prop=value[,...]]``
@@ -213,6 +214,9 @@ SRST
 
     ``boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file``
         Provide paths to a directory and/or a certificate file on the host [s390x only].
+
+    ``secure-boot=on|off``
+        Enables or disables secure boot on s390-ccw guest. The default is off.
 ERST
 
 DEF("M", HAS_ARG, QEMU_OPTION_M,
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 24/30] hw/s390x/ipl: Set IPIB flags for secure IPL
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (22 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 23/30] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 25/30] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
                   ` (5 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

If `-M secure-boot=on` is specified on the command line option, indicating
true secure IPL enabled, set Secure-IPL bit and IPL-Information-Report
bit on in IPIB Flags field, and trigger true secure IPL in the S390 BIOS.

Any error that occurs during true secure IPL will cause the IPL to
terminate.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 hw/s390x/ipl.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index b66dfd06bd..f8dd50f69d 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -440,6 +440,11 @@ static bool s390_has_certificate(void)
     return ipl->cert_store.count > 0;
 }
 
+static bool s390_secure_boot_enabled(void)
+{
+    return S390_CCW_MACHINE(qdev_get_machine())->secure_boot;
+}
+
 static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
 {
     CcwDevice *ccw_dev = NULL;
@@ -497,6 +502,18 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
         s390_ipl_convert_loadparm((char *)lp, iplb->loadparm);
         iplb->flags |= DIAG308_FLAGS_LP_VALID;
 
+        /*
+         * If secure-boot is enabled, then toggle the secure IPL flags to
+         * trigger secure boot in the s390 BIOS.
+         *
+         * Boot process will terminate if any error occurs during secure boot.
+         *
+         * If SIPL is on, IPLIR must also be on.
+         */
+        if (s390_secure_boot_enabled()) {
+            iplb->hdr_flags |= (DIAG308_IPIB_FLAGS_SIPL | DIAG308_IPIB_FLAGS_IPLIR);
+            iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
+        }
         /*
          * Secure boot in audit mode will perform
          * if certificate(s) exist in the key store.
@@ -506,7 +523,7 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
          *
          * Results of secure boot will be stored in IIRB.
          */
-        if (s390_has_certificate()) {
+        else if (s390_has_certificate()) {
             iplb->hdr_flags |= DIAG308_IPIB_FLAGS_IPLIR;
             iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
         }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 25/30] pc-bios/s390-ccw: Handle true secure IPL mode
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (23 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 24/30] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-17  7:02   ` Collin Walling
  2026-03-05 22:41 ` [PATCH v9 26/30] hw/s390x/ipl: Handle secure boot with multiple boot devices Zhuoying Cai
                   ` (4 subsequent siblings)
  29 siblings, 1 reply; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

When secure boot is enabled (-secure-boot on) and certificate(s) are
provided, the boot operates in True Secure IPL mode.

Any verification error during True Secure IPL mode will cause the
entire boot process to terminate.

Secure IPL in audit mode requires at least one certificate provided in
the key store along with necessary facilities. If secure boot is enabled
but no certificate is provided, the boot process will also terminate, as
this is not a valid secure boot configuration.

Note: True Secure IPL mode is implemented for the SCSI scheme of
virtio-blk/virtio-scsi devices.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 docs/system/s390x/secure-ipl.rst | 13 +++++++++++++
 pc-bios/s390-ccw/bootmap.c       |  8 ++++++++
 pc-bios/s390-ccw/main.c          |  3 +++
 pc-bios/s390-ccw/s390-ccw.h      |  2 ++
 pc-bios/s390-ccw/secure-ipl.c    |  4 ++++
 pc-bios/s390-ccw/secure-ipl.h    |  3 +++
 6 files changed, 33 insertions(+)

diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
index 2465f8b26d..e0af086c38 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -65,3 +65,16 @@ 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 ...
+
+Secure Mode
+-----------
+
+When the ``secure-boot=on`` option is set and certificates are provided,
+a secure boot is performed with error reporting enabled. The boot process aborts
+if any error occurs.
+
+Configuration:
+
+.. code-block:: shell
+
+    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem ...
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 43a661325f..9a61e989e0 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -738,6 +738,7 @@ static int zipl_run(ScsiBlockPtr *pte)
     entry = (ComponentEntry *)(&header[1]);
 
     switch (boot_mode) {
+    case ZIPL_BOOT_MODE_SECURE:
     case ZIPL_BOOT_MODE_SECURE_AUDIT:
         rc = zipl_run_secure(&entry, tmp_sec);
         break;
@@ -1120,9 +1121,16 @@ ZiplBootMode get_boot_mode(uint8_t hdr_flags)
 {
     bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
     bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
+    VCStorageSizeBlock *vcssb;
 
     if (!sipl_set && iplir_set) {
         return ZIPL_BOOT_MODE_SECURE_AUDIT;
+    } else if (sipl_set && iplir_set) {
+        vcssb = zipl_secure_get_vcssb();
+        if (vcssb == NULL || vcssb->length == VCSSB_NO_VC) {
+            return ZIPL_BOOT_MODE_INVALID;
+        }
+        return ZIPL_BOOT_MODE_SECURE;
     }
 
     return ZIPL_BOOT_MODE_NORMAL;
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 106cdf9dec..1678ede8fb 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -329,6 +329,9 @@ void main(void)
     }
 
     boot_mode = get_boot_mode(iplb->hdr_flags);
+    if (boot_mode == ZIPL_BOOT_MODE_INVALID) {
+        panic("Need at least one certificate for secure boot!");
+    }
 
     while (have_iplb) {
         boot_setup();
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 7d1a9d4acc..7092942280 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -96,8 +96,10 @@ int virtio_read(unsigned long sector, void *load_addr);
 void zipl_load(void);
 
 typedef enum ZiplBootMode {
+    ZIPL_BOOT_MODE_INVALID = -1,
     ZIPL_BOOT_MODE_NORMAL = 0,
     ZIPL_BOOT_MODE_SECURE_AUDIT = 1,
+    ZIPL_BOOT_MODE_SECURE = 2,
 } ZiplBootMode;
 
 extern ZiplBootMode boot_mode;
diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
index 840b88a699..76b72fc8f4 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -288,6 +288,10 @@ static bool check_sclab_presence(uint8_t *sclab_magic,
     }
 
     /* a missing SCLAB will not be reported in audit mode */
+    if (boot_mode == ZIPL_BOOT_MODE_SECURE) {
+        zipl_secure_handle("Magic does not match. SCLAB does not exist");
+    }
+
     return false;
 }
 
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index 4e9f4f08b9..1e736d53fe 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -60,6 +60,9 @@ static inline void zipl_secure_handle(const char *message)
     case ZIPL_BOOT_MODE_SECURE_AUDIT:
         IPL_check(false, message);
         break;
+    case ZIPL_BOOT_MODE_SECURE:
+        panic(message);
+        break;
     default:
         break;
     }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 26/30] hw/s390x/ipl: Handle secure boot with multiple boot devices
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (24 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 25/30] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 27/30] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
                   ` (3 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

The current approach to enable secure boot relies on providing
secure-boot and boot-certs parameters of s390-ccw-virtio machine
type option, which apply to all boot devices.

With the possibility of multiple boot devices, secure boot expects all
provided devices to be supported and eligible (e.g.,
virtio-blk/virtio-scsi using the SCSI scheme).

If multiple boot devices are provided and include an unsupported (e.g.,
ECKD, VFIO) or a non-eligible (e.g., Net) device, the boot process will
terminate with an error logged to the console.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 hw/s390x/ipl.c          | 79 ++++++++++++++++++++++++++++-------------
 pc-bios/s390-ccw/main.c |  3 --
 2 files changed, 54 insertions(+), 28 deletions(-)

diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index f8dd50f69d..e46e655ef1 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -445,6 +445,58 @@ static bool s390_secure_boot_enabled(void)
     return S390_CCW_MACHINE(qdev_get_machine())->secure_boot;
 }
 
+static bool s390_validate_secure_boot_device(int devtype, Error **errp)
+{
+    switch (devtype) {
+    case CCW_DEVTYPE_VFIO:
+       error_setg(errp, "Passthrough (vfio) CCW device does not support secure boot!");
+       return false;
+    case CCW_DEVTYPE_VIRTIO_NET:
+       error_setg(errp, "Virtio net boot device does not support secure boot!");
+       return false;
+    default:
+       return true;
+    }
+}
+
+static void s390_apply_secure_boot(IplParameterBlock *iplb, int devtype,
+                                   bool secure_boot, bool audit_mode)
+{
+    Error *local_error = NULL;
+
+    if (!secure_boot && !audit_mode) {
+        return;
+    }
+
+    if (!s390_validate_secure_boot_device(devtype, &local_error)) {
+        error_report_err(local_error);
+        exit(1);
+    }
+
+    /*
+     * If secure-boot is enabled, then toggle the secure IPL flags (SIPL) to
+     * trigger secure boot in the s390 BIOS.
+     *
+     * Boot process will terminate if any error occurs during secure boot.
+     */
+    if (secure_boot) {
+        iplb->hdr_flags |= DIAG308_IPIB_FLAGS_SIPL;
+    }
+
+    /*
+     * For both secure boot and audit mode, enable the IPL Information
+     * Report (IPLIR) flag so that the firmware generates an IPL
+     * Information Report Block (IIRB).
+     *
+     * Results of secure boot will be stored in IIRB.
+     *
+     * Extend the IPL parameter block to its maximum length to ensure
+     * sufficient space for the BIOS to populate the IIRB.
+     */
+    iplb->hdr_flags |= DIAG308_IPIB_FLAGS_IPLIR;
+    iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
+}
+
 static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
 {
     CcwDevice *ccw_dev = NULL;
@@ -502,31 +554,8 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
         s390_ipl_convert_loadparm((char *)lp, iplb->loadparm);
         iplb->flags |= DIAG308_FLAGS_LP_VALID;
 
-        /*
-         * If secure-boot is enabled, then toggle the secure IPL flags to
-         * trigger secure boot in the s390 BIOS.
-         *
-         * Boot process will terminate if any error occurs during secure boot.
-         *
-         * If SIPL is on, IPLIR must also be on.
-         */
-        if (s390_secure_boot_enabled()) {
-            iplb->hdr_flags |= (DIAG308_IPIB_FLAGS_SIPL | DIAG308_IPIB_FLAGS_IPLIR);
-            iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
-        }
-        /*
-         * Secure boot in audit mode will perform
-         * if certificate(s) exist in the key store.
-         *
-         * IPL Information Report Block (IIRB) will exist
-         * for secure boot in audit mode.
-         *
-         * Results of secure boot will be stored in IIRB.
-         */
-        else if (s390_has_certificate()) {
-            iplb->hdr_flags |= DIAG308_IPIB_FLAGS_IPLIR;
-            iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
-        }
+        s390_apply_secure_boot(iplb, devtype, s390_secure_boot_enabled(),
+                               s390_has_certificate());
 
         return true;
     }
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 1678ede8fb..6633a2cbaf 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -276,9 +276,6 @@ static void ipl_boot_device(void)
     switch (cutype) {
     case CU_TYPE_DASD_3990:
     case CU_TYPE_DASD_2107:
-        IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
-                    "Passthrough (vfio) CCW device does not support secure boot!");
-
         dasd_ipl(blk_schid, cutype);
         break;
     case CU_TYPE_VIRTIO:
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 27/30] hw/s390x/ipl: Handle secure boot without specifying a boot device
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (25 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 26/30] hw/s390x/ipl: Handle secure boot with multiple boot devices Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 28/30] tests/functional/s390x: Add secure IPL functional test Zhuoying Cai
                   ` (2 subsequent siblings)
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

If secure boot in audit mode or True Secure IPL mode is enabled without
specifying a boot device, the boot process will terminate with an error.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
---
 hw/s390x/ipl.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index e46e655ef1..00ddfcf858 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -799,6 +799,16 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
         cpu->env.psw.addr = ipl->bios_start_addr;
         if (!ipl->iplb_valid) {
             ipl->iplb_valid = s390_init_all_iplbs(ipl);
+
+            /*
+             * Secure IPL without specifying a boot device.
+             * IPLB is not generated if no boot device is defined.
+             */
+            if ((s390_has_certificate() || s390_secure_boot_enabled()) &&
+                !ipl->iplb_valid) {
+                error_report("No boot device defined for Secure IPL");
+                exit(1);
+            }
         } else {
             ipl->qipl.chain_len = 0;
         }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 28/30] tests/functional/s390x: Add secure IPL functional test
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (26 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 27/30] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 29/30] docs/specs: Add secure IPL documentation Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 30/30] docs/system/s390x: " Zhuoying Cai
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Add functional test for secure IPL.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 tests/functional/s390x/meson.build        |   2 +
 tests/functional/s390x/test_secure_ipl.py | 148 ++++++++++++++++++++++
 2 files changed, 150 insertions(+)
 create mode 100755 tests/functional/s390x/test_secure_ipl.py

diff --git a/tests/functional/s390x/meson.build b/tests/functional/s390x/meson.build
index 0f03e1c9db..07191ec996 100644
--- a/tests/functional/s390x/meson.build
+++ b/tests/functional/s390x/meson.build
@@ -2,6 +2,7 @@
 
 test_s390x_timeouts = {
   'ccw_virtio' : 420,
+  'secure_ipl' : 280,
 }
 
 tests_s390x_system_quick = [
@@ -13,6 +14,7 @@ tests_s390x_system_thorough = [
   'ccw_virtio',
   'pxelinux',
   'replay',
+  'secure_ipl',
   'topology',
   'tuxrun',
 ]
diff --git a/tests/functional/s390x/test_secure_ipl.py b/tests/functional/s390x/test_secure_ipl.py
new file mode 100755
index 0000000000..0980daace1
--- /dev/null
+++ b/tests/functional/s390x/test_secure_ipl.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+#
+# s390x Secure IPL functional test: validates secure-boot verification results
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from subprocess import check_call, DEVNULL
+
+from qemu_test import QemuSystemTest, Asset, get_qemu_img
+from qemu_test import exec_command_and_wait_for_pattern, exec_command
+from qemu_test import wait_for_console_pattern, skipBigDataTest
+
+class S390xSecureIpl(QemuSystemTest):
+    ASSET_F40_QCOW2 = Asset(
+        ('https://archives.fedoraproject.org/pub/archive/'
+         'fedora-secondary/releases/40/Server/s390x/images/'
+         'Fedora-Server-KVM-40-1.14.s390x.qcow2'),
+        '091c232a7301be14e19c76ce9a0c1cbd2be2c4157884a731e1fc4f89e7455a5f')
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.root_password = None
+        self.qcow2_path = None
+        self.cert_path = None
+        self.prompt = None
+
+    # Boot a temporary VM to set up secure IPL image:
+    # - Create certificate
+    # - Sign stage3 binary and kernel
+    # - Run zipl
+    # - Extract certificate
+    def setup_s390x_secure_ipl(self):
+        temp_vm = self.get_vm(name='sipl_setup')
+        temp_vm.set_machine('s390-ccw-virtio')
+
+        asset_path = self.ASSET_F40_QCOW2.fetch()
+        self.qcow2_path = self.scratch_file('f40.qcow2')
+        qemu_img = get_qemu_img(self)
+        check_call([qemu_img, 'create', '-f', 'qcow2', '-b', asset_path,
+                    '-F', 'qcow2', self.qcow2_path], stdout=DEVNULL, stderr=DEVNULL)
+
+        temp_vm.set_console()
+        temp_vm.add_args('-nographic',
+                         '-accel', 'kvm',
+                         '-m', '1024',
+                         '-drive',
+                         f'id=drive0,if=none,format=qcow2,file={self.qcow2_path}',
+                         '-device', 'virtio-blk-ccw,drive=drive0,bootindex=1')
+        temp_vm.launch()
+
+        # Initial root account setup (Fedora first boot screen)
+        self.root_password = 'fedora40password'
+        wait_for_console_pattern(self, 'Please make a selection from the above',
+                                 vm=temp_vm)
+        exec_command_and_wait_for_pattern(self, '4', 'Password:', vm=temp_vm)
+        exec_command_and_wait_for_pattern(self, self.root_password,
+                                          'Password (confirm):', vm=temp_vm)
+        exec_command_and_wait_for_pattern(self, self.root_password,
+                                    'Please make a selection from the above',
+                                    vm=temp_vm)
+
+        # Login as root
+        self.prompt = '[root@localhost ~]#'
+        exec_command_and_wait_for_pattern(self, 'c', 'localhost login:', vm=temp_vm)
+        exec_command_and_wait_for_pattern(self, 'root', 'Password:', vm=temp_vm)
+        exec_command_and_wait_for_pattern(self, self.root_password, self.prompt,
+                                          vm=temp_vm)
+
+        # Certificate generation
+        exec_command_and_wait_for_pattern(self,
+                                          'openssl version', 'OpenSSL 3.2.1 30',
+                                          vm=temp_vm)
+        exec_command_and_wait_for_pattern(self,
+                            'openssl req -new -x509 -newkey rsa:2048 '
+                            '-keyout mykey.pem -outform PEM -out mycert.pem '
+                            '-days 36500 -subj "/CN=My Name/" -nodes -verbose',
+                            'Writing private key to \'mykey.pem\'', vm=temp_vm)
+
+        # Install kernel-devel (needed for sign-file)
+        exec_command_and_wait_for_pattern(self,
+                                'sudo dnf install kernel-devel-$(uname -r) -y',
+                                'Complete!', vm=temp_vm)
+        wait_for_console_pattern(self, self.prompt, vm=temp_vm)
+        exec_command_and_wait_for_pattern(self,
+                                    'ls /usr/src/kernels/$(uname -r)/scripts/',
+                                    'sign-file', vm=temp_vm)
+
+        # Sign stage3 binary and kernel
+        exec_command(self, '/usr/src/kernels/$(uname -r)/scripts/sign-file '
+                    'sha256 mykey.pem mycert.pem /lib/s390-tools/stage3.bin',
+                    vm=temp_vm)
+        wait_for_console_pattern(self, self.prompt, vm=temp_vm)
+        exec_command(self, '/usr/src/kernels/$(uname -r)/scripts/sign-file '
+                    'sha256 mykey.pem mycert.pem /boot/vmlinuz-$(uname -r)',
+                    vm=temp_vm)
+        wait_for_console_pattern(self, self.prompt, vm=temp_vm)
+
+        # Run zipl to prepare for secure boot
+        exec_command_and_wait_for_pattern(self, 'zipl --secure 1 -VV', 'Done.',
+                                          vm=temp_vm)
+
+        # Extract certificate to host
+        out = exec_command_and_wait_for_pattern(self, 'cat mycert.pem',
+                                                '-----END CERTIFICATE-----',
+                                                vm=temp_vm)
+        # strip first line to avoid console echo artifacts
+        cert = "\n".join(out.decode("utf-8").splitlines()[1:])
+        self.log.info("%s", cert)
+
+        self.cert_path = self.scratch_file("mycert.pem")
+
+        with open(self.cert_path, 'w', encoding="utf-8") as file_object:
+            file_object.write(cert)
+
+        # Shutdown temp vm
+        temp_vm.shutdown()
+
+    @skipBigDataTest()
+    def test_s390x_secure_ipl(self):
+        self.require_accelerator('kvm')
+        self.setup_s390x_secure_ipl()
+
+        self.set_machine('s390-ccw-virtio')
+
+        self.vm.set_console()
+        self.vm.add_args('-nographic',
+                         '-machine', 's390-ccw-virtio,secure-boot=on,'
+                         f'boot-certs.0.path={self.cert_path}',
+                         '-accel', 'kvm',
+                         '-m', '1024',
+                         '-drive',
+                         f'id=drive1,if=none,format=qcow2,file={self.qcow2_path}',
+                         '-device', 'virtio-blk-ccw,drive=drive1,bootindex=1')
+        self.vm.launch()
+
+        # Expect two verified components
+        verified_output = "Verified component"
+        wait_for_console_pattern(self, verified_output)
+        wait_for_console_pattern(self, verified_output)
+
+        # Login and verify the vm is booted using secure boot
+        wait_for_console_pattern(self, 'localhost login:')
+        exec_command_and_wait_for_pattern(self, 'root', 'Password:')
+        exec_command_and_wait_for_pattern(self, self.root_password, self.prompt)
+        exec_command_and_wait_for_pattern(self, 'cat /sys/firmware/ipl/secure', '1')
+
+if __name__ == '__main__':
+    QemuSystemTest.main()
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 29/30] docs/specs: Add secure IPL documentation
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (27 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 28/30] tests/functional/s390x: Add secure IPL functional test Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  2026-03-05 22:41 ` [PATCH v9 30/30] docs/system/s390x: " Zhuoying Cai
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Add documentation for secure IPL

Signed-off-by: Collin Walling <walling@linux.ibm.com>
---
 docs/specs/s390x-secure-ipl.rst | 55 +++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 9903b9dcf2..60d5246286 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -1,5 +1,60 @@
 .. SPDX-License-Identifier: GPL-2.0-or-later
 
+s390 Secure IPL
+===============
+
+Secure IPL (a.k.a. secure boot) enables s390-ccw virtual machines to
+leverage qcrypto libraries and z/Architecture emulations to verify the
+integrity of signed kernels. The qcrypto libraries are used to perform
+certificate validation and signature-verification, whereas the
+z/Architecture emulations are used to ensure secure IPL data has not
+been tampered with, convey data between QEMU and guest code, and set up
+the relevant secure IPL data structures with verification results.
+
+To find out more about using this feature, see
+``docs/system/s390x/secure-ipl.rst``.
+
+Note that "guest code" will refer to the s390-ccw BIOS unless stated
+otherwise.
+
+Both QEMU and guest code work in cooperation to perform secure IPL. The Secure
+Loading Attributes Facility (SCLAF) is used to check the Secure Code
+Loading Attribute Block (SCLAB) and ensure that secure IPL data has not
+been tampered with. DIAGNOSE 'X'320' is invoked by guest code to query
+the certificate store info and retrieve specific certificates from QEMU.
+DIAGNOSE 'X'508' is used by guest code to leverage qcrypto libraries to
+perform signature-verification in QEMU. Lastly, guest code generates and
+appends an IPL Information Report Block (IIRB) at the end of the IPL
+Parameter Block, which is used by the kernel to store signed and
+verified entries.
+
+The logical steps are as follows:
+
+- guest code reads data payload from disk (e.g. stage3 boot loader, kernel)
+- guest code checks the validity of the SCLAB
+- guest code invokes DIAG 508 subcode 1 and provides it the payload
+- QEMU handles DIAG 508 request by reading the payload and retrieving the
+  certificate store
+- QEMU DIAG 508 utilizes qcrypto libraries to perform signature-verification on
+  the payload, attempting with each cert in the store (until success or
+  exhausted)
+- QEMU DIAG 508 returns:
+
+  - success: index of cert used to verify payload
+  - failure: error code
+
+- guest code responds to this operation:
+
+  - success: retrieves cert from store via DIAG 320 using returned index
+  - failure: reports with warning (audit mode), aborts with error (secure mode)
+
+- guest code appends IIRB at the end of the IPLB
+- guest code kicks off IPL
+
+More information regarding the respective DIAGNOSE commands and IPL data
+structures are outlined within this document.
+
+
 s390 Certificate Store and Functions
 ====================================
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v9 30/30] docs/system/s390x: Add secure IPL documentation
  2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (28 preceding siblings ...)
  2026-03-05 22:41 ` [PATCH v9 29/30] docs/specs: Add secure IPL documentation Zhuoying Cai
@ 2026-03-05 22:41 ` Zhuoying Cai
  29 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-05 22:41 UTC (permalink / raw)
  To: thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, walling, jjherne,
	pasic, borntraeger, farman, mjrosato, iii, eblake, armbru, zycai,
	alifm, brueckner, jdaley

Add documentation for secure IPL

Signed-off-by: Collin Walling <walling@linux.ibm.com>
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 docs/system/s390x/secure-ipl.rst | 97 ++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
index e0af086c38..db9fb46fea 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -1,5 +1,21 @@
 .. SPDX-License-Identifier: GPL-2.0-or-later
 
+s390 Secure IPL
+===============
+
+Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
+verify the integrity of guest kernels.
+
+For technical details of this feature, see ``docs/specs/s390x-secure-ipl.rst``.
+
+This document explains how to use secure IPL with s390x in QEMU. It covers
+the command line options for providing certificates and enabling secure IPL,
+the different IPL modes (Normal, Audit, and Secure), and system requirements.
+
+A quickstart guide is provided to demonstrate how to generate certificates,
+sign images, and start a guest in Secure Mode.
+
+
 Secure IPL Command Line Options
 ===============================
 
@@ -78,3 +94,84 @@ Configuration:
 .. code-block:: shell
 
     qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem ...
+
+
+Constraints
+===========
+
+The following constraints apply when attempting to boot an s390x guest in secure
+mode:
+
+- z16 or "qemu" CPU model
+- certificates must be in X.509 PEM format
+- only support for SCSI scheme of virtio-blk/virtio-scsi devices
+- a boot device must be specified
+- any unsupported devices (e.g., ECKD and VFIO) or non-eligible devices (e.g.,
+  network) will cause the entire boot process terminating early with an error
+  logged to the console.
+
+
+Secure IPL Quickstart
+=====================
+
+Build QEMU with gnutls enabled
+-------------------------------
+
+.. code-block:: shell
+
+    ./configure … --enable-gnutls
+
+Generate certificate (e.g. via certtool)
+----------------------------------------
+
+A private key is required before generating a certificate. This key must be kept
+secure and confidential.
+
+Use an RSA private key for signing.
+
+.. code-block:: shell
+
+    certtool --generate-privkey > key.pem
+
+A self-signed certificate requires the organization name. Use the ``cert.info``
+template to pre-fill values and avoid interactive prompts from certtool.
+
+.. code-block:: shell
+
+    cat > cert.info <<EOF
+    cn = "My Name"
+    expiration_days = 36500
+    cert_signing_key
+    EOF
+
+    certtool --generate-self-signed \
+             --load-privkey key.pem \
+             --template cert.info \
+             --hash=SHA256 \
+             --outfile cert.pem
+
+Sign Images (e.g. via sign-file)
+--------------------------------
+
+- signing must be performed on a guest filesystem
+- sign-file script used in the example below is located within the kernel source
+  repo
+
+.. code-block:: shell
+
+    ./sign-file sha256 key.pem cert.pem /boot/vmlinuz-…
+    ./sign-file sha256 key.pem cert.pem /usr/lib/s390-tools/stage3.bin
+
+Run zipl with secure boot enabled
+---------------------------------
+
+.. code-block:: shell
+
+    zipl --secure 1 -V
+
+Command line options for starting the guest
+-------------------------------------------
+
+.. code-block:: shell
+
+    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certs.0.path=cert.pem ...
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 09/30] s390x/diag: Implement DIAG 320 subcode 2
  2026-03-05 22:41 ` [PATCH v9 09/30] s390x/diag: Implement " Zhuoying Cai
@ 2026-03-13 19:58   ` Collin Walling
  2026-03-16 18:04     ` Zhuoying Cai
  0 siblings, 1 reply; 39+ messages in thread
From: Collin Walling @ 2026-03-13 19:58 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

On 3/5/26 17:41, Zhuoying Cai wrote:
> DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
> certificate store. Only X509 certificates in DER format and SHA-256 hash
> type are recognized.
> 
> The subcode value is denoted by setting the second-left-most bit
> of an 8-byte field.
> 
> The Verification Certificate Block (VCB) contains the output data
> when the operation completes successfully. It includes a common
> header followed by zero or more Verification Certificate Entries (VCEs),
> depending on the VCB input length and the VC range (from the first VC
> index to the last VC index) in the certificate store.
> 
> Each VCE contains information about a certificate retrieved from
> the S390IPLCertificateStore, such as the certificate name, key type,
> key ID length, hash length, and the raw certificate data.
> The key ID and hash are extracted from the raw certificate by the crypto API.
> 
> Note: SHA2-256 VC hash type is required for retrieving the hash
> (fingerprint) of the certificate.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>

Functionally, this looks fine.  I've noted a few nits below, but I don't
think they're worth dragging things out.

The important thing to address:
 - documentation rewording
 - CERT_NAME_MAX_LEN change

The rest are inconsequential.

> ---
>  docs/specs/s390x-secure-ipl.rst |  22 ++
>  hw/s390x/cert-store.h           |   3 +-
>  include/hw/s390x/ipl/diag320.h  |  55 +++++
>  target/s390x/diag.c             | 343 +++++++++++++++++++++++++++++++-
>  4 files changed, 420 insertions(+), 3 deletions(-)
> 
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 52661fab00..708253ac91 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -38,3 +38,25 @@ Subcode 1 - query verification certificate storage information
>      The output is returned in the verification-certificate-storage-size block
>      (VCSSB). A VCSSB length of 4 indicates that no certificates are available
>      in the CS.
> +
> +Subcode 2 - store verification certificates
> +    Provides VCs that are in the certificate store.
> +
> +    The output is provided in a VCB, which includes a common header followed by
> +    zero or more verification-certificate entries (VCEs).
> +
> +    The instruction expects the cert store to
> +    maintain an origin of 1 for the index (i.e. a retrieval of the first
> +    certificate in the store should be denoted by setting first-VC to 1).

Nit: early newline after "...cert store to".  There is space for a few
more characters on that line.

> +
> +    The first-VC index and last-VC index fields of VCB specify the range of VCs
> +    to be stored by subcode 2. Stored count and remained count fields specify
> +    the number of VCs stored and could not be stored in the VCB due to
> +    insufficient storage specified in the VCB input length field.
> +

This might read better.  It makes it more clear how they're used.

"""
The first-VC and last-VC fields of the VCB specify the index range of
VCs to be stored in the VCB. Certs are stored sequentially, starting
with first-VC index. As each cert is stored, a "stored count" is
incremented. If there is not enough space to store all certs requested
by the index range, a "remaining count" will be recorded and no more
certificates will be stored.
"""

> +    Each VCE contains a header followed by information extracted from a
> +    certificate within the certificate store. The information includes:
> +    key-id, hash, and certificate data. This information is stored
> +    contiguously in a VCE (with zero-padding). Following the header, the
> +    key-id is immediately stored. The hash and certificate data follow and
> +    may be accessed via the respective offset fields stored in the VCE.
> diff --git a/hw/s390x/cert-store.h b/hw/s390x/cert-store.h
> index 7fc9503cb9..6f5ee63177 100644
> --- a/hw/s390x/cert-store.h
> +++ b/hw/s390x/cert-store.h
> @@ -11,10 +11,9 @@
>  #define HW_S390_CERT_STORE_H
>  
>  #include "hw/s390x/ipl/qipl.h"
> +#include "hw/s390x/ipl/diag320.h"
>  #include "crypto/x509-utils.h"
>  
> -#define CERT_NAME_MAX_LEN  64
> -

Is there a reason why this was moved to diag320.h?

(comments below this point are inconsequential, only consider if a v10
is required).

>  #define CERT_KEY_ID_LEN    QCRYPTO_HASH_DIGEST_LEN_SHA256
>  #define CERT_HASH_LEN      QCRYPTO_HASH_DIGEST_LEN_SHA256
>  
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index 6e4779c699..bfd6385b40 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -12,19 +12,37 @@
>  
>  #define DIAG_320_SUBC_QUERY_ISM     0
>  #define DIAG_320_SUBC_QUERY_VCSI    1
> +#define DIAG_320_SUBC_STORE_VC      2
>  
>  #define DIAG_320_RC_OK              0x0001
>  #define DIAG_320_RC_NOT_SUPPORTED   0x0102
>  #define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
> +#define DIAG_320_RC_INVAL_VCB_LEN   0x0204
> +#define DIAG_320_RC_BAD_RANGE       0x0302
>  
>  #define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
>  #define DIAG_320_ISM_QUERY_VCSI     0x40000000
> +#define DIAG_320_ISM_STORE_VC       0x20000000
>  
>  #define VCSSB_NO_VC     4
>  #define VCSSB_MIN_LEN   128
>  #define VCE_HEADER_LEN  128
> +/*
> + * If the VCE flags indicate an invalid certificate,
> + * the VCE length is set to 72, containing only the
> + * first five fields of VCEntry.
> + */	
> +#define VCE_INVALID_LEN 72
>  #define VCB_HEADER_LEN  64
>  
> +#define CERT_NAME_MAX_LEN  64
> +
> +#define DIAG_320_VCE_FLAGS_VALID                0x80
> +#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING    0
> +#define DIAG_320_VCE_KEYTYPE_ECDSA_P521         1
> +#define DIAG_320_VCE_FORMAT_X509_DER            1
> +#define DIAG_320_VCE_HASHTYPE_SHA2_256          1
> +
>  struct VCStorageSizeBlock {
>      uint32_t length;
>      uint8_t reserved0[3];
> @@ -39,4 +57,41 @@ struct VCStorageSizeBlock {
>  };
>  typedef struct VCStorageSizeBlock VCStorageSizeBlock;
>  
> +struct VCBlock {
> +    uint32_t in_len;
> +    uint32_t reserved0;
> +    uint16_t first_vc_index;
> +    uint16_t last_vc_index;
> +    uint32_t reserved1[5];
> +    uint32_t out_len;
> +    uint8_t reserved2[4];
> +    uint16_t stored_ct;
> +    uint16_t remain_ct;
> +    uint32_t reserved3[5];
> +    uint8_t vce_buf[];
> +};
> +typedef struct VCBlock VCBlock;
> +
> +struct VCEntry {
> +    uint32_t len;
> +    uint8_t flags;
> +    uint8_t key_type;
> +    uint16_t cert_idx;
> +    uint8_t name[CERT_NAME_MAX_LEN];
> +    uint8_t format;
> +    uint8_t reserved0;
> +    uint16_t keyid_len;
> +    uint8_t reserved1;
> +    uint8_t hash_type;
> +    uint16_t hash_len;
> +    uint32_t reserved2;
> +    uint32_t cert_len;
> +    uint32_t reserved3[2];
> +    uint16_t hash_offset;
> +    uint16_t cert_offset;
> +    uint32_t reserved4[7];
> +    uint8_t cert_buf[];
> +};
> +typedef struct VCEntry VCEntry;
> +
>  #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index c44624e1e6..5326522fda 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -17,13 +17,16 @@
>  #include "s390x-internal.h"
>  #include "hw/watchdog/wdt_diag288.h"
>  #include "system/cpus.h"
> +#include "hw/s390x/cert-store.h"
>  #include "hw/s390x/ipl.h"
>  #include "hw/s390x/ipl/diag320.h"
>  #include "hw/s390x/s390-virtio-ccw.h"
>  #include "system/kvm.h"
>  #include "kvm/kvm_s390x.h"
>  #include "target/s390x/kvm/pv.h"
> +#include "qapi/error.h"
>  #include "qemu/error-report.h"
> +#include "crypto/x509-utils.h"
>  
>  
>  static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
> @@ -236,8 +239,333 @@ static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
>      return DIAG_320_RC_OK;
>  }
>  
> +static bool is_cert_valid(const S390IPLCertificate *cert)
> +{
> +    int rc;
> +    Error *err = NULL;
> +
> +    rc = qcrypto_x509_check_cert_times(cert->raw, cert->size, &err);
> +    if (rc != 0) {
> +        error_report_err(err);
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +static int handle_key_id(VCEntry *vce, const S390IPLCertificate *cert)
> +{
> +    int rc;
> +    g_autofree unsigned char *key_id_data = NULL;
> +    size_t key_id_len;
> +    Error *err = NULL;
> +
> +    rc = qcrypto_x509_get_cert_key_id(cert->raw, cert->size,
> +                                      QCRYPTO_HASH_ALGO_SHA256,
> +                                      &key_id_data, &key_id_len, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +
> +    if (VCE_HEADER_LEN + key_id_len > be32_to_cpu(vce->len)) {
> +        error_report("Unable to write key ID: exceeds buffer bounds");
> +        return -1;
> +    }
> +
> +    vce->keyid_len = cpu_to_be16(key_id_len);
> +
> +    memcpy(vce->cert_buf, key_id_data, key_id_len);
> +
> +    return 0;
> +}
> +
> +static int handle_hash(VCEntry *vce, const S390IPLCertificate *cert,
> +                       uint16_t keyid_field_len)
> +{
> +    int rc;
> +    uint16_t hash_offset;
> +    g_autofree void *hash_data = NULL;
> +    size_t hash_len;
> +    Error *err = NULL;
> +
> +    hash_len = CERT_HASH_LEN;
> +    hash_data = g_malloc0(hash_len);
> +    rc = qcrypto_get_x509_cert_fingerprint(cert->raw, cert->size,
> +                                           QCRYPTO_HASH_ALGO_SHA256,
> +                                           hash_data, &hash_len, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +
> +    hash_offset = VCE_HEADER_LEN + keyid_field_len;
> +    if (hash_offset + hash_len > be32_to_cpu(vce->len)) {
> +        error_report("Unable to write hash: exceeds buffer bounds");
> +        return -1;
> +    }
> +
> +    vce->hash_len = cpu_to_be16(hash_len);
> +    vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
> +    vce->hash_offset = cpu_to_be16(hash_offset);
> +
> +    memcpy((uint8_t *)vce + hash_offset, hash_data, hash_len);
> +
> +    return 0;
> +}
> +
> +static int handle_cert(VCEntry *vce, const S390IPLCertificate *cert,
> +                       uint16_t hash_field_len)
> +{
> +    int rc;
> +    uint16_t cert_offset;
> +    g_autofree uint8_t *cert_der = NULL;
> +    size_t der_size;
> +    Error *err = NULL;
> +
> +    rc = qcrypto_x509_convert_cert_der(cert->raw, cert->size,
> +                                       &cert_der, &der_size, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +
> +    cert_offset = be16_to_cpu(vce->hash_offset) + hash_field_len;
> +    if (cert_offset + der_size > be32_to_cpu(vce->len)) {
> +        error_report("Unable to write certificate: exceeds buffer bounds");
> +        return -1;
> +    }
> +
> +    vce->format = DIAG_320_VCE_FORMAT_X509_DER;
> +    vce->cert_len = cpu_to_be32(der_size);
> +    vce->cert_offset = cpu_to_be16(cert_offset);
> +
> +    memcpy((uint8_t *)vce + cert_offset, cert_der, der_size);
> +
> +    return 0;
> +}
> +
> +static int get_key_type(const S390IPLCertificate *cert)
> +{
> +    int rc;
> +    Error *err = NULL;
> +
> +    rc = qcrypto_x509_check_ecc_curve_p521(cert->raw, cert->size, &err);
> +    if (rc == -1) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +
> +    return (rc == 1) ? DIAG_320_VCE_KEYTYPE_ECDSA_P521 :
> +                        DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
> +}
> +
> +static int build_vce_header(VCEntry *vce, const S390IPLCertificate *cert, int idx)
> +{
> +    int key_type;
> +
> +    vce->len = cpu_to_be32(VCE_HEADER_LEN);
> +    vce->cert_idx = cpu_to_be16(idx + 1);
> +    memcpy(vce->name, cert->name, CERT_NAME_MAX_LEN);
> +
> +    key_type = get_key_type(cert);
> +    if (key_type == -1) {
> +        return -1;
> +    }
> +    vce->key_type = key_type;
> +
> +    return 0;
> +}
> +
> +static int build_vce_data(VCEntry *vce, const S390IPLCertificate *cert)
> +{
> +    uint16_t keyid_field_len;
> +    uint16_t hash_field_len;
> +    uint32_t cert_field_len;
> +    uint32_t vce_len;
> +    int rc;
> +
> +    rc = handle_key_id(vce, cert);
> +    if (rc) {
> +        return -1;
> +    }
> +    keyid_field_len = ROUND_UP(be16_to_cpu(vce->keyid_len), 4);
> +
> +    rc = handle_hash(vce, cert, keyid_field_len);
> +    if (rc) {
> +        return -1;
> +    }
> +    hash_field_len = ROUND_UP(be16_to_cpu(vce->hash_len), 4);
> +
> +    rc = handle_cert(vce, cert, hash_field_len);
> +    if (rc || !is_cert_valid(cert)) {
> +        return -1;
> +    }
> +    cert_field_len = ROUND_UP(be32_to_cpu(vce->cert_len), 4);
> +
> +    vce_len = VCE_HEADER_LEN + keyid_field_len + hash_field_len + cert_field_len;
> +    if (vce_len > be32_to_cpu(vce->len)) {
> +        return -1;
> +    }
> +
> +    vce->flags |= DIAG_320_VCE_FLAGS_VALID;
> +
> +    /* Update vce length to reflect the actual size used by vce */
> +    vce->len = cpu_to_be32(vce_len);
> +
> +    return 0;
> +}
> +
> +static VCEntry *diag_320_build_vce(const S390IPLCertificate *cert, int idx)
> +{
> +    g_autofree VCEntry *vce = NULL;
> +    uint32_t vce_max_size;
> +    int rc;
> +
> +    /*
> +     * Each field of the VCE is word-aligned.
> +     * Allocate enough space for the largest possible size for this VCE.
> +     * As the certificate fields (key-id, hash, data) are parsed, the
> +     * VCE's length field will be updated accordingly.
> +     */
> +    vce_max_size = VCE_HEADER_LEN +
> +                   ROUND_UP(CERT_KEY_ID_LEN, 4) +
> +                   ROUND_UP(CERT_HASH_LEN, 4) +
> +                   ROUND_UP(cert->der_size, 4);
> +
> +    vce = g_malloc0(vce_max_size);
> +    rc = build_vce_header(vce, cert, idx);
> +    if (rc) {
> +        /*
> +         * Error occurs - VCE does not contain a valid certificate.
> +         * Bit 0 of the VCE flags is 0 and the VCE length is set.
> +         */
> +        vce->len = cpu_to_be32(VCE_INVALID_LEN);
> +        goto out;
> +    }
> +
> +    vce->len = cpu_to_be32(vce_max_size);
> +    rc = build_vce_data(vce, cert);
> +    if (rc) {
> +        vce->len = cpu_to_be32(VCE_INVALID_LEN);
> +    }
> +
> +out:
> +    return g_steal_pointer(&vce);
> +}
> +
> +static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
> +                                   S390IPLCertificateStore *cs)
> +{
> +    g_autofree VCBlock *vcb = NULL;
> +    size_t entry_offset;
> +    size_t remaining_space;
> +    uint32_t vce_len;
> +    uint16_t first_vc_index;
> +    uint16_t last_vc_index;
> +    int cs_start_index;
> +    int cs_end_index;
> +    uint32_t in_len;
> +
> +    vcb = g_new0(VCBlock, 1);
> +    if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
> +        s390_cpu_virt_mem_handle_exc(cpu, ra);
> +        return -1;
> +    }
> +
> +    in_len = be32_to_cpu(vcb->in_len);
> +    first_vc_index = be16_to_cpu(vcb->first_vc_index);
> +    last_vc_index = be16_to_cpu(vcb->last_vc_index);
> +
> +    if (in_len % TARGET_PAGE_SIZE != 0) {
> +        return DIAG_320_RC_INVAL_VCB_LEN;
> +    }
> +
> +    if (first_vc_index > last_vc_index) {
> +        return DIAG_320_RC_BAD_RANGE;
> +    }
> +
> +    vcb->out_len = VCB_HEADER_LEN;
> +
> +    /*
> +     * DIAG 320 subcode 2 expects to query a certificate store that
> +     * maintains an index origin of 1. However, the S390IPLCertificateStore
> +     * maintains an index origin of 0. Thus, the indices must be adjusted
> +     * for correct access into the cert store. A couple of special cases
> +     * must also be accounted for.
> +     */
> +
> +    /* Both indices are 0; return header with no certs */
> +    if (first_vc_index == 0 && last_vc_index == 0) {
> +        goto out;
> +    }
> +
> +    /* Normalize indices */
> +    cs_start_index = (first_vc_index == 0) ? 0 : first_vc_index - 1;
> +    cs_end_index = last_vc_index - 1;
> +
> +    /* Requested range is outside the cert store; return header with no certs */
> +    if (cs_start_index >= cs->count || cs_end_index >= cs->count) {
> +        goto out;
> +    }
> +
> +    entry_offset = VCB_HEADER_LEN;
> +    remaining_space = in_len - VCB_HEADER_LEN;
> +
> +    for (int i = cs_start_index; i <= cs_end_index; i++) {
> +        VCEntry *vce;
> +        const S390IPLCertificate *cert = &cs->certs[i];
> +
> +        /*
> +         * Bit 0 of the VCE flags indicates whether the certificate is valid.
> +         * The caller of DIAG320 subcode 2 is responsible for verifying that
> +         * the VCE contains a valid certificate.
> +         */
> +        vce = diag_320_build_vce(cert, i);
> +        vce_len = be32_to_cpu(vce->len);

Why not just use vce->len?  It would seem that diag_320_build_vce will
set the len to one of:
	- max len
	- actual len
	- invalid len (72)

In all cases, endianness is accounted for.

> +
> +        /*
> +         * If there is no more space to store the cert,
> +         * set the remaining verification cert count and
> +         * break early.
> +         */
> +        if (remaining_space < vce_len) {
> +            vcb->remain_ct = cpu_to_be16(last_vc_index - i);
> +            g_free(vce);
> +            break;
> +        }
> +
> +        /* Write VCE */
> +        if (s390_cpu_virt_mem_write(cpu, addr + entry_offset, r1, vce, vce_len)) {
> +            s390_cpu_virt_mem_handle_exc(cpu, ra);
> +            g_free(vce);
> +            return -1;
> +        }
> +
> +        entry_offset += vce_len;

entry_offset seems to be serving the same purpose as vcb->out_len.  I
think it makes sense to drop the entry_offset variable and use
vcb->out_len in its place. Then there is one less thing to keep track of.

> +        vcb->out_len += vce_len;
> +        remaining_space -= vce_len;
> +        vcb->stored_ct++;
> +
> +        g_free(vce);
> +    }
> +    vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
> +
> +out:
> +    vcb->out_len = cpu_to_be32(vcb->out_len);
> +
> +    if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
> +        s390_cpu_virt_mem_handle_exc(cpu, ra);
> +        return -1;
> +    }
> +
> +    return DIAG_320_RC_OK;
> +}
> +
>  QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
>                     "size of VCStorageSizeBlock is wrong");
> +QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != VCB_HEADER_LEN, "size of VCBlock is wrong");
> +QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != VCE_HEADER_LEN, "size of VCEntry is wrong");
>  
>  void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>  {
> @@ -268,7 +596,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>           * for now.
>           */
>          uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
> -                                         DIAG_320_ISM_QUERY_VCSI);
> +                                         DIAG_320_ISM_QUERY_VCSI |
> +                                         DIAG_320_ISM_STORE_VC);
>  
>          if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
>              s390_cpu_virt_mem_handle_exc(cpu, ra);
> @@ -294,6 +623,18 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>          }
>          env->regs[r1 + 1] = rc;
>          break;
> +    case DIAG_320_SUBC_STORE_VC:
> +        if (addr & ~TARGET_PAGE_MASK) {
> +            s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> +            return;
> +        }
> +
> +        rc = handle_diag320_store_vc(cpu, addr, r1, ra, cs);
> +        if (rc == -1) {
> +            return;
> +        }
> +        env->regs[r1 + 1] = rc;
> +        break;
>      default:
>          env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
>          break;


-- 
Regards,
  Collin


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 13/30] s390x/ipl: Introduce IPL Information Report Block (IIRB)
  2026-03-05 22:41 ` [PATCH v9 13/30] s390x/ipl: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
@ 2026-03-13 20:00   ` Collin Walling
  0 siblings, 0 replies; 39+ messages in thread
From: Collin Walling @ 2026-03-13 20:00 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

On 3/5/26 17:41, Zhuoying Cai wrote:
> The IPL information report block (IIRB) contains information used
> to locate IPL records and to report the results of signature verification
> of one or more secure components of the load device.
> 
> IIRB is stored immediately following the IPL Parameter Block. Results on
> component verification in any case (failure or success) are stored.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> Reviewed-by: Farhan Ali<alifm@linux.ibm.com>

Reviewed-by: Collin Walling <walling@linux.ibm.com>


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 09/30] s390x/diag: Implement DIAG 320 subcode 2
  2026-03-13 19:58   ` Collin Walling
@ 2026-03-16 18:04     ` Zhuoying Cai
  0 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-16 18:04 UTC (permalink / raw)
  To: Collin Walling, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

Thanks for the review!

On 3/13/26 3:58 PM, Collin Walling wrote:
> On 3/5/26 17:41, Zhuoying Cai wrote:
>> DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
>> certificate store. Only X509 certificates in DER format and SHA-256 hash
>> type are recognized.
>>
>> The subcode value is denoted by setting the second-left-most bit
>> of an 8-byte field.
>>
>> The Verification Certificate Block (VCB) contains the output data
>> when the operation completes successfully. It includes a common
>> header followed by zero or more Verification Certificate Entries (VCEs),
>> depending on the VCB input length and the VC range (from the first VC
>> index to the last VC index) in the certificate store.
>>
>> Each VCE contains information about a certificate retrieved from
>> the S390IPLCertificateStore, such as the certificate name, key type,
>> key ID length, hash length, and the raw certificate data.
>> The key ID and hash are extracted from the raw certificate by the crypto API.
>>
>> Note: SHA2-256 VC hash type is required for retrieving the hash
>> (fingerprint) of the certificate.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> 
> Functionally, this looks fine.  I've noted a few nits below, but I don't
> think they're worth dragging things out.
> 
> The important thing to address:
>  - documentation rewording
>  - CERT_NAME_MAX_LEN change
> 
> The rest are inconsequential.
> 
>> ---
>>  docs/specs/s390x-secure-ipl.rst |  22 ++
>>  hw/s390x/cert-store.h           |   3 +-
>>  include/hw/s390x/ipl/diag320.h  |  55 +++++
>>  target/s390x/diag.c             | 343 +++++++++++++++++++++++++++++++-
>>  4 files changed, 420 insertions(+), 3 deletions(-)
>>
>> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
>> index 52661fab00..708253ac91 100644
>> --- a/docs/specs/s390x-secure-ipl.rst
>> +++ b/docs/specs/s390x-secure-ipl.rst
>> @@ -38,3 +38,25 @@ Subcode 1 - query verification certificate storage information
>>      The output is returned in the verification-certificate-storage-size block
>>      (VCSSB). A VCSSB length of 4 indicates that no certificates are available
>>      in the CS.
>> +
>> +Subcode 2 - store verification certificates
>> +    Provides VCs that are in the certificate store.
>> +
>> +    The output is provided in a VCB, which includes a common header followed by
>> +    zero or more verification-certificate entries (VCEs).
>> +
>> +    The instruction expects the cert store to
>> +    maintain an origin of 1 for the index (i.e. a retrieval of the first
>> +    certificate in the store should be denoted by setting first-VC to 1).
> 
> Nit: early newline after "...cert store to".  There is space for a few
> more characters on that line.
> 
>> +
>> +    The first-VC index and last-VC index fields of VCB specify the range of VCs
>> +    to be stored by subcode 2. Stored count and remained count fields specify
>> +    the number of VCs stored and could not be stored in the VCB due to
>> +    insufficient storage specified in the VCB input length field.
>> +
> 
> This might read better.  It makes it more clear how they're used.
> 
> """
> The first-VC and last-VC fields of the VCB specify the index range of
> VCs to be stored in the VCB. Certs are stored sequentially, starting
> with first-VC index. As each cert is stored, a "stored count" is
> incremented. If there is not enough space to store all certs requested
> by the index range, a "remaining count" will be recorded and no more
> certificates will be stored.
> """
> 
>> +    Each VCE contains a header followed by information extracted from a
>> +    certificate within the certificate store. The information includes:
>> +    key-id, hash, and certificate data. This information is stored
>> +    contiguously in a VCE (with zero-padding). Following the header, the
>> +    key-id is immediately stored. The hash and certificate data follow and
>> +    may be accessed via the respective offset fields stored in the VCE.
>> diff --git a/hw/s390x/cert-store.h b/hw/s390x/cert-store.h
>> index 7fc9503cb9..6f5ee63177 100644
>> --- a/hw/s390x/cert-store.h
>> +++ b/hw/s390x/cert-store.h
>> @@ -11,10 +11,9 @@
>>  #define HW_S390_CERT_STORE_H
>>  
>>  #include "hw/s390x/ipl/qipl.h"
>> +#include "hw/s390x/ipl/diag320.h"
>>  #include "crypto/x509-utils.h"
>>  
>> -#define CERT_NAME_MAX_LEN  64
>> -
> 
> Is there a reason why this was moved to diag320.h?
> 

It was moved to diag320.h because the same value is used by both
S390IPLCertificate and VCEntry from DIAG320. Since diag320.h is shared
by both QEMU and the BIOS, defining it there keeps the definition in a
single header and avoids duplication across multiple headers.

[...]


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2026-03-05 22:41 ` [PATCH v9 19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2026-03-17  3:41   ` Collin Walling
  2026-03-25 19:11     ` Zhuoying Cai
  0 siblings, 1 reply; 39+ messages in thread
From: Collin Walling @ 2026-03-17  3:41 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

On 3/5/26 17:41, 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 <zycai@linux.ibm.com>
> ---
>  docs/system/s390x/secure-ipl.rst |  35 +++
>  pc-bios/s390-ccw/Makefile        |   3 +-
>  pc-bios/s390-ccw/bootmap.c       |  36 +++-
>  pc-bios/s390-ccw/bootmap.h       |  11 +
>  pc-bios/s390-ccw/main.c          |   6 +
>  pc-bios/s390-ccw/s390-ccw.h      |  27 +++
>  pc-bios/s390-ccw/sclp.c          |  38 ++++
>  pc-bios/s390-ccw/sclp.h          |   6 +
>  pc-bios/s390-ccw/secure-ipl.c    | 357 +++++++++++++++++++++++++++++++
>  pc-bios/s390-ccw/secure-ipl.h    | 102 +++++++++
>  10 files changed, 618 insertions(+), 3 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..3a19b72085 100644
> --- a/docs/system/s390x/secure-ipl.rst
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -18,3 +18,38 @@ 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
> +----------
> +
> +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..43a661325f 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,7 +737,19 @@ static int zipl_run(ScsiBlockPtr *pte)
>      /* Load image(s) into RAM */
>      entry = (ComponentEntry *)(&header[1]);
>  
> -    rc = zipl_run_normal(&entry, tmp_sec);
> +    switch (boot_mode) {
> +    case ZIPL_BOOT_MODE_SECURE_AUDIT:
> +        rc = zipl_run_secure(&entry, tmp_sec);
> +        break;
> +    case ZIPL_BOOT_MODE_NORMAL:
> +        rc = zipl_run_normal(&entry, tmp_sec);
> +        break;
> +    default:
> +        puts("Unknown boot mode");
> +        rc = -1;
> +        break;
> +    }
> +
>      if (rc) {
>          return rc;
>      }
> @@ -1103,17 +1116,33 @@ static int zipl_load_vscsi(void)
>   * IPL starts here
>   */
>  
> +ZiplBootMode get_boot_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;
> +}
> +
>  void zipl_load(void)
>  {
>      VDev *vdev = virtio_get_device();
>  
>      if (vdev->is_cdrom) {
> +        IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
> +                   "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) {
> +        IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
> +                    "Virtio net boot device does not support secure boot!");
>          netmain();
>          puts("Failed to IPL from this network!");
>          return;
> @@ -1124,6 +1153,9 @@ void zipl_load(void)
>          return;
>      }
>  
> +    IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
> +                "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 819f053009..106cdf9dec 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  "        "
> @@ -275,6 +276,9 @@ static void ipl_boot_device(void)
>      switch (cutype) {
>      case CU_TYPE_DASD_3990:
>      case CU_TYPE_DASD_2107:
> +        IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),
> +                    "Passthrough (vfio) CCW device does not support secure boot!");
> +
>          dasd_ipl(blk_schid, cutype);
>          break;
>      case CU_TYPE_VIRTIO:
> @@ -324,6 +328,8 @@ void main(void)
>          probe_boot_device();
>      }
>  
> +    boot_mode = get_boot_mode(iplb->hdr_flags);
> +
>      while (have_iplb) {
>          boot_setup();
>          if (have_iplb && find_boot_device()) {
> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
> index b1dc35cded..a0d568696a 100644
> --- a/pc-bios/s390-ccw/s390-ccw.h
> +++ b/pc-bios/s390-ccw/s390-ccw.h
> @@ -40,6 +40,22 @@ typedef unsigned long long u64;
>                              ((b) == 0 ? (a) : (MIN(a, b))))
>  #endif
>  
> +/*
> + * Round number down to multiple. Requires that d be a power of 2.
> + * Works even if d is a smaller type than n.
> + */
> +#ifndef ROUND_DOWN
> +#define ROUND_DOWN(n, d) ((n) & -(0 ? (n) : (d)))
> +#endif
> +
> +/*
> + * Round number up to multiple. Requires that d be a power of 2.
> + * Works even if d is a smaller type than n.
> + */
> +#ifndef ROUND_UP
> +#define ROUND_UP(n, d) ROUND_DOWN((n) + (d) - 1, (d))
> +#endif
> +
>  #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
>  
>  #include "cio.h"
> @@ -64,6 +80,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 +94,15 @@ int virtio_read(unsigned long sector, void *load_addr);
>  /* bootmap.c */
>  void zipl_load(void);
>  
> +typedef enum ZiplBootMode {
> +    ZIPL_BOOT_MODE_NORMAL = 0,
> +    ZIPL_BOOT_MODE_SECURE_AUDIT = 1,
> +} ZiplBootMode;
> +
> +extern ZiplBootMode boot_mode;
> +
> +ZiplBootMode get_boot_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..e3b6a1f07e 100644
> --- a/pc-bios/s390-ccw/sclp.c
> +++ b/pc-bios/s390-ccw/sclp.c
> @@ -113,6 +113,44 @@ void sclp_get_loadparm_ascii(char *loadparm)
>      }
>  }
>  
> +bool sclp_is_diag320_on(void)
> +{
> +    ReadInfo *sccb = (void *)_sccb;
> +    uint8_t fac134 = 0;
> +
> +    memset((char *)_sccb, 0, sizeof(ReadInfo));
> +    sccb->h.length = SCCB_SIZE;
> +    if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
> +        fac134 = sccb->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)
> +{
> +

nit: remove newline at top of function body

> +    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;
> +    }
> +}
> +
> +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..8d281c1cea
> --- /dev/null
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -0,0 +1,357 @@
> +/*
> + * 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 <zycai@linux.ibm.com>
> + *
> + * 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"
> +
> +static uint8_t vcb_data[MAX_SECTOR_SIZE * 4] __attribute__((__aligned__(PAGE_SIZE)));
> +static uint8_t vcssb_data[VCSSB_MIN_LEN] __attribute__((__aligned__(8)));
> +
> +VCStorageSizeBlock *zipl_secure_get_vcssb(void)
> +{
> +    VCStorageSizeBlock *vcssb;
> +
> +    vcssb = (VCStorageSizeBlock *)vcssb_data;
> +    /* avoid retrieving vcssb multiple times */
> +    if (vcssb->length >= VCSSB_MIN_LEN) {
> +        return vcssb;
> +    }
> +
> +    if (!is_cert_store_facility_supported()) {
> +        puts("Certificate Store Facility is not supported by the hypervisor!");
> +        return NULL;
> +    }
> +
> +    vcssb->length = VCSSB_MIN_LEN;
> +    if (diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI) != DIAG_320_RC_OK) {
> +        vcssb->length = 0;
> +        return NULL;
> +    }
> +
> +    return vcssb;
> +}
> +
> +static uint32_t get_total_certs_length(void)
> +{
> +    VCStorageSizeBlock *vcssb;
> +
> +    vcssb = zipl_secure_get_vcssb();
> +    if (vcssb == NULL) {
> +        return 0;
> +    }
> +
> +    return vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct * VCE_HEADER_LEN;
> +}
> +
> +static uint32_t request_certificate(uint8_t *cert_addr, uint8_t index)
> +{
> +    VCStorageSizeBlock *vcssb;
> +    VCBlock *vcb;
> +    VCEntry *vce;
> +    uint32_t cert_len = 0;
> +
> +    /* 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
> +     */
> +    vcb = (VCBlock *)vcb_data;
> +    vcb->in_len = ROUND_UP(vcssb->max_single_vcb_len, PAGE_SIZE);
> +    vcb->first_vc_index = index;
> +    vcb->last_vc_index = index;
> +
> +    if (diag320(vcb, DIAG_320_SUBC_STORE_VC) != DIAG_320_RC_OK) {
> +        goto out;
> +    }
> +
> +    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_addr, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
> +    memcpy(vcb_data, 0, sizeof(vcb_data));
> +
> +out:

Probably makes more sense to zero the VCB here, under the `out` label.

> +    return cert_len;
> +}
> +
> +static void cert_list_add(IplSignatureCertificateList *cert_list, int cert_index,
> +                          uint8_t *cert_addr, uint64_t cert_len)
> +{
> +    if (cert_index > MAX_CERTIFICATES - 1) {
> +        printf("Warning: Ignoring cert entry #%d because only %d entries are supported\n",
> +                cert_index + 1, MAX_CERTIFICATES);
> +        return;
> +    }
> +
> +    cert_list->cert_entries[cert_index].addr = (uint64_t)cert_addr;
> +    cert_list->cert_entries[cert_index].len = cert_len;
> +    cert_list->ipl_info_header.len += sizeof(cert_list->cert_entries[cert_index]);
> +}
> +
> +static void comp_list_add(IplDeviceComponentList *comp_list, int comp_index,
> +                          int cert_index, uint64_t comp_addr,
> +                          uint64_t comp_len, uint8_t flags)
> +{
> +    if (comp_index > MAX_CERTIFICATES - 1) {
> +        printf("Warning: Ignoring comp entry #%d because only %d entries are supported\n",
> +                comp_index + 1, MAX_CERTIFICATES);
> +        return;
> +    }
> +
> +    comp_list->device_entries[comp_index].addr = comp_addr;
> +    comp_list->device_entries[comp_index].len = comp_len;
> +    comp_list->device_entries[comp_index].flags = flags;
> +    comp_list->device_entries[comp_index].cert_index = cert_index;
> +    comp_list->ipl_info_header.len += sizeof(comp_list->device_entries[comp_index]);
> +}
> +
> +static void update_iirb(IplDeviceComponentList *comp_list,
> +                        IplSignatureCertificateList *cert_list)
> +{
> +    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 = comp_list->ipl_info_header.len;
> +    certs_len = cert_list->ipl_info_header.len;
> +    if ((comps_len + certs_len + iirb_hdr_len) > sizeof(IplInfoReportBlock)) {
> +        panic("Not enough space to hold all components and certificates in IIRB");
> +    }
> +
> +    /* 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, comp_list, 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, cert_list, certs_len);
> +
> +    /* Update IIRB length */
> +    iirb->hdr.len += certs_len;
> +}
> +
> +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_signature_verif_supported()) {
> +        puts("Secure IPL extensions are not supported by the hypervisor!");
> +        return false;
> +    }
> +
> +    if (!is_cert_store_facility_supported()) {
> +        puts("Certificate Store Facility is not supported by the hypervisor!");
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +static void init_lists(IplDeviceComponentList *comp_list,
> +                       IplSignatureCertificateList *cert_list)
> +{
> +    comp_list->ipl_info_header.type = IPL_INFO_BLOCK_TYPE_COMPONENTS;
> +    comp_list->ipl_info_header.len = sizeof(comp_list->ipl_info_header);
> +
> +    cert_list->ipl_info_header.type = IPL_INFO_BLOCK_TYPE_CERTIFICATES;
> +    cert_list->ipl_info_header.len = sizeof(cert_list->ipl_info_header);
> +}
> +
> +static int zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
> +{
> +    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;
> +    }
> +
> +    return entry->compdat.sig_info.sig_len;
> +}
> +
> +int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
> +{
> +    IplDeviceComponentList comp_list = { 0 };
> +    IplSignatureCertificateList cert_list = { 0 };
> +    ComponentEntry *entry = *entry_ptr;
> +    uint8_t *cert_addr = 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;
> +    int cert_index;
> +    uint8_t flags;
> +    bool verified;
> +    /*
> +     * Keep track of which certificate store indices correspond to the
> +     * certificate data entries within the IplSignatureCertificateList to
> +     * prevent allocating space for the same certificate multiple times.
> +     *
> +     * The array index corresponds to the certificate's cert-store index.
> +     *
> +     * The array value corresponds to the certificate's entry within the
> +     * IplSignatureCertificateList (with a value of -1 denoting no entry
> +     * exists for the certificate).
> +     */
> +    int cert_list_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1 };
> +    int signed_count = 0;
> +
> +    if (!secure_ipl_supported()) {
> +        panic("Unable to boot in secure/audit mode");
> +    }
> +
> +    init_lists(&comp_list, &cert_list);
> +    cert_addr = malloc(get_total_certs_length());

I'm concerned about the cert_addr being malloc'd here...

> +    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);
> +
> +            /* default cert index and flags for unverified component */
> +            cert_index = -1;
> +            flags = S390_IPL_DEV_COMP_FLAG_SC;
> +
> +            if (verified) {
> +                if (cert_list_table[cert_table_idx] == -1) {
> +                    if (!request_certificate(cert_addr, cert_table_idx)) {
> +                        puts("Could not get certificate");
> +                        goto out;
> +                    }
> +
> +                    cert_list_table[cert_table_idx] = cert_entry_idx;
> +                    cert_list_add(&cert_list, cert_entry_idx, cert_addr, cert_len);

...then the malloc'd cert is added to the cert list (and this list later
gets appended to the IIRB).

Once IPL starts, the data at this address will eventually be
lost/overwritten. The certificate address needs to point to either:
 - somewhere on the disk
 - some dedicated memory space (likely designated by QEMU)

Though that begs the question: who/what makes use of the IIRB?  It seems
this information gets lost?

> +
> +                    /* increment for the next certificate */
> +                    cert_entry_idx++;
> +                    cert_addr += cert_len;
> +                }
> +
> +                puts("Verified component");
> +                cert_index = cert_list_table[cert_table_idx];
> +                flags |= S390_IPL_DEV_COMP_FLAG_CSV;
> +            }
> +
> +            comp_list_add(&comp_list, comp_entry_idx, cert_index,
> +                          comp_addr, comp_len, flags);
> +
> +            if (!verified) {
> +                zipl_secure_handle("Could not verify component");
> +            }
> +
> +            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");
> +    }
> +
> +    update_iirb(&comp_list, &cert_list);
> +
> +    *entry_ptr = entry;
> +    free(sig);
> +
> +    return 0;
> +out:
> +    free(cert_addr);
> +    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..eb5ba0ed47
> --- /dev/null
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -0,0 +1,102 @@
> +/*
> + * S/390 Secure IPL
> + *
> + * Copyright 2025 IBM Corp.
> + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
> + *
> + * 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)
> +{
> +    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;
> +
> +    if (!sclp_is_diag320_on()) {
> +        return false;
> +    }
> +
> +    diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
> +    return d320_ism & (DIAG_320_ISM_QUERY_VCSI | DIAG_320_ISM_STORE_VC);
> +}
> +
> +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_signature_verif_supported(void)
> +{
> +    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;
> +        /*
> +         * 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 */
-- 
Regards,
  Collin


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 20/30] pc-bios/s390-ccw: Add signed component address overlap checks
  2026-03-05 22:41 ` [PATCH v9 20/30] pc-bios/s390-ccw: Add signed component address overlap checks Zhuoying Cai
@ 2026-03-17  4:25   ` Collin Walling
  0 siblings, 0 replies; 39+ messages in thread
From: Collin Walling @ 2026-03-17  4:25 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

On 3/5/26 17:41, Zhuoying Cai wrote:
> Add address range tracking and overlap checks to ensure that no
> component overlaps with a signed component during secure IPL.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  pc-bios/s390-ccw/secure-ipl.c | 52 +++++++++++++++++++++++++++++++++++
>  pc-bios/s390-ccw/secure-ipl.h |  6 ++++
>  2 files changed, 58 insertions(+)
> 
> diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
> index 8d281c1cea..68596491c5 100644
> --- a/pc-bios/s390-ccw/secure-ipl.c
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -211,6 +211,53 @@ static void init_lists(IplDeviceComponentList *comp_list,
>      cert_list->ipl_info_header.len = sizeof(cert_list->ipl_info_header);
>  }
>  
> +static bool is_comp_overlap(SecureIplCompAddrRange *comp_addr_range,
> +                            int addr_range_index,
> +                            uint64_t start_addr, uint64_t end_addr)
> +{
> +    /* neither a signed nor an unsigned component can overlap with a signed component */

How about: "Check component's address range does not overlap with any
signed component's address range."

> +    for (int i = 0; i < addr_range_index; i++) {
> +        if ((comp_addr_range[i].start_addr < end_addr &&
> +            start_addr < comp_addr_range[i].end_addr) &&
> +            comp_addr_range[i].is_signed) {
> +            return true;
> +       }
> +    }
> +
> +    return false;
> +}
> +
> +static void comp_addr_range_add(SecureIplCompAddrRange *comp_addr_range,
> +                                int addr_range_index, bool is_signed,
> +                                uint64_t start_addr, uint64_t end_addr)
> +{
> +    if (addr_range_index >= MAX_CERTIFICATES) {
> +        zipl_secure_handle("Component address range update failed due to out-of-range"
> +                           " index; Overlapping validation cannot be guaranteed");
> +    }
> +
> +    comp_addr_range[addr_range_index].is_signed = is_signed;
> +    comp_addr_range[addr_range_index].start_addr = start_addr;
> +    comp_addr_range[addr_range_index].end_addr = end_addr;
> +}
> +
> +static void addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
> +                               int *addr_range_index,
> +                               uint64_t start_addr, uint64_t end_addr, bool is_signed)
> +{
> +    bool overlap;
> +
> +    overlap = is_comp_overlap(comp_addr_range, *addr_range_index,
> +                              start_addr, end_addr);
> +    if (overlap) {
> +        zipl_secure_handle("Component addresses overlap");
> +    }
> +
> +    comp_addr_range_add(comp_addr_range, *addr_range_index, is_signed,
> +                        start_addr, end_addr);
> +    *addr_range_index += 1;
> +}
> +
>  static int zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
>  {
>      if (zipl_load_segment(entry, sig_sec) < 0) {
> @@ -254,6 +301,8 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>       * exists for the certificate).
>       */
>      int cert_list_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1 };
> +    SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
> +    int addr_range_index = 0;
>      int signed_count = 0;
>  
>      if (!secure_ipl_supported()) {
> @@ -283,6 +332,9 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>                  goto out;
>              }
>  
> +            addr_overlap_check(comp_addr_range, &addr_range_index,
> +                               comp_addr, comp_addr + comp_len, sig_len > 0);

super nit: I'd prefer !!sig_len versus sig_len > 0.

> +
>              if (!sig_len) {
>                  break;
>              }
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> index eb5ba0ed47..69edfce241 100644
> --- a/pc-bios/s390-ccw/secure-ipl.h
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -16,6 +16,12 @@
>  VCStorageSizeBlock *zipl_secure_get_vcssb(void);
>  int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
>  
> +typedef struct SecureIplCompAddrRange {
> +    bool is_signed;
> +    uint64_t start_addr;
> +    uint64_t end_addr;
> +} SecureIplCompAddrRange;
> +

Since this is a custom construct made for keeping track of address
ranges, why not define your own list data structure that also keeps
track of the index?  Could do:

typedef struct SecureIplCompAddrRangeList {
	SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
	int index;
}

Then you could greatly simplify the function signatures by passing the
list and single entry.  Depending on how reduced the code is after the
changes, it might look better to just do something like this in
zipl_run_secure and get rid of addr_overlap_check:

```
if (is_comp_overlap(list, comp)) {
	zipl_secure_handle("message");
}
comp_addr_range_list_add(list, comp);
```

Would make things look a lot cleaner imho :)

Note: it may make sense to carry this list structure in other areas too
-- it would help keep track of the respective list indexes and result in
less variables passed around.

>  static inline void zipl_secure_handle(const char *message)
>  {
>      switch (boot_mode) {


-- 
Regards,
  Collin


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 22/30] pc-bios/s390-ccw: Add additional security checks for secure boot
  2026-03-05 22:41 ` [PATCH v9 22/30] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
@ 2026-03-17  6:54   ` Collin Walling
  0 siblings, 0 replies; 39+ messages in thread
From: Collin Walling @ 2026-03-17  6:54 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

On 3/5/26 17:41, 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.
> However, a missing SCLAB will not be reported in audit mode. The SCALB
> checking will be skipped in this case.
> 
> Add IPL Information Error Indicators (IIEI) and Component Error
> Indicators (CEI) for IPL Information Report Block (IIRB).

This goes back to my comment in patch 19: who/what is going to make use
of the IIRB? If no one or nothing will inspect this data structure, then
perhaps it's sufficient enough to simply log the errors?

> 
> When SCLAF is installed, additional secure boot checks are performed
> during zipl and store results of verification into IIRB.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  include/hw/s390x/ipl/qipl.h   |  29 +++-
>  pc-bios/s390-ccw/s390-ccw.h   |   1 +
>  pc-bios/s390-ccw/sclp.c       |   8 +
>  pc-bios/s390-ccw/sclp.h       |   1 +
>  pc-bios/s390-ccw/secure-ipl.c | 314 +++++++++++++++++++++++++++++++++-
>  pc-bios/s390-ccw/secure-ipl.h |  42 +++++
>  6 files changed, 391 insertions(+), 4 deletions(-)
> 
> diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
> index 1b6cb3231d..9518fcb1dc 100644
> --- a/include/hw/s390x/ipl/qipl.h
> +++ b/include/hw/s390x/ipl/qipl.h
> @@ -136,10 +136,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;
>  
> @@ -163,13 +173,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_SCLA    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/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
> index a0d568696a..7d1a9d4acc 100644
> --- a/pc-bios/s390-ccw/s390-ccw.h
> +++ b/pc-bios/s390-ccw/s390-ccw.h
> @@ -82,6 +82,7 @@ void sclp_setup(void);
>  void sclp_get_loadparm_ascii(char *loadparm);
>  bool sclp_is_diag320_on(void);
>  bool sclp_is_sipl_on(void);
> +bool sclp_is_sclaf_on(void);
>  int sclp_read(char *str, size_t count);
>  
>  /* virtio.c */
> diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
> index e3b6a1f07e..5dbddff9ae 100644
> --- a/pc-bios/s390-ccw/sclp.c
> +++ b/pc-bios/s390-ccw/sclp.c
> @@ -151,6 +151,14 @@ bool sclp_is_sipl_on(void)
>      return fac_ipl & SCCB_FAC_IPL_SIPL_BIT;
>  }
>  
> +bool sclp_is_sclaf_on(void)
> +{
> +    uint16_t fac_ipl = 0;
> +
> +    sclp_get_fac_ipl(&fac_ipl);
> +    return fac_ipl & SCCB_FAC_IPL_SCLAF_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 cf147f4634..3441020d6b 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 68596491c5..840b88a699 100644
> --- a/pc-bios/s390-ccw/secure-ipl.c
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -198,6 +198,12 @@ static bool secure_ipl_supported(void)
>          return false;
>      }
>  
> +    if (!sclp_is_sclaf_on()) {
> +        puts("Secure IPL Code Loading Attributes Facility is not supported by"
> +             " the hypervisor!");
> +        return false;
> +    }
> +
>      return true;
>  }
>  
> @@ -258,6 +264,286 @@ static void addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
>      *addr_range_index += 1;
>  }
>  
> +static void check_unsigned_addr(uint64_t load_addr, IplDeviceComponentEntry *comp_entry)
> +{
> +    /* unsigned load address must be greater than or equal to 0x2000 */
> +    if (load_addr >= 0x2000) {
> +        return;
> +    }
> +
> +    set_comp_cei_with_log(comp_entry, S390_CEI_INVALID_UNSIGNED_ADDR,
> +                          "Load address is less than 0x2000");
> +}

I think I still would have preferred some sort of assert-styled function
that could be used in place of a lot of these simpler functions e.g.:

```
component_check(load_addr >= 0x2000, comp_entry,
                S390_CEI_INVALID_UNSIGNED_ADDR,
                "Load address is less than 0x2000");
```

(Might need a more fitting name, though.)

It's basically the `set_comp_cei_with_log()` function with a boolean
parameter up front.

> +
> +static bool check_sclab_presence(uint8_t *sclab_magic,
> +                                 IplDeviceComponentEntry *comp_entry)
> +{
> +    /* identifies the presence of SCLAB */
> +    if (magic_match(sclab_magic, ZIPL_MAGIC)) {
> +        return true;
> +    }
> +
> +    if (comp_entry) {
> +        comp_entry->cei |= S390_CEI_INVALID_SCLAB;
> +    }
> +
> +    /* a missing SCLAB will not be reported in audit mode */
> +    return false;
> +}
> +
> +static void check_sclab_length(uint16_t sclab_len, IplDeviceComponentEntry *comp_entry)
> +{
> +    if (sclab_len >= S390_SECURE_IPL_SCLAB_MIN_LEN) {
> +        return;
> +    }
> +
> +    set_comp_cei_with_log(comp_entry,
> +                          S390_CEI_INVALID_SCLAB_LEN | S390_CEI_INVALID_SCLAB,
> +                          "Invalid SCLAB length");
> +}
> +
> +static void check_sclab_format(uint8_t sclab_format, IplDeviceComponentEntry *comp_entry)
> +{
> +    /* SCLAB format must set to zero, indicating a format-0 SCLAB being used */
> +    if (sclab_format == 0) {
> +        return;
> +    }
> +
> +    set_comp_cei_with_log(comp_entry, S390_CEI_INVALID_SCLAB_FORMAT,
> +                          "Format-0 SCLAB is not being used");
> +}
> +
> +static void check_sclab_opsw(SecureCodeLoadingAttributesBlock *sclab,
> +                             SecureIplSclabInfo *sclab_info,
> +                             IplDeviceComponentEntry *comp_entry)
> +{
> +    const char *msg;
> +    uint32_t cei_flag = 0;
> +
> +    if (!(sclab->flags & S390_SECURE_IPL_SCLAB_FLAG_OPSW)) {
> +        /* OPSW = 0 - Load PSW field in SCLAB must contain zeros */
> +        if (sclab->load_psw != 0) {
> +            cei_flag |= S390_CEI_SCLAB_LOAD_PSW_NOT_ZERO;
> +            msg = "Load PSW is not zero when Override PSW bit is zero";
> +        }
> +    } else {
> +        /* OPSW = 1 indicating global SCLAB */
> +        sclab_info->global_count += 1;
> +        if (sclab_info->global_count == 1) {
> +            sclab_info->load_psw = sclab->load_psw;
> +            sclab_info->flags = sclab->flags;
> +        }
> +
> +        /* OLA must set to one */
> +        if (!(sclab->flags & S390_SECURE_IPL_SCLAB_FLAG_OLA)) {
> +            cei_flag |= S390_CEI_SCLAB_OLA_NOT_ONE;
> +            msg = "Override Load Address bit is not set to one in the global SCLAB";
> +        }
> +    }
> +
> +    if (cei_flag) {
> +        set_comp_cei_with_log(comp_entry, cei_flag, msg);
> +    }
> +}
> +
> +static void check_sclab_ola(SecureCodeLoadingAttributesBlock *sclab, uint64_t load_addr,
> +                            IplDeviceComponentEntry *comp_entry)
> +{
> +    const char *msg;
> +    uint32_t cei_flag = 0;
> +
> +    if (!(sclab->flags & S390_SECURE_IPL_SCLAB_FLAG_OLA)) {
> +        /* OLA = 0 - Load address field in SCLAB must contain zeros */
> +        if (sclab->load_addr != 0) {
> +            cei_flag |= S390_CEI_SCLAB_LOAD_ADDR_NOT_ZERO;
> +            msg = "Load Address is not zero when Override Load Address bit is zero";
> +        }
> +    } else {
> +        /* OLA = 1 - Load address field must match storage address of the component */
> +        if (sclab->load_addr != load_addr) {
> +            cei_flag |= S390_CEI_UNMATCHED_SCLAB_LOAD_ADDR;
> +            msg = "Load Address does not match with component load address";
> +        }
> +    }
> +
> +    if (cei_flag) {
> +        set_comp_cei_with_log(comp_entry, cei_flag, msg);
> +    }
> +}
> +
> +static void check_sclab_nuc(uint16_t sclab_flags, IplDeviceComponentEntry *comp_entry)
> +{
> +    const char *msg;
> +    bool is_nuc_set;
> +    bool is_global_sclab;
> +
> +    is_nuc_set = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_NUC;
> +    is_global_sclab = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_OPSW;
> +    if (is_nuc_set && !is_global_sclab) {
> +        msg = "No Unsigned Components bit is set, but not in the global SCLAB";
> +        set_comp_cei_with_log(comp_entry, S390_CEI_NUC_NOT_IN_GLOBAL_SCLA, msg);
> +    }
> +}
> +
> +static void check_sclab_sc(uint16_t sclab_flags, IplDeviceComponentEntry *comp_entry)
> +{
> +    const char *msg;
> +    bool is_sc_set;
> +    bool is_global_sclab;
> +
> +    is_sc_set = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_SC;
> +    is_global_sclab = sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_OPSW;
> +    if (is_sc_set && !is_global_sclab) {
> +        msg = "Single Component bit is set, but not in the global SCLAB";
> +        set_comp_cei_with_log(comp_entry, S390_CEI_SC_NOT_IN_GLOBAL_SCLAB, msg);
> +    }
> +}
> +
> +static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
> +                         int range_index)
> +{
> +    uint32_t addr = psw & 0x7fffffff;
> +
> +    /* PSW points within a signed binary code component */
> +    for (int i = 0; i < range_index; i++) {
> +        if (comp_addr_range[i].is_signed &&
> +            addr >= comp_addr_range[i].start_addr &&
> +            addr <= comp_addr_range[i].end_addr - 2) {
> +            return true;
> +       }
> +    }
> +
> +    return false;
> +}
> +
> +static void check_load_psw(SecureIplCompAddrRange *comp_addr_range,
> +                           int addr_range_index, uint64_t sclab_load_psw,
> +                           uint64_t load_psw, IplDeviceComponentEntry *comp_entry)
> +{
> +    bool valid;
> +
> +    valid = is_psw_valid(sclab_load_psw, comp_addr_range, addr_range_index) &&
> +            is_psw_valid(load_psw, comp_addr_range, addr_range_index);
> +    if (!valid) {
> +        set_comp_cei_with_log(comp_entry, S390_CEI_INVALID_LOAD_PSW, "Invalid PSW");
> +    }
> +
> +    /* compare load PSW with the PSW specified in component */
> +    if (sclab_load_psw != load_psw) {
> +        set_comp_cei_with_log(comp_entry, S390_CEI_UNMATCHED_SCLAB_LOAD_PSW,
> +                              "Load PSW does not match with PSW in component");
> +    }
> +}
> +
> +static void check_nuc(uint16_t global_sclab_flags, int unsigned_count,
> +                      IplDeviceComponentList *comp_list)
> +{
> +    bool is_nuc_set;
> +
> +    is_nuc_set = global_sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_NUC;
> +    if (is_nuc_set && unsigned_count > 0) {
> +        comp_list->ipl_info_header.iiei |= S390_IIEI_FOUND_UNSIGNED_COMP;
> +        zipl_secure_handle("Unsigned components are not allowed");
> +    }
> +}
> +
> +static void check_sc(uint16_t global_sclab_flags,
> +                     int signed_count, int unsigned_count,
> +                     IplDeviceComponentList *comp_list)
> +{
> +    bool is_sc_set;
> +
> +    is_sc_set = global_sclab_flags & S390_SECURE_IPL_SCLAB_FLAG_SC;
> +    if (is_sc_set && signed_count != 1 && unsigned_count >= 0) {
> +        comp_list->ipl_info_header.iiei |= S390_IIEI_MORE_SIGNED_COMP;
> +        zipl_secure_handle("Only one signed component is allowed");
> +    }
> +}
> +
> +void check_global_sclab(SecureIplSclabInfo sclab_info,
> +                        int unsigned_count, int signed_count,
> +                        IplDeviceComponentList *comp_list)
> +{
> +    if (sclab_info.count == 0) {
> +        return;
> +    }
> +
> +    if (sclab_info.global_count == 0) {
> +        comp_list->ipl_info_header.iiei |= S390_IIEI_NO_GLOBAL_SCLAB;
> +        zipl_secure_handle("Global SCLAB does not exists");
> +        return;
> +    }
> +
> +    if (sclab_info.global_count > 1) {
> +        comp_list->ipl_info_header.iiei |= S390_IIEI_MORE_GLOBAL_SCLAB;
> +        zipl_secure_handle("More than one global SCLAB");
> +        return;
> +    }
> +
> +    if (sclab_info.flags) {
> +        /* Unsigned components are not allowed if NUC flag is set in the global SCLAB */
> +        check_nuc(sclab_info.flags, unsigned_count, comp_list);
> +
> +        /* Only one signed component is allowed is SC flag is set in the global SCLAB */
> +        check_sc(sclab_info.flags, signed_count, unsigned_count, comp_list);

There's `check_sc()` and also `check_sclab_sc()` (and
`check_signed_comp()`, which is similar).  At least with the first two,
the functions are very easy to mix up (in fact, I was going to suggest
renaming check_sc to check_sclab_sc).

> +    }
> +}
> +
> +static void check_signed_comp(int signed_count, IplDeviceComponentList *comp_list)
> +{
> +    if (signed_count > 0) {
> +        return;
> +    }
> +
> +    comp_list->ipl_info_header.iiei |= S390_IIEI_NO_SIGNED_COMP;
> +    zipl_secure_handle("Secure boot is on, but components are not signed");
> +}
> +
> +static void check_sclab_count(int count, IplDeviceComponentList *comp_list)
> +{
> +    if (count > 0) {
> +        return;
> +    }
> +
> +    comp_list->ipl_info_header.iiei |= S390_IIEI_NO_SCLAB;
> +    zipl_secure_handle("No recognizable SCLAB");
> +}
> +
> +static void check_sclab(uint64_t comp_addr, uint64_t comp_len,
> +                        IplDeviceComponentEntry *comp_entry,
> +                        SecureIplSclabInfo *sclab_info)
> +{
> +    SclabOriginLocator *sclab_locator;
> +    SecureCodeLoadingAttributesBlock *sclab;
> +    bool exist;
> +
> +    /* sclab locator is located at the last 8 bytes of the signed comp */
> +    sclab_locator = (SclabOriginLocator *)(comp_addr + comp_len - 8);
> +
> +    /* return early if sclab does not exist */
> +    exist = check_sclab_presence(sclab_locator->magic, comp_entry);

Could just merge this into the check below:

```
if (!check_sclab_presence(...)) {
    return;
}
```

> +    if (!exist) {
> +        return;
> +    }
> +
> +    check_sclab_length(sclab_locator->len, comp_entry);
> +
> +    /* return early if sclab is invalid */
> +    if (comp_entry && (comp_entry->cei & S390_CEI_INVALID_SCLAB)) {
> +        return;
> +    }
> +
> +    sclab_info->count += 1;
> +    sclab = (SecureCodeLoadingAttributesBlock *)(comp_addr + comp_len -
> +                                                 sclab_locator->len);
> +
> +    check_sclab_format(sclab->format, comp_entry);
> +    check_sclab_opsw(sclab, sclab_info, comp_entry);
> +    check_sclab_ola(sclab, comp_addr, comp_entry);
> +    check_sclab_nuc(sclab->flags, comp_entry);
> +    check_sclab_sc(sclab->flags, comp_entry);
> +}
> +
>  static int zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
>  {
>      if (zipl_load_segment(entry, sig_sec) < 0) {
> @@ -304,6 +590,9 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>      SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
>      int addr_range_index = 0;
>      int signed_count = 0;
> +    int unsigned_count = 0;
> +    SecureIplSclabInfo sclab_info = { 0 };
> +    IplDeviceComponentEntry *comp_entry;
>  
>      if (!secure_ipl_supported()) {
>          panic("Unable to boot in secure/audit mode");
> @@ -335,10 +624,21 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>              addr_overlap_check(comp_addr_range, &addr_range_index,
>                                 comp_addr, comp_addr + comp_len, sig_len > 0);
>  
> +            comp_entry = (comp_entry_idx < MAX_CERTIFICATES) ?
> +                         &comp_list.device_entries[comp_entry_idx] : NULL;
> +

This took me a little bit of time to understand why this was happening.
It'd make a *little* more sense if it were nested under the if statement
below.

It would read better to `#define` a `MAX_COMPONENTS` as well.  Could
just alias `MAX_CERTIFICATES` so both use the same value.

>              if (!sig_len) {
> +                check_unsigned_addr(comp_addr, comp_entry);
> +                comp_list_add(&comp_list, comp_entry_idx, cert_entry_idx,
> +                              comp_addr, comp_len, 0x00);
> +
> +                unsigned_count += 1;
> +                comp_entry_idx++;
>                  break;
>              }
>  
> +            check_sclab(comp_addr, comp_len,
> +                        &comp_list.device_entries[comp_entry_idx], &sclab_info);
>              verified = verify_signature(comp_len, comp_addr, sig_len, (uint64_t)sig,
>                                          &cert_len, &cert_table_idx);
>  
> @@ -391,10 +691,20 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>          }
>      }
>  
> -    if (signed_count == 0) {
> -        zipl_secure_handle("Secure boot is on, but components are not signed");
> +    /* validate load PSW with PSW specified in the final entry */
> +    if (sclab_info.load_psw) {
> +        comp_entry = (comp_entry_idx < MAX_CERTIFICATES) ?
> +                     &comp_list.device_entries[comp_entry_idx] : NULL;

Silently accepting a NULL component makes it seem like an error should
be thrown, or a special case should be handled.

It looks like these `check_*()` functions only need the `cei` field of
the `comp_entry`.  Instead of passing the entire `comp_entry` to these
functions, pass something like an independent `cei_flags` variable. The
functions will set the appropriate error bits where appropriate.  This
also eliminates the various checks for `comp_entry != NULL`.

Then change `comp_list_add()` to accept the `cei_flags` field and set it
in the respective component while still accounting for the
`MAX_CERTIFICATES` check.

> +        check_load_psw(comp_addr_range, addr_range_index,
> +                       sclab_info.load_psw, entry->compdat.load_psw, comp_entry);
> +        comp_list_add(&comp_list, comp_entry_idx, -1,
> +                      entry->compdat.load_psw, 0, 0x00);

Why is the exec component added to the list?  It wasn't added before
this patch.

>      }
>  
> +    check_signed_comp(signed_count, &comp_list);
> +    check_sclab_count(sclab_info.count, &comp_list);
> +    check_global_sclab(sclab_info, unsigned_count, signed_count, &comp_list);
> +
>      update_iirb(&comp_list, &cert_list);
>  
>      *entry_ptr = entry;
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> index 69edfce241..4e9f4f08b9 100644
> --- a/pc-bios/s390-ccw/secure-ipl.h
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -16,6 +16,38 @@
>  VCStorageSizeBlock *zipl_secure_get_vcssb(void);
>  int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
>  
> +#define S390_SECURE_IPL_SCLAB_FLAG_OPSW    0x8000
> +#define S390_SECURE_IPL_SCLAB_FLAG_OLA     0x4000
> +#define S390_SECURE_IPL_SCLAB_FLAG_NUC     0x2000
> +#define S390_SECURE_IPL_SCLAB_FLAG_SC      0x1000
> +
> +#define S390_SECURE_IPL_SCLAB_MIN_LEN      32
> +
> +struct SecureCodeLoadingAttributesBlock {
> +    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 SecureCodeLoadingAttributesBlock SecureCodeLoadingAttributesBlock;
> +
> +struct SclabOriginLocator {
> +    uint8_t reserved[2];
> +    uint16_t len;
> +    uint8_t magic[4];
> +} __attribute__ ((packed));
> +typedef struct SclabOriginLocator SclabOriginLocator;
> +
> +typedef struct SecureIplSclabInfo {
> +    int count;
> +    int global_count;
> +    uint64_t load_psw;
> +    uint16_t flags;
> +} SecureIplSclabInfo;

IIRC, `SecureIplSclabInfo` was invented in these patches to help
consolidate some fields.  Unless I'm wrong, you could add an
`unsigned_count` and `signed_count` fields.  That will clear up some
stuff in `zipl_run_secure()`.

I'd also suggest prepending `global_` to `load_psw` and `flags` here to
make it clear what they represent.

Put a comment above explaining something like "Custom struct used to
consolidate SCLAB overhead".  Anything to signal that this isn't a
documented s390 data structure.

> +
>  typedef struct SecureIplCompAddrRange {
>      bool is_signed;
>      uint64_t start_addr;
> @@ -33,6 +65,16 @@ static inline void zipl_secure_handle(const char *message)
>      }
>  }
>  
> +static inline void set_comp_cei_with_log(IplDeviceComponentEntry *comp_entry,
> +                                         uint32_t flag, const char *message)
> +{
> +    if (comp_entry) {
> +        comp_entry->cei |= flag;
> +    }
> +
> +    zipl_secure_handle(message);
> +}
> +
>  static inline uint64_t diag320(void *data, unsigned long subcode)
>  {
>      register unsigned long addr asm("0") = (unsigned long)data;

Functionally, I think things look sane.  Please look into a way to
simplify the functions that exhibit similar patterns and reduce the
number of parameters required by each function.  SCLAF is a bit of an
abstract concept, so anything to make code simpler will help out a lot.

-- 
Regards,
  Collin


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 25/30] pc-bios/s390-ccw: Handle true secure IPL mode
  2026-03-05 22:41 ` [PATCH v9 25/30] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
@ 2026-03-17  7:02   ` Collin Walling
  0 siblings, 0 replies; 39+ messages in thread
From: Collin Walling @ 2026-03-17  7:02 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

On 3/5/26 17:41, Zhuoying Cai wrote:
> When secure boot is enabled (-secure-boot on) and certificate(s) are
> provided, the boot operates in True Secure IPL mode.
> 
> Any verification error during True Secure IPL mode will cause the
> entire boot process to terminate.
> 
> Secure IPL in audit mode requires at least one certificate provided in
> the key store along with necessary facilities. If secure boot is enabled
> but no certificate is provided, the boot process will also terminate, as
> this is not a valid secure boot configuration.
> 
> Note: True Secure IPL mode is implemented for the SCSI scheme of
> virtio-blk/virtio-scsi devices.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  docs/system/s390x/secure-ipl.rst | 13 +++++++++++++
>  pc-bios/s390-ccw/bootmap.c       |  8 ++++++++
>  pc-bios/s390-ccw/main.c          |  3 +++
>  pc-bios/s390-ccw/s390-ccw.h      |  2 ++
>  pc-bios/s390-ccw/secure-ipl.c    |  4 ++++
>  pc-bios/s390-ccw/secure-ipl.h    |  3 +++
>  6 files changed, 33 insertions(+)
> 
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> index 2465f8b26d..e0af086c38 100644
> --- a/docs/system/s390x/secure-ipl.rst
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -65,3 +65,16 @@ 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 ...
> +
> +Secure Mode
> +-----------
> +
> +When the ``secure-boot=on`` option is set and certificates are provided,
> +a secure boot is performed with error reporting enabled. The boot process aborts
> +if any error occurs.
> +
> +Configuration:
> +
> +.. code-block:: shell
> +
> +    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem ...
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index 43a661325f..9a61e989e0 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -738,6 +738,7 @@ static int zipl_run(ScsiBlockPtr *pte)
>      entry = (ComponentEntry *)(&header[1]);
>  
>      switch (boot_mode) {
> +    case ZIPL_BOOT_MODE_SECURE:
>      case ZIPL_BOOT_MODE_SECURE_AUDIT:
>          rc = zipl_run_secure(&entry, tmp_sec);
>          break;
> @@ -1120,9 +1121,16 @@ ZiplBootMode get_boot_mode(uint8_t hdr_flags)
>  {
>      bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
>      bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
> +    VCStorageSizeBlock *vcssb;
>  
>      if (!sipl_set && iplir_set) {
>          return ZIPL_BOOT_MODE_SECURE_AUDIT;
> +    } else if (sipl_set && iplir_set) {
> +        vcssb = zipl_secure_get_vcssb();
> +        if (vcssb == NULL || vcssb->length == VCSSB_NO_VC) {
> +            return ZIPL_BOOT_MODE_INVALID;

Is an INVALID mode necessary, especially if the error is going to be
reported immediately after when the function exits?  Might as well just
put the `panic()` here instead.

Otherwise, patch LGTM:

Reviewed-by: Collin Walling <walling@linux.ibm.com>

> +        }
> +        return ZIPL_BOOT_MODE_SECURE;
>      }
>  
>      return ZIPL_BOOT_MODE_NORMAL;
> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
> index 106cdf9dec..1678ede8fb 100644
> --- a/pc-bios/s390-ccw/main.c
> +++ b/pc-bios/s390-ccw/main.c
> @@ -329,6 +329,9 @@ void main(void)
>      }
>  
>      boot_mode = get_boot_mode(iplb->hdr_flags);
> +    if (boot_mode == ZIPL_BOOT_MODE_INVALID) {
> +        panic("Need at least one certificate for secure boot!");
> +    }
>  
>      while (have_iplb) {
>          boot_setup();
> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
> index 7d1a9d4acc..7092942280 100644
> --- a/pc-bios/s390-ccw/s390-ccw.h
> +++ b/pc-bios/s390-ccw/s390-ccw.h
> @@ -96,8 +96,10 @@ int virtio_read(unsigned long sector, void *load_addr);
>  void zipl_load(void);
>  
>  typedef enum ZiplBootMode {
> +    ZIPL_BOOT_MODE_INVALID = -1,
>      ZIPL_BOOT_MODE_NORMAL = 0,
>      ZIPL_BOOT_MODE_SECURE_AUDIT = 1,
> +    ZIPL_BOOT_MODE_SECURE = 2,
>  } ZiplBootMode;
>  
>  extern ZiplBootMode boot_mode;
> diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
> index 840b88a699..76b72fc8f4 100644
> --- a/pc-bios/s390-ccw/secure-ipl.c
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -288,6 +288,10 @@ static bool check_sclab_presence(uint8_t *sclab_magic,
>      }
>  
>      /* a missing SCLAB will not be reported in audit mode */
> +    if (boot_mode == ZIPL_BOOT_MODE_SECURE) {
> +        zipl_secure_handle("Magic does not match. SCLAB does not exist");
> +    }
> +
>      return false;
>  }
>  
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> index 4e9f4f08b9..1e736d53fe 100644
> --- a/pc-bios/s390-ccw/secure-ipl.h
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -60,6 +60,9 @@ static inline void zipl_secure_handle(const char *message)
>      case ZIPL_BOOT_MODE_SECURE_AUDIT:
>          IPL_check(false, message);
>          break;
> +    case ZIPL_BOOT_MODE_SECURE:
> +        panic(message);
> +        break;
>      default:
>          break;
>      }
-- 
Regards,
  Collin


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v9 19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2026-03-17  3:41   ` Collin Walling
@ 2026-03-25 19:11     ` Zhuoying Cai
  0 siblings, 0 replies; 39+ messages in thread
From: Zhuoying Cai @ 2026-03-25 19:11 UTC (permalink / raw)
  To: Collin Walling, thuth, berrange, jrossi, qemu-s390x, qemu-devel
  Cc: richard.henderson, pierrick.bouvier, david, jjherne, pasic,
	borntraeger, farman, mjrosato, iii, eblake, armbru, alifm,
	brueckner, jdaley

On 3/16/26 11:41 PM, Collin Walling wrote:
> On 3/5/26 17:41, 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 <zycai@linux.ibm.com>
>> ---
>>  docs/system/s390x/secure-ipl.rst |  35 +++
>>  pc-bios/s390-ccw/Makefile        |   3 +-
>>  pc-bios/s390-ccw/bootmap.c       |  36 +++-
>>  pc-bios/s390-ccw/bootmap.h       |  11 +
>>  pc-bios/s390-ccw/main.c          |   6 +
>>  pc-bios/s390-ccw/s390-ccw.h      |  27 +++
>>  pc-bios/s390-ccw/sclp.c          |  38 ++++
>>  pc-bios/s390-ccw/sclp.h          |   6 +
>>  pc-bios/s390-ccw/secure-ipl.c    | 357 +++++++++++++++++++++++++++++++
>>  pc-bios/s390-ccw/secure-ipl.h    | 102 +++++++++
>>  10 files changed, 618 insertions(+), 3 deletions(-)
>>  create mode 100644 pc-bios/s390-ccw/secure-ipl.c
>>  create mode 100644 pc-bios/s390-ccw/secure-ipl.h
>>

[...]

>> +
>> +int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>> +{
>> +    IplDeviceComponentList comp_list = { 0 };
>> +    IplSignatureCertificateList cert_list = { 0 };
>> +    ComponentEntry *entry = *entry_ptr;
>> +    uint8_t *cert_addr = 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;
>> +    int cert_index;
>> +    uint8_t flags;
>> +    bool verified;
>> +    /*
>> +     * Keep track of which certificate store indices correspond to the
>> +     * certificate data entries within the IplSignatureCertificateList to
>> +     * prevent allocating space for the same certificate multiple times.
>> +     *
>> +     * The array index corresponds to the certificate's cert-store index.
>> +     *
>> +     * The array value corresponds to the certificate's entry within the
>> +     * IplSignatureCertificateList (with a value of -1 denoting no entry
>> +     * exists for the certificate).
>> +     */
>> +    int cert_list_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1 };
>> +    int signed_count = 0;
>> +
>> +    if (!secure_ipl_supported()) {
>> +        panic("Unable to boot in secure/audit mode");
>> +    }
>> +
>> +    init_lists(&comp_list, &cert_list);
>> +    cert_addr = malloc(get_total_certs_length());
> 
> I'm concerned about the cert_addr being malloc'd here...
> 
>> +    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);
>> +
>> +            /* default cert index and flags for unverified component */
>> +            cert_index = -1;
>> +            flags = S390_IPL_DEV_COMP_FLAG_SC;
>> +
>> +            if (verified) {
>> +                if (cert_list_table[cert_table_idx] == -1) {
>> +                    if (!request_certificate(cert_addr, cert_table_idx)) {
>> +                        puts("Could not get certificate");
>> +                        goto out;
>> +                    }
>> +
>> +                    cert_list_table[cert_table_idx] = cert_entry_idx;
>> +                    cert_list_add(&cert_list, cert_entry_idx, cert_addr, cert_len);
> 
> ...then the malloc'd cert is added to the cert list (and this list later
> gets appended to the IIRB).
> 
> Once IPL starts, the data at this address will eventually be
> lost/overwritten. The certificate address needs to point to either:
>  - somewhere on the disk
>  - some dedicated memory space (likely designated by QEMU)
> 
> Though that begs the question: who/what makes use of the IIRB?  It seems
> this information gets lost?
> 

Thanks for raising the concern. I looked into this further.

The IIRB is actively consumed by the kernel, and its contents are
preserved until they are explicitly copied.

It is used for:
* Boot logging: log_component_list() reports what was loaded and verified.
* kexec operations: kexec_file_add_ipl_report() builds the next kernel’s
IPL report from the existing one.
* Keying: load_ipl_certs() installs IPL certificates into the platform
trusted keyring.

In addition, the IPL report block data is not lost during boot (see
arch/s390/boot/ipl_report.c).

Early during boot, read_ipl_report() reserves the entire IPL report
memory region, including the IPL parameter block and the IPL report
itself. Certificate entries reference the actual certificate data via
physical addresses, and all subsequent physical memory allocations are
checked for overlap using ipl_report_certs_intersects(). This ensures
that no allocation can overwrite certificate data before it is saved.

Later, save_ipl_cert_comp_list() allocates permanent storage and copies
the actual certificate data. As a result, the IIRB data is both used and
protected throughout early boot and is not lost.

I also verified, from both the BIOS and the guest kernel, that the
certificate addresses, lengths, and contents are correctly preserved.
For example:

BIOS debug log:
cert_entries = {{addr = 0x3fd6e98c, len = 0x3e7},
                {addr = 0x3fd6ed73, len = 0x3e7}, ...}

Guest kernel debug log:
[    0.060583] boot: copy_certificates_bootdata()
[    0.060584] boot: Certificate data at 0x3fd6e98c (len=0x3e7):
...
[    0.060588] boot: Certificate data at 0x3fd6ed73 (len=0x3e7):
...

>> +
>> +                    /* increment for the next certificate */
>> +                    cert_entry_idx++;
>> +                    cert_addr += cert_len;
>> +                }
>> +
>> +                puts("Verified component");
>> +                cert_index = cert_list_table[cert_table_idx];
>> +                flags |= S390_IPL_DEV_COMP_FLAG_CSV;
>> +            }
>> +
>> +            comp_list_add(&comp_list, comp_entry_idx, cert_index,
>> +                          comp_addr, comp_len, flags);
>> +
>> +            if (!verified) {
>> +                zipl_secure_handle("Could not verify component");
>> +            }
>> +
>> +            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");
>> +    }
>> +
>> +    update_iirb(&comp_list, &cert_list);
>> +
>> +    *entry_ptr = entry;
>> +    free(sig);
>> +
>> +    return 0;
>> +out:
>> +    free(cert_addr);
>> +    free(sig);
>> +
>> +    return -1;
>> +}

[...]



^ permalink raw reply	[flat|nested] 39+ messages in thread

end of thread, other threads:[~2026-03-25 19:12 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-05 22:41 [PATCH v9 00/30] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 01/30] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 02/30] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 03/30] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 04/30] hw/s390x/ipl: Create " Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 05/30] s390x/diag: Introduce DIAG 320 for Certificate Store Facility Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 06/30] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 07/30] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 08/30] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 09/30] s390x/diag: Implement " Zhuoying Cai
2026-03-13 19:58   ` Collin Walling
2026-03-16 18:04     ` Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 10/30] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 11/30] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 12/30] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 13/30] s390x/ipl: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
2026-03-13 20:00   ` Collin Walling
2026-03-05 22:41 ` [PATCH v9 14/30] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 15/30] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 16/30] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 17/30] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 18/30] pc-bios/s390-ccw: Rework zipl_load_segment function Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
2026-03-17  3:41   ` Collin Walling
2026-03-25 19:11     ` Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 20/30] pc-bios/s390-ccw: Add signed component address overlap checks Zhuoying Cai
2026-03-17  4:25   ` Collin Walling
2026-03-05 22:41 ` [PATCH v9 21/30] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 22/30] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
2026-03-17  6:54   ` Collin Walling
2026-03-05 22:41 ` [PATCH v9 23/30] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 24/30] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 25/30] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
2026-03-17  7:02   ` Collin Walling
2026-03-05 22:41 ` [PATCH v9 26/30] hw/s390x/ipl: Handle secure boot with multiple boot devices Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 27/30] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 28/30] tests/functional/s390x: Add secure IPL functional test Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 29/30] docs/specs: Add secure IPL documentation Zhuoying Cai
2026-03-05 22:41 ` [PATCH v9 30/30] docs/system/s390x: " Zhuoying Cai

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox