qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices
@ 2025-07-11 21:10 Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
                   ` (27 more replies)
  0 siblings, 28 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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.

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

# 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 KVM 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. Multiple paths can be separated by a colon (:).

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

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

Secure IPL command options overview:

If neither the -secure-boot nor the -boot-certificates 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-certificates 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-certificates
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-certificates option is not provided in true secure IPL
    mode, the boot process will fail for the corresponding device.

## Constraints

- z16 CPU model

- certificates must be in X.509 PEM format

- only sha256 encryption is supported

- 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 - KVM 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 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 (26):
  Add boot-certificates to s390-ccw-virtio machine type option
  crypto/x509-utils: Add helper functions for certificate store
  hw/s390x/ipl: Create certificate store
  s390x: Guest support for Certificate Store Facility (CS)
  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
  pc-bios/s390-ccw: 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
  hw/s390x/ipl: Set iplb->len to maximum length of IPL Parameter Block
  s390x: Guest support for Secure-IPL Facility
  pc-bios/s390-ccw: Refactor zipl_run()
  pc-bios/s390-ccw: Refactor zipl_load_segment function
  pc-bios/s390-ccw: Add signature verification for secure IPL in audit
    mode
  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
  pc-bios/s390-ccw: Handle secure boot with multiple boot devices
  hw/s390x/ipl: Handle secure boot without specifying a boot device
  docs: Add secure IPL documentation

 crypto/meson.build                  |   5 +-
 crypto/x509-utils.c                 | 464 ++++++++++++++++++++++
 docs/specs/s390x-secure-ipl.rst     | 159 ++++++++
 docs/system/s390x/secure-ipl.rst    | 156 ++++++++
 hw/s390x/cert-store.c               | 223 +++++++++++
 hw/s390x/cert-store.h               |  39 ++
 hw/s390x/ipl.c                      |  61 ++-
 hw/s390x/ipl.h                      |  27 +-
 hw/s390x/meson.build                |   1 +
 hw/s390x/s390-virtio-ccw.c          |  44 +++
 hw/s390x/sclp.c                     |   2 +
 include/crypto/x509-utils.h         | 180 +++++++++
 include/hw/s390x/ipl/diag308.h      |  34 ++
 include/hw/s390x/ipl/diag320.h      |  87 ++++
 include/hw/s390x/ipl/diag508.h      |  38 ++
 include/hw/s390x/ipl/qipl.h         |   7 +-
 include/hw/s390x/s390-virtio-ccw.h  |   2 +
 include/hw/s390x/sclp.h             |   4 +-
 pc-bios/s390-ccw/Makefile           |   3 +-
 pc-bios/s390-ccw/bootmap.c          | 284 +++++++++++--
 pc-bios/s390-ccw/bootmap.h          |   9 +
 pc-bios/s390-ccw/iplb.h             |  96 ++++-
 pc-bios/s390-ccw/jump2ipl.c         |   6 +-
 pc-bios/s390-ccw/main.c             | 111 +++++-
 pc-bios/s390-ccw/netmain.c          |   8 +-
 pc-bios/s390-ccw/s390-ccw.h         |  18 +
 pc-bios/s390-ccw/sclp.c             |  52 +++
 pc-bios/s390-ccw/sclp.h             |   7 +
 pc-bios/s390-ccw/secure-ipl.c       | 590 ++++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h       | 228 +++++++++++
 qemu-options.hx                     |  11 +-
 target/s390x/cpu_features.c         |   5 +
 target/s390x/cpu_features.h         |   1 +
 target/s390x/cpu_features_def.h.inc |   5 +
 target/s390x/cpu_models.c           |   7 +
 target/s390x/diag.c                 | 468 +++++++++++++++++++++-
 target/s390x/gen-features.c         |   7 +
 target/s390x/kvm/kvm.c              |  36 ++
 target/s390x/s390x-internal.h       |   4 +
 target/s390x/tcg/misc_helper.c      |  14 +
 40 files changed, 3424 insertions(+), 79 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

-- 
2.49.0



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

* [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio machine type option
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-22 15:56   ` Daniel P. Berrangé
  2025-07-11 21:10 ` [PATCH v4 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
                   ` (26 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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

The `boot-certificates=/path/dir:/path/file` parameter is implemented
to provide path to either a directory or a single certificate.

Multiple paths can be delineated using a colon.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 hw/s390x/s390-virtio-ccw.c         | 22 ++++++++++++++++++++++
 include/hw/s390x/s390-virtio-ccw.h |  1 +
 qemu-options.hx                    |  7 ++++++-
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index c294106a74..167876295e 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -798,6 +798,22 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
     g_free(val);
 }
 
+static inline char *machine_get_boot_certificates(Object *obj, Error **errp)
+{
+    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+    return g_strdup(ms->boot_certificates);
+}
+
+static void machine_set_boot_certificates(Object *obj, const char *str,
+                                          Error **errp)
+{
+    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+    g_free(ms->boot_certificates);
+    ms->boot_certificates = g_strdup(str);
+}
+
 static void ccw_machine_class_init(ObjectClass *oc, const void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
@@ -851,6 +867,12 @@ 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_str(oc, "boot-certificates",
+                                  machine_get_boot_certificates,
+                                  machine_set_boot_certificates);
+    object_class_property_set_description(oc, "boot-certificates",
+            "provide path to a directory or a single certificate 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 526078a4e2..45adc8bce6 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -31,6 +31,7 @@ struct S390CcwMachineState {
     uint8_t loadparm[8];
     uint64_t memory_limit;
     uint64_t max_pagesize;
+    char *boot_certificates;
 
     SCLPDevice *sclp;
 };
diff --git a/qemu-options.hx b/qemu-options.hx
index 1f862b19a6..d42e6f502c 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -43,7 +43,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
 #endif
     "                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"
-    "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n",
+    "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
+    "                boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n",
     QEMU_ARCH_ALL)
 SRST
 ``-machine [type=]name[,prop=value[,...]]``
@@ -200,6 +201,10 @@ SRST
         ::
 
             -machine smp-cache.0.cache=l1d,smp-cache.0.topology=core,smp-cache.1.cache=l1i,smp-cache.1.topology=core
+
+    ``boot-certificates='/path/directory:/path/file'``
+        Provide a path to a directory or a boot certificate on the host [s390x only].
+        A colon may be used to delineate multiple paths.
 ERST
 
 DEF("M", HAS_ARG, QEMU_OPTION_M,
-- 
2.49.0



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

* [PATCH v4 02/28] crypto/x509-utils: Add helper functions for certificate store
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-22 16:06   ` Daniel P. Berrangé
  2025-07-11 21:10 ` [PATCH v4 03/28] hw/s390x/ipl: Create " Zhuoying Cai
                   ` (25 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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
qcrypto_x509_get_keyid_len() - returns the length of the key ID
qcrypto_x509_get_signature_algorithm() - returns signature algorithm of the certificate

These functions provide support for certificate format conversion and
metadata extraction.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 crypto/meson.build          |   5 +-
 crypto/x509-utils.c         | 155 ++++++++++++++++++++++++++++++++++++
 include/crypto/x509-utils.h |  71 +++++++++++++++++
 3 files changed, 227 insertions(+), 4 deletions(-)

diff --git a/crypto/meson.build b/crypto/meson.build
index 735635de1f..0614bfa914 100644
--- a/crypto/meson.build
+++ b/crypto/meson.build
@@ -22,12 +22,9 @@ crypto_ss.add(files(
   'tlscredsx509.c',
   'tlssession.c',
   'rsakey.c',
+  'x509-utils.c',
 ))
 
-if gnutls.found()
-  crypto_ss.add(files('x509-utils.c'))
-endif
-
 if nettle.found()
   crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c'))
   if hogweed.found()
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 8bad00a51b..d2cf790d5b 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>
@@ -25,6 +27,87 @@ 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_KEYID_FLAGS_SHA1] = GNUTLS_KEYID_USE_SHA1,
+    [QCRYPTO_KEYID_FLAGS_SHA256] = GNUTLS_KEYID_USE_SHA256,
+    [QCRYPTO_KEYID_FLAGS_SHA512] = GNUTLS_KEYID_USE_SHA512,
+    [QCRYPTO_KEYID_FLAGS_BEST_KNOWN] = GNUTLS_KEYID_USE_BEST_KNOWN,
+};
+
+static const int gnutls_to_qcrypto_sig_alg_map[] = {
+    [GNUTLS_SIGN_UNKNOWN] = QCRYPTO_SIG_ALGO_UNKNOWN,
+    [GNUTLS_SIGN_RSA_SHA1] = QCRYPTO_SIG_ALGO_RSA_SHA1,
+    [GNUTLS_SIGN_RSA_SHA] = QCRYPTO_SIG_ALGO_RSA_SHA1,
+    [GNUTLS_SIGN_DSA_SHA1] = QCRYPTO_SIG_ALGO_DSA_SHA1,
+    [GNUTLS_SIGN_RSA_MD5] = QCRYPTO_SIG_ALGO_RSA_MD5,
+    [GNUTLS_SIGN_RSA_MD2] = QCRYPTO_SIG_ALGO_RSA_MD2,
+    [GNUTLS_SIGN_RSA_RMD160] = QCRYPTO_SIG_ALGO_RSA_RMD160,
+    [GNUTLS_SIGN_RSA_SHA256] = QCRYPTO_SIG_ALGO_RSA_SHA256,
+    [GNUTLS_SIGN_RSA_SHA384] = QCRYPTO_SIG_ALGO_RSA_SHA384,
+    [GNUTLS_SIGN_RSA_SHA512] = QCRYPTO_SIG_ALGO_RSA_SHA512,
+    [GNUTLS_SIGN_RSA_SHA224] = QCRYPTO_SIG_ALGO_RSA_SHA224,
+    [GNUTLS_SIGN_DSA_SHA224] = QCRYPTO_SIG_ALGO_DSA_SHA224,
+    [GNUTLS_SIGN_DSA_SHA256] = QCRYPTO_SIG_ALGO_DSA_SHA256,
+    [GNUTLS_SIGN_ECDSA_SHA1] = QCRYPTO_SIG_ALGO_ECDSA_SHA1,
+    [GNUTLS_SIGN_ECDSA_SHA224] = QCRYPTO_SIG_ALGO_ECDSA_SHA224,
+    [GNUTLS_SIGN_ECDSA_SHA256] = QCRYPTO_SIG_ALGO_ECDSA_SHA256,
+    [GNUTLS_SIGN_ECDSA_SHA384] = QCRYPTO_SIG_ALGO_ECDSA_SHA384,
+    [GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
+};
+
+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};
+
+    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;
+    }
+
+    *result = g_malloc0(*resultlen);
+    rc = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_DER, *result, resultlen);
+    if (rc != 0) {
+        error_setg(errp, "Failed to convert certificate to DER format: %s",
+                   gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
+int qcrypto_x509_get_keyid_len(QCryptoKeyidFlags flag, Error **errp)
+{
+    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
+        error_setg(errp, "Unknow key ID flag %d", flag);
+        return -1;
+    }
+
+    if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
+        (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
+        return QCRYPTO_HASH_DIGEST_LEN_SHA512;
+    } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
+        return QCRYPTO_HASH_DIGEST_LEN_SHA256;
+    } else {
+        return QCRYPTO_HASH_DIGEST_LEN_SHA1;
+    }
+}
+
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
                                       QCryptoHashAlgo alg,
                                       uint8_t *result,
@@ -74,3 +157,75 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
     gnutls_x509_crt_deinit(crt);
     return ret;
 }
+
+int qcrypto_x509_get_signature_algorithm(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};
+
+    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;
+    }
+
+    /*
+     * This function never returns a negative error code.
+     * Error cases and unknown/unsupported signature algorithms
+     * are mapped to GNUTLS_SIGN_UNKNOWN.
+     */
+    rc = gnutls_x509_crt_get_signature_algorithm(crt);
+    if (rc >= G_N_ELEMENTS(gnutls_to_qcrypto_sig_alg_map)) {
+        error_setg(errp, "Unknown signature algorithm %d", rc);
+        goto cleanup;
+    }
+
+    ret = gnutls_to_qcrypto_sig_alg_map[rc];
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
+#else /* ! CONFIG_GNUTLS */
+
+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;
+}
+
+int qcrypto_x509_get_keyid_len(QCryptoKeyidFlags flag, Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get key ID length");
+    return -1;
+}
+
+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;
+}
+
+int qcrypto_x509_get_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get signature algorithm");
+    return -1;
+}
+
+#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 1e99661a71..d916d248bb 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -13,10 +13,81 @@
 
 #include "crypto/hash.h"
 
+typedef enum {
+    QCRYPTO_KEYID_FLAGS_SHA1,
+    QCRYPTO_KEYID_FLAGS_SHA256,
+    QCRYPTO_KEYID_FLAGS_SHA512,
+    QCRYPTO_KEYID_FLAGS_BEST_KNOWN,
+} QCryptoKeyidFlags;
+
+typedef enum {
+    QCRYPTO_SIG_ALGO_UNKNOWN,
+    QCRYPTO_SIG_ALGO_RSA_SHA1,
+    QCRYPTO_SIG_ALGO_DSA_SHA1,
+    QCRYPTO_SIG_ALGO_RSA_MD5,
+    QCRYPTO_SIG_ALGO_RSA_MD2,
+    QCRYPTO_SIG_ALGO_RSA_RMD160,
+    QCRYPTO_SIG_ALGO_RSA_SHA256,
+    QCRYPTO_SIG_ALGO_RSA_SHA384,
+    QCRYPTO_SIG_ALGO_RSA_SHA512,
+    QCRYPTO_SIG_ALGO_RSA_SHA224,
+    QCRYPTO_SIG_ALGO_DSA_SHA224,
+    QCRYPTO_SIG_ALGO_DSA_SHA256,
+    QCRYPTO_SIG_ALGO_ECDSA_SHA1,
+    QCRYPTO_SIG_ALGO_ECDSA_SHA224,
+    QCRYPTO_SIG_ALGO_ECDSA_SHA256,
+    QCRYPTO_SIG_ALGO_ECDSA_SHA384,
+    QCRYPTO_SIG_ALGO_ECDSA_SHA512,
+} QCryptoSigAlgo;
+
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
                                       QCryptoHashAlgo hash,
                                       uint8_t *result,
                                       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 replaced by the actual size of the DER-encoded certificate)
+ * @errp: error pointer
+ *
+ * Convert 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);
+
+/**
+ * qcrypto_x509_get_keyid_len
+ * @flag: the key ID flag
+ *
+ * Determine the length of the key ID of the given @flag.
+ *
+ * Returns: the length on success,
+ *          -1 on error.
+ */
+int qcrypto_x509_get_keyid_len(QCryptoKeyidFlags flag, Error **errp);
+
+/**
+ * qcrypto_x509_get_signature_algorithm
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine the signature algorithm used to sign the @cert.
+ *
+ * Returns: a value from the QCryptoSigAlgo enum on success,
+ *          -1 on error.
+ */
+int qcrypto_x509_get_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
+
 #endif
-- 
2.49.0



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

* [PATCH v4 03/28] hw/s390x/ipl: Create certificate store
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-22 16:14   ` Daniel P. Berrangé
  2025-07-11 21:10 ` [PATCH v4 04/28] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
                   ` (24 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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

Load certificates from the boot-certificate 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. Additionally,
only the SHA-256 hashing algorithm is supported, as it is required for
secure boot on s390.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 hw/s390x/cert-store.c       | 223 ++++++++++++++++++++++++++++++++++++
 hw/s390x/cert-store.h       |  39 +++++++
 hw/s390x/ipl.c              |   9 ++
 hw/s390x/ipl.h              |   3 +
 hw/s390x/meson.build        |   1 +
 include/hw/s390x/ipl/qipl.h |   2 +
 6 files changed, 277 insertions(+)
 create mode 100644 hw/s390x/cert-store.c
 create mode 100644 hw/s390x/cert-store.h

diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
new file mode 100644
index 0000000000..3b4faa3738
--- /dev/null
+++ b/hw/s390x/cert-store.c
@@ -0,0 +1,223 @@
+/*
+ * 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"
+
+static const char *s390_get_boot_certificates(void)
+{
+    return S390_CCW_MACHINE(qdev_get_machine())->boot_certificates;
+}
+
+static size_t cert2buf(char *path, char **cert_buf)
+{
+    size_t size;
+
+    if (!g_file_get_contents(path, cert_buf, &size, NULL) || size == 0) {
+        return 0;
+    }
+
+    return size;
+}
+
+static S390IPLCertificate *init_cert_x509(size_t size, uint8_t *raw)
+{
+    S390IPLCertificate *q_cert = NULL;
+    int key_id_size;
+    int hash_size;
+    int hash_type;
+    Error *err = NULL;
+
+    g_autofree uint8_t *cert_der = NULL;
+    size_t der_len = size;
+    int rc;
+
+    hash_type = qcrypto_x509_get_signature_algorithm(raw, size, &err);
+    if (hash_type == -1) {
+        error_report_err(err);
+        return NULL;
+    }
+    if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
+        error_report_err(err);
+        return NULL;
+    }
+
+    key_id_size = qcrypto_x509_get_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256, &err);
+    if (key_id_size == -1) {
+        error_report_err(err);
+        return NULL;
+    }
+
+    hash_size = QCRYPTO_HASH_DIGEST_LEN_SHA256;
+
+    rc = qcrypto_x509_convert_cert_der(raw, size, &cert_der, &der_len, &err);
+    if (rc != 0) {
+        error_report_err(err);
+        return NULL;
+    }
+
+    q_cert = g_new0(S390IPLCertificate, 1);
+    q_cert->size = size;
+    q_cert->der_size = der_len;
+    q_cert->key_id_size = key_id_size;
+    q_cert->hash_size = hash_size;
+    q_cert->raw = raw;
+    q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
+
+    return q_cert;
+}
+
+static S390IPLCertificate *init_cert(char *path)
+{
+    char *buf;
+    size_t size;
+    char vc_name[VC_NAME_LEN_BYTES];
+    g_autofree gchar *filename = NULL;
+    S390IPLCertificate *qcert = NULL;
+
+    filename = g_path_get_basename(path);
+
+    size = cert2buf(path, &buf);
+    if (size == 0) {
+        error_report("Failed to load certificate: %s", path);
+        g_free(buf);
+        return NULL;
+    }
+
+    qcert = init_cert_x509(size, (uint8_t *)buf);
+    if (qcert == NULL) {
+        error_report("Failed to initialize certificate: %s", path);
+        g_free(buf);
+        return NULL;
+    }
+
+    /*
+     * Left justified certificate name with padding on the right with blanks.
+     * Convert certificate name to EBCDIC.
+     */
+    strpadcpy(vc_name, VC_NAME_LEN_BYTES, filename, ' ');
+    ebcdic_put(qcert->vc_name, vc_name, VC_NAME_LEN_BYTES);
+
+    return qcert;
+}
+
+static void update_cert_store(S390IPLCertificateStore *cert_store,
+                              S390IPLCertificate *qcert)
+{
+    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(qcert->key_id_size, 4);
+    hash_buf_size = ROUND_UP(qcert->hash_size, 4);
+    cert_buf_size = ROUND_UP(qcert->der_size, 4);
+    data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
+
+    if (cert_store->max_cert_size < data_buf_size) {
+        cert_store->max_cert_size = data_buf_size;
+    }
+
+    cert_store->certs[cert_store->count] = *qcert;
+    cert_store->total_bytes += data_buf_size;
+    cert_store->count++;
+}
+
+static GPtrArray *get_cert_paths(void)
+{
+    const char *paths_list;
+    g_auto(GStrv) paths = NULL;
+    gchar **paths_copy;
+    GDir *dir = NULL;
+    const gchar *filename;
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_full(0, g_free);
+
+    paths_list = s390_get_boot_certificates();
+    if (paths_list == NULL) {
+        return g_steal_pointer(&cert_path_builder);
+    }
+
+    paths = g_strsplit(paths_list, ":", -1);
+
+    paths_copy = paths;
+    while (*paths_copy) {
+        if (!strcmp(*paths_copy, "")) {
+            error_report("Empty path in certificate path list is not allowed");
+            exit(1);
+        }
+
+        struct stat st;
+        if (stat(*paths_copy, &st) != 0) {
+            error_report("Failed to stat path '%s': %s", *paths_copy, g_strerror(errno));
+            exit(1);
+        }
+
+        if (S_ISREG(st.st_mode)) {
+            g_ptr_array_add(cert_path_builder, g_strdup(*paths_copy));
+        } else if (S_ISDIR(st.st_mode)) {
+            dir = g_dir_open(*paths_copy, 0, &err);
+            if (dir == NULL) {
+                error_report("Failed to open directory '%s': %s",
+                             *paths_copy, err->message);
+                exit(1);
+            }
+
+            while ((filename = g_dir_read_name(dir))) {
+                g_ptr_array_add(cert_path_builder,
+                                g_build_filename(*paths_copy, filename, NULL));
+            }
+
+            g_dir_close(dir);
+        } else {
+            error_report("Path '%s' is neither a file nor a directory", *paths_copy);
+        }
+
+        paths_copy += 1;
+    }
+
+    return g_steal_pointer(&cert_path_builder);
+}
+
+void s390_ipl_create_cert_store(S390IPLCertificateStore *cert_store)
+{
+    GPtrArray *cert_path_builder;
+
+    cert_path_builder = get_cert_paths();
+    if (cert_path_builder->len == 0) {
+        g_ptr_array_free(cert_path_builder, TRUE);
+        return;
+    }
+
+    cert_store->max_cert_size = 0;
+    cert_store->total_bytes = 0;
+
+    for (int i = 0; i < cert_path_builder->len; i++) {
+        if (i > MAX_CERTIFICATES - 1) {
+            error_report("Maximum %d certificates are allowed", MAX_CERTIFICATES);
+            exit(1);
+        }
+
+        S390IPLCertificate *qcert = init_cert((char *) cert_path_builder->pdata[i]);
+        if (qcert) {
+            update_cert_store(cert_store, qcert);
+        }
+    }
+
+    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..00a107815b
--- /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 VC_NAME_LEN_BYTES  64
+
+struct S390IPLCertificate {
+    uint8_t vc_name[VC_NAME_LEN_BYTES];
+    size_t  size;
+    size_t  der_size;
+    size_t  key_id_size;
+    size_t  hash_size;
+    uint8_t *raw;
+    QCryptoSigAlgo hash_type;
+};
+typedef struct S390IPLCertificate S390IPLCertificate;
+
+struct S390IPLCertificateStore {
+    uint16_t count;
+    size_t   max_cert_size;
+    size_t   total_bytes;
+    S390IPLCertificate certs[MAX_CERTIFICATES];
+} QEMU_PACKED;
+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 2f082396c7..186be923d7 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -35,6 +35,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
@@ -422,6 +423,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;
@@ -717,6 +725,7 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
 
     if (!ipl->kernel || ipl->iplb_valid) {
         cpu->env.psw.addr = ipl->bios_start_addr;
+        s390_ipl_create_cert_store(&ipl->cert_store);
         if (!ipl->iplb_valid) {
             ipl->iplb_valid = s390_init_all_iplbs(ipl);
         } else {
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 8f83c7da29..bee72dfbb3 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 */
@@ -64,6 +66,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 8866012ddc..80d3d4a74d 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.49.0



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

* [PATCH v4 04/28] s390x: Guest support for Certificate Store Facility (CS)
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (2 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 03/28] hw/s390x/ipl: Create " Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-21 21:30   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
                   ` (23 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

DIAG 320 is supported when the certificate-store (CS) facility
is installed.

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.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 target/s390x/cpu_features.c         | 1 +
 target/s390x/cpu_features_def.h.inc | 1 +
 target/s390x/cpu_models.c           | 2 ++
 target/s390x/gen-features.c         | 3 +++
 target/s390x/kvm/kvm.c              | 2 ++
 5 files changed, 9 insertions(+)

diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 4b5be6798e..99089ab3f5 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_DIAG_320)->bit, data);
         break;
     default:
         return;
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index e23e603a79..7b13a95d98 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(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide 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..33ef5c190c 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_DIAG_320:
         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_DIAG_320, 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 8218e6470e..86486def23 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_DIAG_320,
 };
 
 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_DIAG_320,
 };
 
 /****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 2e02d2c4de..8f655a4b7f 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2490,6 +2490,8 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
         set_bit(S390_FEAT_DIAG_318, model->features);
     }
 
+    set_bit(S390_FEAT_DIAG_320, model->features);
+
     /* Test for Ultravisor features that influence secure guest behavior */
     query_uv_feat_guest(model->features);
 
-- 
2.49.0



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

* [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (3 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 04/28] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-21 21:26   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
                   ` (22 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

DIAGNOSE 320 is introduced to support certificate store 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 a bitmap of which subcodes are supported.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 include/hw/s390x/ipl/diag320.h | 17 ++++++++++++++
 target/s390x/diag.c            | 41 ++++++++++++++++++++++++++++++++++
 target/s390x/kvm/kvm.c         | 14 ++++++++++++
 target/s390x/s390x-internal.h  |  2 ++
 target/s390x/tcg/misc_helper.c |  7 ++++++
 5 files changed, 81 insertions(+)
 create mode 100644 include/hw/s390x/ipl/diag320.h

diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
new file mode 100644
index 0000000000..713570545d
--- /dev/null
+++ b/include/hw/s390x/ipl/diag320.h
@@ -0,0 +1,17 @@
+/*
+ * 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
+
+#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index cff9fbc4b0..d33c5daf38 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"
@@ -191,3 +192,43 @@ 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];
+    int rc;
+
+    if (env->psw.mask & PSW_MASK_PSTATE) {
+        s390_program_interrupt(env, PGM_PRIVILEGED, ra);
+        return;
+    }
+
+    if (!s390_has_feat(S390_FEAT_DIAG_320)) {
+        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+        return;
+    }
+
+    if (r1 & 1) {
+        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+        return;
+    }
+
+    switch (subcode) {
+    case DIAG_320_SUBC_QUERY_ISM:
+        uint64_t ism =  0;
+
+        if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
+            s390_cpu_virt_mem_handle_exc(cpu, ra);
+            return;
+        }
+
+        rc = DIAG_320_RC_OK;
+        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 8f655a4b7f..d5b3694600 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);
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index a4ba6227ab..86a652f833 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -400,6 +400,8 @@ int mmu_translate_real(CPUS390XState *env, target_ulong 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 f7101be574..412c34ed93 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.49.0



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

* [PATCH v4 06/28] s390x/diag: Refactor address validation check from diag308_parm_check
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (4 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
                   ` (21 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 hw/s390x/ipl.h      | 6 ++++++
 target/s390x/diag.c | 4 +---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index bee72dfbb3..e26fc1cd6a 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -118,6 +118,12 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
 #define S390_IPLB_MIN_FCP_LEN 384
 #define S390_IPLB_MIN_QEMU_SCSI_LEN 200
 
+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);
+}
+
 static inline bool iplb_valid_len(IplParameterBlock *iplb)
 {
     return be32_to_cpu(iplb->len) <= sizeof(IplParameterBlock);
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index d33c5daf38..7b9b47a171 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -65,9 +65,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.49.0



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

* [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (5 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-23 20:17   ` Eric Farman
  2025-07-23 22:15   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
                   ` (20 subsequent siblings)
  27 siblings, 2 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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

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.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 include/hw/s390x/ipl/diag320.h | 23 ++++++++++++++++++++++
 target/s390x/diag.c            | 36 +++++++++++++++++++++++++++++++++-
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index 713570545d..3916a2915e 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -11,7 +11,30 @@
 #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 VCSSB_MAX_LEN   128
+#define VCE_HEADER_LEN  128
+#define VCB_HEADER_LEN  64
+
+#define DIAG_320_ISM_QUERY_VCSI     0x4000000000000000
+
+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[7];
+    uint32_t max_vce_len;
+    uint32_t reserved4[3];
+    uint32_t max_single_vcb_len;
+    uint32_t total_vcb_len;
+    uint32_t reserved5[10];
+};
+typedef struct VCStorageSizeBlock VCStorageSizeBlock;
+
 #endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 7b9b47a171..1f7d0cb2f6 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -191,9 +191,13 @@ out:
     }
 }
 
+QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != 128,
+                   "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 *qcs = s390_ipl_get_certificate_store();
     uint64_t subcode = env->regs[r3];
     uint64_t addr = env->regs[r1];
     int rc;
@@ -215,13 +219,43 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 
     switch (subcode) {
     case DIAG_320_SUBC_QUERY_ISM:
-        uint64_t ism =  0;
+        uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI);
 
         if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
             s390_cpu_virt_mem_handle_exc(cpu, ra);
             return;
         }
 
+        rc = DIAG_320_RC_OK;
+        break;
+    case DIAG_320_SUBC_QUERY_VCSI:
+        VCStorageSizeBlock vcssb;
+
+        if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock),
+                                  true)) {
+            s390_program_interrupt(env, PGM_ADDRESSING, ra);
+            return;
+        }
+
+        if (!qcs || !qcs->count) {
+            vcssb.length = cpu_to_be32(4);
+        } else {
+            vcssb.length = cpu_to_be32(VCSSB_MAX_LEN);
+            vcssb.version = 0;
+            vcssb.total_vc_ct = cpu_to_be16(qcs->count);
+            vcssb.max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
+            vcssb.max_vce_len = cpu_to_be32(VCE_HEADER_LEN + qcs->max_cert_size);
+            vcssb.max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
+                                                   qcs->max_cert_size);
+            vcssb.total_vcb_len = cpu_to_be32(VCB_HEADER_LEN +
+                                              qcs->count * VCE_HEADER_LEN +
+                                              qcs->total_bytes);
+        }
+
+        if (s390_cpu_virt_mem_write(cpu, addr, r1, &vcssb, sizeof(VCStorageSizeBlock))) {
+            s390_cpu_virt_mem_handle_exc(cpu, ra);
+            return;
+        }
         rc = DIAG_320_RC_OK;
         break;
     default:
-- 
2.49.0



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

* [PATCH v4 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (6 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-22 16:20   ` Daniel P. Berrangé
  2025-07-11 21:10 ` [PATCH v4 09/28] s390x/diag: Implement " Zhuoying Cai
                   ` (19 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

Introduce new helper functions to extract certificate metadata needed for
DIAG 320 subcode 2:

qcrypto_x509_get_cert_version() - retrieves version of a certificate
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

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

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 crypto/x509-utils.c         | 199 ++++++++++++++++++++++++++++++++++++
 include/crypto/x509-utils.h |  76 +++++++++++++-
 2 files changed, 272 insertions(+), 3 deletions(-)

diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index d2cf790d5b..135f83f55e 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -55,6 +55,14 @@ static const int gnutls_to_qcrypto_sig_alg_map[] = {
     [GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
 };
 
+static const int gnutls_to_qcrypto_pk_alg_map[] = {
+    [GNUTLS_PK_UNKNOWN] = QCRYPTO_PK_ALGO_UNKNOWN,
+    [GNUTLS_PK_RSA] = QCRYPTO_PK_ALGO_RSA,
+    [GNUTLS_PK_DSA] = QCRYPTO_PK_ALGO_DSA,
+    [GNUTLS_PK_DH] = QCRYPTO_PK_ALGO_DH,
+    [GNUTLS_PK_ECDSA] = QCRYPTO_PK_ALGO_ECDSA,
+};
+
 int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
                                   uint8_t **result, size_t *resultlen,
                                   Error **errp)
@@ -195,6 +203,169 @@ cleanup:
     return ret;
 }
 
+int qcrypto_x509_get_cert_version(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};
+
+    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_version(crt);
+    if (rc < 0) {
+        error_setg(errp, "Failed to get certificate version: %s", gnutls_strerror(rc));
+        goto cleanup;
+    }
+
+    ret = rc;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    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(0);
+    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;
+}
+
+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 >= G_N_ELEMENTS(gnutls_to_qcrypto_pk_alg_map)) {
+        error_setg(errp, "Unknown public key algorithm %d", rc);
+        goto cleanup;
+    }
+
+    ret = gnutls_to_qcrypto_pk_alg_map[rc];
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoKeyidFlags flag,
+                                 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};
+
+    *resultlen = qcrypto_x509_get_keyid_len(qcrypto_to_gnutls_keyid_flags_map[flag],
+                                            errp);
+    if (*resultlen == -1) {
+        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;
+    }
+
+    *result = g_malloc0(*resultlen);
+    if (gnutls_x509_crt_get_key_id(crt,
+                                   qcrypto_to_gnutls_keyid_flags_map[flag],
+                                   *result, resultlen) != 0) {
+        error_setg(errp, "Failed to get key ID from certificate");
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
 #else /* ! CONFIG_GNUTLS */
 
 int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
@@ -228,4 +399,32 @@ int qcrypto_x509_get_signature_algorithm(uint8_t *cert, size_t size, Error **err
     return -1;
 }
 
+int qcrypto_x509_get_cert_version(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get certificate version");
+    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_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get public key algorithm");
+    return -1;
+}
+
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoKeyidFlags flag,
+                                 uint8_t **result,
+                                 size_t *resultlen,
+                                 Error **errp)
+{
+    error_setg(errp, "GNUTLS is required to get key ID");
+    return -1;
+}
+
 #endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index d916d248bb..c122df0a98 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -40,6 +40,14 @@ typedef enum {
     QCRYPTO_SIG_ALGO_ECDSA_SHA512,
 } QCryptoSigAlgo;
 
+typedef enum {
+    QCRYPTO_PK_ALGO_UNKNOWN,
+    QCRYPTO_PK_ALGO_RSA,
+    QCRYPTO_PK_ALGO_DSA,
+    QCRYPTO_PK_ALGO_DH,
+    QCRYPTO_PK_ALGO_ECDSA,
+} QCryptoPkAlgo;
+
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
                                       QCryptoHashAlgo hash,
                                       uint8_t *result,
@@ -53,10 +61,10 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
  * @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 replaced by the actual size of the DER-encoded certificate)
+               (will be updated with the actual size of the DER-encoded certificate)
  * @errp: error pointer
  *
- * Convert given @cert from PEM to DER format.
+ * Convert the given @cert from PEM to DER format.
  *
  * Returns: 0 on success,
  *         -1 on error.
@@ -70,7 +78,7 @@ int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
  * qcrypto_x509_get_keyid_len
  * @flag: the key ID flag
  *
- * Determine the length of the key ID of the given @flag.
+ * Determine the length of the key ID corresponding to the given @flag.
  *
  * Returns: the length on success,
  *          -1 on error.
@@ -90,4 +98,66 @@ int qcrypto_x509_get_keyid_len(QCryptoKeyidFlags flag, Error **errp);
  */
 int qcrypto_x509_get_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
 
+/**
+ * qcrypto_x509_get_cert_version
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine the version of the @cert.
+ *
+ * Returns: the version on success,
+ *          -1 on error.
+ */
+int qcrypto_x509_get_cert_version(uint8_t *cert, size_t size, 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_pk_algorithm
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine the public key algorithm of the @cert.
+ *
+ * Returns: a value from the QCryptoPkAlgo enum on success,
+ *          -1 on error.
+ */
+int qcrypto_x509_get_pk_algorithm(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
+ * @flag: the key ID 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,
+                                 QCryptoKeyidFlags flag,
+                                 uint8_t **result,
+                                 size_t *resultlen,
+                                 Error **errp);
+
 #endif
-- 
2.49.0



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

* [PATCH v4 09/28] s390x/diag: Implement DIAG 320 subcode 2
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (7 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-22 16:23   ` Daniel P. Berrangé
  2025-07-28 20:59   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
                   ` (18 subsequent siblings)
  27 siblings, 2 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 include/hw/s390x/ipl/diag320.h |  47 ++++++
 target/s390x/diag.c            | 254 ++++++++++++++++++++++++++++++++-
 2 files changed, 300 insertions(+), 1 deletion(-)

diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index 3916a2915e..a926cf7d25 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -12,14 +12,23 @@
 
 #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_INVAL_VCB_LEN   0x0204
+#define DIAG_320_RC_BAD_RANGE       0x0302
 
 #define VCSSB_MAX_LEN   128
 #define VCE_HEADER_LEN  128
 #define VCB_HEADER_LEN  64
 
 #define DIAG_320_ISM_QUERY_VCSI     0x4000000000000000
+#define DIAG_320_ISM_STORE_VC       0x2000000000000000
+
+#define DIAG_320_VCE_FLAGS_VALID                0x80
+#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING    0
+#define DIAG_320_VCE_FORMAT_X509_DER            1
+#define DIAG_320_VCE_HASHTYPE_SHA2_256          1
 
 struct VCStorageSizeBlock {
     uint32_t length;
@@ -37,4 +46,42 @@ 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[3];
+    uint8_t version;
+    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;
+    uint32_t name[16];
+    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 1f7d0cb2f6..4641f88278 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -17,6 +17,7 @@
 #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"
@@ -24,6 +25,7 @@
 #include "kvm/kvm_s390x.h"
 #include "target/s390x/kvm/pv.h"
 #include "qemu/error-report.h"
+#include "crypto/x509-utils.h"
 
 
 int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
@@ -191,8 +193,252 @@ out:
     }
 }
 
+static int diag_320_is_cert_valid(S390IPLCertificate qcert)
+{
+    int version;
+    int rc;
+    Error *err = NULL;
+
+    version = qcrypto_x509_get_cert_version(qcert.raw, qcert.size, &err);
+    if (version < 0) {
+        error_report_err(err);
+        return -1;
+    }
+
+    rc = qcrypto_x509_check_cert_times(qcert.raw, qcert.size, &err);
+    if (rc != 0) {
+        error_report_err(err);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int diag_320_get_cert_info(VCEntry *vce, S390IPLCertificate qcert,
+                                  uint8_t **cert_der, unsigned char **key_id_data,
+                                  void **hash_data)
+{
+    int algo;
+    int rc;
+    Error *err = NULL;
+
+    /* key-type */
+    algo = qcrypto_x509_get_pk_algorithm(qcert.raw, qcert.size, &err);
+    if (algo < 0) {
+        error_report_err(err);
+        return -1;
+    }
+    if (algo == QCRYPTO_PK_ALGO_RSA) {
+        vce->key_type = DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
+    }
+
+    /* certificate in DER format */
+    rc = qcrypto_x509_convert_cert_der(qcert.raw, qcert.size,
+                                       cert_der, &qcert.der_size, &err);
+    if (rc < 0) {
+        error_report_err(err);
+        goto out;
+    }
+
+    /* VC format */
+    vce->format = DIAG_320_VCE_FORMAT_X509_DER;
+
+    /* key id and key id len */
+    rc = qcrypto_x509_get_cert_key_id(qcert.raw, qcert.size,
+                                      QCRYPTO_KEYID_FLAGS_SHA256,
+                                      key_id_data, &qcert.key_id_size, &err);
+    if (rc < 0) {
+        error_report_err(err);
+        goto out;
+    }
+    vce->keyid_len = cpu_to_be16(qcert.key_id_size);
+
+    /* hash type */
+    if (qcert.hash_type == QCRYPTO_SIG_ALGO_RSA_SHA256) {
+        vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
+    }
+
+    /* hash and hash len */
+    *hash_data = g_malloc0(qcert.hash_size);
+    rc = qcrypto_get_x509_cert_fingerprint(qcert.raw, qcert.size,
+                                           QCRYPTO_HASH_ALGO_SHA256,
+                                           *hash_data, &qcert.hash_size, &err);
+    if (rc < 0) {
+        error_report_err(err);
+        goto out;
+    }
+    vce->hash_len = cpu_to_be16(qcert.hash_size);
+
+    return 0;
+
+out:
+    g_clear_pointer(cert_der, g_free);
+    g_clear_pointer(key_id_data, g_free);
+    g_clear_pointer(hash_data, g_free);
+
+    return -1;
+}
+
+static VCEntry *build_vce(S390IPLCertificate qcert, uint32_t vce_len, int idx,
+                          size_t keyid_buf_size, size_t hash_buf_size)
+{
+    g_autofree VCEntry *vce = NULL;
+    g_autofree uint8_t *cert_der = NULL;
+    g_autofree unsigned char *key_id_data = NULL;
+    g_autofree void *hash_data = NULL;
+    int is_valid = -1;
+    int rc;
+
+    /*
+     * Construct VCE
+     * Unused area following the VCE field contains zeros.
+     */
+    vce = g_malloc0(vce_len);
+
+    rc = diag_320_get_cert_info(vce, qcert, &cert_der, &key_id_data, &hash_data);
+    if (rc) {
+        return NULL;
+    }
+
+    is_valid = diag_320_is_cert_valid(qcert);
+
+    vce->len = cpu_to_be32(vce_len);
+    vce->cert_idx = cpu_to_be16(idx + 1);
+    vce->cert_len = cpu_to_be32(qcert.der_size);
+
+    strncpy((char *)vce->name, (char *)qcert.vc_name, VC_NAME_LEN_BYTES);
+
+    /* VCE field offset is also word aligned */
+    vce->hash_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size);
+    vce->cert_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size + hash_buf_size);
+
+    /* Write Key ID */
+    memcpy(vce->cert_buf, key_id_data, qcert.key_id_size);
+    /* Write Hash key */
+    memcpy(vce->cert_buf + keyid_buf_size, hash_data, qcert.hash_size);
+    /* Write VCE cert data */
+    memcpy(vce->cert_buf + keyid_buf_size + hash_buf_size, cert_der, qcert.der_size);
+
+    /* The certificate is valid and VCE contains the certificate */
+    if (is_valid == 0) {
+        vce->flags |= DIAG_320_VCE_FLAGS_VALID;
+    }
+
+    return g_steal_pointer(&vce);
+}
+
+static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
+                                   S390IPLCertificateStore *qcs)
+{
+    g_autofree VCBlock *vcb = NULL;
+    size_t vce_offset;
+    size_t remaining_space;
+    size_t keyid_buf_size;
+    size_t hash_buf_size;
+    size_t cert_buf_size;
+    uint32_t vce_len;
+    uint16_t first_vc_index;
+    uint16_t last_vc_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;
+    }
+
+    if (first_vc_index == 0) {
+        /*
+         * Zero is a valid index for the first and last VC index.
+         * Zero index results in the VCB header and zero certificates returned.
+         */
+        if (last_vc_index == 0) {
+            goto out;
+        }
+
+        /* DIAG320 certificate store remains a one origin for cert entries */
+        vcb->first_vc_index = 1;
+        first_vc_index = 1;
+    }
+
+    vce_offset = VCB_HEADER_LEN;
+    vcb->out_len = VCB_HEADER_LEN;
+    remaining_space = in_len - VCB_HEADER_LEN;
+
+    for (int i = first_vc_index - 1; i < last_vc_index && i < qcs->count; i++) {
+        VCEntry *vce;
+        S390IPLCertificate qcert = qcs->certs[i];
+        /*
+         * Each VCE is word aligned.
+         * Each variable length field within the VCE is also word aligned.
+         */
+        keyid_buf_size = ROUND_UP(qcert.key_id_size, 4);
+        hash_buf_size = ROUND_UP(qcert.hash_size, 4);
+        cert_buf_size = ROUND_UP(qcert.der_size, 4);
+        vce_len = VCE_HEADER_LEN + cert_buf_size + keyid_buf_size + hash_buf_size;
+
+        /*
+         * 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);
+            break;
+        }
+
+        vce = build_vce(qcert, vce_len, i, keyid_buf_size, hash_buf_size);
+        if (vce == NULL) {
+            continue;
+        }
+
+        /* Write VCE */
+        if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1, vce, vce_len)) {
+            s390_cpu_virt_mem_handle_exc(cpu, ra);
+            return -1;
+        }
+
+        vce_offset += vce_len;
+        vcb->out_len += vce_len;
+        remaining_space -= vce_len;
+        vcb->stored_ct++;
+
+        g_free(vce);
+    }
+
+    vcb->out_len = cpu_to_be32(vcb->out_len);
+    vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
+
+out:
+    /*
+     * Write VCB header
+     * All VCEs have been populated with the latest information
+     * and write VCB header last.
+     */
+    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) != 128,
                    "size of VCStorageSizeBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != 64, "size of VCBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != 128, "size of VCEntry is wrong");
 
 void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 {
@@ -219,7 +465,7 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 
     switch (subcode) {
     case DIAG_320_SUBC_QUERY_ISM:
-        uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI);
+        uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI | DIAG_320_ISM_STORE_VC);
 
         if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
             s390_cpu_virt_mem_handle_exc(cpu, ra);
@@ -258,6 +504,12 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
         }
         rc = DIAG_320_RC_OK;
         break;
+    case DIAG_320_SUBC_STORE_VC:
+        rc = handle_diag320_store_vc(cpu, addr, r1, ra, qcs);
+        if (rc == -1) {
+            return;
+        }
+        break;
     default:
         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
         return;
-- 
2.49.0



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

* [PATCH v4 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (8 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 09/28] s390x/diag: Implement " Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
                   ` (17 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 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 +++++++
 5 files changed, 65 insertions(+)
 create mode 100644 include/hw/s390x/ipl/diag508.h

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 4641f88278..e01ea0b289 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"
@@ -516,3 +517,29 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
     }
     env->regs[r1 + 1] = rc;
 }
+
+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 d5b3694600..840330709b 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 86a652f833..df0973266a 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -402,6 +402,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 412c34ed93..ddbf495118 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.49.0



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

* [PATCH v4 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (9 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-22 16:26   ` Daniel P. Berrangé
  2025-07-11 21:10 ` [PATCH v4 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
                   ` (16 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 crypto/x509-utils.c         | 110 ++++++++++++++++++++++++++++++++++++
 include/crypto/x509-utils.h |  39 +++++++++++++
 2 files changed, 149 insertions(+)

diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 135f83f55e..2b1ed5ee26 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,
@@ -366,6 +367,98 @@ cleanup:
     return ret;
 }
 
+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 = { 0, };
+
+    rc = gnutls_pkcs7_init(&signature);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initalize 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));
+        gnutls_free(sig_datum_pem.data);
+        goto cleanup;
+    }
+
+    *result = g_steal_pointer(&sig_datum_pem.data);
+    *resultlen = sig_datum_pem.size;
+
+    ret = 0;
+
+cleanup:
+    gnutls_pkcs7_deinit(signature);
+    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;
+    gnutls_pkcs7_t signature;
+    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));
+        return ret;
+    }
+
+    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));
+        gnutls_x509_crt_deinit(crt);
+        return ret;
+    }
+
+    rc = gnutls_pkcs7_init(&signature);
+    if (rc < 0) {
+        error_setg(errp, "Failed to initalize pkcs7 data: %s", gnutls_strerror(rc));
+        gnutls_x509_crt_deinit(crt);
+        return ret;
+     }
+
+    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_x509_convert_cert_der(uint8_t *cert, size_t size,
@@ -427,4 +520,21 @@ int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
     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 c122df0a98..8fcf7becbd 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -160,4 +160,43 @@ int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
                                  size_t *resultlen,
                                  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.49.0



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

* [PATCH v4 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (10 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
                   ` (15 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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 Diag508SignatureVerificationBlock, with the CertificateStoreInfo
fields ignored. 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.

The subcode value is denoted by setting the second-to-left-most bit of
a 2-byte field.

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

	0x0202: component data is invalid
	0x0302: signature is not in PKCS#7 format
	0x0402: signature-verification failed

Signed-off-by: Collin Walling <walling@linux.ibm.com>
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 include/hw/s390x/ipl/diag508.h |  23 +++++++
 target/s390x/diag.c            | 112 ++++++++++++++++++++++++++++++++-
 2 files changed, 134 insertions(+), 1 deletion(-)

diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
index 6281ad8299..c99c6705c0 100644
--- a/include/hw/s390x/ipl/diag508.h
+++ b/include/hw/s390x/ipl/diag508.h
@@ -11,5 +11,28 @@
 #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
+
+struct Diag508CertificateStoreInfo {
+    uint8_t  idx;
+    uint8_t  reserved[7];
+    uint64_t len;
+};
+typedef struct Diag508CertificateStoreInfo Diag508CertificateStoreInfo;
+
+struct Diag508SignatureVerificationBlock {
+    Diag508CertificateStoreInfo csi;
+    uint64_t comp_len;
+    uint64_t comp_addr;
+    uint64_t sig_len;
+    uint64_t sig_addr;
+};
+typedef struct Diag508SignatureVerificationBlock Diag508SignatureVerificationBlock;
 
 #endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index e01ea0b289..0e7e594bf5 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -518,9 +518,107 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
     env->regs[r1 + 1] = rc;
 }
 
+static int 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
+     */
+    rc = qcrypto_pkcs7_convert_sig_pem(sig, sig_size, &sig_pem, &sig_size_pem, NULL);
+    if (rc < 0) {
+        return -1;
+    }
+
+    /*
+     * Ignore errors from signature format convertion and verification,
+     * because currently in the certificate lookup process.
+     *
+     * Any error is treated as a verification failure,
+     * and the final result (verified or not) will be reported later.
+     */
+    rc = qcrypto_x509_verify_sig(cert, cert_size,
+                                 comp, comp_size,
+                                 sig_pem, sig_size_pem, NULL);
+    if (rc < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int handle_diag508_sig_verif(uint64_t addr, size_t csi_size, size_t svb_size,
+                                    S390IPLCertificateStore *qcs)
+{
+    int rc;
+    int verified;
+    uint64_t comp_len, comp_addr;
+    uint64_t sig_len, sig_addr;
+    g_autofree uint8_t *svb_comp = NULL;
+    g_autofree uint8_t *svb_sig = NULL;
+    g_autofree Diag508SignatureVerificationBlock *svb = NULL;
+
+    if (!qcs || !qcs->count) {
+        return DIAG_508_RC_NO_CERTS;
+    }
+
+    svb = g_new0(Diag508SignatureVerificationBlock, 1);
+    cpu_physical_memory_read(addr, svb, svb_size);
+
+    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) {
+        return DIAG_508_RC_INVAL_COMP_DATA;
+    }
+
+    if (!sig_len || !sig_addr) {
+        return DIAG_508_RC_INVAL_PKCS7_SIG;
+    }
+
+    svb_comp = g_malloc0(comp_len);
+    cpu_physical_memory_read(comp_addr, svb_comp, comp_len);
+
+    svb_sig = g_malloc0(sig_len);
+    cpu_physical_memory_read(sig_addr, svb_sig, sig_len);
+
+    rc = DIAG_508_RC_FAIL_VERIF;
+    /*
+     * It is uncertain which certificate contains
+     * the analogous key to verify the signed data
+     */
+    for (int i = 0; i < qcs->count; i++) {
+        verified = diag_508_verify_sig(qcs->certs[i].raw,
+                                       qcs->certs[i].size,
+                                       svb_comp, comp_len,
+                                       svb_sig, sig_len);
+        if (verified == 0) {
+            svb->csi.idx = i;
+            svb->csi.len = cpu_to_be64(qcs->certs[i].der_size);
+            cpu_physical_memory_write(addr, &svb->csi, be32_to_cpu(csi_size));
+            rc = DIAG_508_RC_OK;
+            break;
+       }
+    }
+
+    return rc;
+}
+
+QEMU_BUILD_BUG_MSG(sizeof(Diag508SignatureVerificationBlock) != 48,
+                   "size of Diag508SignatureVerificationBlock is wrong");
+
 void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 {
+    S390IPLCertificateStore *qcs = 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) {
@@ -535,7 +633,19 @@ 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:
+        size_t csi_size = sizeof(Diag508CertificateStoreInfo);
+        size_t svb_size = sizeof(Diag508SignatureVerificationBlock);
+
+        if (!diag_parm_addr_valid(addr, svb_size, false) ||
+            !diag_parm_addr_valid(addr, csi_size, true)) {
+            s390_program_interrupt(env, PGM_ADDRESSING, ra);
+            return;
+        }
+
+        rc = handle_diag508_sig_verif(addr, csi_size, svb_size, qcs);
         break;
     default:
         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
-- 
2.49.0



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

* [PATCH v4 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB)
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (11 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
                   ` (14 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 pc-bios/s390-ccw/iplb.h | 62 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index 08f259ff31..bdbc733e16 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -23,6 +23,68 @@ extern QemuIplParameters qipl;
 extern IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE)));
 extern bool have_iplb;
 
+struct IplInfoReportBlockHeader {
+    uint32_t len;
+    uint8_t  iirb_flags;
+    uint8_t  reserved1[2];
+    uint8_t  version;
+    uint8_t  reserved2[8];
+} __attribute__ ((packed));
+typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
+
+struct IplInfoBlockHeader {
+    uint32_t len;
+    uint8_t  ibt;
+    uint8_t  reserved1[3];
+    uint8_t  reserved2[8];
+} __attribute__ ((packed));
+typedef struct IplInfoBlockHeader IplInfoBlockHeader;
+
+enum IplIbt {
+    IPL_IBT_CERTIFICATES = 1,
+    IPL_IBT_COMPONENTS = 2,
+};
+
+struct IplSignatureCertificateEntry {
+    uint64_t addr;
+    uint64_t len;
+} __attribute__ ((packed));
+typedef struct IplSignatureCertificateEntry IplSignatureCertificateEntry;
+
+struct IplSignatureCertificateList {
+    IplInfoBlockHeader            ipl_info_header;
+    IplSignatureCertificateEntry  cert_entries[MAX_CERTIFICATES];
+} __attribute__ ((packed));
+typedef struct IplSignatureCertificateList IplSignatureCertificateList;
+
+#define S390_IPL_COMPONENT_FLAG_SC  0x80
+#define S390_IPL_COMPONENT_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];
+} __attribute__ ((packed));
+typedef struct IplDeviceComponentEntry IplDeviceComponentEntry;
+
+struct IplDeviceComponentList {
+    IplInfoBlockHeader       ipl_info_header;
+    IplDeviceComponentEntry  device_entries[MAX_CERTIFICATES];
+} __attribute__ ((packed));
+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];
+} __attribute__ ((packed));
+typedef struct IplInfoReportBlock IplInfoReportBlock;
+
 #define S390_IPL_TYPE_FCP 0x00
 #define S390_IPL_TYPE_CCW 0x02
 #define S390_IPL_TYPE_QEMU_SCSI 0xff
-- 
2.49.0



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

* [PATCH v4 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (12 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
                   ` (13 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 pc-bios/s390-ccw/iplb.h     | 12 ++++++++++--
 pc-bios/s390-ccw/jump2ipl.c |  6 +++---
 pc-bios/s390-ccw/main.c     | 34 +++++++++++++++++++---------------
 pc-bios/s390-ccw/netmain.c  |  8 ++++----
 4 files changed, 36 insertions(+), 24 deletions(-)

diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index bdbc733e16..11302e004d 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -20,7 +20,7 @@
 #include <string.h>
 
 extern QemuIplParameters qipl;
-extern IplParameterBlock iplb __attribute__((__aligned__(PAGE_SIZE)));
+extern IplParameterBlock *iplb;
 extern bool have_iplb;
 
 struct IplInfoReportBlockHeader {
@@ -85,6 +85,14 @@ struct IplInfoReportBlock {
 } __attribute__ ((packed));
 typedef struct IplInfoReportBlock IplInfoReportBlock;
 
+struct IplBlocks {
+    IplParameterBlock   iplb;
+    IplInfoReportBlock  iirb;
+} __attribute__ ((packed));
+typedef struct IplBlocks IplBlocks;
+
+extern IplBlocks ipl_data __attribute__((__aligned__(PAGE_SIZE)));
+
 #define S390_IPL_TYPE_FCP 0x00
 #define S390_IPL_TYPE_CCW 0x02
 #define S390_IPL_TYPE_QEMU_SCSI 0xff
@@ -127,7 +135,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..c9328f1c51 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;
+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 719a547ada..49afd9100d 100644
--- a/pc-bios/s390-ccw/netmain.c
+++ b/pc-bios/s390-ccw/netmain.c
@@ -488,11 +488,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.49.0



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

* [PATCH v4 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (13 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 16/28] hw/s390x/ipl: Set iplb->len to maximum length of " Zhuoying Cai
                   ` (12 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 hw/s390x/ipl.c                 | 20 ++++++++++++++++++++
 hw/s390x/ipl.h                 | 17 -----------------
 include/hw/s390x/ipl/diag308.h | 34 ++++++++++++++++++++++++++++++++++
 include/hw/s390x/ipl/qipl.h    |  5 ++++-
 4 files changed, 58 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 186be923d7..8ac0cee73d 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -430,6 +430,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;
@@ -487,6 +494,19 @@ 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;
+        }
+
         return true;
     }
 
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index e26fc1cd6a..3b8cc5474e 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);
@@ -91,22 +90,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
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 e505f44020..5c2bf3051c 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.49.0



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

* [PATCH v4 16/28] hw/s390x/ipl: Set iplb->len to maximum length of IPL Parameter Block
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (14 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 17/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
                   ` (11 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

The IPL Information Report Block (IIRB) immediately follows the IPL
Parameter Block (IPLB).

The IPLB struct is allocated 4KB in memory, and iplb->len indicates
the amount of memory currently used by the IPLB.

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>
---
 hw/s390x/ipl.c | 6 +++---
 hw/s390x/ipl.h | 1 +
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index 8ac0cee73d..d1a972ac8d 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -459,7 +459,7 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
             if (scsi_lp && strlen(scsi_lp) > 0) {
                 lp = scsi_lp;
             }
-            iplb->len = cpu_to_be32(S390_IPLB_MIN_QEMU_SCSI_LEN);
+            iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
             iplb->blk0_len =
                 cpu_to_be32(S390_IPLB_MIN_QEMU_SCSI_LEN - S390_IPLB_HEADER_LEN);
             iplb->pbt = S390_IPL_TYPE_QEMU_SCSI;
@@ -470,14 +470,14 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
             iplb->scsi.ssid = ccw_dev->sch->ssid & 3;
             break;
         case CCW_DEVTYPE_VFIO:
-            iplb->len = cpu_to_be32(S390_IPLB_MIN_CCW_LEN);
+            iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
             iplb->pbt = S390_IPL_TYPE_CCW;
             iplb->ccw.devno = cpu_to_be16(ccw_dev->sch->devno);
             iplb->ccw.ssid = ccw_dev->sch->ssid & 3;
             break;
         case CCW_DEVTYPE_VIRTIO_NET:
         case CCW_DEVTYPE_VIRTIO:
-            iplb->len = cpu_to_be32(S390_IPLB_MIN_CCW_LEN);
+            iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
             iplb->blk0_len =
                 cpu_to_be32(S390_IPLB_MIN_CCW_LEN - S390_IPLB_HEADER_LEN);
             iplb->pbt = S390_IPL_TYPE_CCW;
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 3b8cc5474e..01922d80c4 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -100,6 +100,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 diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
 {
-- 
2.49.0



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

* [PATCH v4 17/28] s390x: Guest support for Secure-IPL Facility
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (15 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 16/28] hw/s390x/ipl: Set iplb->len to maximum length of " Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-14 20:33   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 18/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
                   ` (10 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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.

When SIPL facility is installed, the IPL Parameter Block length must
contains value that is multiple of 8 bytes.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 hw/s390x/sclp.c                     | 2 ++
 include/hw/s390x/sclp.h             | 4 +++-
 target/s390x/cpu_features.c         | 3 +++
 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, 19 insertions(+), 1 deletion(-)

diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
index 9718564fa4..69d3328a3d 100644
--- a/hw/s390x/sclp.c
+++ b/hw/s390x/sclp.c
@@ -145,6 +145,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 d32f6180e0..bfd330c340 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 - 137];     /* 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 99089ab3f5..3f3d6a80af 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -149,6 +149,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_DIAG_320)->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 7b13a95d98..956bd8a123 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(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide 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 33ef5c190c..ab46204d9e 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_DIAG_320, 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 86486def23..6ee9bad4c6 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_DIAG_320,
+    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_DIAG_320,
+    S390_FEAT_SIPL,
 };
 
 /****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 840330709b..fc9cad32a1 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2520,6 +2520,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
 
     set_bit(S390_FEAT_DIAG_320, model->features);
 
+    /* Secure-IPL facility is handled entirely within QEMU */
+    set_bit(S390_FEAT_SIPL, model->features);
+
     /* Test for Ultravisor features that influence secure guest behavior */
     query_uv_feat_guest(model->features);
 
-- 
2.49.0



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

* [PATCH v4 18/28] pc-bios/s390-ccw: Refactor zipl_run()
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (16 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 17/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-14 20:53   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
                   ` (9 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 pc-bios/s390-ccw/bootmap.c | 58 ++++++++++++++++++++++----------------
 1 file changed, 34 insertions(+), 24 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 0f8baa0198..ced5190888 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -674,6 +674,38 @@ static int zipl_load_segment(ComponentEntry *entry)
     return 0;
 }
 
+static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
+{
+    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;
+        }
+    }
+
+    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
+        puts("No EXEC entry");
+        return -EINVAL;
+    }
+
+    write_reset_psw(entry->compdat.load_psw);
+    return 0;
+}
+
 /* Run a zipl program */
 static int zipl_run(ScsiBlockPtr *pte)
 {
@@ -700,34 +732,12 @@ 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;
-        }
-    }
-
-    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
-        puts("No EXEC entry");
-        return -EINVAL;
+    if (zipl_run_normal(entry, tmp_sec)) {
+        return -1;
     }
 
     /* should not return */
-    write_reset_psw(entry->compdat.load_psw);
     jump_to_IPL_code(0);
     return -1;
 }
-- 
2.49.0



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

* [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (17 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 18/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-14 22:10   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
                   ` (8 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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

Modify this function for reuse in the next patch, which allows
loading segment or signature data to the destination memory address.

Add a comp_len variable to store the length of a segment and return this
variable 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.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 pc-bios/s390-ccw/bootmap.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index ced5190888..2513e6c131 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -613,19 +613,18 @@ static int ipl_eckd(void)
  * IPL a SCSI disk
  */
 
-static int zipl_load_segment(ComponentEntry *entry)
+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 comp_len = 0;
 
     blockno = entry->data.blockno;
-    address = entry->compdat.load_addr;
 
     debug_print_int("loading segment at block", blockno);
     debug_print_int("addr", address);
@@ -662,6 +661,9 @@ static int zipl_load_segment(ComponentEntry *entry)
                  */
                 break;
             }
+
+            comp_len += bprs->size * (bprs[i].blockct + 1);
+
             address = virtio_load_direct(cur_desc[0], cur_desc[1], 0,
                                          (void *)address);
             if (!address) {
@@ -671,7 +673,7 @@ static int zipl_load_segment(ComponentEntry *entry)
         }
     } while (blockno);
 
-    return 0;
+    return comp_len;
 }
 
 static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
@@ -685,7 +687,7 @@ static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
             continue;
         }
 
-        if (zipl_load_segment(entry)) {
+        if (zipl_load_segment(entry, entry->compdat.load_addr) < 0) {
             return -1;
         }
 
-- 
2.49.0



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

* [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (18 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 22:50   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
                   ` (7 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 pc-bios/s390-ccw/Makefile     |   3 +-
 pc-bios/s390-ccw/bootmap.c    | 193 +++++++++++++++++++++++++++++++++-
 pc-bios/s390-ccw/bootmap.h    |   9 ++
 pc-bios/s390-ccw/main.c       |   9 ++
 pc-bios/s390-ccw/s390-ccw.h   |  14 +++
 pc-bios/s390-ccw/sclp.c       |  44 ++++++++
 pc-bios/s390-ccw/sclp.h       |   6 ++
 pc-bios/s390-ccw/secure-ipl.c | 190 +++++++++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h |  98 +++++++++++++++++
 9 files changed, 563 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/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
index dc69dd484f..fedb89a387 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 2513e6c131..0827459803 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -8,6 +8,7 @@
  * directory.
  */
 
+#include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 #include "s390-ccw.h"
@@ -15,6 +16,7 @@
 #include "bootmap.h"
 #include "virtio.h"
 #include "bswap.h"
+#include "secure-ipl.h"
 
 #ifdef DEBUG
 /* #define DEBUG_FALLBACK */
@@ -34,6 +36,9 @@ static uint8_t sec[MAX_SECTOR_SIZE*4] __attribute__((__aligned__(PAGE_SIZE)));
 const uint8_t el_torito_magic[] = "EL TORITO SPECIFICATION"
                                   "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
 
+/* sector for storing signatures */
+static uint8_t sig_sec[MAX_SECTOR_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
+
 /*
  * Match two CCWs located after PSW and eight filler bytes.
  * From libmagic and arch/s390/kernel/head.S.
@@ -676,6 +681,159 @@ static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
     return comp_len;
 }
 
+static uint32_t zipl_handle_sig_entry(ComponentEntry *entry)
+{
+    uint32_t sig_len;
+
+    if (zipl_load_segment(entry, (uint64_t)sig_sec) < 0) {
+        return -1;
+    }
+
+    if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {
+        puts("Signature is not in DER format");
+        return -1;
+    }
+    sig_len = entry->compdat.sig_info.sig_len;
+
+    return sig_len;
+}
+
+static int handle_certificate(int *cert_table, uint64_t **cert,
+                             uint64_t cert_len, uint8_t cert_idx,
+                             IplSignatureCertificateList *certs, int cert_index)
+{
+    bool unused;
+
+    unused = cert_table[cert_idx] == -1;
+    if (unused) {
+        if (zipl_secure_request_certificate(*cert, cert_idx)) {
+            zipl_secure_cert_list_add(certs, cert_index, *cert, cert_len);
+            cert_table[cert_idx] = cert_index;
+            *cert += cert_len;
+        } else {
+            puts("Could not get certificate");
+            return -1;
+        }
+
+        /* increment cert_index for the next cert entry */
+        return ++cert_index;
+    }
+
+    return cert_index;
+}
+
+static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
+{
+    IplDeviceComponentList comps;
+    IplSignatureCertificateList certs;
+    uint64_t *cert = NULL;
+    int cert_index = 0;
+    int comp_index = 0;
+    uint64_t comp_addr;
+    int comp_len;
+    bool have_sig;
+    uint32_t sig_len;
+    uint64_t cert_len = -1;
+    uint8_t cert_idx = -1;
+    bool verified;
+    uint32_t certs_len;
+    /*
+     * Store indices of cert entry that have already used for signature verification
+     * to prevent allocating the same certificate multiple times.
+     * cert_table index: index of certificate from qemu cert store used for verification
+     * cert_table value: index of cert entry in cert list that contains the certificate
+     */
+    int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
+    int signed_count = 0;
+
+    if (!zipl_secure_ipl_supported()) {
+        return -1;
+    }
+
+    zipl_secure_init_lists(&comps, &certs);
+    certs_len = zipl_secure_get_certs_length();
+    cert = malloc(certs_len);
+
+    have_sig = false;
+    while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
+           entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+
+        if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+            /* There should never be two signatures in a row */
+            if (have_sig) {
+                return -1;
+            }
+
+            sig_len = zipl_handle_sig_entry(entry);
+            if (sig_len < 0) {
+                return -1;
+            }
+
+            have_sig = true;
+        } else {
+            comp_addr = entry->compdat.load_addr;
+            comp_len = zipl_load_segment(entry, comp_addr);
+            if (comp_len < 0) {
+                return -1;
+            }
+
+            if (have_sig) {
+                verified = verify_signature(comp_len, comp_addr,
+                                            sig_len, (uint64_t)sig_sec,
+                                            &cert_len, &cert_idx);
+
+                if (verified) {
+                    cert_index = handle_certificate(cert_table, &cert,
+                                                    cert_len, cert_idx,
+                                                    &certs, cert_index);
+                    if (cert_index == -1) {
+                        return -1;
+                    }
+
+                    puts("Verified component");
+                    zipl_secure_comp_list_add(&comps, comp_index, cert_table[cert_idx],
+                                              comp_addr, comp_len,
+                                              S390_IPL_COMPONENT_FLAG_SC |
+                                              S390_IPL_COMPONENT_FLAG_CSV);
+                } else {
+                    zipl_secure_comp_list_add(&comps, comp_index, -1,
+                                              comp_addr, comp_len,
+                                              S390_IPL_COMPONENT_FLAG_SC);
+                    zipl_secure_print("Could not verify component");
+                }
+
+                comp_index++;
+                signed_count += 1;
+                /* After a signature is used another new one can be accepted */
+                have_sig = false;
+            }
+        }
+
+        entry++;
+
+        if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
+            puts("Wrong entry value");
+            return -EINVAL;
+        }
+    }
+
+    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
+        puts("No EXEC entry");
+        return -EINVAL;
+    }
+
+    if (signed_count == 0) {
+        zipl_secure_print("Secure boot is on, but components are not signed");
+    }
+
+    if (zipl_secure_update_iirb(&comps, &certs)) {
+        zipl_secure_print("Failed to write IPL Information Report Block");
+    }
+    write_reset_psw(entry->compdat.load_psw);
+
+    return 0;
+}
+
 static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
 {
     while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
@@ -735,8 +893,17 @@ static int zipl_run(ScsiBlockPtr *pte)
     /* Load image(s) into RAM */
     entry = (ComponentEntry *)(&header[1]);
 
-    if (zipl_run_normal(entry, tmp_sec)) {
-        return -1;
+    switch (boot_mode) {
+    case ZIPL_SECURE_AUDIT_MODE:
+        if (zipl_run_secure(entry, tmp_sec)) {
+            return -1;
+        }
+        break;
+    case ZIPL_NORMAL_MODE:
+        if (zipl_run_normal(entry, tmp_sec)) {
+            return -1;
+        }
+        break;
     }
 
     /* should not return */
@@ -1095,17 +1262,35 @@ static int zipl_load_vscsi(void)
  * IPL starts here
  */
 
+ZiplBootMode zipl_mode(uint8_t hdr_flags)
+{
+    bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
+    bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
+
+    if (!sipl_set && iplir_set) {
+        return ZIPL_SECURE_AUDIT_MODE;
+    }
+
+    return ZIPL_NORMAL_MODE;
+}
+
 void zipl_load(void)
 {
     VDev *vdev = virtio_get_device();
 
     if (vdev->is_cdrom) {
+        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+            panic("Secure boot from ISO image is not supported!");
+        }
         ipl_iso_el_torito();
         puts("Failed to IPL this ISO image!");
         return;
     }
 
     if (virtio_get_device_type() == VIRTIO_ID_NET) {
+        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+            panic("Virtio net boot device does not support secure boot!");
+        }
         netmain();
         puts("Failed to IPL from this network!");
         return;
@@ -1116,6 +1301,10 @@ void zipl_load(void)
         return;
     }
 
+    if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+        panic("ECKD boot device does not support secure boot!");
+    }
+
     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..e48823a835 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;
+} __attribute__((packed)) SignatureInformation;
+
 typedef union ComponentEntryData {
     uint64_t load_psw;
     uint64_t load_addr;
+    SignatureInformation sig_info;
 } ComponentEntryData;
 
 typedef struct ComponentEntry {
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index c9328f1c51..38962da1dd 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -28,6 +28,7 @@ IplParameterBlock *iplb;
 bool have_iplb;
 static uint16_t cutype;
 LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */
+ZiplBootMode boot_mode;
 
 #define LOADPARM_PROMPT "PROMPT  "
 #define LOADPARM_EMPTY  "        "
@@ -272,9 +273,17 @@ static int virtio_setup(void)
 
 static void ipl_boot_device(void)
 {
+    if (boot_mode == 0) {
+        boot_mode = zipl_mode(iplb->hdr_flags);
+    }
+
     switch (cutype) {
     case CU_TYPE_DASD_3990:
     case CU_TYPE_DASD_2107:
+        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+            panic("Passthrough (vfio) device does not support secure boot!");
+        }
+
         dasd_ipl(blk_schid, cutype);
         break;
     case CU_TYPE_VIRTIO:
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 6cdce3e5e5..648f407dc5 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -39,6 +39,9 @@ typedef unsigned long long u64;
 #define MIN_NON_ZERO(a, b) ((a) == 0 ? (b) : \
                             ((b) == 0 ? (a) : (MIN(a, b))))
 #endif
+#ifndef ROUND_UP
+#define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d)))
+#endif
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
 
@@ -64,6 +67,8 @@ void sclp_print(const char *string);
 void sclp_set_write_mask(uint32_t receive_mask, uint32_t send_mask);
 void sclp_setup(void);
 void sclp_get_loadparm_ascii(char *loadparm);
+bool sclp_is_diag320_on(void);
+bool sclp_is_sipl_on(void);
 int sclp_read(char *str, size_t count);
 
 /* virtio.c */
@@ -76,6 +81,15 @@ int virtio_read(unsigned long sector, void *load_addr);
 /* bootmap.c */
 void zipl_load(void);
 
+typedef enum ZiplBootMode {
+    ZIPL_NORMAL_MODE = 1,
+    ZIPL_SECURE_AUDIT_MODE = 2,
+} ZiplBootMode;
+
+extern ZiplBootMode boot_mode;
+
+ZiplBootMode zipl_mode(uint8_t hdr_flags);
+
 /* jump2ipl.c */
 void write_reset_psw(uint64_t psw);
 int jump_to_IPL_code(uint64_t address);
diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
index 4a07de018d..0b03c3164f 100644
--- a/pc-bios/s390-ccw/sclp.c
+++ b/pc-bios/s390-ccw/sclp.c
@@ -113,6 +113,50 @@ void sclp_get_loadparm_ascii(char *loadparm)
     }
 }
 
+static void sclp_get_fac134(uint8_t *fac134)
+{
+
+    ReadInfo *sccb = (void *)_sccb;
+
+    memset((char *)_sccb, 0, sizeof(ReadInfo));
+    sccb->h.length = SCCB_SIZE;
+    if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
+        *fac134 = sccb->fac134;
+    }
+}
+
+bool sclp_is_diag320_on(void)
+{
+    uint8_t fac134 = 0;
+
+    sclp_get_fac134(&fac134);
+    return fac134 & SCCB_FAC134_DIAG320_BIT;
+}
+
+/*
+ * Get fac_ipl (byte 136 and byte 137 of the SCLP Read Info block)
+ * for IPL device facilities.
+ */
+static void sclp_get_fac_ipl(uint16_t *fac_ipl)
+{
+
+    ReadInfo *sccb = (void *)_sccb;
+
+    memset((char *)_sccb, 0, sizeof(ReadInfo));
+    sccb->h.length = SCCB_SIZE;
+    if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
+        *fac_ipl = sccb->fac_ipl;
+    }
+}
+
+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..4b8321e401
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -0,0 +1,190 @@
+/*
+ * S/390 Secure IPL
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include "s390-ccw.h"
+#include "secure-ipl.h"
+
+uint8_t vcb_data[MAX_SECTOR_SIZE * 4] __attribute__((__aligned__(PAGE_SIZE)));
+uint8_t vcssb_data[VCSSB_MAX_LEN] __attribute__((__aligned__(PAGE_SIZE)));
+
+VCStorageSizeBlock *zipl_secure_get_vcssb(void)
+{
+    VCStorageSizeBlock *vcssb;
+    int rc;
+
+    vcssb = (VCStorageSizeBlock *)vcssb_data;
+    /* avoid retrieving vcssb multiple times */
+    if (vcssb->length == VCSSB_MAX_LEN) {
+        return vcssb;
+    }
+
+    rc = diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI);
+    if (rc != DIAG_320_RC_OK) {
+        return NULL;
+    }
+
+    return vcssb;
+}
+
+uint32_t zipl_secure_get_certs_length(void)
+{
+    VCStorageSizeBlock *vcssb;
+    uint32_t len;
+
+    vcssb = zipl_secure_get_vcssb();
+    if (vcssb == NULL) {
+        return 0;
+    }
+
+    len = vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct * VCE_HEADER_LEN;
+
+    return len;
+}
+
+uint32_t zipl_secure_request_certificate(uint64_t *cert, uint8_t index)
+{
+    VCStorageSizeBlock *vcssb;
+    VCBlock *vcb;
+    VCEntry *vce;
+    uint64_t rc = 0;
+    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 + 1;
+    vcb->last_vc_index = index + 1;
+
+    rc = diag320(vcb, DIAG_320_SUBC_STORE_VC);
+    if (rc == DIAG_320_RC_OK) {
+        vce = (VCEntry *)vcb->vce_buf;
+        cert_len = vce->cert_len;
+        memcpy(cert, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
+        /* clear out region for next cert(s) */
+        memcpy(vcb_data, 0, sizeof(vcb_data));
+    }
+
+    return cert_len;
+}
+
+void zipl_secure_cert_list_add(IplSignatureCertificateList *certs, int cert_index,
+                               uint64_t *cert, uint64_t cert_len)
+{
+    if (cert_index > MAX_CERTIFICATES - 1) {
+        printf("Warning: Ignoring cert entry [%d] because it's over %d entires\n",
+                cert_index + 1, MAX_CERTIFICATES);
+        return;
+    }
+
+    certs->cert_entries[cert_index].addr = (uint64_t)cert;
+    certs->cert_entries[cert_index].len = cert_len;
+    certs->ipl_info_header.len += sizeof(certs->cert_entries[cert_index]);
+}
+
+void zipl_secure_comp_list_add(IplDeviceComponentList *comps, 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 it's over %d entires\n",
+                comp_index + 1, MAX_CERTIFICATES);
+        return;
+    }
+
+    comps->device_entries[comp_index].addr = comp_addr;
+    comps->device_entries[comp_index].len = comp_len;
+    comps->device_entries[comp_index].flags = flags;
+    comps->device_entries[comp_index].cert_index = cert_index;
+    comps->ipl_info_header.len += sizeof(comps->device_entries[comp_index]);
+}
+
+int zipl_secure_update_iirb(IplDeviceComponentList *comps,
+                            IplSignatureCertificateList *certs)
+{
+    IplInfoReportBlock *iirb;
+    IplDeviceComponentList *iirb_comps;
+    IplSignatureCertificateList *iirb_certs;
+    uint32_t iirb_hdr_len;
+    uint32_t comps_len;
+    uint32_t certs_len;
+
+    if (iplb->len % 8 != 0) {
+        panic("IPL parameter block length field value is not multiple of 8 bytes");
+    }
+
+    iirb_hdr_len = sizeof(IplInfoReportBlockHeader);
+    comps_len = comps->ipl_info_header.len;
+    certs_len = certs->ipl_info_header.len;
+    if ((comps_len + certs_len + iirb_hdr_len) > sizeof(IplInfoReportBlock)) {
+        puts("Not enough space to hold all components and certificates in IIRB");
+        return -1;
+    }
+
+    /* IIRB immediately follows IPLB */
+    iirb = &ipl_data.iirb;
+    iirb->hdr.len = iirb_hdr_len;
+
+    /* Copy IPL device component list after IIRB Header */
+    iirb_comps = (IplDeviceComponentList *) iirb->info_blks;
+    memcpy(iirb_comps, comps, comps_len);
+
+    /* Update IIRB length */
+    iirb->hdr.len += comps_len;
+
+    /* Copy IPL sig cert list after IPL device component list */
+    iirb_certs = (IplSignatureCertificateList *) (iirb->info_blks +
+                                                  iirb_comps->ipl_info_header.len);
+    memcpy(iirb_certs, certs, certs_len);
+
+    /* Update IIRB length */
+    iirb->hdr.len += certs_len;
+
+    return 0;
+}
+
+bool zipl_secure_ipl_supported(void)
+{
+    if (!sclp_is_sipl_on()) {
+        puts("Secure IPL Facility is not supported by the hypervisor!");
+        return false;
+    }
+
+    if (!is_secure_ipl_extension_supported()) {
+        puts("Secure IPL extensions are not supported by the hypervisor!");
+        return false;
+    }
+
+    if (!(sclp_is_diag320_on() && is_cert_store_facility_supported())) {
+        puts("Certificate Store Facility is not supported by the hypervisor!");
+        return false;
+    }
+
+    return true;
+}
+
+void zipl_secure_init_lists(IplDeviceComponentList *comps,
+                            IplSignatureCertificateList *certs)
+{
+    comps->ipl_info_header.ibt = IPL_IBT_COMPONENTS;
+    comps->ipl_info_header.len = sizeof(comps->ipl_info_header);
+
+    certs->ipl_info_header.ibt = IPL_IBT_CERTIFICATES;
+    certs->ipl_info_header.len = sizeof(certs->ipl_info_header);
+}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
new file mode 100644
index 0000000000..b9052e8c78
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -0,0 +1,98 @@
+/*
+ * 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);
+uint32_t zipl_secure_get_certs_length(void);
+uint32_t zipl_secure_request_certificate(uint64_t *cert, uint8_t index);
+void zipl_secure_cert_list_add(IplSignatureCertificateList *certs, int cert_index,
+                               uint64_t *cert, uint64_t cert_len);
+void zipl_secure_comp_list_add(IplDeviceComponentList *comps, int comp_index,
+                               int cert_index, uint64_t comp_addr,
+                               uint64_t comp_len, uint8_t flags);
+int zipl_secure_update_iirb(IplDeviceComponentList *comps,
+                            IplSignatureCertificateList *certs);
+bool zipl_secure_ipl_supported(void);
+void zipl_secure_init_lists(IplDeviceComponentList *comps,
+                            IplSignatureCertificateList *certs);
+
+static inline void zipl_secure_print(const char *message)
+{
+    switch (boot_mode) {
+    case ZIPL_SECURE_AUDIT_MODE:
+        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)
+{
+    uint64_t d320_ism;
+
+    diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
+    return (d320_ism & DIAG_320_ISM_QUERY_VCSI) &&
+           (d320_ism & 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_secure_ipl_extension_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)
+{
+    Diag508SignatureVerificationBlock svb = {{}, comp_len, comp_addr,
+                                             sig_len, sig_addr };
+
+    if (_diag508(&svb, DIAG_508_SUBC_SIG_VERIF) == DIAG_508_RC_OK) {
+        *cert_len = svb.csi.len;
+        *cert_idx = svb.csi.idx;
+        return true;
+    }
+
+    return false;
+}
+
+#endif /* _PC_BIOS_S390_CCW_SECURE_IPL_H */
-- 
2.49.0



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

* [PATCH v4 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (19 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-14 22:13   ` Collin Walling
  2025-07-11 21:10 ` [PATCH v4 22/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
                   ` (6 subsequent siblings)
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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

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

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 target/s390x/cpu_features.c         | 1 +
 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              | 3 +++
 5 files changed, 10 insertions(+)

diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 3f3d6a80af..8d5614fa59 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -151,6 +151,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 956bd8a123..2e91817d75 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -142,6 +142,7 @@ DEF_FEAT(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide Certificate Store function
 
 /* 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 ab46204d9e..cb1c6b5350 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_DIAG_320, 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 6ee9bad4c6..987e291cf9 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_DIAG_320,
     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_DIAG_320,
     S390_FEAT_SIPL,
+    S390_FEAT_SCLAF,
 };
 
 /****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index fc9cad32a1..be3ad7316d 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2523,6 +2523,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
     /* Secure-IPL facility is handled entirely within QEMU */
     set_bit(S390_FEAT_SIPL, model->features);
 
+    /* Secure-IPL-code-loading-attributes facility is handled entirely within QEMU */
+    set_bit(S390_FEAT_SCLAF, model->features);
+
     /* Test for Ultravisor features that influence secure guest behavior */
     query_uv_feat_guest(model->features);
 
-- 
2.49.0



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

* [PATCH v4 22/28] pc-bios/s390-ccw: Add additional security checks for secure boot
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (20 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:10 ` [PATCH v4 23/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
                   ` (5 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 pc-bios/s390-ccw/bootmap.c    |  27 ++-
 pc-bios/s390-ccw/iplb.h       |  26 ++-
 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 | 396 ++++++++++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h | 127 +++++++++++
 7 files changed, 579 insertions(+), 7 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 0827459803..eae8dfa113 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -745,6 +745,10 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
      */
     int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
     int signed_count = 0;
+    int unsigned_count = 0;
+    SecureIplSclabInfo sclab_info;
+    SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
+    int addr_range_index = 0;
 
     if (!zipl_secure_ipl_supported()) {
         return -1;
@@ -777,7 +781,17 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
                 return -1;
             }
 
-            if (have_sig) {
+            zipl_secure_addr_overlap_check(comp_addr_range, &addr_range_index,
+                                           comp_addr, comp_addr + comp_len, have_sig);
+
+            if (!have_sig) {
+                zipl_secure_check_unsigned_comp(comp_addr, &comps, comp_index,
+                                                cert_index, comp_len);
+                unsigned_count += 1;
+            } else {
+                zipl_secure_check_sclab(comp_addr, &comps, comp_len, comp_index,
+                                        &sclab_info);
+
                 verified = verify_signature(comp_len, comp_addr,
                                             sig_len, (uint64_t)sig_sec,
                                             &cert_len, &cert_idx);
@@ -802,11 +816,12 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
                     zipl_secure_print("Could not verify component");
                 }
 
-                comp_index++;
                 signed_count += 1;
                 /* After a signature is used another new one can be accepted */
                 have_sig = false;
             }
+
+            comp_index++;
         }
 
         entry++;
@@ -822,9 +837,11 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
         return -EINVAL;
     }
 
-    if (signed_count == 0) {
-        zipl_secure_print("Secure boot is on, but components are not signed");
-    }
+    zipl_secure_check_signed_comp(signed_count, &comps);
+    zipl_secure_check_sclab_count(sclab_info.count, &comps);
+    zipl_secure_check_global_sclab(sclab_info, comp_addr_range, addr_range_index,
+                                   entry->compdat.load_psw, unsigned_count, signed_count,
+                                   &comps, comp_index);
 
     if (zipl_secure_update_iirb(&comps, &certs)) {
         zipl_secure_print("Failed to write IPL Information Report Block");
diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index 11302e004d..41cec91a68 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -32,11 +32,19 @@ struct IplInfoReportBlockHeader {
 } __attribute__ ((packed));
 typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
 
+#define S390_IPL_INFO_IIEI_NO_SIGNED_COMP      0x8000 /* bit 0 */
+#define S390_IPL_INFO_IIEI_NO_SCLAB            0x4000 /* bit 1 */
+#define S390_IPL_INFO_IIEI_NO_GLOBAL_SCLAB     0x2000 /* bit 2 */
+#define S390_IPL_INFO_IIEI_MORE_GLOBAL_SCLAB   0x1000 /* bit 3 */
+#define S390_IPL_INFO_IIEI_FOUND_UNSIGNED_COMP 0x800 /* bit 4 */
+#define S390_IPL_INFO_IIEI_MORE_SIGNED_COMP    0x400 /* bit 5 */
+
 struct IplInfoBlockHeader {
     uint32_t len;
     uint8_t  ibt;
     uint8_t  reserved1[3];
-    uint8_t  reserved2[8];
+    uint16_t iiei;
+    uint8_t  reserved2[6];
 } __attribute__ ((packed));
 typedef struct IplInfoBlockHeader IplInfoBlockHeader;
 
@@ -60,13 +68,27 @@ typedef struct IplSignatureCertificateList IplSignatureCertificateList;
 #define S390_IPL_COMPONENT_FLAG_SC  0x80
 #define S390_IPL_COMPONENT_FLAG_CSV 0x40
 
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB             0x80000000 /* bit 0 */
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN         0x40000000 /* bit 1 */
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT      0x20000000 /* bit 2 */
+#define S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR 0x10000000 /* bit 3 */
+#define S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_PSW  0x8000000  /* bit 4 */
+#define S390_IPL_COMPONENT_CEI_INVALID_LOAD_PSW          0x4000000  /* bit 5 */
+#define S390_IPL_COMPONENT_CEI_NUC_NOT_IN_GLOBAL_SCLA    0x2000000  /* bit 6 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE         0x1000000  /* bit 7 */
+#define S390_IPL_COMPONENT_CEI_SC_NOT_IN_GLOBAL_SCLAB    0x800000   /* bit 8 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO  0x400000   /* bit 9 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO   0x200000   /* bit 10 */
+#define S390_IPL_COMPONENT_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];
 } __attribute__ ((packed));
 typedef struct IplDeviceComponentEntry IplDeviceComponentEntry;
 
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 648f407dc5..85f92685f6 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -69,6 +69,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 0b03c3164f..16f973dde8 100644
--- a/pc-bios/s390-ccw/sclp.c
+++ b/pc-bios/s390-ccw/sclp.c
@@ -157,6 +157,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 4b8321e401..2586c81d01 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -9,6 +9,7 @@
 
 #include <string.h>
 #include <stdio.h>
+#include "bootmap.h"
 #include "s390-ccw.h"
 #include "secure-ipl.h"
 
@@ -176,6 +177,12 @@ bool zipl_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;
 }
 
@@ -188,3 +195,392 @@ void zipl_secure_init_lists(IplDeviceComponentList *comps,
     certs->ipl_info_header.ibt = IPL_IBT_CERTIFICATES;
     certs->ipl_info_header.len = sizeof(certs->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 - 1) {
+        return;
+    }
+
+    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 check_unsigned_addr(uint64_t load_addr, IplDeviceComponentList *comps,
+                                int comp_index)
+{
+    uint32_t flag;
+    const char *msg;
+    bool valid;
+
+    valid = validate_unsigned_addr(load_addr);
+    if (!valid) {
+        flag = S390_IPL_COMPONENT_CEI_INVALID_UNSIGNED_ADDR;
+        msg = "Load address is less than 0x2000";
+        set_cei_with_log(comps, comp_index, flag, msg);
+    }
+}
+
+void zipl_secure_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) {
+        comp_addr_range_add(comp_addr_range, *addr_range_index, is_signed,
+                            start_addr, end_addr);
+        *addr_range_index += 1;
+    } else {
+        zipl_secure_print("Component addresses overlap");
+    }
+}
+
+static bool check_sclab_presence(uint8_t *sclab_magic,
+                                 IplDeviceComponentList *comps, int comp_index)
+{
+    if (!validate_sclab_magic(sclab_magic)) {
+        comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+
+        /* a missing SCLAB will not be reported in audit mode */
+        return false;
+    }
+
+    return true;
+}
+
+static void check_sclab_length(uint16_t sclab_len,
+                               IplDeviceComponentList *comps, int comp_index)
+{
+    const char *msg;
+    uint32_t flag;
+    bool valid;
+
+    valid = validate_sclab_length(sclab_len);
+    if (!valid) {
+        flag = S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN |
+               S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+        msg = "Invalid SCLAB length";
+        set_cei_with_log(comps, comp_index, flag, msg);
+    }
+}
+
+static void check_sclab_format(uint8_t sclab_format,
+                               IplDeviceComponentList *comps, int comp_index)
+{
+    const char *msg;
+    uint32_t flag;
+    bool valid;
+
+    valid = validate_sclab_format(sclab_format);
+    if (!valid) {
+        flag = S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT;
+        msg = "Format-0 SCLAB is not being use";
+        set_cei_with_log(comps, comp_index, flag, msg);
+    }
+}
+
+static void check_sclab_opsw(SecureCodeLoadingAttributesBlock *sclab,
+                             SecureIplSclabInfo *sclab_info,
+                             IplDeviceComponentList *comps, int comp_index)
+{
+    const char *msg;
+    uint32_t flag;
+    bool is_opsw_set;
+    bool valid;
+
+    is_opsw_set = is_sclab_flag_set(sclab->flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
+    if (!is_opsw_set) {
+        valid = validate_sclab_opsw_zero(sclab->load_psw);
+        if (!valid) {
+            flag = S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO;
+            msg = "Load PSW is not zero when Override PSW bit is zero";
+            set_cei_with_log(comps, comp_index, flag, msg);
+        }
+    } else {
+        /* OPSW = 1 indicating global SCLAB */
+        valid = validate_sclab_opsw_one(sclab->flags);
+        if (!valid) {
+            flag = S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE;
+            msg = "Override Load Address bit is not set to one in the global SCLAB";
+            set_cei_with_log(comps, comp_index, flag, msg);
+        }
+
+        sclab_info->global_count += 1;
+        if (sclab_info->global_count == 1) {
+            sclab_info->load_psw = sclab->load_psw;
+            sclab_info->flags = sclab->flags;
+        }
+    }
+}
+
+static void check_sclab_ola(SecureCodeLoadingAttributesBlock *sclab,
+                            uint64_t load_addr, IplDeviceComponentList *comps,
+                            int comp_index)
+{
+    const char *msg;
+    uint32_t flag;
+    bool is_ola_set;
+    bool valid;
+
+    is_ola_set = is_sclab_flag_set(sclab->flags, S390_SECURE_IPL_SCLAB_FLAG_OLA);
+    if (!is_ola_set) {
+        valid = validate_sclab_ola_zero(sclab->load_addr);
+        if (!(valid)) {
+            flag = S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO;
+            msg = "Load Address is not zero when Override Load Address bit is zero";
+            set_cei_with_log(comps, comp_index, flag, msg);
+        }
+
+    } else {
+        valid = validate_sclab_ola_one(sclab->load_addr, load_addr);
+        if (!valid) {
+            flag = S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR;
+            msg = "Load Address does not match with component load address";
+            set_cei_with_log(comps, comp_index, flag, msg);
+        }
+    }
+}
+
+static void check_sclab_nuc(uint16_t sclab_flags, IplDeviceComponentList *comps,
+                            int comp_index)
+{
+    const char *msg;
+    uint32_t flag;
+    bool is_nuc_set;
+    bool is_global_sclab;
+
+    is_nuc_set = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_NUC);
+    is_global_sclab = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
+    if (is_nuc_set && !is_global_sclab) {
+        flag = S390_IPL_COMPONENT_CEI_NUC_NOT_IN_GLOBAL_SCLA;
+        msg = "No Unsigned Components bit is set, but not in the global SCLAB";
+        set_cei_with_log(comps, comp_index, flag, msg);
+    }
+}
+
+static void check_sclab_sc(uint16_t sclab_flags, IplDeviceComponentList *comps,
+                           int comp_index)
+{
+    const char *msg;
+    uint32_t flag;
+    bool is_sc_set;
+    bool is_global_sclab;
+
+    is_sc_set = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_SC);
+    is_global_sclab = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
+    if (is_sc_set && !is_global_sclab) {
+        flag = S390_IPL_COMPONENT_CEI_SC_NOT_IN_GLOBAL_SCLAB;
+        msg = "Single Component bit is set, but not in the global SCLAB";
+        set_cei_with_log(comps, comp_index, flag, msg);
+    }
+}
+
+static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
+                         int range_index)
+{
+    uint32_t addr = psw & 0x3FFFFFFF;
+
+    /* 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) {
+            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, IplDeviceComponentList *comps,
+                           int comp_index)
+{
+    uint32_t flag;
+    const char *msg;
+    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) {
+        flag = S390_IPL_COMPONENT_CEI_INVALID_LOAD_PSW;
+        msg = "Invalid PSW";
+        set_cei_with_log(comps, comp_index, flag, msg);
+    }
+
+    valid = validate_lpsw(sclab_load_psw, load_psw);
+    if (!valid) {
+        flag = S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_PSW;
+        msg = "Load PSW does not match with PSW in component";
+        set_cei_with_log(comps, comp_index, flag, msg);
+    }
+}
+
+static void check_nuc(uint16_t global_sclab_flags, int unsigned_count,
+                      IplDeviceComponentList *comps)
+{
+    uint16_t flag;
+    const char *msg;
+    bool is_nuc_set;
+
+    is_nuc_set = is_sclab_flag_set(global_sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_NUC);
+    if (is_nuc_set && unsigned_count > 0) {
+        flag = S390_IPL_INFO_IIEI_FOUND_UNSIGNED_COMP;
+        msg = "Unsigned components are not allowed";
+        set_iiei_with_log(comps, flag, msg);
+    }
+}
+
+static void check_sc(uint16_t global_sclab_flags, int signed_count,
+                     IplDeviceComponentList *comps)
+{
+    uint16_t flag;
+    const char *msg;
+    bool is_sc_set;
+
+    is_sc_set = is_sclab_flag_set(global_sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_SC);
+    if (is_sc_set && signed_count != 1) {
+        flag = S390_IPL_INFO_IIEI_MORE_SIGNED_COMP;
+        msg = "Only one signed component is allowed";
+        set_iiei_with_log(comps, flag, msg);
+    }
+}
+
+void zipl_secure_check_global_sclab(SecureIplSclabInfo sclab_info,
+                                    SecureIplCompAddrRange *comp_addr_range,
+                                    int addr_range_index, uint64_t load_psw,
+                                    int unsigned_count, int signed_count,
+                                    IplDeviceComponentList *comps, int comp_index)
+{
+    uint16_t flag;
+    const char *msg;
+
+    if (sclab_info.count == 0) {
+        return;
+    }
+
+    if (sclab_info.global_count == 0) {
+        flag = S390_IPL_INFO_IIEI_NO_GLOBAL_SCLAB;
+        msg = "Global SCLAB does not exists";
+        set_iiei_with_log(comps, flag, msg);
+        return;
+    }
+
+    if (sclab_info.global_count > 1) {
+        flag = S390_IPL_INFO_IIEI_MORE_GLOBAL_SCLAB;
+        msg = "More than one global SCLAB";
+        set_iiei_with_log(comps, flag, msg);
+        return;
+    }
+
+    if (sclab_info.load_psw) {
+        /* Verify PSW from the final component entry with PSW from the global SCLAB */
+        check_load_psw(comp_addr_range, addr_range_index,
+                          sclab_info.load_psw, load_psw,
+                          comps, comp_index);
+    }
+
+    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, comps);
+
+        /* Only one signed component is allowed is SC flag is set in the global SCLAB */
+        check_sc(sclab_info.flags, signed_count, comps);
+    }
+}
+
+void zipl_secure_check_signed_comp(int signed_count, IplDeviceComponentList *comps)
+{
+    uint16_t flag;
+    const char *msg;
+
+    if (signed_count > 0) {
+        return;
+    }
+
+    flag =  S390_IPL_INFO_IIEI_NO_SIGNED_COMP;
+    msg = "Secure boot is on, but components are not signed";
+    set_iiei_with_log(comps, flag, msg);
+}
+
+void zipl_secure_check_sclab_count(int count, IplDeviceComponentList *comps)
+{
+    uint16_t flag;
+    const char *msg;
+
+    if (count > 0) {
+        return;
+    }
+
+    flag = S390_IPL_INFO_IIEI_NO_SCLAB;
+    msg = "No recognizable SCLAB";
+    set_iiei_with_log(comps, flag, msg);
+}
+
+void zipl_secure_check_unsigned_comp(uint64_t comp_addr, IplDeviceComponentList *comps,
+                                     int comp_index, int cert_index, uint64_t comp_len)
+{
+    check_unsigned_addr(comp_addr, comps, comp_index);
+
+    zipl_secure_comp_list_add(comps, comp_index, cert_index, comp_addr, comp_len, 0x00);
+}
+
+void zipl_secure_check_sclab(uint64_t comp_addr, IplDeviceComponentList *comps,
+                             uint64_t comp_len, int comp_index,
+                             SecureIplSclabInfo *sclab_info)
+{
+    SclabOriginLocator *sclab_locator;
+    SecureCodeLoadingAttributesBlock *sclab;
+    bool exist;
+    bool valid;
+
+    sclab_locator = (SclabOriginLocator *)(comp_addr + comp_len - 8);
+
+    /* return early if sclab does not exist */
+    exist = check_sclab_presence(sclab_locator->magic, comps, comp_index);
+    if (!exist) {
+        return;
+    }
+
+    check_sclab_length(sclab_locator->len, comps, comp_index);
+
+    /* return early if sclab is invalid */
+    valid = (comps->device_entries[comp_index].cei &
+             S390_IPL_COMPONENT_CEI_INVALID_SCLAB) == 0;
+    if (!valid) {
+        return;
+    }
+
+    sclab_info->count += 1;
+    sclab = (SecureCodeLoadingAttributesBlock *)(comp_addr + comp_len -
+                                                    sclab_locator->len);
+
+    check_sclab_format(sclab->format, comps, comp_index);
+    check_sclab_opsw(sclab, sclab_info, comps, comp_index);
+    check_sclab_ola(sclab, comp_addr, comps, comp_index);
+    check_sclab_nuc(sclab->flags, comps, comp_index);
+    check_sclab_sc(sclab->flags, comps, comp_index);
+}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index b9052e8c78..d6c1a21a98 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -27,6 +27,59 @@ bool zipl_secure_ipl_supported(void);
 void zipl_secure_init_lists(IplDeviceComponentList *comps,
                             IplSignatureCertificateList *certs);
 
+#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
+
+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 SecureIplCompAddrRange {
+    bool is_signed;
+    uint64_t start_addr;
+    uint64_t end_addr;
+} SecureIplCompAddrRange;
+
+typedef struct SecureIplSclabInfo {
+    int count;
+    int global_count;
+    uint64_t load_psw;
+    uint16_t flags;
+} SecureIplSclabInfo;
+
+void zipl_secure_addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
+                                    int *addr_range_index,
+                                    uint64_t start_addr, uint64_t end_addr,
+                                    bool is_signed);
+void zipl_secure_check_signed_comp(int signed_count, IplDeviceComponentList *comps);
+void zipl_secure_check_sclab_count(int count, IplDeviceComponentList *comps);
+void zipl_secure_check_global_sclab(SecureIplSclabInfo sclab_info,
+                                    SecureIplCompAddrRange *comp_addr_range,
+                                    int addr_range_index, uint64_t load_psw,
+                                    int unsigned_count, int signed_count,
+                                    IplDeviceComponentList *comps, int comp_index);
+void zipl_secure_check_unsigned_comp(uint64_t comp_addr, IplDeviceComponentList *comps,
+                                     int comp_index, int cert_index, uint64_t comp_len);
+void zipl_secure_check_sclab(uint64_t comp_addr, IplDeviceComponentList *comps,
+                             uint64_t comp_len, int comp_index,
+                             SecureIplSclabInfo *sclab_info);
+
 static inline void zipl_secure_print(const char *message)
 {
     switch (boot_mode) {
@@ -38,6 +91,80 @@ static inline void zipl_secure_print(const char *message)
     }
 }
 
+static inline bool is_sclab_flag_set(uint16_t sclab_flags, uint16_t flag)
+{
+    return (sclab_flags & flag) != 0;
+}
+
+static inline bool validate_unsigned_addr(uint64_t comp_load_addr)
+{
+    /* usigned load address must be greater than or equal to 0x2000 */
+    return comp_load_addr >= 0x2000;
+}
+
+static inline bool validate_sclab_magic(uint8_t *sclab_magic)
+{
+    /* identifies the presence of SCLAB */
+    return magic_match(sclab_magic, ZIPL_MAGIC);
+}
+
+static inline bool validate_sclab_length(uint16_t sclab_len)
+{
+    /* minimum SCLAB length is 32 bytes */
+    return sclab_len >= 32;
+}
+
+static inline bool validate_sclab_format(uint8_t sclab_format)
+{
+    /* SCLAB format must set to zero, indicating a format-0 SCLAB being used */
+    return sclab_format == 0;
+}
+
+static inline bool validate_sclab_ola_zero(uint64_t sclab_load_addr)
+{
+    /* Load address field in SCLAB must contain zeros */
+    return sclab_load_addr == 0;
+}
+
+static inline bool validate_sclab_ola_one(uint64_t sclab_load_addr,
+                                          uint64_t comp_load_addr)
+{
+   /* Load address field must match storage address of the component */
+   return sclab_load_addr == comp_load_addr;
+}
+
+static inline bool validate_sclab_opsw_zero(uint64_t sclab_load_psw)
+{
+    /* Load PSW field in SCLAB must contain zeros */
+    return sclab_load_psw == 0;
+}
+
+static inline bool validate_sclab_opsw_one(uint16_t sclab_flags)
+{
+   /* OLA must set to one */
+   return is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OLA);
+}
+
+static inline bool validate_lpsw(uint64_t sclab_load_psw, uint64_t comp_load_psw)
+{
+    /* compare load PSW with the PSW specified in component */
+    return sclab_load_psw == comp_load_psw;
+}
+
+static inline void set_cei_with_log(IplDeviceComponentList *comps, int comp_index,
+                                    uint32_t flag, const char *message)
+{
+    comps->device_entries[comp_index].cei |= flag;
+    zipl_secure_print(message);
+}
+
+static inline void set_iiei_with_log(IplDeviceComponentList *comps, uint16_t flag,
+                                     const char *message)
+{
+    comps->ipl_info_header.iiei |= flag;
+    zipl_secure_print(message);
+}
+
 static inline uint64_t diag320(void *data, unsigned long subcode)
 {
     register unsigned long addr asm("0") = (unsigned long)data;
-- 
2.49.0



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

* [PATCH v4 23/28] Add secure-boot to s390-ccw-virtio machine type option
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (21 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 22/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
@ 2025-07-11 21:10 ` Zhuoying Cai
  2025-07-11 21:11 ` [PATCH v4 24/28] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
                   ` (4 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:10 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 hw/s390x/s390-virtio-ccw.c         | 22 ++++++++++++++++++++++
 include/hw/s390x/s390-virtio-ccw.h |  1 +
 qemu-options.hx                    |  6 +++++-
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index 167876295e..306325d744 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -814,6 +814,21 @@ static void machine_set_boot_certificates(Object *obj, const char *str,
     ms->boot_certificates = g_strdup(str);
 }
 
+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);
@@ -873,6 +888,13 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
                                   machine_set_boot_certificates);
     object_class_property_set_description(oc, "boot-certificates",
             "provide path to a directory or a single certificate 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 45adc8bce6..901e013089 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -32,6 +32,7 @@ struct S390CcwMachineState {
     uint64_t memory_limit;
     uint64_t max_pagesize;
     char *boot_certificates;
+    bool secure_boot;
 
     SCLPDevice *sclp;
 };
diff --git a/qemu-options.hx b/qemu-options.hx
index d42e6f502c..7258136d5d 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -44,7 +44,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"
     "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
-    "                boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n",
+    "                boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n"
+    "                secure-boot=on|off enable/disable secure boot (default=off) \n",
     QEMU_ARCH_ALL)
 SRST
 ``-machine [type=]name[,prop=value[,...]]``
@@ -205,6 +206,9 @@ SRST
     ``boot-certificates='/path/directory:/path/file'``
         Provide a path to a directory or a boot certificate on the host [s390x only].
         A colon may be used to delineate multiple paths.
+
+    ``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.49.0



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

* [PATCH v4 24/28] hw/s390x/ipl: Set IPIB flags for secure IPL
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (22 preceding siblings ...)
  2025-07-11 21:10 ` [PATCH v4 23/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-07-11 21:11 ` Zhuoying Cai
  2025-07-11 21:11 ` [PATCH v4 25/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
                   ` (3 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:11 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

If `-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>
---
 hw/s390x/ipl.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index d1a972ac8d..a196e1d648 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -437,6 +437,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;
@@ -494,6 +499,17 @@ 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);
+        }
         /*
          * Secure boot in audit mode will perform
          * if certificate(s) exist in the key store.
@@ -503,7 +519,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;
         }
 
-- 
2.49.0



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

* [PATCH v4 25/28] pc-bios/s390-ccw: Handle true secure IPL mode
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (23 preceding siblings ...)
  2025-07-11 21:11 ` [PATCH v4 24/28] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
@ 2025-07-11 21:11 ` Zhuoying Cai
  2025-07-11 21:11 ` [PATCH v4 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
                   ` (2 subsequent siblings)
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:11 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 pc-bios/s390-ccw/bootmap.c    | 16 +++++++++++++---
 pc-bios/s390-ccw/main.c       |  6 +++++-
 pc-bios/s390-ccw/s390-ccw.h   |  2 ++
 pc-bios/s390-ccw/secure-ipl.c |  4 ++++
 pc-bios/s390-ccw/secure-ipl.h |  3 +++
 5 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index eae8dfa113..b2a0fb2201 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -911,6 +911,9 @@ static int zipl_run(ScsiBlockPtr *pte)
     entry = (ComponentEntry *)(&header[1]);
 
     switch (boot_mode) {
+    case ZIPL_SECURE_INVALID_MODE:
+        return -1;
+    case ZIPL_SECURE_MODE:
     case ZIPL_SECURE_AUDIT_MODE:
         if (zipl_run_secure(entry, tmp_sec)) {
             return -1;
@@ -1283,9 +1286,16 @@ ZiplBootMode zipl_mode(uint8_t hdr_flags)
 {
     bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
     bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
+    VCStorageSizeBlock *vcssb;
 
     if (!sipl_set && iplir_set) {
         return ZIPL_SECURE_AUDIT_MODE;
+    } else if (sipl_set && iplir_set) {
+        vcssb = zipl_secure_get_vcssb();
+        if (vcssb == NULL || vcssb->length == 4) {
+            return ZIPL_SECURE_INVALID_MODE;
+        }
+        return ZIPL_SECURE_MODE;
     }
 
     return ZIPL_NORMAL_MODE;
@@ -1296,7 +1306,7 @@ void zipl_load(void)
     VDev *vdev = virtio_get_device();
 
     if (vdev->is_cdrom) {
-        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+        if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
             panic("Secure boot from ISO image is not supported!");
         }
         ipl_iso_el_torito();
@@ -1305,7 +1315,7 @@ void zipl_load(void)
     }
 
     if (virtio_get_device_type() == VIRTIO_ID_NET) {
-        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+        if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
             panic("Virtio net boot device does not support secure boot!");
         }
         netmain();
@@ -1318,7 +1328,7 @@ void zipl_load(void)
         return;
     }
 
-    if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+    if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
         panic("ECKD boot device does not support secure boot!");
     }
 
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 38962da1dd..3e17550854 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -277,10 +277,14 @@ static void ipl_boot_device(void)
         boot_mode = zipl_mode(iplb->hdr_flags);
     }
 
+    if (boot_mode == ZIPL_SECURE_INVALID_MODE) {
+        panic("Need at least one certificate for secure boot!");
+    }
+
     switch (cutype) {
     case CU_TYPE_DASD_3990:
     case CU_TYPE_DASD_2107:
-        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+        if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
             panic("Passthrough (vfio) device does not support secure boot!");
         }
 
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 85f92685f6..bf20efe88e 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -83,8 +83,10 @@ int virtio_read(unsigned long sector, void *load_addr);
 void zipl_load(void);
 
 typedef enum ZiplBootMode {
+    ZIPL_SECURE_INVALID_MODE = -1,
     ZIPL_NORMAL_MODE = 1,
     ZIPL_SECURE_AUDIT_MODE = 2,
+    ZIPL_SECURE_MODE = 3,
 } ZiplBootMode;
 
 extern ZiplBootMode boot_mode;
diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
index 2586c81d01..d7ca766058 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -264,6 +264,10 @@ static bool check_sclab_presence(uint8_t *sclab_magic,
         comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
 
         /* a missing SCLAB will not be reported in audit mode */
+        if (boot_mode == ZIPL_SECURE_MODE) {
+            zipl_secure_print("Magic is not matched. 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 d6c1a21a98..2e30b55437 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -86,6 +86,9 @@ static inline void zipl_secure_print(const char *message)
     case ZIPL_SECURE_AUDIT_MODE:
         IPL_check(false, message);
         break;
+    case ZIPL_SECURE_MODE:
+        IPL_assert(false, message);
+        break;
     default:
         break;
     }
-- 
2.49.0



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

* [PATCH v4 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (24 preceding siblings ...)
  2025-07-11 21:11 ` [PATCH v4 25/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
@ 2025-07-11 21:11 ` Zhuoying Cai
  2025-07-22 16:28   ` Daniel P. Berrangé
  2025-07-11 21:11 ` [PATCH v4 27/28] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
  2025-07-11 21:11 ` [PATCH v4 28/28] docs: Add secure IPL documentation Zhuoying Cai
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:11 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

The current approach to enabling secure boot relies on providing
-secure-boot and -boot-certificates options, 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>
---
 pc-bios/s390-ccw/bootmap.c  | 28 +++++++++-----
 pc-bios/s390-ccw/main.c     | 74 ++++++++++++++++++++++++++++++++++---
 pc-bios/s390-ccw/s390-ccw.h |  1 +
 3 files changed, 88 insertions(+), 15 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index b2a0fb2201..da4318313e 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -1301,23 +1301,35 @@ ZiplBootMode zipl_mode(uint8_t hdr_flags)
     return ZIPL_NORMAL_MODE;
 }
 
+int zipl_check_scsi_mbr_magic(void)
+{
+    ScsiMbr *mbr = (void *)sec;
+
+    /* Grab the MBR */
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    if (virtio_read(0, mbr)) {
+        puts("Cannot read block 0");
+        return -EIO;
+    }
+
+    if (!magic_match(mbr->magic, ZIPL_MAGIC)) {
+        return -1;
+    }
+
+    return 0;
+}
+
 void zipl_load(void)
 {
     VDev *vdev = virtio_get_device();
 
     if (vdev->is_cdrom) {
-        if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
-            panic("Secure boot from ISO image is not supported!");
-        }
         ipl_iso_el_torito();
         puts("Failed to IPL this ISO image!");
         return;
     }
 
     if (virtio_get_device_type() == VIRTIO_ID_NET) {
-        if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
-            panic("Virtio net boot device does not support secure boot!");
-        }
         netmain();
         puts("Failed to IPL from this network!");
         return;
@@ -1328,10 +1340,6 @@ void zipl_load(void)
         return;
     }
 
-    if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
-        panic("ECKD boot device does not support secure boot!");
-    }
-
     switch (virtio_get_device_type()) {
     case VIRTIO_ID_BLOCK:
         zipl_load_vblk();
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 3e17550854..ffb87e264b 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -271,8 +271,43 @@ static int virtio_setup(void)
     return ret;
 }
 
-static void ipl_boot_device(void)
+static void validate_secure_boot_device(void)
+{
+    switch (cutype) {
+    case CU_TYPE_DASD_3990:
+    case CU_TYPE_DASD_2107:
+        panic("Passthrough (vfio) device does not support secure boot!");
+        break;
+    case CU_TYPE_VIRTIO:
+        if (virtio_setup() == 0) {
+            VDev *vdev = virtio_get_device();
+
+            if (vdev->is_cdrom) {
+                panic("Secure boot from ISO image is not supported!");
+            }
+
+            if (virtio_get_device_type() == VIRTIO_ID_NET) {
+                panic("Virtio net boot device does not support secure boot!");
+            }
+
+            if (zipl_check_scsi_mbr_magic()) {
+                panic("ECKD boot device does not support secure boot!");
+            }
+        }
+        break;
+    default:
+        panic("Secure boot from unexpected device type is not supported!");
+    }
+
+    printf("SCSI boot device supports secure boot.\n");
+}
+
+static void check_secure_boot_support(void)
 {
+    bool have_iplb_copy;
+    IplParameterBlock *iplb_copy;
+    QemuIplParameters *qipl_copy;
+
     if (boot_mode == 0) {
         boot_mode = zipl_mode(iplb->hdr_flags);
     }
@@ -281,13 +316,40 @@ static void ipl_boot_device(void)
         panic("Need at least one certificate for secure boot!");
     }
 
+    if (boot_mode == ZIPL_NORMAL_MODE) {
+        return;
+    }
+
+    /*
+     * Store copies of have_iplb, iplb and qipl.
+     * They will be updated in load_next_iplb().
+     */
+    have_iplb_copy = have_iplb;
+    iplb_copy = malloc(sizeof(IplParameterBlock));
+    qipl_copy = malloc(sizeof(QemuIplParameters));
+
+    memcpy(qipl_copy, &qipl, sizeof(QemuIplParameters));
+    memcpy(iplb_copy, iplb, sizeof(IplParameterBlock));
+
+    while (have_iplb_copy) {
+        if (have_iplb_copy && find_boot_device()) {
+            validate_secure_boot_device();
+        }
+        have_iplb_copy = load_next_iplb();
+    }
+
+    memcpy(&qipl, qipl_copy, sizeof(QemuIplParameters));
+    memcpy(iplb, iplb_copy, sizeof(IplParameterBlock));
+
+    free(qipl_copy);
+    free(iplb_copy);
+}
+
+static void ipl_boot_device(void)
+{
     switch (cutype) {
     case CU_TYPE_DASD_3990:
     case CU_TYPE_DASD_2107:
-        if (boot_mode == ZIPL_SECURE_AUDIT_MODE || boot_mode == ZIPL_SECURE_MODE) {
-            panic("Passthrough (vfio) device does not support secure boot!");
-        }
-
         dasd_ipl(blk_schid, cutype);
         break;
     case CU_TYPE_VIRTIO:
@@ -337,6 +399,8 @@ void main(void)
         probe_boot_device();
     }
 
+    check_secure_boot_support();
+
     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 bf20efe88e..a26233afac 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -92,6 +92,7 @@ typedef enum ZiplBootMode {
 extern ZiplBootMode boot_mode;
 
 ZiplBootMode zipl_mode(uint8_t hdr_flags);
+int zipl_check_scsi_mbr_magic(void);
 
 /* jump2ipl.c */
 void write_reset_psw(uint64_t psw);
-- 
2.49.0



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

* [PATCH v4 27/28] hw/s390x/ipl: Handle secure boot without specifying a boot device
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (25 preceding siblings ...)
  2025-07-11 21:11 ` [PATCH v4 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
@ 2025-07-11 21:11 ` Zhuoying Cai
  2025-07-11 21:11 ` [PATCH v4 28/28] docs: Add secure IPL documentation Zhuoying Cai
  27 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:11 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

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>
---
 hw/s390x/ipl.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index a196e1d648..da50b52c75 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -764,6 +764,16 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
         s390_ipl_create_cert_store(&ipl->cert_store);
         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.49.0



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

* [PATCH v4 28/28] docs: Add secure IPL documentation
  2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (26 preceding siblings ...)
  2025-07-11 21:11 ` [PATCH v4 27/28] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
@ 2025-07-11 21:11 ` Zhuoying Cai
  2025-07-21 19:13   ` Collin Walling
  27 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-11 21:11 UTC (permalink / raw)
  To: thuth, berrange, richard.henderson, david, pbonzini, jrossi,
	qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
	zycai

Add documentation for secure IPL

Signed-off-by: Collin Walling <walling@linux.ibm.com>
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 docs/specs/s390x-secure-ipl.rst  | 159 +++++++++++++++++++++++++++++++
 docs/system/s390x/secure-ipl.rst | 156 ++++++++++++++++++++++++++++++
 2 files changed, 315 insertions(+)
 create mode 100644 docs/specs/s390x-secure-ipl.rst
 create mode 100644 docs/system/s390x/secure-ipl.rst

diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
new file mode 100644
index 0000000000..1ff69092e2
--- /dev/null
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -0,0 +1,159 @@
+.. 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 userspace, 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 "userspace" will refer to the s390-ccw BIOS unless stated
+otherwise.
+
+Both QEMU and userspace work in tandem 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 userspace to query
+the certificate store info and retrieve specific certificates from QEMU.
+DIAGNOSE 'X'508' is used by userspace to leverage qcrypto libraries to
+perform signature-verification in QEMU. Lastly, userspace 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:
+
+- Userspace reads data payload from disk (e.g. stage3 boot loader, kernel)
+- Userspace checks the validity of the SCLAB
+- Userspace 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
+
+- Userspace 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)
+
+- Userspace appends IIRB at the end of the IPLB
+- Userspace kicks off IPL
+
+More information regarding the respective DIAGNOSE commands and IPL data
+structures are outlined within this document.
+
+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.
+
+DIAGNOSE function code 'X'320' - Certificate Store Facility
+-----------------------------------------------------------
+
+DIAGNOSE 'X'320' is used to provide support for userspace to directly
+query the s390 certificate store. Userspace 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 sucodes 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).
+
+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 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.
+
+    VCE contains various information of a VC from the CS.
+
+
+Secure IPL Data Structures, Facilities, and Functions
+=====================================================
+
+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
+
+
+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. Specifically, it may:
+
+* Provide direction on how to process the rest of the component.
+
+* Provide further validation of information on where to load the signed binary code
+  from the load device.
+
+* Specify where to start the execution of the loaded OS code.
+
+
+DIAGNOSE function code 'X'508' - KVM IPL extensions
+---------------------------------------------------
+
+DIAGNOSE 'X'508' is reserved for KVM guest use in order to facilitate
+communication of additional IPL operations that cannot be handled by userspace,
+such as signature verification for secure IPL.
+
+If the function code specifies 0x508, KVM 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.
+
+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.
diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
new file mode 100644
index 0000000000..83223ddd7c
--- /dev/null
+++ b/docs/system/s390x/secure-ipl.rst
@@ -0,0 +1,156 @@
+.. 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
+new 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
+===============================
+
+New parameters have been introduced to s390-ccw-virtio machine type option
+to support 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 comma-delimited list of
+certificates on the command-line:
+
+.. code-block:: shell
+
+    qemu-system-s390x -machine s390-ccw-virtio, \
+    boot-certificates=/.../qemu/certs:/another/path/cert.pem
+
+Enabling Secure IPL
+-------------------
+
+Different IPL modes may be toggled with the following command line option:
+
+.. code-block:: shell
+
+    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on|off
+
+Additionally, the provision of certificates affect the mode.
+
+
+IPL Modes
+=========
+
+Normal 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``.
+
+
+Audit Mode
+----------
+
+With *only* the presence of certificates in the store, it is assumed that secure
+boot operations should be performed with errors reported as warnings. As such,
+the secure IPL operations will be performed, and any errors that stem from these
+operations will report a warning via the SCLP console.
+
+
+Secure Mode
+-----------
+
+With *both* the presence of certificates in the store and the ``secure-boot=on``
+option, it is understood that secure boot should be performed with errors
+reported and boot will abort.
+
+
+Constraints
+===========
+
+The following constraints apply when attempting to secure IPL an s390 guest:
+
+- z16 CPU model
+- certificates must be in X.509 PEM format
+- only sha256 encryption is supported
+- 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.,
+  Net) 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 keypt 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 KVM 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
+
+Start Guest with Cmd Options
+----------------------------
+
+.. code-block:: shell
+
+    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certificates=cert.pem ...
-- 
2.49.0



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

* Re: [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-07-11 21:10 ` [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2025-07-11 22:50   ` Collin Walling
  2025-07-14 14:54     ` Jared Rossi
  2025-07-21 21:50     ` Zhuoying Cai
  0 siblings, 2 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-11 22:50 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 5:10 PM, 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>

I would suggest encapsulating all secure-boot related functions into the
new secure-ipl.c|h files to help keep bootmap.c less bloated.  I would
suggest moving the following to the new file:

- zipl_handle_sig_entry
- handle_certificate
- zipl_run_secure (this should probably be the only non-static func,
since it'll be used by bootmap.c, and you'll probably need to mess with
the levels of indirection for the params)

Of course, make all functions used by zipl_run_secure as static since
they'll only be used there.

I'll revist this patch later to verify the SCLP bits and triple-check
the data structure layouts.

More comments below:

> ---
>  pc-bios/s390-ccw/Makefile     |   3 +-
>  pc-bios/s390-ccw/bootmap.c    | 193 +++++++++++++++++++++++++++++++++-
>  pc-bios/s390-ccw/bootmap.h    |   9 ++
>  pc-bios/s390-ccw/main.c       |   9 ++
>  pc-bios/s390-ccw/s390-ccw.h   |  14 +++
>  pc-bios/s390-ccw/sclp.c       |  44 ++++++++
>  pc-bios/s390-ccw/sclp.h       |   6 ++
>  pc-bios/s390-ccw/secure-ipl.c | 190 +++++++++++++++++++++++++++++++++
>  pc-bios/s390-ccw/secure-ipl.h |  98 +++++++++++++++++
>  9 files changed, 563 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/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
> index dc69dd484f..fedb89a387 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 2513e6c131..0827459803 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -8,6 +8,7 @@
>   * directory.
>   */
>  
> +#include <stdlib.h>
>  #include <string.h>
>  #include <stdio.h>
>  #include "s390-ccw.h"
> @@ -15,6 +16,7 @@
>  #include "bootmap.h"
>  #include "virtio.h"
>  #include "bswap.h"
> +#include "secure-ipl.h"
>  
>  #ifdef DEBUG
>  /* #define DEBUG_FALLBACK */
> @@ -34,6 +36,9 @@ static uint8_t sec[MAX_SECTOR_SIZE*4] __attribute__((__aligned__(PAGE_SIZE)));
>  const uint8_t el_torito_magic[] = "EL TORITO SPECIFICATION"
>                                    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
>  
> +/* sector for storing signatures */
> +static uint8_t sig_sec[MAX_SECTOR_SIZE] __attribute__((__aligned__(PAGE_SIZE)));

Since only zipl_secure_run is making use of this scratch space, I'd
suggest declaring some chunk of mem within that function and then
passing it as a parameter to zipl_handle_sig_entry.  It'll also make it
a lot easier to understand what is going on in the
ZIPL_COMP_ENTRY_SIGNATURE case.

> +
>  /*
>   * Match two CCWs located after PSW and eight filler bytes.
>   * From libmagic and arch/s390/kernel/head.S.
> @@ -676,6 +681,159 @@ static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
>      return comp_len;
>  }
>  
> +static uint32_t zipl_handle_sig_entry(ComponentEntry *entry)

I think "zipl_load_signature" reads better.

> +{
> +    uint32_t sig_len;
> +
> +    if (zipl_load_segment(entry, (uint64_t)sig_sec) < 0) {
> +        return -1;
> +    }
> +
> +    if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {
> +        puts("Signature is not in DER format");
> +        return -1;
> +    }
> +    sig_len = entry->compdat.sig_info.sig_len;
> +
> +    return sig_len;
> +}
> +
> +static int handle_certificate(int *cert_table, uint64_t **cert,
> +                             uint64_t cert_len, uint8_t cert_idx,
> +                             IplSignatureCertificateList *certs, int cert_index)
> +{
> +    bool unused;
> +
> +    unused = cert_table[cert_idx] == -1;
> +    if (unused) {
> +        if (zipl_secure_request_certificate(*cert, cert_idx)) {
> +            zipl_secure_cert_list_add(certs, cert_index, *cert, cert_len);
> +            cert_table[cert_idx] = cert_index;
> +            *cert += cert_len;
> +        } else {
> +            puts("Could not get certificate");
> +            return -1;
> +        }
> +
> +        /* increment cert_index for the next cert entry */
> +        return ++cert_index;
> +    }
> +
> +    return cert_index;
> +}
> +
> +static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
> +{
> +    IplDeviceComponentList comps;
> +    IplSignatureCertificateList certs;
> +    uint64_t *cert = NULL;
> +    int cert_index = 0;
> +    int comp_index = 0;
> +    uint64_t comp_addr;
> +    int comp_len;
> +    bool have_sig;
> +    uint32_t sig_len;
> +    uint64_t cert_len = -1;
> +    uint8_t cert_idx = -1;
> +    bool verified;
> +    uint32_t certs_len;
> +    /*
> +     * Store indices of cert entry that have already used for signature verification
> +     * to prevent allocating the same certificate multiple times.
> +     * cert_table index: index of certificate from qemu cert store used for verification
> +     * cert_table value: index of cert entry in cert list that contains the certificate
> +     */
> +    int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
> +    int signed_count = 0;
> +
> +    if (!zipl_secure_ipl_supported()) {
> +        return -1;
> +    }
> +
> +    zipl_secure_init_lists(&comps, &certs);
> +    certs_len = zipl_secure_get_certs_length();
> +    cert = malloc(certs_len);
> +
> +    have_sig = false;

You can get rid of this boolean and simply use sig_len as the indicator
that there were nonzero bytes read when a signature entry was detected
in the previous loop.  Where you set have_sig to false below, you can
set sig_len to 0 to reset it.

> +    while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
> +           entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
> +

I'll be honest, I'm not a big fan of neither the design of this while
loop nor the one in zipl_run_normal.  I'd prefer something like:

while (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {

    // sanity checking

    switch (entry->component_type) {
    case ZIPL_COMP_ENTRY_SIGNATURE:
        ...
        break;
    case ZIPL_COMP_ENTRY_LOAD:
        ...
        break;
    default:
        // Unrecognized entry type, return error
    }

    entry++;
}

The above makes it clear that we're loading in segments until the exec
entry is found, and the handling for each recognized component type is
clearly labeled and organized.

I won't speak for making this change to the zipl_run_normal function
yet, as it may introduce too many changes in this patch series.

> +        if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
> +            /* There should never be two signatures in a row */
> +            if (have_sig) {
> +                return -1;
> +            }
> +
> +            sig_len = zipl_handle_sig_entry(entry);
> +            if (sig_len < 0) {
> +                return -1;
> +            }
> +
> +            have_sig = true;
> +        } else {
> +            comp_addr = entry->compdat.load_addr;
> +            comp_len = zipl_load_segment(entry, comp_addr);
> +            if (comp_len < 0) {
> +                return -1;
> +            }
> +
> +            if (have_sig) {

If you use my idea to re-write this loop, you'll be able to reduce one
level of indentation of the code that follows by checking the inverse
condition:

if (!have_sig) { // or if (!sig_len)
    break;

> +                verified = verify_signature(comp_len, comp_addr,
> +                                            sig_len, (uint64_t)sig_sec,
> +                                            &cert_len, &cert_idx);
> +
> +                if (verified) {
> +                    cert_index = handle_certificate(cert_table, &cert,
> +                                                    cert_len, cert_idx,
> +                                                    &certs, cert_index);
> +                    if (cert_index == -1) {
> +                        return -1;
> +                    }
> +
> +                    puts("Verified component");
> +                    zipl_secure_comp_list_add(&comps, comp_index, cert_table[cert_idx],
> +                                              comp_addr, comp_len,
> +                                              S390_IPL_COMPONENT_FLAG_SC |
> +                                              S390_IPL_COMPONENT_FLAG_CSV);
> +                } else {
> +                    zipl_secure_comp_list_add(&comps, comp_index, -1,
> +                                              comp_addr, comp_len,
> +                                              S390_IPL_COMPONENT_FLAG_SC);
> +                    zipl_secure_print("Could not verify component");
> +                }
> +
> +                comp_index++;
> +                signed_count += 1;
> +                /* After a signature is used another new one can be accepted */
> +                have_sig = false;
> +            }
> +        }
> +
> +        entry++;
> +
> +        if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
> +            puts("Wrong entry value");
> +            return -EINVAL;
> +        }

Can someone who is more informed than I am of the IPL process please
explain to me what is the purpose of the above check?  Why does it check
if the next entry, the one which isn't going to be inspected/loaded, is
within the bounds of tmp_sec?  This has been here since this file's
inception and I can't find any documentation or mention that supports it.

This code precludes any of the secure IPL changes.

Was this actually meant to be entry[0] to ensure the actual entry we
want to work on is not outside the bounds of tmp_sec?  Or perhaps it was
meant to be done before the increment to entry?

> +    }
> +
> +    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
> +        puts("No EXEC entry");
> +        return -EINVAL;
> +    }
> +
> +    if (signed_count == 0) {
> +        zipl_secure_print("Secure boot is on, but components are not signed");
> +    }

Shouldn't this be handled within the loop?  If it gets to a LOAD type
without a signature, audit mode should report and move on.  Secure mode
should report and abort on the spot.  Then you can get rid of signed_count.

I'll get back to reviewing patch 22 wrt to this variable's usage later on...

> +
> +    if (zipl_secure_update_iirb(&comps, &certs)) {
> +        zipl_secure_print("Failed to write IPL Information Report Block");
> +    }
> +    write_reset_psw(entry->compdat.load_psw);
> +
> +    return 0;
> +}
> +
>  static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
>  {
>      while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
> @@ -735,8 +893,17 @@ static int zipl_run(ScsiBlockPtr *pte)
>      /* Load image(s) into RAM */
>      entry = (ComponentEntry *)(&header[1]);
>  
> -    if (zipl_run_normal(entry, tmp_sec)) {
> -        return -1;
> +    switch (boot_mode) {
> +    case ZIPL_SECURE_AUDIT_MODE:
> +        if (zipl_run_secure(entry, tmp_sec)) {
> +            return -1;
> +        }
> +        break;
> +    case ZIPL_NORMAL_MODE:
> +        if (zipl_run_normal(entry, tmp_sec)) {
> +            return -1;
> +        }
> +        break;
>      }
>  
>      /* should not return */
> @@ -1095,17 +1262,35 @@ static int zipl_load_vscsi(void)
>   * IPL starts here
>   */
>  
> +ZiplBootMode zipl_mode(uint8_t hdr_flags)
> +{
> +    bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
> +    bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
> +
> +    if (!sipl_set && iplir_set) {
> +        return ZIPL_SECURE_AUDIT_MODE;
> +    }
> +
> +    return ZIPL_NORMAL_MODE;
> +}
> +
>  void zipl_load(void)
>  {
>      VDev *vdev = virtio_get_device();
>  
>      if (vdev->is_cdrom) {
> +        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
> +            panic("Secure boot from ISO image is not supported!");
> +        }
>          ipl_iso_el_torito();
>          puts("Failed to IPL this ISO image!");
>          return;
>      }
>  
>      if (virtio_get_device_type() == VIRTIO_ID_NET) {
> +        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
> +            panic("Virtio net boot device does not support secure boot!");
> +        }
>          netmain();
>          puts("Failed to IPL from this network!");
>          return;
> @@ -1116,6 +1301,10 @@ void zipl_load(void)
>          return;
>      }
>  
> +    if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
> +        panic("ECKD boot device does not support secure boot!");
> +    }
> +
>      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..e48823a835 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;
> +} __attribute__((packed)) SignatureInformation;
> +
>  typedef union ComponentEntryData {
>      uint64_t load_psw;
>      uint64_t load_addr;
> +    SignatureInformation sig_info;
>  } ComponentEntryData;
>  
>  typedef struct ComponentEntry {
> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
> index c9328f1c51..38962da1dd 100644
> --- a/pc-bios/s390-ccw/main.c
> +++ b/pc-bios/s390-ccw/main.c
> @@ -28,6 +28,7 @@ IplParameterBlock *iplb;
>  bool have_iplb;
>  static uint16_t cutype;
>  LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */
> +ZiplBootMode boot_mode;
>  
>  #define LOADPARM_PROMPT "PROMPT  "
>  #define LOADPARM_EMPTY  "        "
> @@ -272,9 +273,17 @@ static int virtio_setup(void)
>  
>  static void ipl_boot_device(void)
>  {
> +    if (boot_mode == 0) {
> +        boot_mode = zipl_mode(iplb->hdr_flags);
> +    }
> +
>      switch (cutype) {
>      case CU_TYPE_DASD_3990:
>      case CU_TYPE_DASD_2107:
> +        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
> +            panic("Passthrough (vfio) device does not support secure boot!");
> +        }
> +
>          dasd_ipl(blk_schid, cutype);
>          break;
>      case CU_TYPE_VIRTIO:
> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
> index 6cdce3e5e5..648f407dc5 100644
> --- a/pc-bios/s390-ccw/s390-ccw.h
> +++ b/pc-bios/s390-ccw/s390-ccw.h
> @@ -39,6 +39,9 @@ typedef unsigned long long u64;
>  #define MIN_NON_ZERO(a, b) ((a) == 0 ? (b) : \
>                              ((b) == 0 ? (a) : (MIN(a, b))))
>  #endif
> +#ifndef ROUND_UP
> +#define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d)))
> +#endif
>  
>  #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
>  
> @@ -64,6 +67,8 @@ void sclp_print(const char *string);
>  void sclp_set_write_mask(uint32_t receive_mask, uint32_t send_mask);
>  void sclp_setup(void);
>  void sclp_get_loadparm_ascii(char *loadparm);
> +bool sclp_is_diag320_on(void);
> +bool sclp_is_sipl_on(void);
>  int sclp_read(char *str, size_t count);
>  
>  /* virtio.c */
> @@ -76,6 +81,15 @@ int virtio_read(unsigned long sector, void *load_addr);
>  /* bootmap.c */
>  void zipl_load(void);
>  
> +typedef enum ZiplBootMode {
> +    ZIPL_NORMAL_MODE = 1,
> +    ZIPL_SECURE_AUDIT_MODE = 2,
> +} ZiplBootMode;

These should be ZIPL_BOOT_MODE_*

Also, is there a reason why the list starts at 1 and not defaulting to
the implicit 0?

> +
> +extern ZiplBootMode boot_mode;
> +
> +ZiplBootMode zipl_mode(uint8_t hdr_flags);
> +
>  /* jump2ipl.c */
>  void write_reset_psw(uint64_t psw);
>  int jump_to_IPL_code(uint64_t address);
> diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
> index 4a07de018d..0b03c3164f 100644
> --- a/pc-bios/s390-ccw/sclp.c
> +++ b/pc-bios/s390-ccw/sclp.c
> @@ -113,6 +113,50 @@ void sclp_get_loadparm_ascii(char *loadparm)
>      }
>  }
>  
> +static void sclp_get_fac134(uint8_t *fac134)
> +{
> +
> +    ReadInfo *sccb = (void *)_sccb;
> +
> +    memset((char *)_sccb, 0, sizeof(ReadInfo));
> +    sccb->h.length = SCCB_SIZE;
> +    if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
> +        *fac134 = sccb->fac134;
> +    }
> +}
> +
> +bool sclp_is_diag320_on(void)
> +{
> +    uint8_t fac134 = 0;
> +
> +    sclp_get_fac134(&fac134);
> +    return fac134 & SCCB_FAC134_DIAG320_BIT;
> +}
> +
> +/*
> + * Get fac_ipl (byte 136 and byte 137 of the SCLP Read Info block)
> + * for IPL device facilities.
> + */
> +static void sclp_get_fac_ipl(uint16_t *fac_ipl)
> +{
> +
> +    ReadInfo *sccb = (void *)_sccb;
> +
> +    memset((char *)_sccb, 0, sizeof(ReadInfo));
> +    sccb->h.length = SCCB_SIZE;
> +    if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {
> +        *fac_ipl = sccb->fac_ipl;
> +    }
> +}
> +
> +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..4b8321e401
> --- /dev/null
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -0,0 +1,190 @@
> +/*
> + * S/390 Secure IPL
> + *
> + * Copyright 2025 IBM Corp.
> + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */

Let's take the opportunity to add some meaningful content to the file
header.  Something like:

/*
 * 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
 */

No need to put in the .h

> +
> +#include <string.h>
> +#include <stdio.h>
> +#include "s390-ccw.h"
> +#include "secure-ipl.h"
> +
> +uint8_t vcb_data[MAX_SECTOR_SIZE * 4] __attribute__((__aligned__(PAGE_SIZE)));
> +uint8_t vcssb_data[VCSSB_MAX_LEN] __attribute__((__aligned__(PAGE_SIZE)));
> +
> +VCStorageSizeBlock *zipl_secure_get_vcssb(void)
> +{
> +    VCStorageSizeBlock *vcssb;
> +    int rc;
> +
> +    vcssb = (VCStorageSizeBlock *)vcssb_data;
> +    /* avoid retrieving vcssb multiple times */
> +    if (vcssb->length == VCSSB_MAX_LEN) {
> +        return vcssb;
> +    }
> +
> +    rc = diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI);
> +    if (rc != DIAG_320_RC_OK) {
> +        return NULL;
> +    }
> +
> +    return vcssb;
> +}
> +
> +uint32_t zipl_secure_get_certs_length(void)
> +{
> +    VCStorageSizeBlock *vcssb;
> +    uint32_t len;
> +
> +    vcssb = zipl_secure_get_vcssb();
> +    if (vcssb == NULL) {
> +        return 0;
> +    }
> +
> +    len = vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct * VCE_HEADER_LEN;
> +
> +    return len;
> +}
> +
> +uint32_t zipl_secure_request_certificate(uint64_t *cert, uint8_t index)
> +{
> +    VCStorageSizeBlock *vcssb;
> +    VCBlock *vcb;
> +    VCEntry *vce;
> +    uint64_t rc = 0;
> +    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 + 1;
> +    vcb->last_vc_index = index + 1;
> +
> +    rc = diag320(vcb, DIAG_320_SUBC_STORE_VC);
> +    if (rc == DIAG_320_RC_OK) {
> +        vce = (VCEntry *)vcb->vce_buf;
> +        cert_len = vce->cert_len;
> +        memcpy(cert, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
> +        /* clear out region for next cert(s) */
> +        memcpy(vcb_data, 0, sizeof(vcb_data));
> +    }
> +
> +    return cert_len;
> +}
> +
> +void zipl_secure_cert_list_add(IplSignatureCertificateList *certs, int cert_index,
> +                               uint64_t *cert, uint64_t cert_len)
> +{
> +    if (cert_index > MAX_CERTIFICATES - 1) {
> +        printf("Warning: Ignoring cert entry [%d] because it's over %d entires\n",
> +                cert_index + 1, MAX_CERTIFICATES);
> +        return;
> +    }
> +
> +    certs->cert_entries[cert_index].addr = (uint64_t)cert;
> +    certs->cert_entries[cert_index].len = cert_len;
> +    certs->ipl_info_header.len += sizeof(certs->cert_entries[cert_index]);
> +}
> +
> +void zipl_secure_comp_list_add(IplDeviceComponentList *comps, 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 it's over %d entires\n",
> +                comp_index + 1, MAX_CERTIFICATES);
> +        return;
> +    }
> +
> +    comps->device_entries[comp_index].addr = comp_addr;
> +    comps->device_entries[comp_index].len = comp_len;
> +    comps->device_entries[comp_index].flags = flags;
> +    comps->device_entries[comp_index].cert_index = cert_index;
> +    comps->ipl_info_header.len += sizeof(comps->device_entries[comp_index]);
> +}
> +
> +int zipl_secure_update_iirb(IplDeviceComponentList *comps,
> +                            IplSignatureCertificateList *certs)
> +{
> +    IplInfoReportBlock *iirb;
> +    IplDeviceComponentList *iirb_comps;
> +    IplSignatureCertificateList *iirb_certs;
> +    uint32_t iirb_hdr_len;
> +    uint32_t comps_len;
> +    uint32_t certs_len;
> +
> +    if (iplb->len % 8 != 0) {
> +        panic("IPL parameter block length field value is not multiple of 8 bytes");
> +    }
> +
> +    iirb_hdr_len = sizeof(IplInfoReportBlockHeader);
> +    comps_len = comps->ipl_info_header.len;
> +    certs_len = certs->ipl_info_header.len;
> +    if ((comps_len + certs_len + iirb_hdr_len) > sizeof(IplInfoReportBlock)) {
> +        puts("Not enough space to hold all components and certificates in IIRB");
> +        return -1;
> +    }
> +
> +    /* IIRB immediately follows IPLB */
> +    iirb = &ipl_data.iirb;
> +    iirb->hdr.len = iirb_hdr_len;
> +
> +    /* Copy IPL device component list after IIRB Header */
> +    iirb_comps = (IplDeviceComponentList *) iirb->info_blks;
> +    memcpy(iirb_comps, comps, comps_len);
> +
> +    /* Update IIRB length */
> +    iirb->hdr.len += comps_len;
> +
> +    /* Copy IPL sig cert list after IPL device component list */
> +    iirb_certs = (IplSignatureCertificateList *) (iirb->info_blks +
> +                                                  iirb_comps->ipl_info_header.len);
> +    memcpy(iirb_certs, certs, certs_len);
> +
> +    /* Update IIRB length */
> +    iirb->hdr.len += certs_len;
> +
> +    return 0;
> +}
> +
> +bool zipl_secure_ipl_supported(void)
> +{
> +    if (!sclp_is_sipl_on()) {
> +        puts("Secure IPL Facility is not supported by the hypervisor!");
> +        return false;
> +    }
> +
> +    if (!is_secure_ipl_extension_supported()) {
> +        puts("Secure IPL extensions are not supported by the hypervisor!");
> +        return false;
> +    }
> +
> +    if (!(sclp_is_diag320_on() && is_cert_store_facility_supported())) {
> +        puts("Certificate Store Facility is not supported by the hypervisor!");
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +void zipl_secure_init_lists(IplDeviceComponentList *comps,
> +                            IplSignatureCertificateList *certs)
> +{
> +    comps->ipl_info_header.ibt = IPL_IBT_COMPONENTS;
> +    comps->ipl_info_header.len = sizeof(comps->ipl_info_header);
> +
> +    certs->ipl_info_header.ibt = IPL_IBT_CERTIFICATES;
> +    certs->ipl_info_header.len = sizeof(certs->ipl_info_header);
> +}
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> new file mode 100644
> index 0000000000..b9052e8c78
> --- /dev/null
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -0,0 +1,98 @@
> +/*
> + * 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);
> +uint32_t zipl_secure_get_certs_length(void);
> +uint32_t zipl_secure_request_certificate(uint64_t *cert, uint8_t index);
> +void zipl_secure_cert_list_add(IplSignatureCertificateList *certs, int cert_index,
> +                               uint64_t *cert, uint64_t cert_len);
> +void zipl_secure_comp_list_add(IplDeviceComponentList *comps, int comp_index,
> +                               int cert_index, uint64_t comp_addr,
> +                               uint64_t comp_len, uint8_t flags);
> +int zipl_secure_update_iirb(IplDeviceComponentList *comps,
> +                            IplSignatureCertificateList *certs);
> +bool zipl_secure_ipl_supported(void);
> +void zipl_secure_init_lists(IplDeviceComponentList *comps,
> +                            IplSignatureCertificateList *certs);
> +
> +static inline void zipl_secure_print(const char *message)

This name is a bit misleading, since it will do something more sinister
in a later patch when secure mode is introduced: it will assert and
abort IPL... not something I'd expect a "print" function to do ;)

Perhaps something like zipl_secure_check... or something better... I
just don't think "print" makes sense.

> +{
> +    switch (boot_mode) {
> +    case ZIPL_SECURE_AUDIT_MODE:
> +        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)
> +{
> +    uint64_t d320_ism;
> +
> +    diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
> +    return (d320_ism & DIAG_320_ISM_QUERY_VCSI) &&
> +           (d320_ism & 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_secure_ipl_extension_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)
> +{
> +    Diag508SignatureVerificationBlock svb = {{}, comp_len, comp_addr,
> +                                             sig_len, sig_addr };
> +
> +    if (_diag508(&svb, DIAG_508_SUBC_SIG_VERIF) == DIAG_508_RC_OK) {
> +        *cert_len = svb.csi.len;
> +        *cert_idx = svb.csi.idx;
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +#endif /* _PC_BIOS_S390_CCW_SECURE_IPL_H */


-- 
Regards,
  Collin


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

* Re: [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-07-11 22:50   ` Collin Walling
@ 2025-07-14 14:54     ` Jared Rossi
  2025-07-14 15:34       ` Thomas Huth
  2025-07-21 21:50     ` Zhuoying Cai
  1 sibling, 1 reply; 62+ messages in thread
From: Jared Rossi @ 2025-07-14 14:54 UTC (permalink / raw)
  To: Collin Walling, Zhuoying Cai, thuth, berrange, richard.henderson,
	david, pbonzini, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii


[snip...]
>> +
>> +        entry++;
>> +
>> +        if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
>> +            puts("Wrong entry value");
>> +            return -EINVAL;
>> +        }
> Can someone who is more informed than I am of the IPL process please
> explain to me what is the purpose of the above check?  Why does it check
> if the next entry, the one which isn't going to be inspected/loaded, is
> within the bounds of tmp_sec?  This has been here since this file's
> inception and I can't find any documentation or mention that supports it.
>
> This code precludes any of the secure IPL changes.
>
> Was this actually meant to be entry[0] to ensure the actual entry we
> want to work on is not outside the bounds of tmp_sec?  Or perhaps it was
> meant to be done before the increment to entry?
>

I noticed that as well and came to the same conclusions as you, which is 
to say,
it has always been that way and it is not clear what the purpose is, but 
it does
not appear to have any impact on the proposed secure IPL functionality.  
I agree
that it seems somehow strange, but I believe any changes/fixes would be 
outside
the scope of this item.

In my opinion, since this is already a large patch series, it is better 
to leave
it alone for now unless we find a compelling reason to change it 
immediately.

Regards,
Jared Rossi


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

* Re: [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-07-14 14:54     ` Jared Rossi
@ 2025-07-14 15:34       ` Thomas Huth
  2025-07-14 17:46         ` Collin Walling
  0 siblings, 1 reply; 62+ messages in thread
From: Thomas Huth @ 2025-07-14 15:34 UTC (permalink / raw)
  To: Jared Rossi, Collin Walling, Zhuoying Cai, berrange,
	richard.henderson, david, pbonzini, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 14/07/2025 16.54, Jared Rossi wrote:
> 
> [snip...]
>>> +
>>> +        entry++;
>>> +
>>> +        if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
>>> +            puts("Wrong entry value");
>>> +            return -EINVAL;
>>> +        }
>> Can someone who is more informed than I am of the IPL process please
>> explain to me what is the purpose of the above check?  Why does it check
>> if the next entry, the one which isn't going to be inspected/loaded, is
>> within the bounds of tmp_sec?  This has been here since this file's
>> inception and I can't find any documentation or mention that supports it.
>>
>> This code precludes any of the secure IPL changes.
>>
>> Was this actually meant to be entry[0] to ensure the actual entry we
>> want to work on is not outside the bounds of tmp_sec?  Or perhaps it was
>> meant to be done before the increment to entry?
>>
> 
> I noticed that as well and came to the same conclusions as you, which is to 
> say,
> it has always been that way and it is not clear what the purpose is, but it 
> does
> not appear to have any impact on the proposed secure IPL functionality.

I think it's meant as a check for the *end* of entry[0], so it's likely just 
a quirky way of saying:

    if (((uint8_t *)entry) + sizeof(*entry) > tmp_sec + MAX_SECTOR_SIZE)

?

  Thomas



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

* Re: [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-07-14 15:34       ` Thomas Huth
@ 2025-07-14 17:46         ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-14 17:46 UTC (permalink / raw)
  To: Thomas Huth, Jared Rossi, Zhuoying Cai, berrange,
	richard.henderson, david, pbonzini, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/14/25 11:34 AM, Thomas Huth wrote:
> On 14/07/2025 16.54, Jared Rossi wrote:
>>
>> [snip...]
>>>> +
>>>> +        entry++;
>>>> +
>>>> +        if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
>>>> +            puts("Wrong entry value");
>>>> +            return -EINVAL;
>>>> +        }
>>> Can someone who is more informed than I am of the IPL process please
>>> explain to me what is the purpose of the above check?  Why does it check
>>> if the next entry, the one which isn't going to be inspected/loaded, is
>>> within the bounds of tmp_sec?  This has been here since this file's
>>> inception and I can't find any documentation or mention that supports it.
>>>
>>> This code precludes any of the secure IPL changes.
>>>
>>> Was this actually meant to be entry[0] to ensure the actual entry we
>>> want to work on is not outside the bounds of tmp_sec?  Or perhaps it was
>>> meant to be done before the increment to entry?
>>>
>>
>> I noticed that as well and came to the same conclusions as you, which is to 
>> say,
>> it has always been that way and it is not clear what the purpose is, but it 
>> does
>> not appear to have any impact on the proposed secure IPL functionality.

Fair enough.  Let's keep the current code in and address it later.
Thanks, Jared.

> 
> I think it's meant as a check for the *end* of entry[0], so it's likely just 
> a quirky way of saying:
> 
>     if (((uint8_t *)entry) + sizeof(*entry) > tmp_sec + MAX_SECTOR_SIZE)
> 
> ?
> 
>   Thomas
> 

This makes a lot more sense to me.  Thanks, Thomas.

-- 
Regards,
  Collin


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

* Re: [PATCH v4 17/28] s390x: Guest support for Secure-IPL Facility
  2025-07-11 21:10 ` [PATCH v4 17/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
@ 2025-07-14 20:33   ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-14 20:33 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii, brueckner

On 7/11/25 5:10 PM, Zhuoying Cai wrote:
> 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.
> 
> When SIPL facility is installed, the IPL Parameter Block length must
> contains value that is multiple of 8 bytes.

This has no relevance to the code below.

Add a comment that Secure IPL is not available for guests under
protected virtualization.

> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>

I have a few nits below to about some code comments, but the code itself
looks functionally sound.

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

@David, would you be able to review wrt adding this to the qemu_max model?

@Christian, @Hendrik, can either of you review wrt that it looks good to
add this feature starting with the gen16a model?

> ---
>  hw/s390x/sclp.c                     | 2 ++
>  include/hw/s390x/sclp.h             | 4 +++-
>  target/s390x/cpu_features.c         | 3 +++
>  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, 19 insertions(+), 1 deletion(-)
> 
> diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
> index 9718564fa4..69d3328a3d 100644
> --- a/hw/s390x/sclp.c
> +++ b/hw/s390x/sclp.c
> @@ -145,6 +145,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 d32f6180e0..bfd330c340 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 - 137];     /* 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 99089ab3f5..3f3d6a80af 100644
> --- a/target/s390x/cpu_features.c
> +++ b/target/s390x/cpu_features.c
> @@ -149,6 +149,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_DIAG_320)->bit, data);
>          break;
> +    case S390_FEAT_TYPE_SCLP_FAC_IPL:
> +        clear_be_bit(s390_feat_def(S390_FEAT_SIPL)->bit, data);
> +        break;

There's a comment above this switch case that lists features which are
not supported under PV.  Please add a bullet for "Secure IPL
facilities".  This single mention should be sufficient to cover SCLAF as
well.

>      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 7b13a95d98..956bd8a123 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(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide 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 33ef5c190c..ab46204d9e 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_DIAG_320, 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 86486def23..6ee9bad4c6 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_DIAG_320,
> +    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_DIAG_320,
> +    S390_FEAT_SIPL,
>  };
>  
>  /****** END FEATURE DEFS ******/
> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> index 840330709b..fc9cad32a1 100644
> --- a/target/s390x/kvm/kvm.c
> +++ b/target/s390x/kvm/kvm.c
> @@ -2520,6 +2520,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
>  
>      set_bit(S390_FEAT_DIAG_320, model->features);
>  
> +    /* Secure-IPL facility is handled entirely within QEMU */
> +    set_bit(S390_FEAT_SIPL, model->features);
> +

nit: change this comment to "Some Secure IPL facilities are emulated by
QEMU"

When you introduce SCLAF, you could add that line-of-code directly below
this one since it's in the secure IPL family.

>      /* Test for Ultravisor features that influence secure guest behavior */
>      query_uv_feat_guest(model->features);
>  
-- 
Regards,
  Collin


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

* Re: [PATCH v4 18/28] pc-bios/s390-ccw: Refactor zipl_run()
  2025-07-11 21:10 ` [PATCH v4 18/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2025-07-14 20:53   ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-14 20:53 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 5:10 PM, Zhuoying Cai wrote:
> Refactor to enhance readability before enabling secure IPL in later
> patches.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  pc-bios/s390-ccw/bootmap.c | 58 ++++++++++++++++++++++----------------
>  1 file changed, 34 insertions(+), 24 deletions(-)
> 
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index 0f8baa0198..ced5190888 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -674,6 +674,38 @@ static int zipl_load_segment(ComponentEntry *entry)
>      return 0;
>  }
>  
> +static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
> +{
> +    while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
> +        entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
> +

Add space to align this line with the one above, as it was in the code
you're refactoring:

       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;
> +        }
> +    }
> +
> +    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
> +        puts("No EXEC entry");
> +        return -EINVAL;
> +    }
> +
> +    write_reset_psw(entry->compdat.load_psw);
> +    return 0;
> +}
> +
>  /* Run a zipl program */
>  static int zipl_run(ScsiBlockPtr *pte)
>  {
> @@ -700,34 +732,12 @@ 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;
> -        }
> -    }
> -
> -    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
> -        puts("No EXEC entry");
> -        return -EINVAL;
> +    if (zipl_run_normal(entry, tmp_sec)) {
> +        return -1;
>      }
>  
>      /* should not return */
> -    write_reset_psw(entry->compdat.load_psw);
>      jump_to_IPL_code(0);
>      return -1;
>  }

Both zipl_run_normal and zipl_run_secure will end with:

   if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
       puts("No EXEC entry");
       return -EINVAL;
   }

   write_reset_psw(entry->compdat.load_psw);

I'd suggest making the refactor pass &entry and leaving the checks
mentioned above at the end of zipl_run.  That way there is less
duplicate code between _normal and _secure.

-- 
Regards,
  Collin


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

* Re: [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function
  2025-07-11 21:10 ` [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
@ 2025-07-14 22:10   ` Collin Walling
  2025-07-15 15:59     ` Zhuoying Cai
  0 siblings, 1 reply; 62+ messages in thread
From: Collin Walling @ 2025-07-14 22:10 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 5:10 PM, Zhuoying Cai wrote:
> Make the address variable a parameter of zipl_load_segment and return
> segment length.

There's mixed use of the term "comp_len" and "segment length".  Since
the context here is "zipl_load_segment", perhaps the variable should be
"seg_len"?

> 
> Modify this function for reuse in the next patch, which allows
> loading segment or signature data to the destination memory address.

The function is still loading a segment from the disk regardless if it's
a signature or something else.  I'd suggest rewording the above for more
precision about the change:

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

> 
> Add a comp_len variable to store the length of a segment and return this
> variable in zipl_load_segment.

This sentence is redundant since the change in the return behavior is
mentioned in the first sentence.

> 
> 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.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>

Bit of a nit: technically this isn't refactoring since the function's
behavior has changed (new param and different return meaning).  Change
the commit header from "refactor" to "rework" or something akin to that.

> ---
>  pc-bios/s390-ccw/bootmap.c | 12 +++++++-----
>  1 file changed, 7 insertions(+), 5 deletions(-)
> 
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index ced5190888..2513e6c131 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -613,19 +613,18 @@ static int ipl_eckd(void)
>   * IPL a SCSI disk
>   */
>  
> -static int zipl_load_segment(ComponentEntry *entry)
> +static int zipl_load_segment(ComponentEntry *entry, uint64_t address)

The return value meaning of this function has changed from being "< 0
means error, 0 is okay" to "< 0 means error, otherwise the total size of
the component is returned".  Please add a comment above this function to
describe its return behavior so it's easy for future developers to
understand it.

>  {
>      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 comp_len = 0;
>  
>      blockno = entry->data.blockno;
> -    address = entry->compdat.load_addr;
>  
>      debug_print_int("loading segment at block", blockno);
>      debug_print_int("addr", address);
> @@ -662,6 +661,9 @@ static int zipl_load_segment(ComponentEntry *entry)
>                   */
>                  break;
>              }
> +
> +            comp_len += bprs->size * (bprs[i].blockct + 1);
> +

I'm confused by the arithmetic here.  Why is size multiplied by the
block count?  Won't that artificially inflate the value representing the
size of the component?  What's the reason that comp_len += bprs->size
isn't sufficient?

>              address = virtio_load_direct(cur_desc[0], cur_desc[1], 0,
>                                           (void *)address);
>              if (!address) {
> @@ -671,7 +673,7 @@ static int zipl_load_segment(ComponentEntry *entry)
>          }
>      } while (blockno);
>  
> -    return 0;
> +    return comp_len;
>  }
>  
>  static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
> @@ -685,7 +687,7 @@ static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
>              continue;
>          }
>  
> -        if (zipl_load_segment(entry)) {
> +        if (zipl_load_segment(entry, entry->compdat.load_addr) < 0) {
>              return -1;
>          }
>  

-- 
Regards,
  Collin


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

* Re: [PATCH v4 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
  2025-07-11 21:10 ` [PATCH v4 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2025-07-14 22:13   ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-14 22:13 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 5:10 PM, Zhuoying Cai wrote:
> The secure-IPL-code-loading-attributes facility (SCLAF)
> provides additional security during IPL.
> 
> Availability of SCLAF is determined by byte 136 bit 3 of the
> SCLP Read Info block.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>

Aside from the comment I made in patch 17, LGTM.

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

> ---
>  target/s390x/cpu_features.c         | 1 +
>  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              | 3 +++
>  5 files changed, 10 insertions(+)
> 
> diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
> index 3f3d6a80af..8d5614fa59 100644
> --- a/target/s390x/cpu_features.c
> +++ b/target/s390x/cpu_features.c
> @@ -151,6 +151,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 956bd8a123..2e91817d75 100644
> --- a/target/s390x/cpu_features_def.h.inc
> +++ b/target/s390x/cpu_features_def.h.inc
> @@ -142,6 +142,7 @@ DEF_FEAT(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide Certificate Store function
>  
>  /* 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 ab46204d9e..cb1c6b5350 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_DIAG_320, 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 6ee9bad4c6..987e291cf9 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_DIAG_320,
>      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_DIAG_320,
>      S390_FEAT_SIPL,
> +    S390_FEAT_SCLAF,
>  };
>  
>  /****** END FEATURE DEFS ******/
> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> index fc9cad32a1..be3ad7316d 100644
> --- a/target/s390x/kvm/kvm.c
> +++ b/target/s390x/kvm/kvm.c
> @@ -2523,6 +2523,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
>      /* Secure-IPL facility is handled entirely within QEMU */
>      set_bit(S390_FEAT_SIPL, model->features);
>  
> +    /* Secure-IPL-code-loading-attributes facility is handled entirely within QEMU */
> +    set_bit(S390_FEAT_SCLAF, model->features);
> +
>      /* Test for Ultravisor features that influence secure guest behavior */
>      query_uv_feat_guest(model->features);
>  


-- 
Regards,
  Collin


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

* Re: [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function
  2025-07-14 22:10   ` Collin Walling
@ 2025-07-15 15:59     ` Zhuoying Cai
  2025-07-15 21:48       ` Collin Walling
  0 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-15 15:59 UTC (permalink / raw)
  To: Collin Walling, thuth, berrange, richard.henderson, david,
	pbonzini, jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/14/25 6:10 PM, Collin Walling wrote:
> On 7/11/25 5:10 PM, Zhuoying Cai wrote:
>> Make the address variable a parameter of zipl_load_segment and return
>> segment length.
> 
> There's mixed use of the term "comp_len" and "segment length".  Since
> the context here is "zipl_load_segment", perhaps the variable should be
> "seg_len"?
> 
>>
>> Modify this function for reuse in the next patch, which allows
>> loading segment or signature data to the destination memory address.
> 
> The function is still loading a segment from the disk regardless if it's
> a signature or something else.  I'd suggest rewording the above for more
> precision about the change:
> 
> "Modify this function to allow the caller to specify a memory address
> where segment data should be loaded into."
> 
>>
>> Add a comp_len variable to store the length of a segment and return this
>> variable in zipl_load_segment.
> 
> This sentence is redundant since the change in the return behavior is
> mentioned in the first sentence.
> 
>>
>> 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.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> 
> Bit of a nit: technically this isn't refactoring since the function's
> behavior has changed (new param and different return meaning).  Change
> the commit header from "refactor" to "rework" or something akin to that.
> 
>> ---
>>  pc-bios/s390-ccw/bootmap.c | 12 +++++++-----
>>  1 file changed, 7 insertions(+), 5 deletions(-)
>>
>> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
>> index ced5190888..2513e6c131 100644
>> --- a/pc-bios/s390-ccw/bootmap.c
>> +++ b/pc-bios/s390-ccw/bootmap.c
>> @@ -613,19 +613,18 @@ static int ipl_eckd(void)
>>   * IPL a SCSI disk
>>   */
>>  
>> -static int zipl_load_segment(ComponentEntry *entry)
>> +static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
> 
> The return value meaning of this function has changed from being "< 0
> means error, 0 is okay" to "< 0 means error, otherwise the total size of
> the component is returned".  Please add a comment above this function to
> describe its return behavior so it's easy for future developers to
> understand it.
> 
>>  {
>>      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 comp_len = 0;
>>  
>>      blockno = entry->data.blockno;
>> -    address = entry->compdat.load_addr;
>>  
>>      debug_print_int("loading segment at block", blockno);
>>      debug_print_int("addr", address);
>> @@ -662,6 +661,9 @@ static int zipl_load_segment(ComponentEntry *entry)
>>                   */
>>                  break;
>>              }
>> +
>> +            comp_len += bprs->size * (bprs[i].blockct + 1);
>> +
> 
> I'm confused by the arithmetic here.  Why is size multiplied by the
> block count?  Won't that artificially inflate the value representing the
> size of the component?  What's the reason that comp_len += bprs->size
> isn't sufficient?
> 

A component table entry points to a segment table, which holds pointers
to code segments loaded into contiguous memory.

Since segments can vary in length, the block count field may be nonzero.

Block size indicates the number of bytes in a single logical block, so
the total component size is the block size multiplied by the block count.

>>              address = virtio_load_direct(cur_desc[0], cur_desc[1], 0,
>>                                           (void *)address);
>>              if (!address) {
>> @@ -671,7 +673,7 @@ static int zipl_load_segment(ComponentEntry *entry)
>>          }
>>      } while (blockno);
>>  
>> -    return 0;
>> +    return comp_len;
>>  }
>>  
>>  static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
>> @@ -685,7 +687,7 @@ static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
>>              continue;
>>          }
>>  
>> -        if (zipl_load_segment(entry)) {
>> +        if (zipl_load_segment(entry, entry->compdat.load_addr) < 0) {
>>              return -1;
>>          }
>>  
> 



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

* Re: [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function
  2025-07-15 15:59     ` Zhuoying Cai
@ 2025-07-15 21:48       ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-15 21:48 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

[...]

>>> @@ -662,6 +661,9 @@ static int zipl_load_segment(ComponentEntry *entry)
>>>                   */
>>>                  break;
>>>              }
>>> +
>>> +            comp_len += bprs->size * (bprs[i].blockct + 1);
>>> +
>>
>> I'm confused by the arithmetic here.  Why is size multiplied by the
>> block count?  Won't that artificially inflate the value representing the
>> size of the component?  What's the reason that comp_len += bprs->size
>> isn't sufficient?
>>
> 
> A component table entry points to a segment table, which holds pointers
> to code segments loaded into contiguous memory.
> 
> Since segments can vary in length, the block count field may be nonzero.
> 
> Block size indicates the number of bytes in a single logical block, so
> the total component size is the block size multiplied by the block count.
> 

Okay.  After referencing both your explanation and our disk layout docs,
this makes more sense to me.  Thanks!

More nitpicking: I think it reads better to put this line of code as the
last line of the for loop.  That will keep all disk-reading code
together and make it a little more clear that this variable isn't used
elsewhere in the inner loop.

Functionally, I think the patch is fine :)

[...]

-- 
Regards,
  Collin


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

* Re: [PATCH v4 28/28] docs: Add secure IPL documentation
  2025-07-11 21:11 ` [PATCH v4 28/28] docs: Add secure IPL documentation Zhuoying Cai
@ 2025-07-21 19:13   ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-21 19:13 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 17:11, Zhuoying Cai wrote:
> Add documentation for secure IPL
> 
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>

We should consider segmenting these documents by introducing the wording
with the same patch that introduces the relevant functionality.  I'll
try to do my best here to give some guidance on how it should be broken
up, but I might miss a few things.  Hopefully I've successfully conveyed
what I meant in my first sentence.

I'll organize my feedback here using XML tags to help encapsulate the
segments and provide a comment to which patch in this series they should
be merged with. E.g.

	<segment>

	Comment

	Diff

	</segment>

You do not need to provide my sign-off on the patches you merge the
documentation with.

> ---
>  docs/specs/s390x-secure-ipl.rst  | 159 +++++++++++++++++++++++++++++++
>  docs/system/s390x/secure-ipl.rst | 156 ++++++++++++++++++++++++++++++
>  2 files changed, 315 insertions(+)
>  create mode 100644 docs/specs/s390x-secure-ipl.rst
>  create mode 100644 docs/system/s390x/secure-ipl.rst
> 
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> new file mode 100644
> index 0000000000..1ff69092e2
> --- /dev/null
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -0,0 +1,159 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +

<segment>

This can be introduced in a standalone patch at the end of the series
(basically this patch).  You can keep my sign-off on that patch.

> +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 userspace, 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 "userspace" will refer to the s390-ccw BIOS unless stated
> +otherwise.
> +
> +Both QEMU and userspace work in tandem 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 userspace to query
> +the certificate store info and retrieve specific certificates from QEMU.
> +DIAGNOSE 'X'508' is used by userspace to leverage qcrypto libraries to
> +perform signature-verification in QEMU. Lastly, userspace 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:
> +
> +- Userspace reads data payload from disk (e.g. stage3 boot loader, kernel)
> +- Userspace checks the validity of the SCLAB
> +- Userspace 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
> +
> +- Userspace 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)
> +
> +- Userspace appends IIRB at the end of the IPLB
> +- Userspace kicks off IPL
> +
> +More information regarding the respective DIAGNOSE commands and IPL data
> +structures are outlined within this document.

</segment>



<segment>

This should be merged with [PATCH v4 05/28] s390x/diag: Introduce DIAG
320 for certificate store facility

> +
> +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.
> +
> +DIAGNOSE function code 'X'320' - Certificate Store Facility
> +-----------------------------------------------------------
> +
> +DIAGNOSE 'X'320' is used to provide support for userspace to directly
> +query the s390 certificate store. Userspace 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 sucodes are currently
> +    installed and available for use.

</segment>



<segment>

Merge with [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1

> +
> +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).
> +

</segment>



<segment>

Merge with [PATCH v4 09/28] s390x/diag: Implement DIAG 320 subcode 2

> +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 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.
> +
> +    VCE contains various information of a VC from the CS.
> +
> +

</segment>



<segment>

Merge with [PATCH v4 13/28] pc-bios/s390-ccw: Introduce IPL Information
Report Block (IIRB)

> +Secure IPL Data Structures, Facilities, and Functions
> +=====================================================
> +
> +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
> +
> +

</segment>



<segment>

Merge with [PATCH v4 21/28] s390x: Guest support for Secure-IPL Code
Loading Attributes Facility (SCLAF)

Though we may need to revisit if this documentation is valuable.  SCLAF
has a lot of heavy technical information that may be better suited for
the PoPs.

For now, let's keep it.

> +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. Specifically, it may:
> +
> +* Provide direction on how to process the rest of the component.
> +
> +* Provide further validation of information on where to load the signed binary code
> +  from the load device.
> +
> +* Specify where to start the execution of the loaded OS code.
> +
> +

</segment>



<segment>

Merge with [PATCH v4 10/28] s390x/diag: Introduce DIAG 508 for secure
IPL operations

> +DIAGNOSE function code 'X'508' - KVM IPL extensions
> +---------------------------------------------------
> +
> +DIAGNOSE 'X'508' is reserved for KVM guest use in order to facilitate
> +communication of additional IPL operations that cannot be handled by userspace,
> +such as signature verification for secure IPL.
> +
> +If the function code specifies 0x508, KVM 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.
> +

</segment>



<segment>

Merge with [PATCH v4 12/28] s390x/diag: Implement DIAG 508 subcode 1 for
signature verification

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

Add more detail here later to describe the data structures and
input/output, similar to how the documentation for 320 is laid out.


</segment>



[...]



<segment>

Introduce this as another standalone patch (not with the same standalone
as mentioned above)

> +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
> +new 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
> +===============================
> +
> +New parameters have been introduced to s390-ccw-virtio machine type option
> +to support secure IPL. These parameters allow users to provide certificates
> +and enable secure IPL directly via the command line.
> +

</segment>



<segment>

Merge with [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio
machine type option

> +Providing Certificates
> +----------------------
> +
> +The certificate store can be populated by supplying a comma-delimited list of
> +certificates on the command-line:
> +
> +.. code-block:: shell
> +
> +    qemu-system-s390x -machine s390-ccw-virtio, \
> +    boot-certificates=/.../qemu/certs:/another/path/cert.pem
> +

</segment>



<segment>

Merge with [PATCH v4 23/28] Add secure-boot to s390-ccw-virtio machine
type option

> +Enabling Secure IPL
> +-------------------
> +
> +Different IPL modes may be toggled with the following command line option:
> +

Change to: "Secure IPL is enabled by explicitly providing the
secure-boot='on' option.  The absence of this option is equivalent to
setting this option to off.

(maybe reword it so "option" isn't used three times in one breath?)

> +.. code-block:: shell
> +
> +    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on|off
> +
> +Additionally, the provision of certificates affect the mode.
> +
> +

Remove the line above.  It'll be explained in the IPL Modes section.

</segment>



<segment>

Merge each mode below with the same patch that implements the respective
enum.

> +IPL Modes
> +=========

Let's add a quick description of what "IPL Mode" means:

"The concept of IPL Modes are introduced to differentiate between the
IPL configurations. These modes are mutually exclusive and enabled based
on specific combinations of the ``secure-boot`` and
``boot-certificates`` options on the QEMU command line."

> +
> +Normal Mode
> +-----------
> +

Configuration: ``qemu-system-s390x -machine s390-ccw-virtio ...``

> +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``.
> +
> +
> +Audit Mode
> +----------
> +

Configuration: ``qemu-system-s390x -machine
s390-ccw-virtio,boot-certificates=path/to/cert.pem ...``

> +With *only* the presence of certificates in the store, it is assumed that secure
> +boot operations should be performed with errors reported as warnings. As such,
> +the secure IPL operations will be performed, and any errors that stem from these
> +operations will report a warning via the SCLP console.
> +
> +
> +Secure Mode
> +-----------
> +

Configuration: ``qemu-system-s390x -machine
s390-ccw-virtio,boot-certificates=path/to/cert.pem,secure-boot=on ...``

> +With *both* the presence of certificates in the store and the ``secure-boot=on``
> +option, it is understood that secure boot should be performed with errors
> +reported and boot will abort.
> +

</segment>



<segment>

Include this with the standalone patch for this doc.

> +
> +Constraints
> +===========
> +
> +The following constraints apply when attempting to secure IPL an s390 guest:
> +
> +- z16 CPU model
> +- certificates must be in X.509 PEM format
> +- only sha256 encryption is supported
> +- 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.,
> +  Net) 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 keypt secure
> +and confidential.

s/keypt/kept

> +
> +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 KVM 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
> +
> +Start Guest with Cmd Options
> +----------------------------
> +
> +.. code-block:: shell
> +
> +    qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certificates=cert.pem ...

</segment>

-- 
Regards,
  Collin


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

* Re: [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility
  2025-07-11 21:10 ` [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
@ 2025-07-21 21:26   ` Collin Walling
  2025-07-21 21:39     ` Collin Walling
  2025-07-23 17:50     ` Eric Farman
  0 siblings, 2 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-21 21:26 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 17:10, Zhuoying Cai wrote:
> DIAGNOSE 320 is introduced to support certificate store 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 a bitmap of which subcodes are supported.

Change to: "used to query the Installed Subcodes Mask (ISM)."

Based on my feedback below, make sure to include a statement that this
subcode is only supported when the Certificate Store facility is enabled.

> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  include/hw/s390x/ipl/diag320.h | 17 ++++++++++++++
>  target/s390x/diag.c            | 41 ++++++++++++++++++++++++++++++++++
>  target/s390x/kvm/kvm.c         | 14 ++++++++++++
>  target/s390x/s390x-internal.h  |  2 ++
>  target/s390x/tcg/misc_helper.c |  7 ++++++
>  5 files changed, 81 insertions(+)
>  create mode 100644 include/hw/s390x/ipl/diag320.h
> 
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> new file mode 100644
> index 0000000000..713570545d
> --- /dev/null
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -0,0 +1,17 @@
> +/*
> + * 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
> +
> +#endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index cff9fbc4b0..d33c5daf38 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"
> @@ -191,3 +192,43 @@ 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];
> +    int rc;

Even though "rc" likely means "response code" in the context of DIAG, it
screams "return code" in a void function, which looks a little odd.  I'd
remove this variable and simply set `env->regs[r1 + 1] = DIAG_320_RC_*`
instead, similar to how DIAG 308 handles this.

> +
> +    if (env->psw.mask & PSW_MASK_PSTATE) {
> +        s390_program_interrupt(env, PGM_PRIVILEGED, ra);
> +        return;
> +    }
> +
> +    if (!s390_has_feat(S390_FEAT_DIAG_320)) {
> +        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> +        return;
> +    }
> +
> +    if (r1 & 1) {
> +        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> +        return;
> +    }
> +
> +    switch (subcode) {

In patch 4, you introduce the feature bit for DIAG 320.  It is
documented that subcodes 0-3 are only supported if this feature bit is
on.  Please add a check for this feature.  Subcode 0 (and later 1 and 2)
should not be handled if this feature bit is not present.

> +    case DIAG_320_SUBC_QUERY_ISM:
> +        uint64_t ism =  0;

Technically, this subcode is suppose to write to an 8-word installed
subcodes block (ISB).  Though I can't imagine that we'll grow beyond
even the first word for quite some time.  I guess it's up to the caller
to provide a proper ISB anyway.

I'd suggest the following:

change from `uint64_t` to `uint32_t ism_word0`

Add a comment:

/*
 * 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.
 */

> +
> +        if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
> +            s390_cpu_virt_mem_handle_exc(cpu, ra);
> +            return;
> +        }
> +
> +        rc = DIAG_320_RC_OK;

env->regs[r1 + 1] = DIAG_320_RC_OK;

> +        break;
> +    default:
> +        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> +        return;

As specified in the documentation, "response code 0x0102 indicates that
the subcode is reserved or not supported on this model". There is no
program specification when an incorrect subcode has been provided.

> +    }
> +    env->regs[r1 + 1] = rc;

Remove this line.

> +}
> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> index 8f655a4b7f..d5b3694600 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);
> diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
> index a4ba6227ab..86a652f833 100644
> --- a/target/s390x/s390x-internal.h
> +++ b/target/s390x/s390x-internal.h
> @@ -400,6 +400,8 @@ int mmu_translate_real(CPUS390XState *env, target_ulong 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 f7101be574..412c34ed93 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;


-- 
Regards,
  Collin


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

* Re: [PATCH v4 04/28] s390x: Guest support for Certificate Store Facility (CS)
  2025-07-11 21:10 ` [PATCH v4 04/28] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
@ 2025-07-21 21:30   ` Collin Walling
  2025-07-23 20:15     ` Collin Walling
  0 siblings, 1 reply; 62+ messages in thread
From: Collin Walling @ 2025-07-21 21:30 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 17:10, Zhuoying Cai wrote:
> DIAG 320 is supported when the certificate-store (CS) facility
> is installed.
> 
> 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.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>

For consistency with documentation, please change this from
"S390_FEAT_DIAG_320" to "S390_FEAT_CERT_STORE".

> ---
>  target/s390x/cpu_features.c         | 1 +
>  target/s390x/cpu_features_def.h.inc | 1 +
>  target/s390x/cpu_models.c           | 2 ++
>  target/s390x/gen-features.c         | 3 +++
>  target/s390x/kvm/kvm.c              | 2 ++
>  5 files changed, 9 insertions(+)
> 
> diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
> index 4b5be6798e..99089ab3f5 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_DIAG_320)->bit, data);
>          break;
>      default:
>          return;
> diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
> index e23e603a79..7b13a95d98 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(DIAG_320, "cstore", SCLP_FAC134, 5, "Provide 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..33ef5c190c 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_DIAG_320:
>          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_DIAG_320, 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 8218e6470e..86486def23 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_DIAG_320,
>  };
>  
>  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_DIAG_320,
>  };
>  
>  /****** END FEATURE DEFS ******/
> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> index 2e02d2c4de..8f655a4b7f 100644
> --- a/target/s390x/kvm/kvm.c
> +++ b/target/s390x/kvm/kvm.c
> @@ -2490,6 +2490,8 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
>          set_bit(S390_FEAT_DIAG_318, model->features);
>      }
>  
> +    set_bit(S390_FEAT_DIAG_320, model->features);
> +
>      /* Test for Ultravisor features that influence secure guest behavior */
>      query_uv_feat_guest(model->features);
>  


-- 
Regards,
  Collin


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

* Re: [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility
  2025-07-21 21:26   ` Collin Walling
@ 2025-07-21 21:39     ` Collin Walling
  2025-07-22 21:08       ` Collin Walling
  2025-07-23 17:50     ` Eric Farman
  1 sibling, 1 reply; 62+ messages in thread
From: Collin Walling @ 2025-07-21 21:39 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/21/25 17:26, Collin Walling wrote:
> On 7/11/25 17:10, Zhuoying Cai wrote:

[...]

>> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
>> index 8f655a4b7f..d5b3694600 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);
>> +}

One more piece I forgot to add here:

This handler function should check if the required facilities are
installed for DIAG 320.  For now, the Certificate Store facility is used
to indicate DIAG 320 is provided and possibly subcodes 0-3 are
available.  Please add a feature check here and throw a program
specification interrupt if the facility is not available before
handle_diag_320 is called.

>> +
>>  #define DIAG_KVM_CODE_MASK 0x000000000000ffff
>>  
>>  static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)

[...]

-- 
Regards,
  Collin


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

* Re: [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-07-11 22:50   ` Collin Walling
  2025-07-14 14:54     ` Jared Rossi
@ 2025-07-21 21:50     ` Zhuoying Cai
  2025-07-28 21:05       ` Collin Walling
  1 sibling, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-21 21:50 UTC (permalink / raw)
  To: Collin Walling, thuth, berrange, richard.henderson, david,
	pbonzini, jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 6:50 PM, Collin Walling wrote:
> On 7/11/25 5:10 PM, 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>

[snip...]

>> +static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
>> +{
>> +    IplDeviceComponentList comps;
>> +    IplSignatureCertificateList certs;
>> +    uint64_t *cert = NULL;
>> +    int cert_index = 0;
>> +    int comp_index = 0;
>> +    uint64_t comp_addr;
>> +    int comp_len;
>> +    bool have_sig;
>> +    uint32_t sig_len;
>> +    uint64_t cert_len = -1;
>> +    uint8_t cert_idx = -1;
>> +    bool verified;
>> +    uint32_t certs_len;
>> +    /*
>> +     * Store indices of cert entry that have already used for signature verification
>> +     * to prevent allocating the same certificate multiple times.
>> +     * cert_table index: index of certificate from qemu cert store used for verification
>> +     * cert_table value: index of cert entry in cert list that contains the certificate
>> +     */
>> +    int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
>> +    int signed_count = 0;
>> +
>> +    if (!zipl_secure_ipl_supported()) {
>> +        return -1;
>> +    }
>> +
>> +    zipl_secure_init_lists(&comps, &certs);
>> +    certs_len = zipl_secure_get_certs_length();
>> +    cert = malloc(certs_len);
>> +
>> +    have_sig = false;
> 
> You can get rid of this boolean and simply use sig_len as the indicator
> that there were nonzero bytes read when a signature entry was detected
> in the previous loop.  Where you set have_sig to false below, you can
> set sig_len to 0 to reset it.
> 
>> +    while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
>> +           entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
>> +
> 
> I'll be honest, I'm not a big fan of neither the design of this while
> loop nor the one in zipl_run_normal.  I'd prefer something like:
> 
> while (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
> 
>     // sanity checking
> 
>     switch (entry->component_type) {
>     case ZIPL_COMP_ENTRY_SIGNATURE:
>         ...
>         break;
>     case ZIPL_COMP_ENTRY_LOAD:
>         ...
>         break;
>     default:
>         // Unrecognized entry type, return error
>     }
> 
>     entry++;
> }
> 
> The above makes it clear that we're loading in segments until the exec
> entry is found, and the handling for each recognized component type is
> clearly labeled and organized.
> 
> I won't speak for making this change to the zipl_run_normal function
> yet, as it may introduce too many changes in this patch series.
> 
>> +        if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
>> +            /* There should never be two signatures in a row */
>> +            if (have_sig) {
>> +                return -1;
>> +            }
>> +
>> +            sig_len = zipl_handle_sig_entry(entry);
>> +            if (sig_len < 0) {
>> +                return -1;
>> +            }
>> +
>> +            have_sig = true;
>> +        } else {
>> +            comp_addr = entry->compdat.load_addr;
>> +            comp_len = zipl_load_segment(entry, comp_addr);
>> +            if (comp_len < 0) {
>> +                return -1;
>> +            }
>> +
>> +            if (have_sig) {
> 
> If you use my idea to re-write this loop, you'll be able to reduce one
> level of indentation of the code that follows by checking the inverse
> condition:
> 
> if (!have_sig) { // or if (!sig_len)
>     break;
> 
>> +                verified = verify_signature(comp_len, comp_addr,
>> +                                            sig_len, (uint64_t)sig_sec,
>> +                                            &cert_len, &cert_idx);
>> +
>> +                if (verified) {
>> +                    cert_index = handle_certificate(cert_table, &cert,
>> +                                                    cert_len, cert_idx,
>> +                                                    &certs, cert_index);
>> +                    if (cert_index == -1) {
>> +                        return -1;
>> +                    }
>> +
>> +                    puts("Verified component");
>> +                    zipl_secure_comp_list_add(&comps, comp_index, cert_table[cert_idx],
>> +                                              comp_addr, comp_len,
>> +                                              S390_IPL_COMPONENT_FLAG_SC |
>> +                                              S390_IPL_COMPONENT_FLAG_CSV);
>> +                } else {
>> +                    zipl_secure_comp_list_add(&comps, comp_index, -1,
>> +                                              comp_addr, comp_len,
>> +                                              S390_IPL_COMPONENT_FLAG_SC);
>> +                    zipl_secure_print("Could not verify component");
>> +                }
>> +
>> +                comp_index++;
>> +                signed_count += 1;
>> +                /* After a signature is used another new one can be accepted */
>> +                have_sig = false;
>> +            }
>> +        }
>> +
>> +        entry++;
>> +
>> +        if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
>> +            puts("Wrong entry value");
>> +            return -EINVAL;
>> +        }
> 
> Can someone who is more informed than I am of the IPL process please
> explain to me what is the purpose of the above check?  Why does it check
> if the next entry, the one which isn't going to be inspected/loaded, is
> within the bounds of tmp_sec?  This has been here since this file's
> inception and I can't find any documentation or mention that supports it.
> 
> This code precludes any of the secure IPL changes.
> 
> Was this actually meant to be entry[0] to ensure the actual entry we
> want to work on is not outside the bounds of tmp_sec?  Or perhaps it was
> meant to be done before the increment to entry?
> 
>> +    }
>> +
>> +    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
>> +        puts("No EXEC entry");
>> +        return -EINVAL;
>> +    }
>> +
>> +    if (signed_count == 0) {
>> +        zipl_secure_print("Secure boot is on, but components are not signed");
>> +    }
> 
> Shouldn't this be handled within the loop?  If it gets to a LOAD type
> without a signature, audit mode should report and move on.  Secure mode
> should report and abort on the spot.  Then you can get rid of signed_count.
> 
> I'll get back to reviewing patch 22 wrt to this variable's usage later on...
> 

This is handled after the loop because the number of LOAD components can
vary, and not all of them require signatures. We count the signed
components once all have been processed to verify whether components,
such as the stage3 bootloader and kernel, are signed for secure IPL.

The signed_count is also used in a later patch for the SCLAB check,
which allows only one signed component when the Single Component flag is
set.

>> +
>> +    if (zipl_secure_update_iirb(&comps, &certs)) {
>> +        zipl_secure_print("Failed to write IPL Information Report Block");
>> +    }
>> +    write_reset_psw(entry->compdat.load_psw);
>> +
>> +    return 0;
>> +}
>> +
>>  static int zipl_run_normal(ComponentEntry *entry, uint8_t *tmp_sec)
>>  {
>>      while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
>> @@ -735,8 +893,17 @@ static int zipl_run(ScsiBlockPtr *pte)
>>      /* Load image(s) into RAM */
>>      entry = (ComponentEntry *)(&header[1]);
>>  
>> -    if (zipl_run_normal(entry, tmp_sec)) {
>> -        return -1;
>> +    switch (boot_mode) {
>> +    case ZIPL_SECURE_AUDIT_MODE:
>> +        if (zipl_run_secure(entry, tmp_sec)) {
>> +            return -1;
>> +        }
>> +        break;
>> +    case ZIPL_NORMAL_MODE:
>> +        if (zipl_run_normal(entry, tmp_sec)) {
>> +            return -1;
>> +        }
>> +        break;
>>      }
>>  
>>      /* should not return */
>> @@ -1095,17 +1262,35 @@ static int zipl_load_vscsi(void)
>>   * IPL starts here
>>   */
>>  
>> +ZiplBootMode zipl_mode(uint8_t hdr_flags)
>> +{
>> +    bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;
>> +    bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;
>> +
>> +    if (!sipl_set && iplir_set) {
>> +        return ZIPL_SECURE_AUDIT_MODE;
>> +    }
>> +
>> +    return ZIPL_NORMAL_MODE;
>> +}
>> +
>>  void zipl_load(void)
>>  {
>>      VDev *vdev = virtio_get_device();
>>  
>>      if (vdev->is_cdrom) {
>> +        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
>> +            panic("Secure boot from ISO image is not supported!");
>> +        }
>>          ipl_iso_el_torito();
>>          puts("Failed to IPL this ISO image!");
>>          return;
>>      }
>>  
>>      if (virtio_get_device_type() == VIRTIO_ID_NET) {
>> +        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
>> +            panic("Virtio net boot device does not support secure boot!");
>> +        }
>>          netmain();
>>          puts("Failed to IPL from this network!");
>>          return;
>> @@ -1116,6 +1301,10 @@ void zipl_load(void)
>>          return;
>>      }
>>  
>> +    if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
>> +        panic("ECKD boot device does not support secure boot!");
>> +    }
>> +
>>      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..e48823a835 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;
>> +} __attribute__((packed)) SignatureInformation;
>> +
>>  typedef union ComponentEntryData {
>>      uint64_t load_psw;
>>      uint64_t load_addr;
>> +    SignatureInformation sig_info;
>>  } ComponentEntryData;
>>  
>>  typedef struct ComponentEntry {
>> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
>> index c9328f1c51..38962da1dd 100644
>> --- a/pc-bios/s390-ccw/main.c
>> +++ b/pc-bios/s390-ccw/main.c
>> @@ -28,6 +28,7 @@ IplParameterBlock *iplb;
>>  bool have_iplb;
>>  static uint16_t cutype;
>>  LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */
>> +ZiplBootMode boot_mode;
>>  
>>  #define LOADPARM_PROMPT "PROMPT  "
>>  #define LOADPARM_EMPTY  "        "
>> @@ -272,9 +273,17 @@ static int virtio_setup(void)
>>  
>>  static void ipl_boot_device(void)
>>  {
>> +    if (boot_mode == 0) {
>> +        boot_mode = zipl_mode(iplb->hdr_flags);
>> +    }
>> +
>>      switch (cutype) {
>>      case CU_TYPE_DASD_3990:
>>      case CU_TYPE_DASD_2107:
>> +        if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
>> +            panic("Passthrough (vfio) device does not support secure boot!");
>> +        }
>> +
>>          dasd_ipl(blk_schid, cutype);
>>          break;
>>      case CU_TYPE_VIRTIO:
>> diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
>> index 6cdce3e5e5..648f407dc5 100644
>> --- a/pc-bios/s390-ccw/s390-ccw.h
>> +++ b/pc-bios/s390-ccw/s390-ccw.h
>> @@ -39,6 +39,9 @@ typedef unsigned long long u64;
>>  #define MIN_NON_ZERO(a, b) ((a) == 0 ? (b) : \
>>                              ((b) == 0 ? (a) : (MIN(a, b))))
>>  #endif
>> +#ifndef ROUND_UP
>> +#define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d)))
>> +#endif
>>  
>>  #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
>>  
>> @@ -64,6 +67,8 @@ void sclp_print(const char *string);
>>  void sclp_set_write_mask(uint32_t receive_mask, uint32_t send_mask);
>>  void sclp_setup(void);
>>  void sclp_get_loadparm_ascii(char *loadparm);
>> +bool sclp_is_diag320_on(void);
>> +bool sclp_is_sipl_on(void);
>>  int sclp_read(char *str, size_t count);
>>  
>>  /* virtio.c */
>> @@ -76,6 +81,15 @@ int virtio_read(unsigned long sector, void *load_addr);
>>  /* bootmap.c */
>>  void zipl_load(void);
>>  
>> +typedef enum ZiplBootMode {
>> +    ZIPL_NORMAL_MODE = 1,
>> +    ZIPL_SECURE_AUDIT_MODE = 2,
>> +} ZiplBootMode;
> 
> These should be ZIPL_BOOT_MODE_*
> 
> Also, is there a reason why the list starts at 1 and not defaulting to
> the implicit 0?
> 

boot_mode is a global variable defined in pc-bios/s390-ccw/main.c, and
it defaults to 0, which indicates that the boot mode hasn’t been
determined yet.

We start the list at 1 to reserve 0 as the implicit “undefined” or “not
yet set” value. The actual boot mode is only set later when boot_mode == 0:
    if (boot_mode == 0) {
        boot_mode = zipl_mode(iplb->hdr_flags);
    }
This allows us to distinguish between an unset state and valid boot modes.

>> +
>> +extern ZiplBootMode boot_mode;
>> +
>> +ZiplBootMode zipl_mode(uint8_t hdr_flags);
>> +
>>  /* jump2ipl.c */
>>  void write_reset_psw(uint64_t psw);
>>  int jump_to_IPL_code(uint64_t address);
>> diff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c
>> index 4a07de018d..0b03c3164f 100644
>> --- a/pc-bios/s390-ccw/sclp.c
>> +++ b/pc-bios/s390-ccw/sclp.c
>> @@ -113,6 +113,50 @@ void sclp_get_loadparm_ascii(char *loadparm)
>>      }
>>  }
>>  

[snip...]






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

* Re: [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio machine type option
  2025-07-11 21:10 ` [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-07-22 15:56   ` Daniel P. Berrangé
  0 siblings, 0 replies; 62+ messages in thread
From: Daniel P. Berrangé @ 2025-07-22 15:56 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, jrossi, qemu-s390x,
	qemu-devel, walling, jjherne, pasic, borntraeger, farman,
	mjrosato, iii

On Fri, Jul 11, 2025 at 05:10:37PM -0400, Zhuoying Cai wrote:
> Add boot-certificates as a parameter of s390-ccw-virtio machine type option.
> 
> The `boot-certificates=/path/dir:/path/file` parameter is implemented
> to provide path to either a directory or a single certificate.
> 
> Multiple paths can be delineated using a colon.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  hw/s390x/s390-virtio-ccw.c         | 22 ++++++++++++++++++++++
>  include/hw/s390x/s390-virtio-ccw.h |  1 +
>  qemu-options.hx                    |  7 ++++++-
>  3 files changed, 29 insertions(+), 1 deletion(-)
> 
> diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
> index c294106a74..167876295e 100644
> --- a/hw/s390x/s390-virtio-ccw.c
> +++ b/hw/s390x/s390-virtio-ccw.c
> @@ -798,6 +798,22 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
>      g_free(val);
>  }
>  
> +static inline char *machine_get_boot_certificates(Object *obj, Error **errp)
> +{
> +    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
> +
> +    return g_strdup(ms->boot_certificates);
> +}
> +
> +static void machine_set_boot_certificates(Object *obj, const char *str,
> +                                          Error **errp)
> +{
> +    S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
> +
> +    g_free(ms->boot_certificates);
> +    ms->boot_certificates = g_strdup(str);
> +}
> +
>  static void ccw_machine_class_init(ObjectClass *oc, const void *data)
>  {
>      MachineClass *mc = MACHINE_CLASS(oc);
> @@ -851,6 +867,12 @@ 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_str(oc, "boot-certificates",
> +                                  machine_get_boot_certificates,
> +                                  machine_set_boot_certificates);
> +    object_class_property_set_description(oc, "boot-certificates",
> +            "provide path to a directory or a single certificate 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 526078a4e2..45adc8bce6 100644
> --- a/include/hw/s390x/s390-virtio-ccw.h
> +++ b/include/hw/s390x/s390-virtio-ccw.h
> @@ -31,6 +31,7 @@ struct S390CcwMachineState {
>      uint8_t loadparm[8];
>      uint64_t memory_limit;
>      uint64_t max_pagesize;
> +    char *boot_certificates;
>  
>      SCLPDevice *sclp;
>  };
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 1f862b19a6..d42e6f502c 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -43,7 +43,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
>  #endif
>      "                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"
> -    "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n",
> +    "                smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
> +    "                boot-certificates='/path/directory:/path/file' provide a path to a directory or a boot certificate\n",

I forgot to respond to your previous question about how to do
arrays for this. The command line exmaples here for cxl-fmw
and smp-cache conveniently illustrate the syntax.

It would be something like this on the CLI:

  boot-certs.0.path=/path/to/dir,boot-certs.1.path=/to/other/dir,boot-certs.2.path=/some/...



With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



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

* Re: [PATCH v4 02/28] crypto/x509-utils: Add helper functions for certificate store
  2025-07-11 21:10 ` [PATCH v4 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
@ 2025-07-22 16:06   ` Daniel P. Berrangé
  0 siblings, 0 replies; 62+ messages in thread
From: Daniel P. Berrangé @ 2025-07-22 16:06 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, jrossi, qemu-s390x,
	qemu-devel, walling, jjherne, pasic, borntraeger, farman,
	mjrosato, iii

On Fri, Jul 11, 2025 at 05:10:38PM -0400, Zhuoying Cai wrote:
> 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
> qcrypto_x509_get_keyid_len() - returns the length of the key ID
> qcrypto_x509_get_signature_algorithm() - returns signature algorithm of the certificate
> 
> These functions provide support for certificate format conversion and
> metadata extraction.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  crypto/meson.build          |   5 +-
>  crypto/x509-utils.c         | 155 ++++++++++++++++++++++++++++++++++++
>  include/crypto/x509-utils.h |  71 +++++++++++++++++
>  3 files changed, 227 insertions(+), 4 deletions(-)
> 
> diff --git a/crypto/meson.build b/crypto/meson.build
> index 735635de1f..0614bfa914 100644
> --- a/crypto/meson.build
> +++ b/crypto/meson.build
> @@ -22,12 +22,9 @@ crypto_ss.add(files(
>    'tlscredsx509.c',
>    'tlssession.c',
>    'rsakey.c',
> +  'x509-utils.c',
>  ))
>  
> -if gnutls.found()
> -  crypto_ss.add(files('x509-utils.c'))
> -endif
> -

Lets make this change, plus....

>  if nettle.found()
>    crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c'))
>    if hogweed.found()
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 8bad00a51b..d2cf790d5b 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

...this addition as a separate commit. They are refactoring the
way we build the code, without adding any features, so best
practice says they should be separated from feature additions.



>  #include <gnutls/gnutls.h>
>  #include <gnutls/crypto.h>
>  #include <gnutls/x509.h>
> @@ -25,6 +27,87 @@ 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_KEYID_FLAGS_SHA1] = GNUTLS_KEYID_USE_SHA1,
> +    [QCRYPTO_KEYID_FLAGS_SHA256] = GNUTLS_KEYID_USE_SHA256,
> +    [QCRYPTO_KEYID_FLAGS_SHA512] = GNUTLS_KEYID_USE_SHA512,
> +    [QCRYPTO_KEYID_FLAGS_BEST_KNOWN] = GNUTLS_KEYID_USE_BEST_KNOWN,
> +};
> +
> +static const int gnutls_to_qcrypto_sig_alg_map[] = {
> +    [GNUTLS_SIGN_UNKNOWN] = QCRYPTO_SIG_ALGO_UNKNOWN,
> +    [GNUTLS_SIGN_RSA_SHA1] = QCRYPTO_SIG_ALGO_RSA_SHA1,
> +    [GNUTLS_SIGN_RSA_SHA] = QCRYPTO_SIG_ALGO_RSA_SHA1,
> +    [GNUTLS_SIGN_DSA_SHA1] = QCRYPTO_SIG_ALGO_DSA_SHA1,
> +    [GNUTLS_SIGN_RSA_MD5] = QCRYPTO_SIG_ALGO_RSA_MD5,
> +    [GNUTLS_SIGN_RSA_MD2] = QCRYPTO_SIG_ALGO_RSA_MD2,
> +    [GNUTLS_SIGN_RSA_RMD160] = QCRYPTO_SIG_ALGO_RSA_RMD160,
> +    [GNUTLS_SIGN_RSA_SHA256] = QCRYPTO_SIG_ALGO_RSA_SHA256,
> +    [GNUTLS_SIGN_RSA_SHA384] = QCRYPTO_SIG_ALGO_RSA_SHA384,
> +    [GNUTLS_SIGN_RSA_SHA512] = QCRYPTO_SIG_ALGO_RSA_SHA512,
> +    [GNUTLS_SIGN_RSA_SHA224] = QCRYPTO_SIG_ALGO_RSA_SHA224,
> +    [GNUTLS_SIGN_DSA_SHA224] = QCRYPTO_SIG_ALGO_DSA_SHA224,
> +    [GNUTLS_SIGN_DSA_SHA256] = QCRYPTO_SIG_ALGO_DSA_SHA256,
> +    [GNUTLS_SIGN_ECDSA_SHA1] = QCRYPTO_SIG_ALGO_ECDSA_SHA1,
> +    [GNUTLS_SIGN_ECDSA_SHA224] = QCRYPTO_SIG_ALGO_ECDSA_SHA224,
> +    [GNUTLS_SIGN_ECDSA_SHA256] = QCRYPTO_SIG_ALGO_ECDSA_SHA256,
> +    [GNUTLS_SIGN_ECDSA_SHA384] = QCRYPTO_SIG_ALGO_ECDSA_SHA384,
> +    [GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
> +};
> +
> +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};
> +
> +    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;
> +    }
> +
> +    *result = g_malloc0(*resultlen);
> +    rc = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_DER, *result, resultlen);
> +    if (rc != 0) {

We need to  do   "g_clear_pointer(result, g_free)"  in this error path

> +        error_setg(errp, "Failed to convert certificate to DER format: %s",
> +                   gnutls_strerror(rc));
> +        goto cleanup;
> +    }
> +
> +    ret = 0;
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}
> +
> +int qcrypto_x509_get_keyid_len(QCryptoKeyidFlags flag, Error **errp)
> +{
> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
> +        error_setg(errp, "Unknow key ID flag %d", flag);
> +        return -1;
> +    }
> +
> +    if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
> +        (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
> +        return QCRYPTO_HASH_DIGEST_LEN_SHA512;
> +    } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
> +        return QCRYPTO_HASH_DIGEST_LEN_SHA256;
> +    } else {
> +        return QCRYPTO_HASH_DIGEST_LEN_SHA1;
> +    }
> +}
> +

If we eliminate the QCryptoKeyidFlags enum, then the need for this
method also goes away.

>  int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>                                        QCryptoHashAlgo alg,
>                                        uint8_t *result,
> @@ -74,3 +157,75 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>      gnutls_x509_crt_deinit(crt);
>      return ret;
>  }
> +
> +int qcrypto_x509_get_signature_algorithm(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};
> +
> +    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;
> +    }
> +
> +    /*
> +     * This function never returns a negative error code.
> +     * Error cases and unknown/unsupported signature algorithms
> +     * are mapped to GNUTLS_SIGN_UNKNOWN.
> +     */
> +    rc = gnutls_x509_crt_get_signature_algorithm(crt);
> +    if (rc >= G_N_ELEMENTS(gnutls_to_qcrypto_sig_alg_map)) {
> +        error_setg(errp, "Unknown signature algorithm %d", rc);
> +        goto cleanup;
> +    }
> +
> +    ret = gnutls_to_qcrypto_sig_alg_map[rc];
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}
> +
> +#else /* ! CONFIG_GNUTLS */
> +
> +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;
> +}
> +
> +int qcrypto_x509_get_keyid_len(QCryptoKeyidFlags flag, Error **errp)
> +{
> +    error_setg(errp, "GNUTLS is required to get key ID length");
> +    return -1;
> +}
> +
> +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;
> +}

If you separate out the  meson.build + #ifdef CONFIG_GNUTLS change, then
this specific stub should be done in that refactoring commit too.

> +
> +int qcrypto_x509_get_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> +    error_setg(errp, "GNUTLS is required to get signature algorithm");
> +    return -1;
> +}
> +
> +#endif /* ! CONFIG_GNUTLS */
> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index 1e99661a71..d916d248bb 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -13,10 +13,81 @@
>  
>  #include "crypto/hash.h"
>  
> +typedef enum {
> +    QCRYPTO_KEYID_FLAGS_SHA1,
> +    QCRYPTO_KEYID_FLAGS_SHA256,
> +    QCRYPTO_KEYID_FLAGS_SHA512,
> +    QCRYPTO_KEYID_FLAGS_BEST_KNOWN,
> +} QCryptoKeyidFlags;

I don't think this enum serves any useful purpose.

The qcrypto_x509_get_cert_key_id method should just accept
the QCryptoHashAlgo enum directly and avoid the redirextion.

> +
> +typedef enum {
> +    QCRYPTO_SIG_ALGO_UNKNOWN,
> +    QCRYPTO_SIG_ALGO_RSA_SHA1,
> +    QCRYPTO_SIG_ALGO_DSA_SHA1,
> +    QCRYPTO_SIG_ALGO_RSA_MD5,
> +    QCRYPTO_SIG_ALGO_RSA_MD2,
> +    QCRYPTO_SIG_ALGO_RSA_RMD160,
> +    QCRYPTO_SIG_ALGO_RSA_SHA256,
> +    QCRYPTO_SIG_ALGO_RSA_SHA384,
> +    QCRYPTO_SIG_ALGO_RSA_SHA512,
> +    QCRYPTO_SIG_ALGO_RSA_SHA224,
> +    QCRYPTO_SIG_ALGO_DSA_SHA224,
> +    QCRYPTO_SIG_ALGO_DSA_SHA256,
> +    QCRYPTO_SIG_ALGO_ECDSA_SHA1,
> +    QCRYPTO_SIG_ALGO_ECDSA_SHA224,
> +    QCRYPTO_SIG_ALGO_ECDSA_SHA256,
> +    QCRYPTO_SIG_ALGO_ECDSA_SHA384,
> +    QCRYPTO_SIG_ALGO_ECDSA_SHA512,
> +} QCryptoSigAlgo;
> +
>  int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>                                        QCryptoHashAlgo hash,
>                                        uint8_t *result,
>                                        size_t *resultlen,
>                                        Error **errp);
>  

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



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

* Re: [PATCH v4 03/28] hw/s390x/ipl: Create certificate store
  2025-07-11 21:10 ` [PATCH v4 03/28] hw/s390x/ipl: Create " Zhuoying Cai
@ 2025-07-22 16:14   ` Daniel P. Berrangé
  0 siblings, 0 replies; 62+ messages in thread
From: Daniel P. Berrangé @ 2025-07-22 16:14 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, jrossi, qemu-s390x,
	qemu-devel, walling, jjherne, pasic, borntraeger, farman,
	mjrosato, iii

On Fri, Jul 11, 2025 at 05:10:39PM -0400, Zhuoying Cai wrote:
> Create a certificate store for boot certificates used for secure IPL.
> 
> Load certificates from the boot-certificate 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. Additionally,
> only the SHA-256 hashing algorithm is supported, as it is required for
> secure boot on s390.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  hw/s390x/cert-store.c       | 223 ++++++++++++++++++++++++++++++++++++
>  hw/s390x/cert-store.h       |  39 +++++++
>  hw/s390x/ipl.c              |   9 ++
>  hw/s390x/ipl.h              |   3 +
>  hw/s390x/meson.build        |   1 +
>  include/hw/s390x/ipl/qipl.h |   2 +
>  6 files changed, 277 insertions(+)
>  create mode 100644 hw/s390x/cert-store.c
>  create mode 100644 hw/s390x/cert-store.h
> 
> diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
> new file mode 100644
> index 0000000000..3b4faa3738
> --- /dev/null
> +++ b/hw/s390x/cert-store.c
> @@ -0,0 +1,223 @@
> +/*
> + * 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"
> +
> +static const char *s390_get_boot_certificates(void)
> +{
> +    return S390_CCW_MACHINE(qdev_get_machine())->boot_certificates;
> +}
> +
> +static size_t cert2buf(char *path, char **cert_buf)
> +{
> +    size_t size;
> +
> +    if (!g_file_get_contents(path, cert_buf, &size, NULL) || size == 0) {
> +        return 0;
> +    }
> +
> +    return size;
> +}
> +
> +static S390IPLCertificate *init_cert_x509(size_t size, uint8_t *raw)

Add 'Error **errp' to this method, and don't use error_report


> +{
> +    S390IPLCertificate *q_cert = NULL;
> +    int key_id_size;
> +    int hash_size;
> +    int hash_type;
> +    Error *err = NULL;
> +
> +    g_autofree uint8_t *cert_der = NULL;
> +    size_t der_len = size;
> +    int rc;
> +
> +    hash_type = qcrypto_x509_get_signature_algorithm(raw, size, &err);

Pass in errp

> +    if (hash_type == -1) {

> +        error_report_err(err);

drop this

> +        return NULL;
> +    }
> +    if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
> +        error_report_err(err);

'err' is not set, in this branch, so you must use

  error_setg(errp, "Only SHA256 hash type is suppoorted, not %s",
             QCryptoHashAlgo_str(hash_type));
 
> +        return NULL;
> +    }
> +
> +    key_id_size = qcrypto_x509_get_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256, &err);

Pass in errp

> +    if (key_id_size == -1) {
> +        error_report_err(err);

Drop this

> +        return NULL;
> +    }

this can just be  'key_id_size = QCRYPTO_HASH_DIGEST_LEN_SHA256'

> +
> +    hash_size = QCRYPTO_HASH_DIGEST_LEN_SHA256;
> +
> +    rc = qcrypto_x509_convert_cert_der(raw, size, &cert_der, &der_len, &err);
> +    if (rc != 0) {
> +        error_report_err(err);
> +        return NULL;
> +    }
> +
> +    q_cert = g_new0(S390IPLCertificate, 1);
> +    q_cert->size = size;
> +    q_cert->der_size = der_len;
> +    q_cert->key_id_size = key_id_size;
> +    q_cert->hash_size = hash_size;
> +    q_cert->raw = raw;
> +    q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
> +
> +    return q_cert;
> +}

> +
> +static S390IPLCertificate *init_cert(char *path)
> +{
> +    char *buf;
> +    size_t size;
> +    char vc_name[VC_NAME_LEN_BYTES];
> +    g_autofree gchar *filename = NULL;
> +    S390IPLCertificate *qcert = NULL;

 Error *local_err = NULL;

> +
> +    filename = g_path_get_basename(path);
> +
> +    size = cert2buf(path, &buf);
> +    if (size == 0) {
> +        error_report("Failed to load certificate: %s", path);
> +        g_free(buf);
> +        return NULL;
> +    }
> +
> +    qcert = init_cert_x509(size, (uint8_t *)buf);

Pass in &local_err

> +    if (qcert == NULL) {
> +        error_report("Failed to initialize certificate: %s", path);

Use:

   error_reportf_err(local_err, "Failed to initialize certificate: %s", path);

> +        g_free(buf);
> +        return NULL;
> +    }
> +
> +    /*
> +     * Left justified certificate name with padding on the right with blanks.
> +     * Convert certificate name to EBCDIC.
> +     */
> +    strpadcpy(vc_name, VC_NAME_LEN_BYTES, filename, ' ');
> +    ebcdic_put(qcert->vc_name, vc_name, VC_NAME_LEN_BYTES);
> +
> +    return qcert;
> +}


> +
> +static void update_cert_store(S390IPLCertificateStore *cert_store,
> +                              S390IPLCertificate *qcert)
> +{
> +    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(qcert->key_id_size, 4);
> +    hash_buf_size = ROUND_UP(qcert->hash_size, 4);
> +    cert_buf_size = ROUND_UP(qcert->der_size, 4);
> +    data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
> +
> +    if (cert_store->max_cert_size < data_buf_size) {
> +        cert_store->max_cert_size = data_buf_size;
> +    }
> +
> +    cert_store->certs[cert_store->count] = *qcert;
> +    cert_store->total_bytes += data_buf_size;
> +    cert_store->count++;
> +}
> +
> +static GPtrArray *get_cert_paths(void)
> +{
> +    const char *paths_list;
> +    g_auto(GStrv) paths = NULL;
> +    gchar **paths_copy;
> +    GDir *dir = NULL;
> +    const gchar *filename;
> +    g_autoptr(GError) err = NULL;
> +    g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_full(0, g_free);
> +
> +    paths_list = s390_get_boot_certificates();
> +    if (paths_list == NULL) {
> +        return g_steal_pointer(&cert_path_builder);
> +    }
> +
> +    paths = g_strsplit(paths_list, ":", -1);
> +
> +    paths_copy = paths;
> +    while (*paths_copy) {
> +        if (!strcmp(*paths_copy, "")) {
> +            error_report("Empty path in certificate path list is not allowed");
> +            exit(1);
> +        }
> +
> +        struct stat st;
> +        if (stat(*paths_copy, &st) != 0) {
> +            error_report("Failed to stat path '%s': %s", *paths_copy, g_strerror(errno));
> +            exit(1);
> +        }
> +
> +        if (S_ISREG(st.st_mode)) {
> +            g_ptr_array_add(cert_path_builder, g_strdup(*paths_copy));
> +        } else if (S_ISDIR(st.st_mode)) {
> +            dir = g_dir_open(*paths_copy, 0, &err);
> +            if (dir == NULL) {
> +                error_report("Failed to open directory '%s': %s",
> +                             *paths_copy, err->message);
> +                exit(1);
> +            }
> +
> +            while ((filename = g_dir_read_name(dir))) {
> +                g_ptr_array_add(cert_path_builder,
> +                                g_build_filename(*paths_copy, filename, NULL));
> +            }

IMHO it should only append files with an extension of ".pem" to avoid picking
up any extraneous files in that location.

> +
> +            g_dir_close(dir);
> +        } else {
> +            error_report("Path '%s' is neither a file nor a directory", *paths_copy);
> +        }
> +
> +        paths_copy += 1;
> +    }
> +
> +    return g_steal_pointer(&cert_path_builder);
> +}



With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



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

* Re: [PATCH v4 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
  2025-07-11 21:10 ` [PATCH v4 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
@ 2025-07-22 16:20   ` Daniel P. Berrangé
  0 siblings, 0 replies; 62+ messages in thread
From: Daniel P. Berrangé @ 2025-07-22 16:20 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, jrossi, qemu-s390x,
	qemu-devel, walling, jjherne, pasic, borntraeger, farman,
	mjrosato, iii

On Fri, Jul 11, 2025 at 05:10:44PM -0400, Zhuoying Cai wrote:
> Introduce new helper functions to extract certificate metadata needed for
> DIAG 320 subcode 2:
> 
> qcrypto_x509_get_cert_version() - retrieves version of a certificate
> 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
> 
> These functions provide support for metadata extraction and validity checking
> for X.509 certificates.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  crypto/x509-utils.c         | 199 ++++++++++++++++++++++++++++++++++++
>  include/crypto/x509-utils.h |  76 +++++++++++++-
>  2 files changed, 272 insertions(+), 3 deletions(-)
> 
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index d2cf790d5b..135f83f55e 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -55,6 +55,14 @@ static const int gnutls_to_qcrypto_sig_alg_map[] = {
>      [GNUTLS_SIGN_ECDSA_SHA512] = QCRYPTO_SIG_ALGO_ECDSA_SHA512,
>  };
>  
> +static const int gnutls_to_qcrypto_pk_alg_map[] = {
> +    [GNUTLS_PK_UNKNOWN] = QCRYPTO_PK_ALGO_UNKNOWN,
> +    [GNUTLS_PK_RSA] = QCRYPTO_PK_ALGO_RSA,
> +    [GNUTLS_PK_DSA] = QCRYPTO_PK_ALGO_DSA,
> +    [GNUTLS_PK_DH] = QCRYPTO_PK_ALGO_DH,
> +    [GNUTLS_PK_ECDSA] = QCRYPTO_PK_ALGO_ECDSA,
> +};
> +
>  int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
>                                    uint8_t **result, size_t *resultlen,
>                                    Error **errp)
> @@ -195,6 +203,169 @@ cleanup:
>      return ret;
>  }
>  
> +int qcrypto_x509_get_cert_version(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};
> +
> +    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_version(crt);
> +    if (rc < 0) {
> +        error_setg(errp, "Failed to get certificate version: %s", gnutls_strerror(rc));
> +        goto cleanup;
> +    }
> +
> +    ret = rc;
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}

I'm not convinced this method needs to exist. QEMU doesn't care about the
cert version, and if something else consuming the data from guest OS/firmware
cares, then it can validate the version.

> +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 >= G_N_ELEMENTS(gnutls_to_qcrypto_pk_alg_map)) {
> +        error_setg(errp, "Unknown public key algorithm %d", rc);
> +        goto cleanup;
> +    }
> +
> +    ret = gnutls_to_qcrypto_pk_alg_map[rc];
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}
> +
> +int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
> +                                 QCryptoKeyidFlags flag,

IMHO this can just use  "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};
> +
> +    *resultlen = qcrypto_x509_get_keyid_len(qcrypto_to_gnutls_keyid_flags_map[flag],
> +                                            errp);
> +    if (*resultlen == -1) {
> +        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;
> +    }
> +
> +    *result = g_malloc0(*resultlen);
> +    if (gnutls_x509_crt_get_key_id(crt,
> +                                   qcrypto_to_gnutls_keyid_flags_map[flag],
> +                                   *result, resultlen) != 0) {
> +        error_setg(errp, "Failed to get key ID from certificate");
> +        goto cleanup;
> +    }
> +
> +    ret = 0;
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}


> @@ -53,10 +61,10 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>   * @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 replaced by the actual size of the DER-encoded certificate)
> +               (will be updated with the actual size of the DER-encoded certificate)
>   * @errp: error pointer
>   *
> - * Convert given @cert from PEM to DER format.
> + * Convert the given @cert from PEM to DER format.
>   *
>   * Returns: 0 on success,
>   *         -1 on error.
> @@ -70,7 +78,7 @@ int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
>   * qcrypto_x509_get_keyid_len
>   * @flag: the key ID flag
>   *
> - * Determine the length of the key ID of the given @flag.
> + * Determine the length of the key ID corresponding to the given @flag.
>   *
>   * Returns: the length on success,
>   *          -1 on error.


Squash these changes into the arlier patch that introduced the mistakes,


With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



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

* Re: [PATCH v4 09/28] s390x/diag: Implement DIAG 320 subcode 2
  2025-07-11 21:10 ` [PATCH v4 09/28] s390x/diag: Implement " Zhuoying Cai
@ 2025-07-22 16:23   ` Daniel P. Berrangé
  2025-07-28 20:59   ` Collin Walling
  1 sibling, 0 replies; 62+ messages in thread
From: Daniel P. Berrangé @ 2025-07-22 16:23 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, jrossi, qemu-s390x,
	qemu-devel, walling, jjherne, pasic, borntraeger, farman,
	mjrosato, iii

On Fri, Jul 11, 2025 at 05:10:45PM -0400, 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.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  include/hw/s390x/ipl/diag320.h |  47 ++++++
>  target/s390x/diag.c            | 254 ++++++++++++++++++++++++++++++++-
>  2 files changed, 300 insertions(+), 1 deletion(-)
> 

>  int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
> @@ -191,8 +193,252 @@ out:
>      }
>  }
>  
> +static int diag_320_is_cert_valid(S390IPLCertificate qcert)
> +{
> +    int version;
> +    int rc;
> +    Error *err = NULL;
> +
> +    version = qcrypto_x509_get_cert_version(qcert.raw, qcert.size, &err);
> +    if (version < 0) {
> +        error_report_err(err);
> +        return -1;
> +    }

IMHO this isn't doing anything worthwhile given qemu accepts
any version at all here.

> +
> +    rc = qcrypto_x509_check_cert_times(qcert.raw, qcert.size, &err);
> +    if (rc != 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +
> +    return 0;
> +}
> +
> +static int diag_320_get_cert_info(VCEntry *vce, S390IPLCertificate qcert,
> +                                  uint8_t **cert_der, unsigned char **key_id_data,
> +                                  void **hash_data)
> +{
> +    int algo;
> +    int rc;
> +    Error *err = NULL;
> +
> +    /* key-type */
> +    algo = qcrypto_x509_get_pk_algorithm(qcert.raw, qcert.size, &err);
> +    if (algo < 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +    if (algo == QCRYPTO_PK_ALGO_RSA) {
> +        vce->key_type = DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
> +    }

Shouldn't this have an "else" clause to either fill in vce->key_type,
or raise an error

> +
> +    /* certificate in DER format */
> +    rc = qcrypto_x509_convert_cert_der(qcert.raw, qcert.size,
> +                                       cert_der, &qcert.der_size, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        goto out;
> +    }
> +
> +    /* VC format */
> +    vce->format = DIAG_320_VCE_FORMAT_X509_DER;
> +
> +    /* key id and key id len */
> +    rc = qcrypto_x509_get_cert_key_id(qcert.raw, qcert.size,
> +                                      QCRYPTO_KEYID_FLAGS_SHA256,
> +                                      key_id_data, &qcert.key_id_size, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        goto out;
> +    }
> +    vce->keyid_len = cpu_to_be16(qcert.key_id_size);
> +
> +    /* hash type */
> +    if (qcert.hash_type == QCRYPTO_SIG_ALGO_RSA_SHA256) {
> +        vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
> +    }

Again, an "else" clause to either fill in vce->hash_type,
or raise an error ?

> +
> +    /* hash and hash len */
> +    *hash_data = g_malloc0(qcert.hash_size);
> +    rc = qcrypto_get_x509_cert_fingerprint(qcert.raw, qcert.size,
> +                                           QCRYPTO_HASH_ALGO_SHA256,
> +                                           *hash_data, &qcert.hash_size, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        goto out;
> +    }
> +    vce->hash_len = cpu_to_be16(qcert.hash_size);
> +
> +    return 0;
> +
> +out:
> +    g_clear_pointer(cert_der, g_free);
> +    g_clear_pointer(key_id_data, g_free);
> +    g_clear_pointer(hash_data, g_free);
> +
> +    return -1;
> +}



With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



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

* Re: [PATCH v4 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1
  2025-07-11 21:10 ` [PATCH v4 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
@ 2025-07-22 16:26   ` Daniel P. Berrangé
  0 siblings, 0 replies; 62+ messages in thread
From: Daniel P. Berrangé @ 2025-07-22 16:26 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, jrossi, qemu-s390x,
	qemu-devel, walling, jjherne, pasic, borntraeger, farman,
	mjrosato, iii

On Fri, Jul 11, 2025 at 05:10:47PM -0400, Zhuoying Cai wrote:
> 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>
> ---
>  crypto/x509-utils.c         | 110 ++++++++++++++++++++++++++++++++++++
>  include/crypto/x509-utils.h |  39 +++++++++++++
>  2 files changed, 149 insertions(+)
> 
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 135f83f55e..2b1ed5ee26 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,
> @@ -366,6 +367,98 @@ cleanup:
>      return ret;
>  }
>  
> +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 = { 0, };
> +
> +    rc = gnutls_pkcs7_init(&signature);
> +    if (rc < 0) {
> +        error_setg(errp, "Failed to initalize 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));
> +        gnutls_free(sig_datum_pem.data);
> +        goto cleanup;
> +    }
> +
> +    *result = g_steal_pointer(&sig_datum_pem.data);
> +    *resultlen = sig_datum_pem.size;
> +
> +    ret = 0;
> +
> +cleanup:
> +    gnutls_pkcs7_deinit(signature);
> +    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));
> +        return ret;

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));
> +        gnutls_x509_crt_deinit(crt);

Drop this

> +        return ret;

goto cleanup;

> +    }
> +
> +    rc = gnutls_pkcs7_init(&signature);
> +    if (rc < 0) {
> +        error_setg(errp, "Failed to initalize pkcs7 data: %s", gnutls_strerror(rc));
> +        gnutls_x509_crt_deinit(crt);
> +        return ret;

Likewise

> +     }
> +
> +    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);

Both of these are safe to call with NULL, hence we can add the
"goto cleanup" mentioned above.

> +    return ret;
> +}

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



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

* Re: [PATCH v4 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices
  2025-07-11 21:11 ` [PATCH v4 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
@ 2025-07-22 16:28   ` Daniel P. Berrangé
  0 siblings, 0 replies; 62+ messages in thread
From: Daniel P. Berrangé @ 2025-07-22 16:28 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, jrossi, qemu-s390x,
	qemu-devel, walling, jjherne, pasic, borntraeger, farman,
	mjrosato, iii

On Fri, Jul 11, 2025 at 05:11:02PM -0400, Zhuoying Cai wrote:
> The current approach to enabling secure boot relies on providing
> -secure-boot and -boot-certificates options, which apply to all boot
> devices.

This reference of -secure-boot and -boot-certificates presumably
is a left over from an older version of this series which didn't
use machine properties ?

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

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



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

* Re: [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility
  2025-07-21 21:39     ` Collin Walling
@ 2025-07-22 21:08       ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-22 21:08 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/21/25 17:39, Collin Walling wrote:
> On 7/21/25 17:26, Collin Walling wrote:
>> On 7/11/25 17:10, Zhuoying Cai wrote:
> 
> [...]
> 
>>> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
>>> index 8f655a4b7f..d5b3694600 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);
>>> +}
> 
> One more piece I forgot to add here:
> 
> This handler function should check if the required facilities are
> installed for DIAG 320.  For now, the Certificate Store facility is used
> to indicate DIAG 320 is provided and possibly subcodes 0-3 are
> available.  Please add a feature check here and throw a program
> specification interrupt if the facility is not available before
> handle_diag_320 is called.
> 

Ack, sorry.  I think I was too far into the weeds w.r.t. to fencing this
off that I more-or-less duplicated this feedback in another spot.  I
realize now that fencing this feature off in handle_diag_320() makes
more sense, as TCG also invokes it and should handled for that case as well.

My other sentence in the previous reply "In patch 4, you introduce the
feature bit for DIAG 320" should be disregarded, as I see it's already
there in that function.  My bad.

>>> +
>>>  #define DIAG_KVM_CODE_MASK 0x000000000000ffff
>>>  
>>>  static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
> 
> [...]
> 


-- 
Regards,
  Collin


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

* Re: [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility
  2025-07-21 21:26   ` Collin Walling
  2025-07-21 21:39     ` Collin Walling
@ 2025-07-23 17:50     ` Eric Farman
  1 sibling, 0 replies; 62+ messages in thread
From: Eric Farman @ 2025-07-23 17:50 UTC (permalink / raw)
  To: Collin Walling, Zhuoying Cai, thuth, berrange, richard.henderson,
	david, pbonzini, jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, mjrosato, iii

On Mon, 2025-07-21 at 17:26 -0400, Collin Walling wrote:
> On 7/11/25 17:10, Zhuoying Cai wrote:
> > DIAGNOSE 320 is introduced to support certificate store 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 a bitmap of which subcodes are supported.
> 
> Change to: "used to query the Installed Subcodes Mask (ISM)."
> 
> Based on my feedback below, make sure to include a statement that this
> subcode is only supported when the Certificate Store facility is enabled.
> 
> > 
> > Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> > ---
> >  include/hw/s390x/ipl/diag320.h | 17 ++++++++++++++
> >  target/s390x/diag.c            | 41 ++++++++++++++++++++++++++++++++++
> >  target/s390x/kvm/kvm.c         | 14 ++++++++++++
> >  target/s390x/s390x-internal.h  |  2 ++
> >  target/s390x/tcg/misc_helper.c |  7 ++++++
> >  5 files changed, 81 insertions(+)
> >  create mode 100644 include/hw/s390x/ipl/diag320.h
> > 
> > diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> > new file mode 100644
> > index 0000000000..713570545d
> > --- /dev/null
> > +++ b/include/hw/s390x/ipl/diag320.h
> > @@ -0,0 +1,17 @@
> > +/*
> > + * 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
> > +
> > +#endif
> > diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> > index cff9fbc4b0..d33c5daf38 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"
> > @@ -191,3 +192,43 @@ 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];
> > +    int rc;
> 
> Even though "rc" likely means "response code" in the context of DIAG, it
> screams "return code" in a void function, which looks a little odd.  I'd
> remove this variable and simply set `env->regs[r1 + 1] = DIAG_320_RC_*`
> instead, similar to how DIAG 308 handles this.
> 
> > +
> > +    if (env->psw.mask & PSW_MASK_PSTATE) {
> > +        s390_program_interrupt(env, PGM_PRIVILEGED, ra);
> > +        return;
> > +    }
> > +
> > +    if (!s390_has_feat(S390_FEAT_DIAG_320)) {
> > +        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> > +        return;
> > +    }
> > +
> > +    if (r1 & 1) {
> > +        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> > +        return;
> > +    }
> > +
> > +    switch (subcode) {
> 
> In patch 4, you introduce the feature bit for DIAG 320.  It is
> documented that subcodes 0-3 are only supported if this feature bit is
> on.  Please add a check for this feature.  Subcode 0 (and later 1 and 2)
> should not be handled if this feature bit is not present.
> 
> > +    case DIAG_320_SUBC_QUERY_ISM:
> > +        uint64_t ism =  0;
> 
> Technically, this subcode is suppose to write to an 8-word installed
> subcodes block (ISB).  Though I can't imagine that we'll grow beyond
> even the first word for quite some time.  I guess it's up to the caller
> to provide a proper ISB anyway.
> 
> I'd suggest the following:
> 
> change from `uint64_t` to `uint32_t ism_word0`
> 
> Add a comment:
> 
> /*
>  * 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.
>  */
> 
> > +
> > +        if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
> > +            s390_cpu_virt_mem_handle_exc(cpu, ra);
> > +            return;
> > +        }
> > +
> > +        rc = DIAG_320_RC_OK;
> 
> env->regs[r1 + 1] = DIAG_320_RC_OK;
> 
> > +        break;
> > +    default:
> > +        s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> > +        return;
> 
> As specified in the documentation, "response code 0x0102 indicates that
> the subcode is reserved or not supported on this model". There is no
> program specification when an incorrect subcode has been provided.

Yes to this, but subcode is limited to one byte so we still need to do a spec exception if r3 is
greater than 255 (you can do it before the switch, like is done to r1).

> 
> > +    }
> > +    env->regs[r1 + 1] = rc;
> 
> Remove this line.
> 
> > +}
> > diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> > index 8f655a4b7f..d5b3694600 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);
> > diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
> > index a4ba6227ab..86a652f833 100644
> > --- a/target/s390x/s390x-internal.h
> > +++ b/target/s390x/s390x-internal.h
> > @@ -400,6 +400,8 @@ int mmu_translate_real(CPUS390XState *env, target_ulong 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 f7101be574..412c34ed93 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;
> 


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

* Re: [PATCH v4 04/28] s390x: Guest support for Certificate Store Facility (CS)
  2025-07-21 21:30   ` Collin Walling
@ 2025-07-23 20:15     ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-23 20:15 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/21/25 17:30, Collin Walling wrote:
> On 7/11/25 17:10, Zhuoying Cai wrote:
>> DIAG 320 is supported when the certificate-store (CS) facility
>> is installed.
>>
>> 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.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> 
> For consistency with documentation, please change this from
> "S390_FEAT_DIAG_320" to "S390_FEAT_CERT_STORE".
> 

I also believe this patch and the subsequent patch #5 should be merged.
Functionally, this patch does nothing meaningful.  This introduces the
CPU feature and the next one makes use of it.  They're both relatively
small patches.

Make sure you merge the relevant information in the commit message.


-- 
Regards,
  Collin


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

* Re: [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1
  2025-07-11 21:10 ` [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2025-07-23 20:17   ` Eric Farman
  2025-07-28 22:01     ` Zhuoying Cai
  2025-07-23 22:15   ` Collin Walling
  1 sibling, 1 reply; 62+ messages in thread
From: Eric Farman @ 2025-07-23 20:17 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, mjrosato, iii

On Fri, 2025-07-11 at 17:10 -0400, Zhuoying Cai wrote:
> DIAG 320 subcode 1 provides information needed to determine
> the amount of storage to store one or more certificates.
> 
> 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.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  include/hw/s390x/ipl/diag320.h | 23 ++++++++++++++++++++++
>  target/s390x/diag.c            | 36 +++++++++++++++++++++++++++++++++-
>  2 files changed, 58 insertions(+), 1 deletion(-)
> 
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index 713570545d..3916a2915e 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -11,7 +11,30 @@
>  #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 VCSSB_MAX_LEN   128
> +#define VCE_HEADER_LEN  128
> +#define VCB_HEADER_LEN  64
> +
> +#define DIAG_320_ISM_QUERY_VCSI     0x4000000000000000
> +
> +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[7];
> +    uint32_t max_vce_len;
> +    uint32_t reserved4[3];
> +    uint32_t max_single_vcb_len;
> +    uint32_t total_vcb_len;
> +    uint32_t reserved5[10];
> +};
> +typedef struct VCStorageSizeBlock VCStorageSizeBlock;
> +
>  #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 7b9b47a171..1f7d0cb2f6 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -191,9 +191,13 @@ out:
>      }
>  }
>  
> +QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != 128,
> +                   "size of VCStorageSizeBlock is wrong");
> +

The message doesn't provide any information that QEMU_BUILD_BUG_ON wouldn't give us, so maybe just
use that.

Also, maybe replace 128 with VCSSB_MAX_LEN?

>  void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>  {
>      S390CPU *cpu = env_archcpu(env);
> +    S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
>      uint64_t subcode = env->regs[r3];
>      uint64_t addr = env->regs[r1];
>      int rc;
> @@ -215,13 +219,43 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>  
>      switch (subcode) {
>      case DIAG_320_SUBC_QUERY_ISM:
> -        uint64_t ism =  0;
> +        uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI);

Oh... Doesn't this suggest that there should have been a bit here stating that Query ISM itself is
supported? (I agree it's a catch-22, but...)

>  
>          if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
>              s390_cpu_virt_mem_handle_exc(cpu, ra);
>              return;
>          }
>  
> +        rc = DIAG_320_RC_OK;
> +        break;
> +    case DIAG_320_SUBC_QUERY_VCSI:
> +        VCStorageSizeBlock vcssb;
> +

Verify that addr is doubleword aligned?

> +        if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock),
> +                                  true)) {
> +            s390_program_interrupt(env, PGM_ADDRESSING, ra);
> +            return;
> +        }
> +
> +        if (!qcs || !qcs->count) {

For my own curiosity, can qcs actually be NULL? Looks like s390_ipl_get_certificate_store() returns
the address of the cert_store struct in the IPL device.

> +            vcssb.length = cpu_to_be32(4);
> +        } else {
> +            vcssb.length = cpu_to_be32(VCSSB_MAX_LEN);
> +            vcssb.version = 0;
> +            vcssb.total_vc_ct = cpu_to_be16(qcs->count);
> +            vcssb.max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
> +            vcssb.max_vce_len = cpu_to_be32(VCE_HEADER_LEN + qcs->max_cert_size);
> +            vcssb.max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
> +                                                   qcs->max_cert_size);
> +            vcssb.total_vcb_len = cpu_to_be32(VCB_HEADER_LEN +
> +                                              qcs->count * VCE_HEADER_LEN +
> +                                              qcs->total_bytes);
> +        }
> +
> +        if (s390_cpu_virt_mem_write(cpu, addr, r1, &vcssb, sizeof(VCStorageSizeBlock))) {
> +            s390_cpu_virt_mem_handle_exc(cpu, ra);
> +            return;
> +        }
>          rc = DIAG_320_RC_OK;
>          break;
>      default:


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

* Re: [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1
  2025-07-11 21:10 ` [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
  2025-07-23 20:17   ` Eric Farman
@ 2025-07-23 22:15   ` Collin Walling
  2025-07-23 22:20     ` Collin Walling
  1 sibling, 1 reply; 62+ messages in thread
From: Collin Walling @ 2025-07-23 22:15 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 17:10, Zhuoying Cai wrote:
> DIAG 320 subcode 1 provides information needed to determine
> the amount of storage to store one or more certificates.
> 
> The subcode value is denoted by setting the left-most bit
> of an 8-byte field.

This is general DIAG knowledge.  Remove this sentence.

> 
> The verification-certificate-storage-size block (VCSSB) contains
> the output data when the operation completes successfully.
> 

Please add more detail describing where the data that the VCSSB gets
filled with comes from (s390 cert store), how this subcode is useful
(e.g. getting num of certs, knowing how much space may need to be
allocated to store a cert).

There are some #defines for the VCE (cert entries) and VCB (subcode 2
data structure).  Please elaborate on them in the commit message.

> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  include/hw/s390x/ipl/diag320.h | 23 ++++++++++++++++++++++
>  target/s390x/diag.c            | 36 +++++++++++++++++++++++++++++++++-
>  2 files changed, 58 insertions(+), 1 deletion(-)
> 
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index 713570545d..3916a2915e 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -11,7 +11,30 @@
>  #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 VCSSB_MAX_LEN   128

This is just the size of the struct, right?  Why not just use sizeof
instead of this define?

> +#define VCE_HEADER_LEN  128
> +#define VCB_HEADER_LEN  64
> +
> +#define DIAG_320_ISM_QUERY_VCSI     0x4000000000000000

FYI: need a bit for subcode 0 (as Eric mentioned)

> +
> +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[7];
> +    uint32_t max_vce_len;
> +    uint32_t reserved4[3];
> +    uint32_t max_single_vcb_len;
> +    uint32_t total_vcb_len;
> +    uint32_t reserved5[10];
> +};
> +typedef struct VCStorageSizeBlock VCStorageSizeBlock;
> +
>  #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 7b9b47a171..1f7d0cb2f6 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -191,9 +191,13 @@ out:
>      }
>  }
>  
> +QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != 128,
> +                   "size of VCStorageSizeBlock is wrong");
> +

I'm unsure of why this is needed?  It's not necessarily up to the build
to determine that the data structure sizing hasn't been tampered with.
See below, which may help clarify where the 128 bytes need to be checked.

>  void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>  {
>      S390CPU *cpu = env_archcpu(env);
> +    S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
>      uint64_t subcode = env->regs[r3];
>      uint64_t addr = env->regs[r1];
>      int rc;
> @@ -215,13 +219,43 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>  
>      switch (subcode) {
>      case DIAG_320_SUBC_QUERY_ISM:
> -        uint64_t ism =  0;
> +        uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI);
>  
>          if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
>              s390_cpu_virt_mem_handle_exc(cpu, ra);
>              return;
>          }
>  
> +        rc = DIAG_320_RC_OK;
> +        break;
> +    case DIAG_320_SUBC_QUERY_VCSI:
> +        VCStorageSizeBlock vcssb;
> +
> +        if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock),
> +                                  true)) {
> +            s390_program_interrupt(env, PGM_ADDRESSING, ra);
> +            return;
> +        }

There is a piece missing of the "negotiation" between here and the
userspace.  Userspace must set the length to a "minimum of 128 [bytes]
...; otherwise, a response code of 0x0202 is returned ..."

So, you're going to need to read the VCSSB denoted by addr and check
that the length field is >= 128.  Otherwise set that response code.

Check out function `get_vcssb` in the kernel cert_store.c file for guidance.

> +
> +        if (!qcs || !qcs->count) {
> +            vcssb.length = cpu_to_be32(4);

Please use a #define instead of a literal.  4 is denoted as the length
set if no certs are found.

> +        } else {
> +            vcssb.length = cpu_to_be32(VCSSB_MAX_LEN);
> +            vcssb.version = 0;
> +            vcssb.total_vc_ct = cpu_to_be16(qcs->count);
> +            vcssb.max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
> +            vcssb.max_vce_len = cpu_to_be32(VCE_HEADER_LEN + qcs->max_cert_size);

This field is suppose to represent a constraint imposed by the cert
store that denotes the largest sized cert that it will allow to be
stored, not necessarily the "size of the largest cert currently stored"
(that is reserved for max_single_vcb_len below).  I do not think you
have such a limitation in place, so it may be safe to ignore it?

Kernel only prints this field for debugging.  Your code later on doesn't
utilize it.

> +            vcssb.max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
> +                                                   qcs->max_cert_size);
> +            vcssb.total_vcb_len = cpu_to_be32(VCB_HEADER_LEN +
> +                                              qcs->count * VCE_HEADER_LEN +
> +                                              qcs->total_bytes);
> +        }
> +
> +        if (s390_cpu_virt_mem_write(cpu, addr, r1, &vcssb, sizeof(VCStorageSizeBlock))) {
> +            s390_cpu_virt_mem_handle_exc(cpu, ra);
> +            return;
> +        }
>          rc = DIAG_320_RC_OK;
>          break;
>      default:


-- 
Regards,
  Collin


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

* Re: [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1
  2025-07-23 22:15   ` Collin Walling
@ 2025-07-23 22:20     ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-23 22:20 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/23/25 18:15, Collin Walling wrote:
> On 7/11/25 17:10, Zhuoying Cai wrote:
>> DIAG 320 subcode 1 provides information needed to determine
>> the amount of storage to store one or more certificates.
>>
>> The subcode value is denoted by setting the left-most bit
>> of an 8-byte field.
> 
> This is general DIAG knowledge.  Remove this sentence.
> 
>>
>> The verification-certificate-storage-size block (VCSSB) contains
>> the output data when the operation completes successfully.
>>
> 
> Please add more detail describing where the data that the VCSSB gets
> filled with comes from (s390 cert store), how this subcode is useful
> (e.g. getting num of certs, knowing how much space may need to be
> allocated to store a cert).
> 
> There are some #defines for the VCE (cert entries) and VCB (subcode 2
> data structure).  Please elaborate on them in the commit message.
> 

This may get satisfied when merging in the relevant documentation from
patch 28, but it's still good practice to summarize things in the commit
message.


-- 
Regards,
  Collin


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

* Re: [PATCH v4 09/28] s390x/diag: Implement DIAG 320 subcode 2
  2025-07-11 21:10 ` [PATCH v4 09/28] s390x/diag: Implement " Zhuoying Cai
  2025-07-22 16:23   ` Daniel P. Berrangé
@ 2025-07-28 20:59   ` Collin Walling
  2025-07-29 18:18     ` Zhuoying Cai
  1 sibling, 1 reply; 62+ messages in thread
From: Collin Walling @ 2025-07-28 20:59 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/11/25 17:10, 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.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  include/hw/s390x/ipl/diag320.h |  47 ++++++
>  target/s390x/diag.c            | 254 ++++++++++++++++++++++++++++++++-
>  2 files changed, 300 insertions(+), 1 deletion(-)
> 
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index 3916a2915e..a926cf7d25 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -12,14 +12,23 @@
>  
>  #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_INVAL_VCB_LEN   0x0204
> +#define DIAG_320_RC_BAD_RANGE       0x0302
>  
>  #define VCSSB_MAX_LEN   128
>  #define VCE_HEADER_LEN  128
>  #define VCB_HEADER_LEN  64
>  
>  #define DIAG_320_ISM_QUERY_VCSI     0x4000000000000000
> +#define DIAG_320_ISM_STORE_VC       0x2000000000000000
> +
> +#define DIAG_320_VCE_FLAGS_VALID                0x80
> +#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING    0
> +#define DIAG_320_VCE_FORMAT_X509_DER            1
> +#define DIAG_320_VCE_HASHTYPE_SHA2_256          1
>  
>  struct VCStorageSizeBlock {
>      uint32_t length;
> @@ -37,4 +46,42 @@ 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[3];
> +    uint8_t version;
> +    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;
> +    uint32_t name[16];
> +    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 1f7d0cb2f6..4641f88278 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -17,6 +17,7 @@
>  #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"
> @@ -24,6 +25,7 @@
>  #include "kvm/kvm_s390x.h"
>  #include "target/s390x/kvm/pv.h"
>  #include "qemu/error-report.h"
> +#include "crypto/x509-utils.h"
>  
>  
>  int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
> @@ -191,8 +193,252 @@ out:
>      }
>  }
>  
> +static int diag_320_is_cert_valid(S390IPLCertificate qcert)

This could be a boolean type and return true or false
instead of 0 or -1.

> +{
> +    int version;
> +    int rc;
> +    Error *err = NULL;
> +
> +    version = qcrypto_x509_get_cert_version(qcert.raw, qcert.size, &err);
> +    if (version < 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +
> +    rc = qcrypto_x509_check_cert_times(qcert.raw, qcert.size, &err);
> +    if (rc != 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +
> +    return 0;
> +}
> +
> +static int diag_320_get_cert_info(VCEntry *vce, S390IPLCertificate qcert,
> +                                  uint8_t **cert_der, unsigned char **key_id_data,
> +                                  void **hash_data)
> +{

This function is doing a lot: extracting info from the cert, converting
raw cert data into a X509 data, extracting key_id_data, and extracting
hash_data.  I'm afraid it's a bit tricky to follow along :(

I would suggest splitting this function into three: going in order of
key, hash, cert.  It'll help to consolidate the various fields and
reduce some overhead, while producing some easy-to-follow patterns.
Something like:

diag_320_convert_key(VCEntry *vce, S390IPLCertificate qcert) {
 // set all key-related fields & error checking
 // memcpy to vce
}

diag_320_convert_hash(VCEntry *vce, S390IPLCertificate qcert) {
 // set all hash-related fields & error checking
 // memcpy to vce
}

diag_320_convert_cert(VCEntry *vce, S390IPLCertificate qcert) {
 // set all cert-related fields & error checking
 // memcpy to vce
}

This will clean up the loop in handle_diag320_store_vc and the body of
build_vce.  Offsets should be set in the respective vce field.

build_vce and the loop could handle setting whichever remaining pieces
of data that do not directly correlate to the above three -- whatever
makes sense.

> +    int algo;
> +    int rc;
> +    Error *err = NULL;
> +
> +    /* key-type */
> +    algo = qcrypto_x509_get_pk_algorithm(qcert.raw, qcert.size, &err);
> +    if (algo < 0) {
> +        error_report_err(err);
> +        return -1;
> +    }
> +    if (algo == QCRYPTO_PK_ALGO_RSA) {
> +        vce->key_type = DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
> +    }
> +
> +    /* certificate in DER format */
> +    rc = qcrypto_x509_convert_cert_der(qcert.raw, qcert.size,
> +                                       cert_der, &qcert.der_size, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        goto out;
> +    }
> +
> +    /* VC format */
> +    vce->format = DIAG_320_VCE_FORMAT_X509_DER;
> +
> +    /* key id and key id len */
> +    rc = qcrypto_x509_get_cert_key_id(qcert.raw, qcert.size,
> +                                      QCRYPTO_KEYID_FLAGS_SHA256,
> +                                      key_id_data, &qcert.key_id_size, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        goto out;
> +    }
> +    vce->keyid_len = cpu_to_be16(qcert.key_id_size);
> +
> +    /* hash type */
> +    if (qcert.hash_type == QCRYPTO_SIG_ALGO_RSA_SHA256) {
> +        vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
> +    }
> +
> +    /* hash and hash len */
> +    *hash_data = g_malloc0(qcert.hash_size);
> +    rc = qcrypto_get_x509_cert_fingerprint(qcert.raw, qcert.size,
> +                                           QCRYPTO_HASH_ALGO_SHA256,
> +                                           *hash_data, &qcert.hash_size, &err);
> +    if (rc < 0) {
> +        error_report_err(err);
> +        goto out;
> +    }
> +    vce->hash_len = cpu_to_be16(qcert.hash_size);
> +
> +    return 0;
> +
> +out:
> +    g_clear_pointer(cert_der, g_free);
> +    g_clear_pointer(key_id_data, g_free);
> +    g_clear_pointer(hash_data, g_free);
> +
> +    return -1;
> +}
> +
> +static VCEntry *build_vce(S390IPLCertificate qcert, uint32_t vce_len, int idx,
> +                          size_t keyid_buf_size, size_t hash_buf_size)
> +{
> +    g_autofree VCEntry *vce = NULL;
> +    g_autofree uint8_t *cert_der = NULL;
> +    g_autofree unsigned char *key_id_data = NULL;
> +    g_autofree void *hash_data = NULL;
> +    int is_valid = -1;
> +    int rc;
> +
> +    /*
> +     * Construct VCE
> +     * Unused area following the VCE field contains zeros.
> +     */
> +    vce = g_malloc0(vce_len);
> +
> +    rc = diag_320_get_cert_info(vce, qcert, &cert_der, &key_id_data, &hash_data);
> +    if (rc) {
> +        return NULL;
> +    }

Hrm, not sure if returning NULL is the right call here.

> +
> +    is_valid = diag_320_is_cert_valid(qcert);

With changing this function to boolean, you can do:

	if (diag_320_is_cert_valid(qcert)) {
		vce->flags |= DIAG_320_VCE_FLAGS_VALID;
	}

Additionally, I think it makes more sense to handle any errors from
diag_320_get_cert_info by setting the VCV flag bit to invalid (aka
set to 0) and set the vce len to 72 (see below).

Judging by the fields encapsulated by those 72 bytes, you'll need to
have key-type, cert index, and name filled out as well (if possible).

> +
> +    vce->len = cpu_to_be32(vce_len);
> +    vce->cert_idx = cpu_to_be16(idx + 1);
> +    vce->cert_len = cpu_to_be32(qcert.der_size);
> +
> +    strncpy((char *)vce->name, (char *)qcert.vc_name, VC_NAME_LEN_BYTES);
> +
> +    /* VCE field offset is also word aligned */
> +    vce->hash_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size);
> +    vce->cert_offset = cpu_to_be16(VCE_HEADER_LEN + keyid_buf_size + hash_buf_size);
> +

You can use these calculated offsets...

> +    /* Write Key ID */
> +    memcpy(vce->cert_buf, key_id_data, qcert.key_id_size);
> +    /* Write Hash key */
> +    memcpy(vce->cert_buf + keyid_buf_size, hash_data, qcert.hash_size);
> +    /* Write VCE cert data */
> +    memcpy(vce->cert_buf + keyid_buf_size + hash_buf_size, cert_der, qcert.der_size);

...here instead, with vce + vce->*_offset

> +
> +    /* The certificate is valid and VCE contains the certificate */
> +    if (is_valid == 0) {
> +        vce->flags |= DIAG_320_VCE_FLAGS_VALID;
> +    }

else, vce->len = 72

> +
> +    return g_steal_pointer(&vce);
> +}
> +
> +static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
> +                                   S390IPLCertificateStore *qcs)
> +{
> +    g_autofree VCBlock *vcb = NULL;
> +    size_t vce_offset;
> +    size_t remaining_space;
> +    size_t keyid_buf_size;
> +    size_t hash_buf_size;
> +    size_t cert_buf_size;

The above three fields can go into the respective helper functions that
I mentioned earlier and reduce some of the bloat here.

> +    uint32_t vce_len;
> +    uint16_t first_vc_index;
> +    uint16_t last_vc_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);
> +

You need a check somewhere for no certs found in either the specified
range or no certs exist in the store at all:
 - VCB output len = 64
 - stored and remaining count = 0
 - response code 0x0001

> +    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;
> +    }
> +
> +    if (first_vc_index == 0) {
> +        /*
> +         * Zero is a valid index for the first and last VC index.
> +         * Zero index results in the VCB header and zero certificates returned.
> +         */
> +        if (last_vc_index == 0) {
> +            goto out;
> +        }
> +
> +        /* DIAG320 certificate store remains a one origin for cert entries */
> +        vcb->first_vc_index = 1;
> +        first_vc_index = 1;
> +    }
> +
> +    vce_offset = VCB_HEADER_LEN;
> +    vcb->out_len = VCB_HEADER_LEN;
> +    remaining_space = in_len - VCB_HEADER_LEN;
> +
> +    for (int i = first_vc_index - 1; i < last_vc_index && i < qcs->count; i++) {
> +        VCEntry *vce;
> +        S390IPLCertificate qcert = qcs->certs[i];
> +        /*
> +         * Each VCE is word aligned.
> +         * Each variable length field within the VCE is also word aligned.
> +         */
> +        keyid_buf_size = ROUND_UP(qcert.key_id_size, 4);
> +        hash_buf_size = ROUND_UP(qcert.hash_size, 4);
> +        cert_buf_size = ROUND_UP(qcert.der_size, 4);
> +        vce_len = VCE_HEADER_LEN + cert_buf_size + keyid_buf_size + hash_buf_size;

You could define & set the above four lines inside build_vce (or as
respective fields in the helper functions mentioned above).

The remaining space check could be done after the vce has been built.

> +
> +        /*
> +         * 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);
> +            break;
> +        }

You also need to check somewhere if there is enough space to store *at
least* the first cert in the range:
 - VCB output len = 64
 - stored count = 0
 - remaining count = // however are remaining
 - response code 0x0001

> +
> +        vce = build_vce(qcert, vce_len, i, keyid_buf_size, hash_buf_size);
> +        if (vce == NULL) {
> +            continue;
> +        }

See above, there shouldn't be a NULL case.

> +
> +        /* Write VCE */
> +        if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1, vce, vce_len)) {
> +            s390_cpu_virt_mem_handle_exc(cpu, ra);
> +            return -1;
> +        }> +
> +        vce_offset += vce_len;
> +        vcb->out_len += vce_len;
> +        remaining_space -= vce_len;
> +        vcb->stored_ct++;
> +
> +        g_free(vce);
> +    }
> +
> +    vcb->out_len = cpu_to_be32(vcb->out_len);
> +    vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
> +
> +out:
> +    /*
> +     * Write VCB header
> +     * All VCEs have been populated with the latest information
> +     * and write VCB header last.
> +     */
> +    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) != 128,
>                     "size of VCStorageSizeBlock is wrong");
> +QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != 64, "size of VCBlock is wrong");
> +QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != 128, "size of VCEntry is wrong");

I don't think these are necessary.

>  
>  void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>  {
> @@ -219,7 +465,7 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>  
>      switch (subcode) {
>      case DIAG_320_SUBC_QUERY_ISM:
> -        uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI);
> +        uint64_t ism = cpu_to_be64(DIAG_320_ISM_QUERY_VCSI | DIAG_320_ISM_STORE_VC);
>  
>          if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism, sizeof(ism))) {
>              s390_cpu_virt_mem_handle_exc(cpu, ra);
> @@ -258,6 +504,12 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>          }
>          rc = DIAG_320_RC_OK;
>          break;
> +    case DIAG_320_SUBC_STORE_VC:
> +        rc = handle_diag320_store_vc(cpu, addr, r1, ra, qcs);
> +        if (rc == -1) {
> +            return;
> +        }
> +        break;
>      default:
>          s390_program_interrupt(env, PGM_SPECIFICATION, ra);
>          return;


-- 
Regards,
  Collin


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

* Re: [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-07-21 21:50     ` Zhuoying Cai
@ 2025-07-28 21:05       ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-28 21:05 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/21/25 17:50, Zhuoying Cai wrote:
> On 7/11/25 6:50 PM, Collin Walling wrote:
>> On 7/11/25 5:10 PM, Zhuoying Cai wrote:

[...]

>>>  
>>> +typedef enum ZiplBootMode {
>>> +    ZIPL_NORMAL_MODE = 1,
>>> +    ZIPL_SECURE_AUDIT_MODE = 2,
>>> +} ZiplBootMode;
>>
>> These should be ZIPL_BOOT_MODE_*
>>
>> Also, is there a reason why the list starts at 1 and not defaulting to
>> the implicit 0?
>>
> 
> boot_mode is a global variable defined in pc-bios/s390-ccw/main.c, and
> it defaults to 0, which indicates that the boot mode hasn’t been
> determined yet.
> 
> We start the list at 1 to reserve 0 as the implicit “undefined” or “not
> yet set” value. The actual boot mode is only set later when boot_mode == 0:
>     if (boot_mode == 0) {
>         boot_mode = zipl_mode(iplb->hdr_flags);
>     }
> This allows us to distinguish between an unset state and valid boot modes.
> 

I would have thought to default the boot mode to NORMAL, but I haven't
had my eyes on the BIOS patches in a bit.  Unsure what makes sense.

For now, I'd suggest adding ZIPL_MODE_UNSPECIFIED (or something like
that) to the list.

[...]

-- 
Regards,
  Collin


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

* Re: [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1
  2025-07-23 20:17   ` Eric Farman
@ 2025-07-28 22:01     ` Zhuoying Cai
  0 siblings, 0 replies; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-28 22:01 UTC (permalink / raw)
  To: Eric Farman, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: walling, jjherne, pasic, borntraeger, mjrosato, iii

On 7/23/25 4:17 PM, Eric Farman wrote:
> On Fri, 2025-07-11 at 17:10 -0400, Zhuoying Cai wrote:

...

>> +        if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock),
>> +                                  true)) {
>> +            s390_program_interrupt(env, PGM_ADDRESSING, ra);
>> +            return;
>> +        }
>> +
>> +        if (!qcs || !qcs->count) {
> 
> For my own curiosity, can qcs actually be NULL? Looks like s390_ipl_get_certificate_store() returns
> the address of the cert_store struct in the IPL device.
>

Thanks for pointing it out. qcs can't be NULL for the reason you
mentioned. I'll remove the check since it's unnecessary.

>> +            vcssb.length = cpu_to_be32(4);
>> +        } else {
>> +            vcssb.length = cpu_to_be32(VCSSB_MAX_LEN);
>> +            vcssb.version = 0;
>> +            vcssb.total_vc_ct = cpu_to_be16(qcs->count);
>> +            vcssb.max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
>> +            vcssb.max_vce_len = cpu_to_be32(VCE_HEADER_LEN + qcs->max_cert_size);
>> +            vcssb.max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
>> +                                                   qcs->max_cert_size);
>> +            vcssb.total_vcb_len = cpu_to_be32(VCB_HEADER_LEN +
>> +                                              qcs->count * VCE_HEADER_LEN +
>> +                                              qcs->total_bytes);
>> +        }
>> +
>> +        if (s390_cpu_virt_mem_write(cpu, addr, r1, &vcssb, sizeof(VCStorageSizeBlock))) {
>> +            s390_cpu_virt_mem_handle_exc(cpu, ra);
>> +            return;
>> +        }
>>          rc = DIAG_320_RC_OK;
>>          break;
>>      default:



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

* Re: [PATCH v4 09/28] s390x/diag: Implement DIAG 320 subcode 2
  2025-07-28 20:59   ` Collin Walling
@ 2025-07-29 18:18     ` Zhuoying Cai
  2025-07-29 18:56       ` Collin Walling
  0 siblings, 1 reply; 62+ messages in thread
From: Zhuoying Cai @ 2025-07-29 18:18 UTC (permalink / raw)
  To: Collin Walling, thuth, berrange, richard.henderson, david,
	pbonzini, jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

Thank you for the feedback!

On 7/28/25 4:59 PM, Collin Walling wrote:
> On 7/11/25 17:10, Zhuoying Cai wrote:

...

>> +static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
>> +                                   S390IPLCertificateStore *qcs)
>> +{
>> +    g_autofree VCBlock *vcb = NULL;
>> +    size_t vce_offset;
>> +    size_t remaining_space;
>> +    size_t keyid_buf_size;
>> +    size_t hash_buf_size;
>> +    size_t cert_buf_size;
> 
> The above three fields can go into the respective helper functions that
> I mentioned earlier and reduce some of the bloat here.
> 
>> +    uint32_t vce_len;
>> +    uint16_t first_vc_index;
>> +    uint16_t last_vc_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);
>> +
> 
> You need a check somewhere for no certs found in either the specified
> range or no certs exist in the store at all:
>  - VCB output len = 64
>  - stored and remaining count = 0
>  - response code 0x0001
>
>> +    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;
>> +    }
>> +
>> +    if (first_vc_index == 0) {
>> +        /*
>> +         * Zero is a valid index for the first and last VC index.
>> +         * Zero index results in the VCB header and zero certificates returned.
>> +         */
>> +        if (last_vc_index == 0) {
>> +            goto out;
>> +        }
>> +
>> +        /* DIAG320 certificate store remains a one origin for cert entries */
>> +        vcb->first_vc_index = 1;
>> +        first_vc_index = 1;
>> +    }
>> +
>> +    vce_offset = VCB_HEADER_LEN;
>> +    vcb->out_len = VCB_HEADER_LEN;
>> +    remaining_space = in_len - VCB_HEADER_LEN;
>> +

Re: check for no certs found in either the specified range or no certs
exist.

This case is already handled.

vcb->out_len = VCB_HEADER_LEN is set outside the for loop. If the index
is invalid, the loop won’t execute, and both the stored and remaining VC
counts remain unchanged at 0.

>> +    for (int i = first_vc_index - 1; i < last_vc_index && i < qcs->count; i++) {
>> +        VCEntry *vce;
>> +        S390IPLCertificate qcert = qcs->certs[i];
>> +        /*
>> +         * Each VCE is word aligned.
>> +         * Each variable length field within the VCE is also word aligned.
>> +         */
>> +        keyid_buf_size = ROUND_UP(qcert.key_id_size, 4);
>> +        hash_buf_size = ROUND_UP(qcert.hash_size, 4);
>> +        cert_buf_size = ROUND_UP(qcert.der_size, 4);
>> +        vce_len = VCE_HEADER_LEN + cert_buf_size + keyid_buf_size + hash_buf_size;
> 
> You could define & set the above four lines inside build_vce (or as
> respective fields in the helper functions mentioned above).
> 
> The remaining space check could be done after the vce has been built.
> 
>> +
>> +        /*
>> +         * 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);
>> +            break;
>> +        }
> 
> You also need to check somewhere if there is enough space to store *at
> least* the first cert in the range:
>  - VCB output len = 64
>  - stored count = 0
>  - remaining count = // however are remaining
>  - response code 0x0001
> 

This case is also covered by the if statement above:

	if (remaining_space < vce_len) {
            vcb->remain_ct = cpu_to_be16(last_vc_index - i);
            break;
        }

Response code 0x0001 is returned at the end of the function for both cases.

>> +
>> +        vce = build_vce(qcert, vce_len, i, keyid_buf_size, hash_buf_size);
>> +        if (vce == NULL) {
>> +            continue;
>> +        }
> 
> See above, there shouldn't be a NULL case.
> 
>> +
>> +        /* Write VCE */
>> +        if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1, vce, vce_len)) {
>> +            s390_cpu_virt_mem_handle_exc(cpu, ra);
>> +            return -1;
>> +        }> +
>> +        vce_offset += vce_len;
>> +        vcb->out_len += vce_len;
>> +        remaining_space -= vce_len;
>> +        vcb->stored_ct++;
>> +
>> +        g_free(vce);
>> +    }
>> +
>> +    vcb->out_len = cpu_to_be32(vcb->out_len);
>> +    vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
>> +
>> +out:
>> +    /*
>> +     * Write VCB header
>> +     * All VCEs have been populated with the latest information
>> +     * and write VCB header last.
>> +     */
>> +    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;
>> +}

...




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

* Re: [PATCH v4 09/28] s390x/diag: Implement DIAG 320 subcode 2
  2025-07-29 18:18     ` Zhuoying Cai
@ 2025-07-29 18:56       ` Collin Walling
  0 siblings, 0 replies; 62+ messages in thread
From: Collin Walling @ 2025-07-29 18:56 UTC (permalink / raw)
  To: Zhuoying Cai, thuth, berrange, richard.henderson, david, pbonzini,
	jrossi, qemu-s390x, qemu-devel
  Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii

On 7/29/25 14:18, Zhuoying Cai wrote:
> Thank you for the feedback!
> 
> On 7/28/25 4:59 PM, Collin Walling wrote:
>> On 7/11/25 17:10, Zhuoying Cai wrote:
> 
> ...
> 

[...]

>>
>> You need a check somewhere for no certs found in either the specified
>> range or no certs exist in the store at all:
>>  - VCB output len = 64
>>  - stored and remaining count = 0
>>  - response code 0x0001
>>
>>> +    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;
>>> +    }
>>> +
>>> +    if (first_vc_index == 0) {
>>> +        /*
>>> +         * Zero is a valid index for the first and last VC index.
>>> +         * Zero index results in the VCB header and zero certificates returned.
>>> +         */
>>> +        if (last_vc_index == 0) {
>>> +            goto out;
>>> +        }
>>> +
>>> +        /* DIAG320 certificate store remains a one origin for cert entries */
>>> +        vcb->first_vc_index = 1;
>>> +        first_vc_index = 1;
>>> +    }
>>> +
>>> +    vce_offset = VCB_HEADER_LEN;
>>> +    vcb->out_len = VCB_HEADER_LEN;
>>> +    remaining_space = in_len - VCB_HEADER_LEN;
>>> +
> 
> Re: check for no certs found in either the specified range or no certs
> exist.
> 
> This case is already handled.
> 
> vcb->out_len = VCB_HEADER_LEN is set outside the for loop. If the index
> is invalid, the loop won’t execute, and both the stored and remaining VC
> counts remain unchanged at 0.
> 
>>> +    for (int i = first_vc_index - 1; i < last_vc_index && i < qcs->count; i++) {
>>> +        VCEntry *vce;
>>> +        S390IPLCertificate qcert = qcs->certs[i];
>>> +        /*
>>> +         * Each VCE is word aligned.
>>> +         * Each variable length field within the VCE is also word aligned.
>>> +         */
>>> +        keyid_buf_size = ROUND_UP(qcert.key_id_size, 4);
>>> +        hash_buf_size = ROUND_UP(qcert.hash_size, 4);
>>> +        cert_buf_size = ROUND_UP(qcert.der_size, 4);
>>> +        vce_len = VCE_HEADER_LEN + cert_buf_size + keyid_buf_size + hash_buf_size;
>>
>> You could define & set the above four lines inside build_vce (or as
>> respective fields in the helper functions mentioned above).
>>
>> The remaining space check could be done after the vce has been built.
>>
>>> +
>>> +        /*
>>> +         * 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);
>>> +            break;
>>> +        }
>>
>> You also need to check somewhere if there is enough space to store *at
>> least* the first cert in the range:
>>  - VCB output len = 64
>>  - stored count = 0
>>  - remaining count = // however are remaining
>>  - response code 0x0001
>>
> 
> This case is also covered by the if statement above:
> 
> 	if (remaining_space < vce_len) {
>             vcb->remain_ct = cpu_to_be16(last_vc_index - i);
>             break;
>         }
> 
> Response code 0x0001 is returned at the end of the function for both cases.
> 

Sorry, I neglected to mentally trace some different loop variables to
see that these cases are indeed handled.  Thanks for clarifying!

[...]

-- 
Regards,
  Collin


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

end of thread, other threads:[~2025-07-29 21:38 UTC | newest]

Thread overview: 62+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-11 21:10 [PATCH v4 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 01/28] Add boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
2025-07-22 15:56   ` Daniel P. Berrangé
2025-07-11 21:10 ` [PATCH v4 02/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
2025-07-22 16:06   ` Daniel P. Berrangé
2025-07-11 21:10 ` [PATCH v4 03/28] hw/s390x/ipl: Create " Zhuoying Cai
2025-07-22 16:14   ` Daniel P. Berrangé
2025-07-11 21:10 ` [PATCH v4 04/28] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
2025-07-21 21:30   ` Collin Walling
2025-07-23 20:15     ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 05/28] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
2025-07-21 21:26   ` Collin Walling
2025-07-21 21:39     ` Collin Walling
2025-07-22 21:08       ` Collin Walling
2025-07-23 17:50     ` Eric Farman
2025-07-11 21:10 ` [PATCH v4 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
2025-07-23 20:17   ` Eric Farman
2025-07-28 22:01     ` Zhuoying Cai
2025-07-23 22:15   ` Collin Walling
2025-07-23 22:20     ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
2025-07-22 16:20   ` Daniel P. Berrangé
2025-07-11 21:10 ` [PATCH v4 09/28] s390x/diag: Implement " Zhuoying Cai
2025-07-22 16:23   ` Daniel P. Berrangé
2025-07-28 20:59   ` Collin Walling
2025-07-29 18:18     ` Zhuoying Cai
2025-07-29 18:56       ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
2025-07-22 16:26   ` Daniel P. Berrangé
2025-07-11 21:10 ` [PATCH v4 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 16/28] hw/s390x/ipl: Set iplb->len to maximum length of " Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 17/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
2025-07-14 20:33   ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 18/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
2025-07-14 20:53   ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 19/28] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
2025-07-14 22:10   ` Collin Walling
2025-07-15 15:59     ` Zhuoying Cai
2025-07-15 21:48       ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 20/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
2025-07-11 22:50   ` Collin Walling
2025-07-14 14:54     ` Jared Rossi
2025-07-14 15:34       ` Thomas Huth
2025-07-14 17:46         ` Collin Walling
2025-07-21 21:50     ` Zhuoying Cai
2025-07-28 21:05       ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 21/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
2025-07-14 22:13   ` Collin Walling
2025-07-11 21:10 ` [PATCH v4 22/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
2025-07-11 21:10 ` [PATCH v4 23/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
2025-07-11 21:11 ` [PATCH v4 24/28] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
2025-07-11 21:11 ` [PATCH v4 25/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
2025-07-11 21:11 ` [PATCH v4 26/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
2025-07-22 16:28   ` Daniel P. Berrangé
2025-07-11 21:11 ` [PATCH v4 27/28] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
2025-07-11 21:11 ` [PATCH v4 28/28] docs: Add secure IPL documentation Zhuoying Cai
2025-07-21 19:13   ` Collin Walling

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).