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

Changelog v1->v2:

- Fix typos in patches
- Edit cover letter
- Add secure IPL documentation

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

hw/s390x/ipl: Create Certificate Store
- Define internal GNUTLS-related APIs
- Add check to only accept certificates using SHA-256 hashing
- Recalculate data_buf_size to ensure word alignment
- Clean up memory allocation
- Refactor functions for clarity

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

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

s390x/diag: Implement DIAG 320 Subcode 2
- Edit commit message for clarity
- Define internal GNUTLS-related APIs
- Rename data structure variables
- Ensure length fields in VCE are word-aligned
- Handle the VC index 0 case
- General refactoring

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

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

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

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

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

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

	openssl req -new -x509 -newkey rsa:2048 -keyout mykey.priv
	-outform DER -out mycert.der -days 36500 -subj "/CN=My Name/"
	-nodes

Use an RSA private key for signing.

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 mykey.priv mycert.der /boot/vmlinuz-…

	./sign-file sha256 mykey.priv mycert.der /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.der

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

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

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 (23):
  Add -boot-certificates to s390-ccw-virtio machine type option
  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
  s390x/diag: Implement DIAG 320 subcode 2
  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/system/s390x: Add secure IPL documentation

 crypto/meson.build                  |   5 +-
 crypto/x509-utils.c                 | 421 +++++++++++++++++++++++++++-
 docs/system/s390x/secure-ipl.rst    | 249 ++++++++++++++++
 hw/s390x/cert-store.c               | 242 ++++++++++++++++
 hw/s390x/cert-store.h               |  39 +++
 hw/s390x/ipl.c                      |  61 +++-
 hw/s390x/ipl.h                      |  29 +-
 hw/s390x/meson.build                |   1 +
 hw/s390x/s390-virtio-ccw.c          |  44 +++
 hw/s390x/sclp.c                     |   2 +
 include/crypto/x509-utils.h         |  20 ++
 include/hw/s390x/ipl/diag308.h      |  34 +++
 include/hw/s390x/ipl/diag320.h      |  89 ++++++
 include/hw/s390x/ipl/diag508.h      |  37 +++
 include/hw/s390x/ipl/qipl.h         |   8 +-
 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          | 306 ++++++++++++++++++--
 pc-bios/s390-ccw/bootmap.h          |   9 +
 pc-bios/s390-ccw/iplb.h             | 113 +++++++-
 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       | 412 +++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h       | 131 +++++++++
 qapi/crypto.json                    | 100 +++++++
 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           |   6 +
 target/s390x/diag.c                 | 410 ++++++++++++++++++++++++++-
 target/s390x/gen-features.c         |   3 +
 target/s390x/kvm/kvm.c              |  36 +++
 target/s390x/s390x-internal.h       |   4 +
 39 files changed, 2964 insertions(+), 80 deletions(-)
 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] 49+ messages in thread

* [PATCH v2 01/25] Add -boot-certificates to s390-ccw-virtio machine type option
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-13 14:58   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 02/25] hw/s390x/ipl: Create certificate store Zhuoying Cai
                   ` (23 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, zycai

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

The `-boot-certificates /path/dir:/path/file` option 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 192feb128b..f82f78255a 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -824,6 +824,22 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
     s390_ipl_fmt_loadparm(ms->loadparm, val, errp);
 }
 
+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);
@@ -878,6 +894,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 direcotry 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 fc4112fbf5..ed25939243 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 dc694a99a3..e592f6a757 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 s390-ccw host.
+        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] 49+ messages in thread

* [PATCH v2 02/25] hw/s390x/ipl: Create certificate store
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 01/25] Add -boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-14  5:43   ` Thomas Huth
  2025-05-14  9:03   ` Daniel P. Berrangé
  2025-05-08 22:50 ` [PATCH v2 03/25] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
                   ` (22 subsequent siblings)
  24 siblings, 2 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, zycai

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

Load certificates from the -boot-certificate option into the cert store.

Currently, only x509 certificates in DER format and uses SHA-256 hashing
algorithm are supported, as these are the types required for secure boot
on s390.

Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
 crypto/meson.build          |   5 +-
 crypto/x509-utils.c         | 163 ++++++++++++++++++++++++
 hw/s390x/cert-store.c       | 242 ++++++++++++++++++++++++++++++++++++
 hw/s390x/cert-store.h       |  39 ++++++
 hw/s390x/ipl.c              |   9 ++
 hw/s390x/ipl.h              |   3 +
 hw/s390x/meson.build        |   1 +
 include/crypto/x509-utils.h |   6 +
 include/hw/s390x/ipl/qipl.h |   3 +
 qapi/crypto.json            |  80 ++++++++++++
 10 files changed, 547 insertions(+), 4 deletions(-)
 create mode 100644 hw/s390x/cert-store.c
 create mode 100644 hw/s390x/cert-store.h

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..0b8cfc9022 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -11,6 +11,12 @@
 #include "qemu/osdep.h"
 #include "qapi/error.h"
 #include "crypto/x509-utils.h"
+
+/*
+ * Surround with GNUTLS marco to ensure the interfaces are
+ * still available when GNUTLS is not enabled.
+ */
+#ifdef CONFIG_GNUTLS
 #include <gnutls/gnutls.h>
 #include <gnutls/crypto.h>
 #include <gnutls/x509.h>
@@ -25,6 +31,94 @@ 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__MAX] = {
+    [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 qcrypto_to_gnutls_cert_fmt_map[QCRYPTO_CERT_FMT__MAX] = {
+    [QCRYPTO_CERT_FMT_DER] = GNUTLS_X509_FMT_DER,
+    [QCRYPTO_CERT_FMT_PEM] = GNUTLS_X509_FMT_PEM,
+};
+
+int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
+                                 QCryptoCertFmt fmt, Error **errp)
+{
+    int rc;
+    int ret = 0;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+
+    if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
+        error_setg(errp, "Unknown certificate format");
+        return ret;
+    }
+
+    if (gnutls_x509_crt_init(&crt) < 0) {
+        error_setg(errp, "Failed to initialize certificate");
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
+    if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
+        ret = 0;
+        goto cleanup;
+    }
+
+    ret = 1;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
+static int qcrypto_get_x509_cert_fmt(uint8_t *cert, size_t size, Error **errp)
+{
+    int fmt;
+
+    if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_DER, errp)) {
+        fmt = GNUTLS_X509_FMT_DER;
+    } else if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_PEM, errp)) {
+        fmt = GNUTLS_X509_FMT_PEM;
+    } else {
+        return -1;
+    }
+
+    return fmt;
+}
+
+int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
+{
+    if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
+        error_setg(errp, "Unknown hash algorithm");
+        return 0;
+    }
+
+    return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);
+}
+
+int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
+{
+    QCryptoHashAlgo alg;
+
+    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
+        error_setg(errp, "Unknown key id flag");
+        return 0;
+    }
+
+    alg = QCRYPTO_HASH_ALGO_SHA1;
+    if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
+        (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
+        alg = QCRYPTO_HASH_ALGO_SHA512;
+    } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
+        alg = QCRYPTO_HASH_ALGO_SHA256;
+    }
+
+    return qcrypto_get_x509_hash_len(alg, errp);
+}
+
 int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
                                       QCryptoHashAlgo alg,
                                       uint8_t *result,
@@ -74,3 +168,72 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
     gnutls_x509_crt_deinit(crt);
     return ret;
 }
+
+int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+    int rc = -1;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    gnutls_x509_crt_fmt_t fmt;
+
+    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
+    if (fmt == -1) {
+        error_setg(errp, "Certificate is neither in DER or PEM format");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_init(&crt) < 0) {
+        error_setg(errp, "Failed to initialize certificate");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
+        error_setg(errp, "Failed to import certificate");
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_get_signature_algorithm(crt);
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return rc;
+}
+
+#else /* ! CONFIG_GNUTLS */
+
+int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
+                                 QCryptoCertFmt fmt, Error **errp)
+{
+    error_setg(errp, "To get certificate format requires GNUTLS");
+    return -ENOTSUP;
+}
+
+int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
+{
+    error_setg(errp, "To get hash length requires GNUTLS");
+    return -ENOTSUP;
+}
+
+int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
+{
+    error_setg(errp, "To get key ID length requires GNUTLS");
+    return -ENOTSUP;
+}
+
+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, "To get fingerprint requires GNUTLS");
+    return -ENOTSUP;
+}
+
+int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "To get signature algorithm requires GNUTLS");
+    return -ENOTSUP;
+}
+
+#endif /* ! CONFIG_GNUTLS */
diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
new file mode 100644
index 0000000000..87bf9e381d
--- /dev/null
+++ b/hw/s390x/cert-store.c
@@ -0,0 +1,242 @@
+/*
+ * 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 "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, size_t max_size, char **cert_buf)
+{
+    size_t size;
+    g_autofree char *buf;
+
+    /*
+     * maximum allowed size of the certificate file to avoid consuming excessive memory
+     * (malformed or maliciously large files)
+     */
+    if (!g_file_get_contents(path, &buf, &size, NULL) ||
+        size == 0 || size > max_size) {
+        return 0;
+    }
+
+    *cert_buf = g_steal_pointer(&buf);
+
+    return size;
+}
+
+static S390IPLCertificate *init_cert_x509_der(size_t size, char *raw)
+{
+    S390IPLCertificate *q_cert = NULL;
+    int key_id_size;
+    int hash_size;
+    int is_der;
+    int hash_type;
+    Error *err = NULL;
+
+    is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
+                                         QCRYPTO_CERT_FMT_DER, &err);
+    /* return early if GNUTLS is not enabled */
+    if (is_der == -ENOTSUP) {
+        error_report("GNUTLS is not enabled");
+        return q_cert;
+    }
+    if (!is_der) {
+        error_report("The certificate is not in DER format");
+        return q_cert;
+    }
+
+    hash_type = qcrypto_get_x509_signature_algorithm((uint8_t *)raw, size, &err);
+    if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
+        error_report("The certificate does not use SHA-256 hashing");
+        return q_cert;
+    }
+
+    key_id_size = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256, &err);
+    if (key_id_size == 0) {
+        error_report("Failed to get certificate key ID size");
+        return q_cert;
+    }
+
+    hash_size = qcrypto_get_x509_hash_len(QCRYPTO_HASH_ALGO_SHA256, &err);
+    if (hash_size == 0) {
+        error_report("Failed to get certificate hash size");
+        return q_cert;
+    }
+
+    q_cert = g_new(S390IPLCertificate, 1);
+    q_cert->size = size;
+    q_cert->key_id_size = key_id_size;
+    q_cert->hash_size = hash_size;
+    q_cert->raw = raw;
+    q_cert->format = QCRYPTO_CERT_FMT_DER;
+    q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
+
+    return q_cert;
+}
+
+static int check_path_type(const char *path)
+{
+    struct stat path_stat;
+
+    if (stat(path, &path_stat) != 0) {
+        perror("stat");
+        return -1;
+    }
+
+    if (S_ISDIR(path_stat.st_mode)) {
+        return S_IFDIR;
+    } else if (S_ISREG(path_stat.st_mode)) {
+        return S_IFREG;
+    } else {
+        return -1;
+    }
+}
+
+static S390IPLCertificate *init_cert(char *paths)
+{
+    char *buf;
+    char vc_name[VC_NAME_LEN_BYTES];
+    g_autofree gchar *filename;
+    size_t size;
+    S390IPLCertificate *qcert = NULL;
+
+    filename = g_path_get_basename(paths);
+
+    size = cert2buf(paths, CERT_MAX_SIZE, &buf);
+    if (size == 0) {
+        error_report("Failed to load certificate: %s", paths);
+        return qcert;
+    }
+
+    qcert = init_cert_x509_der(size, buf);
+    if (qcert == NULL) {
+        error_report("Failed to initialize certificate: %s", paths);
+        g_free(buf);
+        return qcert;
+    }
+
+    /*
+     * 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->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 *path;
+    gchar **paths;
+    gchar **paths_copy;
+    int path_type;
+    GDir *dir = NULL;
+    gchar *cert_path;
+    const gchar *filename;
+    GPtrArray *cert_path_builder;
+
+    cert_path_builder = g_ptr_array_new();
+
+    path = s390_get_boot_certificates();
+    if (path == NULL) {
+        return cert_path_builder;
+    }
+
+    paths = g_strsplit(path, ":", -1);
+    /* save the original pointer for freeing later */
+    paths_copy = paths;
+    while (*paths) {
+        /* skip empty certificate path */
+        if (!strcmp(*paths, "")) {
+            paths += 1;
+            continue;
+        }
+
+        cert_path = NULL;
+        path_type = check_path_type(*paths);
+        if (path_type == S_IFREG) {
+            cert_path = g_strdup(*paths);
+            g_ptr_array_add(cert_path_builder, cert_path);
+        } else if (path_type == S_IFDIR) {
+            dir = g_dir_open(*paths, 0, NULL);
+
+            if (dir) {
+                while ((filename = g_dir_read_name(dir))) {
+                    cert_path = g_build_filename(*paths, filename, NULL);
+                    g_ptr_array_add(cert_path_builder, (gpointer) cert_path);
+                }
+
+                g_dir_close(dir);
+            }
+        }
+
+        paths += 1;
+    }
+
+    g_strfreev(paths_copy);
+    return 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++) {
+        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..04acc6c2e0
--- /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 key_id_size;
+    size_t hash_size;
+    char *raw;
+    QCryptoCertFmt format;
+    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 505cded490..ac1f7517ea 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -13,6 +13,7 @@
 #ifndef HW_S390_IPL_H
 #define HW_S390_IPL_H
 
+#include "cert-store.h"
 #include "cpu.h"
 #include "exec/target_page.h"
 #include "system/address-spaces.h"
@@ -35,6 +36,7 @@ int s390_ipl_pv_unpack(struct S390PVResponse *pv_resp);
 void s390_ipl_prepare_cpu(S390CPU *cpu);
 IplParameterBlock *s390_ipl_get_iplb(void);
 IplParameterBlock *s390_ipl_get_iplb_pv(void);
+S390IPLCertificateStore *s390_ipl_get_certificate_store(void);
 
 enum s390_reset {
     /* default is a reset not triggered by a CPU e.g. issued by QMP */
@@ -63,6 +65,7 @@ struct S390IPLState {
     IplParameterBlock iplb;
     IplParameterBlock iplb_pv;
     QemuIplParameters qipl;
+    S390IPLCertificateStore cert_store;
     uint64_t start_addr;
     uint64_t compat_start_addr;
     uint64_t bios_start_addr;
diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build
index 11e4e78b85..5b02f47155 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/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 1e99661a71..8fb263b9e1 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -19,4 +19,10 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
                                       size_t *resultlen,
                                       Error **errp);
 
+int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
+                                 QCryptoCertFmt fmt, Error **errp);
+int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp);
+int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp);
+int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
+
 #endif
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 6824391111..b8e7d1da71 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -20,6 +20,9 @@
 #define LOADPARM_LEN    8
 #define NO_LOADPARM "\0\0\0\0\0\0\0\0"
 
+#define MAX_CERTIFICATES 64
+#define CERT_MAX_SIZE     (1024 * 8)
+
 /*
  * The QEMU IPL Parameters will be stored at absolute address
  * 204 (0xcc) which means it is 32-bit word aligned but not
diff --git a/qapi/crypto.json b/qapi/crypto.json
index c9d967d782..2bbf29affe 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -612,3 +612,83 @@
   'base': { 'alg': 'QCryptoAkCipherAlgo' },
   'discriminator': 'alg',
   'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
+
+##
+# @QCryptoKeyidFlags:
+#
+# The supported flags for the key ID
+#
+# @sha1: SHA-1
+#
+# @sha256: SHA-256
+#
+# @sha512: SHA-512
+#
+# @best-known: BEST-KNOWN
+#
+# Since: 9.2
+##
+{ 'enum': 'QCryptoKeyidFlags',
+  'data': ['sha1', 'sha256', 'sha512', 'best-known']}
+
+##
+# @QCryptoCertFmt:
+#
+# The supported certificate encoding formats
+#
+# @der: DER
+#
+# @pem: PEM
+#
+# Since: 9.2
+##
+{ 'enum': 'QCryptoCertFmt',
+  'data': ['der', 'pem']}
+
+##
+# @QCryptoSigAlgo:
+#
+# Algorithms for digital signature
+#
+# @unknown: UNKNOWN
+#
+# @rsa-sha1: RSA-SHA1 or RSA-SHA
+#
+# @dsa-sha1: DSA-SHA1 or DSA-SHA
+#
+# @rsa-md5: RSA-MD5
+#
+# @rsa-md2: RSA-MD2
+#
+# @rsa-rmd160: RSA-RMD160
+#
+# @rsa-sha256: RSA-SHA256
+#
+# @rsa-sha384: RSA-SHA384
+#
+# @rsa-sha512: RSA-SHA512
+#
+# @rsa-sha224: RSA-SHA224
+#
+# @dsa-sha224: DSA-SHA224
+#
+# @dsa-sha256: DSA-SHA256
+#
+# @ecdsa-sha1: ECDSA-SHA1
+#
+# @ecdsa-sha224: ECDSA-SHA224
+#
+# @ecdsa-sha256: ECDSA-SHA256
+#
+# @ecdsa-sha384: ECDSA-SHA384
+#
+# @ecdsa-sha512: ECDSA-SHA512
+#
+# Since: 9.2
+##
+{ 'enum': 'QCryptoSigAlgo',
+  'data': ['unknown', 'rsa-sha1', 'dsa-sha1',
+           'rsa-md5', 'rsa-md2', 'rsa-rmd160',
+           'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
+           'dsa-sha224', 'dsa-sha256',
+           'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
-- 
2.49.0



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

* [PATCH v2 03/25] s390x: Guest support for Certificate Store Facility (CS)
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 01/25] Add -boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 02/25] hw/s390x/ipl: Create certificate store Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-14  6:11   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 04/25] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
                   ` (21 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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         | 1 +
 target/s390x/kvm/kvm.c              | 2 ++
 5 files changed, 7 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..65d38f546d 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, "diag320", 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 8951f1b36f..ab38fc9882 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 41840677ce..3d9fbe62ea 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[] = {
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index b9f1422197..6bad1713d2 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2487,6 +2487,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] 49+ messages in thread

* [PATCH v2 04/25] s390x/diag: Introduce DIAG 320 for certificate store facility
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (2 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 03/25] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-14  8:17   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 05/25] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
                   ` (20 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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>
---
 hw/s390x/ipl.h                 |  1 +
 include/hw/s390x/ipl/diag320.h | 17 +++++++++++++++
 target/s390x/diag.c            | 40 ++++++++++++++++++++++++++++++++++
 target/s390x/kvm/kvm.c         | 14 ++++++++++++
 target/s390x/s390x-internal.h  |  2 ++
 5 files changed, 74 insertions(+)
 create mode 100644 include/hw/s390x/ipl/diag320.h

diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index ac1f7517ea..d9c089928e 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -19,6 +19,7 @@
 #include "system/address-spaces.h"
 #include "system/memory.h"
 #include "hw/qdev-core.h"
+#include "hw/s390x/ipl/diag320.h"
 #include "hw/s390x/ipl/qipl.h"
 #include "qom/object.h"
 #include "target/s390x/kvm/pv.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 da44b0133e..21129dbc91 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -192,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 6bad1713d2..3ae00b9a6d 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
 
@@ -1561,6 +1562,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)
@@ -1591,6 +1602,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 */
-- 
2.49.0



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

* [PATCH v2 05/25] s390x/diag: Refactor address validation check from diag308_parm_check
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (3 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 04/25] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 06/25] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
                   ` (19 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 d9c089928e..254cf6a46a 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 21129dbc91..9d249831b3 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -64,9 +64,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] 49+ messages in thread

* [PATCH v2 06/25] s390x/diag: Implement DIAG 320 subcode 1
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (4 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 05/25] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-14 15:32   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 07/25] s390x/diag: Implement DIAG 320 subcode 2 Zhuoying Cai
                   ` (18 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 | 25 ++++++++++++++++++++++
 target/s390x/diag.c            | 38 +++++++++++++++++++++++++++++++++-
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index 713570545d..e91eb7238c 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -11,7 +11,32 @@
 #define S390X_DIAG320_H
 
 #define DIAG_320_SUBC_QUERY_ISM     0
+#define DIAG_320_SUBC_QUERY_VCSI    1
 
 #define DIAG_320_RC_OK              0x0001
+#define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
+
+#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];
+} QEMU_PACKED;
+typedef struct VCStorageSizeBlock \
+VCStorageSizeBlock;
 
 #endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 9d249831b3..0743f5ec0e 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -194,6 +194,7 @@ out:
 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 +216,48 @@ 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 = 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 (be32_to_cpu(vcssb.length) > 4 && be32_to_cpu(vcssb.length) < 128) {
+            rc = DIAG_320_RC_INVAL_VCSSB_LEN;
+            break;
+        }
+
+        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] 49+ messages in thread

* [PATCH v2 07/25] s390x/diag: Implement DIAG 320 subcode 2
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (5 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 06/25] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-14 16:18   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 08/25] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
                   ` (17 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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>
---
 crypto/x509-utils.c            | 204 ++++++++++++++++++++++++++++-
 include/crypto/x509-utils.h    |  10 ++
 include/hw/s390x/ipl/diag320.h |  47 +++++++
 qapi/crypto.json               |  20 +++
 target/s390x/diag.c            | 227 ++++++++++++++++++++++++++++++++-
 5 files changed, 506 insertions(+), 2 deletions(-)

diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 0b8cfc9022..51bd75d4eb 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -129,6 +129,7 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
     int hlen;
     gnutls_x509_crt_t crt;
     gnutls_datum_t datum = {.data = cert, .size = size};
+    gnutls_x509_crt_fmt_t fmt;
 
     if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
         error_setg(errp, "Unknown hash algorithm");
@@ -140,9 +141,15 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
         return -1;
     }
 
+    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
+    if (fmt == -1) {
+        error_setg(errp, "Certificate is neither in DER or PEM format");
+        return -1;
+    }
+
     gnutls_x509_crt_init(&crt);
 
-    if (gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM) != 0) {
+    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
         error_setg(errp, "Failed to import certificate");
         goto cleanup;
     }
@@ -199,6 +206,173 @@ cleanup:
     return rc;
 }
 
+int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
+{
+    int rc = -1;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    gnutls_x509_crt_fmt_t fmt;
+
+    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
+    if (fmt == -1) {
+        error_setg(errp, "Certificate is neither in DER or PEM format");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_init(&crt) < 0) {
+        error_setg(errp, "Failed to initialize certificate");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
+        error_setg(errp, "Failed to import certificate");
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_get_version(crt);
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return rc;
+}
+
+int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+    int rc = -1;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    time_t now = time(0);
+    gnutls_x509_crt_fmt_t fmt;
+
+    if (now == ((time_t)-1)) {
+        error_setg(errp, "Cannot get current time");
+        return rc;
+    }
+
+    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
+    if (fmt == -1) {
+        error_setg(errp, "Certificate is neither in DER or PEM format");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_init(&crt) < 0) {
+        error_setg(errp, "Failed to initialize certificate");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
+        error_setg(errp, "Failed to import certificate");
+        goto cleanup;
+    }
+
+    if (gnutls_x509_crt_get_expiration_time(crt) < now) {
+        error_setg(errp, "The certificate has expired");
+        goto cleanup;
+    }
+
+    if (gnutls_x509_crt_get_activation_time(crt) > now) {
+        error_setg(errp, "The certificate is not yet active");
+        goto cleanup;
+    }
+
+    rc = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return rc;
+}
+
+int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+    int rc = -1;
+    unsigned int bits;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    gnutls_x509_crt_fmt_t fmt;
+
+    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
+    if (fmt == -1) {
+        error_setg(errp, "Certificate is neither in DER or PEM format");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_init(&crt) < 0) {
+        error_setg(errp, "Failed to initialize certificate");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
+        error_setg(errp, "Failed to import certificate");
+        goto cleanup;
+    }
+
+    rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return rc;
+}
+
+int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoKeyidFlags flag,
+                                 uint8_t *result,
+                                 size_t *resultlen,
+                                 Error **errp)
+{
+    int ret = -1;
+    int keyid_len;
+    gnutls_x509_crt_t crt;
+    gnutls_datum_t datum = {.data = cert, .size = size};
+    gnutls_x509_crt_fmt_t fmt;
+
+    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
+        error_setg(errp, "Unknown key id flag");
+        return -1;
+    }
+
+    if (result == NULL) {
+        error_setg(errp, "No valid buffer given");
+        return -1;
+    }
+
+    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
+    if (fmt == -1) {
+        error_setg(errp, "Certificate is neither in DER or PEM format");
+        return -1;
+    }
+
+    if (gnutls_x509_crt_init(&crt)) {
+        error_setg(errp, "Failed to initialize certificate");
+        return -1;
+    }
+
+    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
+        error_setg(errp, "Failed to import certificate");
+        goto cleanup;
+    }
+
+    keyid_len = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256, errp);
+    if (*resultlen < keyid_len) {
+        error_setg(errp,
+                   "Result buffer size %zu is smaller than key id %d",
+                   *resultlen, keyid_len);
+        goto cleanup;
+    }
+
+    if (gnutls_x509_crt_get_key_id(crt,
+                                   qcrypto_to_gnutls_keyid_flags_map[flag],
+                                   result, resultlen) != 0) {
+        error_setg(errp, "Failed to get fingerprint from certificate");
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    return ret;
+}
+
 #else /* ! CONFIG_GNUTLS */
 
 int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
@@ -236,4 +410,32 @@ int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **err
     return -ENOTSUP;
 }
 
+int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "To get certificate version requires GNUTLS");
+    return -ENOTSUP;
+}
+
+int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "To get certificate times requires GNUTLS");
+    return -ENOTSUP;
+}
+
+int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+    error_setg(errp, "To get public key algorithm requires GNUTLS");
+    return -ENOTSUP;
+}
+
+int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoKeyidFlags flag,
+                                 uint8_t *result,
+                                 size_t *resultlen,
+                                 Error **errp)
+{
+    error_setg(errp, "To get key ID requires GNUTLS");
+    return -ENOTSUP;
+}
+
 #endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 8fb263b9e1..cf43de0b2c 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -25,4 +25,14 @@ int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp);
 int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp);
 int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
 
+int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp);
+int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp);
+int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp);
+
+int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
+                                 QCryptoKeyidFlags flag,
+                                 uint8_t *result,
+                                 size_t *resultlen,
+                                 Error **errp);
+
 #endif
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index e91eb7238c..dc1dae1d76 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -12,15 +12,24 @@
 
 #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_VCSSB_LEN 0x0202
+#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;
@@ -39,4 +48,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[];
+} QEMU_PACKED;
+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[];
+} QEMU_PACKED;
+typedef struct VCEntry VCEntry;
+
 #endif
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 2bbf29affe..4757bcd3db 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -692,3 +692,23 @@
            'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
            'dsa-sha224', 'dsa-sha256',
            'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
+
+##
+# @QCryptoPkAlgo:
+#
+# Algorithms for public-key
+#
+# @unknown: UNKNOWN
+#
+# @rsa: RSA
+#
+# @dsa: DSA
+#
+# @dh: DH
+#
+# @ecdsa: ECDSA
+#
+# Since: 9.2
+##
+{ 'enum': 'QCryptoPkAlgo',
+  'data': ['unknown', 'rsa', 'dsa', 'dh', 'ecdsa']}
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 0743f5ec0e..6fd59ac863 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -17,12 +17,14 @@
 #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/s390-virtio-ccw.h"
 #include "system/kvm.h"
 #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,6 +193,87 @@ out:
     }
 }
 
+static int diag_320_is_cert_valid(S390IPLCertificate qcert, Error **errp)
+{
+    int version;
+    int rc;
+
+    version = qcrypto_get_x509_cert_version((uint8_t *)qcert.raw, qcert.size, errp);
+    if (version < 0) {
+        return version == -ENOTSUP ? -ENOTSUP : 0;
+    }
+
+    rc = qcrypto_check_x509_cert_times((uint8_t *)qcert.raw, qcert.size, errp);
+    if (rc) {
+        return 0;
+    }
+
+    return 1;
+}
+
+static int diag_320_get_cert_info(VCEntry *vce, S390IPLCertificate qcert, int *is_valid,
+                                  unsigned char **key_id_data, void **hash_data)
+{
+    int algo;
+    int rc;
+    Error *err = NULL;
+
+    /* VCE flag (validity) */
+    *is_valid = diag_320_is_cert_valid(qcert, &err);
+    /* return early if GNUTLS is not enabled */
+    if (*is_valid == -ENOTSUP) {
+        error_report("GNUTLS is not supported");
+        return -1;
+    }
+
+    /* key-type */
+    algo = qcrypto_get_x509_pk_algorithm((uint8_t *)qcert.raw, qcert.size, &err);
+    if (algo == QCRYPTO_PK_ALGO_RSA) {
+        vce->key_type = DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
+    }
+
+    /* VC format */
+    if (qcert.format == QCRYPTO_CERT_FMT_DER) {
+        vce->format = DIAG_320_VCE_FORMAT_X509_DER;
+    }
+
+    /* key id and key id len */
+    *key_id_data = g_malloc0(qcert.key_id_size);
+    rc = qcrypto_get_x509_cert_key_id((uint8_t *)qcert.raw, qcert.size,
+                                      QCRYPTO_KEYID_FLAGS_SHA256,
+                                      *key_id_data, &qcert.key_id_size, &err);
+    if (rc < 0) {
+        error_report("Fail to retrieve certificate key ID");
+        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((uint8_t *)qcert.raw, qcert.size,
+                                           QCRYPTO_HASH_ALGO_SHA256,
+                                           *hash_data, &qcert.hash_size, &err);
+    if (rc < 0) {
+        error_report("Fail to retrieve certificate hash");
+        goto out;
+    }
+    vce->hash_len = cpu_to_be16(qcert.hash_size);
+
+    return 0;
+
+out:
+    g_free(*key_id_data);
+    g_free(*hash_data);
+
+    return -1;
+}
+
+
 void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
 {
     S390CPU *cpu = env_archcpu(env);
@@ -216,7 +299,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);
@@ -260,6 +343,148 @@ 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:
+        VCBlock *vcb;
+        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;
+        unsigned char *key_id_data = NULL;
+        void *hash_data = NULL;
+        int is_valid = 0;
+        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;
+        }
+
+        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) {
+            rc = DIAG_320_RC_INVAL_VCB_LEN;
+            g_free(vcb);
+            break;
+        }
+
+        if (first_vc_index > last_vc_index) {
+            rc = DIAG_320_RC_BAD_RANGE;
+            g_free(vcb);
+            break;
+        }
+
+        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;
+        }
+
+        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.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;
+            }
+
+            /*
+             * Construct VCE
+             * Unused area following the VCE field contains zeros.
+             */
+            vce = g_malloc0(vce_len);
+            vce->len = cpu_to_be32(vce_len);
+            vce->cert_idx = cpu_to_be16(i + 1);
+            vce->cert_len = cpu_to_be32(qcert.size);
+
+            strncpy((char *)vce->name, (char *)qcert.vc_name, VC_NAME_LEN_BYTES);
+
+            rc = diag_320_get_cert_info(vce, qcert, &is_valid, &key_id_data, &hash_data);
+            if (rc) {
+                g_free(vce);
+                continue;
+            }
+
+            /* 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, qcert.raw, qcert.size);
+
+            /* The certificate is valid and VCE contains the certificate */
+            if (is_valid) {
+                vce->flags |= DIAG_320_VCE_FLAGS_VALID;
+            }
+
+            /* Write VCE Header */
+            if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1, vce, vce_len)) {
+                s390_cpu_virt_mem_handle_exc(cpu, ra);
+                return;
+            }
+
+            vce_offset += vce_len;
+            vcb->out_len += vce_len;
+            remaining_space -= vce_len;
+            vcb->stored_ct++;
+
+            g_free(vce);
+            g_free(key_id_data);
+            g_free(hash_data);
+        }
+
+        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;
+        }
+        rc = DIAG_320_RC_OK;
+        g_free(vcb);
+        break;
     default:
         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
         return;
-- 
2.49.0



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

* [PATCH v2 08/25] s390x/diag: Introduce DIAG 508 for secure IPL operations
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (6 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 07/25] s390x/diag: Implement DIAG 320 subcode 2 Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 09/25] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
                   ` (16 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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>
---
 hw/s390x/ipl.h                 |  1 +
 include/hw/s390x/ipl/diag508.h | 15 +++++++++++++++
 target/s390x/diag.c            | 26 ++++++++++++++++++++++++++
 target/s390x/kvm/kvm.c         | 14 ++++++++++++++
 target/s390x/s390x-internal.h  |  2 ++
 5 files changed, 58 insertions(+)
 create mode 100644 include/hw/s390x/ipl/diag508.h

diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 254cf6a46a..b4d4566293 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -20,6 +20,7 @@
 #include "system/memory.h"
 #include "hw/qdev-core.h"
 #include "hw/s390x/ipl/diag320.h"
+#include "hw/s390x/ipl/diag508.h"
 #include "hw/s390x/ipl/qipl.h"
 #include "qom/object.h"
 #include "target/s390x/kvm/pv.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 6fd59ac863..954c95fe50 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -491,3 +491,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 3ae00b9a6d..9bf3ed694b 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
@@ -1572,6 +1573,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)
@@ -1605,6 +1616,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 */
-- 
2.49.0



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

* [PATCH v2 09/25] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (7 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 08/25] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-20  6:11   ` Thomas Huth
  2025-05-20  8:16   ` Daniel P. Berrangé
  2025-05-08 22:50 ` [PATCH v2 10/25] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
                   ` (15 subsequent siblings)
  24 siblings, 2 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 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>
---
 crypto/x509-utils.c            | 54 +++++++++++++++++++++++
 include/crypto/x509-utils.h    |  4 ++
 include/hw/s390x/ipl/diag508.h | 22 +++++++++
 target/s390x/diag.c            | 81 +++++++++++++++++++++++++++++++++-
 4 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 51bd75d4eb..56d9a42f39 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -20,6 +20,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,
@@ -373,6 +374,51 @@ cleanup:
     return ret;
 }
 
+int qcrypto_verify_x509_cert(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 = -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};
+    gnutls_x509_crt_fmt_t fmt;
+
+    fmt = qcrypto_get_x509_cert_fmt(cert, cert_size, errp);
+    if (fmt == -1) {
+        error_setg(errp, "Certificate is neither in DER or PEM format");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_init(&crt) < 0) {
+        error_setg(errp, "Failed to initialize certificate");
+        return rc;
+    }
+
+    if (gnutls_x509_crt_import(crt, &cert_datum, fmt) != 0) {
+        error_setg(errp, "Failed to import certificate");
+        goto cleanup;
+    }
+
+    if (gnutls_pkcs7_init(&signature) < 0) {
+        error_setg(errp, "Failed to initalize pkcs7 data.");
+        return rc;
+    }
+
+    if (gnutls_pkcs7_import(signature, &sig_datum , fmt) != 0) {
+        error_setg(errp, "Failed to import signature");
+    }
+
+    rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
+
+cleanup:
+    gnutls_x509_crt_deinit(crt);
+    gnutls_pkcs7_deinit(signature);
+    return rc;
+}
+
 #else /* ! CONFIG_GNUTLS */
 
 int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
@@ -438,4 +484,12 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
     return -ENOTSUP;
 }
 
+int qcrypto_verify_x509_cert(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, "signature-verification support requires GNUTLS");
+    return -ENOTSUP;
+}
+
 #endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index cf43de0b2c..ec90667376 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -35,4 +35,8 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
                                  size_t *resultlen,
                                  Error **errp);
 
+int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
+                             uint8_t *comp, size_t comp_size,
+                             uint8_t *sig, size_t sig_size, Error **errp);
+
 #endif
diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
index 6281ad8299..80a5bb906b 100644
--- a/include/hw/s390x/ipl/diag508.h
+++ b/include/hw/s390x/ipl/diag508.h
@@ -11,5 +11,27 @@
 #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;
+    uint64_t len;
+} QEMU_PACKED;
+typedef struct Diag508CertificateStoreInfo Diag508CertificateStoreInfo;
+
+struct Diag508SignatureVerificationBlock {
+    Diag508CertificateStoreInfo csi;
+    uint64_t comp_len;
+    uint64_t comp_addr;
+    uint64_t sig_len;
+    uint64_t sig_addr;
+} QEMU_PACKED;
+typedef struct Diag508SignatureVerificationBlock Diag508SignatureVerificationBlock;
 
 #endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 954c95fe50..2171e3275d 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -494,7 +494,10 @@ 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)
 {
+    S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
+    size_t csi_size = sizeof(Diag508CertificateStoreInfo);
     uint64_t subcode = env->regs[r3];
+    uint64_t addr = env->regs[r1];
     int rc;
 
     if (env->psw.mask & PSW_MASK_PSTATE) {
@@ -509,7 +512,83 @@ 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 svb_size = sizeof(Diag508SignatureVerificationBlock);
+        Diag508SignatureVerificationBlock *svb;
+        uint64_t comp_len, comp_addr;
+        uint64_t sig_len, sig_addr;
+        uint8_t *svb_comp;
+        uint8_t *svb_sig;
+        int verified;
+        Error *err = NULL;
+        int i;
+
+        if (!qcs || !qcs->count) {
+            rc = DIAG_508_RC_NO_CERTS;
+            break;
+        }
+
+        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;
+        }
+
+        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) {
+            rc = DIAG_508_RC_INVAL_COMP_DATA;
+            g_free(svb);
+            break;
+        }
+
+        if (!sig_len || !sig_addr) {
+            rc = DIAG_508_RC_INVAL_PKCS7_SIG;
+            g_free(svb);
+            break;
+        }
+
+        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 (i = 0; i < qcs->count; i++) {
+            verified = qcrypto_verify_x509_cert((uint8_t *)qcs->certs[i].raw,
+                                                qcs->certs[i].size,
+                                                svb_comp, comp_len,
+                                                svb_sig, sig_len, &err);
+
+            /* return early if GNUTLS is not enabled */
+            if (verified == -ENOTSUP) {
+                g_free(svb);
+                break;
+            }
+
+            if (verified == 0) {
+                svb->csi.idx = i;
+                svb->csi.len = cpu_to_be64(qcs->certs[i].size);
+                cpu_physical_memory_write(addr, &svb->csi, be32_to_cpu(csi_size));
+                rc = DIAG_508_RC_OK;
+                break;
+           }
+        }
+
+        g_free(svb);
         break;
     default:
         s390_program_interrupt(env, PGM_SPECIFICATION, ra);
-- 
2.49.0



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

* [PATCH v2 10/25] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB)
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (8 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 09/25] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 11/25] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
                   ` (14 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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] 49+ messages in thread

* [PATCH v2 11/25] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (9 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 10/25] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-20  9:24   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 12/25] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
                   ` (13 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, zycai

This patch is necessary because of the architectural design of
IPL Parameter Block (IPLB) and IPL Information Report Block (IIRB).
IIRB will be introduced in the next patch.

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] 49+ messages in thread

* [PATCH v2 12/25] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (10 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 11/25] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 13/25] hw/s390x/ipl: Set iplb->len to maximum length of " Zhuoying Cai
                   ` (12 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 b4d4566293..3e7190c7d8 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -25,7 +25,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);
@@ -92,22 +91,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 b8e7d1da71..7126ec2a21 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
@@ -104,7 +106,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] 49+ messages in thread

* [PATCH v2 13/25] hw/s390x/ipl: Set iplb->len to maximum length of IPL Parameter Block
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (11 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 12/25] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 14/25] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
                   ` (11 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 3e7190c7d8..ed29881dfa 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -101,6 +101,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] 49+ messages in thread

* [PATCH v2 14/25] s390x: Guest support for Secure-IPL Facility
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (12 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 13/25] hw/s390x/ipl: Set iplb->len to maximum length of " Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 15/25] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
                   ` (10 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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         | 1 +
 target/s390x/kvm/kvm.c              | 3 +++
 8 files changed, 18 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 65d38f546d..516d65d245 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, "diag320", 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 ab38fc9882..63d4120640 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 3d9fbe62ea..638a1cef3b 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[] = {
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 9bf3ed694b..c4b6cc1809 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2517,6 +2517,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] 49+ messages in thread

* [PATCH v2 15/25] pc-bios/s390-ccw: Refactor zipl_run()
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (13 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 14/25] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-20  9:29   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 16/25] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
                   ` (9 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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..485b55f1bf 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] 49+ messages in thread

* [PATCH v2 16/25] pc-bios/s390-ccw: Refactor zipl_load_segment function
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (14 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 15/25] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-20  9:39   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 17/25] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
                   ` (8 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 485b55f1bf..3dd09fda7e 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 += (uint64_t)bprs->size * ((uint64_t)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] 49+ messages in thread

* [PATCH v2 17/25] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (15 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 16/25] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-20 10:25   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 18/25] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
                   ` (7 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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    | 192 +++++++++++++++++++++++++++++++++-
 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 | 175 +++++++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h | 109 +++++++++++++++++++
 9 files changed, 558 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 3dd09fda7e..06cea0929a 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -15,6 +15,7 @@
 #include "bootmap.h"
 #include "virtio.h"
 #include "bswap.h"
+#include "secure-ipl.h"
 
 #ifdef DEBUG
 /* #define DEBUG_FALLBACK */
@@ -34,6 +35,13 @@ 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 certificates */
+static uint8_t certs_sec[CERT_MAX_SIZE * MAX_CERTIFICATES];
+/* sector for storing signatures */
+static uint8_t sig_sec[MAX_SECTOR_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
+
+ipl_print_func_t zipl_secure_print_func;
+
 /*
  * Match two CCWs located after PSW and eight filler bytes.
  * From libmagic and arch/s390/kernel/head.S.
@@ -676,6 +684,155 @@ 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)
+{
+    bool found_signature = false;
+    IplDeviceComponentList comps;
+    IplSignatureCertificateList certs;
+    uint64_t *cert = (uint64_t *)certs_sec;
+    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;
+    /*
+     * 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};
+    zipl_secure_print_func = zipl_secure_get_print_func(boot_mode);
+
+    if (!zipl_secure_ipl_supported()) {
+        return -1;
+    }
+
+    zipl_secure_init_lists(&comps, &certs);
+
+    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);
+
+                    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_func(verified, "Could not verify component");
+                }
+
+                comp_index++;
+                found_signature = true;
+                /* 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 (!found_signature) {
+        zipl_secure_print_func(found_signature,
+                               "Secure boot is on, but components are not signed");
+    }
+
+    if (zipl_secure_update_iirb(&comps, &certs)) {
+        zipl_secure_print_func(false, "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 +892,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 +1261,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 +1300,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..da795079f4
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -0,0 +1,175 @@
+/*
+ * 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_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..4e2328840b
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -0,0 +1,109 @@
+/*
+ * 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_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);
+
+typedef void (*ipl_print_func_t)(bool, const char *);
+
+static inline ipl_print_func_t zipl_secure_get_print_func(ZiplBootMode boot_mode)
+{
+    if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
+        return &IPL_check;
+    }
+
+    return NULL;
+}
+
+extern ipl_print_func_t zipl_secure_print_func;
+
+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 uint64_t get_320_subcodes(uint64_t *ism)
+{
+    return diag320(ism, DIAG_320_SUBC_QUERY_ISM);
+}
+
+static inline bool is_cert_store_facility_supported(void)
+{
+    uint64_t d320_ism;
+
+    get_320_subcodes(&d320_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 uint64_t get_508_subcodes(void)
+{
+    return _diag508(NULL, DIAG_508_SUBC_QUERY_SUBC);
+}
+
+static inline bool is_secure_ipl_extension_supported(void)
+{
+    uint64_t d508_subcodes;
+
+    d508_subcodes = get_508_subcodes();
+    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] 49+ messages in thread

* [PATCH v2 18/25] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (16 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 17/25] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-26 14:46   ` Hendrik Brueckner
  2025-05-08 22:50 ` [PATCH v2 19/25] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
                   ` (6 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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           | 2 ++
 target/s390x/gen-features.c         | 1 +
 target/s390x/kvm/kvm.c              | 3 +++
 5 files changed, 8 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 516d65d245..23079fe117 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, "diag320", SCLP_FAC134, 5, "Provide Certificate Store functio
 
 /* 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 63d4120640..5a0bdd6659 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,7 @@ 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_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 638a1cef3b..32661e7377 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[] = {
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index c4b6cc1809..ac7a7c9443 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)
     /* 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] 49+ messages in thread

* [PATCH v2 19/25] pc-bios/s390-ccw: Add additional security checks for secure boot
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (17 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 18/25] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 20/25] Add -secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
                   ` (5 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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    |  44 ++++++-
 pc-bios/s390-ccw/iplb.h       |  43 ++++++-
 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 | 232 ++++++++++++++++++++++++++++++++++
 pc-bios/s390-ccw/secure-ipl.h |  20 +++
 7 files changed, 345 insertions(+), 4 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 06cea0929a..24356820ca 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -747,6 +747,14 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
      * 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 sclab_count = 0;
+    int global_sclab_count = 0;
+    uint64_t sclab_load_psw = 0;
+
+    SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
+    int addr_range_index = 0;
+
     zipl_secure_print_func = zipl_secure_get_print_func(boot_mode);
 
     if (!zipl_secure_ipl_supported()) {
@@ -778,7 +786,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);
+            } else {
+                zipl_secure_check_sclab(comp_addr, &comps, comp_len, comp_index,
+                                        &sclab_count, &sclab_load_psw,
+                                        &global_sclab_count);
+
                 verified = verify_signature(comp_len, comp_addr,
                                             sig_len, (uint64_t)sig_sec,
                                             &cert_len, &cert_idx);
@@ -800,11 +818,12 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
                     zipl_secure_print_func(verified, "Could not verify component");
                 }
 
-                comp_index++;
                 found_signature = true;
                 /* After a signature is used another new one can be accepted */
                 have_sig = false;
             }
+
+            comp_index++;
         }
 
         entry++;
@@ -821,8 +840,29 @@ static int zipl_run_secure(ComponentEntry *entry, uint8_t *tmp_sec)
     }
 
     if (!found_signature) {
+        comps.ipl_info_header.iiei |= S390_IPL_INFO_IIEI_NO_SIGED_COMP;
         zipl_secure_print_func(found_signature,
                                "Secure boot is on, but components are not signed");
+    } else {
+        if (sclab_count == 0) {
+            comps.ipl_info_header.iiei |= S390_IPL_INFO_IIEI_NO_SCLAB;
+            zipl_secure_print_func(false, "No recognizable SCLAB");
+        }
+
+        /* Verify PSW from the final component entry with PSW from the global SCLAB. */
+        if ((comps.ipl_info_header.iiei & S390_IPL_INFO_IIEI_NO_SCLAB) == 0) {
+            if (global_sclab_count == 0) {
+                comps.ipl_info_header.iiei |= S390_IPL_INFO_IIEI_NO_GLOBAL_SCLAB;
+                zipl_secure_print_func(false, "Global SCLAB does not exists");
+            } else if (global_sclab_count == 1 && sclab_load_psw) {
+                zipl_secure_load_psw_check(comp_addr_range, addr_range_index,
+                                           sclab_load_psw, entry->compdat.load_psw,
+                                           &comps, comp_index);
+            } else {
+                /* Program will only reach here in audit mode */
+                puts("Multiple global SCLABs");
+            }
+        }
     }
 
     if (zipl_secure_update_iirb(&comps, &certs)) {
diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index 11302e004d..566e5f78b6 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -32,11 +32,17 @@ struct IplInfoReportBlockHeader {
 } __attribute__ ((packed));
 typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
 
+#define S390_IPL_INFO_IIEI_NO_SIGED_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 */
+
 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 +66,25 @@ 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_SCLAB_OLA_NOT_ONE         0x1000000  /* bit 7 */
+#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;
 
@@ -93,6 +111,27 @@ typedef struct IplBlocks IplBlocks;
 
 extern IplBlocks ipl_data __attribute__((__aligned__(PAGE_SIZE)));
 
+#define S390_IPL_SCLAB_FLAG_OPSW    0x8000
+#define S390_IPL_SCLAB_FLAG_OLA     0x4000
+
+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;
+
 #define S390_IPL_TYPE_FCP 0x00
 #define S390_IPL_TYPE_CCW 0x02
 #define S390_IPL_TYPE_QEMU_SCSI 0xff
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 da795079f4..6e91ec95a8 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"
 
@@ -161,6 +162,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;
 }
 
@@ -173,3 +180,228 @@ 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)
+{
+    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 unsigned_addr_check(uint64_t load_addr, IplDeviceComponentList *comps,
+                                int comp_index)
+{
+    bool is_addr_valid;
+
+    is_addr_valid = load_addr >= 0x2000;
+    if (!is_addr_valid) {
+        comps->device_entries[comp_index].cei |=
+        S390_IPL_COMPONENT_CEI_INVALID_UNSIGNED_ADDR;
+        zipl_secure_print_func(is_addr_valid, "Load address is less than 0x2000");
+    }
+}
+
+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_func(!overlap, "Component addresses overlap");
+    }
+}
+
+static void valid_sclab_check(SclabOriginLocator *sclab_locator,
+                              IplDeviceComponentList *comps, int comp_index)
+{
+    bool is_magic_match;
+    bool is_len_valid;
+
+    /* identifies the presence of SCLAB */
+    is_magic_match = magic_match(sclab_locator->magic, ZIPL_MAGIC);
+    if (!is_magic_match) {
+        comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+
+        /* a missing SCLAB will not be reported in audit mode */
+        return;
+    }
+
+    is_len_valid = sclab_locator->len >= 32;
+    if (!is_len_valid) {
+        comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN;
+        comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+        zipl_secure_print_func(is_len_valid, "Invalid SCLAB length");
+    }
+}
+
+static void sclab_format_check(SecureCodeLoadingAttributesBlock *sclab,
+                               IplDeviceComponentList *comps, int comp_index)
+{
+    bool valid_format;
+
+    valid_format = sclab->format == 0;
+    if (!valid_format) {
+        comps->device_entries[comp_index].cei |=
+        S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT;
+    }
+    zipl_secure_print_func(valid_format, "Format-0 SCLAB is not being used");
+}
+
+static void sclab_opsw_check(SecureCodeLoadingAttributesBlock *sclab,
+                             int *global_sclab_count, uint64_t *sclab_load_psw,
+                             IplDeviceComponentList *comps, int comp_index)
+{
+    bool is_load_psw_zero;
+    bool is_ola_on;
+    bool has_one_glob_sclab;
+
+    /* OPSW is zero */
+    if (!(sclab->flags & S390_IPL_SCLAB_FLAG_OPSW)) {
+        is_load_psw_zero = sclab->load_psw == 0;
+        if (!is_load_psw_zero) {
+            comps->device_entries[comp_index].cei |=
+            S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO;
+            zipl_secure_print_func(is_load_psw_zero,
+                       "Load PSW is not zero when Override PSW bit is zero");
+        }
+    } else {
+        is_ola_on = sclab->flags & S390_IPL_SCLAB_FLAG_OLA;
+        if (!is_ola_on) {
+            comps->device_entries[comp_index].cei |=
+            S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE;
+            zipl_secure_print_func(is_ola_on,
+                       "Override Load Address bit is not set to one in the global SCLAB");
+        }
+
+        *global_sclab_count += 1;
+        if (*global_sclab_count == 1) {
+            *sclab_load_psw = sclab->load_psw;
+        } else {
+            has_one_glob_sclab = false;
+            comps->ipl_info_header.iiei |= S390_IPL_INFO_IIEI_MORE_GLOBAL_SCLAB;
+            zipl_secure_print_func(has_one_glob_sclab, "More than one global SCLAB");
+        }
+    }
+}
+
+static void sclab_ola_check(SecureCodeLoadingAttributesBlock *sclab,
+                            uint64_t load_addr, IplDeviceComponentList *comps,
+                            int comp_index)
+{
+    bool is_load_addr_zero;
+    bool is_matched;
+
+    /* OLA is zero */
+    if (!(sclab->flags & S390_IPL_SCLAB_FLAG_OLA)) {
+        is_load_addr_zero = sclab->load_addr == 0;
+        if (!is_load_addr_zero) {
+            comps->device_entries[comp_index].cei |=
+            S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO;
+            zipl_secure_print_func(is_load_addr_zero,
+                       "Load Address is not zero when Override Load Address bit is zero");
+        }
+    } else {
+        is_matched = sclab->load_addr == load_addr;
+        if (!is_matched) {
+            comps->device_entries[comp_index].cei |=
+            S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR;
+            zipl_secure_print_func(is_matched,
+                       "Load Address does not match with component load address");
+        }
+    }
+}
+
+static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
+                         int range_index)
+{
+    uint32_t addr = psw & 0x3FFFFFFF;
+
+    /* PSW points to the beginning of a signed binary code component */
+    for (int i = 0; i < range_index; i++) {
+        if (comp_addr_range[i].is_signed && comp_addr_range[i].start_addr == addr) {
+            return true;
+       }
+    }
+
+    return false;
+}
+
+void zipl_secure_load_psw_check(SecureIplCompAddrRange *comp_addr_range,
+                                int addr_range_index, uint64_t sclab_load_psw,
+                                uint64_t load_psw, IplDeviceComponentList *comps,
+                                int comp_index)
+{
+    bool is_valid;
+    bool is_matched;
+
+    is_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 (!is_valid) {
+        comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_LOAD_PSW;
+        zipl_secure_print_func(is_valid, "Invalid PSW");
+    }
+
+    is_matched = load_psw == sclab_load_psw;
+    if (!is_matched) {
+        comps->device_entries[comp_index].cei |=
+        S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_PSW;
+        zipl_secure_print_func(is_matched,
+                               "Load PSW does not match with PSW in component");
+    }
+}
+
+void zipl_secure_check_unsigned_comp(uint64_t comp_addr, IplDeviceComponentList *comps,
+                                     int comp_index, int cert_index, uint64_t comp_len)
+{
+    unsigned_addr_check(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, int *sclab_count,
+                             uint64_t *sclab_load_psw, int *global_sclab_count)
+{
+    SclabOriginLocator *sclab_locator;
+    SecureCodeLoadingAttributesBlock *sclab;
+
+    sclab_locator = (SclabOriginLocator *)(comp_addr + comp_len - 8);
+    valid_sclab_check(sclab_locator, comps, comp_index);
+
+    if ((comps->device_entries[comp_index].cei &
+         S390_IPL_COMPONENT_CEI_INVALID_SCLAB) == 0) {
+        *sclab_count += 1;
+        sclab = (SecureCodeLoadingAttributesBlock *)(comp_addr + comp_len -
+                                                     sclab_locator->len);
+
+        sclab_format_check(sclab, comps, comp_index);
+        sclab_opsw_check(sclab, global_sclab_count, sclab_load_psw,
+                         comps, comp_index);
+        sclab_ola_check(sclab, comp_addr, comps, comp_index);
+    }
+}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index 4e2328840b..713491671f 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -26,6 +26,26 @@ bool zipl_secure_ipl_supported(void);
 void zipl_secure_init_lists(IplDeviceComponentList *comps,
                             IplSignatureCertificateList *certs);
 
+typedef struct SecureIplCompAddrRange {
+    bool is_signed;
+    uint64_t start_addr;
+    uint64_t end_addr;
+} SecureIplCompAddrRange;
+
+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_load_psw_check(SecureIplCompAddrRange *comp_addr_range,
+                                int addr_range_index, uint64_t sclab_load_psw,
+                                uint64_t load_psw, 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, int *sclab_count,
+                             uint64_t *sclab_load_psw, int *global_sclab_count);
+
 typedef void (*ipl_print_func_t)(bool, const char *);
 
 static inline ipl_print_func_t zipl_secure_get_print_func(ZiplBootMode boot_mode)
-- 
2.49.0



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

* [PATCH v2 20/25] Add -secure-boot to s390-ccw-virtio machine type option
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (18 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 19/25] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-21 12:14   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 21/25] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
                   ` (4 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, zycai

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

The `-secure-boot on|off` command line option 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 f82f78255a..8486f19ece 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -840,6 +840,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);
@@ -900,6 +915,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 direcotry 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 ed25939243..f4f5df010e 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 e592f6a757..498320ad21 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 s390-ccw host.
         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] 49+ messages in thread

* [PATCH v2 21/25] hw/s390x/ipl: Set IPIB flags for secure IPL
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (19 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 20/25] Add -secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-21 12:20   ` Thomas Huth
  2025-05-08 22:50 ` [PATCH v2 22/25] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
                   ` (3 subsequent siblings)
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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..4c827be121 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 on, 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] 49+ messages in thread

* [PATCH v2 22/25] pc-bios/s390-ccw: Handle true secure IPL mode
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (20 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 21/25] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 23/25] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
                   ` (2 subsequent siblings)
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 |  5 +++++
 pc-bios/s390-ccw/secure-ipl.h |  2 ++
 5 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 24356820ca..395d52c65c 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -933,6 +933,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;
@@ -1305,9 +1308,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;
@@ -1318,7 +1328,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();
@@ -1327,7 +1337,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();
@@ -1340,7 +1350,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 6e91ec95a8..7d02622c37 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -248,6 +248,11 @@ static void valid_sclab_check(SclabOriginLocator *sclab_locator,
         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_func(is_magic_match,
+                                   "Magic is not matched. SCLAB does not exist");
+         }
+
         return;
     }
 
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index 713491671f..9a3b3f016b 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -52,6 +52,8 @@ static inline ipl_print_func_t zipl_secure_get_print_func(ZiplBootMode boot_mode
 {
     if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
         return &IPL_check;
+    } else if (boot_mode == ZIPL_SECURE_MODE) {
+        return &IPL_assert;
     }
 
     return NULL;
-- 
2.49.0



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

* [PATCH v2 23/25] pc-bios/s390-ccw: Handle secure boot with multiple boot devices
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (21 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 22/25] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 24/25] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 25/25] docs/system/s390x: Add secure IPL documentation Zhuoying Cai
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 395d52c65c..bff9c75c49 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -1323,23 +1323,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;
@@ -1350,10 +1362,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] 49+ messages in thread

* [PATCH v2 24/25] hw/s390x/ipl: Handle secure boot without specifying a boot device
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (22 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 23/25] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-08 22:50 ` [PATCH v2 25/25] docs/system/s390x: Add secure IPL documentation Zhuoying Cai
  24 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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 4c827be121..7954ff6bae 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] 49+ messages in thread

* [PATCH v2 25/25] docs/system/s390x: Add secure IPL documentation
  2025-05-08 22:50 [PATCH v2 00/25] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
                   ` (23 preceding siblings ...)
  2025-05-08 22:50 ` [PATCH v2 24/25] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
@ 2025-05-08 22:50 ` Zhuoying Cai
  2025-05-21 12:37   ` Thomas Huth
  24 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-08 22:50 UTC (permalink / raw)
  To: thuth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel, 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/system/s390x/secure-ipl.rst | 249 +++++++++++++++++++++++++++++++
 1 file changed, 249 insertions(+)
 create mode 100644 docs/system/s390x/secure-ipl.rst

diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
new file mode 100644
index 0000000000..4f80d7741e
--- /dev/null
+++ b/docs/system/s390x/secure-ipl.rst
@@ -0,0 +1,249 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+s390 Secure IPL
+===============
+
+Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
+leverage qcrypto libraries and z/Arch implementations to verify the integrity of
+guest kernels. These operations are rely on userspace invocations and QEMU
+interpretation. The user provides one or more certificates via the command line
+options, which populates a certificate store. DIAGNOSE 'X'320' is invoked by
+userspace to query cert 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.
+
+The steps are as follows:
+
+- Userspace retrieves 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
+
+
+Constraints
+-----------
+
+The following constraints apply when attempting to secure IPL an s390 guest:
+
+- z16 CPU model
+- certificates must be in X.509 DER 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.
+
+
+s390 Certificate Store
+======================
+
+Secure boot relies on user certificates for signature-verification. Normally, 
+these certificates would be stored somewhere on the LPAR. Instead, for virtual
+guests, a certificate store is implemented within QEMU. This store will read
+any certificates provided by the user via command-line, which are expected to
+be stored somewhere on the host file system. Once these certificates are
+stored, they are ready to be queried/requested by DIAGNOSE 'X'320' or used for
+verification by DIAGNOSE 'X'508'.
+
+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.der
+
+
+DIAGNOSE function code 'X'320' - Certificate Store Facility
+-----------------------------------------------------------
+
+DIAGNOSE 'X'320' is used to provide support to query the certificate store.
+
+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.
+
+
+IPL Modes
+=========
+
+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.
+
+
+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.
+
+
+Secure IPL 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
+---------------------------------
+
+Secure Code Loading Attributes Facility (SCLAF) provides additional security during IPL.
+
+When SCLAF is available, its behavior depends on the IPL Modes.
+
+* secure mode: IPL will terminate on any errors detected by this facility. 
+* audit mode:  IPL may proceed regardless of any errors detected by this facility.
+
+Errors detected by the SCLAF are reported in IIRB.
+
+Unsigned components may only be loaded at absolute storage address x’2000’ or higher.
+
+Signed components must include a Secure Code Loading Attribute Block (SCLAB),
+which is located at the very end of the signed component.
+
+**Secure Code Loading Attribute Block (SCLAB)**
+
+The SCLAB is located at the end of each signed component. It defines the code loading
+attributes for the component and 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
+    Used to perform signature-verification on a signed component, leveraging
+    qcrypto libraries to perform this operation and pulling from the certificate
+    store.
+
+
+Secure IPL Quickstart
+=====================
+
+Build QEMU with gnutls enabled:
+
+.. code-block:: shell
+
+    ./configure … --enable-gnutls
+
+Generate certificate (e.g. via openssl):
+
+.. code-block:: shell
+
+    openssl req -new -x509 -newkey rsa:2048 -keyout mykey.priv \
+                -outform DER -out mycert.der -days 36500 \
+                -subj "/CN=My Name/" -nodes
+
+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 mykey.priv mycert.der /boot/vmlinuz-…
+    ./sign-file sha256 mykey.priv mycert.der /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=mycert.der ...
-- 
2.49.0



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

* Re: [PATCH v2 01/25] Add -boot-certificates to s390-ccw-virtio machine type option
  2025-05-08 22:50 ` [PATCH v2 01/25] Add -boot-certificates to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-05-13 14:58   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-13 14:58 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

  Hi!

On 09/05/2025 00.50, Zhuoying Cai wrote:
> Add -boot-certificates as a parameter of s390-ccw-virtio machine type option.

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

Nit: Remove the "-" before "boot-certificates" now in the patch description 
and in the patch subject since this is not a stand-alone option anymore.

> 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 192feb128b..f82f78255a 100644
> --- a/hw/s390x/s390-virtio-ccw.c
> +++ b/hw/s390x/s390-virtio-ccw.c
> @@ -824,6 +824,22 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
>       s390_ipl_fmt_loadparm(ms->loadparm, val, errp);
>   }
>   
> +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);
> @@ -878,6 +894,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 direcotry or a single certificate for secure boot");

s/direcotry/directory/

>   }
>   
>   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 fc4112fbf5..ed25939243 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 dc694a99a3..e592f6a757 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 s390-ccw host.

"s390-ccw" does not make too much sense when talking about the host. I'd 
maybe rather say "on the host [s390x only]" instead?

  Thomas



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

* Re: [PATCH v2 02/25] hw/s390x/ipl: Create certificate store
  2025-05-08 22:50 ` [PATCH v2 02/25] hw/s390x/ipl: Create certificate store Zhuoying Cai
@ 2025-05-14  5:43   ` Thomas Huth
  2025-05-29 18:49     ` Zhuoying Cai
  2025-05-14  9:03   ` Daniel P. Berrangé
  1 sibling, 1 reply; 49+ messages in thread
From: Thomas Huth @ 2025-05-14  5:43 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini,
	Daniel P. Berrange
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, Zhuoying Cai wrote:
> Create a certificate store for boot certificates used for secure IPL.
> 
> Load certificates from the -boot-certificate option into the cert store.

Nit: Remove the "-" before the "boot-certificate" here now, too.

> 
> Currently, only x509 certificates in DER format and uses SHA-256 hashing
> algorithm are supported, as these are the types required for secure boot
> on s390.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>   crypto/meson.build          |   5 +-
>   crypto/x509-utils.c         | 163 ++++++++++++++++++++++++
>   hw/s390x/cert-store.c       | 242 ++++++++++++++++++++++++++++++++++++
>   hw/s390x/cert-store.h       |  39 ++++++
>   hw/s390x/ipl.c              |   9 ++
>   hw/s390x/ipl.h              |   3 +
>   hw/s390x/meson.build        |   1 +
>   include/crypto/x509-utils.h |   6 +
>   include/hw/s390x/ipl/qipl.h |   3 +
>   qapi/crypto.json            |  80 ++++++++++++
>   10 files changed, 547 insertions(+), 4 deletions(-)
>   create mode 100644 hw/s390x/cert-store.c
>   create mode 100644 hw/s390x/cert-store.h
> 
> 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

Alternatively, you could put the "return -ENOTSUP;" functions into a 
x509-utils-stub.c file instead. Just as an idea. Not sure what is nicer 
here, though.

>   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..0b8cfc9022 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -11,6 +11,12 @@
>   #include "qemu/osdep.h"
>   #include "qapi/error.h"
>   #include "crypto/x509-utils.h"
> +
> +/*
> + * Surround with GNUTLS marco to ensure the interfaces are
> + * still available when GNUTLS is not enabled.
> + */
> +#ifdef CONFIG_GNUTLS
>   #include <gnutls/gnutls.h>
>   #include <gnutls/crypto.h>
>   #include <gnutls/x509.h>
> @@ -25,6 +31,94 @@ 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__MAX] = {
> +    [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 qcrypto_to_gnutls_cert_fmt_map[QCRYPTO_CERT_FMT__MAX] = {
> +    [QCRYPTO_CERT_FMT_DER] = GNUTLS_X509_FMT_DER,
> +    [QCRYPTO_CERT_FMT_PEM] = GNUTLS_X509_FMT_PEM,
> +};
> +
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> +                                 QCryptoCertFmt fmt, Error **errp)

Indentation seems to be off by one?

> +{
> +    int rc;
> +    int ret = 0;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +
> +    if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
> +        error_setg(errp, "Unknown certificate format");
> +        return ret;
 > +    }> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        goto cleanup;

So this will do a gnutls_x509_crt_deinit() in case the init() failed ... is 
that OK or should deinit() only be called after init() succeeded?

> +    }
> +
> +    rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
> +    if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
> +        ret = 0;
> +        goto cleanup;
> +    }
> +
> +    ret = 1;
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}

The return code handling of this function is somewhat confusing. It looks 
like it is meant to return a boolean value (1 for success, 0 for failure), 
and that's also the way you use it in the function below, but the return 
type is "int". Even worse the stub function at the end of the file does a " 
   return -ENOTSUP;". Although this does not seem to be a problem right now, 
this might be very fragile for future changes (future code that's expecting 
a 0 for failures and non-zero for succcess will fail with -ENOTSUP).

I'd suggest to rework this function so that it 0 for success and a negative 
error code in case of errors to avoid the possibility of confusion.

> +static int qcrypto_get_x509_cert_fmt(uint8_t *cert, size_t size, Error **errp)
> +{
> +    int fmt;

If you initialize fmt with -1 here ...

> +    if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_DER, errp)) {
> +        fmt = GNUTLS_X509_FMT_DER;
> +    } else if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_PEM, errp)) {
> +        fmt = GNUTLS_X509_FMT_PEM;
> +    } else {
> +        return -1;

... you can drop the final else branch here.

> +    }
> +
> +    return fmt;
> +}
> +
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
> +{
> +    if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
> +        error_setg(errp, "Unknown hash algorithm");
> +        return 0;

Should that maybe be an assert statement instead?

Anyway, in this case you set errp and return 0 ...

> +    }
> +
> +    return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);

... but this also might return 0 and does not set errp. That means that the 
caller cannot rely on errp being set (and I think you also never use it?).
==> Remove the error_setg() from this function?

> +}
> +
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
> +{
> +    QCryptoHashAlgo alg;
> +
> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
> +        error_setg(errp, "Unknown key id flag");
> +        return 0;
> +    }

dito?

> +    alg = QCRYPTO_HASH_ALGO_SHA1;
> +    if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
> +        (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
> +        alg = QCRYPTO_HASH_ALGO_SHA512;
> +    } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
> +        alg = QCRYPTO_HASH_ALGO_SHA256;
> +    }
> +
> +    return qcrypto_get_x509_hash_len(alg, errp);
> +}
> +
>   int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>                                         QCryptoHashAlgo alg,
>                                         uint8_t *result,
> @@ -74,3 +168,72 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>       gnutls_x509_crt_deinit(crt);
>       return ret;
>   }
> +
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> +    int rc = -1;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }
> +
> +    rc = gnutls_x509_crt_get_signature_algorithm(crt);
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return rc;
> +}
> +
> +#else /* ! CONFIG_GNUTLS */
> +
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> +                                 QCryptoCertFmt fmt, Error **errp)
> +{
> +    error_setg(errp, "To get certificate format requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
> +{
> +    error_setg(errp, "To get hash length requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
> +{
> +    error_setg(errp, "To get key ID length requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +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, "To get fingerprint requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> +    error_setg(errp, "To get signature algorithm requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +#endif /* ! CONFIG_GNUTLS */
> diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
> new file mode 100644
> index 0000000000..87bf9e381d
> --- /dev/null
> +++ b/hw/s390x/cert-store.c
> @@ -0,0 +1,242 @@
> +/*
> + * 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 "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, size_t max_size, char **cert_buf)
> +{
> +    size_t size;
> +    g_autofree char *buf;

Maybe set *cert_buf = NULL here, just in case the get_contents() fails below?

> +    /*
> +     * maximum allowed size of the certificate file to avoid consuming excessive memory
> +     * (malformed or maliciously large files)
> +     */
> +    if (!g_file_get_contents(path, &buf, &size, NULL) ||
> +        size == 0 || size > max_size) {
> +        return 0;
> +    }
> +
> +    *cert_buf = g_steal_pointer(&buf);

I wonder whether you need the detour via buf here at all? I expect that glib 
does not return a valid buffer pointer in case the get_contents() function 
fails, so you could rather pass cert_buf to it directly?

> +    return size;
> +}
> +
> +static S390IPLCertificate *init_cert_x509_der(size_t size, char *raw)
> +{
> +    S390IPLCertificate *q_cert = NULL;
> +    int key_id_size;
> +    int hash_size;
> +    int is_der;
> +    int hash_type;
> +    Error *err = NULL;
> +
> +    is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
> +                                         QCRYPTO_CERT_FMT_DER, &err);
> +    /* return early if GNUTLS is not enabled */
> +    if (is_der == -ENOTSUP) {
> +        error_report("GNUTLS is not enabled");
> +        return q_cert;

I'd maybe use "return NULL" here instead to make it more obvious. (same for 
the other early returns below)

> +    }
> +    if (!is_der) {
> +        error_report("The certificate is not in DER format");
> +        return q_cert;
> +    }
> +
> +    hash_type = qcrypto_get_x509_signature_algorithm((uint8_t *)raw, size, &err);
> +    if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
> +        error_report("The certificate does not use SHA-256 hashing");
> +        return q_cert;
> +    }
> +
> +    key_id_size = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256, &err);
> +    if (key_id_size == 0) {
> +        error_report("Failed to get certificate key ID size");
> +        return q_cert;
> +    }
> +
> +    hash_size = qcrypto_get_x509_hash_len(QCRYPTO_HASH_ALGO_SHA256, &err);
> +    if (hash_size == 0) {
> +        error_report("Failed to get certificate hash size");
> +        return q_cert;
> +    }
> +
> +    q_cert = g_new(S390IPLCertificate, 1);
> +    q_cert->size = size;
> +    q_cert->key_id_size = key_id_size;
> +    q_cert->hash_size = hash_size;
> +    q_cert->raw = raw;
> +    q_cert->format = QCRYPTO_CERT_FMT_DER;
> +    q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
> +
> +    return q_cert;
> +}
> +
> +static int check_path_type(const char *path)
> +{
> +    struct stat path_stat;
> +
> +    if (stat(path, &path_stat) != 0) {
> +        perror("stat");
> +        return -1;
> +    }
> +
> +    if (S_ISDIR(path_stat.st_mode)) {
> +        return S_IFDIR;
> +    } else if (S_ISREG(path_stat.st_mode)) {
> +        return S_IFREG;
> +    } else {
> +        return -1;
> +    }
> +}
> +
> +static S390IPLCertificate *init_cert(char *paths)
> +{
> +    char *buf;
> +    char vc_name[VC_NAME_LEN_BYTES];
> +    g_autofree gchar *filename;
> +    size_t size;
> +    S390IPLCertificate *qcert = NULL;
> +
> +    filename = g_path_get_basename(paths);
> +
> +    size = cert2buf(paths, CERT_MAX_SIZE, &buf);
> +    if (size == 0) {
> +        error_report("Failed to load certificate: %s", paths);
> +        return qcert;

dito

> +    }
> +
> +    qcert = init_cert_x509_der(size, buf);
> +    if (qcert == NULL) {
> +        error_report("Failed to initialize certificate: %s", paths);
> +        g_free(buf);
> +        return qcert;
> +    }
> +
> +    /*
> +     * 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);

No g_free(buf) here? Should it be marked with g_autofree ?

  Thomas


> +    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->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 *path;
> +    gchar **paths;
> +    gchar **paths_copy;
> +    int path_type;
> +    GDir *dir = NULL;
> +    gchar *cert_path;
> +    const gchar *filename;
> +    GPtrArray *cert_path_builder;
> +
> +    cert_path_builder = g_ptr_array_new();
> +
> +    path = s390_get_boot_certificates();
> +    if (path == NULL) {
> +        return cert_path_builder;
> +    }
> +
> +    paths = g_strsplit(path, ":", -1);
> +    /* save the original pointer for freeing later */
> +    paths_copy = paths;
> +    while (*paths) {
> +        /* skip empty certificate path */
> +        if (!strcmp(*paths, "")) {
> +            paths += 1;
> +            continue;
> +        }
> +
> +        cert_path = NULL;
> +        path_type = check_path_type(*paths);
> +        if (path_type == S_IFREG) {
> +            cert_path = g_strdup(*paths);
> +            g_ptr_array_add(cert_path_builder, cert_path);
> +        } else if (path_type == S_IFDIR) {
> +            dir = g_dir_open(*paths, 0, NULL);
> +
> +            if (dir) {
> +                while ((filename = g_dir_read_name(dir))) {
> +                    cert_path = g_build_filename(*paths, filename, NULL);
> +                    g_ptr_array_add(cert_path_builder, (gpointer) cert_path);
> +                }
> +
> +                g_dir_close(dir);
> +            }
> +        }
> +
> +        paths += 1;
> +    }
> +
> +    g_strfreev(paths_copy);
> +    return 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++) {
> +        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..04acc6c2e0
> --- /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 key_id_size;
> +    size_t hash_size;
> +    char *raw;
> +    QCryptoCertFmt format;
> +    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 505cded490..ac1f7517ea 100644
> --- a/hw/s390x/ipl.h
> +++ b/hw/s390x/ipl.h
> @@ -13,6 +13,7 @@
>   #ifndef HW_S390_IPL_H
>   #define HW_S390_IPL_H
>   
> +#include "cert-store.h"
>   #include "cpu.h"
>   #include "exec/target_page.h"
>   #include "system/address-spaces.h"
> @@ -35,6 +36,7 @@ int s390_ipl_pv_unpack(struct S390PVResponse *pv_resp);
>   void s390_ipl_prepare_cpu(S390CPU *cpu);
>   IplParameterBlock *s390_ipl_get_iplb(void);
>   IplParameterBlock *s390_ipl_get_iplb_pv(void);
> +S390IPLCertificateStore *s390_ipl_get_certificate_store(void);
>   
>   enum s390_reset {
>       /* default is a reset not triggered by a CPU e.g. issued by QMP */
> @@ -63,6 +65,7 @@ struct S390IPLState {
>       IplParameterBlock iplb;
>       IplParameterBlock iplb_pv;
>       QemuIplParameters qipl;
> +    S390IPLCertificateStore cert_store;
>       uint64_t start_addr;
>       uint64_t compat_start_addr;
>       uint64_t bios_start_addr;
> diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build
> index 11e4e78b85..5b02f47155 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/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index 1e99661a71..8fb263b9e1 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -19,4 +19,10 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>                                         size_t *resultlen,
>                                         Error **errp);
>   
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> +                                 QCryptoCertFmt fmt, Error **errp);
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp);
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp);
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
> +
>   #endif
> diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
> index 6824391111..b8e7d1da71 100644
> --- a/include/hw/s390x/ipl/qipl.h
> +++ b/include/hw/s390x/ipl/qipl.h
> @@ -20,6 +20,9 @@
>   #define LOADPARM_LEN    8
>   #define NO_LOADPARM "\0\0\0\0\0\0\0\0"
>   
> +#define MAX_CERTIFICATES 64
> +#define CERT_MAX_SIZE     (1024 * 8)
> +
>   /*
>    * The QEMU IPL Parameters will be stored at absolute address
>    * 204 (0xcc) which means it is 32-bit word aligned but not
> diff --git a/qapi/crypto.json b/qapi/crypto.json
> index c9d967d782..2bbf29affe 100644
> --- a/qapi/crypto.json
> +++ b/qapi/crypto.json
> @@ -612,3 +612,83 @@
>     'base': { 'alg': 'QCryptoAkCipherAlgo' },
>     'discriminator': 'alg',
>     'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
> +
> +##
> +# @QCryptoKeyidFlags:
> +#
> +# The supported flags for the key ID
> +#
> +# @sha1: SHA-1
> +#
> +# @sha256: SHA-256
> +#
> +# @sha512: SHA-512
> +#
> +# @best-known: BEST-KNOWN
> +#
> +# Since: 9.2
> +##
> +{ 'enum': 'QCryptoKeyidFlags',
> +  'data': ['sha1', 'sha256', 'sha512', 'best-known']}
> +
> +##
> +# @QCryptoCertFmt:
> +#
> +# The supported certificate encoding formats
> +#
> +# @der: DER
> +#
> +# @pem: PEM
> +#
> +# Since: 9.2
> +##
> +{ 'enum': 'QCryptoCertFmt',
> +  'data': ['der', 'pem']}
> +
> +##
> +# @QCryptoSigAlgo:
> +#
> +# Algorithms for digital signature
> +#
> +# @unknown: UNKNOWN
> +#
> +# @rsa-sha1: RSA-SHA1 or RSA-SHA
> +#
> +# @dsa-sha1: DSA-SHA1 or DSA-SHA
> +#
> +# @rsa-md5: RSA-MD5
> +#
> +# @rsa-md2: RSA-MD2
> +#
> +# @rsa-rmd160: RSA-RMD160
> +#
> +# @rsa-sha256: RSA-SHA256
> +#
> +# @rsa-sha384: RSA-SHA384
> +#
> +# @rsa-sha512: RSA-SHA512
> +#
> +# @rsa-sha224: RSA-SHA224
> +#
> +# @dsa-sha224: DSA-SHA224
> +#
> +# @dsa-sha256: DSA-SHA256
> +#
> +# @ecdsa-sha1: ECDSA-SHA1
> +#
> +# @ecdsa-sha224: ECDSA-SHA224
> +#
> +# @ecdsa-sha256: ECDSA-SHA256
> +#
> +# @ecdsa-sha384: ECDSA-SHA384
> +#
> +# @ecdsa-sha512: ECDSA-SHA512
> +#
> +# Since: 9.2
> +##
> +{ 'enum': 'QCryptoSigAlgo',
> +  'data': ['unknown', 'rsa-sha1', 'dsa-sha1',
> +           'rsa-md5', 'rsa-md2', 'rsa-rmd160',
> +           'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
> +           'dsa-sha224', 'dsa-sha256',
> +           'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}



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

* Re: [PATCH v2 03/25] s390x: Guest support for Certificate Store Facility (CS)
  2025-05-08 22:50 ` [PATCH v2 03/25] s390x: Guest support for Certificate Store Facility (CS) Zhuoying Cai
@ 2025-05-14  6:11   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-14  6:11 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, 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>
> ---
>   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         | 1 +
>   target/s390x/kvm/kvm.c              | 2 ++
>   5 files changed, 7 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..65d38f546d 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, "diag320", SCLP_FAC134, 5, "Provide Certificate Store functions")

Not sure, but "diag320" does not sound like a very good name for something 
related to security that is used in the interface for the users. Maybe 
"cert" or "cstore" would be a better name?

...
> diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
> index 41840677ce..3d9fbe62ea 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[] = {
> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> index b9f1422197..6bad1713d2 100644
> --- a/target/s390x/kvm/kvm.c
> +++ b/target/s390x/kvm/kvm.c
> @@ -2487,6 +2487,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);

Question: Could this feature be enabled of the TCG max CPU, too? If so, it 
might be a good idea to do so, so it could be used in CI tests more easily.

By the way, any chance that you could also add a functional test to this 
patch series, so that we can be sure that there are no regressions in the 
future?

  Thomas



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

* Re: [PATCH v2 04/25] s390x/diag: Introduce DIAG 320 for certificate store facility
  2025-05-08 22:50 ` [PATCH v2 04/25] s390x/diag: Introduce DIAG 320 for certificate store facility Zhuoying Cai
@ 2025-05-14  8:17   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-14  8:17 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, 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.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>   hw/s390x/ipl.h                 |  1 +
>   include/hw/s390x/ipl/diag320.h | 17 +++++++++++++++
>   target/s390x/diag.c            | 40 ++++++++++++++++++++++++++++++++++
>   target/s390x/kvm/kvm.c         | 14 ++++++++++++
>   target/s390x/s390x-internal.h  |  2 ++
>   5 files changed, 74 insertions(+)
>   create mode 100644 include/hw/s390x/ipl/diag320.h
> 
> diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
> index ac1f7517ea..d9c089928e 100644
> --- a/hw/s390x/ipl.h
> +++ b/hw/s390x/ipl.h
> @@ -19,6 +19,7 @@
>   #include "system/address-spaces.h"
>   #include "system/memory.h"
>   #include "hw/qdev-core.h"
> +#include "hw/s390x/ipl/diag320.h"

Only adding a header #include without any other modifications to this file 
is a good indication that this include is not needed here. Unless you need 
this in later patches, please try to move it to the .c file(s) instead.

  Thanks,
   Thomas



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

* Re: [PATCH v2 02/25] hw/s390x/ipl: Create certificate store
  2025-05-08 22:50 ` [PATCH v2 02/25] hw/s390x/ipl: Create certificate store Zhuoying Cai
  2025-05-14  5:43   ` Thomas Huth
@ 2025-05-14  9:03   ` Daniel P. Berrangé
  2025-05-29 17:51     ` Zhuoying Cai
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel P. Berrangé @ 2025-05-14  9:03 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
	jrossi, fiuczy, pasic, borntraeger, farman, iii, qemu-s390x,
	qemu-devel

On Thu, May 08, 2025 at 06:50:18PM -0400, Zhuoying Cai wrote:
> Create a certificate store for boot certificates used for secure IPL.
> 
> Load certificates from the -boot-certificate option into the cert store.
> 
> Currently, only x509 certificates in DER format and uses SHA-256 hashing
> algorithm are supported, as these are the types required for secure boot
> on s390.
> 
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
>  crypto/meson.build          |   5 +-
>  crypto/x509-utils.c         | 163 ++++++++++++++++++++++++
>  hw/s390x/cert-store.c       | 242 ++++++++++++++++++++++++++++++++++++
>  hw/s390x/cert-store.h       |  39 ++++++
>  hw/s390x/ipl.c              |   9 ++
>  hw/s390x/ipl.h              |   3 +
>  hw/s390x/meson.build        |   1 +
>  include/crypto/x509-utils.h |   6 +
>  include/hw/s390x/ipl/qipl.h |   3 +
>  qapi/crypto.json            |  80 ++++++++++++


Please split the crypto subsystem changes from the s390x subsystem
changes, as separate commits. Also be sure to CC myself (as crypto
maintainer) on patches that change the crypto subsystem.


> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 8bad00a51b..0b8cfc9022 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -11,6 +11,12 @@
>  #include "qemu/osdep.h"
>  #include "qapi/error.h"
>  #include "crypto/x509-utils.h"
> +
> +/*
> + * Surround with GNUTLS marco to ensure the interfaces are
> + * still available when GNUTLS is not enabled.

This comment is redundant - we don't need to explain
what an #ifdef does.

> + */
> +#ifdef CONFIG_GNUTLS
>  #include <gnutls/gnutls.h>
>  #include <gnutls/crypto.h>
>  #include <gnutls/x509.h>
> @@ -25,6 +31,94 @@ 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__MAX] = {
> +    [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_KEYI_DUSE_BEST_KNOWN,
> +};
> +
> +static const int qcrypto_to_gnutls_cert_fmt_map[QCRYPTO_CERT_FMT__MAX] = {
> +    [QCRYPTO_CERT_FMT_DER] = GNUTLS_X509_FMT_DER,
> +    [QCRYPTO_CERT_FMT_PEM] = GNUTLS_X509_FMT_PEM,
> +};
> +
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> +                                 QCryptoCertFmt fmt, Error **errp)
> +{
> +    int rc;
> +    int ret = 0;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +
> +    if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
> +        error_setg(errp, "Unknown certificate format");
> +        return ret;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        goto cleanup;
> +    }
> +
> +    rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
> +    if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
> +        ret = 0;
> +        goto cleanup;
> +    }
> +
> +    ret = 1;
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}
> +
> +static int qcrypto_get_x509_cert_fmt(uint8_t *cert, size_t size, Error **errp)
> +{
> +    int fmt;
> +
> +    if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_DER, errp)) {
> +        fmt = GNUTLS_X509_FMT_DER;
> +    } else if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_PEM, errp)) {
> +        fmt = GNUTLS_X509_FMT_PEM;
> +    } else {
> +        return -1;
> +    }
> +
> +    return fmt;
> +}
> +
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
> +{
> +    if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
> +        error_setg(errp, "Unknown hash algorithm");
> +        return 0;
> +    }
> +
> +    return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);
> +}
> +
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
> +{
> +    QCryptoHashAlgo alg;
> +
> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
> +        error_setg(errp, "Unknown key id flag");
> +        return 0;
> +    }
> +
> +    alg = QCRYPTO_HASH_ALGO_SHA1;
> +    if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
> +        (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
> +        alg = QCRYPTO_HASH_ALGO_SHA512;
> +    } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
> +        alg = QCRYPTO_HASH_ALGO_SHA256;
> +    }
> +
> +    return qcrypto_get_x509_hash_len(alg, errp);
> +}

This method looks fairly pointless to me. Why doesn't the caller just
directly call qcrypto_get_x509_hash_len without this indirection of
QCRYPTO_KEYID_FLAGS, especially given that you've just hardcoded to
use of QCRYPTO_KEYID_FLAGS_SHA256 in the caller ?

> @@ -74,3 +168,72 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>      gnutls_x509_crt_deinit(crt);
>      return ret;
>  }
> +
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> +    int rc = -1;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }

This code pattern looks dubious to me.  The qcrypto_get_x509_cert_fmt
method will call qcrypto_check_x509_cert_fmt which will call
gnutls_x509_crt_import(), the result of which is then thrown
away, and this method calls gnutls_x509_crt_import again. Get
rid of qcrypto_check_x509_cert_fmt from this. 

> +
> +    rc = gnutls_x509_crt_get_signature_algorithm(crt);

This needs to be remapped to the QCryptoSigAlgo enum values.

> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return rc;
> +}
> +
> +#else /* ! CONFIG_GNUTLS */
> +
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> +                                 QCryptoCertFmt fmt, Error **errp)
> +{
> +    error_setg(errp, "To get certificate format requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
> +{
> +    error_setg(errp, "To get hash length requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
> +{
> +    error_setg(errp, "To get key ID length requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +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, "To get fingerprint requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> +    error_setg(errp, "To get signature algorithm requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
> +#endif /* ! CONFIG_GNUTLS */
> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index 1e99661a71..8fb263b9e1 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -19,4 +19,10 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>                                        size_t *resultlen,
>                                        Error **errp);
>  
> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> +                                 QCryptoCertFmt fmt, Error **errp);
> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp);
> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp);
> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);

Please add API docs for each method.


> diff --git a/qapi/crypto.json b/qapi/crypto.json
> index c9d967d782..2bbf29affe 100644
> --- a/qapi/crypto.json
> +++ b/qapi/crypto.json
> @@ -612,3 +612,83 @@
>    'base': { 'alg': 'QCryptoAkCipherAlgo' },
>    'discriminator': 'alg',
>    'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
> +
> +##
> +# @QCryptoKeyidFlags:
> +#
> +# The supported flags for the key ID
> +#
> +# @sha1: SHA-1
> +#
> +# @sha256: SHA-256
> +#
> +# @sha512: SHA-512
> +#
> +# @best-known: BEST-KNOWN
> +#
> +# Since: 9.2
> +##
> +{ 'enum': 'QCryptoKeyidFlags',
> +  'data': ['sha1', 'sha256', 'sha512', 'best-known']}
> +
> +##
> +# @QCryptoCertFmt:
> +#
> +# The supported certificate encoding formats
> +#
> +# @der: DER
> +#
> +# @pem: PEM
> +#
> +# Since: 9.2

We're in the 10.1 dev cycle


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] 49+ messages in thread

* Re: [PATCH v2 06/25] s390x/diag: Implement DIAG 320 subcode 1
  2025-05-08 22:50 ` [PATCH v2 06/25] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2025-05-14 15:32   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-14 15:32 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, 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 | 25 ++++++++++++++++++++++
>   target/s390x/diag.c            | 38 +++++++++++++++++++++++++++++++++-
>   2 files changed, 62 insertions(+), 1 deletion(-)
> 
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index 713570545d..e91eb7238c 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -11,7 +11,32 @@
>   #define S390X_DIAG320_H
>   
>   #define DIAG_320_SUBC_QUERY_ISM     0
> +#define DIAG_320_SUBC_QUERY_VCSI    1
>   
>   #define DIAG_320_RC_OK              0x0001
> +#define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
> +
> +#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];
> +} QEMU_PACKED;

Please try to avoid QEMU_PACKED as long as it is not really necessary. The 
compiler can create better code without it and we have less trouble on 
exotic host architectures that way.

Here, all members seem to have proper natural alignment, so you don't need 
it. You can use something like this to make sure that there is no unexpected 
padding in the structure:

QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != ...,
                           "size of VCStorageSizeBlock is wrong");

> +typedef struct VCStorageSizeBlock \
> +VCStorageSizeBlock;

That looks like it fits into one line.

>   #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 9d249831b3..0743f5ec0e 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -194,6 +194,7 @@ out:
>   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 +216,48 @@ 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 = 4;

You missed the cpu_to_be32() here.

> +        } 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 (be32_to_cpu(vcssb.length) > 4 && be32_to_cpu(vcssb.length) < 128) {

How is this supposed to happen? There are only two hard-coded values for 
this field above?

> +            rc = DIAG_320_RC_INVAL_VCSSB_LEN;
> +            break;
> +        }
> +
> +        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:

  Thomas



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

* Re: [PATCH v2 07/25] s390x/diag: Implement DIAG 320 subcode 2
  2025-05-08 22:50 ` [PATCH v2 07/25] s390x/diag: Implement DIAG 320 subcode 2 Zhuoying Cai
@ 2025-05-14 16:18   ` Thomas Huth
  2025-05-29 19:09     ` Zhuoying Cai
  0 siblings, 1 reply; 49+ messages in thread
From: Thomas Huth @ 2025-05-14 16:18 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini,
	Daniel P. Berrange
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, 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>
> ---
>   crypto/x509-utils.c            | 204 ++++++++++++++++++++++++++++-
>   include/crypto/x509-utils.h    |  10 ++
>   include/hw/s390x/ipl/diag320.h |  47 +++++++
>   qapi/crypto.json               |  20 +++
>   target/s390x/diag.c            | 227 ++++++++++++++++++++++++++++++++-
>   5 files changed, 506 insertions(+), 2 deletions(-)
> 
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 0b8cfc9022..51bd75d4eb 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -129,6 +129,7 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>       int hlen;
>       gnutls_x509_crt_t crt;
>       gnutls_datum_t datum = {.data = cert, .size = size};
> +    gnutls_x509_crt_fmt_t fmt;
>   
>       if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
>           error_setg(errp, "Unknown hash algorithm");
> @@ -140,9 +141,15 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>           return -1;
>       }
>   
> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");

qcrypto_get_x509_cert_fmt() already has an errp parameter, so I'd expect 
that the qcrypto_get_x509_cert_fmt() function already sets up errp in case 
of errors, doesn't it? In that case you should not set errp here again, I think.

> +        return -1;
> +    }
> +
>       gnutls_x509_crt_init(&crt);
>   
> -    if (gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM) != 0) {
> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
>           error_setg(errp, "Failed to import certificate");
>           goto cleanup;
>       }
> @@ -199,6 +206,173 @@ cleanup:
>       return rc;
>   }
>   
> +int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
> +{
> +    int rc = -1;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");

dito?

> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }
> +
> +    rc = gnutls_x509_crt_get_version(crt);
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return rc;
> +}
> +
> +int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
> +{
> +    int rc = -1;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +    time_t now = time(0);
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    if (now == ((time_t)-1)) {
> +        error_setg(errp, "Cannot get current time");

Maybe use error_setg_errno() here to get the information from errno, too?

> +        return rc;
> +    }
> +
> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");

This is again ignoring the errp from qcrypto_get_x509_cert_fmt().

> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }
> +
> +    if (gnutls_x509_crt_get_expiration_time(crt) < now) {
> +        error_setg(errp, "The certificate has expired");
> +        goto cleanup;
> +    }
> +
> +    if (gnutls_x509_crt_get_activation_time(crt) > now) {
> +        error_setg(errp, "The certificate is not yet active");
> +        goto cleanup;
> +    }

gnutls_x509_crt_get_expiration_time() and 
gnutls_x509_crt_get_activation_time() can both return -1 on errors. I think 
you should take that into account in the checks here, too.

> +    rc = 0;
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return rc;
> +}
> +
> +int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> +    int rc = -1;
> +    unsigned int bits;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }
> +
> +    rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return rc;
> +}
> +
> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
> +                                 QCryptoKeyidFlags flag,
> +                                 uint8_t *result,
> +                                 size_t *resultlen,
> +                                 Error **errp)
> +{
> +    int ret = -1;
> +    int keyid_len;
> +    gnutls_x509_crt_t crt;
> +    gnutls_datum_t datum = {.data = cert, .size = size};
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
> +        error_setg(errp, "Unknown key id flag");
> +        return -1;
> +    }
> +
> +    if (result == NULL) {
> +        error_setg(errp, "No valid buffer given");
> +        return -1;
> +    }

This check sounds like it could also be a simple g_assert() statement instead?

> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");

overwriting errp again

> +        return -1;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt)) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return -1;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }
> +
> +    keyid_len = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256, errp);
> +    if (*resultlen < keyid_len) {
> +        error_setg(errp,
> +                   "Result buffer size %zu is smaller than key id %d",
> +                   *resultlen, keyid_len);
> +        goto cleanup;
> +    }
> +
> +    if (gnutls_x509_crt_get_key_id(crt,
> +                                   qcrypto_to_gnutls_keyid_flags_map[flag],
> +                                   result, resultlen) != 0) {
> +        error_setg(errp, "Failed to get fingerprint from certificate");
> +        goto cleanup;
> +    }
> +
> +    ret = 0;
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    return ret;
> +}
> +
>   #else /* ! CONFIG_GNUTLS */
>   
>   int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> @@ -236,4 +410,32 @@ int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **err
>       return -ENOTSUP;
>   }
>   
> +int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
> +{
> +    error_setg(errp, "To get certificate version requires GNUTLS");

I'm not a native speaker, but that sentence sounds weird to me. Maybe:

GNUTLS is required to get certificate versions

?

> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
> +{
> +    error_setg(errp, "To get certificate times requires GNUTLS");

dito

> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> +    error_setg(errp, "To get public key algorithm requires GNUTLS");

dito

> +    return -ENOTSUP;
> +}
> +
> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
> +                                 QCryptoKeyidFlags flag,
> +                                 uint8_t *result,
> +                                 size_t *resultlen,
> +                                 Error **errp)
> +{
> +    error_setg(errp, "To get key ID requires GNUTLS");

dito

> +    return -ENOTSUP;
> +}
> +
>   #endif /* ! CONFIG_GNUTLS */
> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index 8fb263b9e1..cf43de0b2c 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -25,4 +25,14 @@ int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp);
>   int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp);
>   int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
>   
> +int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp);
> +int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp);
> +int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp);
> +
> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
> +                                 QCryptoKeyidFlags flag,
> +                                 uint8_t *result,
> +                                 size_t *resultlen,
> +                                 Error **errp);
> +
>   #endif
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index e91eb7238c..dc1dae1d76 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -12,15 +12,24 @@
>   
>   #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_VCSSB_LEN 0x0202
> +#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;
> @@ -39,4 +48,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[];
> +} QEMU_PACKED;
> +typedef struct VCBlock VCBlock;

Looks like it could be done without QEMU_PACKED ?

> +
> +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[];
> +} QEMU_PACKED;
> +typedef struct VCEntry VCEntry;

dito

>   #endif
> diff --git a/qapi/crypto.json b/qapi/crypto.json
> index 2bbf29affe..4757bcd3db 100644
> --- a/qapi/crypto.json
> +++ b/qapi/crypto.json
> @@ -692,3 +692,23 @@
>              'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
>              'dsa-sha224', 'dsa-sha256',
>              'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
> +
> +##
> +# @QCryptoPkAlgo:
> +#
> +# Algorithms for public-key
> +#
> +# @unknown: UNKNOWN
> +#
> +# @rsa: RSA
> +#
> +# @dsa: DSA
> +#
> +# @dh: DH
> +#
> +# @ecdsa: ECDSA
> +#
> +# Since: 9.2

10.1

> +##
> +{ 'enum': 'QCryptoPkAlgo',
> +  'data': ['unknown', 'rsa', 'dsa', 'dh', 'ecdsa']}
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 0743f5ec0e..6fd59ac863 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -17,12 +17,14 @@
>   #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/s390-virtio-ccw.h"
>   #include "system/kvm.h"
>   #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,6 +193,87 @@ out:
>       }
>   }
>   
> +static int diag_320_is_cert_valid(S390IPLCertificate qcert, Error **errp)
> +{
> +    int version;
> +    int rc;
> +
> +    version = qcrypto_get_x509_cert_version((uint8_t *)qcert.raw, qcert.size, errp);
> +    if (version < 0) {
> +        return version == -ENOTSUP ? -ENOTSUP : 0;
> +    }
> +
> +    rc = qcrypto_check_x509_cert_times((uint8_t *)qcert.raw, qcert.size, errp);
> +    if (rc) {
> +        return 0;
> +    }
> +
> +    return 1;
> +}

Ok, here we have again that problem that the function name and the "normal" 
return values indicate a boolean return value (is valid = true or false), 
but the function can also return -ENOTSUP which could be misinterpreted as 
"true" if the caller is not careful. Could you please rework this?

> +static int diag_320_get_cert_info(VCEntry *vce, S390IPLCertificate qcert, int *is_valid,
> +                                  unsigned char **key_id_data, void **hash_data)
> +{
> +    int algo;
> +    int rc;
> +    Error *err = NULL;
> +
> +    /* VCE flag (validity) */
> +    *is_valid = diag_320_is_cert_valid(qcert, &err);
> +    /* return early if GNUTLS is not enabled */
> +    if (*is_valid == -ENOTSUP) {
> +        error_report("GNUTLS is not supported");

Oh well, and here we also return with is_valid != 0 in case of errors ... 
that's super confusing :-(

> +        return -1;
> +    }
> +
> +    /* key-type */
> +    algo = qcrypto_get_x509_pk_algorithm((uint8_t *)qcert.raw, qcert.size, &err);
> +    if (algo == QCRYPTO_PK_ALGO_RSA) {
> +        vce->key_type = DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
> +    }
> +
> +    /* VC format */
> +    if (qcert.format == QCRYPTO_CERT_FMT_DER) {
> +        vce->format = DIAG_320_VCE_FORMAT_X509_DER;
> +    }
> +
> +    /* key id and key id len */
> +    *key_id_data = g_malloc0(qcert.key_id_size);
> +    rc = qcrypto_get_x509_cert_key_id((uint8_t *)qcert.raw, qcert.size,
> +                                      QCRYPTO_KEYID_FLAGS_SHA256,
> +                                      *key_id_data, &qcert.key_id_size, &err);
> +    if (rc < 0) {
> +        error_report("Fail to retrieve certificate key ID");

Don't you want to report the "err" variable here?

> +        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((uint8_t *)qcert.raw, qcert.size,
> +                                           QCRYPTO_HASH_ALGO_SHA256,
> +                                           *hash_data, &qcert.hash_size, &err);
> +    if (rc < 0) {
> +        error_report("Fail to retrieve certificate hash");

dito

> +        goto out;
> +    }
> +    vce->hash_len = cpu_to_be16(qcert.hash_size);
> +
> +    return 0;
> +
> +out:
> +    g_free(*key_id_data);
> +    g_free(*hash_data);

Please set *key_id_data and *hash_data to NULL to make sure to not leave 
dangling pointers floating around.

> +    return -1;
> +}
> +
> +
>   void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>   {
>       S390CPU *cpu = env_archcpu(env);
> @@ -216,7 +299,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);
> @@ -260,6 +343,148 @@ 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:
> +        VCBlock *vcb;
> +        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;
> +        unsigned char *key_id_data = NULL;
> +        void *hash_data = NULL;
> +        int is_valid = 0;
> +        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;
> +        }
> +
> +        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) {
> +            rc = DIAG_320_RC_INVAL_VCB_LEN;
> +            g_free(vcb);
> +            break;
> +        }
> +
> +        if (first_vc_index > last_vc_index) {
> +            rc = DIAG_320_RC_BAD_RANGE;
> +            g_free(vcb);
> +            break;
> +        }
> +
> +        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;
> +        }
> +
> +        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.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;
> +            }
> +
> +            /*
> +             * Construct VCE
> +             * Unused area following the VCE field contains zeros.
> +             */
> +            vce = g_malloc0(vce_len);
> +            vce->len = cpu_to_be32(vce_len);
> +            vce->cert_idx = cpu_to_be16(i + 1);
> +            vce->cert_len = cpu_to_be32(qcert.size);
> +
> +            strncpy((char *)vce->name, (char *)qcert.vc_name, VC_NAME_LEN_BYTES);
> +
> +            rc = diag_320_get_cert_info(vce, qcert, &is_valid, &key_id_data, &hash_data);
> +            if (rc) {
> +                g_free(vce);
> +                continue;
> +            }
> +
> +            /* 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, qcert.raw, qcert.size);
> +
> +            /* The certificate is valid and VCE contains the certificate */
> +            if (is_valid) {
> +                vce->flags |= DIAG_320_VCE_FLAGS_VALID;
> +            }
> +
> +            /* Write VCE Header */
> +            if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1, vce, vce_len)) {
> +                s390_cpu_virt_mem_handle_exc(cpu, ra);
> +                return;
> +            }
> +
> +            vce_offset += vce_len;
> +            vcb->out_len += vce_len;
> +            remaining_space -= vce_len;
> +            vcb->stored_ct++;
> +
> +            g_free(vce);
> +            g_free(key_id_data);
> +            g_free(hash_data);
> +        }
> +
> +        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;
> +        }
> +        rc = DIAG_320_RC_OK;
> +        g_free(vcb);
> +        break;

IMHO this "case" block is way too big for being specified directly here. 
Please move it to a separate function.

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

  Thanks,
   Thomas



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

* Re: [PATCH v2 09/25] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
  2025-05-08 22:50 ` [PATCH v2 09/25] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2025-05-20  6:11   ` Thomas Huth
  2025-05-20  8:16   ` Daniel P. Berrangé
  1 sibling, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-20  6:11 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini,
	Daniel P. Berrange
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, Zhuoying Cai wrote:
> 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 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>
> ---
>   crypto/x509-utils.c            | 54 +++++++++++++++++++++++
>   include/crypto/x509-utils.h    |  4 ++
>   include/hw/s390x/ipl/diag508.h | 22 +++++++++
>   target/s390x/diag.c            | 81 +++++++++++++++++++++++++++++++++-
>   4 files changed, 160 insertions(+), 1 deletion(-)
> 
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 51bd75d4eb..56d9a42f39 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -20,6 +20,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,
> @@ -373,6 +374,51 @@ cleanup:
>       return ret;
>   }
>   
> +int qcrypto_verify_x509_cert(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 = -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};
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    fmt = qcrypto_get_x509_cert_fmt(cert, cert_size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &cert_datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }
> +
> +    if (gnutls_pkcs7_init(&signature) < 0) {
> +        error_setg(errp, "Failed to initalize pkcs7 data.");
> +        return rc;
> +    }
> +
> +    if (gnutls_pkcs7_import(signature, &sig_datum , fmt) != 0) {
> +        error_setg(errp, "Failed to import signature");
> +    }
> +
> +    rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    gnutls_pkcs7_deinit(signature);
> +    return rc;
> +}
> +
>   #else /* ! CONFIG_GNUTLS */
>   
>   int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> @@ -438,4 +484,12 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
>       return -ENOTSUP;
>   }
>   
> +int qcrypto_verify_x509_cert(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, "signature-verification support requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
>   #endif /* ! CONFIG_GNUTLS */

You might want to introduce this function in a separate patch (so that the 
crypto maintainer can review it separately)


> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index cf43de0b2c..ec90667376 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -35,4 +35,8 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
>                                    size_t *resultlen,
>                                    Error **errp);
>   
> +int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
> +                             uint8_t *comp, size_t comp_size,
> +                             uint8_t *sig, size_t sig_size, Error **errp);
> +
>   #endif
> diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
> index 6281ad8299..80a5bb906b 100644
> --- a/include/hw/s390x/ipl/diag508.h
> +++ b/include/hw/s390x/ipl/diag508.h
> @@ -11,5 +11,27 @@
>   #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;
> +    uint64_t len;
> +} QEMU_PACKED;
> +typedef struct Diag508CertificateStoreInfo Diag508CertificateStoreInfo;

Is that struct defined by some pre-existing specification already, or is 
that invented by you here? In the latter case, I'd suggest to add a "uint8_t 
reserved[7]" field after the idx line to pad manually so that len is 
naturally aligned. Then you could get rid of the QEMU_PACKED here.

> +
> +struct Diag508SignatureVerificationBlock {
> +    Diag508CertificateStoreInfo csi;
> +    uint64_t comp_len;
> +    uint64_t comp_addr;
> +    uint64_t sig_len;
> +    uint64_t sig_addr;
> +} QEMU_PACKED;
> +typedef struct Diag508SignatureVerificationBlock Diag508SignatureVerificationBlock;

Depending on whether you can get rid of the QEMU_PACKED for the previous 
struct, you could maybe get of the QEMU_PACKED here, too.

>   #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 954c95fe50..2171e3275d 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -494,7 +494,10 @@ 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)
>   {
> +    S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
> +    size_t csi_size = sizeof(Diag508CertificateStoreInfo);
>       uint64_t subcode = env->regs[r3];
> +    uint64_t addr = env->regs[r1];
>       int rc;
>   
>       if (env->psw.mask & PSW_MASK_PSTATE) {
> @@ -509,7 +512,83 @@ 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 svb_size = sizeof(Diag508SignatureVerificationBlock);
> +        Diag508SignatureVerificationBlock *svb;
> +        uint64_t comp_len, comp_addr;
> +        uint64_t sig_len, sig_addr;
> +        uint8_t *svb_comp;
> +        uint8_t *svb_sig;
> +        int verified;
> +        Error *err = NULL;
> +        int i;
> +
> +        if (!qcs || !qcs->count) {
> +            rc = DIAG_508_RC_NO_CERTS;
> +            break;
> +        }
> +
> +        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;
> +        }
> +
> +        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) {
> +            rc = DIAG_508_RC_INVAL_COMP_DATA;
> +            g_free(svb);
> +            break;
> +        }
> +
> +        if (!sig_len || !sig_addr) {
> +            rc = DIAG_508_RC_INVAL_PKCS7_SIG;
> +            g_free(svb);
> +            break;
> +        }
> +
> +        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 (i = 0; i < qcs->count; i++) {
> +            verified = qcrypto_verify_x509_cert((uint8_t *)qcs->certs[i].raw,
> +                                                qcs->certs[i].size,
> +                                                svb_comp, comp_len,
> +                                                svb_sig, sig_len, &err);
> +
> +            /* return early if GNUTLS is not enabled */
> +            if (verified == -ENOTSUP) {
> +                g_free(svb);
> +                break;
> +            }
> +
> +            if (verified == 0) {
> +                svb->csi.idx = i;
> +                svb->csi.len = cpu_to_be64(qcs->certs[i].size);
> +                cpu_physical_memory_write(addr, &svb->csi, be32_to_cpu(csi_size));
> +                rc = DIAG_508_RC_OK;
> +                break;
> +           }
> +        }
> +
> +        g_free(svb);

What about freeing svb_comp and svb_sig?

Also, this block introduces a bunch of local variables and is quite huge 
already. Thus this should not be done in a case block, please move the code 
into a separate function instead.

  Thanks,
   Thomas

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



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

* Re: [PATCH v2 09/25] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
  2025-05-08 22:50 ` [PATCH v2 09/25] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
  2025-05-20  6:11   ` Thomas Huth
@ 2025-05-20  8:16   ` Daniel P. Berrangé
  1 sibling, 0 replies; 49+ messages in thread
From: Daniel P. Berrangé @ 2025-05-20  8:16 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
	jrossi, fiuczy, pasic, borntraeger, farman, iii, qemu-s390x,
	qemu-devel

On Thu, May 08, 2025 at 06:50:25PM -0400, Zhuoying Cai wrote:
> 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 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>
> ---
>  crypto/x509-utils.c            | 54 +++++++++++++++++++++++
>  include/crypto/x509-utils.h    |  4 ++
>  include/hw/s390x/ipl/diag508.h | 22 +++++++++
>  target/s390x/diag.c            | 81 +++++++++++++++++++++++++++++++++-
>  4 files changed, 160 insertions(+), 1 deletion(-)
> 
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 51bd75d4eb..56d9a42f39 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -20,6 +20,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,
> @@ -373,6 +374,51 @@ cleanup:
>      return ret;
>  }
>  
> +int qcrypto_verify_x509_cert(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 = -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};
> +    gnutls_x509_crt_fmt_t fmt;
> +
> +    fmt = qcrypto_get_x509_cert_fmt(cert, cert_size, errp);
> +    if (fmt == -1) {
> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_init(&crt) < 0) {
> +        error_setg(errp, "Failed to initialize certificate");
> +        return rc;
> +    }
> +
> +    if (gnutls_x509_crt_import(crt, &cert_datum, fmt) != 0) {
> +        error_setg(errp, "Failed to import certificate");
> +        goto cleanup;
> +    }
> +
> +    if (gnutls_pkcs7_init(&signature) < 0) {
> +        error_setg(errp, "Failed to initalize pkcs7 data.");
> +        return rc;
> +    }
> +
> +    if (gnutls_pkcs7_import(signature, &sig_datum , fmt) != 0) {
> +        error_setg(errp, "Failed to import signature");
> +    }
> +
> +    rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
> +
> +cleanup:
> +    gnutls_x509_crt_deinit(crt);
> +    gnutls_pkcs7_deinit(signature);
> +    return rc;
> +}
> +
>  #else /* ! CONFIG_GNUTLS */
>  
>  int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
> @@ -438,4 +484,12 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
>      return -ENOTSUP;
>  }
>  
> +int qcrypto_verify_x509_cert(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, "signature-verification support requires GNUTLS");
> +    return -ENOTSUP;
> +}
> +
>  #endif /* ! CONFIG_GNUTLS */
> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index cf43de0b2c..ec90667376 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -35,4 +35,8 @@ int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
>                                   size_t *resultlen,
>                                   Error **errp);
>  
> +int qcrypto_verify_x509_cert(uint8_t *cert, size_t cert_size,
> +                             uint8_t *comp, size_t comp_size,
> +                             uint8_t *sig, size_t sig_size, Error **errp);
> +

Needs API docs to say what all these different params mean

>  #endif

Please always aim to seperate out code from different subsystems into
separate patches, if technically possible.

> diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
> index 6281ad8299..80a5bb906b 100644
> --- a/include/hw/s390x/ipl/diag508.h
> +++ b/include/hw/s390x/ipl/diag508.h

> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 954c95fe50..2171e3275d 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -494,7 +494,10 @@ 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)
>  {
> +    S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
> +    size_t csi_size = sizeof(Diag508CertificateStoreInfo);
>      uint64_t subcode = env->regs[r3];
> +    uint64_t addr = env->regs[r1];
>      int rc;
>  
>      if (env->psw.mask & PSW_MASK_PSTATE) {
> @@ -509,7 +512,83 @@ 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 svb_size = sizeof(Diag508SignatureVerificationBlock);
> +        Diag508SignatureVerificationBlock *svb;
> +        uint64_t comp_len, comp_addr;
> +        uint64_t sig_len, sig_addr;
> +        uint8_t *svb_comp;
> +        uint8_t *svb_sig;
> +        int verified;
> +        Error *err = NULL;
> +        int i;
> +
> +        if (!qcs || !qcs->count) {
> +            rc = DIAG_508_RC_NO_CERTS;
> +            break;
> +        }
> +
> +        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;
> +        }
> +
> +        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) {
> +            rc = DIAG_508_RC_INVAL_COMP_DATA;
> +            g_free(svb);
> +            break;
> +        }
> +
> +        if (!sig_len || !sig_addr) {
> +            rc = DIAG_508_RC_INVAL_PKCS7_SIG;
> +            g_free(svb);
> +            break;
> +        }
> +
> +        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 (i = 0; i < qcs->count; i++) {
> +            verified = qcrypto_verify_x509_cert((uint8_t *)qcs->certs[i].raw,
> +                                                qcs->certs[i].size,
> +                                                svb_comp, comp_len,
> +                                                svb_sig, sig_len, &err);
> +
> +            /* return early if GNUTLS is not enabled */
> +            if (verified == -ENOTSUP) {
> +                g_free(svb);
> +                break;
> +            }
> +
> +            if (verified == 0) {
> +                svb->csi.idx = i;
> +                svb->csi.len = cpu_to_be64(qcs->certs[i].size);
> +                cpu_physical_memory_write(addr, &svb->csi, be32_to_cpu(csi_size));
> +                rc = DIAG_508_RC_OK;

Leaks 'svb' here

> +                break;
> +           }
> +        }
> +
> +        g_free(svb);

This and all the other paths leak 'svb_comp' and
'svb_sig'.

Declare all three allocated vars with 'g_autofree' and then you don't
need to remember to manually g_free them.

>          break;
>      default:
>          s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> -- 
> 2.49.0
> 
> 

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] 49+ messages in thread

* Re: [PATCH v2 11/25] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
  2025-05-08 22:50 ` [PATCH v2 11/25] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
@ 2025-05-20  9:24   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-20  9:24 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, Zhuoying Cai wrote:
> This patch is necessary because of the architectural design of

I'd rather not use something like "This patch is necessary because" in a 
patch description, since it does not really provide much information. Just 
describe what the patch does and why it does it in the patch description, 
but don't justify that it is necessary here ;-)

> IPL Parameter Block (IPLB) and IPL Information Report Block (IIRB).
> IIRB will be introduced in the next patch.

Actually, IIRB has been introduced in the previous patch already?

  Thomas



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

* Re: [PATCH v2 15/25] pc-bios/s390-ccw: Refactor zipl_run()
  2025-05-08 22:50 ` [PATCH v2 15/25] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2025-05-20  9:29   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-20  9:29 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, 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..485b55f1bf 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)) {

Should be possible with less parentheses:

         if ((uint8_t *)&entry[1] > tmp_sec + MAX_SECTOR_SIZE) {

  Thomas



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

* Re: [PATCH v2 16/25] pc-bios/s390-ccw: Refactor zipl_load_segment function
  2025-05-08 22:50 ` [PATCH v2 16/25] pc-bios/s390-ccw: Refactor zipl_load_segment function Zhuoying Cai
@ 2025-05-20  9:39   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-20  9:39 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, Zhuoying Cai wrote:
> 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 485b55f1bf..3dd09fda7e 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 += (uint64_t)bprs->size * ((uint64_t)bprs[i].blockct + 1);

So you're doing the multiplication in 64-bit here, but comp_len and the 
return value is only 32-bit ... that sounds like either the multiplication 
could be 32-bit only, too, or comp_len and the return type should be 64-bit?

  Thomas


>               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] 49+ messages in thread

* Re: [PATCH v2 17/25] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-05-08 22:50 ` [PATCH v2 17/25] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2025-05-20 10:25   ` Thomas Huth
  2025-05-29 19:28     ` Zhuoying Cai
  0 siblings, 1 reply; 49+ messages in thread
From: Thomas Huth @ 2025-05-20 10:25 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, 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>
> ---
>   pc-bios/s390-ccw/Makefile     |   3 +-
>   pc-bios/s390-ccw/bootmap.c    | 192 +++++++++++++++++++++++++++++++++-
>   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 | 175 +++++++++++++++++++++++++++++++
>   pc-bios/s390-ccw/secure-ipl.h | 109 +++++++++++++++++++
>   9 files changed, 558 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 3dd09fda7e..06cea0929a 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -15,6 +15,7 @@
>   #include "bootmap.h"
>   #include "virtio.h"
>   #include "bswap.h"
> +#include "secure-ipl.h"
>   
>   #ifdef DEBUG
>   /* #define DEBUG_FALLBACK */
> @@ -34,6 +35,13 @@ 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 certificates */
> +static uint8_t certs_sec[CERT_MAX_SIZE * MAX_CERTIFICATES];

If I calculated correctly, that's a buffer of 512 kB... That's quite huge 
already. Would it be possible to malloc() it only if we really need this 
instead of statically allocating it?

> +/* sector for storing signatures */
> +static uint8_t sig_sec[MAX_SECTOR_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
> +
> +ipl_print_func_t zipl_secure_print_func;
> +
>   /*
>    * Match two CCWs located after PSW and eight filler bytes.
>    * From libmagic and arch/s390/kernel/head.S.
> @@ -676,6 +684,155 @@ 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;

So zipl_secure_cert_list_add() checks for the index not going beyond 
MAX_CERTIFICATES, but here you ignore that error and update cert_table and 
*cert anyway? Sounds like a potential bug to me.

> +        } 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)
> +{
> +    bool found_signature = false;
> +    IplDeviceComponentList comps;
> +    IplSignatureCertificateList certs;
> +    uint64_t *cert = (uint64_t *)certs_sec;
> +    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;
> +    /*
> +     * 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};
> +    zipl_secure_print_func = zipl_secure_get_print_func(boot_mode);
> +
> +    if (!zipl_secure_ipl_supported()) {
> +        return -1;
> +    }
> +
> +    zipl_secure_init_lists(&comps, &certs);
> +
> +    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);
> +
> +                    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_func(verified, "Could not verify component");
> +                }
> +
> +                comp_index++;
> +                found_signature = true;
> +                /* 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)) {

Less parentheses please:

         if ((uint8_t *)&entry[1] > tmp_sec + MAX_SECTOR_SIZE) {

> +            puts("Wrong entry value");
> +            return -EINVAL;
> +        }
> +    }
...
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> new file mode 100644
> index 0000000000..4e2328840b
> --- /dev/null
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -0,0 +1,109 @@
> +/*
> + * 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_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);
> +
> +typedef void (*ipl_print_func_t)(bool, const char *);
> +
> +static inline ipl_print_func_t zipl_secure_get_print_func(ZiplBootMode boot_mode)
> +{
> +    if (boot_mode == ZIPL_SECURE_AUDIT_MODE) {
> +        return &IPL_check;
> +    }
> +
> +    return NULL;
> +}

What is this function really good for?? And why do we need the function 
pointer below??? Aparently, the function pointer can also be NULL, but you 
call it also without checking for NULL first in the various other functions 
of this file ... looks very buggy to me, please rework this!

> +extern ipl_print_func_t zipl_secure_print_func;
> +
> +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 uint64_t get_320_subcodes(uint64_t *ism)
> +{
> +    return diag320(ism, DIAG_320_SUBC_QUERY_ISM);
> +}

For such a simple call, it's likely not worth the effort to have a wrapper 
function.

> +static inline bool is_cert_store_facility_supported(void)
> +{
> +    uint64_t d320_ism;
> +
> +    get_320_subcodes(&d320_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 uint64_t get_508_subcodes(void)
> +{
> +    return _diag508(NULL, DIAG_508_SUBC_QUERY_SUBC);
> +}

dito.

> +static inline bool is_secure_ipl_extension_supported(void)
> +{
> +    uint64_t d508_subcodes;
> +
> +    d508_subcodes = get_508_subcodes();
> +    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 */

  Thomas



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

* Re: [PATCH v2 20/25] Add -secure-boot to s390-ccw-virtio machine type option
  2025-05-08 22:50 ` [PATCH v2 20/25] Add -secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-05-21 12:14   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-21 12:14 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, Zhuoying Cai wrote:
> Add -secure-boot as a parameter of s390-ccw-virtio machine type option.
>> The `-secure-boot on|off` command line option is implemented
> to enable secure IPL.
> 
> By default, -secure-boot is set to false if not specified in
> the command line.

Remove the "-" in front of "secure-boot" now?

  Thomas



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

* Re: [PATCH v2 21/25] hw/s390x/ipl: Set IPIB flags for secure IPL
  2025-05-08 22:50 ` [PATCH v2 21/25] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
@ 2025-05-21 12:20   ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-21 12:20 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, Zhuoying Cai wrote:
> 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..4c827be121 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 on, then toggle the secure IPL flags to trigger
> +         * secure boot in the s390 BIOS.

"If secure-boot is enabled" ?

  Thomas


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



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

* Re: [PATCH v2 25/25] docs/system/s390x: Add secure IPL documentation
  2025-05-08 22:50 ` [PATCH v2 25/25] docs/system/s390x: Add secure IPL documentation Zhuoying Cai
@ 2025-05-21 12:37   ` Thomas Huth
  2025-05-23 20:28     ` Collin Walling
  0 siblings, 1 reply; 49+ messages in thread
From: Thomas Huth @ 2025-05-21 12:37 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 09/05/2025 00.50, 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>
> ---
>   docs/system/s390x/secure-ipl.rst | 249 +++++++++++++++++++++++++++++++
>   1 file changed, 249 insertions(+)
>   create mode 100644 docs/system/s390x/secure-ipl.rst
> 
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> new file mode 100644
> index 0000000000..4f80d7741e
> --- /dev/null
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -0,0 +1,249 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +s390 Secure IPL
> +===============
> +
> +Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
> +leverage qcrypto libraries and z/Arch implementations to verify the integrity of
> +guest kernels. These operations are rely on userspace invocations and QEMU
> +interpretation.

I don't understand the last sentence. Could you please rephrase it?

> The user provides one or more certificates via the command line
> +options, which populates a certificate store. DIAGNOSE 'X'320' is invoked by
> +userspace to query cert 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.
> +
> +The steps are as follows:
> +
> +- Userspace retrieves 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

So this doc is rather meant as a specification, not as a documentation for 
the end user? In that case it should maybe rather reside in docs/specs/ and 
not in docs/system/s390x.

> +- 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
> +
> +
> +Constraints
> +-----------
> +
> +The following constraints apply when attempting to secure IPL an s390 guest:
> +
> +- z16 CPU model
> +- certificates must be in X.509 DER 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.
> +
> +
> +s390 Certificate Store
> +======================
> +
> +Secure boot relies on user certificates for signature-verification. Normally,
> +these certificates would be stored somewhere on the LPAR. Instead, for virtual
> +guests, a certificate store is implemented within QEMU. This store will read
> +any certificates provided by the user via command-line, which are expected to
> +be stored somewhere on the host file system. Once these certificates are
> +stored, they are ready to be queried/requested by DIAGNOSE 'X'320' or used for
> +verification by DIAGNOSE 'X'508'.
> +
> +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.der
> +
> +
> +DIAGNOSE function code 'X'320' - Certificate Store Facility
> +-----------------------------------------------------------
> +
> +DIAGNOSE 'X'320' is used to provide support to query the certificate store.
> +
> +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.
> +
> +
> +IPL Modes
> +=========
> +
> +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.
> +
> +
> +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.
> +
> +
> +Secure IPL 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
> +---------------------------------
> +
> +Secure Code Loading Attributes Facility (SCLAF) provides additional security during IPL.
> +
> +When SCLAF is available, its behavior depends on the IPL Modes.
> +
> +* secure mode: IPL will terminate on any errors detected by this facility.
> +* audit mode:  IPL may proceed regardless of any errors detected by this facility.
> +
> +Errors detected by the SCLAF are reported in IIRB.
> +
> +Unsigned components may only be loaded at absolute storage address x’2000’ or higher.
> +
> +Signed components must include a Secure Code Loading Attribute Block (SCLAB),
> +which is located at the very end of the signed component.
> +
> +**Secure Code Loading Attribute Block (SCLAB)**
> +
> +The SCLAB is located at the end of each signed component. It defines the code loading
> +attributes for the component and 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
> +    Used to perform signature-verification on a signed component, leveraging
> +    qcrypto libraries to perform this operation and pulling from the certificate
> +    store.
> +
> +
> +Secure IPL Quickstart
> +=====================
> +
> +Build QEMU with gnutls enabled:
> +
> +.. code-block:: shell
> +
> +    ./configure … --enable-gnutls
> +
> +Generate certificate (e.g. via openssl):
> +
> +.. code-block:: shell
> +
> +    openssl req -new -x509 -newkey rsa:2048 -keyout mykey.priv \
> +                -outform DER -out mycert.der -days 36500 \
> +                -subj "/CN=My Name/" -nodes
> +
> +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 mykey.priv mycert.der /boot/vmlinuz-…
> +    ./sign-file sha256 mykey.priv mycert.der /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=mycert.der ...

So that quickstart section looks like it could be useful for the end user? 
Maybe you could split your doc up into two part, one going into 
docs/system/s390x/ for the normal users, and one going into docs/specs/ as 
technical reference for the developers?

  Thomas



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

* Re: [PATCH v2 25/25] docs/system/s390x: Add secure IPL documentation
  2025-05-21 12:37   ` Thomas Huth
@ 2025-05-23 20:28     ` Collin Walling
  0 siblings, 0 replies; 49+ messages in thread
From: Collin Walling @ 2025-05-23 20:28 UTC (permalink / raw)
  To: Thomas Huth, Zhuoying Cai, richard.henderson, david, pbonzini
  Cc: jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 5/21/25 8:37 AM, Thomas Huth wrote:
> On 09/05/2025 00.50, 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>
>> ---
>>   docs/system/s390x/secure-ipl.rst | 249 +++++++++++++++++++++++++++++++
>>   1 file changed, 249 insertions(+)
>>   create mode 100644 docs/system/s390x/secure-ipl.rst
>>
>> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
>> new file mode 100644
>> index 0000000000..4f80d7741e
>> --- /dev/null
>> +++ b/docs/system/s390x/secure-ipl.rst
>> @@ -0,0 +1,249 @@
>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>> +
>> +s390 Secure IPL
>> +===============
>> +
>> +Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
>> +leverage qcrypto libraries and z/Arch implementations to verify the integrity of
>> +guest kernels. These operations are rely on userspace invocations and QEMU
>> +interpretation.
> 
> I don't understand the last sentence. Could you please rephrase it?

It's my poorly worded phrasing to say "userspace and QEMU must work in
tandem to complete the secure IPL operations".  Basically encapsulating
the bulleted list below.

"Interpretation" was a poor choice -- should have said "QEMU handling"
or something akin to that.  Will revisit when splitting this into specs
and doc.

> 
>> The user provides one or more certificates via the command line
>> +options, which populates a certificate store. DIAGNOSE 'X'320' is invoked by
>> +userspace to query cert 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.
>> +
>> +The steps are as follows:
>> +
>> +- Userspace retrieves 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
> 
> So this doc is rather meant as a specification, not as a documentation for 
> the end user? In that case it should maybe rather reside in docs/specs/ and 
> not in docs/system/s390x.
> 
>> +- 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
>> +
>> +
>> +Constraints
>> +-----------
>> +
>> +The following constraints apply when attempting to secure IPL an s390 guest:
>> +
>> +- z16 CPU model
>> +- certificates must be in X.509 DER 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.
>> +
>> +
>> +s390 Certificate Store
>> +======================
>> +
>> +Secure boot relies on user certificates for signature-verification. Normally,
>> +these certificates would be stored somewhere on the LPAR. Instead, for virtual
>> +guests, a certificate store is implemented within QEMU. This store will read
>> +any certificates provided by the user via command-line, which are expected to
>> +be stored somewhere on the host file system. Once these certificates are
>> +stored, they are ready to be queried/requested by DIAGNOSE 'X'320' or used for
>> +verification by DIAGNOSE 'X'508'.
>> +
>> +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.der
>> +
>> +
>> +DIAGNOSE function code 'X'320' - Certificate Store Facility
>> +-----------------------------------------------------------
>> +
>> +DIAGNOSE 'X'320' is used to provide support to query the certificate store.
>> +
>> +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.
>> +
>> +
>> +IPL Modes
>> +=========
>> +
>> +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.
>> +
>> +
>> +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.
>> +
>> +
>> +Secure IPL 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
>> +---------------------------------
>> +
>> +Secure Code Loading Attributes Facility (SCLAF) provides additional security during IPL.
>> +
>> +When SCLAF is available, its behavior depends on the IPL Modes.
>> +
>> +* secure mode: IPL will terminate on any errors detected by this facility.
>> +* audit mode:  IPL may proceed regardless of any errors detected by this facility.
>> +
>> +Errors detected by the SCLAF are reported in IIRB.
>> +
>> +Unsigned components may only be loaded at absolute storage address x’2000’ or higher.
>> +
>> +Signed components must include a Secure Code Loading Attribute Block (SCLAB),
>> +which is located at the very end of the signed component.
>> +
>> +**Secure Code Loading Attribute Block (SCLAB)**
>> +
>> +The SCLAB is located at the end of each signed component. It defines the code loading
>> +attributes for the component and 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
>> +    Used to perform signature-verification on a signed component, leveraging
>> +    qcrypto libraries to perform this operation and pulling from the certificate
>> +    store.
>> +
>> +
>> +Secure IPL Quickstart
>> +=====================
>> +
>> +Build QEMU with gnutls enabled:
>> +
>> +.. code-block:: shell
>> +
>> +    ./configure … --enable-gnutls
>> +
>> +Generate certificate (e.g. via openssl):
>> +
>> +.. code-block:: shell
>> +
>> +    openssl req -new -x509 -newkey rsa:2048 -keyout mykey.priv \
>> +                -outform DER -out mycert.der -days 36500 \
>> +                -subj "/CN=My Name/" -nodes
>> +
>> +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 mykey.priv mycert.der /boot/vmlinuz-…
>> +    ./sign-file sha256 mykey.priv mycert.der /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=mycert.der ...
> 
> So that quickstart section looks like it could be useful for the end user? 
> Maybe you could split your doc up into two part, one going into 
> docs/system/s390x/ for the normal users, and one going into docs/specs/ as 
> technical reference for the developers?
> 
>   Thomas
> 
> 

Sure.  So, for the specs we can include the following sections:
 - s390 Secure IPL
 - s390 Certificate Store (description)
 - DIAG 320 and 508
 - Secure IPL Functions (and all sub sections)

For the docs:
 - Constraints
 - s390 Certificate Store (command line), probably rename this section
 - IPL Modes
 - Quickstart

-- 
Regards,
  Collin


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

* Re: [PATCH v2 18/25] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
  2025-05-08 22:50 ` [PATCH v2 18/25] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2025-05-26 14:46   ` Hendrik Brueckner
  0 siblings, 0 replies; 49+ messages in thread
From: Hendrik Brueckner @ 2025-05-26 14:46 UTC (permalink / raw)
  To: Zhuoying Cai
  Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
	jrossi, fiuczy, pasic, borntraeger, farman, iii, qemu-s390x,
	qemu-devel

On Thu, May 08, 2025 at 06:50:34PM -0400, 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>
> ---
>  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         | 1 +
>  target/s390x/kvm/kvm.c              | 3 +++
>  5 files changed, 8 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 516d65d245..23079fe117 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, "diag320", SCLP_FAC134, 5, "Provide Certificate Store functio
>  
>  /* 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 63d4120640..5a0bdd6659 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,7 @@ 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 },

For SCLAF, should we also check if SIPL is enabled? I think, SCLAF w/o SIPL
does not make much sense.


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

* Re: [PATCH v2 02/25] hw/s390x/ipl: Create certificate store
  2025-05-14  9:03   ` Daniel P. Berrangé
@ 2025-05-29 17:51     ` Zhuoying Cai
  0 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-29 17:51 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: thuth, richard.henderson, david, pbonzini, walling, jjherne,
	jrossi, fiuczy, pasic, borntraeger, farman, iii, qemu-s390x,
	qemu-devel


On 5/14/25 5:03 AM, Daniel P. Berrangé wrote:
> On Thu, May 08, 2025 at 06:50:18PM -0400, Zhuoying Cai wrote:
>> Create a certificate store for boot certificates used for secure IPL.
>>
>> Load certificates from the -boot-certificate option into the cert store.
>>
>> Currently, only x509 certificates in DER format and uses SHA-256 hashing
>> algorithm are supported, as these are the types required for secure boot
>> on s390.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>> ---
>>  crypto/meson.build          |   5 +-
>>  crypto/x509-utils.c         | 163 ++++++++++++++++++++++++
>>  hw/s390x/cert-store.c       | 242 ++++++++++++++++++++++++++++++++++++
>>  hw/s390x/cert-store.h       |  39 ++++++
>>  hw/s390x/ipl.c              |   9 ++
>>  hw/s390x/ipl.h              |   3 +
>>  hw/s390x/meson.build        |   1 +
>>  include/crypto/x509-utils.h |   6 +
>>  include/hw/s390x/ipl/qipl.h |   3 +
>>  qapi/crypto.json            |  80 ++++++++++++
> 
> 
> Please split the crypto subsystem changes from the s390x subsystem
> changes, as separate commits. Also be sure to CC myself (as crypto
> maintainer) on patches that change the crypto subsystem.
> 
> 
>> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
>> index 8bad00a51b..0b8cfc9022 100644
>> --- a/crypto/x509-utils.c
>> +++ b/crypto/x509-utils.c
>> @@ -11,6 +11,12 @@
>>  #include "qemu/osdep.h"
>>  #include "qapi/error.h"
>>  #include "crypto/x509-utils.h"
>> +
>> +/*
>> + * Surround with GNUTLS marco to ensure the interfaces are
>> + * still available when GNUTLS is not enabled.
> 
> This comment is redundant - we don't need to explain
> what an #ifdef does.
> 
>> + */
>> +#ifdef CONFIG_GNUTLS
>>  #include <gnutls/gnutls.h>
>>  #include <gnutls/crypto.h>
>>  #include <gnutls/x509.h>
>> @@ -25,6 +31,94 @@ 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__MAX] = {
>> +    [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_KEYI_DUSE_BEST_KNOWN,
>> +};
>> +
>> +static const int qcrypto_to_gnutls_cert_fmt_map[QCRYPTO_CERT_FMT__MAX] = {
>> +    [QCRYPTO_CERT_FMT_DER] = GNUTLS_X509_FMT_DER,
>> +    [QCRYPTO_CERT_FMT_PEM] = GNUTLS_X509_FMT_PEM,
>> +};
>> +
>> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
>> +                                 QCryptoCertFmt fmt, Error **errp)
>> +{
>> +    int rc;
>> +    int ret = 0;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +
>> +    if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
>> +        error_setg(errp, "Unknown certificate format");
>> +        return ret;
>> +    }
>> +
>> +    if (gnutls_x509_crt_init(&crt) < 0) {
>> +        error_setg(errp, "Failed to initialize certificate");
>> +        goto cleanup;
>> +    }
>> +
>> +    rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
>> +    if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
>> +        ret = 0;
>> +        goto cleanup;
>> +    }
>> +
>> +    ret = 1;
>> +
>> +cleanup:
>> +    gnutls_x509_crt_deinit(crt);
>> +    return ret;
>> +}
>> +
>> +static int qcrypto_get_x509_cert_fmt(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    int fmt;
>> +
>> +    if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_DER, errp)) {
>> +        fmt = GNUTLS_X509_FMT_DER;
>> +    } else if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_PEM, errp)) {
>> +        fmt = GNUTLS_X509_FMT_PEM;
>> +    } else {
>> +        return -1;
>> +    }
>> +
>> +    return fmt;
>> +}
>> +
>> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
>> +{
>> +    if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
>> +        error_setg(errp, "Unknown hash algorithm");
>> +        return 0;
>> +    }
>> +
>> +    return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);
>> +}
>> +
>> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
>> +{
>> +    QCryptoHashAlgo alg;
>> +
>> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
>> +        error_setg(errp, "Unknown key id flag");
>> +        return 0;
>> +    }
>> +
>> +    alg = QCRYPTO_HASH_ALGO_SHA1;
>> +    if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
>> +        (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
>> +        alg = QCRYPTO_HASH_ALGO_SHA512;
>> +    } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
>> +        alg = QCRYPTO_HASH_ALGO_SHA256;
>> +    }
>> +
>> +    return qcrypto_get_x509_hash_len(alg, errp);
>> +}
> 
> This method looks fairly pointless to me. Why doesn't the caller just
> directly call qcrypto_get_x509_hash_len without this indirection of
> QCRYPTO_KEYID_FLAGS, especially given that you've just hardcoded to
> use of QCRYPTO_KEYID_FLAGS_SHA256 in the caller ?
> 

This function will be used in a later patch related to DIAG320 subcode
support, where we need to obtain the certificate key ID. That process
requires calculating the key ID length based on the QCryptoKeyidFlags value.

It's a mistake that I hardcoded QCRYPTO_KEYID_FLAGS_SHA256 in the later
patch instead of using the QCryptoKeyidFlags parameter. I’ll correct
that in the next version.

>> @@ -74,3 +168,72 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>>      gnutls_x509_crt_deinit(crt);
>>      return ret;
>>  }
>> +
>> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    int rc = -1;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +    gnutls_x509_crt_fmt_t fmt;
>> +
>> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
>> +    if (fmt == -1) {
>> +        error_setg(errp, "Certificate is neither in DER or PEM format");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_init(&crt) < 0) {
>> +        error_setg(errp, "Failed to initialize certificate");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
>> +        error_setg(errp, "Failed to import certificate");
>> +        goto cleanup;
>> +    }
> 
> This code pattern looks dubious to me.  The qcrypto_get_x509_cert_fmt
> method will call qcrypto_check_x509_cert_fmt which will call
> gnutls_x509_crt_import(), the result of which is then thrown
> away, and this method calls gnutls_x509_crt_import again. Get
> rid of qcrypto_check_x509_cert_fmt from this. 
> 
>> +
>> +    rc = gnutls_x509_crt_get_signature_algorithm(crt);
> 
> This needs to be remapped to the QCryptoSigAlgo enum values.
> 
>> +
>> +cleanup:
>> +    gnutls_x509_crt_deinit(crt);
>> +    return rc;
>> +}
>> +
>> +#else /* ! CONFIG_GNUTLS */
>> +
>> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
>> +                                 QCryptoCertFmt fmt, Error **errp)
>> +{
>> +    error_setg(errp, "To get certificate format requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
>> +{
>> +    error_setg(errp, "To get hash length requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
>> +{
>> +    error_setg(errp, "To get key ID length requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +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, "To get fingerprint requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    error_setg(errp, "To get signature algorithm requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +#endif /* ! CONFIG_GNUTLS */
>> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
>> index 1e99661a71..8fb263b9e1 100644
>> --- a/include/crypto/x509-utils.h
>> +++ b/include/crypto/x509-utils.h
>> @@ -19,4 +19,10 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>>                                        size_t *resultlen,
>>                                        Error **errp);
>>  
>> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
>> +                                 QCryptoCertFmt fmt, Error **errp);
>> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp);
>> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp);
>> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
> 
> Please add API docs for each method.
> 
> 
>> diff --git a/qapi/crypto.json b/qapi/crypto.json
>> index c9d967d782..2bbf29affe 100644
>> --- a/qapi/crypto.json
>> +++ b/qapi/crypto.json
>> @@ -612,3 +612,83 @@
>>    'base': { 'alg': 'QCryptoAkCipherAlgo' },
>>    'discriminator': 'alg',
>>    'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
>> +
>> +##
>> +# @QCryptoKeyidFlags:
>> +#
>> +# The supported flags for the key ID
>> +#
>> +# @sha1: SHA-1
>> +#
>> +# @sha256: SHA-256
>> +#
>> +# @sha512: SHA-512
>> +#
>> +# @best-known: BEST-KNOWN
>> +#
>> +# Since: 9.2
>> +##
>> +{ 'enum': 'QCryptoKeyidFlags',
>> +  'data': ['sha1', 'sha256', 'sha512', 'best-known']}
>> +
>> +##
>> +# @QCryptoCertFmt:
>> +#
>> +# The supported certificate encoding formats
>> +#
>> +# @der: DER
>> +#
>> +# @pem: PEM
>> +#
>> +# Since: 9.2
> 
> We're in the 10.1 dev cycle
> 
> 
> With regards,
> Daniel



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

* Re: [PATCH v2 02/25] hw/s390x/ipl: Create certificate store
  2025-05-14  5:43   ` Thomas Huth
@ 2025-05-29 18:49     ` Zhuoying Cai
  0 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-29 18:49 UTC (permalink / raw)
  To: Thomas Huth, richard.henderson, david, pbonzini,
	Daniel P. Berrange
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel


On 5/14/25 1:43 AM, Thomas Huth wrote:
> On 09/05/2025 00.50, Zhuoying Cai wrote:
>> Create a certificate store for boot certificates used for secure IPL.
>>
>> Load certificates from the -boot-certificate option into the cert store.
> 
> Nit: Remove the "-" before the "boot-certificate" here now, too.
> 
>>
>> Currently, only x509 certificates in DER format and uses SHA-256 hashing
>> algorithm are supported, as these are the types required for secure boot
>> on s390.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>> ---
>>   crypto/meson.build          |   5 +-
>>   crypto/x509-utils.c         | 163 ++++++++++++++++++++++++
>>   hw/s390x/cert-store.c       | 242 ++++++++++++++++++++++++++++++++++++
>>   hw/s390x/cert-store.h       |  39 ++++++
>>   hw/s390x/ipl.c              |   9 ++
>>   hw/s390x/ipl.h              |   3 +
>>   hw/s390x/meson.build        |   1 +
>>   include/crypto/x509-utils.h |   6 +
>>   include/hw/s390x/ipl/qipl.h |   3 +
>>   qapi/crypto.json            |  80 ++++++++++++
>>   10 files changed, 547 insertions(+), 4 deletions(-)
>>   create mode 100644 hw/s390x/cert-store.c
>>   create mode 100644 hw/s390x/cert-store.h
>>
>> 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
> 
> Alternatively, you could put the "return -ENOTSUP;" functions into a 
> x509-utils-stub.c file instead. Just as an idea. Not sure what is nicer 
> here, though.
> 
>>   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..0b8cfc9022 100644
>> --- a/crypto/x509-utils.c
>> +++ b/crypto/x509-utils.c
>> @@ -11,6 +11,12 @@
>>   #include "qemu/osdep.h"
>>   #include "qapi/error.h"
>>   #include "crypto/x509-utils.h"
>> +
>> +/*
>> + * Surround with GNUTLS marco to ensure the interfaces are
>> + * still available when GNUTLS is not enabled.
>> + */
>> +#ifdef CONFIG_GNUTLS
>>   #include <gnutls/gnutls.h>
>>   #include <gnutls/crypto.h>
>>   #include <gnutls/x509.h>
>> @@ -25,6 +31,94 @@ 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__MAX] = {
>> +    [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 qcrypto_to_gnutls_cert_fmt_map[QCRYPTO_CERT_FMT__MAX] = {
>> +    [QCRYPTO_CERT_FMT_DER] = GNUTLS_X509_FMT_DER,
>> +    [QCRYPTO_CERT_FMT_PEM] = GNUTLS_X509_FMT_PEM,
>> +};
>> +
>> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
>> +                                 QCryptoCertFmt fmt, Error **errp)
> 
> Indentation seems to be off by one?
> 
>> +{
>> +    int rc;
>> +    int ret = 0;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +
>> +    if (fmt >= G_N_ELEMENTS(qcrypto_to_gnutls_cert_fmt_map)) {
>> +        error_setg(errp, "Unknown certificate format");
>> +        return ret;
>  > +    }> +
>> +    if (gnutls_x509_crt_init(&crt) < 0) {
>> +        error_setg(errp, "Failed to initialize certificate");
>> +        goto cleanup;
> 
> So this will do a gnutls_x509_crt_deinit() in case the init() failed ... is 
> that OK or should deinit() only be called after init() succeeded?
> 
>> +    }
>> +
>> +    rc = gnutls_x509_crt_import(crt, &datum, qcrypto_to_gnutls_cert_fmt_map[fmt]);
>> +    if (rc == GNUTLS_E_ASN1_TAG_ERROR) {
>> +        ret = 0;
>> +        goto cleanup;
>> +    }
>> +
>> +    ret = 1;
>> +
>> +cleanup:
>> +    gnutls_x509_crt_deinit(crt);
>> +    return ret;
>> +}
> 
> The return code handling of this function is somewhat confusing. It looks 
> like it is meant to return a boolean value (1 for success, 0 for failure), 
> and that's also the way you use it in the function below, but the return 
> type is "int". Even worse the stub function at the end of the file does a " 
>    return -ENOTSUP;". Although this does not seem to be a problem right now, 
> this might be very fragile for future changes (future code that's expecting 
> a 0 for failures and non-zero for succcess will fail with -ENOTSUP).
> 
> I'd suggest to rework this function so that it 0 for success and a negative 
> error code in case of errors to avoid the possibility of confusion.
> 
>> +static int qcrypto_get_x509_cert_fmt(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    int fmt;
> 
> If you initialize fmt with -1 here ...
> 
>> +    if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_DER, errp)) {
>> +        fmt = GNUTLS_X509_FMT_DER;
>> +    } else if (qcrypto_check_x509_cert_fmt(cert, size, QCRYPTO_CERT_FMT_PEM, errp)) {
>> +        fmt = GNUTLS_X509_FMT_PEM;
>> +    } else {
>> +        return -1;
> 
> ... you can drop the final else branch here.
> 
>> +    }
>> +
>> +    return fmt;
>> +}
>> +
>> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
>> +{
>> +    if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
>> +        error_setg(errp, "Unknown hash algorithm");
>> +        return 0;
> 
> Should that maybe be an assert statement instead?
> 
> Anyway, in this case you set errp and return 0 ...
> 
>> +    }
>> +
>> +    return gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]);
> 
> ... but this also might return 0 and does not set errp. That means that the 
> caller cannot rely on errp being set (and I think you also never use it?).
> ==> Remove the error_setg() from this function?
> 
>> +}
>> +
>> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
>> +{
>> +    QCryptoHashAlgo alg;
>> +
>> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
>> +        error_setg(errp, "Unknown key id flag");
>> +        return 0;
>> +    }
> 
> dito?
> 
>> +    alg = QCRYPTO_HASH_ALGO_SHA1;
>> +    if ((flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA512]) ||
>> +        (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_BEST_KNOWN])) {
>> +        alg = QCRYPTO_HASH_ALGO_SHA512;
>> +    } else if (flag & qcrypto_to_gnutls_keyid_flags_map[QCRYPTO_KEYID_FLAGS_SHA256]) {
>> +        alg = QCRYPTO_HASH_ALGO_SHA256;
>> +    }
>> +
>> +    return qcrypto_get_x509_hash_len(alg, errp);
>> +}
>> +
>>   int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>>                                         QCryptoHashAlgo alg,
>>                                         uint8_t *result,
>> @@ -74,3 +168,72 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>>       gnutls_x509_crt_deinit(crt);
>>       return ret;
>>   }
>> +
>> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    int rc = -1;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +    gnutls_x509_crt_fmt_t fmt;
>> +
>> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
>> +    if (fmt == -1) {
>> +        error_setg(errp, "Certificate is neither in DER or PEM format");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_init(&crt) < 0) {
>> +        error_setg(errp, "Failed to initialize certificate");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
>> +        error_setg(errp, "Failed to import certificate");
>> +        goto cleanup;
>> +    }
>> +
>> +    rc = gnutls_x509_crt_get_signature_algorithm(crt);
>> +
>> +cleanup:
>> +    gnutls_x509_crt_deinit(crt);
>> +    return rc;
>> +}
>> +
>> +#else /* ! CONFIG_GNUTLS */
>> +
>> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
>> +                                 QCryptoCertFmt fmt, Error **errp)
>> +{
>> +    error_setg(errp, "To get certificate format requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp)
>> +{
>> +    error_setg(errp, "To get hash length requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp)
>> +{
>> +    error_setg(errp, "To get key ID length requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +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, "To get fingerprint requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    error_setg(errp, "To get signature algorithm requires GNUTLS");
>> +    return -ENOTSUP;
>> +}
>> +
>> +#endif /* ! CONFIG_GNUTLS */
>> diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
>> new file mode 100644
>> index 0000000000..87bf9e381d
>> --- /dev/null
>> +++ b/hw/s390x/cert-store.c
>> @@ -0,0 +1,242 @@
>> +/*
>> + * 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 "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, size_t max_size, char **cert_buf)
>> +{
>> +    size_t size;
>> +    g_autofree char *buf;
> 
> Maybe set *cert_buf = NULL here, just in case the get_contents() fails below?
> 
>> +    /*
>> +     * maximum allowed size of the certificate file to avoid consuming excessive memory
>> +     * (malformed or maliciously large files)
>> +     */
>> +    if (!g_file_get_contents(path, &buf, &size, NULL) ||
>> +        size == 0 || size > max_size) {
>> +        return 0;
>> +    }
>> +
>> +    *cert_buf = g_steal_pointer(&buf);
> 
> I wonder whether you need the detour via buf here at all? I expect that glib 
> does not return a valid buffer pointer in case the get_contents() function 
> fails, so you could rather pass cert_buf to it directly?
> 
>> +    return size;
>> +}
>> +
>> +static S390IPLCertificate *init_cert_x509_der(size_t size, char *raw)
>> +{
>> +    S390IPLCertificate *q_cert = NULL;
>> +    int key_id_size;
>> +    int hash_size;
>> +    int is_der;
>> +    int hash_type;
>> +    Error *err = NULL;
>> +
>> +    is_der = qcrypto_check_x509_cert_fmt((uint8_t *)raw, size,
>> +                                         QCRYPTO_CERT_FMT_DER, &err);
>> +    /* return early if GNUTLS is not enabled */
>> +    if (is_der == -ENOTSUP) {
>> +        error_report("GNUTLS is not enabled");
>> +        return q_cert;
> 
> I'd maybe use "return NULL" here instead to make it more obvious. (same for 
> the other early returns below)
> 
>> +    }
>> +    if (!is_der) {
>> +        error_report("The certificate is not in DER format");
>> +        return q_cert;
>> +    }
>> +
>> +    hash_type = qcrypto_get_x509_signature_algorithm((uint8_t *)raw, size, &err);
>> +    if (hash_type != QCRYPTO_SIG_ALGO_RSA_SHA256) {
>> +        error_report("The certificate does not use SHA-256 hashing");
>> +        return q_cert;
>> +    }
>> +
>> +    key_id_size = qcrypto_get_x509_keyid_len(QCRYPTO_KEYID_FLAGS_SHA256, &err);
>> +    if (key_id_size == 0) {
>> +        error_report("Failed to get certificate key ID size");
>> +        return q_cert;
>> +    }
>> +
>> +    hash_size = qcrypto_get_x509_hash_len(QCRYPTO_HASH_ALGO_SHA256, &err);
>> +    if (hash_size == 0) {
>> +        error_report("Failed to get certificate hash size");
>> +        return q_cert;
>> +    }
>> +
>> +    q_cert = g_new(S390IPLCertificate, 1);
>> +    q_cert->size = size;
>> +    q_cert->key_id_size = key_id_size;
>> +    q_cert->hash_size = hash_size;
>> +    q_cert->raw = raw;
>> +    q_cert->format = QCRYPTO_CERT_FMT_DER;
>> +    q_cert->hash_type = QCRYPTO_SIG_ALGO_RSA_SHA256;
>> +
>> +    return q_cert;
>> +}
>> +
>> +static int check_path_type(const char *path)
>> +{
>> +    struct stat path_stat;
>> +
>> +    if (stat(path, &path_stat) != 0) {
>> +        perror("stat");
>> +        return -1;
>> +    }
>> +
>> +    if (S_ISDIR(path_stat.st_mode)) {
>> +        return S_IFDIR;
>> +    } else if (S_ISREG(path_stat.st_mode)) {
>> +        return S_IFREG;
>> +    } else {
>> +        return -1;
>> +    }
>> +}
>> +
>> +static S390IPLCertificate *init_cert(char *paths)
>> +{
>> +    char *buf;
>> +    char vc_name[VC_NAME_LEN_BYTES];
>> +    g_autofree gchar *filename;
>> +    size_t size;
>> +    S390IPLCertificate *qcert = NULL;
>> +
>> +    filename = g_path_get_basename(paths);
>> +
>> +    size = cert2buf(paths, CERT_MAX_SIZE, &buf);
>> +    if (size == 0) {
>> +        error_report("Failed to load certificate: %s", paths);
>> +        return qcert;
> 
> dito
> 
>> +    }
>> +
>> +    qcert = init_cert_x509_der(size, buf);
>> +    if (qcert == NULL) {
>> +        error_report("Failed to initialize certificate: %s", paths);
>> +        g_free(buf);
>> +        return qcert;
>> +    }
>> +
>> +    /*
>> +     * 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);
> 
> No g_free(buf) here? Should it be marked with g_autofree ?
> 
>   Thomas
> 
> 

I believe g_free can be omitted here, as ownership of the buf pointer is
transferred to qcert after init_cert_x509_der() completes successfully.

>> +    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->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 *path;
>> +    gchar **paths;
>> +    gchar **paths_copy;
>> +    int path_type;
>> +    GDir *dir = NULL;
>> +    gchar *cert_path;
>> +    const gchar *filename;
>> +    GPtrArray *cert_path_builder;
>> +
>> +    cert_path_builder = g_ptr_array_new();
>> +
>> +    path = s390_get_boot_certificates();
>> +    if (path == NULL) {
>> +        return cert_path_builder;
>> +    }
>> +
>> +    paths = g_strsplit(path, ":", -1);
>> +    /* save the original pointer for freeing later */
>> +    paths_copy = paths;
>> +    while (*paths) {
>> +        /* skip empty certificate path */
>> +        if (!strcmp(*paths, "")) {
>> +            paths += 1;
>> +            continue;
>> +        }
>> +
>> +        cert_path = NULL;
>> +        path_type = check_path_type(*paths);
>> +        if (path_type == S_IFREG) {
>> +            cert_path = g_strdup(*paths);
>> +            g_ptr_array_add(cert_path_builder, cert_path);
>> +        } else if (path_type == S_IFDIR) {
>> +            dir = g_dir_open(*paths, 0, NULL);
>> +
>> +            if (dir) {
>> +                while ((filename = g_dir_read_name(dir))) {
>> +                    cert_path = g_build_filename(*paths, filename, NULL);
>> +                    g_ptr_array_add(cert_path_builder, (gpointer) cert_path);
>> +                }
>> +
>> +                g_dir_close(dir);
>> +            }
>> +        }
>> +
>> +        paths += 1;
>> +    }
>> +
>> +    g_strfreev(paths_copy);
>> +    return 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++) {
>> +        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..04acc6c2e0
>> --- /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 key_id_size;
>> +    size_t hash_size;
>> +    char *raw;
>> +    QCryptoCertFmt format;
>> +    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 505cded490..ac1f7517ea 100644
>> --- a/hw/s390x/ipl.h
>> +++ b/hw/s390x/ipl.h
>> @@ -13,6 +13,7 @@
>>   #ifndef HW_S390_IPL_H
>>   #define HW_S390_IPL_H
>>   
>> +#include "cert-store.h"
>>   #include "cpu.h"
>>   #include "exec/target_page.h"
>>   #include "system/address-spaces.h"
>> @@ -35,6 +36,7 @@ int s390_ipl_pv_unpack(struct S390PVResponse *pv_resp);
>>   void s390_ipl_prepare_cpu(S390CPU *cpu);
>>   IplParameterBlock *s390_ipl_get_iplb(void);
>>   IplParameterBlock *s390_ipl_get_iplb_pv(void);
>> +S390IPLCertificateStore *s390_ipl_get_certificate_store(void);
>>   
>>   enum s390_reset {
>>       /* default is a reset not triggered by a CPU e.g. issued by QMP */
>> @@ -63,6 +65,7 @@ struct S390IPLState {
>>       IplParameterBlock iplb;
>>       IplParameterBlock iplb_pv;
>>       QemuIplParameters qipl;
>> +    S390IPLCertificateStore cert_store;
>>       uint64_t start_addr;
>>       uint64_t compat_start_addr;
>>       uint64_t bios_start_addr;
>> diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build
>> index 11e4e78b85..5b02f47155 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/crypto/x509-utils.h b/include/crypto/x509-utils.h
>> index 1e99661a71..8fb263b9e1 100644
>> --- a/include/crypto/x509-utils.h
>> +++ b/include/crypto/x509-utils.h
>> @@ -19,4 +19,10 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>>                                         size_t *resultlen,
>>                                         Error **errp);
>>   
>> +int qcrypto_check_x509_cert_fmt(uint8_t *cert, size_t size,
>> +                                 QCryptoCertFmt fmt, Error **errp);
>> +int qcrypto_get_x509_hash_len(QCryptoHashAlgo alg, Error **errp);
>> +int qcrypto_get_x509_keyid_len(QCryptoKeyidFlags flag, Error **errp);
>> +int qcrypto_get_x509_signature_algorithm(uint8_t *cert, size_t size, Error **errp);
>> +
>>   #endif
>> diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
>> index 6824391111..b8e7d1da71 100644
>> --- a/include/hw/s390x/ipl/qipl.h
>> +++ b/include/hw/s390x/ipl/qipl.h
>> @@ -20,6 +20,9 @@
>>   #define LOADPARM_LEN    8
>>   #define NO_LOADPARM "\0\0\0\0\0\0\0\0"
>>   
>> +#define MAX_CERTIFICATES 64
>> +#define CERT_MAX_SIZE     (1024 * 8)
>> +
>>   /*
>>    * The QEMU IPL Parameters will be stored at absolute address
>>    * 204 (0xcc) which means it is 32-bit word aligned but not
>> diff --git a/qapi/crypto.json b/qapi/crypto.json
>> index c9d967d782..2bbf29affe 100644
>> --- a/qapi/crypto.json
>> +++ b/qapi/crypto.json
>> @@ -612,3 +612,83 @@
>>     'base': { 'alg': 'QCryptoAkCipherAlgo' },
>>     'discriminator': 'alg',
>>     'data': { 'rsa': 'QCryptoAkCipherOptionsRSA' }}
>> +
>> +##
>> +# @QCryptoKeyidFlags:
>> +#
>> +# The supported flags for the key ID
>> +#
>> +# @sha1: SHA-1
>> +#
>> +# @sha256: SHA-256
>> +#
>> +# @sha512: SHA-512
>> +#
>> +# @best-known: BEST-KNOWN
>> +#
>> +# Since: 9.2
>> +##
>> +{ 'enum': 'QCryptoKeyidFlags',
>> +  'data': ['sha1', 'sha256', 'sha512', 'best-known']}
>> +
>> +##
>> +# @QCryptoCertFmt:
>> +#
>> +# The supported certificate encoding formats
>> +#
>> +# @der: DER
>> +#
>> +# @pem: PEM
>> +#
>> +# Since: 9.2
>> +##
>> +{ 'enum': 'QCryptoCertFmt',
>> +  'data': ['der', 'pem']}
>> +
>> +##
>> +# @QCryptoSigAlgo:
>> +#
>> +# Algorithms for digital signature
>> +#
>> +# @unknown: UNKNOWN
>> +#
>> +# @rsa-sha1: RSA-SHA1 or RSA-SHA
>> +#
>> +# @dsa-sha1: DSA-SHA1 or DSA-SHA
>> +#
>> +# @rsa-md5: RSA-MD5
>> +#
>> +# @rsa-md2: RSA-MD2
>> +#
>> +# @rsa-rmd160: RSA-RMD160
>> +#
>> +# @rsa-sha256: RSA-SHA256
>> +#
>> +# @rsa-sha384: RSA-SHA384
>> +#
>> +# @rsa-sha512: RSA-SHA512
>> +#
>> +# @rsa-sha224: RSA-SHA224
>> +#
>> +# @dsa-sha224: DSA-SHA224
>> +#
>> +# @dsa-sha256: DSA-SHA256
>> +#
>> +# @ecdsa-sha1: ECDSA-SHA1
>> +#
>> +# @ecdsa-sha224: ECDSA-SHA224
>> +#
>> +# @ecdsa-sha256: ECDSA-SHA256
>> +#
>> +# @ecdsa-sha384: ECDSA-SHA384
>> +#
>> +# @ecdsa-sha512: ECDSA-SHA512
>> +#
>> +# Since: 9.2
>> +##
>> +{ 'enum': 'QCryptoSigAlgo',
>> +  'data': ['unknown', 'rsa-sha1', 'dsa-sha1',
>> +           'rsa-md5', 'rsa-md2', 'rsa-rmd160',
>> +           'rsa-sha256', 'rsa-sha384', 'rsa-sha512', 'rsa-sha224',
>> +           'dsa-sha224', 'dsa-sha256',
>> +           'ecdsa-sha1', 'ecdsa-sha224', 'ecdsa-sha256', 'ecdsa-sha384', 'ecdsa-sha512']}
> 



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

* Re: [PATCH v2 07/25] s390x/diag: Implement DIAG 320 subcode 2
  2025-05-14 16:18   ` Thomas Huth
@ 2025-05-29 19:09     ` Zhuoying Cai
  2025-05-30  6:38       ` Thomas Huth
  0 siblings, 1 reply; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-29 19:09 UTC (permalink / raw)
  To: Thomas Huth, richard.henderson, david, pbonzini,
	Daniel P. Berrange
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel


On 5/14/25 12:18 PM, Thomas Huth wrote:
> On 09/05/2025 00.50, 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>
>> ---
>>   crypto/x509-utils.c            | 204 ++++++++++++++++++++++++++++-
>>   include/crypto/x509-utils.h    |  10 ++
>>   include/hw/s390x/ipl/diag320.h |  47 +++++++
>>   qapi/crypto.json               |  20 +++
>>   target/s390x/diag.c            | 227 ++++++++++++++++++++++++++++++++-
>>   5 files changed, 506 insertions(+), 2 deletions(-)
>>
>> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
>> index 0b8cfc9022..51bd75d4eb 100644
>> --- a/crypto/x509-utils.c
>> +++ b/crypto/x509-utils.c
>> @@ -129,6 +129,7 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>>       int hlen;
>>       gnutls_x509_crt_t crt;
>>       gnutls_datum_t datum = {.data = cert, .size = size};
>> +    gnutls_x509_crt_fmt_t fmt;
>>   
>>       if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
>>           error_setg(errp, "Unknown hash algorithm");
>> @@ -140,9 +141,15 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
>>           return -1;
>>       }
>>   
>> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
>> +    if (fmt == -1) {
>> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> 
> qcrypto_get_x509_cert_fmt() already has an errp parameter, so I'd expect 
> that the qcrypto_get_x509_cert_fmt() function already sets up errp in case 
> of errors, doesn't it? In that case you should not set errp here again, I think.
> 
>> +        return -1;
>> +    }
>> +
>>       gnutls_x509_crt_init(&crt);
>>   
>> -    if (gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM) != 0) {
>> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
>>           error_setg(errp, "Failed to import certificate");
>>           goto cleanup;
>>       }
>> @@ -199,6 +206,173 @@ cleanup:
>>       return rc;
>>   }
>>   
>> +int qcrypto_get_x509_cert_version(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    int rc = -1;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +    gnutls_x509_crt_fmt_t fmt;
>> +
>> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
>> +    if (fmt == -1) {
>> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> 
> dito?
> 
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_init(&crt) < 0) {
>> +        error_setg(errp, "Failed to initialize certificate");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
>> +        error_setg(errp, "Failed to import certificate");
>> +        goto cleanup;
>> +    }
>> +
>> +    rc = gnutls_x509_crt_get_version(crt);
>> +
>> +cleanup:
>> +    gnutls_x509_crt_deinit(crt);
>> +    return rc;
>> +}
>> +
>> +int qcrypto_check_x509_cert_times(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    int rc = -1;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +    time_t now = time(0);
>> +    gnutls_x509_crt_fmt_t fmt;
>> +
>> +    if (now == ((time_t)-1)) {
>> +        error_setg(errp, "Cannot get current time");
> 
> Maybe use error_setg_errno() here to get the information from errno, too?
> 
>> +        return rc;
>> +    }
>> +
>> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
>> +    if (fmt == -1) {
>> +        error_setg(errp, "Certificate is neither in DER or PEM format");
> 
> This is again ignoring the errp from qcrypto_get_x509_cert_fmt().
> 
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_init(&crt) < 0) {
>> +        error_setg(errp, "Failed to initialize certificate");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
>> +        error_setg(errp, "Failed to import certificate");
>> +        goto cleanup;
>> +    }
>> +
>> +    if (gnutls_x509_crt_get_expiration_time(crt) < now) {
>> +        error_setg(errp, "The certificate has expired");
>> +        goto cleanup;
>> +    }
>> +
>> +    if (gnutls_x509_crt_get_activation_time(crt) > now) {
>> +        error_setg(errp, "The certificate is not yet active");
>> +        goto cleanup;
>> +    }
> 
> gnutls_x509_crt_get_expiration_time() and 
> gnutls_x509_crt_get_activation_time() can both return -1 on errors. I think 
> you should take that into account in the checks here, too.
> 
>> +    rc = 0;
>> +
>> +cleanup:
>> +    gnutls_x509_crt_deinit(crt);
>> +    return rc;
>> +}
>> +
>> +int qcrypto_get_x509_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
>> +{
>> +    int rc = -1;
>> +    unsigned int bits;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +    gnutls_x509_crt_fmt_t fmt;
>> +
>> +    fmt = qcrypto_get_x509_cert_fmt(cert, size, errp);
>> +    if (fmt == -1) {
>> +        error_setg(errp, "Certificate is neither in DER or PEM format");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_init(&crt) < 0) {
>> +        error_setg(errp, "Failed to initialize certificate");
>> +        return rc;
>> +    }
>> +
>> +    if (gnutls_x509_crt_import(crt, &datum, fmt) != 0) {
>> +        error_setg(errp, "Failed to import certificate");
>> +        goto cleanup;
>> +    }
>> +
>> +    rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
>> +
>> +cleanup:
>> +    gnutls_x509_crt_deinit(crt);
>> +    return rc;
>> +}
>> +
>> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
>> +                                 QCryptoKeyidFlags flag,
>> +                                 uint8_t *result,
>> +                                 size_t *resultlen,
>> +                                 Error **errp)
>> +{
>> +    int ret = -1;
>> +    int keyid_len;
>> +    gnutls_x509_crt_t crt;
>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>> +    gnutls_x509_crt_fmt_t fmt;
>> +
>> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
>> +        error_setg(errp, "Unknown key id flag");
>> +        return -1;
>> +    }
>> +
>> +    if (result == NULL) {
>> +        error_setg(errp, "No valid buffer given");
>> +        return -1;
>> +    }
> 
> This check sounds like it could also be a simple g_assert() statement instead?
> 

g_assert() may not be ideal here, as it will terminate the guest if the
assertion fails, which is not the intended behavior.

[...]


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

* Re: [PATCH v2 17/25] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
  2025-05-20 10:25   ` Thomas Huth
@ 2025-05-29 19:28     ` Zhuoying Cai
  0 siblings, 0 replies; 49+ messages in thread
From: Zhuoying Cai @ 2025-05-29 19:28 UTC (permalink / raw)
  To: Thomas Huth, richard.henderson, david, pbonzini
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel


On 5/20/25 6:25 AM, Thomas Huth wrote:
> On 09/05/2025 00.50, 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>
>> ---
>>   pc-bios/s390-ccw/Makefile     |   3 +-
>>   pc-bios/s390-ccw/bootmap.c    | 192 +++++++++++++++++++++++++++++++++-
>>   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 | 175 +++++++++++++++++++++++++++++++
>>   pc-bios/s390-ccw/secure-ipl.h | 109 +++++++++++++++++++
>>   9 files changed, 558 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 3dd09fda7e..06cea0929a 100644
>> --- a/pc-bios/s390-ccw/bootmap.c
>> +++ b/pc-bios/s390-ccw/bootmap.c
>> @@ -15,6 +15,7 @@
>>   #include "bootmap.h"
>>   #include "virtio.h"
>>   #include "bswap.h"
>> +#include "secure-ipl.h"
>>   
>>   #ifdef DEBUG
>>   /* #define DEBUG_FALLBACK */
>> @@ -34,6 +35,13 @@ 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 certificates */
>> +static uint8_t certs_sec[CERT_MAX_SIZE * MAX_CERTIFICATES];
> 
> If I calculated correctly, that's a buffer of 512 kB... That's quite huge 
> already. Would it be possible to malloc() it only if we really need this 
> instead of statically allocating it?
> 
>> +/* sector for storing signatures */
>> +static uint8_t sig_sec[MAX_SECTOR_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
>> +
>> +ipl_print_func_t zipl_secure_print_func;
>> +
>>   /*
>>    * Match two CCWs located after PSW and eight filler bytes.
>>    * From libmagic and arch/s390/kernel/head.S.
>> @@ -676,6 +684,155 @@ 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;
> 
> So zipl_secure_cert_list_add() checks for the index not going beyond 
> MAX_CERTIFICATES, but here you ignore that error and update cert_table and 
> *cert anyway? Sounds like a potential bug to me.
> 

If zipl_secure_request_certificate() successfully retrieves a
certificate from the S390 certificate store, updating the corresponding
entry in cert_table should not pose any issues. This is because we
strictly limit the number of certificates to MAX_CERTIFICATES, and
cert_idx is guaranteed to be within the range [0, 63].

The size of cert_table and the memory allocated for *cert are both
defined based on MAX_CERTIFICATES and MAX_CERT_SIZE, so as long as the
request is successful, the update is safe.

The index check in zipl_secure_cert_list_add() ensures that the IIRB
cert list does not exceed the valid range defined by MAX_CERTIFICATES,
preventing out-of-bound memory overwrites.

>> +        } else {
>> +            puts("Could not get certificate");
>> +            return -1;
>> +        }
>> +
>> +        /* increment cert_index for the next cert entry */
>> +        return ++cert_index;
>> +    }
>> +
>> +    return cert_index;
>> +}
>> +

[...]


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

* Re: [PATCH v2 07/25] s390x/diag: Implement DIAG 320 subcode 2
  2025-05-29 19:09     ` Zhuoying Cai
@ 2025-05-30  6:38       ` Thomas Huth
  0 siblings, 0 replies; 49+ messages in thread
From: Thomas Huth @ 2025-05-30  6:38 UTC (permalink / raw)
  To: Zhuoying Cai, richard.henderson, david, pbonzini,
	Daniel P. Berrange
  Cc: walling, jjherne, jrossi, fiuczy, pasic, borntraeger, farman, iii,
	qemu-s390x, qemu-devel

On 29/05/2025 21.09, Zhuoying Cai wrote:
> 
> On 5/14/25 12:18 PM, Thomas Huth wrote:
>> On 09/05/2025 00.50, Zhuoying Cai wrote:
...
>>> +int qcrypto_get_x509_cert_key_id(uint8_t *cert, size_t size,
>>> +                                 QCryptoKeyidFlags flag,
>>> +                                 uint8_t *result,
>>> +                                 size_t *resultlen,
>>> +                                 Error **errp)
>>> +{
>>> +    int ret = -1;
>>> +    int keyid_len;
>>> +    gnutls_x509_crt_t crt;
>>> +    gnutls_datum_t datum = {.data = cert, .size = size};
>>> +    gnutls_x509_crt_fmt_t fmt;
>>> +
>>> +    if (flag >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
>>> +        error_setg(errp, "Unknown key id flag");
>>> +        return -1;
>>> +    }
>>> +
>>> +    if (result == NULL) {
>>> +        error_setg(errp, "No valid buffer given");
>>> +        return -1;
>>> +    }
>>
>> This check sounds like it could also be a simple g_assert() statement instead?
>>
> 
> g_assert() may not be ideal here, as it will terminate the guest if the
> assertion fails, which is not the intended behavior.

OK, but if it is valid to call this function with result == NULL, then this 
does not sound like an error, thus you likely should not use error_setg() 
here. If it *not* valid to call this function with result == NULL, then 
there is a programming error and it is OK to use g_assert() here.
Anyway, please add a comment in front of the function where you describe the 
parameters, including the information whether the pointers are allowed to be 
NULL or not.

  Thomas



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

end of thread, other threads:[~2025-05-30  6:39 UTC | newest]

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