* [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-18 6:56 ` Markus Armbruster
2025-09-30 9:34 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
` (27 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
machine. This allows users to specify one or more certificate file paths
or directories to be used during secure boot.
Each entry is specified using the syntax:
boot-certs.<index>.path=/path/to/cert.pem
Multiple paths can be specify using array properties:
boot-certs.0.path=/path/to/cert.pem,
boot-certs.1.path=/path/to/cert-dir,
boot-certs.2.path=/path/to/another-dir...
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/system/s390x/secure-ipl.rst | 21 +++++++++++++++++++++
hw/s390x/s390-virtio-ccw.c | 30 ++++++++++++++++++++++++++++++
include/hw/s390x/s390-virtio-ccw.h | 2 ++
qapi/machine-s390x.json | 22 ++++++++++++++++++++++
qapi/pragma.json | 1 +
qemu-options.hx | 6 +++++-
6 files changed, 81 insertions(+), 1 deletion(-)
create mode 100644 docs/system/s390x/secure-ipl.rst
diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
new file mode 100644
index 0000000000..92c1bb2153
--- /dev/null
+++ b/docs/system/s390x/secure-ipl.rst
@@ -0,0 +1,21 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Secure IPL Command Line Options
+===============================
+
+The s390-ccw-virtio machine type supports secure IPL. These parameters allow users
+to provide certificates and enable secure IPL directly via the command line.
+
+Providing Certificates
+----------------------
+
+The certificate store can be populated by supplying a list of X.509 certificate file
+paths or directories containing certificate files on the command-line:
+
+Note: certificate files must have a .pem extension.
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio, \
+ boot-certs.0.path=/.../qemu/certs, \
+ boot-certs.1.path=/another/path/cert.pem ...
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index b1dc52807a..b825f4cce1 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -45,6 +45,7 @@
#include "target/s390x/kvm/pv.h"
#include "migration/blocker.h"
#include "qapi/visitor.h"
+#include "qapi/qapi-visit-machine-s390x.h"
#include "hw/s390x/cpu-topology.h"
#include "kvm/kvm_s390x.h"
#include "hw/virtio/virtio-md-pci.h"
@@ -798,6 +799,30 @@ static void machine_set_loadparm(Object *obj, Visitor *v,
g_free(val);
}
+static void machine_get_boot_certs(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+ BootCertificateList **certs = &ms->boot_certs;
+
+ visit_type_BootCertificateList(v, name, certs, errp);
+}
+
+static void machine_set_boot_certs(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+ BootCertificateList *cert_list = NULL;
+
+ visit_type_BootCertificateList(v, name, &cert_list, errp);
+ if (!cert_list) {
+ return;
+ }
+
+ ms->boot_certs = cert_list;
+}
+
static void ccw_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -851,6 +876,11 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
"Up to 8 chars in set of [A-Za-z0-9. ] (lower case chars converted"
" to upper case) to pass to machine loader, boot manager,"
" and guest kernel");
+
+ object_class_property_add(oc, "boot-certs", "BootCertificateList",
+ machine_get_boot_certs, machine_set_boot_certs, NULL, NULL);
+ object_class_property_set_description(oc, "boot-certs",
+ "provide paths to a directory and/or a certificate file for secure boot");
}
static inline void s390_machine_initfn(Object *obj)
diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
index 526078a4e2..334b67ef05 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -14,6 +14,7 @@
#include "hw/boards.h"
#include "qom/object.h"
#include "hw/s390x/sclp.h"
+#include "qapi/qapi-types-machine-s390x.h"
#define TYPE_S390_CCW_MACHINE "s390-ccw-machine"
@@ -31,6 +32,7 @@ struct S390CcwMachineState {
uint8_t loadparm[8];
uint64_t memory_limit;
uint64_t max_pagesize;
+ BootCertificateList *boot_certs;
SCLPDevice *sclp;
};
diff --git a/qapi/machine-s390x.json b/qapi/machine-s390x.json
index 966dbd61d2..51bf791fe6 100644
--- a/qapi/machine-s390x.json
+++ b/qapi/machine-s390x.json
@@ -119,3 +119,25 @@
{ 'command': 'query-s390x-cpu-polarization', 'returns': 'CpuPolarizationInfo',
'features': [ 'unstable' ]
}
+
+##
+# @BootCertificate:
+#
+# Boot certificate for secure IPL.
+#
+# @path: path to an X.509 certificate file or a directory containing certificate files.
+#
+# Since: 10.2
+##
+{ 'struct': 'BootCertificate',
+ 'data': {'path': 'str'} }
+
+##
+# @DummyBootCertificates:
+#
+# Not used by QMP; hack to let us use BootCertificateList internally.
+#
+# Since: 10.2
+##
+{ 'struct': 'DummyBootCertificates',
+ 'data': {'unused-boot-certs': ['BootCertificate'] } }
diff --git a/qapi/pragma.json b/qapi/pragma.json
index 023a2ef7bc..66401837ad 100644
--- a/qapi/pragma.json
+++ b/qapi/pragma.json
@@ -49,6 +49,7 @@
'DisplayProtocol',
'DriveBackupWrapper',
'DummyBlockCoreForceArrays',
+ 'DummyBootCertificates',
'DummyForceArrays',
'DummyVirtioForceArrays',
'HotKeyMod',
diff --git a/qemu-options.hx b/qemu-options.hx
index ab23f14d21..ac497eb3a0 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -44,7 +44,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-certs.0.path=/path/directory,boot-certs.1.path=/path/file provides paths to a directory and/or a certificate file\n",
QEMU_ARCH_ALL)
SRST
``-machine [type=]name[,prop=value[,...]]``
@@ -205,6 +206,9 @@ SRST
::
-machine smp-cache.0.cache=l1d,smp-cache.0.topology=core,smp-cache.1.cache=l1i,smp-cache.1.topology=core
+
+ ``boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file``
+ Provide paths to a directory and/or a certificate file on the host [s390x only].
ERST
DEF("M", HAS_ARG, QEMU_OPTION_M,
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-17 23:21 ` [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-09-18 6:56 ` Markus Armbruster
2025-09-18 8:38 ` Daniel P. Berrangé
2025-09-30 9:34 ` Thomas Huth
1 sibling, 1 reply; 89+ messages in thread
From: Markus Armbruster @ 2025-09-18 6:56 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, alifm
Zhuoying Cai <zycai@linux.ibm.com> writes:
> Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
> machine. This allows users to specify one or more certificate file paths
> or directories to be used during secure boot.
>
> Each entry is specified using the syntax:
> boot-certs.<index>.path=/path/to/cert.pem
>
> Multiple paths can be specify using array properties:
> boot-certs.0.path=/path/to/cert.pem,
> boot-certs.1.path=/path/to/cert-dir,
> boot-certs.2.path=/path/to/another-dir...
Given we can specifiy a directory containing any number of certificate
files, is the ability to specify multiple paths worth the additional
complexity?
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
[...]
> diff --git a/qapi/machine-s390x.json b/qapi/machine-s390x.json
> index 966dbd61d2..51bf791fe6 100644
> --- a/qapi/machine-s390x.json
> +++ b/qapi/machine-s390x.json
> @@ -119,3 +119,25 @@
> { 'command': 'query-s390x-cpu-polarization', 'returns': 'CpuPolarizationInfo',
> 'features': [ 'unstable' ]
> }
> +
> +##
> +# @BootCertificate:
> +#
> +# Boot certificate for secure IPL.
> +#
> +# @path: path to an X.509 certificate file or a directory containing certificate files.
> +#
> +# Since: 10.2
> +##
> +{ 'struct': 'BootCertificate',
> + 'data': {'path': 'str'} }
I'd call this BootCertificates (plural), because it can pull in any
number, not just than one.
> +
> +##
> +# @DummyBootCertificates:
> +#
> +# Not used by QMP; hack to let us use BootCertificateList internally.
> +#
> +# Since: 10.2
> +##
> +{ 'struct': 'DummyBootCertificates',
> + 'data': {'unused-boot-certs': ['BootCertificate'] } }
> diff --git a/qapi/pragma.json b/qapi/pragma.json
> index 023a2ef7bc..66401837ad 100644
> --- a/qapi/pragma.json
> +++ b/qapi/pragma.json
> @@ -49,6 +49,7 @@
> 'DisplayProtocol',
> 'DriveBackupWrapper',
> 'DummyBlockCoreForceArrays',
> + 'DummyBootCertificates',
> 'DummyForceArrays',
> 'DummyVirtioForceArrays',
> 'HotKeyMod',
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-18 6:56 ` Markus Armbruster
@ 2025-09-18 8:38 ` Daniel P. Berrangé
2025-09-18 8:51 ` Markus Armbruster
2025-09-22 23:48 ` Zhuoying Cai
0 siblings, 2 replies; 89+ messages in thread
From: Daniel P. Berrangé @ 2025-09-18 8:38 UTC (permalink / raw)
To: Markus Armbruster
Cc: Zhuoying Cai, thuth, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, alifm
On Thu, Sep 18, 2025 at 08:56:39AM +0200, Markus Armbruster wrote:
> Zhuoying Cai <zycai@linux.ibm.com> writes:
>
> > Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
> > machine. This allows users to specify one or more certificate file paths
> > or directories to be used during secure boot.
> >
> > Each entry is specified using the syntax:
> > boot-certs.<index>.path=/path/to/cert.pem
> >
> > Multiple paths can be specify using array properties:
> > boot-certs.0.path=/path/to/cert.pem,
> > boot-certs.1.path=/path/to/cert-dir,
> > boot-certs.2.path=/path/to/another-dir...
>
> Given we can specifiy a directory containing any number of certificate
> files, is the ability to specify multiple paths worth the additional
> complexity?
The typical scenario would be point to somewhere in /etc/pki
for some globally provided certs, and then also point to
somewhere local ($HOME) for custom extra certs. So IMHO it
is reasonable to want multiple paths, to avoid copying around
certs from different locations.
>
> > Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>
> [...]
>
> > diff --git a/qapi/machine-s390x.json b/qapi/machine-s390x.json
> > index 966dbd61d2..51bf791fe6 100644
> > --- a/qapi/machine-s390x.json
> > +++ b/qapi/machine-s390x.json
> > @@ -119,3 +119,25 @@
> > { 'command': 'query-s390x-cpu-polarization', 'returns': 'CpuPolarizationInfo',
> > 'features': [ 'unstable' ]
> > }
> > +
> > +##
> > +# @BootCertificate:
> > +#
> > +# Boot certificate for secure IPL.
> > +#
> > +# @path: path to an X.509 certificate file or a directory containing certificate files.
> > +#
> > +# Since: 10.2
> > +##
> > +{ 'struct': 'BootCertificate',
> > + 'data': {'path': 'str'} }
>
> I'd call this BootCertificates (plural), because it can pull in any
> number, not just than one.
>
> > +
> > +##
> > +# @DummyBootCertificates:
> > +#
> > +# Not used by QMP; hack to let us use BootCertificateList internally.
> > +#
> > +# Since: 10.2
> > +##
> > +{ 'struct': 'DummyBootCertificates',
> > + 'data': {'unused-boot-certs': ['BootCertificate'] } }
> > diff --git a/qapi/pragma.json b/qapi/pragma.json
> > index 023a2ef7bc..66401837ad 100644
> > --- a/qapi/pragma.json
> > +++ b/qapi/pragma.json
> > @@ -49,6 +49,7 @@
> > 'DisplayProtocol',
> > 'DriveBackupWrapper',
> > 'DummyBlockCoreForceArrays',
> > + 'DummyBootCertificates',
> > 'DummyForceArrays',
> > 'DummyVirtioForceArrays',
> > 'HotKeyMod',
>
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] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-18 8:38 ` Daniel P. Berrangé
@ 2025-09-18 8:51 ` Markus Armbruster
2025-09-23 1:31 ` Zhuoying Cai
2025-09-22 23:48 ` Zhuoying Cai
1 sibling, 1 reply; 89+ messages in thread
From: Markus Armbruster @ 2025-09-18 8:51 UTC (permalink / raw)
To: Daniel P. Berrangé
Cc: Zhuoying Cai, thuth, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, alifm
Daniel P. Berrangé <berrange@redhat.com> writes:
> On Thu, Sep 18, 2025 at 08:56:39AM +0200, Markus Armbruster wrote:
>> Zhuoying Cai <zycai@linux.ibm.com> writes:
>>
>> > Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
>> > machine. This allows users to specify one or more certificate file paths
>> > or directories to be used during secure boot.
>> >
>> > Each entry is specified using the syntax:
>> > boot-certs.<index>.path=/path/to/cert.pem
>> >
>> > Multiple paths can be specify using array properties:
>> > boot-certs.0.path=/path/to/cert.pem,
>> > boot-certs.1.path=/path/to/cert-dir,
>> > boot-certs.2.path=/path/to/another-dir...
>>
>> Given we can specifiy a directory containing any number of certificate
>> files, is the ability to specify multiple paths worth the additional
>> complexity?
>
> The typical scenario would be point to somewhere in /etc/pki
> for some globally provided certs, and then also point to
> somewhere local ($HOME) for custom extra certs. So IMHO it
> is reasonable to want multiple paths, to avoid copying around
> certs from different locations.
Thanks.
Preferably with BootCertificate renamed to BootCertificates
Acked-by: Markus Armbruster <armbru@redhat.com>
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-18 8:51 ` Markus Armbruster
@ 2025-09-23 1:31 ` Zhuoying Cai
0 siblings, 0 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-23 1:31 UTC (permalink / raw)
To: Markus Armbruster, Daniel P. Berrangé
Cc: thuth, richard.henderson, david, jrossi, qemu-s390x, qemu-devel,
walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, alifm
On 9/18/25 4:51 AM, Markus Armbruster wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
>
>> On Thu, Sep 18, 2025 at 08:56:39AM +0200, Markus Armbruster wrote:
>>> Zhuoying Cai <zycai@linux.ibm.com> writes:
>>>
>>>> Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
>>>> machine. This allows users to specify one or more certificate file paths
>>>> or directories to be used during secure boot.
>>>>
>>>> Each entry is specified using the syntax:
>>>> boot-certs.<index>.path=/path/to/cert.pem
>>>>
>>>> Multiple paths can be specify using array properties:
>>>> boot-certs.0.path=/path/to/cert.pem,
>>>> boot-certs.1.path=/path/to/cert-dir,
>>>> boot-certs.2.path=/path/to/another-dir...
>>>
>>> Given we can specifiy a directory containing any number of certificate
>>> files, is the ability to specify multiple paths worth the additional
>>> complexity?
>>
>> The typical scenario would be point to somewhere in /etc/pki
>> for some globally provided certs, and then also point to
>> somewhere local ($HOME) for custom extra certs. So IMHO it
>> is reasonable to want multiple paths, to avoid copying around
>> certs from different locations.
>
> Thanks.
>
> Preferably with BootCertificate renamed to BootCertificates
> Acked-by: Markus Armbruster <armbru@redhat.com>
>
I'll rename it in the next version. Thanks for the review!
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-18 8:38 ` Daniel P. Berrangé
2025-09-18 8:51 ` Markus Armbruster
@ 2025-09-22 23:48 ` Zhuoying Cai
2025-09-29 18:29 ` Collin Walling
1 sibling, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-22 23:48 UTC (permalink / raw)
To: Daniel P. Berrangé, Markus Armbruster
Cc: thuth, richard.henderson, david, jrossi, qemu-s390x, qemu-devel,
walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, alifm
On 9/18/25 4:38 AM, Daniel P. Berrangé wrote:
> On Thu, Sep 18, 2025 at 08:56:39AM +0200, Markus Armbruster wrote:
>> Zhuoying Cai <zycai@linux.ibm.com> writes:
>>
>>> Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
>>> machine. This allows users to specify one or more certificate file paths
>>> or directories to be used during secure boot.
>>>
>>> Each entry is specified using the syntax:
>>> boot-certs.<index>.path=/path/to/cert.pem
>>>
>>> Multiple paths can be specify using array properties:
>>> boot-certs.0.path=/path/to/cert.pem,
>>> boot-certs.1.path=/path/to/cert-dir,
>>> boot-certs.2.path=/path/to/another-dir...
>>
>> Given we can specifiy a directory containing any number of certificate
>> files, is the ability to specify multiple paths worth the additional
>> complexity?
>
> The typical scenario would be point to somewhere in /etc/pki
> for some globally provided certs, and then also point to
> somewhere local ($HOME) for custom extra certs. So IMHO it
> is reasonable to want multiple paths, to avoid copying around
> certs from different locations.
>
Thank you for the comments.
Since Secure IPL on s390x is supported in QEMU, I would like to begin
drafting the corresponding Libvirt interface and seek feedback before
proceeding with the implementation.
While Libvirt already provides a secure boot interface
(https://libvirt.org/kbase/secureboot.html), it appears to be primarily
intended for x86 systems, where secure boot is configured using the
<firmware>, <loader>, and <nvram> tags.
<os firmware='efi'>
<firmware>
<feature enabled='yes' name='enrolled-keys'/>
<feature enabled='yes' name='secure-boot'/>
</firmware>
<loader secure='yes' type='pflash'>...</loader>
<nvram template='...'>...</nvram>
</os>
For s390x, some of these existing tags may be reused, but additional
elements will be needed.
Below is my initial proposal for the secure boot interface in Libvirt:
<!-- New s390-ccw-bios firmware value -->
<os firmware='s390-ccw-bios'>
<type arch='s390x' machine='s390-ccw-virtio-9.2'>hvm</type>
<firmware>
<!-- To enable secure boot -->
<feature enabled='yes' name='secure-boot'/>
</firmware>
<!-- To provide boot certificates for secure boot -->
<boot-certs path='/path/to/cert.pem' />
<boot-certs path='/path/to/cert-dir' />
<boot dev='hd'/>
</os>
I would be greatly appreciate any suggestions or feedback on this
proposal, and I am open to refining the design to better align with
existing Libvirt structures.
Best regards,
Joy
>>
>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>>
>> [...]
>>
>>> diff --git a/qapi/machine-s390x.json b/qapi/machine-s390x.json
>>> index 966dbd61d2..51bf791fe6 100644
>>> --- a/qapi/machine-s390x.json
>>> +++ b/qapi/machine-s390x.json
>>> @@ -119,3 +119,25 @@
>>> { 'command': 'query-s390x-cpu-polarization', 'returns': 'CpuPolarizationInfo',
>>> 'features': [ 'unstable' ]
>>> }
>>> +
>>> +##
>>> +# @BootCertificate:
>>> +#
>>> +# Boot certificate for secure IPL.
>>> +#
>>> +# @path: path to an X.509 certificate file or a directory containing certificate files.
>>> +#
>>> +# Since: 10.2
>>> +##
>>> +{ 'struct': 'BootCertificate',
>>> + 'data': {'path': 'str'} }
>>
>> I'd call this BootCertificates (plural), because it can pull in any
>> number, not just than one.
>>
>>> +
>>> +##
>>> +# @DummyBootCertificates:
>>> +#
>>> +# Not used by QMP; hack to let us use BootCertificateList internally.
>>> +#
>>> +# Since: 10.2
>>> +##
>>> +{ 'struct': 'DummyBootCertificates',
>>> + 'data': {'unused-boot-certs': ['BootCertificate'] } }
>>> diff --git a/qapi/pragma.json b/qapi/pragma.json
>>> index 023a2ef7bc..66401837ad 100644
>>> --- a/qapi/pragma.json
>>> +++ b/qapi/pragma.json
>>> @@ -49,6 +49,7 @@
>>> 'DisplayProtocol',
>>> 'DriveBackupWrapper',
>>> 'DummyBlockCoreForceArrays',
>>> + 'DummyBootCertificates',
>>> 'DummyForceArrays',
>>> 'DummyVirtioForceArrays',
>>> 'HotKeyMod',
>>
>
> With regards,
> Daniel
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-22 23:48 ` Zhuoying Cai
@ 2025-09-29 18:29 ` Collin Walling
2025-10-08 17:49 ` Zhuoying Cai
0 siblings, 1 reply; 89+ messages in thread
From: Collin Walling @ 2025-09-29 18:29 UTC (permalink / raw)
To: Zhuoying Cai, Daniel P. Berrangé, Markus Armbruster
Cc: thuth, richard.henderson, david, jrossi, qemu-s390x, qemu-devel,
jjherne, pasic, borntraeger, farman, mjrosato, iii, eblake, alifm
On 9/22/25 19:48, Zhuoying Cai wrote:
> On 9/18/25 4:38 AM, Daniel P. Berrangé wrote:
[...]
>
> Thank you for the comments.
>
> Since Secure IPL on s390x is supported in QEMU, I would like to begin
> drafting the corresponding Libvirt interface and seek feedback before
> proceeding with the implementation.
>
> While Libvirt already provides a secure boot interface
> (https://libvirt.org/kbase/secureboot.html), it appears to be primarily
> intended for x86 systems, where secure boot is configured using the
> <firmware>, <loader>, and <nvram> tags.
>
> <os firmware='efi'>
> <firmware>
> <feature enabled='yes' name='enrolled-keys'/>
> <feature enabled='yes' name='secure-boot'/>
> </firmware>
> <loader secure='yes' type='pflash'>...</loader>
> <nvram template='...'>...</nvram>
> </os>
>
> For s390x, some of these existing tags may be reused, but additional
> elements will be needed.
>
> Below is my initial proposal for the secure boot interface in Libvirt:
>
> <!-- New s390-ccw-bios firmware value -->
> <os firmware='s390-ccw-bios'>
> <type arch='s390x' machine='s390-ccw-virtio-9.2'>hvm</type>
> <firmware>
> <!-- To enable secure boot -->
> <feature enabled='yes' name='secure-boot'/>
> </firmware>
> <!-- To provide boot certificates for secure boot -->
> <boot-certs path='/path/to/cert.pem' />
> <boot-certs path='/path/to/cert-dir' />
> <boot dev='hd'/>
> </os>
>
> I would be greatly appreciate any suggestions or feedback on this
> proposal, and I am open to refining the design to better align with
> existing Libvirt structures.
>
> Best regards,
> Joy
>
You should post an RFC to the libvirt list -- no code needed. I suggest
posting what you wrote above while also giving an example of the QEMU
commandline. Lastly, give a short background of what you've been
working on and provide a link to these patches for a more detail.
CC those who have been involved in review as well as Boris, please. Thanks!
[...]
--
Regards,
Collin
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-29 18:29 ` Collin Walling
@ 2025-10-08 17:49 ` Zhuoying Cai
0 siblings, 0 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-10-08 17:49 UTC (permalink / raw)
To: Collin Walling, Daniel P. Berrangé, Markus Armbruster
Cc: thuth, richard.henderson, david, jrossi, qemu-s390x, qemu-devel,
jjherne, pasic, borntraeger, farman, mjrosato, iii, eblake, alifm
On 9/29/25 2:29 PM, Collin Walling wrote:
> On 9/22/25 19:48, Zhuoying Cai wrote:
>> On 9/18/25 4:38 AM, Daniel P. Berrangé wrote:
>
> [...]
>
>>
>> Thank you for the comments.
>>
>> Since Secure IPL on s390x is supported in QEMU, I would like to begin
>> drafting the corresponding Libvirt interface and seek feedback before
>> proceeding with the implementation.
>>
>> While Libvirt already provides a secure boot interface
>> (https://libvirt.org/kbase/secureboot.html), it appears to be primarily
>> intended for x86 systems, where secure boot is configured using the
>> <firmware>, <loader>, and <nvram> tags.
>>
>> <os firmware='efi'>
>> <firmware>
>> <feature enabled='yes' name='enrolled-keys'/>
>> <feature enabled='yes' name='secure-boot'/>
>> </firmware>
>> <loader secure='yes' type='pflash'>...</loader>
>> <nvram template='...'>...</nvram>
>> </os>
>>
>> For s390x, some of these existing tags may be reused, but additional
>> elements will be needed.
>>
>> Below is my initial proposal for the secure boot interface in Libvirt:
>>
>> <!-- New s390-ccw-bios firmware value -->
>> <os firmware='s390-ccw-bios'>
>> <type arch='s390x' machine='s390-ccw-virtio-9.2'>hvm</type>
>> <firmware>
>> <!-- To enable secure boot -->
>> <feature enabled='yes' name='secure-boot'/>
>> </firmware>
>> <!-- To provide boot certificates for secure boot -->
>> <boot-certs path='/path/to/cert.pem' />
>> <boot-certs path='/path/to/cert-dir' />
>> <boot dev='hd'/>
>> </os>
>>
>> I would be greatly appreciate any suggestions or feedback on this
>> proposal, and I am open to refining the design to better align with
>> existing Libvirt structures.
>>
>> Best regards,
>> Joy
>>
>
> You should post an RFC to the libvirt list -- no code needed. I suggest
> posting what you wrote above while also giving an example of the QEMU
> commandline. Lastly, give a short background of what you've been
> working on and provide a link to these patches for a more detail.
>
> CC those who have been involved in review as well as Boris, please. Thanks!
>
Thank you for the suggestion!
I posted an RFC to the libvirt list
(https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/DWCOPLUGJKYZ6BOCX3JWU2FJGFLG7DUF/).
> [...]
>
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-17 23:21 ` [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
2025-09-18 6:56 ` Markus Armbruster
@ 2025-09-30 9:34 ` Thomas Huth
2025-09-30 9:37 ` Daniel P. Berrangé
1 sibling, 1 reply; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 9:34 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
> machine. This allows users to specify one or more certificate file paths
> or directories to be used during secure boot.
>
> Each entry is specified using the syntax:
> boot-certs.<index>.path=/path/to/cert.pem
>
> Multiple paths can be specify using array properties:
> boot-certs.0.path=/path/to/cert.pem,
> boot-certs.1.path=/path/to/cert-dir,
> boot-certs.2.path=/path/to/another-dir...
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/system/s390x/secure-ipl.rst | 21 +++++++++++++++++++++
> hw/s390x/s390-virtio-ccw.c | 30 ++++++++++++++++++++++++++++++
> include/hw/s390x/s390-virtio-ccw.h | 2 ++
> qapi/machine-s390x.json | 22 ++++++++++++++++++++++
> qapi/pragma.json | 1 +
> qemu-options.hx | 6 +++++-
> 6 files changed, 81 insertions(+), 1 deletion(-)
> create mode 100644 docs/system/s390x/secure-ipl.rst
>
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> new file mode 100644
> index 0000000000..92c1bb2153
> --- /dev/null
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -0,0 +1,21 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Secure IPL Command Line Options
> +===============================
> +
> +The s390-ccw-virtio machine type supports secure IPL. These parameters allow users
> +to provide certificates and enable secure IPL directly via the command line.
> +
> +Providing Certificates
> +----------------------
> +
> +The certificate store can be populated by supplying a list of X.509 certificate file
> +paths or directories containing certificate files on the command-line:
> +
> +Note: certificate files must have a .pem extension.
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio, \
> + boot-certs.0.path=/.../qemu/certs, \
> + boot-certs.1.path=/another/path/cert.pem ...
Using newlines/spaces between parameters does not work, so people cannot
copy-n-paste this example to the shell.
So I think you either have to merge it into one line, or use multiple
"-machine" statements, e.g.:
qemu-system-s390x -M s390-ccw-virtio \
-M boot-certs.0.path=/.../qemu/certs \
-M boot-certs.1.path=/another/path/cert.pem ...
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-30 9:34 ` Thomas Huth
@ 2025-09-30 9:37 ` Daniel P. Berrangé
2025-09-30 9:43 ` Thomas Huth
0 siblings, 1 reply; 89+ messages in thread
From: Daniel P. Berrangé @ 2025-09-30 9:37 UTC (permalink / raw)
To: Thomas Huth
Cc: Zhuoying Cai, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, armbru, alifm
On Tue, Sep 30, 2025 at 11:34:23AM +0200, Thomas Huth wrote:
> On 18/09/2025 01.21, Zhuoying Cai wrote:
> > Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
> > machine. This allows users to specify one or more certificate file paths
> > or directories to be used during secure boot.
> >
> > Each entry is specified using the syntax:
> > boot-certs.<index>.path=/path/to/cert.pem
> >
> > Multiple paths can be specify using array properties:
> > boot-certs.0.path=/path/to/cert.pem,
> > boot-certs.1.path=/path/to/cert-dir,
> > boot-certs.2.path=/path/to/another-dir...
> >
> > Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> > ---
> > docs/system/s390x/secure-ipl.rst | 21 +++++++++++++++++++++
> > hw/s390x/s390-virtio-ccw.c | 30 ++++++++++++++++++++++++++++++
> > include/hw/s390x/s390-virtio-ccw.h | 2 ++
> > qapi/machine-s390x.json | 22 ++++++++++++++++++++++
> > qapi/pragma.json | 1 +
> > qemu-options.hx | 6 +++++-
> > 6 files changed, 81 insertions(+), 1 deletion(-)
> > create mode 100644 docs/system/s390x/secure-ipl.rst
> >
> > diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> > new file mode 100644
> > index 0000000000..92c1bb2153
> > --- /dev/null
> > +++ b/docs/system/s390x/secure-ipl.rst
> > @@ -0,0 +1,21 @@
> > +.. SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +Secure IPL Command Line Options
> > +===============================
> > +
> > +The s390-ccw-virtio machine type supports secure IPL. These parameters allow users
> > +to provide certificates and enable secure IPL directly via the command line.
> > +
> > +Providing Certificates
> > +----------------------
> > +
> > +The certificate store can be populated by supplying a list of X.509 certificate file
> > +paths or directories containing certificate files on the command-line:
> > +
> > +Note: certificate files must have a .pem extension.
> > +
> > +.. code-block:: shell
> > +
> > + qemu-system-s390x -machine s390-ccw-virtio, \
> > + boot-certs.0.path=/.../qemu/certs, \
> > + boot-certs.1.path=/another/path/cert.pem ...
>
> Using newlines/spaces between parameters does not work, so people cannot
> copy-n-paste this example to the shell.
>
> So I think you either have to merge it into one line, or use multiple
> "-machine" statements, e.g.:
>
> qemu-system-s390x -M s390-ccw-virtio \
> -M boot-certs.0.path=/.../qemu/certs \
> -M boot-certs.1.path=/another/path/cert.pem ...
The inability to copy+paste is unfortunate, but IMHO the docs are better
in the way they are already expressed. Repeating the -M arg in this way
is not a natural way we'd expect people to configure QEMU, even if it
happens to work in the case of -M/-machine.
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] 89+ messages in thread
* Re: [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option
2025-09-30 9:37 ` Daniel P. Berrangé
@ 2025-09-30 9:43 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 9:43 UTC (permalink / raw)
To: Daniel P. Berrangé
Cc: Zhuoying Cai, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, armbru, alifm
On 30/09/2025 11.37, Daniel P. Berrangé wrote:
> On Tue, Sep 30, 2025 at 11:34:23AM +0200, Thomas Huth wrote:
>> On 18/09/2025 01.21, Zhuoying Cai wrote:
>>> Introduce a new `boot-certs` machine type option for the s390-ccw-virtio
>>> machine. This allows users to specify one or more certificate file paths
>>> or directories to be used during secure boot.
>>>
>>> Each entry is specified using the syntax:
>>> boot-certs.<index>.path=/path/to/cert.pem
>>>
>>> Multiple paths can be specify using array properties:
>>> boot-certs.0.path=/path/to/cert.pem,
>>> boot-certs.1.path=/path/to/cert-dir,
>>> boot-certs.2.path=/path/to/another-dir...
>>>
>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>>> ---
>>> docs/system/s390x/secure-ipl.rst | 21 +++++++++++++++++++++
>>> hw/s390x/s390-virtio-ccw.c | 30 ++++++++++++++++++++++++++++++
>>> include/hw/s390x/s390-virtio-ccw.h | 2 ++
>>> qapi/machine-s390x.json | 22 ++++++++++++++++++++++
>>> qapi/pragma.json | 1 +
>>> qemu-options.hx | 6 +++++-
>>> 6 files changed, 81 insertions(+), 1 deletion(-)
>>> create mode 100644 docs/system/s390x/secure-ipl.rst
>>>
>>> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
>>> new file mode 100644
>>> index 0000000000..92c1bb2153
>>> --- /dev/null
>>> +++ b/docs/system/s390x/secure-ipl.rst
>>> @@ -0,0 +1,21 @@
>>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>>> +
>>> +Secure IPL Command Line Options
>>> +===============================
>>> +
>>> +The s390-ccw-virtio machine type supports secure IPL. These parameters allow users
>>> +to provide certificates and enable secure IPL directly via the command line.
>>> +
>>> +Providing Certificates
>>> +----------------------
>>> +
>>> +The certificate store can be populated by supplying a list of X.509 certificate file
>>> +paths or directories containing certificate files on the command-line:
>>> +
>>> +Note: certificate files must have a .pem extension.
>>> +
>>> +.. code-block:: shell
>>> +
>>> + qemu-system-s390x -machine s390-ccw-virtio, \
>>> + boot-certs.0.path=/.../qemu/certs, \
>>> + boot-certs.1.path=/another/path/cert.pem ...
>>
>> Using newlines/spaces between parameters does not work, so people cannot
>> copy-n-paste this example to the shell.
>>
>> So I think you either have to merge it into one line, or use multiple
>> "-machine" statements, e.g.:
>>
>> qemu-system-s390x -M s390-ccw-virtio \
>> -M boot-certs.0.path=/.../qemu/certs \
>> -M boot-certs.1.path=/another/path/cert.pem ...
>
> The inability to copy+paste is unfortunate, but IMHO the docs are better
> in the way they are already expressed. Repeating the -M arg in this way
> is not a natural way we'd expect people to configure QEMU, even if it
> happens to work in the case of -M/-machine.
Then I'd vote to have it rather in one line instead.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
2025-09-17 23:21 ` [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-18 18:14 ` Farhan Ali
` (2 more replies)
2025-09-17 23:21 ` [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
` (26 subsequent siblings)
28 siblings, 3 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Always compile x509-utils.c and add a fallback when GNUTLS is
unavailable.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
crypto/meson.build | 5 +----
crypto/x509-utils.c | 16 ++++++++++++++++
2 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/crypto/meson.build b/crypto/meson.build
index 735635de1f..0614bfa914 100644
--- a/crypto/meson.build
+++ b/crypto/meson.build
@@ -22,12 +22,9 @@ crypto_ss.add(files(
'tlscredsx509.c',
'tlssession.c',
'rsakey.c',
+ 'x509-utils.c',
))
-if gnutls.found()
- crypto_ss.add(files('x509-utils.c'))
-endif
-
if nettle.found()
crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c'))
if hogweed.found()
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 39bb6d4d8c..6176a88653 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -11,6 +11,8 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "crypto/x509-utils.h"
+
+#ifdef CONFIG_GNUTLS
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include <gnutls/x509.h>
@@ -78,3 +80,17 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
gnutls_x509_crt_deinit(crt);
return ret;
}
+
+#else /* ! CONFIG_GNUTLS */
+
+int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
+ QCryptoHashAlgo hash,
+ uint8_t *result,
+ size_t *resultlen,
+ Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get fingerprint");
+ return -1;
+}
+
+#endif /* ! CONFIG_GNUTLS */
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback
2025-09-17 23:21 ` [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
@ 2025-09-18 18:14 ` Farhan Ali
2025-09-30 9:38 ` Thomas Huth
2025-10-02 13:23 ` Daniel P. Berrangé
2 siblings, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-18 18:14 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> Always compile x509-utils.c and add a fallback when GNUTLS is
> unavailable.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/meson.build | 5 +----
> crypto/x509-utils.c | 16 ++++++++++++++++
> 2 files changed, 17 insertions(+), 4 deletions(-)
>
> diff --git a/crypto/meson.build b/crypto/meson.build
> index 735635de1f..0614bfa914 100644
> --- a/crypto/meson.build
> +++ b/crypto/meson.build
> @@ -22,12 +22,9 @@ crypto_ss.add(files(
> 'tlscredsx509.c',
> 'tlssession.c',
> 'rsakey.c',
> + 'x509-utils.c',
> ))
>
> -if gnutls.found()
> - crypto_ss.add(files('x509-utils.c'))
> -endif
> -
> if nettle.found()
> crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c'))
> if hogweed.found()
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 39bb6d4d8c..6176a88653 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -11,6 +11,8 @@
> #include "qemu/osdep.h"
> #include "qapi/error.h"
> #include "crypto/x509-utils.h"
> +
> +#ifdef CONFIG_GNUTLS
> #include <gnutls/gnutls.h>
> #include <gnutls/crypto.h>
> #include <gnutls/x509.h>
> @@ -78,3 +80,17 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> gnutls_x509_crt_deinit(crt);
> return ret;
> }
> +
> +#else /* ! CONFIG_GNUTLS */
> +
> +int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> + QCryptoHashAlgo hash,
> + uint8_t *result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get fingerprint");
> + return -1;
> +}
> +
> +#endif /* ! CONFIG_GNUTLS */
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback
2025-09-17 23:21 ` [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
2025-09-18 18:14 ` Farhan Ali
@ 2025-09-30 9:38 ` Thomas Huth
2025-10-02 13:23 ` Daniel P. Berrangé
2 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 9:38 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Always compile x509-utils.c and add a fallback when GNUTLS is
> unavailable.
Maybe add some rationale to the description, like "we are going to need
these functions in the s390x code even if GNUTLS is not available" or so?
Thomas
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/meson.build | 5 +----
> crypto/x509-utils.c | 16 ++++++++++++++++
> 2 files changed, 17 insertions(+), 4 deletions(-)
>
> diff --git a/crypto/meson.build b/crypto/meson.build
> index 735635de1f..0614bfa914 100644
> --- a/crypto/meson.build
> +++ b/crypto/meson.build
> @@ -22,12 +22,9 @@ crypto_ss.add(files(
> 'tlscredsx509.c',
> 'tlssession.c',
> 'rsakey.c',
> + 'x509-utils.c',
> ))
>
> -if gnutls.found()
> - crypto_ss.add(files('x509-utils.c'))
> -endif
> -
> if nettle.found()
> crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c'))
> if hogweed.found()
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 39bb6d4d8c..6176a88653 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -11,6 +11,8 @@
> #include "qemu/osdep.h"
> #include "qapi/error.h"
> #include "crypto/x509-utils.h"
> +
> +#ifdef CONFIG_GNUTLS
> #include <gnutls/gnutls.h>
> #include <gnutls/crypto.h>
> #include <gnutls/x509.h>
> @@ -78,3 +80,17 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> gnutls_x509_crt_deinit(crt);
> return ret;
> }
> +
> +#else /* ! CONFIG_GNUTLS */
> +
> +int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> + QCryptoHashAlgo hash,
> + uint8_t *result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to get fingerprint");
> + return -1;
> +}
> +
> +#endif /* ! CONFIG_GNUTLS */
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback
2025-09-17 23:21 ` [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
2025-09-18 18:14 ` Farhan Ali
2025-09-30 9:38 ` Thomas Huth
@ 2025-10-02 13:23 ` Daniel P. Berrangé
2 siblings, 0 replies; 89+ messages in thread
From: Daniel P. Berrangé @ 2025-10-02 13:23 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, jrossi, qemu-s390x, qemu-devel,
walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On Wed, Sep 17, 2025 at 07:21:04PM -0400, Zhuoying Cai wrote:
> Always compile x509-utils.c and add a fallback when GNUTLS is
> unavailable.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/meson.build | 5 +----
> crypto/x509-utils.c | 16 ++++++++++++++++
> 2 files changed, 17 insertions(+), 4 deletions(-)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
2025-09-17 23:21 ` [PATCH v6 01/28] Add boot-certs to s390-ccw-virtio machine type option Zhuoying Cai
2025-09-17 23:21 ` [PATCH v6 02/28] crypto/x509-utils: Refactor with GNUTLS fallback Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-18 18:24 ` Farhan Ali
` (2 more replies)
2025-09-17 23:21 ` [PATCH v6 04/28] hw/s390x/ipl: Create " Zhuoying Cai
` (25 subsequent siblings)
28 siblings, 3 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Introduce new helper functions for x509 certificate, which will be used
by the certificate store:
qcrypto_x509_convert_cert_der() - converts a certificate from PEM to DER format
These functions provide support for certificate format conversion.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
crypto/x509-utils.c | 50 +++++++++++++++++++++++++++++++++++++
include/crypto/x509-utils.h | 20 +++++++++++++++
2 files changed, 70 insertions(+)
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 6176a88653..5d43b0ec96 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -81,6 +81,47 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
return ret;
}
+int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
+ uint8_t **result, size_t *resultlen,
+ Error **errp)
+{
+ int ret = -1;
+ int rc;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+ gnutls_datum_t datum_der = {.data = NULL, .size = 0};
+
+ rc = gnutls_x509_crt_init(&crt);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &datum_der);
+ if (rc != 0) {
+ error_setg(errp, "Failed to convert certificate to DER format: %s",
+ gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ *result = g_new0(uint8_t, datum_der.size);
+ *resultlen = datum_der.size;
+ memcpy(*result, datum_der.data, datum_der.size);
+
+ ret = 0;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ gnutls_free(datum_der.data);
+ return ret;
+}
+
#else /* ! CONFIG_GNUTLS */
int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
@@ -93,4 +134,13 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
return -1;
}
+int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
+ uint8_t **result,
+ size_t *resultlen,
+ Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to export X.509 certificate");
+ return -1;
+}
+
#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 1e99661a71..4239e3e55a 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -19,4 +19,24 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
size_t *resultlen,
Error **errp);
+/**
+ * qcrypto_x509_convert_cert_der
+ * @cert: pointer to the raw certificate data in PEM format
+ * @size: size of the certificate
+ * @result: output location for the allocated buffer for the certificate in DER format
+ (the function allocates memory which must be freed by the caller)
+ * @resultlen: pointer to the size of the buffer
+ (will be updated with the actual size of the DER-encoded certificate)
+ * @errp: error pointer
+ *
+ * Convert the given @cert from PEM to DER format.
+ *
+ * Returns: 0 on success,
+ * -1 on error.
+ */
+int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
+ uint8_t **result,
+ size_t *resultlen,
+ Error **errp);
+
#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store
2025-09-17 23:21 ` [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
@ 2025-09-18 18:24 ` Farhan Ali
2025-09-30 9:43 ` Thomas Huth
2025-10-02 13:24 ` Daniel P. Berrangé
2 siblings, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-18 18:24 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> Introduce new helper functions for x509 certificate, which will be used
> by the certificate store:
>
> qcrypto_x509_convert_cert_der() - converts a certificate from PEM to DER format
>
> These functions provide support for certificate format conversion.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/x509-utils.c | 50 +++++++++++++++++++++++++++++++++++++
> include/crypto/x509-utils.h | 20 +++++++++++++++
> 2 files changed, 70 insertions(+)
>
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 6176a88653..5d43b0ec96 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -81,6 +81,47 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> return ret;
> }
>
> +int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
> + uint8_t **result, size_t *resultlen,
> + Error **errp)
> +{
> + int ret = -1;
> + int rc;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> + gnutls_datum_t datum_der = {.data = NULL, .size = 0};
> +
> + rc = gnutls_x509_crt_init(&crt);
> + if (rc < 0) {
> + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> + return ret;
> + }
> +
> + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
> + if (rc != 0) {
> + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + rc = gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &datum_der);
> + if (rc != 0) {
> + error_setg(errp, "Failed to convert certificate to DER format: %s",
> + gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + *result = g_new0(uint8_t, datum_der.size);
> + *resultlen = datum_der.size;
> + memcpy(*result, datum_der.data, datum_der.size);
> +
> + ret = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + gnutls_free(datum_der.data);
> + return ret;
> +}
> +
> #else /* ! CONFIG_GNUTLS */
>
> int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> @@ -93,4 +134,13 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> return -1;
> }
>
> +int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
> + uint8_t **result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to export X.509 certificate");
> + return -1;
> +}
> +
> #endif /* ! CONFIG_GNUTLS */
> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index 1e99661a71..4239e3e55a 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -19,4 +19,24 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> size_t *resultlen,
> Error **errp);
>
> +/**
> + * qcrypto_x509_convert_cert_der
> + * @cert: pointer to the raw certificate data in PEM format
> + * @size: size of the certificate
> + * @result: output location for the allocated buffer for the certificate in DER format
> + (the function allocates memory which must be freed by the caller)
> + * @resultlen: pointer to the size of the buffer
> + (will be updated with the actual size of the DER-encoded certificate)
> + * @errp: error pointer
> + *
> + * Convert the given @cert from PEM to DER format.
> + *
> + * Returns: 0 on success,
> + * -1 on error.
> + */
> +int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
> + uint8_t **result,
> + size_t *resultlen,
> + Error **errp);
> +
> #endif
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store
2025-09-17 23:21 ` [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
2025-09-18 18:24 ` Farhan Ali
@ 2025-09-30 9:43 ` Thomas Huth
2025-10-02 13:24 ` Daniel P. Berrangé
2 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 9:43 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Introduce new helper functions for x509 certificate, which will be used
> by the certificate store:
>
> qcrypto_x509_convert_cert_der() - converts a certificate from PEM to DER format
>
> These functions provide support for certificate format conversion.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/x509-utils.c | 50 +++++++++++++++++++++++++++++++++++++
> include/crypto/x509-utils.h | 20 +++++++++++++++
> 2 files changed, 70 insertions(+)
>
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 6176a88653..5d43b0ec96 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -81,6 +81,47 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> return ret;
> }
>
> +int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
> + uint8_t **result, size_t *resultlen,
> + Error **errp)
> +{
> + int ret = -1;
> + int rc;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> + gnutls_datum_t datum_der = {.data = NULL, .size = 0};
> +
> + rc = gnutls_x509_crt_init(&crt);
> + if (rc < 0) {
> + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> + return ret;
> + }
> +
> + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
> + if (rc != 0) {
> + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + rc = gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &datum_der);
> + if (rc != 0) {
> + error_setg(errp, "Failed to convert certificate to DER format: %s",
> + gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + *result = g_new0(uint8_t, datum_der.size);
> + *resultlen = datum_der.size;
> + memcpy(*result, datum_der.data, datum_der.size);
> +
> + ret = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + gnutls_free(datum_der.data);
> + return ret;
> +}
> +
> #else /* ! CONFIG_GNUTLS */
>
> int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> @@ -93,4 +134,13 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> return -1;
> }
>
> +int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
> + uint8_t **result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + error_setg(errp, "GNUTLS is required to export X.509 certificate");
> + return -1;
> +}
> +
> #endif /* ! CONFIG_GNUTLS */
> diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
> index 1e99661a71..4239e3e55a 100644
> --- a/include/crypto/x509-utils.h
> +++ b/include/crypto/x509-utils.h
> @@ -19,4 +19,24 @@ int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
> size_t *resultlen,
> Error **errp);
>
> +/**
> + * qcrypto_x509_convert_cert_der
> + * @cert: pointer to the raw certificate data in PEM format
> + * @size: size of the certificate
> + * @result: output location for the allocated buffer for the certificate in DER format
> + (the function allocates memory which must be freed by the caller)
Please try to fit the comments into the 80 columns boundary.
Thanks,
Thomas
> + * @resultlen: pointer to the size of the buffer
> + (will be updated with the actual size of the DER-encoded certificate)
> + * @errp: error pointer
> + *
> + * Convert the given @cert from PEM to DER format.
> + *
> + * Returns: 0 on success,
> + * -1 on error.
> + */
> +int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
> + uint8_t **result,
> + size_t *resultlen,
> + Error **errp);
> +
> #endif
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store
2025-09-17 23:21 ` [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
2025-09-18 18:24 ` Farhan Ali
2025-09-30 9:43 ` Thomas Huth
@ 2025-10-02 13:24 ` Daniel P. Berrangé
2 siblings, 0 replies; 89+ messages in thread
From: Daniel P. Berrangé @ 2025-10-02 13:24 UTC (permalink / raw)
To: Zhuoying Cai
Cc: thuth, richard.henderson, david, jrossi, qemu-s390x, qemu-devel,
walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On Wed, Sep 17, 2025 at 07:21:05PM -0400, Zhuoying Cai wrote:
> Introduce new helper functions for x509 certificate, which will be used
> by the certificate store:
>
> qcrypto_x509_convert_cert_der() - converts a certificate from PEM to DER format
>
> These functions provide support for certificate format conversion.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/x509-utils.c | 50 +++++++++++++++++++++++++++++++++++++
> include/crypto/x509-utils.h | 20 +++++++++++++++
> 2 files changed, 70 insertions(+)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 04/28] hw/s390x/ipl: Create certificate store
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (2 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 03/28] crypto/x509-utils: Add helper functions for certificate store Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-18 19:46 ` Farhan Ali
2025-09-30 10:26 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store Facility Zhuoying Cai
` (24 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Create a certificate store for boot certificates used for secure IPL.
Load certificates from the `boot-certs` parameter of s390-ccw-virtio
machine type option into the cert store.
Currently, only X.509 certificates in PEM format are supported, as the
QEMU command line accepts certificates in PEM format only.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 15 +++
hw/s390x/cert-store.c | 213 ++++++++++++++++++++++++++++++++
hw/s390x/cert-store.h | 39 ++++++
hw/s390x/ipl.c | 19 +++
hw/s390x/ipl.h | 3 +
hw/s390x/meson.build | 1 +
include/hw/s390x/ipl/qipl.h | 2 +
7 files changed, 292 insertions(+)
create mode 100644 docs/specs/s390x-secure-ipl.rst
create mode 100644 hw/s390x/cert-store.c
create mode 100644 hw/s390x/cert-store.h
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
new file mode 100644
index 0000000000..9b1de5c604
--- /dev/null
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -0,0 +1,15 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+s390 Certificate Store and Functions
+====================================
+
+s390 Certificate Store
+----------------------
+
+A certificate store is implemented for s390-ccw guests to retain within
+memory all certificates provided by the user via the command-line, which
+are expected to be stored somewhere on the host's file system. The store
+will keep track of the number of certificates, their respective size,
+and a summation of the sizes.
+
+Note: A maximum of 64 certificates are allowed to be stored in the certificate store.
diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
new file mode 100644
index 0000000000..318acfb1f6
--- /dev/null
+++ b/hw/s390x/cert-store.c
@@ -0,0 +1,213 @@
+/*
+ * S390 certificate store implementation
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "cert-store.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "hw/s390x/ebcdic.h"
+#include "hw/s390x/s390-virtio-ccw.h"
+#include "qemu/cutils.h"
+#include "crypto/x509-utils.h"
+#include "qapi/qapi-types-machine-s390x.h"
+
+static BootCertificateList *s390_get_boot_certs(void)
+{
+ return S390_CCW_MACHINE(qdev_get_machine())->boot_certs;
+}
+
+static size_t cert2buf(char *path, char **cert_buf)
+{
+ size_t size;
+
+ if (!g_file_get_contents(path, cert_buf, &size, NULL)) {
+ return 0;
+ }
+
+ return size;
+}
+
+static S390IPLCertificate *init_cert_x509(size_t size, uint8_t *raw, Error **errp)
+{
+ S390IPLCertificate *cert = NULL;
+ g_autofree uint8_t *cert_der = NULL;
+ size_t der_len = size;
+ int rc;
+
+ rc = qcrypto_x509_convert_cert_der(raw, size, &cert_der, &der_len, errp);
+ if (rc != 0) {
+ return NULL;
+ }
+
+ cert = g_new0(S390IPLCertificate, 1);
+ cert->size = size;
+ cert->der_size = der_len;
+ /* store raw pointer - ownership transfers to cert */
+ cert->raw = raw;
+
+ return cert;
+}
+
+static S390IPLCertificate *init_cert(char *path)
+{
+ char *buf;
+ size_t size;
+ char vc_name[VC_NAME_LEN_BYTES];
+ g_autofree gchar *filename = NULL;
+ S390IPLCertificate *cert = NULL;
+ Error *local_err = NULL;
+
+ filename = g_path_get_basename(path);
+
+ size = cert2buf(path, &buf);
+ if (size == 0) {
+ error_report("Failed to load certificate: %s", path);
+ return NULL;
+ }
+
+ cert = init_cert_x509(size, (uint8_t *)buf, &local_err);
+ if (cert == NULL) {
+ error_reportf_err(local_err, "Failed to initialize certificate: %s: ", path);
+ g_free(buf);
+ return NULL;
+ }
+
+ /*
+ * Left justified certificate name with padding on the right with blanks.
+ * Convert certificate name to EBCDIC.
+ */
+ strpadcpy(vc_name, VC_NAME_LEN_BYTES, filename, ' ');
+ ebcdic_put(cert->vc_name, vc_name, VC_NAME_LEN_BYTES);
+
+ return cert;
+}
+
+static void update_cert_store(S390IPLCertificateStore *cert_store,
+ S390IPLCertificate *cert)
+{
+ size_t data_buf_size;
+ size_t keyid_buf_size;
+ size_t hash_buf_size;
+ size_t cert_buf_size;
+
+ /* length field is word aligned for later DIAG use */
+ keyid_buf_size = ROUND_UP(CERT_KEY_ID_LEN, 4);
+ hash_buf_size = ROUND_UP(CERT_HASH_LEN, 4);
+ cert_buf_size = ROUND_UP(cert->der_size, 4);
+ data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
+
+ if (cert_store->max_cert_size < data_buf_size) {
+ cert_store->max_cert_size = data_buf_size;
+ }
+
+ cert_store->certs[cert_store->count] = *cert;
+ cert_store->total_bytes += data_buf_size;
+ cert_store->count++;
+}
+
+static GPtrArray *get_cert_paths(void)
+{
+ BootCertificateList *path_list = NULL;
+ BootCertificateList *list = NULL;
+ gchar *cert_path;
+ GDir *dir = NULL;
+ const gchar *filename;
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_full(0, g_free);
+
+ path_list = s390_get_boot_certs();
+ if (path_list == NULL) {
+ return g_steal_pointer(&cert_path_builder);
+ }
+
+ for (list = path_list; list; list = list->next) {
+ cert_path = list->value->path;
+
+ if (g_strcmp0(cert_path, "") == 0) {
+ error_report("Empty path in certificate path list is not allowed");
+ goto fail;
+ }
+
+ struct stat st;
+ if (stat(cert_path, &st) != 0) {
+ error_report("Failed to stat path '%s': %s", cert_path, g_strerror(errno));
+ goto fail;
+ }
+
+ if (S_ISREG(st.st_mode)) {
+ if (!g_str_has_suffix(cert_path, ".pem")) {
+ error_report("Certificate file '%s' must have a .pem extension",
+ cert_path);
+ goto fail;
+ }
+
+ g_ptr_array_add(cert_path_builder, g_strdup(cert_path));
+ } else if (S_ISDIR(st.st_mode)) {
+ dir = g_dir_open(cert_path, 0, &err);
+ if (dir == NULL) {
+ error_report("Failed to open directory '%s': %s",
+ cert_path, err->message);
+ goto fail;
+ }
+
+ while ((filename = g_dir_read_name(dir))) {
+ if (g_str_has_suffix(filename, ".pem")) {
+ g_ptr_array_add(cert_path_builder,
+ g_build_filename(cert_path, filename, NULL));
+ }
+ }
+
+ g_dir_close(dir);
+ } else {
+ error_report("Path '%s' is neither a file nor a directory", cert_path);
+ goto fail;
+ }
+ }
+
+ qapi_free_BootCertificateList(path_list);
+ return g_steal_pointer(&cert_path_builder);
+
+fail:
+ qapi_free_BootCertificateList(path_list);
+ exit(1);
+}
+
+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;
+ }
+
+ if (cert_path_builder->len > MAX_CERTIFICATES - 1) {
+ error_report("Cert store exceeds maximum of %d certificates", MAX_CERTIFICATES);
+ g_ptr_array_free(cert_path_builder, TRUE);
+ exit(1);
+ }
+
+ cert_store->max_cert_size = 0;
+ cert_store->total_bytes = 0;
+
+ for (int i = 0; i < cert_path_builder->len; i++) {
+ S390IPLCertificate *cert = init_cert((char *) cert_path_builder->pdata[i]);
+ if (!cert) {
+ g_ptr_array_free(cert_path_builder, TRUE);
+ exit(1);
+ }
+
+ update_cert_store(cert_store, cert);
+ }
+
+ g_ptr_array_free(cert_path_builder, TRUE);
+}
diff --git a/hw/s390x/cert-store.h b/hw/s390x/cert-store.h
new file mode 100644
index 0000000000..3f76a00277
--- /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
+
+#define CERT_KEY_ID_LEN QCRYPTO_HASH_DIGEST_LEN_SHA256
+#define CERT_HASH_LEN QCRYPTO_HASH_DIGEST_LEN_SHA256
+
+struct S390IPLCertificate {
+ uint8_t vc_name[VC_NAME_LEN_BYTES];
+ size_t size;
+ size_t der_size;
+ uint8_t *raw;
+};
+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..917166ba31 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,20 @@ 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_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;
@@ -717,6 +732,10 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
if (!ipl->kernel || ipl->iplb_valid) {
cpu->env.psw.addr = ipl->bios_start_addr;
+ /* initialize cert store if it's empty */
+ if (!s390_has_certificate()) {
+ s390_ipl_create_cert_store(&ipl->cert_store);
+ }
if (!ipl->iplb_valid) {
ipl->iplb_valid = s390_init_all_iplbs(ipl);
} else {
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index 8f83c7da29..bee72dfbb3 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -13,6 +13,7 @@
#ifndef HW_S390_IPL_H
#define HW_S390_IPL_H
+#include "cert-store.h"
#include "cpu.h"
#include "exec/target_page.h"
#include "system/address-spaces.h"
@@ -35,6 +36,7 @@ int s390_ipl_pv_unpack(struct S390PVResponse *pv_resp);
void s390_ipl_prepare_cpu(S390CPU *cpu);
IplParameterBlock *s390_ipl_get_iplb(void);
IplParameterBlock *s390_ipl_get_iplb_pv(void);
+S390IPLCertificateStore *s390_ipl_get_certificate_store(void);
enum s390_reset {
/* default is a reset not triggered by a CPU e.g. issued by QMP */
@@ -64,6 +66,7 @@ struct S390IPLState {
IplParameterBlock iplb;
IplParameterBlock iplb_pv;
QemuIplParameters qipl;
+ S390IPLCertificateStore cert_store;
uint64_t start_addr;
uint64_t compat_start_addr;
uint64_t bios_start_addr;
diff --git a/hw/s390x/meson.build b/hw/s390x/meson.build
index 8866012ddc..80d3d4a74d 100644
--- a/hw/s390x/meson.build
+++ b/hw/s390x/meson.build
@@ -17,6 +17,7 @@ s390x_ss.add(files(
'sclpcpu.c',
'sclpquiesce.c',
'tod.c',
+ 'cert-store.c',
))
s390x_ss.add(when: 'CONFIG_KVM', if_true: files(
'tod-kvm.c',
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 6824391111..e505f44020 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -20,6 +20,8 @@
#define LOADPARM_LEN 8
#define NO_LOADPARM "\0\0\0\0\0\0\0\0"
+#define MAX_CERTIFICATES 64
+
/*
* The QEMU IPL Parameters will be stored at absolute address
* 204 (0xcc) which means it is 32-bit word aligned but not
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 04/28] hw/s390x/ipl: Create certificate store
2025-09-17 23:21 ` [PATCH v6 04/28] hw/s390x/ipl: Create " Zhuoying Cai
@ 2025-09-18 19:46 ` Farhan Ali
2025-09-30 10:26 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-18 19:46 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
<..snip..>
> diff --git a/hw/s390x/cert-store.h b/hw/s390x/cert-store.h
> new file mode 100644
> index 0000000000..3f76a00277
> --- /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
> +
> +#define CERT_KEY_ID_LEN QCRYPTO_HASH_DIGEST_LEN_SHA256
> +#define CERT_HASH_LEN QCRYPTO_HASH_DIGEST_LEN_SHA256
> +
> +struct S390IPLCertificate {
> + uint8_t vc_name[VC_NAME_LEN_BYTES];
> + size_t size;
> + size_t der_size;
> + uint8_t *raw;
> +};
> +typedef struct S390IPLCertificate S390IPLCertificate;
> +
> +struct S390IPLCertificateStore {
> + uint16_t count;
> + size_t max_cert_size;
> + size_t total_bytes;
> + S390IPLCertificate certs[MAX_CERTIFICATES];
> +} QEMU_PACKED;
I think the guidance is to avoid using QEMU_PACKED (see Thomas's reply
to me
https://lore.kernel.org/qemu-devel/ff5a3203-6afb-4202-b59a-1021f0d949db@redhat.com/).
Also this is an internal QEMU structure and not an architecture specific
one, so this shouldn't be packed, no?
The rest of the patch LGTM.
<...snip...>
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 04/28] hw/s390x/ipl: Create certificate store
2025-09-17 23:21 ` [PATCH v6 04/28] hw/s390x/ipl: Create " Zhuoying Cai
2025-09-18 19:46 ` Farhan Ali
@ 2025-09-30 10:26 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 10:26 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Create a certificate store for boot certificates used for secure IPL.
>
> Load certificates from the `boot-certs` parameter of s390-ccw-virtio
> machine type option into the cert store.
>
> Currently, only X.509 certificates in PEM format are supported, as the
> QEMU command line accepts certificates in PEM format only.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 15 +++
> hw/s390x/cert-store.c | 213 ++++++++++++++++++++++++++++++++
> hw/s390x/cert-store.h | 39 ++++++
> hw/s390x/ipl.c | 19 +++
> hw/s390x/ipl.h | 3 +
> hw/s390x/meson.build | 1 +
> include/hw/s390x/ipl/qipl.h | 2 +
> 7 files changed, 292 insertions(+)
> create mode 100644 docs/specs/s390x-secure-ipl.rst
> create mode 100644 hw/s390x/cert-store.c
> create mode 100644 hw/s390x/cert-store.h
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> new file mode 100644
> index 0000000000..9b1de5c604
> --- /dev/null
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -0,0 +1,15 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +s390 Certificate Store and Functions
> +====================================
> +
> +s390 Certificate Store
> +----------------------
> +
> +A certificate store is implemented for s390-ccw guests to retain within
> +memory all certificates provided by the user via the command-line, which
> +are expected to be stored somewhere on the host's file system. The store
> +will keep track of the number of certificates, their respective size,
> +and a summation of the sizes.
> +
> +Note: A maximum of 64 certificates are allowed to be stored in the certificate store.
fit into 80 columns, please.
> diff --git a/hw/s390x/cert-store.c b/hw/s390x/cert-store.c
> new file mode 100644
> index 0000000000..318acfb1f6
> --- /dev/null
> +++ b/hw/s390x/cert-store.c
> @@ -0,0 +1,213 @@
> +/*
> + * S390 certificate store implementation
> + *
> + * Copyright 2025 IBM Corp.
> + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "cert-store.h"
> +#include "qapi/error.h"
> +#include "qemu/error-report.h"
> +#include "qemu/option.h"
> +#include "qemu/config-file.h"
> +#include "hw/s390x/ebcdic.h"
> +#include "hw/s390x/s390-virtio-ccw.h"
> +#include "qemu/cutils.h"
> +#include "crypto/x509-utils.h"
> +#include "qapi/qapi-types-machine-s390x.h"
> +
> +static BootCertificateList *s390_get_boot_certs(void)
> +{
> + return S390_CCW_MACHINE(qdev_get_machine())->boot_certs;
> +}
> +
> +static size_t cert2buf(char *path, char **cert_buf)
> +{
> + size_t size;
> +
> + if (!g_file_get_contents(path, cert_buf, &size, NULL)) {
> + return 0;
> + }
> +
> + return size;
> +}
What's the advantage of these wrapper functions? You seem to only use them
in one spot, so I'd rather inline the code where it's used ==> less lines of
code in total, and easier to read.
> +static void update_cert_store(S390IPLCertificateStore *cert_store,
> + S390IPLCertificate *cert)
> +{
> + size_t data_buf_size;
> + size_t keyid_buf_size;
> + size_t hash_buf_size;
> + size_t cert_buf_size;
> +
> + /* length field is word aligned for later DIAG use */
> + keyid_buf_size = ROUND_UP(CERT_KEY_ID_LEN, 4);
> + hash_buf_size = ROUND_UP(CERT_HASH_LEN, 4);
> + cert_buf_size = ROUND_UP(cert->der_size, 4);
> + data_buf_size = keyid_buf_size + hash_buf_size + cert_buf_size;
> +
> + if (cert_store->max_cert_size < data_buf_size) {
> + cert_store->max_cert_size = data_buf_size;
> + }
> +
> + cert_store->certs[cert_store->count] = *cert;
> + cert_store->total_bytes += data_buf_size;
> + cert_store->count++;
> +}
> +
> +static GPtrArray *get_cert_paths(void)
> +{
> + BootCertificateList *path_list = NULL;
> + BootCertificateList *list = NULL;
> + gchar *cert_path;
> + GDir *dir = NULL;
> + const gchar *filename;
> + g_autoptr(GError) err = NULL;
> + g_autoptr(GPtrArray) cert_path_builder = g_ptr_array_new_full(0, g_free);
> +
> + path_list = s390_get_boot_certs();
> + if (path_list == NULL) {
> + return g_steal_pointer(&cert_path_builder);
> + }
> +
> + for (list = path_list; list; list = list->next) {
> + cert_path = list->value->path;
> +
> + if (g_strcmp0(cert_path, "") == 0) {
> + error_report("Empty path in certificate path list is not allowed");
> + goto fail;
> + }
> +
> + struct stat st;
QEMU coding style (see docs/devel/style.rst):
"Mixed declarations (interleaving statements and declarations within
blocks) are generally not allowed; declarations should be at the beginning
of blocks."
> + if (stat(cert_path, &st) != 0) {
> + error_report("Failed to stat path '%s': %s", cert_path, g_strerror(errno));
> + goto fail;
> + }
> +
> + if (S_ISREG(st.st_mode)) {
> + if (!g_str_has_suffix(cert_path, ".pem")) {
> + error_report("Certificate file '%s' must have a .pem extension",
> + cert_path);
> + goto fail;
> + }
> +
> + g_ptr_array_add(cert_path_builder, g_strdup(cert_path));
> + } else if (S_ISDIR(st.st_mode)) {
> + dir = g_dir_open(cert_path, 0, &err);
> + if (dir == NULL) {
> + error_report("Failed to open directory '%s': %s",
> + cert_path, err->message);
> + goto fail;
> + }
> +
> + while ((filename = g_dir_read_name(dir))) {
> + if (g_str_has_suffix(filename, ".pem")) {
> + g_ptr_array_add(cert_path_builder,
> + g_build_filename(cert_path, filename, NULL));
> + }
> + }
> +
> + g_dir_close(dir);
> + } else {
> + error_report("Path '%s' is neither a file nor a directory", cert_path);
> + goto fail;
> + }
> + }
> +
> + qapi_free_BootCertificateList(path_list);
> + return g_steal_pointer(&cert_path_builder);
> +
> +fail:
> + qapi_free_BootCertificateList(path_list);
> + exit(1);
Doing "goto fail"s and then a clean-up and exit(1) at the end of the
function looks weird. I think I'd rather either use "exit(1)" instead of
"goto fail" thorough the function, or use a return NULL here and do the
exit(1) in the caller instead (and maybe pass an "Error **errp" to the
function here, replace all error_report with error_setg and do the
error_report in the caller instead). Otherwise this looks just unbalanced.
> +}
> +
> +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;
> + }
> +
> + if (cert_path_builder->len > MAX_CERTIFICATES - 1) {
> + error_report("Cert store exceeds maximum of %d certificates", MAX_CERTIFICATES);
> + g_ptr_array_free(cert_path_builder, TRUE);
> + exit(1);
So there's a exit(1) here already ... that's another indication that it
would be nicer to have the exit(1) from get_cert_paths() in this function
here, too.
> + }
> +
> + cert_store->max_cert_size = 0;
> + cert_store->total_bytes = 0;
> +
> + for (int i = 0; i < cert_path_builder->len; i++) {
> + S390IPLCertificate *cert = init_cert((char *) cert_path_builder->pdata[i]);
I'd suggest to do the same with init_cert(), i.e. add an "Error **errp" to
it's parameter list and do the error_report() here.
> + if (!cert) {
> + g_ptr_array_free(cert_path_builder, TRUE);
> + exit(1);
> + }
> +
> + update_cert_store(cert_store, cert);
> + }
> +
> + g_ptr_array_free(cert_path_builder, TRUE);
> +}
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store Facility
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (3 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 04/28] hw/s390x/ipl: Create " Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-18 20:07 ` Farhan Ali
2025-09-30 13:08 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
` (23 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
DIAGNOSE 320 is introduced to support Certificate Store (CS)
Facility, which includes operations such as query certificate
storage information and provide certificates in the certificate
store.
Currently, only subcode 0 is supported with this patch, which is
used to query the Installed Subcodes Mask (ISM).
This subcode is only supported when the CS facility is enabled.
Availability of CS facility is determined by byte 134 bit 5 of the
SCLP Read Info block. Byte 134's facilities cannot be represented
without the availability of the extended-length-SCCB, so add it as
a check for consistency.
Note: secure IPL is not available for Secure Execution (SE) guests,
as their images are already integrity protected, and an additional
protection of the kernel by secure IPL is not necessary.
This feature is available starting with the gen16 CPU model.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 12 ++++++++
include/hw/s390x/ipl/diag320.h | 20 +++++++++++++
target/s390x/cpu_features.c | 1 +
target/s390x/cpu_features_def.h.inc | 1 +
target/s390x/cpu_models.c | 2 ++
target/s390x/diag.c | 44 +++++++++++++++++++++++++++++
target/s390x/gen-features.c | 3 ++
target/s390x/kvm/kvm.c | 16 +++++++++++
target/s390x/s390x-internal.h | 2 ++
target/s390x/tcg/misc_helper.c | 7 +++++
10 files changed, 108 insertions(+)
create mode 100644 include/hw/s390x/ipl/diag320.h
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 9b1de5c604..30ddc81c2b 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -13,3 +13,15 @@ will keep track of the number of certificates, their respective size,
and a summation of the sizes.
Note: A maximum of 64 certificates are allowed to be stored in the certificate store.
+
+DIAGNOSE function code 'X'320' - Certificate Store Facility
+-----------------------------------------------------------
+
+DIAGNOSE 'X'320' is used to provide support for userspace to directly
+query the s390 certificate store. Userspace may be the s390-ccw BIOS or
+the guest kernel.
+
+Subcode 0 - query installed subcodes
+ Returns a 256-bit installed subcodes mask (ISM) stored in the installed
+ subcodes block (ISB). This mask indicates which sucodes are currently
+ installed and available for use.
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
new file mode 100644
index 0000000000..aa04b699c6
--- /dev/null
+++ b/include/hw/s390x/ipl/diag320.h
@@ -0,0 +1,20 @@
+/*
+ * S/390 DIAGNOSE 320 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG320_H
+#define S390X_DIAG320_H
+
+#define DIAG_320_SUBC_QUERY_ISM 0
+
+#define DIAG_320_RC_OK 0x0001
+#define DIAG_320_RC_NOT_SUPPORTED 0x0102
+
+#define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
+
+#endif
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 4b5be6798e..436471f4b4 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -147,6 +147,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
break;
case S390_FEAT_TYPE_SCLP_FAC134:
clear_be_bit(s390_feat_def(S390_FEAT_DIAG_318)->bit, data);
+ clear_be_bit(s390_feat_def(S390_FEAT_CERT_STORE)->bit, data);
break;
default:
return;
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index c017bffcdc..941a69e013 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -138,6 +138,7 @@ DEF_FEAT(SIE_IBS, "ibs", SCLP_CONF_CHAR_EXT, 10, "SIE: Interlock-and-broadcast-s
/* Features exposed via SCLP SCCB Facilities byte 134 (bit numbers relative to byte-134) */
DEF_FEAT(DIAG_318, "diag318", SCLP_FAC134, 0, "Control program name and version codes")
+DEF_FEAT(CERT_STORE, "cstore", SCLP_FAC134, 5, "Provide Certificate Store functions")
/* Features exposed via SCLP CPU info. */
DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index 954a7a99a9..6b8471700e 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -248,6 +248,7 @@ bool s390_has_feat(S390Feat feat)
if (s390_is_pv()) {
switch (feat) {
case S390_FEAT_DIAG_318:
+ case S390_FEAT_CERT_STORE:
case S390_FEAT_HPMA2:
case S390_FEAT_SIE_F2:
case S390_FEAT_SIE_SKEY:
@@ -505,6 +506,7 @@ static void check_consistency(const S390CPUModel *model)
{ S390_FEAT_PTFF_STOUE, S390_FEAT_MULTIPLE_EPOCH },
{ S390_FEAT_AP_QUEUE_INTERRUPT_CONTROL, S390_FEAT_AP },
{ S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_CERT_STORE, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_NNPA, S390_FEAT_VECTOR },
{ S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
{ S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index cff9fbc4b0..a35d808fd7 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -18,6 +18,7 @@
#include "hw/watchdog/wdt_diag288.h"
#include "system/cpus.h"
#include "hw/s390x/ipl.h"
+#include "hw/s390x/ipl/diag320.h"
#include "hw/s390x/s390-virtio-ccw.h"
#include "system/kvm.h"
#include "kvm/kvm_s390x.h"
@@ -191,3 +192,46 @@ out:
break;
}
}
+
+void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
+{
+ S390CPU *cpu = env_archcpu(env);
+ uint64_t subcode = env->regs[r3];
+ uint64_t addr = env->regs[r1];
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ s390_program_interrupt(env, PGM_PRIVILEGED, ra);
+ return;
+ }
+
+ if (!s390_has_feat(S390_FEAT_CERT_STORE)) {
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+
+ if ((subcode & ~0x000ffULL) || (r1 & 1)) {
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+
+ switch (subcode) {
+ case DIAG_320_SUBC_QUERY_ISM:
+ /*
+ * The Installed Subcode Block (ISB) can be up 8 words in size,
+ * but the current set of subcodes can fit within a single word
+ * for now.
+ */
+ uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES);
+
+ if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return;
+ }
+
+ env->regs[r1 + 1] = DIAG_320_RC_OK;
+ break;
+ default:
+ env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
+ break;
+ }
+}
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index 8218e6470e..6c20c3a862 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -720,6 +720,7 @@ static uint16_t full_GEN16_GA1[] = {
S390_FEAT_PAIE,
S390_FEAT_UV_FEAT_AP,
S390_FEAT_UV_FEAT_AP_INTR,
+ S390_FEAT_CERT_STORE,
};
static uint16_t full_GEN17_GA1[] = {
@@ -919,6 +920,8 @@ static uint16_t qemu_MAX[] = {
S390_FEAT_KIMD_SHA_512,
S390_FEAT_KLMD_SHA_512,
S390_FEAT_PRNO_TRNG,
+ S390_FEAT_EXTENDED_LENGTH_SCCB,
+ S390_FEAT_CERT_STORE,
};
/****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 8ee33924df..5510fc2fc5 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -98,6 +98,7 @@
#define DIAG_TIMEREVENT 0x288
#define DIAG_IPL 0x308
#define DIAG_SET_CONTROL_PROGRAM_CODES 0x318
+#define DIAG_CERT_STORE 0x320
#define DIAG_KVM_HYPERCALL 0x500
#define DIAG_KVM_BREAKPOINT 0x501
@@ -1560,6 +1561,16 @@ static void handle_diag_318(S390CPU *cpu, struct kvm_run *run)
}
}
+static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
+{
+ uint64_t r1, r3;
+
+ r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
+ r3 = run->s390_sieic.ipa & 0x000f;
+
+ handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
+}
+
#define DIAG_KVM_CODE_MASK 0x000000000000ffff
static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
@@ -1590,6 +1601,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
case DIAG_KVM_BREAKPOINT:
r = handle_sw_breakpoint(cpu, run);
break;
+ case DIAG_CERT_STORE:
+ kvm_handle_diag_320(cpu, run);
+ break;
default:
trace_kvm_insn_diag(func_code);
kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
@@ -2490,6 +2504,8 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
set_bit(S390_FEAT_DIAG_318, model->features);
}
+ set_bit(S390_FEAT_CERT_STORE, model->features);
+
/* Test for Ultravisor features that influence secure guest behavior */
query_uv_feat_guest(model->features);
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index 56cce2e7f5..ecff2d07a1 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -391,6 +391,8 @@ int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw,
int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3);
void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
uintptr_t ra);
+void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
+ uintptr_t ra);
/* translate.c */
diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
index f7101be574..412c34ed93 100644
--- a/target/s390x/tcg/misc_helper.c
+++ b/target/s390x/tcg/misc_helper.c
@@ -142,6 +142,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
/* time bomb (watchdog) */
r = handle_diag_288(env, r1, r3);
break;
+ case 0x320:
+ /* cert store */
+ bql_lock();
+ handle_diag_320(env, r1, r3, GETPC());
+ bql_unlock();
+ r = 0;
+ break;
default:
r = -1;
break;
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store Facility
2025-09-17 23:21 ` [PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store Facility Zhuoying Cai
@ 2025-09-18 20:07 ` Farhan Ali
2025-09-30 13:08 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-18 20:07 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> DIAGNOSE 320 is introduced to support Certificate Store (CS)
> Facility, which includes operations such as query certificate
> storage information and provide certificates in the certificate
> store.
>
> Currently, only subcode 0 is supported with this patch, which is
> used to query the Installed Subcodes Mask (ISM).
>
> This subcode is only supported when the CS facility is enabled.
>
> Availability of CS facility is determined by byte 134 bit 5 of the
> SCLP Read Info block. Byte 134's facilities cannot be represented
> without the availability of the extended-length-SCCB, so add it as
> a check for consistency.
>
> Note: secure IPL is not available for Secure Execution (SE) guests,
> as their images are already integrity protected, and an additional
> protection of the kernel by secure IPL is not necessary.
>
> This feature is available starting with the gen16 CPU model.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> Reviewed-by: Collin Walling <walling@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 12 ++++++++
> include/hw/s390x/ipl/diag320.h | 20 +++++++++++++
> target/s390x/cpu_features.c | 1 +
> target/s390x/cpu_features_def.h.inc | 1 +
> target/s390x/cpu_models.c | 2 ++
> target/s390x/diag.c | 44 +++++++++++++++++++++++++++++
> target/s390x/gen-features.c | 3 ++
> target/s390x/kvm/kvm.c | 16 +++++++++++
> target/s390x/s390x-internal.h | 2 ++
> target/s390x/tcg/misc_helper.c | 7 +++++
> 10 files changed, 108 insertions(+)
> create mode 100644 include/hw/s390x/ipl/diag320.h
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 9b1de5c604..30ddc81c2b 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -13,3 +13,15 @@ will keep track of the number of certificates, their respective size,
> and a summation of the sizes.
>
> Note: A maximum of 64 certificates are allowed to be stored in the certificate store.
> +
> +DIAGNOSE function code 'X'320' - Certificate Store Facility
> +-----------------------------------------------------------
> +
> +DIAGNOSE 'X'320' is used to provide support for userspace to directly
> +query the s390 certificate store. Userspace may be the s390-ccw BIOS or
> +the guest kernel.
> +
> +Subcode 0 - query installed subcodes
> + Returns a 256-bit installed subcodes mask (ISM) stored in the installed
> + subcodes block (ISB). This mask indicates which sucodes are currently
> + installed and available for use.
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> new file mode 100644
> index 0000000000..aa04b699c6
> --- /dev/null
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -0,0 +1,20 @@
> +/*
> + * S/390 DIAGNOSE 320 definitions and structures
> + *
> + * Copyright 2025 IBM Corp.
> + * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef S390X_DIAG320_H
> +#define S390X_DIAG320_H
> +
> +#define DIAG_320_SUBC_QUERY_ISM 0
> +
> +#define DIAG_320_RC_OK 0x0001
> +#define DIAG_320_RC_NOT_SUPPORTED 0x0102
> +
> +#define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
> +
> +#endif
> diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
> index 4b5be6798e..436471f4b4 100644
> --- a/target/s390x/cpu_features.c
> +++ b/target/s390x/cpu_features.c
> @@ -147,6 +147,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
> break;
> case S390_FEAT_TYPE_SCLP_FAC134:
> clear_be_bit(s390_feat_def(S390_FEAT_DIAG_318)->bit, data);
> + clear_be_bit(s390_feat_def(S390_FEAT_CERT_STORE)->bit, data);
> break;
> default:
> return;
> diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
> index c017bffcdc..941a69e013 100644
> --- a/target/s390x/cpu_features_def.h.inc
> +++ b/target/s390x/cpu_features_def.h.inc
> @@ -138,6 +138,7 @@ DEF_FEAT(SIE_IBS, "ibs", SCLP_CONF_CHAR_EXT, 10, "SIE: Interlock-and-broadcast-s
>
> /* Features exposed via SCLP SCCB Facilities byte 134 (bit numbers relative to byte-134) */
> DEF_FEAT(DIAG_318, "diag318", SCLP_FAC134, 0, "Control program name and version codes")
> +DEF_FEAT(CERT_STORE, "cstore", SCLP_FAC134, 5, "Provide Certificate Store functions")
>
> /* Features exposed via SCLP CPU info. */
> DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
> diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
> index 954a7a99a9..6b8471700e 100644
> --- a/target/s390x/cpu_models.c
> +++ b/target/s390x/cpu_models.c
> @@ -248,6 +248,7 @@ bool s390_has_feat(S390Feat feat)
> if (s390_is_pv()) {
> switch (feat) {
> case S390_FEAT_DIAG_318:
> + case S390_FEAT_CERT_STORE:
> case S390_FEAT_HPMA2:
> case S390_FEAT_SIE_F2:
> case S390_FEAT_SIE_SKEY:
> @@ -505,6 +506,7 @@ static void check_consistency(const S390CPUModel *model)
> { S390_FEAT_PTFF_STOUE, S390_FEAT_MULTIPLE_EPOCH },
> { S390_FEAT_AP_QUEUE_INTERRUPT_CONTROL, S390_FEAT_AP },
> { S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
> + { S390_FEAT_CERT_STORE, S390_FEAT_EXTENDED_LENGTH_SCCB },
> { S390_FEAT_NNPA, S390_FEAT_VECTOR },
> { S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
> { S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index cff9fbc4b0..a35d808fd7 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -18,6 +18,7 @@
> #include "hw/watchdog/wdt_diag288.h"
> #include "system/cpus.h"
> #include "hw/s390x/ipl.h"
> +#include "hw/s390x/ipl/diag320.h"
> #include "hw/s390x/s390-virtio-ccw.h"
> #include "system/kvm.h"
> #include "kvm/kvm_s390x.h"
> @@ -191,3 +192,46 @@ out:
> break;
> }
> }
> +
> +void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> +{
> + S390CPU *cpu = env_archcpu(env);
> + uint64_t subcode = env->regs[r3];
> + uint64_t addr = env->regs[r1];
> +
> + if (env->psw.mask & PSW_MASK_PSTATE) {
> + s390_program_interrupt(env, PGM_PRIVILEGED, ra);
> + return;
> + }
> +
> + if (!s390_has_feat(S390_FEAT_CERT_STORE)) {
> + s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> + return;
> + }
> +
> + if ((subcode & ~0x000ffULL) || (r1 & 1)) {
> + s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> + return;
> + }
> +
> + switch (subcode) {
> + case DIAG_320_SUBC_QUERY_ISM:
> + /*
> + * The Installed Subcode Block (ISB) can be up 8 words in size,
> + * but the current set of subcodes can fit within a single word
> + * for now.
> + */
> + uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES);
> +
> + if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
> + return;
> + }
> +
> + env->regs[r1 + 1] = DIAG_320_RC_OK;
> + break;
> + default:
> + env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
> + break;
> + }
> +}
> diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
> index 8218e6470e..6c20c3a862 100644
> --- a/target/s390x/gen-features.c
> +++ b/target/s390x/gen-features.c
> @@ -720,6 +720,7 @@ static uint16_t full_GEN16_GA1[] = {
> S390_FEAT_PAIE,
> S390_FEAT_UV_FEAT_AP,
> S390_FEAT_UV_FEAT_AP_INTR,
> + S390_FEAT_CERT_STORE,
> };
>
> static uint16_t full_GEN17_GA1[] = {
> @@ -919,6 +920,8 @@ static uint16_t qemu_MAX[] = {
> S390_FEAT_KIMD_SHA_512,
> S390_FEAT_KLMD_SHA_512,
> S390_FEAT_PRNO_TRNG,
> + S390_FEAT_EXTENDED_LENGTH_SCCB,
> + S390_FEAT_CERT_STORE,
> };
>
> /****** END FEATURE DEFS ******/
> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> index 8ee33924df..5510fc2fc5 100644
> --- a/target/s390x/kvm/kvm.c
> +++ b/target/s390x/kvm/kvm.c
> @@ -98,6 +98,7 @@
> #define DIAG_TIMEREVENT 0x288
> #define DIAG_IPL 0x308
> #define DIAG_SET_CONTROL_PROGRAM_CODES 0x318
> +#define DIAG_CERT_STORE 0x320
> #define DIAG_KVM_HYPERCALL 0x500
> #define DIAG_KVM_BREAKPOINT 0x501
>
> @@ -1560,6 +1561,16 @@ static void handle_diag_318(S390CPU *cpu, struct kvm_run *run)
> }
> }
>
> +static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
> +{
> + uint64_t r1, r3;
> +
> + r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
> + r3 = run->s390_sieic.ipa & 0x000f;
> +
> + handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
> +}
> +
> #define DIAG_KVM_CODE_MASK 0x000000000000ffff
>
> static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
> @@ -1590,6 +1601,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
> case DIAG_KVM_BREAKPOINT:
> r = handle_sw_breakpoint(cpu, run);
> break;
> + case DIAG_CERT_STORE:
> + kvm_handle_diag_320(cpu, run);
> + break;
> default:
> trace_kvm_insn_diag(func_code);
> kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
> @@ -2490,6 +2504,8 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
> set_bit(S390_FEAT_DIAG_318, model->features);
> }
>
> + set_bit(S390_FEAT_CERT_STORE, model->features);
> +
> /* Test for Ultravisor features that influence secure guest behavior */
> query_uv_feat_guest(model->features);
>
> diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
> index 56cce2e7f5..ecff2d07a1 100644
> --- a/target/s390x/s390x-internal.h
> +++ b/target/s390x/s390x-internal.h
> @@ -391,6 +391,8 @@ int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw,
> int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3);
> void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
> uintptr_t ra);
> +void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
> + uintptr_t ra);
>
>
> /* translate.c */
> diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
> index f7101be574..412c34ed93 100644
> --- a/target/s390x/tcg/misc_helper.c
> +++ b/target/s390x/tcg/misc_helper.c
> @@ -142,6 +142,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
> /* time bomb (watchdog) */
> r = handle_diag_288(env, r1, r3);
> break;
> + case 0x320:
> + /* cert store */
> + bql_lock();
> + handle_diag_320(env, r1, r3, GETPC());
> + bql_unlock();
> + r = 0;
> + break;
> default:
> r = -1;
> break;
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store Facility
2025-09-17 23:21 ` [PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store Facility Zhuoying Cai
2025-09-18 20:07 ` Farhan Ali
@ 2025-09-30 13:08 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 13:08 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> DIAGNOSE 320 is introduced to support Certificate Store (CS)
> Facility, which includes operations such as query certificate
> storage information and provide certificates in the certificate
> store.
>
> Currently, only subcode 0 is supported with this patch, which is
> used to query the Installed Subcodes Mask (ISM).
>
> This subcode is only supported when the CS facility is enabled.
>
> Availability of CS facility is determined by byte 134 bit 5 of the
> SCLP Read Info block. Byte 134's facilities cannot be represented
> without the availability of the extended-length-SCCB, so add it as
> a check for consistency.
>
> Note: secure IPL is not available for Secure Execution (SE) guests,
> as their images are already integrity protected, and an additional
> protection of the kernel by secure IPL is not necessary.
>
> This feature is available starting with the gen16 CPU model.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> Reviewed-by: Collin Walling <walling@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 12 ++++++++
> include/hw/s390x/ipl/diag320.h | 20 +++++++++++++
> target/s390x/cpu_features.c | 1 +
> target/s390x/cpu_features_def.h.inc | 1 +
> target/s390x/cpu_models.c | 2 ++
> target/s390x/diag.c | 44 +++++++++++++++++++++++++++++
> target/s390x/gen-features.c | 3 ++
> target/s390x/kvm/kvm.c | 16 +++++++++++
> target/s390x/s390x-internal.h | 2 ++
> target/s390x/tcg/misc_helper.c | 7 +++++
> 10 files changed, 108 insertions(+)
> create mode 100644 include/hw/s390x/ipl/diag320.h
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 9b1de5c604..30ddc81c2b 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -13,3 +13,15 @@ will keep track of the number of certificates, their respective size,
> and a summation of the sizes.
>
> Note: A maximum of 64 certificates are allowed to be stored in the certificate store.
> +
> +DIAGNOSE function code 'X'320' - Certificate Store Facility
> +-----------------------------------------------------------
> +
> +DIAGNOSE 'X'320' is used to provide support for userspace to directly
> +query the s390 certificate store. Userspace may be the s390-ccw BIOS or
> +the guest kernel.
> +
> +Subcode 0 - query installed subcodes
> + Returns a 256-bit installed subcodes mask (ISM) stored in the installed
> + subcodes block (ISB). This mask indicates which sucodes are currently
s/sucodes/subcodes/
Apart from that, the patch looks fine to me.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 06/28] s390x/diag: Refactor address validation check from diag308_parm_check
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (4 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 05/28] s390x/diag: Introduce DIAG 320 for Certificate Store Facility Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-18 20:38 ` Farhan Ali
2025-09-30 13:13 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
` (22 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Create a function to validate the address parameter of DIAGNOSE.
Refactor the function for reuse in the next patch, which allows address
validation in read or write operation of DIAGNOSE.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/ipl.h | 6 ++++++
target/s390x/diag.c | 4 +---
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index bee72dfbb3..e26fc1cd6a 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -118,6 +118,12 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
#define S390_IPLB_MIN_FCP_LEN 384
#define S390_IPLB_MIN_QEMU_SCSI_LEN 200
+static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
+{
+ return address_space_access_valid(&address_space_memory, addr,
+ size, write, MEMTXATTRS_UNSPECIFIED);
+}
+
static inline bool iplb_valid_len(IplParameterBlock *iplb)
{
return be32_to_cpu(iplb->len) <= sizeof(IplParameterBlock);
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index a35d808fd7..e67ee57f01 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -65,9 +65,7 @@ static int diag308_parm_check(CPUS390XState *env, uint64_t r1, uint64_t addr,
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
return -1;
}
- if (!address_space_access_valid(&address_space_memory, addr,
- sizeof(IplParameterBlock), write,
- MEMTXATTRS_UNSPECIFIED)) {
+ if (!diag_parm_addr_valid(addr, sizeof(IplParameterBlock), write)) {
s390_program_interrupt(env, PGM_ADDRESSING, ra);
return -1;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 06/28] s390x/diag: Refactor address validation check from diag308_parm_check
2025-09-17 23:21 ` [PATCH v6 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
@ 2025-09-18 20:38 ` Farhan Ali
2025-09-30 13:13 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-18 20:38 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> Create a function to validate the address parameter of DIAGNOSE.
>
> Refactor the function for reuse in the next patch, which allows address
> validation in read or write operation of DIAGNOSE.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> hw/s390x/ipl.h | 6 ++++++
> target/s390x/diag.c | 4 +---
> 2 files changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
> index bee72dfbb3..e26fc1cd6a 100644
> --- a/hw/s390x/ipl.h
> +++ b/hw/s390x/ipl.h
> @@ -118,6 +118,12 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
> #define S390_IPLB_MIN_FCP_LEN 384
> #define S390_IPLB_MIN_QEMU_SCSI_LEN 200
>
> +static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
> +{
> + return address_space_access_valid(&address_space_memory, addr,
> + size, write, MEMTXATTRS_UNSPECIFIED);
> +}
> +
> static inline bool iplb_valid_len(IplParameterBlock *iplb)
> {
> return be32_to_cpu(iplb->len) <= sizeof(IplParameterBlock);
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index a35d808fd7..e67ee57f01 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -65,9 +65,7 @@ static int diag308_parm_check(CPUS390XState *env, uint64_t r1, uint64_t addr,
> s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> return -1;
> }
> - if (!address_space_access_valid(&address_space_memory, addr,
> - sizeof(IplParameterBlock), write,
> - MEMTXATTRS_UNSPECIFIED)) {
> + if (!diag_parm_addr_valid(addr, sizeof(IplParameterBlock), write)) {
> s390_program_interrupt(env, PGM_ADDRESSING, ra);
> return -1;
> }
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 06/28] s390x/diag: Refactor address validation check from diag308_parm_check
2025-09-17 23:21 ` [PATCH v6 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
2025-09-18 20:38 ` Farhan Ali
@ 2025-09-30 13:13 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 13:13 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Create a function to validate the address parameter of DIAGNOSE.
>
> Refactor the function for reuse in the next patch, which allows address
> validation in read or write operation of DIAGNOSE.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> hw/s390x/ipl.h | 6 ++++++
> target/s390x/diag.c | 4 +---
> 2 files changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
> index bee72dfbb3..e26fc1cd6a 100644
> --- a/hw/s390x/ipl.h
> +++ b/hw/s390x/ipl.h
> @@ -118,6 +118,12 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
> #define S390_IPLB_MIN_FCP_LEN 384
> #define S390_IPLB_MIN_QEMU_SCSI_LEN 200
>
> +static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
> +{
> + return address_space_access_valid(&address_space_memory, addr,
> + size, write, MEMTXATTRS_UNSPECIFIED);
> +}
The function is only used in diag.c ... could you please move it to that file?
Thanks,
Thomas
> 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 a35d808fd7..e67ee57f01 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -65,9 +65,7 @@ static int diag308_parm_check(CPUS390XState *env, uint64_t r1, uint64_t addr,
> s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> return -1;
> }
> - if (!address_space_access_valid(&address_space_memory, addr,
> - sizeof(IplParameterBlock), write,
> - MEMTXATTRS_UNSPECIFIED)) {
> + if (!diag_parm_addr_valid(addr, sizeof(IplParameterBlock), write)) {
> s390_program_interrupt(env, PGM_ADDRESSING, ra);
> return -1;
> }
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (5 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 06/28] s390x/diag: Refactor address validation check from diag308_parm_check Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-19 17:20 ` Farhan Ali
2025-09-30 13:30 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
` (21 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
DIAG 320 subcode 1 provides information needed to determine
the amount of storage to store one or more certificates from the
certificate store.
Upon successful completion, this subcode returns information of the current
cert store, such as the number of certificates stored and allowed in the cert
store, amount of space may need to be allocate to store a certificate,
etc for verification-certificate blocks (VCBs).
The subcode value is denoted by setting the left-most bit
of an 8-byte field.
The verification-certificate-storage-size block (VCSSB) contains
the output data when the operation completes successfully. A VCSSB
length of 4 indicates that no certificate are available in the cert
store.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 10 ++++++
include/hw/s390x/ipl/diag320.h | 22 ++++++++++++
target/s390x/diag.c | 59 ++++++++++++++++++++++++++++++++-
3 files changed, 90 insertions(+), 1 deletion(-)
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 30ddc81c2b..4217f19c84 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -25,3 +25,13 @@ 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).
+ A VCSSB length of 4 indicates that no certificates are available in the CS.
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index aa04b699c6..6e4779c699 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -11,10 +11,32 @@
#define S390X_DIAG320_H
#define DIAG_320_SUBC_QUERY_ISM 0
+#define DIAG_320_SUBC_QUERY_VCSI 1
#define DIAG_320_RC_OK 0x0001
#define DIAG_320_RC_NOT_SUPPORTED 0x0102
+#define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
#define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
+#define DIAG_320_ISM_QUERY_VCSI 0x40000000
+
+#define VCSSB_NO_VC 4
+#define VCSSB_MIN_LEN 128
+#define VCE_HEADER_LEN 128
+#define VCB_HEADER_LEN 64
+
+struct VCStorageSizeBlock {
+ uint32_t length;
+ uint8_t reserved0[3];
+ uint8_t version;
+ uint32_t reserved1[6];
+ uint16_t total_vc_ct;
+ uint16_t max_vc_ct;
+ uint32_t reserved3[11];
+ uint32_t max_single_vcb_len;
+ uint32_t total_vcb_len;
+ uint32_t reserved4[10];
+};
+typedef struct VCStorageSizeBlock VCStorageSizeBlock;
#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index e67ee57f01..4e6de483b8 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -191,11 +191,50 @@ out:
}
}
+static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
+ uintptr_t ra, S390IPLCertificateStore *qcs)
+{
+ g_autofree VCStorageSizeBlock *vcssb = NULL;
+
+ vcssb = g_new0(VCStorageSizeBlock, 1);
+ if (s390_cpu_virt_mem_read(cpu, addr, r1, vcssb, sizeof(*vcssb))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return -1;
+ }
+
+ if (be32_to_cpu(vcssb->length) < VCSSB_MIN_LEN) {
+ return DIAG_320_RC_INVAL_VCSSB_LEN;
+ }
+
+ if (!qcs->count) {
+ vcssb->length = cpu_to_be32(VCSSB_NO_VC);
+ } else {
+ vcssb->version = 0;
+ vcssb->total_vc_ct = cpu_to_be16(qcs->count);
+ vcssb->max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
+ vcssb->max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
+ qcs->max_cert_size);
+ vcssb->total_vcb_len = cpu_to_be32(VCB_HEADER_LEN + qcs->count * VCE_HEADER_LEN +
+ qcs->total_bytes);
+ }
+
+ if (s390_cpu_virt_mem_write(cpu, addr, r1, vcssb, be32_to_cpu(vcssb->length))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return -1;
+ }
+ return DIAG_320_RC_OK;
+}
+
+QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
+ "size of VCStorageSizeBlock is wrong");
+
void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
{
S390CPU *cpu = env_archcpu(env);
+ S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
uint64_t subcode = env->regs[r3];
uint64_t addr = env->regs[r1];
+ int rc;
if (env->psw.mask & PSW_MASK_PSTATE) {
s390_program_interrupt(env, PGM_PRIVILEGED, ra);
@@ -219,7 +258,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
* but the current set of subcodes can fit within a single word
* for now.
*/
- uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES);
+ uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
+ DIAG_320_ISM_QUERY_VCSI);
if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
s390_cpu_virt_mem_handle_exc(cpu, ra);
@@ -228,6 +268,23 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
env->regs[r1 + 1] = DIAG_320_RC_OK;
break;
+ case DIAG_320_SUBC_QUERY_VCSI:
+ if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock), true)) {
+ s390_program_interrupt(env, PGM_ADDRESSING, ra);
+ return;
+ }
+
+ if (addr & 0x7) {
+ s390_program_interrupt(env, PGM_ADDRESSING, ra);
+ return;
+ }
+
+ rc = handle_diag320_query_vcsi(cpu, addr, r1, ra, qcs);
+ if (rc == -1) {
+ return;
+ }
+ env->regs[r1 + 1] = rc;
+ break;
default:
env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
break;
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1
2025-09-17 23:21 ` [PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2025-09-19 17:20 ` Farhan Ali
2025-09-30 13:30 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-19 17:20 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> DIAG 320 subcode 1 provides information needed to determine
> the amount of storage to store one or more certificates from the
> certificate store.
>
> Upon successful completion, this subcode returns information of the current
> cert store, such as the number of certificates stored and allowed in the cert
> store, amount of space may need to be allocate to store a certificate,
> etc for verification-certificate blocks (VCBs).
>
> The subcode value is denoted by setting the left-most bit
> of an 8-byte field.
>
> The verification-certificate-storage-size block (VCSSB) contains
> the output data when the operation completes successfully. A VCSSB
> length of 4 indicates that no certificate are available in the cert
> store.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 10 ++++++
> include/hw/s390x/ipl/diag320.h | 22 ++++++++++++
> target/s390x/diag.c | 59 ++++++++++++++++++++++++++++++++-
> 3 files changed, 90 insertions(+), 1 deletion(-)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 30ddc81c2b..4217f19c84 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -25,3 +25,13 @@ 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).
> + A VCSSB length of 4 indicates that no certificates are available in the CS.
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index aa04b699c6..6e4779c699 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -11,10 +11,32 @@
> #define S390X_DIAG320_H
>
> #define DIAG_320_SUBC_QUERY_ISM 0
> +#define DIAG_320_SUBC_QUERY_VCSI 1
>
> #define DIAG_320_RC_OK 0x0001
> #define DIAG_320_RC_NOT_SUPPORTED 0x0102
> +#define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
>
> #define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
> +#define DIAG_320_ISM_QUERY_VCSI 0x40000000
> +
> +#define VCSSB_NO_VC 4
> +#define VCSSB_MIN_LEN 128
> +#define VCE_HEADER_LEN 128
> +#define VCB_HEADER_LEN 64
> +
> +struct VCStorageSizeBlock {
> + uint32_t length;
> + uint8_t reserved0[3];
> + uint8_t version;
> + uint32_t reserved1[6];
> + uint16_t total_vc_ct;
> + uint16_t max_vc_ct;
> + uint32_t reserved3[11];
> + uint32_t max_single_vcb_len;
> + uint32_t total_vcb_len;
> + uint32_t reserved4[10];
> +};
> +typedef struct VCStorageSizeBlock VCStorageSizeBlock;
>
> #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index e67ee57f01..4e6de483b8 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -191,11 +191,50 @@ out:
> }
> }
>
> +static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
> + uintptr_t ra, S390IPLCertificateStore *qcs)
> +{
> + g_autofree VCStorageSizeBlock *vcssb = NULL;
> +
> + vcssb = g_new0(VCStorageSizeBlock, 1);
> + if (s390_cpu_virt_mem_read(cpu, addr, r1, vcssb, sizeof(*vcssb))) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
> + return -1;
> + }
> +
> + if (be32_to_cpu(vcssb->length) < VCSSB_MIN_LEN) {
> + return DIAG_320_RC_INVAL_VCSSB_LEN;
> + }
> +
> + if (!qcs->count) {
> + vcssb->length = cpu_to_be32(VCSSB_NO_VC);
> + } else {
> + vcssb->version = 0;
> + vcssb->total_vc_ct = cpu_to_be16(qcs->count);
> + vcssb->max_vc_ct = cpu_to_be16(MAX_CERTIFICATES);
> + vcssb->max_single_vcb_len = cpu_to_be32(VCB_HEADER_LEN + VCE_HEADER_LEN +
> + qcs->max_cert_size);
> + vcssb->total_vcb_len = cpu_to_be32(VCB_HEADER_LEN + qcs->count * VCE_HEADER_LEN +
> + qcs->total_bytes);
> + }
> +
> + if (s390_cpu_virt_mem_write(cpu, addr, r1, vcssb, be32_to_cpu(vcssb->length))) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
> + return -1;
> + }
> + return DIAG_320_RC_OK;
> +}
> +
> +QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
> + "size of VCStorageSizeBlock is wrong");
> +
> void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> {
> S390CPU *cpu = env_archcpu(env);
> + S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
> uint64_t subcode = env->regs[r3];
> uint64_t addr = env->regs[r1];
> + int rc;
>
> if (env->psw.mask & PSW_MASK_PSTATE) {
> s390_program_interrupt(env, PGM_PRIVILEGED, ra);
> @@ -219,7 +258,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> * but the current set of subcodes can fit within a single word
> * for now.
> */
> - uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES);
> + uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
> + DIAG_320_ISM_QUERY_VCSI);
>
> if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
> s390_cpu_virt_mem_handle_exc(cpu, ra);
> @@ -228,6 +268,23 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>
> env->regs[r1 + 1] = DIAG_320_RC_OK;
> break;
> + case DIAG_320_SUBC_QUERY_VCSI:
> + if (!diag_parm_addr_valid(addr, sizeof(VCStorageSizeBlock), true)) {
> + s390_program_interrupt(env, PGM_ADDRESSING, ra);
> + return;
> + }
> +
> + if (addr & 0x7) {
> + s390_program_interrupt(env, PGM_ADDRESSING, ra);
> + return;
> + }
> +
> + rc = handle_diag320_query_vcsi(cpu, addr, r1, ra, qcs);
> + if (rc == -1) {
> + return;
> + }
> + env->regs[r1 + 1] = rc;
> + break;
> default:
> env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
> break;
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1
2025-09-17 23:21 ` [PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
2025-09-19 17:20 ` Farhan Ali
@ 2025-09-30 13:30 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 13:30 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> DIAG 320 subcode 1 provides information needed to determine
> the amount of storage to store one or more certificates from the
> certificate store.
>
> Upon successful completion, this subcode returns information of the current
> cert store, such as the number of certificates stored and allowed in the cert
> store, amount of space may need to be allocate to store a certificate,
> etc for verification-certificate blocks (VCBs).
>
> The subcode value is denoted by setting the left-most bit
> of an 8-byte field.
>
> The verification-certificate-storage-size block (VCSSB) contains
> the output data when the operation completes successfully. A VCSSB
> length of 4 indicates that no certificate are available in the cert
> store.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 10 ++++++
> include/hw/s390x/ipl/diag320.h | 22 ++++++++++++
> target/s390x/diag.c | 59 ++++++++++++++++++++++++++++++++-
> 3 files changed, 90 insertions(+), 1 deletion(-)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 30ddc81c2b..4217f19c84 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -25,3 +25,13 @@ 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).
> + A VCSSB length of 4 indicates that no certificates are available in the CS.
limit to 80 columns, please.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (6 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 07/28] s390x/diag: Implement DIAG 320 subcode 1 Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-19 18:02 ` Farhan Ali
2025-10-07 9:34 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 09/28] s390x/diag: Implement " Zhuoying Cai
` (20 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Introduce new helper functions to extract certificate metadata needed for
DIAG 320 subcode 2:
qcrypto_x509_check_cert_times() - validates the certificate's validity period against the current time
qcrypto_x509_get_pk_algorithm() - returns the public key algorithm used in the certificate
qcrypto_x509_get_cert_key_id() - extracts the key ID from the certificate
qcrypto_x509_is_ecc_curve_p521() - determines the ECC public key algorithm uses P-521 curve
These functions provide support for metadata extraction and validity checking
for X.509 certificates.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
crypto/x509-utils.c | 248 ++++++++++++++++++++++++++++++++++++
include/crypto/x509-utils.h | 72 +++++++++++
2 files changed, 320 insertions(+)
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 5d43b0ec96..763eccb190 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -27,6 +27,25 @@ static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
[QCRYPTO_HASH_ALGO_RIPEMD160] = GNUTLS_DIG_RMD160,
};
+static const int gnutls_to_qcrypto_pk_alg_map[] = {
+ [GNUTLS_PK_RSA] = QCRYPTO_PK_ALGO_RSA,
+ [GNUTLS_PK_DSA] = QCRYPTO_PK_ALGO_DSA,
+ [GNUTLS_PK_ECDSA] = QCRYPTO_PK_ALGO_ECDSA,
+ [GNUTLS_PK_RSA_OAEP] = QCRYPTO_PK_ALGO_RSA_OAEP,
+ [GNUTLS_PK_EDDSA_ED25519] = QCRYPTO_PK_ALGO_ED25519,
+ [GNUTLS_PK_EDDSA_ED448] = QCRYPTO_PK_ALGO_ED448,
+};
+
+static const int qcrypto_to_gnutls_keyid_flags_map[] = {
+ [QCRYPTO_HASH_ALGO_MD5] = -1,
+ [QCRYPTO_HASH_ALGO_SHA1] = GNUTLS_KEYID_USE_SHA1,
+ [QCRYPTO_HASH_ALGO_SHA224] = -1,
+ [QCRYPTO_HASH_ALGO_SHA256] = GNUTLS_KEYID_USE_SHA256,
+ [QCRYPTO_HASH_ALGO_SHA384] = -1,
+ [QCRYPTO_HASH_ALGO_SHA512] = GNUTLS_KEYID_USE_SHA512,
+ [QCRYPTO_HASH_ALGO_RIPEMD160] = -1,
+};
+
int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
QCryptoHashAlgo alg,
uint8_t *result,
@@ -122,6 +141,207 @@ cleanup:
return ret;
}
+int qcrypto_x509_check_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+ int rc;
+ int ret = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+ time_t now = time(0);
+ time_t exp_time;
+ time_t act_time;
+
+ if (now == ((time_t)-1)) {
+ error_setg_errno(errp, errno, "Cannot get current time");
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_init(&crt);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ exp_time = gnutls_x509_crt_get_expiration_time(crt);
+ if (exp_time == ((time_t)-1)) {
+ error_setg(errp, "Failed to get certificate expiration time");
+ goto cleanup;
+ }
+ if (exp_time < now) {
+ error_setg(errp, "The certificate has expired");
+ goto cleanup;
+ }
+
+ act_time = gnutls_x509_crt_get_activation_time(crt);
+ if (act_time == ((time_t)-1)) {
+ error_setg(errp, "Failed to get certificate activation time");
+ goto cleanup;
+ }
+ if (act_time > now) {
+ error_setg(errp, "The certificate is not yet active");
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return ret;
+}
+
+int qcrypto_x509_get_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+ int rc;
+ int ret = -1;
+ unsigned int bits;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+
+ rc = gnutls_x509_crt_init(&crt);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
+ if (rc >= G_N_ELEMENTS(gnutls_to_qcrypto_pk_alg_map)) {
+ error_setg(errp, "Unknown public key algorithm %d", rc);
+ goto cleanup;
+ }
+
+ ret = gnutls_to_qcrypto_pk_alg_map[rc];
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return ret;
+}
+
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+ QCryptoHashAlgo hash_alg,
+ uint8_t **result,
+ size_t *resultlen,
+ Error **errp)
+{
+ int rc;
+ int ret = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+
+ if (hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
+ error_setg(errp, "Unknown hash algorithm %d", hash_alg);
+ return ret;
+ }
+
+ if (qcrypto_to_gnutls_keyid_flags_map[hash_alg] == -1 ||
+ hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
+ error_setg(errp, "Unsupported key id flag %d", hash_alg);
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_init(&crt);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ *resultlen = gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[hash_alg]);
+ if (*resultlen == 0) {
+ error_setg(errp, "Failed to get hash algorithn length: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ *result = g_malloc0(*resultlen);
+ if (gnutls_x509_crt_get_key_id(crt,
+ qcrypto_to_gnutls_keyid_flags_map[hash_alg],
+ *result, resultlen) != 0) {
+ error_setg(errp, "Failed to get key ID from certificate");
+ g_clear_pointer(result, g_free);
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ return ret;
+}
+
+static int qcrypto_x509_get_ecc_curve(uint8_t *cert, size_t size, Error **errp)
+{
+ int rc;
+ int ret = -1;
+ gnutls_x509_crt_t crt;
+ gnutls_datum_t datum = {.data = cert, .size = size};
+ gnutls_ecc_curve_t curve_id;
+ gnutls_datum_t x = {.data = NULL, .size = 0};
+ gnutls_datum_t y = {.data = NULL, .size = 0};
+
+ rc = gnutls_x509_crt_init(&crt);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+ return ret;
+ }
+
+ rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_x509_crt_get_pk_ecc_raw(crt, &curve_id, &x, &y);
+ if (rc != 0) {
+ error_setg(errp, "Failed to get ECC public key curve: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ ret = curve_id;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ gnutls_free(x.data);
+ gnutls_free(y.data);
+ return ret;
+}
+
+int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
+{
+ int curve_id;
+
+ curve_id = qcrypto_x509_get_ecc_curve(cert, size, errp);
+ if (curve_id == -1) {
+ return -1;
+ }
+
+ if (curve_id == GNUTLS_ECC_CURVE_INVALID) {
+ error_setg(errp, "Invalid ECC curve");
+ return -1;
+ }
+
+ if (curve_id == GNUTLS_ECC_CURVE_SECP521R1) {
+ return 1;
+ }
+
+ return 0;
+}
+
#else /* ! CONFIG_GNUTLS */
int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
@@ -143,4 +363,32 @@ int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
return -1;
}
+int qcrypto_x509_check_cert_times(uint8_t *cert, size_t size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get certificate times");
+ return -1;
+}
+
+int qcrypto_x509_get_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get public key algorithm");
+ return -1;
+}
+
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+ QCryptoHashAlgo hash_alg,
+ uint8_t **result,
+ size_t *resultlen,
+ Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to get key ID");
+ return -1;
+}
+
+int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to determine ecc curve");
+ return -1;
+}
+
#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 4239e3e55a..6fc8d982b7 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -13,6 +13,15 @@
#include "crypto/hash.h"
+typedef enum {
+ QCRYPTO_PK_ALGO_RSA,
+ QCRYPTO_PK_ALGO_DSA,
+ QCRYPTO_PK_ALGO_ECDSA,
+ QCRYPTO_PK_ALGO_RSA_OAEP,
+ QCRYPTO_PK_ALGO_ED25519,
+ QCRYPTO_PK_ALGO_ED448,
+} QCryptoPkAlgo;
+
int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
QCryptoHashAlgo hash,
uint8_t *result,
@@ -39,4 +48,67 @@ int qcrypto_x509_convert_cert_der(uint8_t *cert, size_t size,
size_t *resultlen,
Error **errp);
+/**
+ * qcrypto_x509_check_cert_times
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Check whether the activation and expiration times of @cert
+ * are valid at the current time.
+ *
+ * Returns: 0 if the certificate times are valid,
+ * -1 on error.
+ */
+int qcrypto_x509_check_cert_times(uint8_t *cert, size_t size, Error **errp);
+
+/**
+ * qcrypto_x509_get_pk_algorithm
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine the public key algorithm of the @cert.
+ *
+ * Returns: a value from the QCryptoPkAlgo enum on success,
+ * -1 on error.
+ */
+int qcrypto_x509_get_pk_algorithm(uint8_t *cert, size_t size, Error **errp);
+
+/**
+ * qcrypto_x509_get_cert_key_id
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @hash_alg: the hash algorithm flag
+ * @result: output location for the allocated buffer for key ID
+ (the function allocates memory which must be freed by the caller)
+ * @resultlen: pointer to the size of the buffer
+ (will be updated with the actual size of key id)
+ * @errp: error pointer
+ *
+ * Retrieve the key ID from the @cert based on the specified @flag.
+ *
+ * Returns: 0 if key ID was successfully stored in @result,
+ * -1 on error.
+ */
+int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
+ QCryptoHashAlgo hash_alg,
+ uint8_t **result,
+ size_t *resultlen,
+ Error **errp);
+
+/**
+ * qcrypto_x509_is_ecc_curve_p521
+ * @cert: pointer to the raw certificate data
+ * @size: size of the certificate
+ * @errp: error pointer
+ *
+ * Determine whether the ECC public key in the given certificate uses the P-521 curve.
+ *
+ * Returns: 0 if ECC public key does not use P521 curve.
+ * 1 if ECC public key uses P521 curve.
+ -1 on error.
+ */
+int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp);
+
#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-09-17 23:21 ` [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
@ 2025-09-19 18:02 ` Farhan Ali
2025-10-07 9:34 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-19 18:02 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> Introduce new helper functions to extract certificate metadata needed for
> DIAG 320 subcode 2:
Since we are adding generic helper functions to extract certificate
metadata, maybe we should avoid mentioning about "DIAG 320 subcode 2"?
Thanks
Farhan
>
> qcrypto_x509_check_cert_times() - validates the certificate's validity period against the current time
> qcrypto_x509_get_pk_algorithm() - returns the public key algorithm used in the certificate
> qcrypto_x509_get_cert_key_id() - extracts the key ID from the certificate
> qcrypto_x509_is_ecc_curve_p521() - determines the ECC public key algorithm uses P-521 curve
>
> These functions provide support for metadata extraction and validity checking
> for X.509 certificates.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-09-17 23:21 ` [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
2025-09-19 18:02 ` Farhan Ali
@ 2025-10-07 9:34 ` Thomas Huth
2025-10-07 9:38 ` Daniel P. Berrangé
1 sibling, 1 reply; 89+ messages in thread
From: Thomas Huth @ 2025-10-07 9:34 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Introduce new helper functions to extract certificate metadata needed for
> DIAG 320 subcode 2:
>
> qcrypto_x509_check_cert_times() - validates the certificate's validity period against the current time
> qcrypto_x509_get_pk_algorithm() - returns the public key algorithm used in the certificate
> qcrypto_x509_get_cert_key_id() - extracts the key ID from the certificate
> qcrypto_x509_is_ecc_curve_p521() - determines the ECC public key algorithm uses P-521 curve
>
> These functions provide support for metadata extraction and validity checking
> for X.509 certificates.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
...
> +int qcrypto_x509_get_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
> +{
> + int rc;
> + int ret = -1;
> + unsigned int bits;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + rc = gnutls_x509_crt_init(&crt);
> + if (rc < 0) {
> + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> + return ret;
> + }
> +
> + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
> + if (rc != 0) {
> + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
> + if (rc >= G_N_ELEMENTS(gnutls_to_qcrypto_pk_alg_map)) {
gnutls_x509_crt_get_pk_algorithm can also return a negative value according
to the documentation, so I think you should also check for "rc < 0" in
addition here.
> + error_setg(errp, "Unknown public key algorithm %d", rc);
> + goto cleanup;
> + }
> +
> + ret = gnutls_to_qcrypto_pk_alg_map[rc];
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return ret;
> +}
> +
> +int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
> + QCryptoHashAlgo hash_alg,
> + uint8_t **result,
> + size_t *resultlen,
> + Error **errp)
> +{
> + int rc;
> + int ret = -1;
> + gnutls_x509_crt_t crt;
> + gnutls_datum_t datum = {.data = cert, .size = size};
> +
> + if (hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
> + error_setg(errp, "Unknown hash algorithm %d", hash_alg);
> + return ret;
> + }
> +
> + if (qcrypto_to_gnutls_keyid_flags_map[hash_alg] == -1 ||
> + hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
Since "||" conditions are evaluated from left to right, please check for the
boundary first before using hash_alg as index into the array (i.e. swapt the
two sides of the "||").
> + error_setg(errp, "Unsupported key id flag %d", hash_alg);
> + return ret;
> + }
> +
> + rc = gnutls_x509_crt_init(&crt);
> + if (rc < 0) {
> + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> + return ret;
> + }
> +
> + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
> + if (rc != 0) {
> + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + *resultlen = gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[hash_alg]);
> + if (*resultlen == 0) {
> + error_setg(errp, "Failed to get hash algorithn length: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + *result = g_malloc0(*resultlen);
> + if (gnutls_x509_crt_get_key_id(crt,
> + qcrypto_to_gnutls_keyid_flags_map[hash_alg],
> + *result, resultlen) != 0) {
> + error_setg(errp, "Failed to get key ID from certificate");
> + g_clear_pointer(result, g_free);
> + goto cleanup;
> + }
> +
> + ret = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + return ret;
> +}
...
> +int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
> +{
> + int curve_id;
> +
> + curve_id = qcrypto_x509_get_ecc_curve(cert, size, errp);
> + if (curve_id == -1) {
> + return -1;
> + }
> +
> + if (curve_id == GNUTLS_ECC_CURVE_INVALID) {
> + error_setg(errp, "Invalid ECC curve");
> + return -1;
> + }
> +
> + if (curve_id == GNUTLS_ECC_CURVE_SECP521R1) {
> + return 1;
> + }
> +
> + return 0;
> +}
Bikeshedding, but IMHO, if you name a function "..._is_something", I'd
prefer if it returns a bool, and not an "int". Otherwise this might get
confusing if you read something like this later in the code:
if (qcrypto_x509_is_ecc_curve_p521(...)) {
}
The caller could use errp to distinguish between the error case and a
simple "false" as answer to the question.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-10-07 9:34 ` Thomas Huth
@ 2025-10-07 9:38 ` Daniel P. Berrangé
2025-10-07 9:41 ` Thomas Huth
0 siblings, 1 reply; 89+ messages in thread
From: Daniel P. Berrangé @ 2025-10-07 9:38 UTC (permalink / raw)
To: Thomas Huth
Cc: Zhuoying Cai, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, armbru, alifm
On Tue, Oct 07, 2025 at 11:34:29AM +0200, Thomas Huth wrote:
> On 18/09/2025 01.21, Zhuoying Cai wrote:
> > Introduce new helper functions to extract certificate metadata needed for
> > DIAG 320 subcode 2:
> >
> > qcrypto_x509_check_cert_times() - validates the certificate's validity period against the current time
> > qcrypto_x509_get_pk_algorithm() - returns the public key algorithm used in the certificate
> > qcrypto_x509_get_cert_key_id() - extracts the key ID from the certificate
> > qcrypto_x509_is_ecc_curve_p521() - determines the ECC public key algorithm uses P-521 curve
> >
> > These functions provide support for metadata extraction and validity checking
> > for X.509 certificates.
> >
> > Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> > ---
> ...
> > +int qcrypto_x509_get_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
> > +{
> > + int rc;
> > + int ret = -1;
> > + unsigned int bits;
> > + gnutls_x509_crt_t crt;
> > + gnutls_datum_t datum = {.data = cert, .size = size};
> > +
> > + rc = gnutls_x509_crt_init(&crt);
> > + if (rc < 0) {
> > + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> > + return ret;
> > + }
> > +
> > + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
> > + if (rc != 0) {
> > + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> > + goto cleanup;
> > + }
> > +
> > + rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
> > + if (rc >= G_N_ELEMENTS(gnutls_to_qcrypto_pk_alg_map)) {
>
> gnutls_x509_crt_get_pk_algorithm can also return a negative value according
> to the documentation, so I think you should also check for "rc < 0" in
> addition here.
>
> > + error_setg(errp, "Unknown public key algorithm %d", rc);
> > + goto cleanup;
> > + }
> > +
> > + ret = gnutls_to_qcrypto_pk_alg_map[rc];
> > +
> > +cleanup:
> > + gnutls_x509_crt_deinit(crt);
> > + return ret;
> > +}
> > +
> > +int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
> > + QCryptoHashAlgo hash_alg,
> > + uint8_t **result,
> > + size_t *resultlen,
> > + Error **errp)
> > +{
> > + int rc;
> > + int ret = -1;
> > + gnutls_x509_crt_t crt;
> > + gnutls_datum_t datum = {.data = cert, .size = size};
> > +
> > + if (hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
> > + error_setg(errp, "Unknown hash algorithm %d", hash_alg);
> > + return ret;
> > + }
> > +
> > + if (qcrypto_to_gnutls_keyid_flags_map[hash_alg] == -1 ||
> > + hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
>
> Since "||" conditions are evaluated from left to right, please check for the
> boundary first before using hash_alg as index into the array (i.e. swapt the
> two sides of the "||").
>
> > + error_setg(errp, "Unsupported key id flag %d", hash_alg);
> > + return ret;
> > + }
> > +
> > + rc = gnutls_x509_crt_init(&crt);
> > + if (rc < 0) {
> > + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> > + return ret;
> > + }
> > +
> > + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
> > + if (rc != 0) {
> > + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> > + goto cleanup;
> > + }
> > +
> > + *resultlen = gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[hash_alg]);
> > + if (*resultlen == 0) {
> > + error_setg(errp, "Failed to get hash algorithn length: %s", gnutls_strerror(rc));
> > + goto cleanup;
> > + }
> > +
> > + *result = g_malloc0(*resultlen);
> > + if (gnutls_x509_crt_get_key_id(crt,
> > + qcrypto_to_gnutls_keyid_flags_map[hash_alg],
> > + *result, resultlen) != 0) {
> > + error_setg(errp, "Failed to get key ID from certificate");
> > + g_clear_pointer(result, g_free);
> > + goto cleanup;
> > + }
> > +
> > + ret = 0;
> > +
> > +cleanup:
> > + gnutls_x509_crt_deinit(crt);
> > + return ret;
> > +}
> ...
> > +int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
> > +{
> > + int curve_id;
> > +
> > + curve_id = qcrypto_x509_get_ecc_curve(cert, size, errp);
> > + if (curve_id == -1) {
> > + return -1;
> > + }
> > +
> > + if (curve_id == GNUTLS_ECC_CURVE_INVALID) {
> > + error_setg(errp, "Invalid ECC curve");
> > + return -1;
> > + }
> > +
> > + if (curve_id == GNUTLS_ECC_CURVE_SECP521R1) {
> > + return 1;
> > + }
> > +
> > + return 0;
> > +}
>
> Bikeshedding, but IMHO, if you name a function "..._is_something", I'd
> prefer if it returns a bool, and not an "int". Otherwise this might get
> confusing if you read something like this later in the code:
>
> if (qcrypto_x509_is_ecc_curve_p521(...)) {
> }
>
> The caller could use errp to distinguish between the error case and a
> simple "false" as answer to the question.
Overloading one 'false' return value to mean both success & failure
is an anti-pattern IMHO. We have 3 separate return values we need
for this function, so using -1/0/1 is the right approach.
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] 89+ messages in thread
* Re: [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2
2025-10-07 9:38 ` Daniel P. Berrangé
@ 2025-10-07 9:41 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-10-07 9:41 UTC (permalink / raw)
To: Daniel P. Berrangé
Cc: Zhuoying Cai, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, armbru, alifm
On 07/10/2025 11.38, Daniel P. Berrangé wrote:
> On Tue, Oct 07, 2025 at 11:34:29AM +0200, Thomas Huth wrote:
>> On 18/09/2025 01.21, Zhuoying Cai wrote:
>>> Introduce new helper functions to extract certificate metadata needed for
>>> DIAG 320 subcode 2:
>>>
>>> qcrypto_x509_check_cert_times() - validates the certificate's validity period against the current time
>>> qcrypto_x509_get_pk_algorithm() - returns the public key algorithm used in the certificate
>>> qcrypto_x509_get_cert_key_id() - extracts the key ID from the certificate
>>> qcrypto_x509_is_ecc_curve_p521() - determines the ECC public key algorithm uses P-521 curve
>>>
>>> These functions provide support for metadata extraction and validity checking
>>> for X.509 certificates.
>>>
>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>>> ---
>> ...
>>> +int qcrypto_x509_get_pk_algorithm(uint8_t *cert, size_t size, Error **errp)
>>> +{
>>> + int rc;
>>> + int ret = -1;
>>> + unsigned int bits;
>>> + gnutls_x509_crt_t crt;
>>> + gnutls_datum_t datum = {.data = cert, .size = size};
>>> +
>>> + rc = gnutls_x509_crt_init(&crt);
>>> + if (rc < 0) {
>>> + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
>>> + return ret;
>>> + }
>>> +
>>> + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
>>> + if (rc != 0) {
>>> + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
>>> + goto cleanup;
>>> + }
>>> +
>>> + rc = gnutls_x509_crt_get_pk_algorithm(crt, &bits);
>>> + if (rc >= G_N_ELEMENTS(gnutls_to_qcrypto_pk_alg_map)) {
>>
>> gnutls_x509_crt_get_pk_algorithm can also return a negative value according
>> to the documentation, so I think you should also check for "rc < 0" in
>> addition here.
>>
>>> + error_setg(errp, "Unknown public key algorithm %d", rc);
>>> + goto cleanup;
>>> + }
>>> +
>>> + ret = gnutls_to_qcrypto_pk_alg_map[rc];
>>> +
>>> +cleanup:
>>> + gnutls_x509_crt_deinit(crt);
>>> + return ret;
>>> +}
>>> +
>>> +int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
>>> + QCryptoHashAlgo hash_alg,
>>> + uint8_t **result,
>>> + size_t *resultlen,
>>> + Error **errp)
>>> +{
>>> + int rc;
>>> + int ret = -1;
>>> + gnutls_x509_crt_t crt;
>>> + gnutls_datum_t datum = {.data = cert, .size = size};
>>> +
>>> + if (hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) {
>>> + error_setg(errp, "Unknown hash algorithm %d", hash_alg);
>>> + return ret;
>>> + }
>>> +
>>> + if (qcrypto_to_gnutls_keyid_flags_map[hash_alg] == -1 ||
>>> + hash_alg >= G_N_ELEMENTS(qcrypto_to_gnutls_keyid_flags_map)) {
>>
>> Since "||" conditions are evaluated from left to right, please check for the
>> boundary first before using hash_alg as index into the array (i.e. swapt the
>> two sides of the "||").
>>
>>> + error_setg(errp, "Unsupported key id flag %d", hash_alg);
>>> + return ret;
>>> + }
>>> +
>>> + rc = gnutls_x509_crt_init(&crt);
>>> + if (rc < 0) {
>>> + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
>>> + return ret;
>>> + }
>>> +
>>> + rc = gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM);
>>> + if (rc != 0) {
>>> + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
>>> + goto cleanup;
>>> + }
>>> +
>>> + *resultlen = gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[hash_alg]);
>>> + if (*resultlen == 0) {
>>> + error_setg(errp, "Failed to get hash algorithn length: %s", gnutls_strerror(rc));
>>> + goto cleanup;
>>> + }
>>> +
>>> + *result = g_malloc0(*resultlen);
>>> + if (gnutls_x509_crt_get_key_id(crt,
>>> + qcrypto_to_gnutls_keyid_flags_map[hash_alg],
>>> + *result, resultlen) != 0) {
>>> + error_setg(errp, "Failed to get key ID from certificate");
>>> + g_clear_pointer(result, g_free);
>>> + goto cleanup;
>>> + }
>>> +
>>> + ret = 0;
>>> +
>>> +cleanup:
>>> + gnutls_x509_crt_deinit(crt);
>>> + return ret;
>>> +}
>> ...
>>> +int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
>>> +{
>>> + int curve_id;
>>> +
>>> + curve_id = qcrypto_x509_get_ecc_curve(cert, size, errp);
>>> + if (curve_id == -1) {
>>> + return -1;
>>> + }
>>> +
>>> + if (curve_id == GNUTLS_ECC_CURVE_INVALID) {
>>> + error_setg(errp, "Invalid ECC curve");
>>> + return -1;
>>> + }
>>> +
>>> + if (curve_id == GNUTLS_ECC_CURVE_SECP521R1) {
>>> + return 1;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>
>> Bikeshedding, but IMHO, if you name a function "..._is_something", I'd
>> prefer if it returns a bool, and not an "int". Otherwise this might get
>> confusing if you read something like this later in the code:
>>
>> if (qcrypto_x509_is_ecc_curve_p521(...)) {
>> }
>>
>> The caller could use errp to distinguish between the error case and a
>> simple "false" as answer to the question.
>
> Overloading one 'false' return value to mean both success & failure
> is an anti-pattern IMHO. We have 3 separate return values we need
> for this function, so using -1/0/1 is the right approach.
In that case, please rename the function to something like
"qcrypto_x509_check_ecc_curve_p521" or so ... it's IMHO getting
too confusing otherwise.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 09/28] s390x/diag: Implement DIAG 320 subcode 2
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (7 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 08/28] crypto/x509-utils: Add helper functions for DIAG 320 subcode 2 Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-24 21:53 ` Farhan Ali
2025-09-17 23:21 ` [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
` (19 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
certificate store. Only X509 certificates in DER format and SHA-256 hash
type are recognized.
The subcode value is denoted by setting the second-left-most bit
of an 8-byte field.
The Verification Certificate Block (VCB) contains the output data
when the operation completes successfully. It includes a common
header followed by zero or more Verification Certificate Entries (VCEs),
depending on the VCB input length and the VC range (from the first VC
index to the last VC index) in the certificate store.
Each VCE contains information about a certificate retrieved from
the S390IPLCertificateStore, such as the certificate name, key type,
key ID length, hash length, and the raw certificate data.
The key ID and hash are extracted from the raw certificate by the crypto API.
Note: SHA2-256 VC hash type is required for retrieving the hash
(fingerprint) of the certificate.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 13 ++
include/hw/s390x/ipl/diag320.h | 49 +++++
target/s390x/diag.c | 312 +++++++++++++++++++++++++++++++-
3 files changed, 373 insertions(+), 1 deletion(-)
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 4217f19c84..e28f0b40d7 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -35,3 +35,16 @@ Subcode 1 - query verification certificate storage information
The output is returned in the verification-certificate-storage-size block (VCSSB).
A VCSSB length of 4 indicates that no certificates are available in the CS.
+
+Subcode 2 - store verification certificates
+ Provides VCs that are in the certificate store.
+
+ The output is provided in a VCB, which includes a common header followed by zero
+ or more verification-certificate entries (VCEs).
+
+ The 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.
diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
index 6e4779c699..2af14b9f01 100644
--- a/include/hw/s390x/ipl/diag320.h
+++ b/include/hw/s390x/ipl/diag320.h
@@ -12,19 +12,30 @@
#define DIAG_320_SUBC_QUERY_ISM 0
#define DIAG_320_SUBC_QUERY_VCSI 1
+#define DIAG_320_SUBC_STORE_VC 2
#define DIAG_320_RC_OK 0x0001
#define DIAG_320_RC_NOT_SUPPORTED 0x0102
#define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
+#define DIAG_320_RC_INVAL_VCB_LEN 0x0204
+#define DIAG_320_RC_BAD_RANGE 0x0302
#define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
#define DIAG_320_ISM_QUERY_VCSI 0x40000000
+#define DIAG_320_ISM_STORE_VC 0x20000000
#define VCSSB_NO_VC 4
#define VCSSB_MIN_LEN 128
#define VCE_HEADER_LEN 128
+#define VCE_INVALID_LEN 72
#define VCB_HEADER_LEN 64
+#define DIAG_320_VCE_FLAGS_VALID 0x80
+#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING 0
+#define DIAG_320_VCE_KEYTYPE_ECDSA_P521 1
+#define DIAG_320_VCE_FORMAT_X509_DER 1
+#define DIAG_320_VCE_HASHTYPE_SHA2_256 1
+
struct VCStorageSizeBlock {
uint32_t length;
uint8_t reserved0[3];
@@ -39,4 +50,42 @@ struct VCStorageSizeBlock {
};
typedef struct VCStorageSizeBlock VCStorageSizeBlock;
+struct VCBlock {
+ uint32_t in_len;
+ uint32_t reserved0;
+ uint16_t first_vc_index;
+ uint16_t last_vc_index;
+ uint32_t reserved1[5];
+ uint32_t out_len;
+ uint8_t reserved2[3];
+ uint8_t version;
+ uint16_t stored_ct;
+ uint16_t remain_ct;
+ uint32_t reserved3[5];
+ uint8_t vce_buf[];
+};
+typedef struct VCBlock VCBlock;
+
+struct VCEntry {
+ uint32_t len;
+ uint8_t flags;
+ uint8_t key_type;
+ uint16_t cert_idx;
+ uint32_t name[16];
+ uint8_t format;
+ uint8_t reserved0;
+ uint16_t keyid_len;
+ uint8_t reserved1;
+ uint8_t hash_type;
+ uint16_t hash_len;
+ uint32_t reserved2;
+ uint32_t cert_len;
+ uint32_t reserved3[2];
+ uint16_t hash_offset;
+ uint16_t cert_offset;
+ uint32_t reserved4[7];
+ uint8_t cert_buf[];
+};
+typedef struct VCEntry VCEntry;
+
#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 4e6de483b8..d5f6c54df3 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -17,6 +17,7 @@
#include "s390x-internal.h"
#include "hw/watchdog/wdt_diag288.h"
#include "system/cpus.h"
+#include "hw/s390x/cert-store.h"
#include "hw/s390x/ipl.h"
#include "hw/s390x/ipl/diag320.h"
#include "hw/s390x/s390-virtio-ccw.h"
@@ -24,6 +25,7 @@
#include "kvm/kvm_s390x.h"
#include "target/s390x/kvm/pv.h"
#include "qemu/error-report.h"
+#include "crypto/x509-utils.h"
int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
@@ -225,8 +227,308 @@ static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
return DIAG_320_RC_OK;
}
+static bool is_cert_valid(S390IPLCertificate cert)
+{
+ int rc;
+ Error *err = NULL;
+
+ rc = qcrypto_x509_check_cert_times(cert.raw, cert.size, &err);
+ if (rc != 0) {
+ error_report_err(err);
+ return false;
+ }
+
+ return true;
+}
+
+static void handle_key_id(VCEntry *vce, S390IPLCertificate cert)
+{
+ int rc;
+ g_autofree unsigned char *key_id_data = NULL;
+ size_t key_id_len;
+ Error *err = NULL;
+
+ key_id_len = CERT_KEY_ID_LEN;
+ /* key id and key id len */
+ rc = qcrypto_x509_get_cert_key_id(cert.raw, cert.size,
+ QCRYPTO_HASH_ALGO_SHA256,
+ &key_id_data, &key_id_len, &err);
+ if (rc < 0) {
+ error_report_err(err);
+ return;
+ }
+ vce->keyid_len = cpu_to_be16(key_id_len);
+
+ memcpy(vce->cert_buf, key_id_data, key_id_len);
+}
+
+static int handle_hash(VCEntry *vce, S390IPLCertificate cert, uint16_t keyid_field_len)
+{
+ int rc;
+ uint16_t hash_offset;
+ g_autofree void *hash_data = NULL;
+ size_t hash_len;
+ Error *err = NULL;
+
+ hash_len = CERT_HASH_LEN;
+ /* hash and hash len */
+ hash_data = g_malloc0(hash_len);
+ rc = qcrypto_get_x509_cert_fingerprint(cert.raw, cert.size,
+ QCRYPTO_HASH_ALGO_SHA256,
+ hash_data, &hash_len, &err);
+ if (rc < 0) {
+ error_report_err(err);
+ return -1;
+ }
+ vce->hash_len = cpu_to_be16(hash_len);
+
+ /* hash type */
+ vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
+
+ hash_offset = VCE_HEADER_LEN + keyid_field_len;
+ vce->hash_offset = cpu_to_be16(hash_offset);
+
+ memcpy((uint8_t *)vce + hash_offset, hash_data, hash_len);
+
+ return 0;
+}
+
+static int handle_cert(VCEntry *vce, S390IPLCertificate cert, uint16_t hash_field_len)
+{
+ int rc;
+ uint16_t cert_offset;
+ g_autofree uint8_t *cert_der = NULL;
+ Error *err = NULL;
+
+ /* certificate in DER format */
+ rc = qcrypto_x509_convert_cert_der(cert.raw, cert.size,
+ &cert_der, &cert.der_size, &err);
+ if (rc < 0) {
+ error_report_err(err);
+ return -1;
+ }
+ vce->format = DIAG_320_VCE_FORMAT_X509_DER;
+ vce->cert_len = cpu_to_be32(cert.der_size);
+ cert_offset = be16_to_cpu(vce->hash_offset) + hash_field_len;
+ vce->cert_offset = cpu_to_be16(cert_offset);
+
+ memcpy((uint8_t *)vce + cert_offset, cert_der, cert.der_size);
+
+ return 0;
+}
+
+static int get_key_type(S390IPLCertificate cert)
+{
+ int algo;
+ int rc;
+ Error *err = NULL;
+
+ /* public key algorithm */
+ algo = qcrypto_x509_get_pk_algorithm(cert.raw, cert.size, &err);
+ if (algo < 0) {
+ error_report_err(err);
+ return -1;
+ }
+
+ if (algo == QCRYPTO_PK_ALGO_ECDSA) {
+ rc = qcrypto_x509_is_ecc_curve_p521(cert.raw, cert.size, &err);
+ if (rc == -1) {
+ error_report_err(err);
+ return -1;
+ }
+
+ return (rc == 1) ? DIAG_320_VCE_KEYTYPE_ECDSA_P521 :
+ DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
+ }
+
+ return DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
+}
+
+static int build_vce_header(VCEntry *vce, S390IPLCertificate cert, int idx)
+{
+ int key_type;
+
+ vce->len = cpu_to_be32(VCE_HEADER_LEN);
+ vce->cert_idx = cpu_to_be16(idx + 1);
+ strncpy((char *)vce->name, (char *)cert.vc_name, VC_NAME_LEN_BYTES);
+
+ key_type = get_key_type(cert);
+ if (key_type == -1) {
+ return -1;
+ }
+ vce->key_type = key_type;
+
+ return 0;
+}
+
+static int build_vce_data(VCEntry *vce, S390IPLCertificate cert)
+{
+ uint16_t keyid_field_len;
+ uint16_t hash_field_len;
+ uint32_t cert_field_len;
+ int rc;
+
+ handle_key_id(vce, cert);
+ /* vce key id field length - can be 0 if failed to retrieve */
+ keyid_field_len = ROUND_UP(be16_to_cpu(vce->keyid_len), 4);
+
+ rc = handle_hash(vce, cert, keyid_field_len);
+ if (rc) {
+ return -1;
+ }
+ hash_field_len = ROUND_UP(be16_to_cpu(vce->hash_len), 4);
+
+ rc = handle_cert(vce, cert, hash_field_len);
+ if (rc || !is_cert_valid(cert)) {
+ return -1;
+ }
+ /* vce certificate field length */
+ cert_field_len = ROUND_UP(be32_to_cpu(vce->cert_len), 4);
+
+ /* The certificate is valid and VCE contains the certificate */
+ vce->flags |= DIAG_320_VCE_FLAGS_VALID;
+
+ /* Update vce length to reflect the acutal size used by vce */
+ vce->len += cpu_to_be32(keyid_field_len + hash_field_len + cert_field_len);
+
+ return 0;
+}
+
+static VCEntry *diag_320_build_vce(S390IPLCertificate cert, uint32_t vce_len, int idx)
+{
+ g_autofree VCEntry *vce = NULL;
+ int rc;
+
+ /*
+ * Construct VCE
+ * Allocate enough memory for all certificate data (key id, hash and certificate).
+ * Unused area following the VCE field contains zeros.
+ */
+ vce = g_malloc0(vce_len);
+ rc = build_vce_header(vce, cert, idx);
+ if (rc) {
+ vce->len = cpu_to_be32(VCE_INVALID_LEN);
+ goto out;
+ }
+ vce->len = cpu_to_be32(VCE_HEADER_LEN);
+
+ rc = build_vce_data(vce, cert);
+ if (rc) {
+ vce->len = cpu_to_be32(VCE_INVALID_LEN);
+ }
+
+out:
+ return g_steal_pointer(&vce);
+}
+
+static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
+ S390IPLCertificateStore *qcs)
+{
+ g_autofree VCBlock *vcb = NULL;
+ size_t vce_offset;
+ size_t remaining_space;
+ uint32_t vce_len;
+ uint16_t first_vc_index;
+ uint16_t last_vc_index;
+ uint32_t in_len;
+
+ vcb = g_new0(VCBlock, 1);
+ if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return -1;
+ }
+
+ in_len = be32_to_cpu(vcb->in_len);
+ first_vc_index = be16_to_cpu(vcb->first_vc_index);
+ last_vc_index = be16_to_cpu(vcb->last_vc_index);
+
+ if (in_len % TARGET_PAGE_SIZE != 0) {
+ return DIAG_320_RC_INVAL_VCB_LEN;
+ }
+
+ if (first_vc_index > last_vc_index) {
+ return DIAG_320_RC_BAD_RANGE;
+ }
+
+ vcb->out_len = VCB_HEADER_LEN;
+
+ if (first_vc_index == 0) {
+ /*
+ * Zero is a valid index for the first and last VC index.
+ * Zero index results in the VCB header and zero certificates returned.
+ */
+ if (last_vc_index == 0) {
+ goto out;
+ }
+
+ /* DIAG320 certificate store remains a one origin for cert entries */
+ vcb->first_vc_index = 1;
+ first_vc_index = 1;
+ }
+
+ vce_offset = VCB_HEADER_LEN;
+ 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 cert = qcs->certs[i];
+ /*
+ * Each VCE is word aligned.
+ * Each variable length field within the VCE is also word aligned.
+ */
+ vce_len = VCE_HEADER_LEN +
+ ROUND_UP(CERT_KEY_ID_LEN, 4) +
+ ROUND_UP(CERT_HASH_LEN, 4) +
+ ROUND_UP(cert.der_size, 4);
+
+ /*
+ * If there is no more space to store the cert,
+ * set the remaining verification cert count and
+ * break early.
+ */
+ if (remaining_space < vce_len) {
+ vcb->remain_ct = cpu_to_be16(last_vc_index - i);
+ break;
+ }
+
+ vce = diag_320_build_vce(cert, vce_len, i);
+
+ /* Write VCE */
+ if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1,
+ vce, be32_to_cpu(vce->len))) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ g_free(vce);
+ return -1;
+ }
+
+ vce_offset += be32_to_cpu(vce->len);
+ vcb->out_len += be32_to_cpu(vce->len);
+ remaining_space -= be32_to_cpu(vce->len);
+ vcb->stored_ct++;
+
+ g_free(vce);
+ }
+ vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
+
+out:
+ vcb->out_len = cpu_to_be32(vcb->out_len);
+ /*
+ * Write VCB header
+ * All VCEs have been populated with the latest information
+ * and write VCB header last.
+ */
+ if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
+ s390_cpu_virt_mem_handle_exc(cpu, ra);
+ return -1;
+ }
+
+ return DIAG_320_RC_OK;
+}
+
QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
"size of VCStorageSizeBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != VCB_HEADER_LEN, "size of VCBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != VCE_HEADER_LEN, "size of VCEntry is wrong");
void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
{
@@ -259,7 +561,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
* for now.
*/
uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
- DIAG_320_ISM_QUERY_VCSI);
+ DIAG_320_ISM_QUERY_VCSI |
+ DIAG_320_ISM_STORE_VC);
if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
s390_cpu_virt_mem_handle_exc(cpu, ra);
@@ -285,6 +588,13 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
}
env->regs[r1 + 1] = rc;
break;
+ case DIAG_320_SUBC_STORE_VC:
+ rc = handle_diag320_store_vc(cpu, addr, r1, ra, qcs);
+ if (rc == -1) {
+ return;
+ }
+ env->regs[r1 + 1] = rc;
+ break;
default:
env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
break;
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 09/28] s390x/diag: Implement DIAG 320 subcode 2
2025-09-17 23:21 ` [PATCH v6 09/28] s390x/diag: Implement " Zhuoying Cai
@ 2025-09-24 21:53 ` Farhan Ali
2025-09-26 13:42 ` Zhuoying Cai
0 siblings, 1 reply; 89+ messages in thread
From: Farhan Ali @ 2025-09-24 21:53 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
> certificate store. Only X509 certificates in DER format and SHA-256 hash
> type are recognized.
>
> The subcode value is denoted by setting the second-left-most bit
> of an 8-byte field.
>
> The Verification Certificate Block (VCB) contains the output data
> when the operation completes successfully. It includes a common
> header followed by zero or more Verification Certificate Entries (VCEs),
> depending on the VCB input length and the VC range (from the first VC
> index to the last VC index) in the certificate store.
>
> Each VCE contains information about a certificate retrieved from
> the S390IPLCertificateStore, such as the certificate name, key type,
> key ID length, hash length, and the raw certificate data.
> The key ID and hash are extracted from the raw certificate by the crypto API.
>
> Note: SHA2-256 VC hash type is required for retrieving the hash
> (fingerprint) of the certificate.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 13 ++
> include/hw/s390x/ipl/diag320.h | 49 +++++
> target/s390x/diag.c | 312 +++++++++++++++++++++++++++++++-
> 3 files changed, 373 insertions(+), 1 deletion(-)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 4217f19c84..e28f0b40d7 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -35,3 +35,16 @@ Subcode 1 - query verification certificate storage information
>
> The output is returned in the verification-certificate-storage-size block (VCSSB).
> A VCSSB length of 4 indicates that no certificates are available in the CS.
> +
> +Subcode 2 - store verification certificates
> + Provides VCs that are in the certificate store.
> +
> + The output is provided in a VCB, which includes a common header followed by zero
> + or more verification-certificate entries (VCEs).
> +
> + The 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.
> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
> index 6e4779c699..2af14b9f01 100644
> --- a/include/hw/s390x/ipl/diag320.h
> +++ b/include/hw/s390x/ipl/diag320.h
> @@ -12,19 +12,30 @@
>
> #define DIAG_320_SUBC_QUERY_ISM 0
> #define DIAG_320_SUBC_QUERY_VCSI 1
> +#define DIAG_320_SUBC_STORE_VC 2
>
> #define DIAG_320_RC_OK 0x0001
> #define DIAG_320_RC_NOT_SUPPORTED 0x0102
> #define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
> +#define DIAG_320_RC_INVAL_VCB_LEN 0x0204
> +#define DIAG_320_RC_BAD_RANGE 0x0302
>
> #define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
> #define DIAG_320_ISM_QUERY_VCSI 0x40000000
> +#define DIAG_320_ISM_STORE_VC 0x20000000
>
> #define VCSSB_NO_VC 4
> #define VCSSB_MIN_LEN 128
> #define VCE_HEADER_LEN 128
> +#define VCE_INVALID_LEN 72
> #define VCB_HEADER_LEN 64
>
> +#define DIAG_320_VCE_FLAGS_VALID 0x80
> +#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING 0
> +#define DIAG_320_VCE_KEYTYPE_ECDSA_P521 1
> +#define DIAG_320_VCE_FORMAT_X509_DER 1
> +#define DIAG_320_VCE_HASHTYPE_SHA2_256 1
> +
> struct VCStorageSizeBlock {
> uint32_t length;
> uint8_t reserved0[3];
> @@ -39,4 +50,42 @@ struct VCStorageSizeBlock {
> };
> typedef struct VCStorageSizeBlock VCStorageSizeBlock;
>
> +struct VCBlock {
> + uint32_t in_len;
> + uint32_t reserved0;
> + uint16_t first_vc_index;
> + uint16_t last_vc_index;
> + uint32_t reserved1[5];
> + uint32_t out_len;
> + uint8_t reserved2[3];
> + uint8_t version;
> + uint16_t stored_ct;
> + uint16_t remain_ct;
> + uint32_t reserved3[5];
> + uint8_t vce_buf[];
> +};
> +typedef struct VCBlock VCBlock;
> +
> +struct VCEntry {
> + uint32_t len;
> + uint8_t flags;
> + uint8_t key_type;
> + uint16_t cert_idx;
> + uint32_t name[16];
> + uint8_t format;
> + uint8_t reserved0;
> + uint16_t keyid_len;
> + uint8_t reserved1;
> + uint8_t hash_type;
> + uint16_t hash_len;
> + uint32_t reserved2;
> + uint32_t cert_len;
> + uint32_t reserved3[2];
> + uint16_t hash_offset;
> + uint16_t cert_offset;
> + uint32_t reserved4[7];
> + uint8_t cert_buf[];
> +};
> +typedef struct VCEntry VCEntry;
> +
> #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index 4e6de483b8..d5f6c54df3 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -17,6 +17,7 @@
> #include "s390x-internal.h"
> #include "hw/watchdog/wdt_diag288.h"
> #include "system/cpus.h"
> +#include "hw/s390x/cert-store.h"
> #include "hw/s390x/ipl.h"
> #include "hw/s390x/ipl/diag320.h"
> #include "hw/s390x/s390-virtio-ccw.h"
> @@ -24,6 +25,7 @@
> #include "kvm/kvm_s390x.h"
> #include "target/s390x/kvm/pv.h"
> #include "qemu/error-report.h"
> +#include "crypto/x509-utils.h"
>
>
> int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
> @@ -225,8 +227,308 @@ static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
> return DIAG_320_RC_OK;
> }
>
> +static bool is_cert_valid(S390IPLCertificate cert)
> +{
> + int rc;
> + Error *err = NULL;
> +
> + rc = qcrypto_x509_check_cert_times(cert.raw, cert.size, &err);
> + if (rc != 0) {
> + error_report_err(err);
> + return false;
> + }
> +
> + return true;
> +}
> +
> +static void handle_key_id(VCEntry *vce, S390IPLCertificate cert)
> +{
> + int rc;
> + g_autofree unsigned char *key_id_data = NULL;
> + size_t key_id_len;
> + Error *err = NULL;
> +
> + key_id_len = CERT_KEY_ID_LEN;
> + /* key id and key id len */
> + rc = qcrypto_x509_get_cert_key_id(cert.raw, cert.size,
> + QCRYPTO_HASH_ALGO_SHA256,
> + &key_id_data, &key_id_len, &err);
> + if (rc < 0) {
> + error_report_err(err);
> + return;
> + }
> + vce->keyid_len = cpu_to_be16(key_id_len);
> +
> + memcpy(vce->cert_buf, key_id_data, key_id_len);
> +}
> +
> +static int handle_hash(VCEntry *vce, S390IPLCertificate cert, uint16_t keyid_field_len)
> +{
> + int rc;
> + uint16_t hash_offset;
> + g_autofree void *hash_data = NULL;
> + size_t hash_len;
> + Error *err = NULL;
> +
> + hash_len = CERT_HASH_LEN;
> + /* hash and hash len */
> + hash_data = g_malloc0(hash_len);
> + rc = qcrypto_get_x509_cert_fingerprint(cert.raw, cert.size,
> + QCRYPTO_HASH_ALGO_SHA256,
> + hash_data, &hash_len, &err);
> + if (rc < 0) {
> + error_report_err(err);
> + return -1;
> + }
> + vce->hash_len = cpu_to_be16(hash_len);
> +
> + /* hash type */
> + vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
> +
> + hash_offset = VCE_HEADER_LEN + keyid_field_len;
> + vce->hash_offset = cpu_to_be16(hash_offset);
> +
> + memcpy((uint8_t *)vce + hash_offset, hash_data, hash_len);
Do we need any check here to make sure we are not going out of the
bounds of vce buffer?
> +
> + return 0;
> +}
> +
> +static int handle_cert(VCEntry *vce, S390IPLCertificate cert, uint16_t hash_field_len)
> +{
> + int rc;
> + uint16_t cert_offset;
> + g_autofree uint8_t *cert_der = NULL;
> + Error *err = NULL;
> +
> + /* certificate in DER format */
> + rc = qcrypto_x509_convert_cert_der(cert.raw, cert.size,
> + &cert_der, &cert.der_size, &err);
> + if (rc < 0) {
> + error_report_err(err);
> + return -1;
> + }
> + vce->format = DIAG_320_VCE_FORMAT_X509_DER;
> + vce->cert_len = cpu_to_be32(cert.der_size);
> + cert_offset = be16_to_cpu(vce->hash_offset) + hash_field_len;
> + vce->cert_offset = cpu_to_be16(cert_offset);
> +
> + memcpy((uint8_t *)vce + cert_offset, cert_der, cert.der_size);
Similar to above, do we need any check here to make sure we are not
going out of the bounds of vce buffer?
> +
> + return 0;
> +}
> +
> +static int get_key_type(S390IPLCertificate cert)
> +{
> + int algo;
> + int rc;
> + Error *err = NULL;
> +
> + /* public key algorithm */
> + algo = qcrypto_x509_get_pk_algorithm(cert.raw, cert.size, &err);
> + if (algo < 0) {
> + error_report_err(err);
> + return -1;
> + }
> +
> + if (algo == QCRYPTO_PK_ALGO_ECDSA) {
> + rc = qcrypto_x509_is_ecc_curve_p521(cert.raw, cert.size, &err);
> + if (rc == -1) {
> + error_report_err(err);
> + return -1;
> + }
> +
> + return (rc == 1) ? DIAG_320_VCE_KEYTYPE_ECDSA_P521 :
> + DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
> + }
> +
> + return DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
> +}
> +
> +static int build_vce_header(VCEntry *vce, S390IPLCertificate cert, int idx)
> +{
> + int key_type;
> +
> + vce->len = cpu_to_be32(VCE_HEADER_LEN);
> + vce->cert_idx = cpu_to_be16(idx + 1);
> + strncpy((char *)vce->name, (char *)cert.vc_name, VC_NAME_LEN_BYTES);
> +
> + key_type = get_key_type(cert);
> + if (key_type == -1) {
> + return -1;
> + }
> + vce->key_type = key_type;
> +
> + return 0;
> +}
> +
> +static int build_vce_data(VCEntry *vce, S390IPLCertificate cert)
> +{
> + uint16_t keyid_field_len;
> + uint16_t hash_field_len;
> + uint32_t cert_field_len;
> + int rc;
> +
> + handle_key_id(vce, cert);
> + /* vce key id field length - can be 0 if failed to retrieve */
> + keyid_field_len = ROUND_UP(be16_to_cpu(vce->keyid_len), 4);
> +
> + rc = handle_hash(vce, cert, keyid_field_len);
> + if (rc) {
> + return -1;
> + }
> + hash_field_len = ROUND_UP(be16_to_cpu(vce->hash_len), 4);
> +
> + rc = handle_cert(vce, cert, hash_field_len);
> + if (rc || !is_cert_valid(cert)) {
> + return -1;
> + }
> + /* vce certificate field length */
> + cert_field_len = ROUND_UP(be32_to_cpu(vce->cert_len), 4);
> +
> + /* The certificate is valid and VCE contains the certificate */
> + vce->flags |= DIAG_320_VCE_FLAGS_VALID;
> +
> + /* Update vce length to reflect the acutal size used by vce */
> + vce->len += cpu_to_be32(keyid_field_len + hash_field_len + cert_field_len);
Should we validate the final vce->len calculated is <= vce_len?
> +
> + return 0;
> +}
> +
> +static VCEntry *diag_320_build_vce(S390IPLCertificate cert, uint32_t vce_len, int idx)
> +{
> + g_autofree VCEntry *vce = NULL;
> + int rc;
> +
> + /*
> + * Construct VCE
> + * Allocate enough memory for all certificate data (key id, hash and certificate).
> + * Unused area following the VCE field contains zeros.
> + */
> + vce = g_malloc0(vce_len);
> + rc = build_vce_header(vce, cert, idx);
> + if (rc) {
> + vce->len = cpu_to_be32(VCE_INVALID_LEN);
> + goto out;
> + }
> + vce->len = cpu_to_be32(VCE_HEADER_LEN);
This is redundant as we already set vce->len to VCE_HEADER_LEN on
successful call to build_vce_header. I think rather vce->len should be
set to vce_len to make bounds checking easier in the helper functions.
> +
> + rc = build_vce_data(vce, cert);
> + if (rc) {
> + vce->len = cpu_to_be32(VCE_INVALID_LEN);
> + }
> +
> +out:
> + return g_steal_pointer(&vce);
> +}
> +
> +static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
> + S390IPLCertificateStore *qcs)
> +{
> + g_autofree VCBlock *vcb = NULL;
> + size_t vce_offset;
> + size_t remaining_space;
> + uint32_t vce_len;
> + uint16_t first_vc_index;
> + uint16_t last_vc_index;
> + uint32_t in_len;
> +
> + vcb = g_new0(VCBlock, 1);
> + if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
> + return -1;
> + }
> +
> + in_len = be32_to_cpu(vcb->in_len);
> + first_vc_index = be16_to_cpu(vcb->first_vc_index);
> + last_vc_index = be16_to_cpu(vcb->last_vc_index);
> +
> + if (in_len % TARGET_PAGE_SIZE != 0) {
> + return DIAG_320_RC_INVAL_VCB_LEN;
> + }
> +
> + if (first_vc_index > last_vc_index) {
> + return DIAG_320_RC_BAD_RANGE;
> + }
> +
> + vcb->out_len = VCB_HEADER_LEN;
> +
> + if (first_vc_index == 0) {
> + /*
> + * Zero is a valid index for the first and last VC index.
> + * Zero index results in the VCB header and zero certificates returned.
> + */
> + if (last_vc_index == 0) {
> + goto out;
> + }
> +
> + /* DIAG320 certificate store remains a one origin for cert entries */
> + vcb->first_vc_index = 1;
> + first_vc_index = 1;
> + }
> +
> + vce_offset = VCB_HEADER_LEN;
> + 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 cert = qcs->certs[i];
> + /*
> + * Each VCE is word aligned.
> + * Each variable length field within the VCE is also word aligned.
> + */
> + vce_len = VCE_HEADER_LEN +
> + ROUND_UP(CERT_KEY_ID_LEN, 4) +
> + ROUND_UP(CERT_HASH_LEN, 4) +
> + ROUND_UP(cert.der_size, 4);
> +
> + /*
> + * If there is no more space to store the cert,
> + * set the remaining verification cert count and
> + * break early.
> + */
> + if (remaining_space < vce_len) {
> + vcb->remain_ct = cpu_to_be16(last_vc_index - i);
> + break;
> + }
> +
> + vce = diag_320_build_vce(cert, vce_len, i);
> +
> + /* Write VCE */
> + if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1,
> + vce, be32_to_cpu(vce->len))) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
> + g_free(vce);
> + return -1;
> + }
> +
> + vce_offset += be32_to_cpu(vce->len);
> + vcb->out_len += be32_to_cpu(vce->len);
> + remaining_space -= be32_to_cpu(vce->len);
> + vcb->stored_ct++;
> +
> + g_free(vce);
> + }
> + vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
> +
> +out:
> + vcb->out_len = cpu_to_be32(vcb->out_len);
> + /*
> + * Write VCB header
> + * All VCEs have been populated with the latest information
> + * and write VCB header last.
> + */
> + if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
> + s390_cpu_virt_mem_handle_exc(cpu, ra);
> + return -1;
> + }
> +
> + return DIAG_320_RC_OK;
> +}
> +
> QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
> "size of VCStorageSizeBlock is wrong");
> +QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != VCB_HEADER_LEN, "size of VCBlock is wrong");
> +QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != VCE_HEADER_LEN, "size of VCEntry is wrong");
>
> void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> {
> @@ -259,7 +561,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> * for now.
> */
> uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
> - DIAG_320_ISM_QUERY_VCSI);
> + DIAG_320_ISM_QUERY_VCSI |
> + DIAG_320_ISM_STORE_VC);
>
> if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
> s390_cpu_virt_mem_handle_exc(cpu, ra);
> @@ -285,6 +588,13 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> }
> env->regs[r1 + 1] = rc;
> break;
> + case DIAG_320_SUBC_STORE_VC:
> + rc = handle_diag320_store_vc(cpu, addr, r1, ra, qcs);
> + if (rc == -1) {
> + return;
> + }
> + env->regs[r1 + 1] = rc;
> + break;
> default:
> env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
> break;
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 09/28] s390x/diag: Implement DIAG 320 subcode 2
2025-09-24 21:53 ` Farhan Ali
@ 2025-09-26 13:42 ` Zhuoying Cai
0 siblings, 0 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-26 13:42 UTC (permalink / raw)
To: Farhan Ali, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/24/25 5:53 PM, Farhan Ali wrote:
>
> On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
>> DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
>> certificate store. Only X509 certificates in DER format and SHA-256 hash
>> type are recognized.
>>
>> The subcode value is denoted by setting the second-left-most bit
>> of an 8-byte field.
>>
>> The Verification Certificate Block (VCB) contains the output data
>> when the operation completes successfully. It includes a common
>> header followed by zero or more Verification Certificate Entries (VCEs),
>> depending on the VCB input length and the VC range (from the first VC
>> index to the last VC index) in the certificate store.
>>
>> Each VCE contains information about a certificate retrieved from
>> the S390IPLCertificateStore, such as the certificate name, key type,
>> key ID length, hash length, and the raw certificate data.
>> The key ID and hash are extracted from the raw certificate by the crypto API.
>>
>> Note: SHA2-256 VC hash type is required for retrieving the hash
>> (fingerprint) of the certificate.
>>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>> ---
>> docs/specs/s390x-secure-ipl.rst | 13 ++
>> include/hw/s390x/ipl/diag320.h | 49 +++++
>> target/s390x/diag.c | 312 +++++++++++++++++++++++++++++++-
>> 3 files changed, 373 insertions(+), 1 deletion(-)
>>
>> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
>> index 4217f19c84..e28f0b40d7 100644
>> --- a/docs/specs/s390x-secure-ipl.rst
>> +++ b/docs/specs/s390x-secure-ipl.rst
>> @@ -35,3 +35,16 @@ Subcode 1 - query verification certificate storage information
>>
>> The output is returned in the verification-certificate-storage-size block (VCSSB).
>> A VCSSB length of 4 indicates that no certificates are available in the CS.
>> +
>> +Subcode 2 - store verification certificates
>> + Provides VCs that are in the certificate store.
>> +
>> + The output is provided in a VCB, which includes a common header followed by zero
>> + or more verification-certificate entries (VCEs).
>> +
>> + The 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.
>> diff --git a/include/hw/s390x/ipl/diag320.h b/include/hw/s390x/ipl/diag320.h
>> index 6e4779c699..2af14b9f01 100644
>> --- a/include/hw/s390x/ipl/diag320.h
>> +++ b/include/hw/s390x/ipl/diag320.h
>> @@ -12,19 +12,30 @@
>>
>> #define DIAG_320_SUBC_QUERY_ISM 0
>> #define DIAG_320_SUBC_QUERY_VCSI 1
>> +#define DIAG_320_SUBC_STORE_VC 2
>>
>> #define DIAG_320_RC_OK 0x0001
>> #define DIAG_320_RC_NOT_SUPPORTED 0x0102
>> #define DIAG_320_RC_INVAL_VCSSB_LEN 0x0202
>> +#define DIAG_320_RC_INVAL_VCB_LEN 0x0204
>> +#define DIAG_320_RC_BAD_RANGE 0x0302
>>
>> #define DIAG_320_ISM_QUERY_SUBCODES 0x80000000
>> #define DIAG_320_ISM_QUERY_VCSI 0x40000000
>> +#define DIAG_320_ISM_STORE_VC 0x20000000
>>
>> #define VCSSB_NO_VC 4
>> #define VCSSB_MIN_LEN 128
>> #define VCE_HEADER_LEN 128
>> +#define VCE_INVALID_LEN 72
>> #define VCB_HEADER_LEN 64
>>
>> +#define DIAG_320_VCE_FLAGS_VALID 0x80
>> +#define DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING 0
>> +#define DIAG_320_VCE_KEYTYPE_ECDSA_P521 1
>> +#define DIAG_320_VCE_FORMAT_X509_DER 1
>> +#define DIAG_320_VCE_HASHTYPE_SHA2_256 1
>> +
>> struct VCStorageSizeBlock {
>> uint32_t length;
>> uint8_t reserved0[3];
>> @@ -39,4 +50,42 @@ struct VCStorageSizeBlock {
>> };
>> typedef struct VCStorageSizeBlock VCStorageSizeBlock;
>>
>> +struct VCBlock {
>> + uint32_t in_len;
>> + uint32_t reserved0;
>> + uint16_t first_vc_index;
>> + uint16_t last_vc_index;
>> + uint32_t reserved1[5];
>> + uint32_t out_len;
>> + uint8_t reserved2[3];
>> + uint8_t version;
>> + uint16_t stored_ct;
>> + uint16_t remain_ct;
>> + uint32_t reserved3[5];
>> + uint8_t vce_buf[];
>> +};
>> +typedef struct VCBlock VCBlock;
>> +
>> +struct VCEntry {
>> + uint32_t len;
>> + uint8_t flags;
>> + uint8_t key_type;
>> + uint16_t cert_idx;
>> + uint32_t name[16];
>> + uint8_t format;
>> + uint8_t reserved0;
>> + uint16_t keyid_len;
>> + uint8_t reserved1;
>> + uint8_t hash_type;
>> + uint16_t hash_len;
>> + uint32_t reserved2;
>> + uint32_t cert_len;
>> + uint32_t reserved3[2];
>> + uint16_t hash_offset;
>> + uint16_t cert_offset;
>> + uint32_t reserved4[7];
>> + uint8_t cert_buf[];
>> +};
>> +typedef struct VCEntry VCEntry;
>> +
>> #endif
>> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
>> index 4e6de483b8..d5f6c54df3 100644
>> --- a/target/s390x/diag.c
>> +++ b/target/s390x/diag.c
>> @@ -17,6 +17,7 @@
>> #include "s390x-internal.h"
>> #include "hw/watchdog/wdt_diag288.h"
>> #include "system/cpus.h"
>> +#include "hw/s390x/cert-store.h"
>> #include "hw/s390x/ipl.h"
>> #include "hw/s390x/ipl/diag320.h"
>> #include "hw/s390x/s390-virtio-ccw.h"
>> @@ -24,6 +25,7 @@
>> #include "kvm/kvm_s390x.h"
>> #include "target/s390x/kvm/pv.h"
>> #include "qemu/error-report.h"
>> +#include "crypto/x509-utils.h"
>>
>>
>> int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3)
>> @@ -225,8 +227,308 @@ static int handle_diag320_query_vcsi(S390CPU *cpu, uint64_t addr, uint64_t r1,
>> return DIAG_320_RC_OK;
>> }
>>
>> +static bool is_cert_valid(S390IPLCertificate cert)
>> +{
>> + int rc;
>> + Error *err = NULL;
>> +
>> + rc = qcrypto_x509_check_cert_times(cert.raw, cert.size, &err);
>> + if (rc != 0) {
>> + error_report_err(err);
>> + return false;
>> + }
>> +
>> + return true;
>> +}
>> +
>> +static void handle_key_id(VCEntry *vce, S390IPLCertificate cert)
>> +{
>> + int rc;
>> + g_autofree unsigned char *key_id_data = NULL;
>> + size_t key_id_len;
>> + Error *err = NULL;
>> +
>> + key_id_len = CERT_KEY_ID_LEN;
>> + /* key id and key id len */
>> + rc = qcrypto_x509_get_cert_key_id(cert.raw, cert.size,
>> + QCRYPTO_HASH_ALGO_SHA256,
>> + &key_id_data, &key_id_len, &err);
>> + if (rc < 0) {
>> + error_report_err(err);
>> + return;
>> + }
>> + vce->keyid_len = cpu_to_be16(key_id_len);
>> +
>> + memcpy(vce->cert_buf, key_id_data, key_id_len);
>> +}
>> +
>> +static int handle_hash(VCEntry *vce, S390IPLCertificate cert, uint16_t keyid_field_len)
>> +{
>> + int rc;
>> + uint16_t hash_offset;
>> + g_autofree void *hash_data = NULL;
>> + size_t hash_len;
>> + Error *err = NULL;
>> +
>> + hash_len = CERT_HASH_LEN;
>> + /* hash and hash len */
>> + hash_data = g_malloc0(hash_len);
>> + rc = qcrypto_get_x509_cert_fingerprint(cert.raw, cert.size,
>> + QCRYPTO_HASH_ALGO_SHA256,
>> + hash_data, &hash_len, &err);
>> + if (rc < 0) {
>> + error_report_err(err);
>> + return -1;
>> + }
>> + vce->hash_len = cpu_to_be16(hash_len);
>> +
>> + /* hash type */
>> + vce->hash_type = DIAG_320_VCE_HASHTYPE_SHA2_256;
>> +
>> + hash_offset = VCE_HEADER_LEN + keyid_field_len;
>> + vce->hash_offset = cpu_to_be16(hash_offset);
>> +
>> + memcpy((uint8_t *)vce + hash_offset, hash_data, hash_len);
>
> Do we need any check here to make sure we are not going out of the
> bounds of vce buffer?
>
>
>> +
>> + return 0;
>> +}
>> +
>> +static int handle_cert(VCEntry *vce, S390IPLCertificate cert, uint16_t hash_field_len)
>> +{
>> + int rc;
>> + uint16_t cert_offset;
>> + g_autofree uint8_t *cert_der = NULL;
>> + Error *err = NULL;
>> +
>> + /* certificate in DER format */
>> + rc = qcrypto_x509_convert_cert_der(cert.raw, cert.size,
>> + &cert_der, &cert.der_size, &err);
>> + if (rc < 0) {
>> + error_report_err(err);
>> + return -1;
>> + }
>> + vce->format = DIAG_320_VCE_FORMAT_X509_DER;
>> + vce->cert_len = cpu_to_be32(cert.der_size);
>> + cert_offset = be16_to_cpu(vce->hash_offset) + hash_field_len;
>> + vce->cert_offset = cpu_to_be16(cert_offset);
>> +
>> + memcpy((uint8_t *)vce + cert_offset, cert_der, cert.der_size);
>
> Similar to above, do we need any check here to make sure we are not
> going out of the bounds of vce buffer?
>
>
>> +
>> + return 0;
>> +}
>> +
>> +static int get_key_type(S390IPLCertificate cert)
>> +{
>> + int algo;
>> + int rc;
>> + Error *err = NULL;
>> +
>> + /* public key algorithm */
>> + algo = qcrypto_x509_get_pk_algorithm(cert.raw, cert.size, &err);
>> + if (algo < 0) {
>> + error_report_err(err);
>> + return -1;
>> + }
>> +
>> + if (algo == QCRYPTO_PK_ALGO_ECDSA) {
>> + rc = qcrypto_x509_is_ecc_curve_p521(cert.raw, cert.size, &err);
>> + if (rc == -1) {
>> + error_report_err(err);
>> + return -1;
>> + }
>> +
>> + return (rc == 1) ? DIAG_320_VCE_KEYTYPE_ECDSA_P521 :
>> + DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
>> + }
>> +
>> + return DIAG_320_VCE_KEYTYPE_SELF_DESCRIBING;
>> +}
>> +
>> +static int build_vce_header(VCEntry *vce, S390IPLCertificate cert, int idx)
>> +{
>> + int key_type;
>> +
>> + vce->len = cpu_to_be32(VCE_HEADER_LEN);
>> + vce->cert_idx = cpu_to_be16(idx + 1);
>> + strncpy((char *)vce->name, (char *)cert.vc_name, VC_NAME_LEN_BYTES);
>> +
>> + key_type = get_key_type(cert);
>> + if (key_type == -1) {
>> + return -1;
>> + }
>> + vce->key_type = key_type;
>> +
>> + return 0;
>> +}
>> +
>> +static int build_vce_data(VCEntry *vce, S390IPLCertificate cert)
>> +{
>> + uint16_t keyid_field_len;
>> + uint16_t hash_field_len;
>> + uint32_t cert_field_len;
>> + int rc;
>> +
>> + handle_key_id(vce, cert);
>> + /* vce key id field length - can be 0 if failed to retrieve */
>> + keyid_field_len = ROUND_UP(be16_to_cpu(vce->keyid_len), 4);
>> +
>> + rc = handle_hash(vce, cert, keyid_field_len);
>> + if (rc) {
>> + return -1;
>> + }
>> + hash_field_len = ROUND_UP(be16_to_cpu(vce->hash_len), 4);
>> +
>> + rc = handle_cert(vce, cert, hash_field_len);
>> + if (rc || !is_cert_valid(cert)) {
>> + return -1;
>> + }
>> + /* vce certificate field length */
>> + cert_field_len = ROUND_UP(be32_to_cpu(vce->cert_len), 4);
>> +
>> + /* The certificate is valid and VCE contains the certificate */
>> + vce->flags |= DIAG_320_VCE_FLAGS_VALID;
>> +
>> + /* Update vce length to reflect the acutal size used by vce */
>> + vce->len += cpu_to_be32(keyid_field_len + hash_field_len + cert_field_len);
>
> Should we validate the final vce->len calculated is <= vce_len?
>
>
>> +
>> + return 0;
>> +}
>> +
>> +static VCEntry *diag_320_build_vce(S390IPLCertificate cert, uint32_t vce_len, int idx)
>> +{
>> + g_autofree VCEntry *vce = NULL;
>> + int rc;
>> +
>> + /*
>> + * Construct VCE
>> + * Allocate enough memory for all certificate data (key id, hash and certificate).
>> + * Unused area following the VCE field contains zeros.
>> + */
>> + vce = g_malloc0(vce_len);
>> + rc = build_vce_header(vce, cert, idx);
>> + if (rc) {
>> + vce->len = cpu_to_be32(VCE_INVALID_LEN);
>> + goto out;
>> + }
>> + vce->len = cpu_to_be32(VCE_HEADER_LEN);
>
> This is redundant as we already set vce->len to VCE_HEADER_LEN on
> successful call to build_vce_header. I think rather vce->len should be
> set to vce_len to make bounds checking easier in the helper functions.
>
Thanks for the feedback and for spotting the redundant code.
I'll add bounds checking in the next version.
>> +
>> + rc = build_vce_data(vce, cert);
>> + if (rc) {
>> + vce->len = cpu_to_be32(VCE_INVALID_LEN);
>> + }
>> +
>> +out:
>> + return g_steal_pointer(&vce);
>> +}
>> +
>> +static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, uintptr_t ra,
>> + S390IPLCertificateStore *qcs)
>> +{
>> + g_autofree VCBlock *vcb = NULL;
>> + size_t vce_offset;
>> + size_t remaining_space;
>> + uint32_t vce_len;
>> + uint16_t first_vc_index;
>> + uint16_t last_vc_index;
>> + uint32_t in_len;
>> +
>> + vcb = g_new0(VCBlock, 1);
>> + if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
>> + s390_cpu_virt_mem_handle_exc(cpu, ra);
>> + return -1;
>> + }
>> +
>> + in_len = be32_to_cpu(vcb->in_len);
>> + first_vc_index = be16_to_cpu(vcb->first_vc_index);
>> + last_vc_index = be16_to_cpu(vcb->last_vc_index);
>> +
>> + if (in_len % TARGET_PAGE_SIZE != 0) {
>> + return DIAG_320_RC_INVAL_VCB_LEN;
>> + }
>> +
>> + if (first_vc_index > last_vc_index) {
>> + return DIAG_320_RC_BAD_RANGE;
>> + }
>> +
>> + vcb->out_len = VCB_HEADER_LEN;
>> +
>> + if (first_vc_index == 0) {
>> + /*
>> + * Zero is a valid index for the first and last VC index.
>> + * Zero index results in the VCB header and zero certificates returned.
>> + */
>> + if (last_vc_index == 0) {
>> + goto out;
>> + }
>> +
>> + /* DIAG320 certificate store remains a one origin for cert entries */
>> + vcb->first_vc_index = 1;
>> + first_vc_index = 1;
>> + }
>> +
>> + vce_offset = VCB_HEADER_LEN;
>> + 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 cert = qcs->certs[i];
>> + /*
>> + * Each VCE is word aligned.
>> + * Each variable length field within the VCE is also word aligned.
>> + */
>> + vce_len = VCE_HEADER_LEN +
>> + ROUND_UP(CERT_KEY_ID_LEN, 4) +
>> + ROUND_UP(CERT_HASH_LEN, 4) +
>> + ROUND_UP(cert.der_size, 4);
>> +
>> + /*
>> + * If there is no more space to store the cert,
>> + * set the remaining verification cert count and
>> + * break early.
>> + */
>> + if (remaining_space < vce_len) {
>> + vcb->remain_ct = cpu_to_be16(last_vc_index - i);
>> + break;
>> + }
>> +
>> + vce = diag_320_build_vce(cert, vce_len, i);
>> +
>> + /* Write VCE */
>> + if (s390_cpu_virt_mem_write(cpu, addr + vce_offset, r1,
>> + vce, be32_to_cpu(vce->len))) {
>> + s390_cpu_virt_mem_handle_exc(cpu, ra);
>> + g_free(vce);
>> + return -1;
>> + }
>> +
>> + vce_offset += be32_to_cpu(vce->len);
>> + vcb->out_len += be32_to_cpu(vce->len);
>> + remaining_space -= be32_to_cpu(vce->len);
>> + vcb->stored_ct++;
>> +
>> + g_free(vce);
>> + }
>> + vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
>> +
>> +out:
>> + vcb->out_len = cpu_to_be32(vcb->out_len);
>> + /*
>> + * Write VCB header
>> + * All VCEs have been populated with the latest information
>> + * and write VCB header last.
>> + */
>> + if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
>> + s390_cpu_virt_mem_handle_exc(cpu, ra);
>> + return -1;
>> + }
>> +
>> + return DIAG_320_RC_OK;
>> +}
>> +
>> QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
>> "size of VCStorageSizeBlock is wrong");
>> +QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != VCB_HEADER_LEN, "size of VCBlock is wrong");
>> +QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != VCE_HEADER_LEN, "size of VCEntry is wrong");
>>
>> void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>> {
>> @@ -259,7 +561,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>> * for now.
>> */
>> uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
>> - DIAG_320_ISM_QUERY_VCSI);
>> + DIAG_320_ISM_QUERY_VCSI |
>> + DIAG_320_ISM_STORE_VC);
>>
>> if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
>> s390_cpu_virt_mem_handle_exc(cpu, ra);
>> @@ -285,6 +588,13 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
>> }
>> env->regs[r1 + 1] = rc;
>> break;
>> + case DIAG_320_SUBC_STORE_VC:
>> + rc = handle_diag320_store_vc(cpu, addr, r1, ra, qcs);
>> + if (rc == -1) {
>> + return;
>> + }
>> + env->regs[r1 + 1] = rc;
>> + break;
>> default:
>> env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
>> break;
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (8 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 09/28] s390x/diag: Implement " Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-25 20:50 ` Farhan Ali
2025-10-07 9:47 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
` (18 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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>
---
docs/specs/s390x-secure-ipl.rst | 18 ++++++++++++++++++
include/hw/s390x/ipl/diag508.h | 15 +++++++++++++++
target/s390x/diag.c | 27 +++++++++++++++++++++++++++
target/s390x/kvm/kvm.c | 14 ++++++++++++++
target/s390x/s390x-internal.h | 2 ++
target/s390x/tcg/misc_helper.c | 7 +++++++
6 files changed, 83 insertions(+)
create mode 100644 include/hw/s390x/ipl/diag508.h
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index e28f0b40d7..0919425e9a 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -48,3 +48,21 @@ Subcode 2 - store verification certificates
storage specified in the VCB input length field.
VCE contains various information of a VC from the CS.
+
+
+Secure IPL Data Structures, Facilities, and Functions
+=====================================================
+
+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.
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 d5f6c54df3..ee64257dbc 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -20,6 +20,7 @@
#include "hw/s390x/cert-store.h"
#include "hw/s390x/ipl.h"
#include "hw/s390x/ipl/diag320.h"
+#include "hw/s390x/ipl/diag508.h"
#include "hw/s390x/s390-virtio-ccw.h"
#include "system/kvm.h"
#include "kvm/kvm_s390x.h"
@@ -600,3 +601,29 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
break;
}
}
+
+void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
+{
+ uint64_t subcode = env->regs[r3];
+ int rc;
+
+ if (env->psw.mask & PSW_MASK_PSTATE) {
+ s390_program_interrupt(env, PGM_PRIVILEGED, ra);
+ return;
+ }
+
+ if ((subcode & ~0x0ffffULL) || (r1 & 1)) {
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+
+ switch (subcode) {
+ case DIAG_508_SUBC_QUERY_SUBC:
+ rc = 0;
+ break;
+ default:
+ s390_program_interrupt(env, PGM_SPECIFICATION, ra);
+ return;
+ }
+ env->regs[r1 + 1] = rc;
+}
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 5510fc2fc5..ae6cd3d506 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -101,6 +101,7 @@
#define DIAG_CERT_STORE 0x320
#define DIAG_KVM_HYPERCALL 0x500
#define DIAG_KVM_BREAKPOINT 0x501
+#define DIAG_SECURE_IPL 0x508
#define ICPT_INSTRUCTION 0x04
#define ICPT_PROGRAM 0x08
@@ -1571,6 +1572,16 @@ static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
}
+static void kvm_handle_diag_508(S390CPU *cpu, struct kvm_run *run)
+{
+ uint64_t r1, r3;
+
+ r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
+ r3 = run->s390_sieic.ipa & 0x000f;
+
+ handle_diag_508(&cpu->env, r1, r3, RA_IGNORED);
+}
+
#define DIAG_KVM_CODE_MASK 0x000000000000ffff
static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
@@ -1604,6 +1615,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
case DIAG_CERT_STORE:
kvm_handle_diag_320(cpu, run);
break;
+ case DIAG_SECURE_IPL:
+ kvm_handle_diag_508(cpu, run);
+ break;
default:
trace_kvm_insn_diag(func_code);
kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index ecff2d07a1..7cca8a67de 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -393,6 +393,8 @@ void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
uintptr_t ra);
void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
uintptr_t ra);
+void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3,
+ uintptr_t ra);
/* translate.c */
diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
index 412c34ed93..ddbf495118 100644
--- a/target/s390x/tcg/misc_helper.c
+++ b/target/s390x/tcg/misc_helper.c
@@ -149,6 +149,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
bql_unlock();
r = 0;
break;
+ case 0x508:
+ /* secure ipl operations */
+ bql_lock();
+ handle_diag_508(env, r1, r3, GETPC());
+ bql_unlock();
+ r = 0;
+ break;
default:
r = -1;
break;
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations
2025-09-17 23:21 ` [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
@ 2025-09-25 20:50 ` Farhan Ali
2025-10-07 9:47 ` Thomas Huth
1 sibling, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-25 20:50 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> 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>
> ---
> docs/specs/s390x-secure-ipl.rst | 18 ++++++++++++++++++
> include/hw/s390x/ipl/diag508.h | 15 +++++++++++++++
> target/s390x/diag.c | 27 +++++++++++++++++++++++++++
> target/s390x/kvm/kvm.c | 14 ++++++++++++++
> target/s390x/s390x-internal.h | 2 ++
> target/s390x/tcg/misc_helper.c | 7 +++++++
> 6 files changed, 83 insertions(+)
> create mode 100644 include/hw/s390x/ipl/diag508.h
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index e28f0b40d7..0919425e9a 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -48,3 +48,21 @@ Subcode 2 - store verification certificates
> storage specified in the VCB input length field.
>
> VCE contains various information of a VC from the CS.
> +
> +
> +Secure IPL Data Structures, Facilities, and Functions
> +=====================================================
> +
> +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.
> 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 d5f6c54df3..ee64257dbc 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -20,6 +20,7 @@
> #include "hw/s390x/cert-store.h"
> #include "hw/s390x/ipl.h"
> #include "hw/s390x/ipl/diag320.h"
> +#include "hw/s390x/ipl/diag508.h"
> #include "hw/s390x/s390-virtio-ccw.h"
> #include "system/kvm.h"
> #include "kvm/kvm_s390x.h"
> @@ -600,3 +601,29 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> break;
> }
> }
> +
> +void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> +{
> + uint64_t subcode = env->regs[r3];
> + int rc;
> +
> + if (env->psw.mask & PSW_MASK_PSTATE) {
> + s390_program_interrupt(env, PGM_PRIVILEGED, ra);
> + return;
> + }
> +
> + if ((subcode & ~0x0ffffULL) || (r1 & 1)) {
> + s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> + return;
> + }
> +
> + switch (subcode) {
> + case DIAG_508_SUBC_QUERY_SUBC:
> + rc = 0;
> + break;
> + default:
> + s390_program_interrupt(env, PGM_SPECIFICATION, ra);
> + return;
> + }
> + env->regs[r1 + 1] = rc;
> +}
> diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
> index 5510fc2fc5..ae6cd3d506 100644
> --- a/target/s390x/kvm/kvm.c
> +++ b/target/s390x/kvm/kvm.c
> @@ -101,6 +101,7 @@
> #define DIAG_CERT_STORE 0x320
> #define DIAG_KVM_HYPERCALL 0x500
> #define DIAG_KVM_BREAKPOINT 0x501
> +#define DIAG_SECURE_IPL 0x508
>
> #define ICPT_INSTRUCTION 0x04
> #define ICPT_PROGRAM 0x08
> @@ -1571,6 +1572,16 @@ static void kvm_handle_diag_320(S390CPU *cpu, struct kvm_run *run)
> handle_diag_320(&cpu->env, r1, r3, RA_IGNORED);
> }
>
> +static void kvm_handle_diag_508(S390CPU *cpu, struct kvm_run *run)
> +{
> + uint64_t r1, r3;
> +
> + r1 = (run->s390_sieic.ipa & 0x00f0) >> 4;
> + r3 = run->s390_sieic.ipa & 0x000f;
> +
> + handle_diag_508(&cpu->env, r1, r3, RA_IGNORED);
> +}
> +
> #define DIAG_KVM_CODE_MASK 0x000000000000ffff
>
> static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
> @@ -1604,6 +1615,9 @@ static int handle_diag(S390CPU *cpu, struct kvm_run *run, uint32_t ipb)
> case DIAG_CERT_STORE:
> kvm_handle_diag_320(cpu, run);
> break;
> + case DIAG_SECURE_IPL:
> + kvm_handle_diag_508(cpu, run);
> + break;
> default:
> trace_kvm_insn_diag(func_code);
> kvm_s390_program_interrupt(cpu, PGM_SPECIFICATION);
> diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
> index ecff2d07a1..7cca8a67de 100644
> --- a/target/s390x/s390x-internal.h
> +++ b/target/s390x/s390x-internal.h
> @@ -393,6 +393,8 @@ void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3,
> uintptr_t ra);
> void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3,
> uintptr_t ra);
> +void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3,
> + uintptr_t ra);
>
>
> /* translate.c */
> diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
> index 412c34ed93..ddbf495118 100644
> --- a/target/s390x/tcg/misc_helper.c
> +++ b/target/s390x/tcg/misc_helper.c
> @@ -149,6 +149,13 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
> bql_unlock();
> r = 0;
> break;
> + case 0x508:
> + /* secure ipl operations */
> + bql_lock();
> + handle_diag_508(env, r1, r3, GETPC());
> + bql_unlock();
> + r = 0;
> + break;
> default:
> r = -1;
> break;
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations
2025-09-17 23:21 ` [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
2025-09-25 20:50 ` Farhan Ali
@ 2025-10-07 9:47 ` Thomas Huth
2025-10-07 19:46 ` Collin Walling
1 sibling, 1 reply; 89+ messages in thread
From: Thomas Huth @ 2025-10-07 9:47 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> 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>
> ---
> docs/specs/s390x-secure-ipl.rst | 18 ++++++++++++++++++
> include/hw/s390x/ipl/diag508.h | 15 +++++++++++++++
> target/s390x/diag.c | 27 +++++++++++++++++++++++++++
> target/s390x/kvm/kvm.c | 14 ++++++++++++++
> target/s390x/s390x-internal.h | 2 ++
> target/s390x/tcg/misc_helper.c | 7 +++++++
> 6 files changed, 83 insertions(+)
> create mode 100644 include/hw/s390x/ipl/diag508.h
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index e28f0b40d7..0919425e9a 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -48,3 +48,21 @@ Subcode 2 - store verification certificates
> storage specified in the VCB input length field.
>
> VCE contains various information of a VC from the CS.
> +
> +
> +Secure IPL Data Structures, Facilities, and Functions
> +=====================================================
> +
> +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.
Technically, this works also without KVM, right? In that case, I'd maybe
avoid the term "KVM" here, and just talk about "IPL extensions" and "guest",
to avoid the confusion about whether this feature can be used with TCG, too,
or not.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations
2025-10-07 9:47 ` Thomas Huth
@ 2025-10-07 19:46 ` Collin Walling
0 siblings, 0 replies; 89+ messages in thread
From: Collin Walling @ 2025-10-07 19:46 UTC (permalink / raw)
To: Thomas Huth, Zhuoying Cai, berrange, richard.henderson, david,
jrossi, qemu-s390x, qemu-devel
Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii, eblake,
armbru, alifm
On 10/7/25 05:47, Thomas Huth wrote:
> On 18/09/2025 01.21, Zhuoying Cai wrote:
>> 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>
>> ---
>> docs/specs/s390x-secure-ipl.rst | 18 ++++++++++++++++++
>> include/hw/s390x/ipl/diag508.h | 15 +++++++++++++++
>> target/s390x/diag.c | 27 +++++++++++++++++++++++++++
>> target/s390x/kvm/kvm.c | 14 ++++++++++++++
>> target/s390x/s390x-internal.h | 2 ++
>> target/s390x/tcg/misc_helper.c | 7 +++++++
>> 6 files changed, 83 insertions(+)
>> create mode 100644 include/hw/s390x/ipl/diag508.h
>>
>> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
>> index e28f0b40d7..0919425e9a 100644
>> --- a/docs/specs/s390x-secure-ipl.rst
>> +++ b/docs/specs/s390x-secure-ipl.rst
>> @@ -48,3 +48,21 @@ Subcode 2 - store verification certificates
>> storage specified in the VCB input length field.
>>
>> VCE contains various information of a VC from the CS.
>> +
>> +
>> +Secure IPL Data Structures, Facilities, and Functions
>> +=====================================================
>> +
>> +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.
>
> Technically, this works also without KVM, right? In that case, I'd maybe
> avoid the term "KVM" here, and just talk about "IPL extensions" and "guest",
> to avoid the confusion about whether this feature can be used with TCG, too,
> or not.
>
> Thomas
>
>
Fair enough. None of the secure IPL code leverages KVM, so we can
reword the document a bit.
--
Regards,
Collin
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (9 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 10/28] s390x/diag: Introduce DIAG 508 for secure IPL operations Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-10-07 9:58 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
` (17 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Introduce helper functions to support signature verification required by
DIAG 508 subcode 1:
qcrypto_pkcs7_convert_sig_pem() – converts a signature from DER to PEM format
qcrypto_x509_verify_sig() – verifies the provided data against the given signature
These functions enable basic signature verification support.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
crypto/x509-utils.c | 109 ++++++++++++++++++++++++++++++++++++
include/crypto/x509-utils.h | 39 +++++++++++++
2 files changed, 148 insertions(+)
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index 763eccb190..8f3c895d7c 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -16,6 +16,7 @@
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include <gnutls/x509.h>
+#include <gnutls/pkcs7.h>
static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
[QCRYPTO_HASH_ALGO_MD5] = GNUTLS_DIG_MD5,
@@ -342,6 +343,97 @@ int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
return 0;
}
+int qcrypto_pkcs7_convert_sig_pem(uint8_t *sig, size_t sig_size,
+ uint8_t **result, size_t *resultlen,
+ Error **errp)
+{
+ int ret = -1;
+ int rc;
+ gnutls_pkcs7_t signature;
+ gnutls_datum_t sig_datum_der = {.data = sig, .size = sig_size};
+ gnutls_datum_t sig_datum_pem = {.data = NULL, .size = 0};
+
+ rc = gnutls_pkcs7_init(&signature);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initalize pkcs7 data: %s", gnutls_strerror(rc));
+ return ret;
+ }
+
+ rc = gnutls_pkcs7_import(signature, &sig_datum_der, GNUTLS_X509_FMT_DER);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import signature: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_pkcs7_export2(signature, GNUTLS_X509_FMT_PEM, &sig_datum_pem);
+ if (rc != 0) {
+ error_setg(errp, "Failed to convert signature to PEM format: %s",
+ gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ *result = g_new0(uint8_t, sig_datum_pem.size);
+ *resultlen = sig_datum_pem.size;
+ memcpy(*result, sig_datum_pem.data, sig_datum_pem.size);
+
+ ret = 0;
+
+cleanup:
+ gnutls_pkcs7_deinit(signature);
+ gnutls_free(sig_datum_pem.data);
+ return ret;
+}
+
+int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
+ uint8_t *comp, size_t comp_size,
+ uint8_t *sig, size_t sig_size, Error **errp)
+{
+ int rc;
+ int ret = -1;
+ gnutls_x509_crt_t crt = NULL;
+ gnutls_pkcs7_t signature = NULL;
+ gnutls_datum_t cert_datum = {.data = cert, .size = cert_size};
+ gnutls_datum_t data_datum = {.data = comp, .size = comp_size};
+ gnutls_datum_t sig_datum = {.data = sig, .size = sig_size};
+
+ rc = gnutls_x509_crt_init(&crt);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_x509_crt_import(crt, &cert_datum, GNUTLS_X509_FMT_PEM);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_pkcs7_init(&signature);
+ if (rc < 0) {
+ error_setg(errp, "Failed to initalize pkcs7 data: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_pkcs7_import(signature, &sig_datum , GNUTLS_X509_FMT_PEM);
+ if (rc != 0) {
+ error_setg(errp, "Failed to import signature: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
+ if (rc != 0) {
+ error_setg(errp, "Failed to verify signature: %s", gnutls_strerror(rc));
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ gnutls_x509_crt_deinit(crt);
+ gnutls_pkcs7_deinit(signature);
+ return ret;
+}
+
#else /* ! CONFIG_GNUTLS */
int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size,
@@ -391,4 +483,21 @@ int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
return -1;
}
+int qcrypto_pkcs7_convert_sig_pem(uint8_t *sig, size_t sig_size,
+ uint8_t **result,
+ size_t *resultlen,
+ Error **errp)
+{
+ error_setg(errp, "GNUTLS is required to export pkcs7 signature");
+ return -1;
+}
+
+int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
+ uint8_t *comp, size_t comp_size,
+ uint8_t *sig, size_t sig_size, Error **errp)
+{
+ error_setg(errp, "GNUTLS is required for signature-verification support");
+ return -1;
+}
+
#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h
index 6fc8d982b7..43a3dbb7a9 100644
--- a/include/crypto/x509-utils.h
+++ b/include/crypto/x509-utils.h
@@ -111,4 +111,43 @@ int qcrypto_x509_get_cert_key_id(uint8_t *cert, size_t size,
*/
int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp);
+/**
+ * qcrypto_pkcs7_convert_sig_pem
+ * @sig: pointer to the PKCS#7 signature in DER format
+ * @sig_size: size of the signature
+ * @result: output location for the allocated buffer for the signature in PEM format
+ (the function allocates memory which must be freed by the caller)
+ * @resultlen: pointer to the size of the buffer
+ (will be updated with the actual size of the PEM-encoded signature)
+ * @errp: error pointer
+ *
+ * Convert given PKCS#7 @sig from DER to PEM format.
+ *
+ * Returns: 0 if PEM-encoded signature was successfully stored in @result,
+ * -1 on error.
+ */
+int qcrypto_pkcs7_convert_sig_pem(uint8_t *sig, size_t sig_size,
+ uint8_t **result,
+ size_t *resultlen,
+ Error **errp);
+
+/**
+ * qcrypto_x509_verify_sig
+ * @cert: pointer to the raw certificate data
+ * @cert_size: size of the certificate
+ * @comp: pointer to the component to be verified
+ * @comp_size: size of the component
+ * @sig: pointer to the signature
+ * @sig_size: size of the signature
+ * @errp: error pointer
+ *
+ * Verify the provided @comp against the @sig and @cert.
+ *
+ * Returns: 0 on success,
+ * -1 on error.
+ */
+int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
+ uint8_t *comp, size_t comp_size,
+ uint8_t *sig, size_t sig_size, Error **errp);
+
#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1
2025-09-17 23:21 ` [PATCH v6 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
@ 2025-10-07 9:58 ` Thomas Huth
2025-10-07 10:10 ` Daniel P. Berrangé
0 siblings, 1 reply; 89+ messages in thread
From: Thomas Huth @ 2025-10-07 9:58 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Introduce helper functions to support signature verification required by
> DIAG 508 subcode 1:
>
> qcrypto_pkcs7_convert_sig_pem() – converts a signature from DER to PEM format
> qcrypto_x509_verify_sig() – verifies the provided data against the given signature
>
> These functions enable basic signature verification support.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> crypto/x509-utils.c | 109 ++++++++++++++++++++++++++++++++++++
> include/crypto/x509-utils.h | 39 +++++++++++++
> 2 files changed, 148 insertions(+)
>
> diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> index 763eccb190..8f3c895d7c 100644
> --- a/crypto/x509-utils.c
> +++ b/crypto/x509-utils.c
> @@ -16,6 +16,7 @@
> #include <gnutls/gnutls.h>
> #include <gnutls/crypto.h>
> #include <gnutls/x509.h>
> +#include <gnutls/pkcs7.h>
>
> static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALGO__MAX] = {
> [QCRYPTO_HASH_ALGO_MD5] = GNUTLS_DIG_MD5,
> @@ -342,6 +343,97 @@ int qcrypto_x509_is_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
> return 0;
> }
>
> +int qcrypto_pkcs7_convert_sig_pem(uint8_t *sig, size_t sig_size,
> + uint8_t **result, size_t *resultlen,
> + Error **errp)
> +{
> + int ret = -1;
> + int rc;
> + gnutls_pkcs7_t signature;
> + gnutls_datum_t sig_datum_der = {.data = sig, .size = sig_size};
> + gnutls_datum_t sig_datum_pem = {.data = NULL, .size = 0};
> +
> + rc = gnutls_pkcs7_init(&signature);
> + if (rc < 0) {
> + error_setg(errp, "Failed to initalize pkcs7 data: %s", gnutls_strerror(rc));
> + return ret;
> + }
Indentation of the "}" is off by one.
> + rc = gnutls_pkcs7_import(signature, &sig_datum_der, GNUTLS_X509_FMT_DER);
> + if (rc != 0) {
> + error_setg(errp, "Failed to import signature: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + rc = gnutls_pkcs7_export2(signature, GNUTLS_X509_FMT_PEM, &sig_datum_pem);
> + if (rc != 0) {
> + error_setg(errp, "Failed to convert signature to PEM format: %s",
> + gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + *result = g_new0(uint8_t, sig_datum_pem.size);
> + *resultlen = sig_datum_pem.size;
> + memcpy(*result, sig_datum_pem.data, sig_datum_pem.size);
I think you could replace the g_new0 + memcpy with a g_memdup2() here.
> + ret = 0;
> +
> +cleanup:
> + gnutls_pkcs7_deinit(signature);
> + gnutls_free(sig_datum_pem.data);
> + return ret;
> +}
> +
> +int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
> + uint8_t *comp, size_t comp_size,
> + uint8_t *sig, size_t sig_size, Error **errp)
> +{
> + int rc;
> + int ret = -1;
> + gnutls_x509_crt_t crt = NULL;
> + gnutls_pkcs7_t signature = NULL;
> + gnutls_datum_t cert_datum = {.data = cert, .size = cert_size};
> + gnutls_datum_t data_datum = {.data = comp, .size = comp_size};
> + gnutls_datum_t sig_datum = {.data = sig, .size = sig_size};
> +
> + rc = gnutls_x509_crt_init(&crt);
> + if (rc < 0) {
> + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> + goto cleanup;
So if you go to cleanup here, signature is still NULL ...
> + }
> +
> + rc = gnutls_x509_crt_import(crt, &cert_datum, GNUTLS_X509_FMT_PEM);
> + if (rc != 0) {
> + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + rc = gnutls_pkcs7_init(&signature);
> + if (rc < 0) {
> + error_setg(errp, "Failed to initalize pkcs7 data: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + rc = gnutls_pkcs7_import(signature, &sig_datum , GNUTLS_X509_FMT_PEM);
> + if (rc != 0) {
> + error_setg(errp, "Failed to import signature: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
> + if (rc != 0) {
> + error_setg(errp, "Failed to verify signature: %s", gnutls_strerror(rc));
> + goto cleanup;
> + }
> +
> + ret = 0;
> +
> +cleanup:
> + gnutls_x509_crt_deinit(crt);
> + gnutls_pkcs7_deinit(signature);
... did you check whether gnutls_pkcs7_deinit() is able to deal with NULL
pointers? The man-page does not mention it ... so just to be on the save
side, maybe it would be better to have to cleanup labels, and only do the
gnutls_pkcs7_deinit() if it has been initialized before?
> + return ret;
> +}
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1
2025-10-07 9:58 ` Thomas Huth
@ 2025-10-07 10:10 ` Daniel P. Berrangé
0 siblings, 0 replies; 89+ messages in thread
From: Daniel P. Berrangé @ 2025-10-07 10:10 UTC (permalink / raw)
To: Thomas Huth
Cc: Zhuoying Cai, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel, walling, jjherne, pasic, borntraeger, farman,
mjrosato, iii, eblake, armbru, alifm
On Tue, Oct 07, 2025 at 11:58:03AM +0200, Thomas Huth wrote:
> On 18/09/2025 01.21, Zhuoying Cai wrote:
> > Introduce helper functions to support signature verification required by
> > DIAG 508 subcode 1:
> >
> > qcrypto_pkcs7_convert_sig_pem() – converts a signature from DER to PEM format
> > qcrypto_x509_verify_sig() – verifies the provided data against the given signature
> >
> > These functions enable basic signature verification support.
> >
> > Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> > ---
> > crypto/x509-utils.c | 109 ++++++++++++++++++++++++++++++++++++
> > include/crypto/x509-utils.h | 39 +++++++++++++
> > 2 files changed, 148 insertions(+)
> >
> > diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
> > index 763eccb190..8f3c895d7c 100644
> > --- a/crypto/x509-utils.c
> > +++ b/crypto/x509-utils.c
> > +int qcrypto_x509_verify_sig(uint8_t *cert, size_t cert_size,
> > + uint8_t *comp, size_t comp_size,
> > + uint8_t *sig, size_t sig_size, Error **errp)
> > +{
> > + int rc;
> > + int ret = -1;
> > + gnutls_x509_crt_t crt = NULL;
> > + gnutls_pkcs7_t signature = NULL;
> > + gnutls_datum_t cert_datum = {.data = cert, .size = cert_size};
> > + gnutls_datum_t data_datum = {.data = comp, .size = comp_size};
> > + gnutls_datum_t sig_datum = {.data = sig, .size = sig_size};
> > +
> > + rc = gnutls_x509_crt_init(&crt);
> > + if (rc < 0) {
> > + error_setg(errp, "Failed to initialize certificate: %s", gnutls_strerror(rc));
> > + goto cleanup;
>
> So if you go to cleanup here, signature is still NULL ...
>
> > + }
> > +
> > + rc = gnutls_x509_crt_import(crt, &cert_datum, GNUTLS_X509_FMT_PEM);
> > + if (rc != 0) {
> > + error_setg(errp, "Failed to import certificate: %s", gnutls_strerror(rc));
> > + goto cleanup;
> > + }
> > +
> > + rc = gnutls_pkcs7_init(&signature);
> > + if (rc < 0) {
> > + error_setg(errp, "Failed to initalize pkcs7 data: %s", gnutls_strerror(rc));
> > + goto cleanup;
> > + }
> > +
> > + rc = gnutls_pkcs7_import(signature, &sig_datum , GNUTLS_X509_FMT_PEM);
> > + if (rc != 0) {
> > + error_setg(errp, "Failed to import signature: %s", gnutls_strerror(rc));
> > + goto cleanup;
> > + }
> > +
> > + rc = gnutls_pkcs7_verify_direct(signature, crt, 0, &data_datum, 0);
> > + if (rc != 0) {
> > + error_setg(errp, "Failed to verify signature: %s", gnutls_strerror(rc));
> > + goto cleanup;
> > + }
> > +
> > + ret = 0;
> > +
> > +cleanup:
> > + gnutls_x509_crt_deinit(crt);
> > + gnutls_pkcs7_deinit(signature);
>
> ... did you check whether gnutls_pkcs7_deinit() is able to deal with NULL
> pointers? The man-page does not mention it ... so just to be on the save
> side, maybe it would be better to have to cleanup labels, and only do the
> gnutls_pkcs7_deinit() if it has been initialized before?
All gnutls APIs for free'ing objects accept NULL and act as a no-op,
so we're ok.
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] 89+ messages in thread
* [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (10 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 11/28] crypto/x509-utils: Add helper functions for DIAG 508 subcode 1 Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-25 21:30 ` Farhan Ali
` (2 more replies)
2025-09-17 23:21 ` [PATCH v6 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
` (16 subsequent siblings)
28 siblings, 3 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
From: Collin Walling <walling@linux.ibm.com>
DIAG 508 subcode 1 performs signature-verification on signed components.
A signed component may be a Linux kernel image, or any other signed
binary. **Verification of initrd is not supported.**
The instruction call expects two item-pairs: an address of a device
component, an address of the analogous signature file (in PKCS#7 DER format),
and their respective lengths. All of this data should be encapsulated
within a Diag508SigVerifBlock.
The DIAG handler will read from the provided addresses
to retrieve the necessary data, parse the signature file, then
perform the signature-verification. Because there is no way to
correlate a specific certificate to a component, each certificate
in the store is tried until either verification succeeds, or all
certs have been exhausted.
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 Diag508SigVerifBlock.
The following values indicate failure:
0x0102: certificate not available
0x0202: component data is invalid
0x0302: signature is not in PKCS#7 format
0x0402: signature-verification failed
0x0502: length of Diag508SigVerifBlock is invalid
Signed-off-by: Collin Walling <walling@linux.ibm.com>
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 5 ++
include/hw/s390x/ipl/diag508.h | 23 +++++++
target/s390x/diag.c | 115 +++++++++++++++++++++++++++++++-
3 files changed, 142 insertions(+), 1 deletion(-)
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 0919425e9a..eec368d17b 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -66,3 +66,8 @@ that requires assistance from QEMU.
Subcode 0 - query installed subcodes
Returns a 64-bit mask indicating which subcodes are supported.
+
+Subcode 1 - perform signature verification
+ Perform signature-verification on a signed component, using certificates
+ from the certificate store and leveraging qcrypto libraries to perform
+ this operation.
diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
index 6281ad8299..ad401cc867 100644
--- a/include/hw/s390x/ipl/diag508.h
+++ b/include/hw/s390x/ipl/diag508.h
@@ -11,5 +11,28 @@
#define S390X_DIAG508_H
#define DIAG_508_SUBC_QUERY_SUBC 0x0000
+#define DIAG_508_SUBC_SIG_VERIF 0x8000
+
+#define DIAG_508_RC_OK 0x0001
+#define DIAG_508_RC_NO_CERTS 0x0102
+#define DIAG_508_RC_INVAL_COMP_DATA 0x0202
+#define DIAG_508_RC_INVAL_PKCS7_SIG 0x0302
+#define DIAG_508_RC_FAIL_VERIF 0x0402
+#define DIAG_508_RC_INVAL_LEN 0x0502
+
+struct Diag508SigVerifBlock {
+ uint32_t length;
+ uint8_t reserved0[3];
+ uint8_t version;
+ uint32_t reserved[2];
+ uint8_t cert_store_index;
+ uint8_t reserved1[7];
+ uint64_t cert_len;
+ uint64_t comp_len;
+ uint64_t comp_addr;
+ uint64_t sig_len;
+ uint64_t sig_addr;
+};
+typedef struct Diag508SigVerifBlock Diag508SigVerifBlock;
#endif
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index ee64257dbc..379fb8f2b4 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -602,9 +602,112 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
}
}
+static int diag_508_verify_sig(uint8_t *cert, size_t cert_size,
+ uint8_t *comp, size_t comp_size,
+ uint8_t *sig, size_t sig_size)
+{
+ g_autofree uint8_t *sig_pem = NULL;
+ size_t sig_size_pem;
+ int rc;
+
+ /*
+ * PKCS#7 signature with DER format
+ * Convert to PEM format for signature verification
+ */
+ rc = qcrypto_pkcs7_convert_sig_pem(sig, sig_size, &sig_pem, &sig_size_pem, NULL);
+ if (rc < 0) {
+ return -1;
+ }
+
+ rc = qcrypto_x509_verify_sig(cert, cert_size,
+ comp, comp_size,
+ sig_pem, sig_size_pem, NULL);
+ if (rc < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int handle_diag508_sig_verif(uint64_t addr, size_t svb_size,
+ S390IPLCertificateStore *qcs)
+{
+ int rc;
+ int verified;
+ uint32_t svb_len;
+ uint64_t comp_len, comp_addr;
+ uint64_t sig_len, sig_addr;
+ g_autofree uint8_t *svb_comp = NULL;
+ g_autofree uint8_t *svb_sig = NULL;
+ g_autofree Diag508SigVerifBlock *svb = NULL;
+
+ if (!qcs || !qcs->count) {
+ return DIAG_508_RC_NO_CERTS;
+ }
+
+ svb = g_new0(Diag508SigVerifBlock, 1);
+ cpu_physical_memory_read(addr, svb, svb_size);
+
+ svb_len = be32_to_cpu(svb->length);
+ if (svb_len != svb_size) {
+ return DIAG_508_RC_INVAL_LEN;
+ }
+
+ comp_len = be64_to_cpu(svb->comp_len);
+ comp_addr = be64_to_cpu(svb->comp_addr);
+ sig_len = be64_to_cpu(svb->sig_len);
+ sig_addr = be64_to_cpu(svb->sig_addr);
+
+ if (!comp_len || !comp_addr) {
+ return DIAG_508_RC_INVAL_COMP_DATA;
+ }
+
+ if (!sig_len || !sig_addr) {
+ return DIAG_508_RC_INVAL_PKCS7_SIG;
+ }
+
+ svb_comp = g_malloc0(comp_len);
+ cpu_physical_memory_read(comp_addr, svb_comp, comp_len);
+
+ svb_sig = g_malloc0(sig_len);
+ cpu_physical_memory_read(sig_addr, svb_sig, sig_len);
+
+ rc = DIAG_508_RC_FAIL_VERIF;
+ /*
+ * It is uncertain which certificate contains
+ * the analogous key to verify the signed data
+ *
+ * Ignore errors from signature format convertion and verification,
+ * because currently in the certificate lookup process.
+ *
+ * Any error is treated as a verification failure,
+ * and the final result (verified or not) will be reported later.
+ */
+ for (int i = 0; i < qcs->count; i++) {
+ verified = diag_508_verify_sig(qcs->certs[i].raw,
+ qcs->certs[i].size,
+ svb_comp, comp_len,
+ svb_sig, sig_len);
+ if (verified == 0) {
+ svb->cert_store_index = i;
+ svb->cert_len = cpu_to_be64(qcs->certs[i].der_size);
+ cpu_physical_memory_write(addr, svb, be32_to_cpu(svb_size));
+ rc = DIAG_508_RC_OK;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+QEMU_BUILD_BUG_MSG(sizeof(Diag508SigVerifBlock) != 64,
+ "size of Diag508SigVerifBlock is wrong");
+
void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
{
+ S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
uint64_t subcode = env->regs[r3];
+ uint64_t addr = env->regs[r1];
int rc;
if (env->psw.mask & PSW_MASK_PSTATE) {
@@ -619,7 +722,17 @@ 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(Diag508SigVerifBlock);
+
+ if (!diag_parm_addr_valid(addr, svb_size, true)) {
+ s390_program_interrupt(env, PGM_ADDRESSING, ra);
+ return;
+ }
+
+ rc = handle_diag508_sig_verif(addr, svb_size, qcs);
break;
default:
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-09-17 23:21 ` [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2025-09-25 21:30 ` Farhan Ali
2025-10-07 10:27 ` Thomas Huth
2025-10-07 20:22 ` Collin Walling
2 siblings, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-25 21:30 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/17/2025 4:21 PM, 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 DER format),
> and their respective lengths. All of this data should be encapsulated
> within a Diag508SigVerifBlock.
>
> The DIAG handler will read from the provided addresses
> to retrieve the necessary data, parse the signature file, then
> perform the signature-verification. Because there is no way to
> correlate a specific certificate to a component, each certificate
> in the store is tried until either verification succeeds, or all
> certs have been exhausted.
>
> 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 Diag508SigVerifBlock.
> The following values indicate failure:
>
> 0x0102: certificate not available
> 0x0202: component data is invalid
> 0x0302: signature is not in PKCS#7 format
> 0x0402: signature-verification failed
> 0x0502: length of Diag508SigVerifBlock is invalid
>
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 5 ++
> include/hw/s390x/ipl/diag508.h | 23 +++++++
> target/s390x/diag.c | 115 +++++++++++++++++++++++++++++++-
> 3 files changed, 142 insertions(+), 1 deletion(-)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 0919425e9a..eec368d17b 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -66,3 +66,8 @@ that requires assistance from QEMU.
>
> Subcode 0 - query installed subcodes
> Returns a 64-bit mask indicating which subcodes are supported.
> +
> +Subcode 1 - perform signature verification
> + Perform signature-verification on a signed component, using certificates
> + from the certificate store and leveraging qcrypto libraries to perform
> + this operation.
> diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
> index 6281ad8299..ad401cc867 100644
> --- a/include/hw/s390x/ipl/diag508.h
> +++ b/include/hw/s390x/ipl/diag508.h
> @@ -11,5 +11,28 @@
> #define S390X_DIAG508_H
>
> #define DIAG_508_SUBC_QUERY_SUBC 0x0000
> +#define DIAG_508_SUBC_SIG_VERIF 0x8000
> +
> +#define DIAG_508_RC_OK 0x0001
> +#define DIAG_508_RC_NO_CERTS 0x0102
> +#define DIAG_508_RC_INVAL_COMP_DATA 0x0202
> +#define DIAG_508_RC_INVAL_PKCS7_SIG 0x0302
> +#define DIAG_508_RC_FAIL_VERIF 0x0402
> +#define DIAG_508_RC_INVAL_LEN 0x0502
> +
> +struct Diag508SigVerifBlock {
> + uint32_t length;
> + uint8_t reserved0[3];
> + uint8_t version;
> + uint32_t reserved[2];
> + uint8_t cert_store_index;
> + uint8_t reserved1[7];
> + uint64_t cert_len;
> + uint64_t comp_len;
> + uint64_t comp_addr;
> + uint64_t sig_len;
> + uint64_t sig_addr;
> +};
> +typedef struct Diag508SigVerifBlock Diag508SigVerifBlock;
>
> #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index ee64257dbc..379fb8f2b4 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -602,9 +602,112 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> }
> }
>
> +static int diag_508_verify_sig(uint8_t *cert, size_t cert_size,
> + uint8_t *comp, size_t comp_size,
> + uint8_t *sig, size_t sig_size)
Nit we could change the function definition from an int to bool, this
would make reading easier.
> +{
> + g_autofree uint8_t *sig_pem = NULL;
> + size_t sig_size_pem;
> + int rc;
> +
> + /*
> + * PKCS#7 signature with DER format
> + * Convert to PEM format for signature verification
> + */
> + rc = qcrypto_pkcs7_convert_sig_pem(sig, sig_size, &sig_pem, &sig_size_pem, NULL);
> + if (rc < 0) {
> + return -1;
> + }
> +
> + rc = qcrypto_x509_verify_sig(cert, cert_size,
> + comp, comp_size,
> + sig_pem, sig_size_pem, NULL);
> + if (rc < 0) {
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int handle_diag508_sig_verif(uint64_t addr, size_t svb_size,
> + S390IPLCertificateStore *qcs)
> +{
> + int rc;
> + int verified;
> + uint32_t svb_len;
> + uint64_t comp_len, comp_addr;
> + uint64_t sig_len, sig_addr;
> + g_autofree uint8_t *svb_comp = NULL;
> + g_autofree uint8_t *svb_sig = NULL;
> + g_autofree Diag508SigVerifBlock *svb = NULL;
> +
> + if (!qcs || !qcs->count) {
> + return DIAG_508_RC_NO_CERTS;
> + }
> +
> + svb = g_new0(Diag508SigVerifBlock, 1);
> + cpu_physical_memory_read(addr, svb, svb_size);
> +
> + svb_len = be32_to_cpu(svb->length);
> + if (svb_len != svb_size) {
> + return DIAG_508_RC_INVAL_LEN;
> + }
> +
> + comp_len = be64_to_cpu(svb->comp_len);
> + comp_addr = be64_to_cpu(svb->comp_addr);
> + sig_len = be64_to_cpu(svb->sig_len);
> + sig_addr = be64_to_cpu(svb->sig_addr);
> +
> + if (!comp_len || !comp_addr) {
> + return DIAG_508_RC_INVAL_COMP_DATA;
> + }
> +
> + if (!sig_len || !sig_addr) {
> + return DIAG_508_RC_INVAL_PKCS7_SIG;
> + }
> +
> + svb_comp = g_malloc0(comp_len);
> + cpu_physical_memory_read(comp_addr, svb_comp, comp_len);
> +
> + svb_sig = g_malloc0(sig_len);
> + cpu_physical_memory_read(sig_addr, svb_sig, sig_len);
> +
> + rc = DIAG_508_RC_FAIL_VERIF;
> + /*
> + * It is uncertain which certificate contains
> + * the analogous key to verify the signed data
> + *
> + * Ignore errors from signature format convertion and verification,
> + * because currently in the certificate lookup process.
> + *
> + * Any error is treated as a verification failure,
> + * and the final result (verified or not) will be reported later.
> + */
> + for (int i = 0; i < qcs->count; i++) {
> + verified = diag_508_verify_sig(qcs->certs[i].raw,
> + qcs->certs[i].size,
> + svb_comp, comp_len,
> + svb_sig, sig_len);
> + if (verified == 0) {
> + svb->cert_store_index = i;
> + svb->cert_len = cpu_to_be64(qcs->certs[i].der_size);
> + cpu_physical_memory_write(addr, svb, be32_to_cpu(svb_size));
Converting svb_size looks incorrect, shouldn't we just write svb_size
bytes?
Thanks
Farhan
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-09-17 23:21 ` [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
2025-09-25 21:30 ` Farhan Ali
@ 2025-10-07 10:27 ` Thomas Huth
2025-10-10 16:37 ` Zhuoying Cai
2025-10-07 20:22 ` Collin Walling
2 siblings, 1 reply; 89+ messages in thread
From: Thomas Huth @ 2025-10-07 10:27 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, 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 DER format),
> and their respective lengths. All of this data should be encapsulated
> within a Diag508SigVerifBlock.
>
> The DIAG handler will read from the provided addresses
> to retrieve the necessary data, parse the signature file, then
> perform the signature-verification. Because there is no way to
> correlate a specific certificate to a component, each certificate
> in the store is tried until either verification succeeds, or all
> certs have been exhausted.
>
> 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 Diag508SigVerifBlock.
> The following values indicate failure:
>
> 0x0102: certificate not available
> 0x0202: component data is invalid
> 0x0302: signature is not in PKCS#7 format
> 0x0402: signature-verification failed
> 0x0502: length of Diag508SigVerifBlock is invalid
>
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 5 ++
> include/hw/s390x/ipl/diag508.h | 23 +++++++
> target/s390x/diag.c | 115 +++++++++++++++++++++++++++++++-
> 3 files changed, 142 insertions(+), 1 deletion(-)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 0919425e9a..eec368d17b 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -66,3 +66,8 @@ that requires assistance from QEMU.
>
> Subcode 0 - query installed subcodes
> Returns a 64-bit mask indicating which subcodes are supported.
> +
> +Subcode 1 - perform signature verification
> + Perform signature-verification on a signed component, using certificates
> + from the certificate store and leveraging qcrypto libraries to perform
> + this operation.
> diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
> index 6281ad8299..ad401cc867 100644
> --- a/include/hw/s390x/ipl/diag508.h
> +++ b/include/hw/s390x/ipl/diag508.h
> @@ -11,5 +11,28 @@
> #define S390X_DIAG508_H
>
> #define DIAG_508_SUBC_QUERY_SUBC 0x0000
> +#define DIAG_508_SUBC_SIG_VERIF 0x8000
> +
> +#define DIAG_508_RC_OK 0x0001
> +#define DIAG_508_RC_NO_CERTS 0x0102
> +#define DIAG_508_RC_INVAL_COMP_DATA 0x0202
> +#define DIAG_508_RC_INVAL_PKCS7_SIG 0x0302
> +#define DIAG_508_RC_FAIL_VERIF 0x0402
> +#define DIAG_508_RC_INVAL_LEN 0x0502
> +
> +struct Diag508SigVerifBlock {
> + uint32_t length;
> + uint8_t reserved0[3];
> + uint8_t version;
> + uint32_t reserved[2];
> + uint8_t cert_store_index;
> + uint8_t reserved1[7];
> + uint64_t cert_len;
> + uint64_t comp_len;
> + uint64_t comp_addr;
> + uint64_t sig_len;
> + uint64_t sig_addr;
> +};
> +typedef struct Diag508SigVerifBlock Diag508SigVerifBlock;
>
> #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index ee64257dbc..379fb8f2b4 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -602,9 +602,112 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> }
> }
>
> +static int diag_508_verify_sig(uint8_t *cert, size_t cert_size,
> + uint8_t *comp, size_t comp_size,
> + uint8_t *sig, size_t sig_size)
> +{
> + g_autofree uint8_t *sig_pem = NULL;
> + size_t sig_size_pem;
> + int rc;
> +
> + /*
> + * PKCS#7 signature with DER format
> + * Convert to PEM format for signature verification
> + */
> + rc = qcrypto_pkcs7_convert_sig_pem(sig, sig_size, &sig_pem, &sig_size_pem, NULL);
> + if (rc < 0) {
> + return -1;
> + }
> +
> + rc = qcrypto_x509_verify_sig(cert, cert_size,
> + comp, comp_size,
> + sig_pem, sig_size_pem, NULL);
> + if (rc < 0) {
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int handle_diag508_sig_verif(uint64_t addr, size_t svb_size,
> + S390IPLCertificateStore *qcs)
> +{
> + int rc;
> + int verified;
> + uint32_t svb_len;
> + uint64_t comp_len, comp_addr;
> + uint64_t sig_len, sig_addr;
> + g_autofree uint8_t *svb_comp = NULL;
> + g_autofree uint8_t *svb_sig = NULL;
> + g_autofree Diag508SigVerifBlock *svb = NULL;
> +
> + if (!qcs || !qcs->count) {
> + return DIAG_508_RC_NO_CERTS;
> + }
> +
> + svb = g_new0(Diag508SigVerifBlock, 1);
> + cpu_physical_memory_read(addr, svb, svb_size);
> +
> + svb_len = be32_to_cpu(svb->length);
> + if (svb_len != svb_size) {
> + return DIAG_508_RC_INVAL_LEN;
> + }
> +
> + comp_len = be64_to_cpu(svb->comp_len);
> + comp_addr = be64_to_cpu(svb->comp_addr);
> + sig_len = be64_to_cpu(svb->sig_len);
> + sig_addr = be64_to_cpu(svb->sig_addr);
> +
> + if (!comp_len || !comp_addr) {
> + return DIAG_508_RC_INVAL_COMP_DATA;
> + }
> +
> + if (!sig_len || !sig_addr) {
> + return DIAG_508_RC_INVAL_PKCS7_SIG;
> + }
I think there should also be something like an upper limit for comp_len and
sign_len here. Otherwise a malicious guest could force QEMU into allocating
giga- or terabytes of memory here to cause out-of-memory situations in the host.
> + 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
> + *
> + * Ignore errors from signature format convertion and verification,
> + * because currently in the certificate lookup process.
The second half of above sentence looks incomplete?
> + *
> + * Any error is treated as a verification failure,
> + * and the final result (verified or not) will be reported later.
> + */
> + for (int i = 0; i < qcs->count; i++) {
> + verified = diag_508_verify_sig(qcs->certs[i].raw,
> + qcs->certs[i].size,
> + svb_comp, comp_len,
> + svb_sig, sig_len);
> + if (verified == 0) {
> + svb->cert_store_index = i;
> + svb->cert_len = cpu_to_be64(qcs->certs[i].der_size);
> + cpu_physical_memory_write(addr, svb, be32_to_cpu(svb_size));
> + rc = DIAG_508_RC_OK;
> + break;
> + }
> + }
> +
> + return rc;
> +}
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-10-07 10:27 ` Thomas Huth
@ 2025-10-10 16:37 ` Zhuoying Cai
2025-10-10 18:08 ` Thomas Huth
0 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-10-10 16:37 UTC (permalink / raw)
To: Thomas Huth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 10/7/25 6:27 AM, Thomas Huth wrote:
> On 18/09/2025 01.21, 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 DER format),
>> and their respective lengths. All of this data should be encapsulated
>> within a Diag508SigVerifBlock.
>>
>> The DIAG handler will read from the provided addresses
>> to retrieve the necessary data, parse the signature file, then
>> perform the signature-verification. Because there is no way to
>> correlate a specific certificate to a component, each certificate
>> in the store is tried until either verification succeeds, or all
>> certs have been exhausted.
>>
>> 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 Diag508SigVerifBlock.
>> The following values indicate failure:
>>
>> 0x0102: certificate not available
>> 0x0202: component data is invalid
>> 0x0302: signature is not in PKCS#7 format
>> 0x0402: signature-verification failed
>> 0x0502: length of Diag508SigVerifBlock is invalid
>>
>> Signed-off-by: Collin Walling <walling@linux.ibm.com>
>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
[...]
>> +
>> +static int handle_diag508_sig_verif(uint64_t addr, size_t svb_size,
>> + S390IPLCertificateStore *qcs)
>> +{
>> + int rc;
>> + int verified;
>> + uint32_t svb_len;
>> + uint64_t comp_len, comp_addr;
>> + uint64_t sig_len, sig_addr;
>> + g_autofree uint8_t *svb_comp = NULL;
>> + g_autofree uint8_t *svb_sig = NULL;
>> + g_autofree Diag508SigVerifBlock *svb = NULL;
>> +
>> + if (!qcs || !qcs->count) {
>> + return DIAG_508_RC_NO_CERTS;
>> + }
>> +
>> + svb = g_new0(Diag508SigVerifBlock, 1);
>> + cpu_physical_memory_read(addr, svb, svb_size);
>> +
>> + svb_len = be32_to_cpu(svb->length);
>> + if (svb_len != svb_size) {
>> + return DIAG_508_RC_INVAL_LEN;
>> + }
>> +
>> + comp_len = be64_to_cpu(svb->comp_len);
>> + comp_addr = be64_to_cpu(svb->comp_addr);
>> + sig_len = be64_to_cpu(svb->sig_len);
>> + sig_addr = be64_to_cpu(svb->sig_addr);
>> +
>> + if (!comp_len || !comp_addr) {
>> + return DIAG_508_RC_INVAL_COMP_DATA;
>> + }
>> +
>> + if (!sig_len || !sig_addr) {
>> + return DIAG_508_RC_INVAL_PKCS7_SIG;
>> + }
>
> I think there should also be something like an upper limit for comp_len and
> sign_len here. Otherwise a malicious guest could force QEMU into allocating
> giga- or terabytes of memory here to cause out-of-memory situations in the host.
>
Thank you for the suggestion. I agree that setting an upper limit would
help prevent unreasonable memory requests. I think it makes sense to
choose a reasonable value so we don't have to adjust it too often, but
I'm not entirely sure how to determine an appropriate upper bound.
Re: sig_len - the signature length can vary depending on the
cryptographic algorithm, and I don't believe there's a strict limit.
(FYI, in a somewhat similar situation, we haven't enforced a maximum
size on certificate files when loading them into memory, since they're
assumed to be trusted, as Daniel previously suggested -
https://lists.gnu.org/archive/html/qemu-s390x/2025-06/msg00049.html).
If we'd like to set an upper limit for sig_len, the largest signature
I've tested is 1165 bytes, signed with an RSA certificate using an
8192-bit key. Would 4096 be a reasonable upper bound?
Re: comp_len - the size of the guest kernel I'm currently using is
14,184,448 (0xD87000). When I built a kernel with make allyesconfig, the
size can reach 261,005,383 (0xF8EA047). Based on this value, would
262,000,000 (0xF9DCD80) an appropriate upper limit?
>> + 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
>> + *
>> + * Ignore errors from signature format convertion and verification,
>> + * because currently in the certificate lookup process.
>
> The second half of above sentence looks incomplete?
>
>> + *
>> + * Any error is treated as a verification failure,
>> + * and the final result (verified or not) will be reported later.
>> + */
>> + for (int i = 0; i < qcs->count; i++) {
>> + verified = diag_508_verify_sig(qcs->certs[i].raw,
>> + qcs->certs[i].size,
>> + svb_comp, comp_len,
>> + svb_sig, sig_len);
>> + if (verified == 0) {
>> + svb->cert_store_index = i;
>> + svb->cert_len = cpu_to_be64(qcs->certs[i].der_size);
>> + cpu_physical_memory_write(addr, svb, be32_to_cpu(svb_size));
>> + rc = DIAG_508_RC_OK;
>> + break;
>> + }
>> + }
>> +
>> + return rc;
>> +}
>
> Thomas
>
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-10-10 16:37 ` Zhuoying Cai
@ 2025-10-10 18:08 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-10-10 18:08 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 10/10/2025 18.37, Zhuoying Cai wrote:
> On 10/7/25 6:27 AM, Thomas Huth wrote:
>> On 18/09/2025 01.21, 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 DER format),
>>> and their respective lengths. All of this data should be encapsulated
>>> within a Diag508SigVerifBlock.
>>>
>>> The DIAG handler will read from the provided addresses
>>> to retrieve the necessary data, parse the signature file, then
>>> perform the signature-verification. Because there is no way to
>>> correlate a specific certificate to a component, each certificate
>>> in the store is tried until either verification succeeds, or all
>>> certs have been exhausted.
>>>
>>> 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 Diag508SigVerifBlock.
>>> The following values indicate failure:
>>>
>>> 0x0102: certificate not available
>>> 0x0202: component data is invalid
>>> 0x0302: signature is not in PKCS#7 format
>>> 0x0402: signature-verification failed
>>> 0x0502: length of Diag508SigVerifBlock is invalid
>>>
>>> Signed-off-by: Collin Walling <walling@linux.ibm.com>
>>> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
>
> [...]
>
>>> +
>>> +static int handle_diag508_sig_verif(uint64_t addr, size_t svb_size,
>>> + S390IPLCertificateStore *qcs)
>>> +{
>>> + int rc;
>>> + int verified;
>>> + uint32_t svb_len;
>>> + uint64_t comp_len, comp_addr;
>>> + uint64_t sig_len, sig_addr;
>>> + g_autofree uint8_t *svb_comp = NULL;
>>> + g_autofree uint8_t *svb_sig = NULL;
>>> + g_autofree Diag508SigVerifBlock *svb = NULL;
>>> +
>>> + if (!qcs || !qcs->count) {
>>> + return DIAG_508_RC_NO_CERTS;
>>> + }
>>> +
>>> + svb = g_new0(Diag508SigVerifBlock, 1);
>>> + cpu_physical_memory_read(addr, svb, svb_size);
>>> +
>>> + svb_len = be32_to_cpu(svb->length);
>>> + if (svb_len != svb_size) {
>>> + return DIAG_508_RC_INVAL_LEN;
>>> + }
>>> +
>>> + comp_len = be64_to_cpu(svb->comp_len);
>>> + comp_addr = be64_to_cpu(svb->comp_addr);
>>> + sig_len = be64_to_cpu(svb->sig_len);
>>> + sig_addr = be64_to_cpu(svb->sig_addr);
>>> +
>>> + if (!comp_len || !comp_addr) {
>>> + return DIAG_508_RC_INVAL_COMP_DATA;
>>> + }
>>> +
>>> + if (!sig_len || !sig_addr) {
>>> + return DIAG_508_RC_INVAL_PKCS7_SIG;
>>> + }
>>
>> I think there should also be something like an upper limit for comp_len and
>> sign_len here. Otherwise a malicious guest could force QEMU into allocating
>> giga- or terabytes of memory here to cause out-of-memory situations in the host.
>>
>
> Thank you for the suggestion. I agree that setting an upper limit would
> help prevent unreasonable memory requests. I think it makes sense to
> choose a reasonable value so we don't have to adjust it too often, but
> I'm not entirely sure how to determine an appropriate upper bound.
>
> Re: sig_len - the signature length can vary depending on the
> cryptographic algorithm, and I don't believe there's a strict limit.
> (FYI, in a somewhat similar situation, we haven't enforced a maximum
> size on certificate files when loading them into memory, since they're
> assumed to be trusted, as Daniel previously suggested -
> https://lists.gnu.org/archive/html/qemu-s390x/2025-06/msg00049.html).
>
> If we'd like to set an upper limit for sig_len, the largest signature
> I've tested is 1165 bytes, signed with an RSA certificate using an
> 8192-bit key. Would 4096 be a reasonable upper bound?
Sounds reasonable, yes.
> Re: comp_len - the size of the guest kernel I'm currently using is
> 14,184,448 (0xD87000). When I built a kernel with make allyesconfig, the
> size can reach 261,005,383 (0xF8EA047). Based on this value, would
> 262,000,000 (0xF9DCD80) an appropriate upper limit?
Make it 0x10000000 = 268435456 Bytes, that feels like a round boundary ;-)
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification
2025-09-17 23:21 ` [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
2025-09-25 21:30 ` Farhan Ali
2025-10-07 10:27 ` Thomas Huth
@ 2025-10-07 20:22 ` Collin Walling
2 siblings, 0 replies; 89+ messages in thread
From: Collin Walling @ 2025-10-07 20:22 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: jjherne, pasic, borntraeger, farman, mjrosato, iii, eblake,
armbru, alifm
On 9/17/25 19:21, Zhuoying Cai wrote:
Just some nits below based on how far along this patch has come.
> 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 initrd case should be included in the document as well for subcode 1.
>
> The instruction call expects two item-pairs: an address of a device
> component, an address of the analogous signature file (in PKCS#7 DER format),
> and their respective lengths. All of this data should be encapsulated
> within a Diag508SigVerifBlock.
>
> The DIAG handler will read from the provided addresses
> to retrieve the necessary data, parse the signature file, then
> perform the signature-verification. Because there is no way to
> correlate a specific certificate to a component, each certificate
> in the store is tried until either verification succeeds, or all
> certs have been exhausted.
>
> The subcode value is denoted by setting the second-to-left-most bit of
> a 2-byte field.
>
Remove the sentence above. As for the info below, it should also be
included in the document under subcode 1.
> A return code of 1 indicates success, and the index and length of the
> corresponding certificate will be set in the Diag508SigVerifBlock.
> The following values indicate failure:
>
> 0x0102: certificate not available
Change to: no certificates are available in the store
> 0x0202: component data is invalid
> 0x0302: signature is not in PKCS#7 format
> 0x0402: signature-verification failed
> 0x0502: length of Diag508SigVerifBlock is invalid
>
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 5 ++
> include/hw/s390x/ipl/diag508.h | 23 +++++++
> target/s390x/diag.c | 115 +++++++++++++++++++++++++++++++-
> 3 files changed, 142 insertions(+), 1 deletion(-)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 0919425e9a..eec368d17b 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -66,3 +66,8 @@ that requires assistance from QEMU.
>
> Subcode 0 - query installed subcodes
> Returns a 64-bit mask indicating which subcodes are supported.
> +
> +Subcode 1 - perform signature verification
> + Perform signature-verification on a signed component, using certificates
> + from the certificate store and leveraging qcrypto libraries to perform
> + this operation.
> diff --git a/include/hw/s390x/ipl/diag508.h b/include/hw/s390x/ipl/diag508.h
> index 6281ad8299..ad401cc867 100644
> --- a/include/hw/s390x/ipl/diag508.h
> +++ b/include/hw/s390x/ipl/diag508.h
> @@ -11,5 +11,28 @@
> #define S390X_DIAG508_H
>
> #define DIAG_508_SUBC_QUERY_SUBC 0x0000
> +#define DIAG_508_SUBC_SIG_VERIF 0x8000
> +
> +#define DIAG_508_RC_OK 0x0001
> +#define DIAG_508_RC_NO_CERTS 0x0102
> +#define DIAG_508_RC_INVAL_COMP_DATA 0x0202
> +#define DIAG_508_RC_INVAL_PKCS7_SIG 0x0302
> +#define DIAG_508_RC_FAIL_VERIF 0x0402
> +#define DIAG_508_RC_INVAL_LEN 0x0502
> +
> +struct Diag508SigVerifBlock {
> + uint32_t length;
> + uint8_t reserved0[3];
> + uint8_t version;
> + uint32_t reserved[2];
> + uint8_t cert_store_index;
> + uint8_t reserved1[7];
> + uint64_t cert_len;
> + uint64_t comp_len;
> + uint64_t comp_addr;
> + uint64_t sig_len;
> + uint64_t sig_addr;
> +};
> +typedef struct Diag508SigVerifBlock Diag508SigVerifBlock;
>
> #endif
> diff --git a/target/s390x/diag.c b/target/s390x/diag.c
> index ee64257dbc..379fb8f2b4 100644
> --- a/target/s390x/diag.c
> +++ b/target/s390x/diag.c
> @@ -602,9 +602,112 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> }
> }
>
> +static int diag_508_verify_sig(uint8_t *cert, size_t cert_size,
> + uint8_t *comp, size_t comp_size,
> + uint8_t *sig, size_t sig_size)
> +{
> + g_autofree uint8_t *sig_pem = NULL;
> + size_t sig_size_pem;
> + int rc;
> +
> + /*
> + * PKCS#7 signature with DER format
> + * Convert to PEM format for signature verification
> + */
> + rc = qcrypto_pkcs7_convert_sig_pem(sig, sig_size, &sig_pem, &sig_size_pem, NULL);
> + if (rc < 0) {
> + return -1;
> + }
> +
> + rc = qcrypto_x509_verify_sig(cert, cert_size,
> + comp, comp_size,
> + sig_pem, sig_size_pem, NULL);
> + if (rc < 0) {
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int handle_diag508_sig_verif(uint64_t addr, size_t svb_size,
> + S390IPLCertificateStore *qcs)
> +{
> + int rc;
> + int verified;
> + uint32_t svb_len;
> + uint64_t comp_len, comp_addr;
> + uint64_t sig_len, sig_addr;
> + g_autofree uint8_t *svb_comp = NULL;
> + g_autofree uint8_t *svb_sig = NULL;
nit: maybe just call these comp and sig?
> + g_autofree Diag508SigVerifBlock *svb = NULL;
> +
> + if (!qcs || !qcs->count) {
> + return DIAG_508_RC_NO_CERTS;
> + }
> +
> + svb = g_new0(Diag508SigVerifBlock, 1);
> + cpu_physical_memory_read(addr, svb, svb_size);
> +
> + svb_len = be32_to_cpu(svb->length);
> + if (svb_len != svb_size) {
> + return DIAG_508_RC_INVAL_LEN;
> + }
> +
> + comp_len = be64_to_cpu(svb->comp_len);
> + comp_addr = be64_to_cpu(svb->comp_addr);
> + sig_len = be64_to_cpu(svb->sig_len);
> + sig_addr = be64_to_cpu(svb->sig_addr);
> +
> + if (!comp_len || !comp_addr) {
> + return DIAG_508_RC_INVAL_COMP_DATA;
> + }
> +
> + if (!sig_len || !sig_addr) {
> + return DIAG_508_RC_INVAL_PKCS7_SIG;
> + }
> +
> + svb_comp = g_malloc0(comp_len);
> + cpu_physical_memory_read(comp_addr, svb_comp, comp_len);
> +
> + svb_sig = g_malloc0(sig_len);
> + cpu_physical_memory_read(sig_addr, svb_sig, sig_len);
> +
> + rc = DIAG_508_RC_FAIL_VERIF;
> + /*
> + * It is uncertain which certificate contains
> + * the analogous key to verify the signed data
> + *
> + * Ignore errors from signature format convertion and verification,
> + * because currently in the certificate lookup process.
> + *
> + * Any error is treated as a verification failure,
> + * and the final result (verified or not) will be reported later.
> + */
I think these comments may now be rendered redundant, now with the
for-loop significantly simplified since it was originally put in place.
You can remove them.
As for mentioning how errors are handled, you could put that comment in
diag_508_verify_sig since that's where the errors are being ignored.
> + for (int i = 0; i < qcs->count; i++) {
> + verified = diag_508_verify_sig(qcs->certs[i].raw,
> + qcs->certs[i].size,
> + svb_comp, comp_len,
> + svb_sig, sig_len);
> + if (verified == 0) {
> + svb->cert_store_index = i;
> + svb->cert_len = cpu_to_be64(qcs->certs[i].der_size);
> + cpu_physical_memory_write(addr, svb, be32_to_cpu(svb_size));
> + rc = DIAG_508_RC_OK;
Could just return DIAG_508_RC_OK...
> + break;
> + }
> + }
> +
> + return rc;
...and here return DIAG_508_RC_FAIL_VERIF
Then get rid of rc.
> +}
> +
> +QEMU_BUILD_BUG_MSG(sizeof(Diag508SigVerifBlock) != 64,
> + "size of Diag508SigVerifBlock is wrong");
> +
> void handle_diag_508(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
> {
> + S390IPLCertificateStore *qcs = s390_ipl_get_certificate_store();
Move this line into handle_diag508_sig_verif() and remove the qcs param
from the function.
> uint64_t subcode = env->regs[r3];
> + uint64_t addr = env->regs[r1];
> int rc;
>
> if (env->psw.mask & PSW_MASK_PSTATE) {
> @@ -619,7 +722,17 @@ 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(Diag508SigVerifBlock);
Since svb_size is only passed to the functions below, maybe just use
sizeof inline and then you can remove another param from
handle_diag508_sig_verif()? It should fit nicely now that the struct is
less verbose.
> +
> + if (!diag_parm_addr_valid(addr, sizeof(Diag508SigVerifBlock), true)) {
> + s390_program_interrupt(env, PGM_ADDRESSING, ra);
> + return;
> + }
> +
> + rc = handle_diag508_sig_verif(addr, svb_size, qcs);
> break;
> default:
> s390_program_interrupt(env, PGM_SPECIFICATION, ra);
--
Regards,
Collin
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB)
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (11 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 12/28] s390x/diag: Implement DIAG 508 subcode 1 for signature verification Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-25 22:02 ` Farhan Ali
2025-09-17 23:21 ` [PATCH v6 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
` (15 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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>
---
docs/specs/s390x-secure-ipl.rst | 14 ++++++++
pc-bios/s390-ccw/iplb.h | 62 +++++++++++++++++++++++++++++++++
2 files changed, 76 insertions(+)
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index eec368d17b..760a066084 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -71,3 +71,17 @@ Subcode 1 - perform signature verification
Perform signature-verification on a signed component, using certificates
from the certificate store and leveraging qcrypto libraries to perform
this operation.
+
+
+IPL Information Report Block
+----------------------------
+
+The IPL Parameter Block (IPLPB), utilized for IPL operation, is extended with an
+IPL Information Report Block (IIRB), which contains the results from secure IPL
+operations such as:
+
+* component data
+* verification results
+* certificate data
+
+The guest kernel will inspect the IIRB and build the keyring.
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.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB)
2025-09-17 23:21 ` [PATCH v6 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
@ 2025-09-25 22:02 ` Farhan Ali
0 siblings, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-25 22:02 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> The IPL information report block (IIRB) contains information used
> to locate IPL records and to report the results of signature verification
> of one or more secure components of the load device.
>
> IIRB is stored immediately following the IPL Parameter Block. Results on
> component verification in any case (failure or success) are stored.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 14 ++++++++
> pc-bios/s390-ccw/iplb.h | 62 +++++++++++++++++++++++++++++++++
> 2 files changed, 76 insertions(+)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index eec368d17b..760a066084 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -71,3 +71,17 @@ Subcode 1 - perform signature verification
> Perform signature-verification on a signed component, using certificates
> from the certificate store and leveraging qcrypto libraries to perform
> this operation.
> +
> +
> +IPL Information Report Block
> +----------------------------
> +
> +The IPL Parameter Block (IPLPB), utilized for IPL operation, is extended with an
> +IPL Information Report Block (IIRB), which contains the results from secure IPL
> +operations such as:
> +
> +* component data
> +* verification results
> +* certificate data
> +
> +The guest kernel will inspect the IIRB and build the keyring.
> 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
We should avoid packing the structure and just add QEMU_BUILD_BUG_MSG.
Overall the patch LGTM. Thanks Farhan
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (12 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 13/28] pc-bios/s390-ccw: Introduce IPL Information Report Block (IIRB) Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-30 5:17 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
` (14 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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 a9521dff41..457fbc3095 100644
--- a/pc-bios/s390-ccw/netmain.c
+++ b/pc-bios/s390-ccw/netmain.c
@@ -528,11 +528,11 @@ static bool virtio_setup(void)
*/
enable_mss_facility();
- if (have_iplb || store_iplb(&iplb)) {
- IPL_assert(iplb.pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
- dev_no = iplb.ccw.devno;
+ if (have_iplb || store_iplb(iplb)) {
+ IPL_assert(iplb->pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
+ dev_no = iplb->ccw.devno;
debug_print_int("device no. ", dev_no);
- net_schid.ssid = iplb.ccw.ssid & 0x3;
+ net_schid.ssid = iplb->ccw.ssid & 0x3;
debug_print_int("ssid ", net_schid.ssid);
found = find_net_dev(&schib, dev_no);
} else {
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers
2025-09-17 23:21 ` [PATCH v6 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
@ 2025-09-30 5:17 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 5:17 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> 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)));
I think it makes more sense to specify the attribute in the .c file instead
of using it here in the header.
Thomas
> #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 a9521dff41..457fbc3095 100644
> --- a/pc-bios/s390-ccw/netmain.c
> +++ b/pc-bios/s390-ccw/netmain.c
> @@ -528,11 +528,11 @@ static bool virtio_setup(void)
> */
> enable_mss_facility();
>
> - if (have_iplb || store_iplb(&iplb)) {
> - IPL_assert(iplb.pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
> - dev_no = iplb.ccw.devno;
> + if (have_iplb || store_iplb(iplb)) {
> + IPL_assert(iplb->pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected");
> + dev_no = iplb->ccw.devno;
> debug_print_int("device no. ", dev_no);
> - net_schid.ssid = iplb.ccw.ssid & 0x3;
> + net_schid.ssid = iplb->ccw.ssid & 0x3;
> debug_print_int("ssid ", net_schid.ssid);
> found = find_net_dev(&schib, dev_no);
> } else {
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (13 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 14/28] pc-bios/s390-ccw: Define memory for IPLB and convert IPLB to pointers Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-29 21:21 ` Farhan Ali
2025-09-17 23:21 ` [PATCH v6 16/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
` (13 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Add IPIB flags to IPL Parameter Block to determine if IPL needs to
perform securely and if IPL Information Report Block (IIRB) exists.
Move DIAG308 flags to a separated header file and add flags for secure IPL.
Secure boot in audit mode will perform if certificate(s) exist in the
key store. IIRB will exist and results of verification will be stored in
IIRB.
To ensure proper alignment of the IIRB and prevent overlap, set
iplb->len to the maximum length of the IPLB, allowing alignment
constraints to be determined based on its size.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
hw/s390x/ipl.c | 17 +++++++++++++++++
hw/s390x/ipl.h | 18 +-----------------
include/hw/s390x/ipl/diag308.h | 34 ++++++++++++++++++++++++++++++++++
include/hw/s390x/ipl/qipl.h | 5 ++++-
4 files changed, 56 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 917166ba31..c1360905c4 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -494,6 +494,23 @@ 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;
+ }
+
+ if (iplb->hdr_flags & DIAG308_IPIB_FLAGS_IPLIR) {
+ iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
+ }
+
return true;
}
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index e26fc1cd6a..01922d80c4 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -23,7 +23,6 @@
#include "qom/object.h"
#include "target/s390x/kvm/pv.h"
-#define DIAG308_FLAGS_LP_VALID 0x80
#define MAX_BOOT_DEVS 8 /* Max number of devices that may have a bootindex */
void s390_ipl_convert_loadparm(char *ascii_lp, uint8_t *ebcdic_lp);
@@ -91,22 +90,6 @@ struct S390IPLState {
};
QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
-#define DIAG_308_RC_OK 0x0001
-#define DIAG_308_RC_NO_CONF 0x0102
-#define DIAG_308_RC_INVALID 0x0402
-#define DIAG_308_RC_NO_PV_CONF 0x0902
-#define DIAG_308_RC_INVAL_FOR_PV 0x0a02
-
-#define DIAG308_RESET_MOD_CLR 0
-#define DIAG308_RESET_LOAD_NORM 1
-#define DIAG308_LOAD_CLEAR 3
-#define DIAG308_LOAD_NORMAL_DUMP 4
-#define DIAG308_SET 5
-#define DIAG308_STORE 6
-#define DIAG308_PV_SET 8
-#define DIAG308_PV_STORE 9
-#define DIAG308_PV_START 10
-
#define S390_IPL_TYPE_FCP 0x00
#define S390_IPL_TYPE_CCW 0x02
#define S390_IPL_TYPE_PV 0x05
@@ -117,6 +100,7 @@ QEMU_BUILD_BUG_MSG(offsetof(S390IPLState, iplb) & 3, "alignment of iplb wrong");
#define S390_IPLB_MIN_CCW_LEN 200
#define S390_IPLB_MIN_FCP_LEN 384
#define S390_IPLB_MIN_QEMU_SCSI_LEN 200
+#define S390_IPLB_MAX_LEN 4096
static inline bool diag_parm_addr_valid(uint64_t addr, size_t size, bool write)
{
diff --git a/include/hw/s390x/ipl/diag308.h b/include/hw/s390x/ipl/diag308.h
new file mode 100644
index 0000000000..6e62f29215
--- /dev/null
+++ b/include/hw/s390x/ipl/diag308.h
@@ -0,0 +1,34 @@
+/*
+ * S/390 DIAGNOSE 308 definitions and structures
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef S390X_DIAG308_H
+#define S390X_DIAG308_H
+
+#define DIAG_308_RC_OK 0x0001
+#define DIAG_308_RC_NO_CONF 0x0102
+#define DIAG_308_RC_INVALID 0x0402
+#define DIAG_308_RC_NO_PV_CONF 0x0902
+#define DIAG_308_RC_INVAL_FOR_PV 0x0a02
+
+#define DIAG308_RESET_MOD_CLR 0
+#define DIAG308_RESET_LOAD_NORM 1
+#define DIAG308_LOAD_CLEAR 3
+#define DIAG308_LOAD_NORMAL_DUMP 4
+#define DIAG308_SET 5
+#define DIAG308_STORE 6
+#define DIAG308_PV_SET 8
+#define DIAG308_PV_STORE 9
+#define DIAG308_PV_START 10
+
+#define DIAG308_FLAGS_LP_VALID 0x80
+
+#define DIAG308_IPIB_FLAGS_SIPL 0x40
+#define DIAG308_IPIB_FLAGS_IPLIR 0x20
+
+#endif
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index e505f44020..5c2bf3051c 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -12,6 +12,8 @@
#ifndef S390X_QIPL_H
#define S390X_QIPL_H
+#include "diag308.h"
+
/* Boot Menu flags */
#define QIPL_FLAG_BM_OPTS_CMD 0x80
#define QIPL_FLAG_BM_OPTS_ZIPL 0x40
@@ -103,7 +105,8 @@ typedef struct IplBlockQemuScsi IplBlockQemuScsi;
union IplParameterBlock {
struct {
uint32_t len;
- uint8_t reserved0[3];
+ uint8_t hdr_flags;
+ uint8_t reserved0[2];
uint8_t version;
uint32_t blk0_len;
uint8_t pbt;
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block
2025-09-17 23:21 ` [PATCH v6 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
@ 2025-09-29 21:21 ` Farhan Ali
0 siblings, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-09-29 21:21 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> Add IPIB flags to IPL Parameter Block to determine if IPL needs to
> perform securely and if IPL Information Report Block (IIRB) exists.
>
> Move DIAG308 flags to a separated header file and add flags for secure IPL.
>
> Secure boot in audit mode will perform if certificate(s) exist in the
> key store. IIRB will exist and results of verification will be stored in
> IIRB.
>
> To ensure proper alignment of the IIRB and prevent overlap, set
> iplb->len to the maximum length of the IPLB, allowing alignment
> constraints to be determined based on its size.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> hw/s390x/ipl.c | 17 +++++++++++++++++
> hw/s390x/ipl.h | 18 +-----------------
> include/hw/s390x/ipl/diag308.h | 34 ++++++++++++++++++++++++++++++++++
> include/hw/s390x/ipl/qipl.h | 5 ++++-
> 4 files changed, 56 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 917166ba31..c1360905c4 100644
> --- a/hw/s390x/ipl.c
> +++ b/hw/s390x/ipl.c
> @@ -494,6 +494,23 @@ 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;
> + }
> +
> + if (iplb->hdr_flags & DIAG308_IPIB_FLAGS_IPLIR) {
> + iplb->len = cpu_to_be32(S390_IPLB_MAX_LEN);
> + }
> +
Can we move the setting of iplb->len to if block that checks for
certificates? I am not if we really need the if block as we only set the
hdr_flags to DIAG308_IPIB_FLAGS_IPLIR only when we have certificates?
Thanks Farhan
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 16/28] s390x: Guest support for Secure-IPL Facility
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (14 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 15/28] hw/s390x/ipl: Add IPIB flags to IPL Parameter Block Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-17 23:21 ` [PATCH v6 17/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
` (12 subsequent siblings)
28 siblings, 0 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Introduce Secure-IPL (SIPL) facility.
Use fac_ipl to represent bytes 136 and 137 for IPL device facilities
of the SCLP Read Info block.
Availability of SIPL facility is determined by byte 136 bit 1 of the
SCLP Read Info block. Byte 136's facilities cannot be represented
without the availability of the extended-length-SCCB, so add it as a
check for consistency.
Secure IPL is not available for guests under protected virtualization.
This feature is available starting with the gen16 CPU model.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
---
hw/s390x/sclp.c | 2 ++
include/hw/s390x/sclp.h | 4 +++-
target/s390x/cpu_features.c | 4 ++++
target/s390x/cpu_features.h | 1 +
target/s390x/cpu_features_def.h.inc | 3 +++
target/s390x/cpu_models.c | 2 ++
target/s390x/gen-features.c | 2 ++
target/s390x/kvm/kvm.c | 3 +++
8 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
index 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 436471f4b4..200bd8c15b 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -119,6 +119,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
* Some facilities are not available for CPUs in protected mode:
* - All SIE facilities because SIE is not available
* - DIAG318
+ * - Secure IPL Facility
*
* As VMs can move in and out of protected mode the CPU model
* doesn't protect us from that problem because it is only
@@ -149,6 +150,9 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
clear_be_bit(s390_feat_def(S390_FEAT_DIAG_318)->bit, data);
clear_be_bit(s390_feat_def(S390_FEAT_CERT_STORE)->bit, data);
break;
+ case S390_FEAT_TYPE_SCLP_FAC_IPL:
+ clear_be_bit(s390_feat_def(S390_FEAT_SIPL)->bit, data);
+ break;
default:
return;
}
diff --git a/target/s390x/cpu_features.h b/target/s390x/cpu_features.h
index 5635839d03..b038198555 100644
--- a/target/s390x/cpu_features.h
+++ b/target/s390x/cpu_features.h
@@ -24,6 +24,7 @@ typedef enum {
S390_FEAT_TYPE_SCLP_CONF_CHAR,
S390_FEAT_TYPE_SCLP_CONF_CHAR_EXT,
S390_FEAT_TYPE_SCLP_FAC134,
+ S390_FEAT_TYPE_SCLP_FAC_IPL,
S390_FEAT_TYPE_SCLP_CPU,
S390_FEAT_TYPE_MISC,
S390_FEAT_TYPE_PLO,
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index 941a69e013..55eef618b8 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -140,6 +140,9 @@ DEF_FEAT(SIE_IBS, "ibs", SCLP_CONF_CHAR_EXT, 10, "SIE: Interlock-and-broadcast-s
DEF_FEAT(DIAG_318, "diag318", SCLP_FAC134, 0, "Control program name and version codes")
DEF_FEAT(CERT_STORE, "cstore", SCLP_FAC134, 5, "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 6b8471700e..f99536ef9a 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -263,6 +263,7 @@ bool s390_has_feat(S390Feat feat)
case S390_FEAT_SIE_CMMA:
case S390_FEAT_SIE_PFMFI:
case S390_FEAT_SIE_IBS:
+ case S390_FEAT_SIPL:
case S390_FEAT_CONFIGURATION_TOPOLOGY:
return false;
break;
@@ -507,6 +508,7 @@ static void check_consistency(const S390CPUModel *model)
{ S390_FEAT_AP_QUEUE_INTERRUPT_CONTROL, S390_FEAT_AP },
{ S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_CERT_STORE, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_SIPL, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_NNPA, S390_FEAT_VECTOR },
{ S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
{ S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index 6c20c3a862..bd2060ab93 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -721,6 +721,7 @@ static uint16_t full_GEN16_GA1[] = {
S390_FEAT_UV_FEAT_AP,
S390_FEAT_UV_FEAT_AP_INTR,
S390_FEAT_CERT_STORE,
+ S390_FEAT_SIPL,
};
static uint16_t full_GEN17_GA1[] = {
@@ -922,6 +923,7 @@ static uint16_t qemu_MAX[] = {
S390_FEAT_PRNO_TRNG,
S390_FEAT_EXTENDED_LENGTH_SCCB,
S390_FEAT_CERT_STORE,
+ S390_FEAT_SIPL,
};
/****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index ae6cd3d506..31bd574dec 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2520,6 +2520,9 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
set_bit(S390_FEAT_CERT_STORE, model->features);
+ /* Some Secure IPL facilities are emulated by QEMU */
+ set_bit(S390_FEAT_SIPL, model->features);
+
/* Test for Ultravisor features that influence secure guest behavior */
query_uv_feat_guest(model->features);
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* [PATCH v6 17/28] pc-bios/s390-ccw: Refactor zipl_run()
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (15 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 16/28] s390x: Guest support for Secure-IPL Facility Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-26 12:51 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 18/28] pc-bios/s390-ccw: Rework zipl_load_segment function Zhuoying Cai
` (11 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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 | 49 ++++++++++++++++++++++++--------------
1 file changed, 31 insertions(+), 18 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 0f8baa0198..ff0fa78cf0 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -674,6 +674,35 @@ static int zipl_load_segment(ComponentEntry *entry)
return 0;
}
+static int zipl_run_normal(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
+{
+ ComponentEntry *entry = *entry_ptr;
+
+ while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
+ entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+
+ /* Secure boot is off, so we skip signature entries */
+ if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
+ entry++;
+ continue;
+ }
+
+ if (zipl_load_segment(entry)) {
+ return -1;
+ }
+
+ entry++;
+
+ if ((uint8_t *)&entry[1] > tmp_sec + MAX_SECTOR_SIZE) {
+ puts("Wrong entry value");
+ return -EINVAL;
+ }
+ }
+
+ *entry_ptr = entry;
+ return 0;
+}
+
/* Run a zipl program */
static int zipl_run(ScsiBlockPtr *pte)
{
@@ -700,25 +729,9 @@ 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 (zipl_run_normal(&entry, tmp_sec)) {
+ return -1;
}
if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 17/28] pc-bios/s390-ccw: Refactor zipl_run()
2025-09-17 23:21 ` [PATCH v6 17/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2025-09-26 12:51 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-26 12:51 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, 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 | 49 ++++++++++++++++++++++++--------------
> 1 file changed, 31 insertions(+), 18 deletions(-)
>
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index 0f8baa0198..ff0fa78cf0 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -674,6 +674,35 @@ static int zipl_load_segment(ComponentEntry *entry)
> return 0;
> }
>
> +static int zipl_run_normal(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
> +{
> + ComponentEntry *entry = *entry_ptr;
> +
> + while (entry->component_type == ZIPL_COMP_ENTRY_LOAD ||
> + entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
> +
> + /* Secure boot is off, so we skip signature entries */
> + if (entry->component_type == ZIPL_COMP_ENTRY_SIGNATURE) {
> + entry++;
> + continue;
> + }
> +
> + if (zipl_load_segment(entry)) {
> + return -1;
> + }
> +
> + entry++;
> +
> + if ((uint8_t *)&entry[1] > tmp_sec + MAX_SECTOR_SIZE) {
> + puts("Wrong entry value");
> + return -EINVAL;
> + }
> + }
> +
> + *entry_ptr = entry;
> + return 0;
> +}
> +
> /* Run a zipl program */
> static int zipl_run(ScsiBlockPtr *pte)
> {
> @@ -700,25 +729,9 @@ 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;
So in this case the code returned -EINVAL ...
> - }
> + if (zipl_run_normal(&entry, tmp_sec)) {
> + return -1;
... but now it's always returning -1.
It likely does not make much difference in this case here, but it's
certainly better style to preserve the error value.
Thomas
> }
>
> if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 18/28] pc-bios/s390-ccw: Rework zipl_load_segment function
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (16 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 17/28] pc-bios/s390-ccw: Refactor zipl_run() Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-26 13:02 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
` (10 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Make the address variable a parameter of zipl_load_segment and return
segment length.
Modify this function to allow the caller to specify a memory address
where segment data should be loaded into.
seg_len variable is necessary to store the calculated segment length and
is used during signature verification. Return the length on success, or
a negative return code on failure.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/bootmap.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index ff0fa78cf0..4f54c643ff 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -613,19 +613,22 @@ static int ipl_eckd(void)
* IPL a SCSI disk
*/
-static int zipl_load_segment(ComponentEntry *entry)
+/*
+ * Returns: length of the segment on sucess,
+ * negative value on error.
+ */
+static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
{
const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));
ScsiBlockPtr *bprs = (void *)sec;
const int bprs_size = sizeof(sec);
block_number_t blockno;
- uint64_t address;
int i;
char err_msg[] = "zIPL failed to read BPRS at 0xZZZZZZZZZZZZZZZZ";
char *blk_no = &err_msg[30]; /* where to print blockno in (those ZZs) */
+ int seg_len = 0;
blockno = entry->data.blockno;
- address = entry->compdat.load_addr;
debug_print_int("loading segment at block", blockno);
debug_print_int("addr", address);
@@ -668,10 +671,12 @@ static int zipl_load_segment(ComponentEntry *entry)
puts("zIPL load segment failed");
return -EIO;
}
+
+ seg_len += bprs->size * (bprs[i].blockct + 1);
}
} while (blockno);
- return 0;
+ return seg_len;
}
static int zipl_run_normal(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
@@ -687,7 +692,7 @@ static int zipl_run_normal(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
continue;
}
- if (zipl_load_segment(entry)) {
+ if (zipl_load_segment(entry, entry->compdat.load_addr) < 0) {
return -1;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 18/28] pc-bios/s390-ccw: Rework zipl_load_segment function
2025-09-17 23:21 ` [PATCH v6 18/28] pc-bios/s390-ccw: Rework zipl_load_segment function Zhuoying Cai
@ 2025-09-26 13:02 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-26 13:02 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Make the address variable a parameter of zipl_load_segment and return
> segment length.
>
> Modify this function to allow the caller to specify a memory address
> where segment data should be loaded into.
>
> seg_len variable is necessary to store the calculated segment length and
> is used during signature verification. Return the length on success, or
> a negative return code on failure.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
Reviewed-by: Thomas Huth <thuth@redhat.com>
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (17 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 18/28] pc-bios/s390-ccw: Rework zipl_load_segment function Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-26 13:10 ` Thomas Huth
2025-09-30 18:42 ` Farhan Ali
2025-09-17 23:21 ` [PATCH v6 20/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
` (9 subsequent siblings)
28 siblings, 2 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Enable secure IPL in audit mode, which performs signature verification,
but any error does not terminate the boot process. Only warnings will be
logged to the console instead.
Add a comp_len variable to store the length of a segment in
zipl_load_segment. comp_len variable is necessary to store the
calculated segment length and is used during signature verification.
Return the length on success, or a negative return code on failure.
Secure IPL in audit mode requires at least one certificate provided in
the key store along with necessary facilities (Secure IPL Facility,
Certificate Store Facility and secure IPL extension support).
Note: Secure IPL in audit mode is implemented for the SCSI scheme of
virtio-blk/virtio-scsi devices.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/system/s390x/secure-ipl.rst | 36 +++
pc-bios/s390-ccw/Makefile | 3 +-
pc-bios/s390-ccw/bootmap.c | 39 +++-
pc-bios/s390-ccw/bootmap.h | 11 +
pc-bios/s390-ccw/main.c | 9 +
pc-bios/s390-ccw/s390-ccw.h | 15 ++
pc-bios/s390-ccw/sclp.c | 44 ++++
pc-bios/s390-ccw/sclp.h | 6 +
pc-bios/s390-ccw/secure-ipl.c | 371 +++++++++++++++++++++++++++++++
pc-bios/s390-ccw/secure-ipl.h | 99 +++++++++
10 files changed, 630 insertions(+), 3 deletions(-)
create mode 100644 pc-bios/s390-ccw/secure-ipl.c
create mode 100644 pc-bios/s390-ccw/secure-ipl.h
diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
index 92c1bb2153..701594b9de 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -19,3 +19,39 @@ Note: certificate files must have a .pem extension.
qemu-system-s390x -machine s390-ccw-virtio, \
boot-certs.0.path=/.../qemu/certs, \
boot-certs.1.path=/another/path/cert.pem ...
+
+
+IPL Modes
+=========
+
+The concept of IPL Modes are introduced to differentiate between the IPL configurations.
+These modes are mutually exclusive and enabled based on the ``boot-certs`` option on the
+QEMU command line.
+
+Normal Mode
+-----------
+
+The absence of certificates will attempt to IPL a guest without secure IPL operations.
+No checks are performed, and no warnings/errors are reported. This is the default mode.
+
+Configuration:
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio ...
+
+Audit Mode
+----------
+
+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.
+
+Configuration:
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio, \
+ boot-certs.0.path=/.../qemu/certs, \
+ boot-certs.1.path=/another/path/cert.pem ...
diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
index a0f24c94a8..603761a857 100644
--- a/pc-bios/s390-ccw/Makefile
+++ b/pc-bios/s390-ccw/Makefile
@@ -34,7 +34,8 @@ QEMU_DGFLAGS = -MMD -MP -MT $@ -MF $(@D)/$(*F).d
.PHONY : all clean build-all distclean
OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o netmain.o \
- virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o
+ virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o \
+ secure-ipl.o
SLOF_DIR := $(SRC_PATH)/../../roms/SLOF
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 4f54c643ff..3922e7cdde 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -15,6 +15,7 @@
#include "bootmap.h"
#include "virtio.h"
#include "bswap.h"
+#include "secure-ipl.h"
#ifdef DEBUG
/* #define DEBUG_FALLBACK */
@@ -617,7 +618,7 @@ static int ipl_eckd(void)
* Returns: length of the segment on sucess,
* negative value on error.
*/
-static int zipl_load_segment(ComponentEntry *entry, uint64_t address)
+int zipl_load_segment(ComponentEntry *entry, uint64_t address)
{
const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));
ScsiBlockPtr *bprs = (void *)sec;
@@ -735,7 +736,19 @@ static int zipl_run(ScsiBlockPtr *pte)
/* Load image(s) into RAM */
entry = (ComponentEntry *)(&header[1]);
- if (zipl_run_normal(&entry, tmp_sec)) {
+ switch (boot_mode) {
+ case ZIPL_BOOT_MODE_SECURE_AUDIT:
+ if (zipl_run_secure(&entry, tmp_sec)) {
+ return -1;
+ }
+ break;
+ case ZIPL_BOOT_MODE_NORMAL:
+ if (zipl_run_normal(&entry, tmp_sec)) {
+ return -1;
+ }
+ break;
+ default:
+ puts("Unknown boot mode");
return -1;
}
@@ -1101,17 +1114,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_BOOT_MODE_SECURE_AUDIT;
+ }
+
+ return ZIPL_BOOT_MODE_NORMAL;
+}
+
void zipl_load(void)
{
VDev *vdev = virtio_get_device();
if (vdev->is_cdrom) {
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
+ 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_BOOT_MODE_SECURE_AUDIT) {
+ panic("Virtio net boot device does not support secure boot!");
+ }
netmain();
puts("Failed to IPL from this network!");
return;
@@ -1122,6 +1153,10 @@ void zipl_load(void)
return;
}
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
+ 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..90fd530256 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 {
@@ -113,6 +122,8 @@ typedef struct ScsiMbr {
ScsiBlockPtr pt; /* block pointer to program table */
} __attribute__ ((packed)) ScsiMbr;
+int zipl_load_segment(ComponentEntry *entry, uint64_t address);
+
#define ZIPL_MAGIC "zIPL"
#define ZIPL_MAGIC_EBCDIC "\xa9\xc9\xd7\xd3"
#define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index c9328f1c51..668660e64d 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 == ZIPL_BOOT_MODE_UNSPECIFIED) {
+ boot_mode = zipl_mode(iplb->hdr_flags);
+ }
+
switch (cutype) {
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
+ 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 b1dc35cded..c2ba40d067 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,16 @@ int virtio_read(unsigned long sector, void *load_addr);
/* bootmap.c */
void zipl_load(void);
+typedef enum ZiplBootMode {
+ ZIPL_BOOT_MODE_UNSPECIFIED = 0,
+ ZIPL_BOOT_MODE_NORMAL = 1,
+ ZIPL_BOOT_MODE_SECURE_AUDIT = 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..8eab19cb09
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -0,0 +1,371 @@
+/*
+ * S/390 Secure IPL
+ *
+ * Functions to support IPL in secure boot mode (DIAG 320, DIAG 508,
+ * signature verification, and certificate handling).
+ *
+ * For secure IPL overview: docs/system/s390x/secure-ipl.rst
+ * For secure IPL technical: docs/specs/s390x-secure-ipl.rst
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "bootmap.h"
+#include "s390-ccw.h"
+#include "secure-ipl.h"
+
+uint8_t vcssb_data[VCSSB_MIN_LEN] __attribute__((__aligned__(PAGE_SIZE)));
+
+VCStorageSizeBlock *zipl_secure_get_vcssb(void)
+{
+ VCStorageSizeBlock *vcssb;
+ int rc;
+
+ if (!(sclp_is_diag320_on() && is_cert_store_facility_supported())) {
+ puts("Certificate Store Facility is not supported by the hypervisor!");
+ return NULL;
+ }
+
+ vcssb = (VCStorageSizeBlock *)vcssb_data;
+ /* avoid retrieving vcssb multiple times */
+ if (vcssb->length >= VCSSB_MIN_LEN) {
+ return vcssb;
+ }
+
+ vcssb->length = VCSSB_MIN_LEN;
+ rc = diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI);
+ if (rc != DIAG_320_RC_OK) {
+ return NULL;
+ }
+
+ return vcssb;
+}
+
+static uint32_t get_certs_length(void)
+{
+ VCStorageSizeBlock *vcssb;
+ uint32_t len;
+
+ vcssb = zipl_secure_get_vcssb();
+ if (vcssb == NULL) {
+ return 0;
+ }
+
+ len = vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct * VCE_HEADER_LEN;
+
+ return len;
+}
+
+static uint32_t request_certificate(uint8_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 = malloc(MAX_SECTOR_SIZE * 4);
+ 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) {
+ if (vcb->out_len == VCB_HEADER_LEN) {
+ puts("No certificate entry");
+ goto out;
+ }
+ if (vcb->remain_ct != 0) {
+ puts("Not enough memory to store all requested certificates");
+ goto out;
+ }
+
+ vce = (VCEntry *)vcb->vce_buf;
+ if (!is_vce_cert_valid(vce->flags, vce->len)) {
+ puts("Invalid certificate");
+ goto out;
+ }
+
+ cert_len = vce->cert_len;
+ memcpy(cert, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
+ }
+
+out:
+ free(vcb);
+ return cert_len;
+}
+
+static void cert_list_add(IplSignatureCertificateList *certs, int cert_index,
+ uint8_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]);
+}
+
+static void 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]);
+}
+
+static int 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;
+}
+
+static bool secure_ipl_supported(void)
+{
+ if (!sclp_is_sipl_on()) {
+ puts("Secure IPL Facility is not supported by the hypervisor!");
+ return false;
+ }
+
+ if (!is_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;
+}
+
+static void 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);
+}
+
+static uint32_t zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
+{
+ uint32_t sig_len;
+
+ if (zipl_load_segment(entry, sig_sec) < 0) {
+ return -1;
+ }
+
+ if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {
+ puts("Signature is not in DER format");
+ return -1;
+ }
+ sig_len = entry->compdat.sig_info.sig_len;
+
+ return sig_len;
+}
+
+static int handle_certificate(int *cert_table, uint8_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 (request_certificate(*cert, cert_idx)) {
+ 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;
+}
+
+int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
+{
+ IplDeviceComponentList comps;
+ IplSignatureCertificateList certs;
+ ComponentEntry *entry = *entry_ptr;
+ uint8_t *cert = NULL;
+ uint64_t *sig = NULL;
+ int cert_index = 0;
+ int comp_index = 0;
+ uint64_t comp_addr;
+ int comp_len;
+ uint32_t sig_len = 0;
+ uint64_t cert_len = -1;
+ uint8_t cert_idx = -1;
+ bool verified;
+ uint32_t certs_len;
+ /*
+ * Store indices of cert entry that have already used for signature verification
+ * to prevent allocating the same certificate multiple times.
+ * cert_table index: index of certificate from qemu cert store used for verification
+ * cert_table value: index of cert entry in cert list that contains the certificate
+ */
+ int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
+ int signed_count = 0;
+
+ if (!secure_ipl_supported()) {
+ return -1;
+ }
+
+ init_lists(&comps, &certs);
+ certs_len = get_certs_length();
+ cert = malloc(certs_len);
+ sig = malloc(MAX_SECTOR_SIZE);
+
+ while (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
+ switch (entry->component_type) {
+ case ZIPL_COMP_ENTRY_SIGNATURE:
+ if (sig_len) {
+ goto out;
+ }
+
+ sig_len = zipl_load_signature(entry, (uint64_t)sig);
+ if (sig_len < 0) {
+ goto out;
+ }
+ break;
+ case ZIPL_COMP_ENTRY_LOAD:
+ comp_addr = entry->compdat.load_addr;
+ comp_len = zipl_load_segment(entry, comp_addr);
+ if (comp_len < 0) {
+ goto out;
+ }
+
+ if (!sig_len) {
+ break;
+ }
+
+ verified = verify_signature(comp_len, comp_addr, sig_len, (uint64_t)sig,
+ &cert_len, &cert_idx);
+
+ if (verified) {
+ cert_index = handle_certificate(cert_table, &cert, cert_len, cert_idx,
+ &certs, cert_index);
+ if (cert_index == -1) {
+ goto out;
+ }
+
+ puts("Verified component");
+ 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 {
+ comp_list_add(&comps, comp_index, -1,
+ comp_addr, comp_len,
+ S390_IPL_COMPONENT_FLAG_SC);
+ zipl_secure_handle("Could not verify component");
+ }
+
+ comp_index++;
+ signed_count += 1;
+ /* After a signature is used another new one can be accepted */
+ sig_len = 0;
+ break;
+ default:
+ puts("Unknown component entry type");
+ return -1;
+ }
+
+ entry++;
+
+ if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
+ puts("Wrong entry value");
+ return -EINVAL;
+ }
+ }
+
+ if (signed_count == 0) {
+ zipl_secure_handle("Secure boot is on, but components are not signed");
+ }
+
+ if (update_iirb(&comps, &certs)) {
+ zipl_secure_handle("Failed to write IPL Information Report Block");
+ }
+
+ *entry_ptr = entry;
+ free(sig);
+
+ return 0;
+out:
+ free(cert);
+ free(sig);
+
+ return -1;
+}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
new file mode 100644
index 0000000000..a264a44349
--- /dev/null
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -0,0 +1,99 @@
+/*
+ * S/390 Secure IPL
+ *
+ * Copyright 2025 IBM Corp.
+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef _PC_BIOS_S390_CCW_SECURE_IPL_H
+#define _PC_BIOS_S390_CCW_SECURE_IPL_H
+
+#include <diag320.h>
+#include <diag508.h>
+
+VCStorageSizeBlock *zipl_secure_get_vcssb(void);
+int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
+
+static inline void zipl_secure_handle(const char *message)
+{
+ switch (boot_mode) {
+ case ZIPL_BOOT_MODE_SECURE_AUDIT:
+ IPL_check(false, message);
+ break;
+ default:
+ break;
+ }
+}
+
+static inline uint64_t diag320(void *data, unsigned long subcode)
+{
+ register unsigned long addr asm("0") = (unsigned long)data;
+ register unsigned long rc asm("1") = 0;
+
+ asm volatile ("diag %0,%2,0x320\n"
+ : "+d" (addr), "+d" (rc)
+ : "d" (subcode)
+ : "memory", "cc");
+ return rc;
+}
+
+static inline bool is_vce_cert_valid(uint8_t vce_flags, uint32_t vce_len)
+{
+ return (vce_flags & DIAG_320_VCE_FLAGS_VALID) && (vce_len > VCE_INVALID_LEN);
+}
+
+static inline bool is_cert_store_facility_supported(void)
+{
+ uint32_t d320_ism;
+
+ diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
+ return (d320_ism & DIAG_320_ISM_QUERY_SUBCODES) &&
+ (d320_ism & DIAG_320_ISM_QUERY_VCSI) &&
+ (d320_ism & DIAG_320_ISM_STORE_VC);
+}
+
+static inline uint64_t _diag508(void *data, unsigned long subcode)
+{
+ register unsigned long addr asm("0") = (unsigned long)data;
+ register unsigned long rc asm("1") = 0;
+
+ asm volatile ("diag %0,%2,0x508\n"
+ : "+d" (addr), "+d" (rc)
+ : "d" (subcode)
+ : "memory", "cc");
+ return rc;
+}
+
+static inline bool is_secure_ipl_extension_supported(void)
+{
+ uint64_t d508_subcodes;
+
+ d508_subcodes = _diag508(NULL, DIAG_508_SUBC_QUERY_SUBC);
+ return d508_subcodes & DIAG_508_SUBC_SIG_VERIF;
+}
+
+static inline bool verify_signature(uint64_t comp_len, uint64_t comp_addr,
+ uint64_t sig_len, uint64_t sig_addr,
+ uint64_t *cert_len, uint8_t *cert_idx)
+{
+ Diag508SigVerifBlock svb;
+
+ svb.length = sizeof(Diag508SigVerifBlock);
+ svb.version = 0;
+ svb.comp_len = comp_len;
+ svb.comp_addr = comp_addr;
+ svb.sig_len = sig_len;
+ svb.sig_addr = sig_addr;
+
+ if (_diag508(&svb, DIAG_508_SUBC_SIG_VERIF) == DIAG_508_RC_OK) {
+ *cert_len = svb.cert_len;
+ *cert_idx = svb.cert_store_index;
+ return true;
+ }
+
+ return false;
+}
+
+#endif /* _PC_BIOS_S390_CCW_SECURE_IPL_H */
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
2025-09-17 23:21 ` [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2025-09-26 13:10 ` Thomas Huth
2025-09-30 18:42 ` Farhan Ali
1 sibling, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-26 13:10 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Enable secure IPL in audit mode, which performs signature verification,
> but any error does not terminate the boot process. Only warnings will be
> logged to the console instead.
>
> Add a comp_len variable to store the length of a segment in
> zipl_load_segment. comp_len variable is necessary to store the
> calculated segment length and is used during signature verification.
> Return the length on success, or a negative return code on failure.
>
> Secure IPL in audit mode requires at least one certificate provided in
> the key store along with necessary facilities (Secure IPL Facility,
> Certificate Store Facility and secure IPL extension support).
>
> Note: Secure IPL in audit mode is implemented for the SCSI scheme of
> virtio-blk/virtio-scsi devices.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/system/s390x/secure-ipl.rst | 36 +++
> pc-bios/s390-ccw/Makefile | 3 +-
> pc-bios/s390-ccw/bootmap.c | 39 +++-
> pc-bios/s390-ccw/bootmap.h | 11 +
> pc-bios/s390-ccw/main.c | 9 +
> pc-bios/s390-ccw/s390-ccw.h | 15 ++
> pc-bios/s390-ccw/sclp.c | 44 ++++
> pc-bios/s390-ccw/sclp.h | 6 +
> pc-bios/s390-ccw/secure-ipl.c | 371 +++++++++++++++++++++++++++++++
> pc-bios/s390-ccw/secure-ipl.h | 99 +++++++++
> 10 files changed, 630 insertions(+), 3 deletions(-)
> create mode 100644 pc-bios/s390-ccw/secure-ipl.c
> create mode 100644 pc-bios/s390-ccw/secure-ipl.h
>
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> index 92c1bb2153..701594b9de 100644
> --- a/docs/system/s390x/secure-ipl.rst
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -19,3 +19,39 @@ Note: certificate files must have a .pem extension.
> qemu-system-s390x -machine s390-ccw-virtio, \
> boot-certs.0.path=/.../qemu/certs, \
> boot-certs.1.path=/another/path/cert.pem ...
> +
> +
> +IPL Modes
> +=========
> +
> +The concept of IPL Modes are introduced to differentiate between the IPL configurations.
This is not a commit description, but documentation that persists, so "are
introduced" sounds wrong here.
Maybe rather something like:
Multiple IPL modes are available to differentiate between the various IPL
configurations.
?
> +These modes are mutually exclusive and enabled based on the ``boot-certs`` option on the
> +QEMU command line.
> +
> +Normal Mode
> +-----------
> +
> +The absence of certificates will attempt to IPL a guest without secure IPL operations.
> +No checks are performed, and no warnings/errors are reported. This is the default mode.
> +
> +Configuration:
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio ...
> +
> +Audit Mode
> +----------
> +
> +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.
"errors ... will report a warning" sounds weird. maybe rather: "errors ...
will result in a warning" ?
...
> diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h
> index 95943441d3..90fd530256 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;
Everything is naturally aligned, no need to use packed here.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
2025-09-17 23:21 ` [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
2025-09-26 13:10 ` Thomas Huth
@ 2025-09-30 18:42 ` Farhan Ali
2025-10-10 18:00 ` Zhuoying Cai
1 sibling, 1 reply; 89+ messages in thread
From: Farhan Ali @ 2025-09-30 18:42 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
> Enable secure IPL in audit mode, which performs signature verification,
> but any error does not terminate the boot process. Only warnings will be
> logged to the console instead.
>
> Add a comp_len variable to store the length of a segment in
> zipl_load_segment. comp_len variable is necessary to store the
> calculated segment length and is used during signature verification.
> Return the length on success, or a negative return code on failure.
>
> Secure IPL in audit mode requires at least one certificate provided in
> the key store along with necessary facilities (Secure IPL Facility,
> Certificate Store Facility and secure IPL extension support).
>
> Note: Secure IPL in audit mode is implemented for the SCSI scheme of
> virtio-blk/virtio-scsi devices.
>
> Signed-off-by: Zhuoying Cai<zycai@linux.ibm.com>
> ---
> docs/system/s390x/secure-ipl.rst | 36 +++
> pc-bios/s390-ccw/Makefile | 3 +-
> pc-bios/s390-ccw/bootmap.c | 39 +++-
> pc-bios/s390-ccw/bootmap.h | 11 +
> pc-bios/s390-ccw/main.c | 9 +
> pc-bios/s390-ccw/s390-ccw.h | 15 ++
> pc-bios/s390-ccw/sclp.c | 44 ++++
> pc-bios/s390-ccw/sclp.h | 6 +
> pc-bios/s390-ccw/secure-ipl.c | 371 +++++++++++++++++++++++++++++++
> pc-bios/s390-ccw/secure-ipl.h | 99 +++++++++
> 10 files changed, 630 insertions(+), 3 deletions(-)
> create mode 100644 pc-bios/s390-ccw/secure-ipl.c
> create mode 100644 pc-bios/s390-ccw/secure-ipl.h
>
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> index 92c1bb2153..701594b9de 100644
> --- a/docs/system/s390x/secure-ipl.rst
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -19,3 +19,39 @@ Note: certificate files must have a .pem extension.
> qemu-system-s390x -machine s390-ccw-virtio, \
> boot-certs.0.path=/.../qemu/certs, \
> boot-certs.1.path=/another/path/cert.pem ...
> +
> +
> +IPL Modes
> +=========
> +
> +The concept of IPL Modes are introduced to differentiate between the IPL configurations.
> +These modes are mutually exclusive and enabled based on the ``boot-certs`` option on the
> +QEMU command line.
> +
> +Normal Mode
> +-----------
> +
> +The absence of certificates will attempt to IPL a guest without secure IPL operations.
> +No checks are performed, and no warnings/errors are reported. This is the default mode.
> +
> +Configuration:
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio ...
> +
> +Audit Mode
> +----------
> +
> +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.
> +
> +Configuration:
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio, \
> + boot-certs.0.path=/.../qemu/certs, \
> + boot-certs.1.path=/another/path/cert.pem ...
> diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
> index a0f24c94a8..603761a857 100644
> --- a/pc-bios/s390-ccw/Makefile
> +++ b/pc-bios/s390-ccw/Makefile
> @@ -34,7 +34,8 @@ QEMU_DGFLAGS = -MMD -MP -MT $@ -MF $(@D)/$(*F).d
> .PHONY : all clean build-all distclean
>
> OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o netmain.o \
> - virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o
> + virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o \
> + secure-ipl.o
>
> SLOF_DIR := $(SRC_PATH)/../../roms/SLOF
>
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index 4f54c643ff..3922e7cdde 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -15,6 +15,7 @@
> #include "bootmap.h"
> #include "virtio.h"
> #include "bswap.h"
> +#include "secure-ipl.h" #ifdef DEBUG /* #define DEBUG_FALLBACK */ @@ -617,7 +618,7 @@ static
> int ipl_eckd(void) * Returns: length of the segment on sucess, *
> negative value on error. */ -static int
> zipl_load_segment(ComponentEntry *entry, uint64_t address) +int
> zipl_load_segment(ComponentEntry *entry, uint64_t address) { const int
> max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr)); ScsiBlockPtr
> *bprs = (void *)sec; @@ -735,7 +736,19 @@ static int
> zipl_run(ScsiBlockPtr *pte) /* Load image(s) into RAM */ entry =
> (ComponentEntry *)(&header[1]); - if (zipl_run_normal(&entry,
> tmp_sec)) { + switch (boot_mode) { + case ZIPL_BOOT_MODE_SECURE_AUDIT:
> + if (zipl_run_secure(&entry, tmp_sec)) { + return -1; + } + break; +
> case ZIPL_BOOT_MODE_NORMAL: + if (zipl_run_normal(&entry, tmp_sec)) {
> + return -1; + } + break; + default: + puts("Unknown boot mode");
> return -1;
> }
>
> @@ -1101,17 +1114,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_BOOT_MODE_SECURE_AUDIT;
> + }
> +
> + return ZIPL_BOOT_MODE_NORMAL;
> +}
> +
> void zipl_load(void)
> {
> VDev *vdev = virtio_get_device();
>
> if (vdev->is_cdrom) {
> + if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
> + 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_BOOT_MODE_SECURE_AUDIT) {
> + panic("Virtio net boot device does not support secure boot!");
> + }
> netmain();
> puts("Failed to IPL from this network!");
> return;
> @@ -1122,6 +1153,10 @@ void zipl_load(void)
> return;
> }
>
> + if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
> + 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..90fd530256 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 {
> @@ -113,6 +122,8 @@ typedef struct ScsiMbr {
> ScsiBlockPtr pt; /* block pointer to program table */
> } __attribute__ ((packed)) ScsiMbr;
>
> +int zipl_load_segment(ComponentEntry *entry, uint64_t address);
> +
> #define ZIPL_MAGIC "zIPL"
> #define ZIPL_MAGIC_EBCDIC "\xa9\xc9\xd7\xd3"
> #define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */
> diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
> index c9328f1c51..668660e64d 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 == ZIPL_BOOT_MODE_UNSPECIFIED) {
> + boot_mode = zipl_mode(iplb->hdr_flags);
> + }
> +
> switch (cutype) {
> case CU_TYPE_DASD_3990:
> case CU_TYPE_DASD_2107:
> + if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
> + panic("Passthrough (vfio) device does not support secure boot!");
Nit: We could be more specific here and mention "Passthrough (vfio) CCW
device...". I know we don't support any other vfio device for booting
today but just makes it clear what kind of passthrough device we are
dealing with.
> + }
> +
> 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 b1dc35cded..c2ba40d067 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,16 @@ int virtio_read(unsigned long sector, void *load_addr);
> /* bootmap.c */
> void zipl_load(void);
>
> +typedef enum ZiplBootMode {
> + ZIPL_BOOT_MODE_UNSPECIFIED = 0,
> + ZIPL_BOOT_MODE_NORMAL = 1,
> + ZIPL_BOOT_MODE_SECURE_AUDIT = 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..8eab19cb09
> --- /dev/null
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -0,0 +1,371 @@
> +/*
> + * S/390 Secure IPL
> + *
> + * Functions to support IPL in secure boot mode (DIAG 320, DIAG 508,
> + * signature verification, and certificate handling).
> + *
> + * For secure IPL overview: docs/system/s390x/secure-ipl.rst
> + * For secure IPL technical: docs/specs/s390x-secure-ipl.rst
> + *
> + * Copyright 2025 IBM Corp.
> + * Author(s): Zhuoying Cai<zycai@linux.ibm.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include "bootmap.h"
> +#include "s390-ccw.h"
> +#include "secure-ipl.h"
> +
> +uint8_t vcssb_data[VCSSB_MIN_LEN] __attribute__((__aligned__(PAGE_SIZE)));
> +
> +VCStorageSizeBlock *zipl_secure_get_vcssb(void)
> +{
> + VCStorageSizeBlock *vcssb;
> + int rc;
> +
> + if (!(sclp_is_diag320_on() && is_cert_store_facility_supported())) {
Should we fail if either diag320 or certificate store is not available?
Might make the check simpler
> + puts("Certificate Store Facility is not supported by the hypervisor!");
> + return NULL;
> + }
> +
> + vcssb = (VCStorageSizeBlock *)vcssb_data;
> + /* avoid retrieving vcssb multiple times */
> + if (vcssb->length >= VCSSB_MIN_LEN) {
> + return vcssb;
> + }
> +
> + vcssb->length = VCSSB_MIN_LEN;
> + rc = diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI);
> + if (rc != DIAG_320_RC_OK) {
> + return NULL;
> + }
> +
> + return vcssb;
> +}
> +
> +static uint32_t get_certs_length(void)
> +{
> + VCStorageSizeBlock *vcssb;
> + uint32_t len;
> +
> + vcssb = zipl_secure_get_vcssb();
> + if (vcssb == NULL) {
> + return 0;
> + }
> +
> + len = vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct * VCE_HEADER_LEN;
> +
> + return len;
> +}
> +
> +static uint32_t request_certificate(uint8_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 = malloc(MAX_SECTOR_SIZE * 4);
Why are we using MAX_SECTOR _SIZE? Shouldn't we use max_single_vcb_len
to allocate the memory for vcb?
> + 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) {
Nit: We could just check if rc != DIAG_320_RC_OK and goto out. This way
we avoid a bigger if block.
> + if (vcb->out_len == VCB_HEADER_LEN) {
> + puts("No certificate entry");
> + goto out;
> + }
> + if (vcb->remain_ct != 0) {
> + puts("Not enough memory to store all requested certificates");
> + goto out;
> + }
> +
> + vce = (VCEntry *)vcb->vce_buf;
> + if (!is_vce_cert_valid(vce->flags, vce->len)) {
> + puts("Invalid certificate");
> + goto out;
> + }
> +
> + cert_len = vce->cert_len;
> + memcpy(cert, (uint8_t *)vce + vce->cert_offset, vce->cert_len);
> + }
> +
> +out:
> + free(vcb);
> + return cert_len;
> +}
> +
> +static void cert_list_add(IplSignatureCertificateList *certs, int cert_index,
> + uint8_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]);
> +}
> +
> +static void 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]);
> +}
> +
> +static int 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;
> +}
> +
> +static bool secure_ipl_supported(void)
> +{
> + if (!sclp_is_sipl_on()) {
> + puts("Secure IPL Facility is not supported by the hypervisor!");
> + return false;
> + }
> +
> + if (!is_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;
> +}
> +
> +static void 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);
> +}
> +
> +static uint32_t zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
> +{
> + uint32_t sig_len;
> +
> + if (zipl_load_segment(entry, sig_sec) < 0) {
> + return -1;
> + }
> +
> + if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {
> + puts("Signature is not in DER format");
> + return -1;
> + }
> + sig_len = entry->compdat.sig_info.sig_len;
> +
> + return sig_len;
> +}
> +
> +static int handle_certificate(int *cert_table, uint8_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 (request_certificate(*cert, cert_idx)) {
> + cert_list_add(certs, cert_index, *cert, cert_len);
> + cert_table[cert_idx] = cert_index;
> + *cert += cert_len;
It's hard to understand why we increment *cert in this function by just
looking at the function. But this function is called in the loop in
zipl_run_secure, could we move this entire function in zipl_run_secure?
> + } else {
> + puts("Could not get certificate");
> + return -1;
> + }
> +
> + /* increment cert_index for the next cert entry */
> + return ++cert_index;
> + }
> +
> + return cert_index;
> +}
> +
> +int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
> +{
> + IplDeviceComponentList comps;
> + IplSignatureCertificateList certs;
> + ComponentEntry *entry = *entry_ptr;
> + uint8_t *cert = NULL;
> + uint64_t *sig = NULL;
> + int cert_index = 0;
> + int comp_index = 0;
> + uint64_t comp_addr;
> + int comp_len;
> + uint32_t sig_len = 0;
> + uint64_t cert_len = -1;
> + uint8_t cert_idx = -1;
Why do we have 2 variables (cert_idx, cert_index) that are named
similar? Can we rename cert_index to cert_table_idx?
> + bool verified;
> + uint32_t certs_len;
> + /*
> + * Store indices of cert entry that have already used for signature verification
> + * to prevent allocating the same certificate multiple times.
> + * cert_table index: index of certificate from qemu cert store used for verification
> + * cert_table value: index of cert entry in cert list that contains the certificate
> + */
> + int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
> + int signed_count = 0;
> +
> + if (!secure_ipl_supported()) {
> + return -1;
> + }
> +
> + init_lists(&comps, &certs);
> + certs_len = get_certs_length();
> + cert = malloc(certs_len);
> + sig = malloc(MAX_SECTOR_SIZE);
> +
> + while (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
> + switch (entry->component_type) {
> + case ZIPL_COMP_ENTRY_SIGNATURE:
> + if (sig_len) {
> + goto out;
> + }
> +
> + sig_len = zipl_load_signature(entry, (uint64_t)sig);
> + if (sig_len < 0) {
> + goto out;
> + }
> + break;
> + case ZIPL_COMP_ENTRY_LOAD:
> + comp_addr = entry->compdat.load_addr;
> + comp_len = zipl_load_segment(entry, comp_addr);
> + if (comp_len < 0) {
> + goto out;
> + }
> +
> + if (!sig_len) {
> + break;
> + }
> +
> + verified = verify_signature(comp_len, comp_addr, sig_len, (uint64_t)sig,
> + &cert_len, &cert_idx);
> +
> + if (verified) {
> + cert_index = handle_certificate(cert_table, &cert, cert_len, cert_idx,
> + &certs, cert_index);
> + if (cert_index == -1) {
> + goto out;
> + }
> +
> + puts("Verified component");
> + 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 {
> + comp_list_add(&comps, comp_index, -1,
> + comp_addr, comp_len,
> + S390_IPL_COMPONENT_FLAG_SC);
> + zipl_secure_handle("Could not verify component");
> + }
> +
> + comp_index++;
> + signed_count += 1;
> + /* After a signature is used another new one can be accepted */
> + sig_len = 0;
> + break;
> + default:
> + puts("Unknown component entry type");
> + return -1;
> + }
> +
> + entry++;
> +
> + if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
> + puts("Wrong entry value");
> + return -EINVAL;
> + }
> + }
> +
> + if (signed_count == 0) {
> + zipl_secure_handle("Secure boot is on, but components are not signed");
> + }
> +
> + if (update_iirb(&comps, &certs)) {
> + zipl_secure_handle("Failed to write IPL Information Report Block");
> + }
> +
> + *entry_ptr = entry;
> + free(sig);
> +
> + return 0;
> +out:
> + free(cert);
> + free(sig);
> +
> + return -1;
> +}
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> new file mode 100644
> index 0000000000..a264a44349
> --- /dev/null
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -0,0 +1,99 @@
> +/*
> + * S/390 Secure IPL
> + *
> + * Copyright 2025 IBM Corp.
> + * Author(s): Zhuoying Cai<zycai@linux.ibm.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef _PC_BIOS_S390_CCW_SECURE_IPL_H
> +#define _PC_BIOS_S390_CCW_SECURE_IPL_H
> +
> +#include <diag320.h>
> +#include <diag508.h>
> +
> +VCStorageSizeBlock *zipl_secure_get_vcssb(void);
> +int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
> +
> +static inline void zipl_secure_handle(const char *message)
> +{
> + switch (boot_mode) {
> + case ZIPL_BOOT_MODE_SECURE_AUDIT:
> + IPL_check(false, message);
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static inline uint64_t diag320(void *data, unsigned long subcode)
> +{
> + register unsigned long addr asm("0") = (unsigned long)data;
> + register unsigned long rc asm("1") = 0;
> +
> + asm volatile ("diag %0,%2,0x320\n"
> + : "+d" (addr), "+d" (rc)
> + : "d" (subcode)
> + : "memory", "cc");
> + return rc;
> +}
> +
> +static inline bool is_vce_cert_valid(uint8_t vce_flags, uint32_t vce_len)
> +{
> + return (vce_flags & DIAG_320_VCE_FLAGS_VALID) && (vce_len > VCE_INVALID_LEN);
Shouldn't just checking the vce_flag be enough?
> +}
> +
> +static inline bool is_cert_store_facility_supported(void)
> +{
> + uint32_t d320_ism;
> +
> + diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);
> + return (d320_ism & DIAG_320_ISM_QUERY_SUBCODES) &&
> + (d320_ism & DIAG_320_ISM_QUERY_VCSI) &&
> + (d320_ism & DIAG_320_ISM_STORE_VC);
> +}
> +
> +static inline uint64_t _diag508(void *data, unsigned long subcode)
> +{
> + register unsigned long addr asm("0") = (unsigned long)data;
> + register unsigned long rc asm("1") = 0;
> +
> + asm volatile ("diag %0,%2,0x508\n"
> + : "+d" (addr), "+d" (rc)
> + : "d" (subcode)
> + : "memory", "cc");
> + return rc;
> +}
> +
> +static inline bool is_secure_ipl_extension_supported(void)
> +{
> + uint64_t d508_subcodes;
> +
> + d508_subcodes = _diag508(NULL, DIAG_508_SUBC_QUERY_SUBC);
> + return d508_subcodes & DIAG_508_SUBC_SIG_VERIF;
> +}
> +
> +static inline bool verify_signature(uint64_t comp_len, uint64_t comp_addr,
> + uint64_t sig_len, uint64_t sig_addr,
> + uint64_t *cert_len, uint8_t *cert_idx)
> +{
> + Diag508SigVerifBlock svb;
> +
> + svb.length = sizeof(Diag508SigVerifBlock);
> + svb.version = 0;
> + svb.comp_len = comp_len;
> + svb.comp_addr = comp_addr;
> + svb.sig_len = sig_len;
> + svb.sig_addr = sig_addr;
> +
> + if (_diag508(&svb, DIAG_508_SUBC_SIG_VERIF) == DIAG_508_RC_OK) {
> + *cert_len = svb.cert_len;
> + *cert_idx = svb.cert_store_index;
> + return true;
> + }
> +
> + return false;
> +}
> +
> +#endif /* _PC_BIOS_S390_CCW_SECURE_IPL_H */
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
2025-09-30 18:42 ` Farhan Ali
@ 2025-10-10 18:00 ` Zhuoying Cai
2025-10-10 19:37 ` Farhan Ali
0 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-10-10 18:00 UTC (permalink / raw)
To: Farhan Ali, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 9/30/25 2:42 PM, Farhan Ali wrote:
>
> On 9/17/2025 4:21 PM, Zhuoying Cai wrote:
>> Enable secure IPL in audit mode, which performs signature verification,
>> but any error does not terminate the boot process. Only warnings will be
>> logged to the console instead.
>>
>> Add a comp_len variable to store the length of a segment in
>> zipl_load_segment. comp_len variable is necessary to store the
>> calculated segment length and is used during signature verification.
>> Return the length on success, or a negative return code on failure.
>>
>> Secure IPL in audit mode requires at least one certificate provided in
>> the key store along with necessary facilities (Secure IPL Facility,
>> Certificate Store Facility and secure IPL extension support).
>>
>> Note: Secure IPL in audit mode is implemented for the SCSI scheme of
>> virtio-blk/virtio-scsi devices.
>>
>> Signed-off-by: Zhuoying Cai<zycai@linux.ibm.com>
>> ---
>> docs/system/s390x/secure-ipl.rst | 36 +++
>> pc-bios/s390-ccw/Makefile | 3 +-
>> pc-bios/s390-ccw/bootmap.c | 39 +++-
>> pc-bios/s390-ccw/bootmap.h | 11 +
>> pc-bios/s390-ccw/main.c | 9 +
>> pc-bios/s390-ccw/s390-ccw.h | 15 ++
>> pc-bios/s390-ccw/sclp.c | 44 ++++
>> pc-bios/s390-ccw/sclp.h | 6 +
>> pc-bios/s390-ccw/secure-ipl.c | 371 +++++++++++++++++++++++++++++++
>> pc-bios/s390-ccw/secure-ipl.h | 99 +++++++++
>> 10 files changed, 630 insertions(+), 3 deletions(-)
>> create mode 100644 pc-bios/s390-ccw/secure-ipl.c
>> create mode 100644 pc-bios/s390-ccw/secure-ipl.h
>>
[...]
>> +
>> +static int handle_certificate(int *cert_table, uint8_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 (request_certificate(*cert, cert_idx)) {
>> + cert_list_add(certs, cert_index, *cert, cert_len);
>> + cert_table[cert_idx] = cert_index;
>> + *cert += cert_len;
>
> It's hard to understand why we increment *cert in this function by just
> looking at the function. But this function is called in the loop in
> zipl_run_secure, could we move this entire function in zipl_run_secure?
>
>
Thanks for the suggestion.
I'm concerned that moving this function into zipl_run_secure() could
make it harder to read, since it's already quite large. I'd prefer to
keep this function separate because it handles all certificate related
operations here and add comments to clarify why *cert is incremented.
I'd be happy to hear additional thoughts.
>> + } else {
>> + puts("Could not get certificate");
>> + return -1;
>> + }
>> +
>> + /* increment cert_index for the next cert entry */
>> + return ++cert_index;
>> + }
>> +
>> + return cert_index;
>> +}
>> +
>> +int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
>> +{
>> + IplDeviceComponentList comps;
>> + IplSignatureCertificateList certs;
>> + ComponentEntry *entry = *entry_ptr;
>> + uint8_t *cert = NULL;
>> + uint64_t *sig = NULL;
>> + int cert_index = 0;
>> + int comp_index = 0;
>> + uint64_t comp_addr;
>> + int comp_len;
>> + uint32_t sig_len = 0;
>> + uint64_t cert_len = -1;
>> + uint8_t cert_idx = -1;
>
> Why do we have 2 variables (cert_idx, cert_index) that are named
> similar? Can we rename cert_index to cert_table_idx?
>
>
>> + bool verified;
>> + uint32_t certs_len;
>> + /*
>> + * Store indices of cert entry that have already used for signature verification
>> + * to prevent allocating the same certificate multiple times.
>> + * cert_table index: index of certificate from qemu cert store used for verification
>> + * cert_table value: index of cert entry in cert list that contains the certificate
>> + */
>> + int cert_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1};
>> + int signed_count = 0;
>> +
>> + if (!secure_ipl_supported()) {
>> + return -1;
>> + }
>> +
>> + init_lists(&comps, &certs);
>> + certs_len = get_certs_length();
>> + cert = malloc(certs_len);
>> + sig = malloc(MAX_SECTOR_SIZE);
>> +
>> + while (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
>> + switch (entry->component_type) {
>> + case ZIPL_COMP_ENTRY_SIGNATURE:
>> + if (sig_len) {
>> + goto out;
>> + }
>> +
>> + sig_len = zipl_load_signature(entry, (uint64_t)sig);
>> + if (sig_len < 0) {
>> + goto out;
>> + }
>> + break;
>> + case ZIPL_COMP_ENTRY_LOAD:
>> + comp_addr = entry->compdat.load_addr;
>> + comp_len = zipl_load_segment(entry, comp_addr);
>> + if (comp_len < 0) {
>> + goto out;
>> + }
>> +
>> + if (!sig_len) {
>> + break;
>> + }
>> +
>> + verified = verify_signature(comp_len, comp_addr, sig_len, (uint64_t)sig,
>> + &cert_len, &cert_idx);
>> +
>> + if (verified) {
>> + cert_index = handle_certificate(cert_table, &cert, cert_len, cert_idx,
>> + &certs, cert_index);
>> + if (cert_index == -1) {
>> + goto out;
>> + }
>> +
>> + puts("Verified component");
>> + 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 {
>> + comp_list_add(&comps, comp_index, -1,
>> + comp_addr, comp_len,
>> + S390_IPL_COMPONENT_FLAG_SC);
>> + zipl_secure_handle("Could not verify component");
>> + }
>> +
>> + comp_index++;
>> + signed_count += 1;
>> + /* After a signature is used another new one can be accepted */
>> + sig_len = 0;
>> + break;
>> + default:
>> + puts("Unknown component entry type");
>> + return -1;
>> + }
>> +
>> + entry++;
>> +
>> + if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {
>> + puts("Wrong entry value");
>> + return -EINVAL;
>> + }
>> + }
>> +
>> + if (signed_count == 0) {
>> + zipl_secure_handle("Secure boot is on, but components are not signed");
>> + }
>> +
>> + if (update_iirb(&comps, &certs)) {
>> + zipl_secure_handle("Failed to write IPL Information Report Block");
>> + }
>> +
>> + *entry_ptr = entry;
>> + free(sig);
>> +
>> + return 0;
>> +out:
>> + free(cert);
>> + free(sig);
>> +
>> + return -1;
>> +}
[...]
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode
2025-10-10 18:00 ` Zhuoying Cai
@ 2025-10-10 19:37 ` Farhan Ali
0 siblings, 0 replies; 89+ messages in thread
From: Farhan Ali @ 2025-10-10 19:37 UTC (permalink / raw)
To: Zhuoying Cai, thuth, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru
On 10/10/2025 11:00 AM, Zhuoying Cai wrote:
>>> +
>>> +static int handle_certificate(int *cert_table, uint8_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 (request_certificate(*cert, cert_idx)) {
>>> + cert_list_add(certs, cert_index, *cert, cert_len);
>>> + cert_table[cert_idx] = cert_index;
>>> + *cert += cert_len;
>> It's hard to understand why we increment *cert in this function by just
>> looking at the function. But this function is called in the loop in
>> zipl_run_secure, could we move this entire function in zipl_run_secure?
>>
>>
> Thanks for the suggestion.
>
> I'm concerned that moving this function into zipl_run_secure() could
> make it harder to read, since it's already quite large. I'd prefer to
> keep this function separate because it handles all certificate related
> operations here and add comments to clarify why *cert is incremented.
> I'd be happy to hear additional thoughts.
Maybe we could simplify this by at least moving the increment (both
cert_index and *cert) to zipl_run_secure() if this function succeeds?
That way it will be easier to follow in the loop itself?
Thanks
Farhan
>>> + } 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] 89+ messages in thread
* [PATCH v6 20/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (18 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 19/28] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-29 12:25 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
` (8 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
The secure-IPL-code-loading-attributes facility (SCLAF)
provides additional security during secure IPL.
Availability of SCLAF is determined by byte 136 bit 3 of the
SCLP Read Info block.
This feature is available starting with the gen16 CPU model.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
Reviewed-by: Collin Walling <walling@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 25 +++++++++++++++++++++++++
target/s390x/cpu_features.c | 2 ++
target/s390x/cpu_features_def.h.inc | 1 +
target/s390x/cpu_models.c | 3 +++
target/s390x/gen-features.c | 2 ++
target/s390x/kvm/kvm.c | 1 +
6 files changed, 34 insertions(+)
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index 760a066084..a19b976e25 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -85,3 +85,28 @@ operations such as:
* certificate data
The guest kernel will inspect the IIRB and build the keyring.
+
+
+Secure Code Loading Attributes Facility
+---------------------------------
+
+The Secure Code Loading Attributes Facility (SCLAF) enhances system security during the
+IPL by enforcing additional verification rules.
+
+When SCLAF is available, its behavior depends on the IPL mode. It introduces verification
+of both signed and unsigned components to help ensure that only authorized code is loaded
+during the IPL process. Any errors detected by SCLAF are reported in the IIRB.
+
+Unsigned components are restricted to load addresses at or above absolute storage address
+``0x2000``.
+
+Signed components must include a Secure Code Loading Attribute Block (SCLAB), which is
+appended at the very end of the component. The SCLAB defines security attributes for
+handling the signed code. Specifically, it may:
+
+* Provide direction on how to process the rest of the component.
+
+* Provide further validation of information on where to load the signed binary code
+ from the load device.
+
+* Specify where to start the execution of the loaded OS code.
diff --git a/target/s390x/cpu_features.c b/target/s390x/cpu_features.c
index 200bd8c15b..29ea3bfec2 100644
--- a/target/s390x/cpu_features.c
+++ b/target/s390x/cpu_features.c
@@ -120,6 +120,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
* - All SIE facilities because SIE is not available
* - DIAG318
* - Secure IPL Facility
+ * - Secure IPL Code Loading Attributes Facility
*
* As VMs can move in and out of protected mode the CPU model
* doesn't protect us from that problem because it is only
@@ -152,6 +153,7 @@ void s390_fill_feat_block(const S390FeatBitmap features, S390FeatType type,
break;
case S390_FEAT_TYPE_SCLP_FAC_IPL:
clear_be_bit(s390_feat_def(S390_FEAT_SIPL)->bit, data);
+ clear_be_bit(s390_feat_def(S390_FEAT_SCLAF)->bit, data);
break;
default:
return;
diff --git a/target/s390x/cpu_features_def.h.inc b/target/s390x/cpu_features_def.h.inc
index 55eef618b8..ecfca0faef 100644
--- a/target/s390x/cpu_features_def.h.inc
+++ b/target/s390x/cpu_features_def.h.inc
@@ -142,6 +142,7 @@ DEF_FEAT(CERT_STORE, "cstore", SCLP_FAC134, 5, "Provide Certificate Store functi
/* Features exposed via SCLP SCCB Facilities byte 136 - 137 (bit numbers relative to byte-136) */
DEF_FEAT(SIPL, "sipl", SCLP_FAC_IPL, 1, "Secure-IPL facility")
+DEF_FEAT(SCLAF, "sclaf", SCLP_FAC_IPL, 3, "Secure-IPL-code-loading-attributes facility")
/* Features exposed via SCLP CPU info. */
DEF_FEAT(SIE_F2, "sief2", SCLP_CPU, 4, "SIE: interception format 2 (Virtual SIE)")
diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c
index f99536ef9a..7d214b5f72 100644
--- a/target/s390x/cpu_models.c
+++ b/target/s390x/cpu_models.c
@@ -264,6 +264,7 @@ bool s390_has_feat(S390Feat feat)
case S390_FEAT_SIE_PFMFI:
case S390_FEAT_SIE_IBS:
case S390_FEAT_SIPL:
+ case S390_FEAT_SCLAF:
case S390_FEAT_CONFIGURATION_TOPOLOGY:
return false;
break;
@@ -509,6 +510,8 @@ static void check_consistency(const S390CPUModel *model)
{ S390_FEAT_DIAG_318, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_CERT_STORE, S390_FEAT_EXTENDED_LENGTH_SCCB },
{ S390_FEAT_SIPL, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_SCLAF, S390_FEAT_EXTENDED_LENGTH_SCCB },
+ { S390_FEAT_SCLAF, S390_FEAT_SIPL },
{ S390_FEAT_NNPA, S390_FEAT_VECTOR },
{ S390_FEAT_RDP, S390_FEAT_LOCAL_TLB_CLEARING },
{ S390_FEAT_UV_FEAT_AP, S390_FEAT_AP },
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index bd2060ab93..c3e0c6ceff 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -722,6 +722,7 @@ static uint16_t full_GEN16_GA1[] = {
S390_FEAT_UV_FEAT_AP_INTR,
S390_FEAT_CERT_STORE,
S390_FEAT_SIPL,
+ S390_FEAT_SCLAF,
};
static uint16_t full_GEN17_GA1[] = {
@@ -924,6 +925,7 @@ static uint16_t qemu_MAX[] = {
S390_FEAT_EXTENDED_LENGTH_SCCB,
S390_FEAT_CERT_STORE,
S390_FEAT_SIPL,
+ S390_FEAT_SCLAF,
};
/****** END FEATURE DEFS ******/
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 31bd574dec..2ed11fab52 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -2522,6 +2522,7 @@ bool kvm_s390_get_host_cpu_model(S390CPUModel *model, Error **errp)
/* Some Secure IPL facilities are emulated by QEMU */
set_bit(S390_FEAT_SIPL, model->features);
+ set_bit(S390_FEAT_SCLAF, model->features);
/* Test for Ultravisor features that influence secure guest behavior */
query_uv_feat_guest(model->features);
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 20/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
2025-09-17 23:21 ` [PATCH v6 20/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2025-09-29 12:25 ` Thomas Huth
2025-09-30 13:06 ` Thomas Huth
0 siblings, 1 reply; 89+ messages in thread
From: Thomas Huth @ 2025-09-29 12:25 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> The secure-IPL-code-loading-attributes facility (SCLAF)
> provides additional security during secure IPL.
>
> Availability of SCLAF is determined by byte 136 bit 3 of the
> SCLP Read Info block.
>
> This feature is available starting with the gen16 CPU model.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> Reviewed-by: Collin Walling <walling@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 25 +++++++++++++++++++++++++
> target/s390x/cpu_features.c | 2 ++
> target/s390x/cpu_features_def.h.inc | 1 +
> target/s390x/cpu_models.c | 3 +++
> target/s390x/gen-features.c | 2 ++
> target/s390x/kvm/kvm.c | 1 +
> 6 files changed, 34 insertions(+)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index 760a066084..a19b976e25 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -85,3 +85,28 @@ operations such as:
> * certificate data
>
> The guest kernel will inspect the IIRB and build the keyring.
> +
> +
> +Secure Code Loading Attributes Facility
> +---------------------------------
> +
> +The Secure Code Loading Attributes Facility (SCLAF) enhances system security during the
> +IPL by enforcing additional verification rules.
Please wrap your text so that it fits into 80 columns.
(Not sure why checkpatch.pl is not warning here...?)
> +When SCLAF is available, its behavior depends on the IPL mode. It introduces verification
> +of both signed and unsigned components to help ensure that only authorized code is loaded
> +during the IPL process. Any errors detected by SCLAF are reported in the IIRB.
>
> +Unsigned components are restricted to load addresses at or above absolute storage address
> +``0x2000``.
> +
> +Signed components must include a Secure Code Loading Attribute Block (SCLAB), which is
> +appended at the very end of the component. The SCLAB defines security attributes for
> +handling the signed code. Specifically, it may:
> +
> +* Provide direction on how to process the rest of the component.
> +
> +* Provide further validation of information on where to load the signed binary code
> + from the load device.
> +
> +* Specify where to start the execution of the loaded OS code.
Do you maybe want to mention any tool from s390-tools that helps with this
task (assuming that there is one)? (or should that rather not be part of the
specs here?)
...
> diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
> index bd2060ab93..c3e0c6ceff 100644
> --- a/target/s390x/gen-features.c
> +++ b/target/s390x/gen-features.c
> @@ -722,6 +722,7 @@ static uint16_t full_GEN16_GA1[] = {
> S390_FEAT_UV_FEAT_AP_INTR,
> S390_FEAT_CERT_STORE,
> S390_FEAT_SIPL,
> + S390_FEAT_SCLAF,
> };
>
> static uint16_t full_GEN17_GA1[] = {
> @@ -924,6 +925,7 @@ static uint16_t qemu_MAX[] = {
> S390_FEAT_EXTENDED_LENGTH_SCCB,
> S390_FEAT_CERT_STORE,
> S390_FEAT_SIPL,
> + S390_FEAT_SCLAF,
> };
In the cover letter you wrote "All actions must be performed on a KVM guest"
... so does this feature depend on KVM or not? If you cannot use the feature
with TCG, I think you should not add this to the "qemu_MAX" CPU model?
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 20/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF)
2025-09-29 12:25 ` Thomas Huth
@ 2025-09-30 13:06 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 13:06 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 29/09/2025 14.25, Thomas Huth wrote:
> On 18/09/2025 01.21, Zhuoying Cai wrote:
> ...
>> diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
>> index bd2060ab93..c3e0c6ceff 100644
>> --- a/target/s390x/gen-features.c
>> +++ b/target/s390x/gen-features.c
>> @@ -722,6 +722,7 @@ static uint16_t full_GEN16_GA1[] = {
>> S390_FEAT_UV_FEAT_AP_INTR,
>> S390_FEAT_CERT_STORE,
>> S390_FEAT_SIPL,
>> + S390_FEAT_SCLAF,
>> };
> >
>> static uint16_t full_GEN17_GA1[] = {
>> @@ -924,6 +925,7 @@ static uint16_t qemu_MAX[] = {
>> S390_FEAT_EXTENDED_LENGTH_SCCB,
>> S390_FEAT_CERT_STORE,
>> S390_FEAT_SIPL,
>> + S390_FEAT_SCLAF,
>> };
>
> In the cover letter you wrote "All actions must be performed on a KVM
> guest" ... so does this feature depend on KVM or not? If you cannot use the
> feature with TCG, I think you should not add this to the "qemu_MAX" CPU model?
Ok, after reading through a bunch of patches now, it seems like this feature
also works with TCG, right? So the modification to qemu_MAX should stay,
never mind me previous comment here, I was just confused by the term "KVM
guest" in the cover letter.
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for secure boot
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (19 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 20/28] s390x: Guest support for Secure-IPL Code Loading Attributes Facility (SCLAF) Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-29 13:30 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 22/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
` (7 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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/iplb.h | 26 ++-
pc-bios/s390-ccw/s390-ccw.h | 1 +
pc-bios/s390-ccw/sclp.c | 8 +
pc-bios/s390-ccw/sclp.h | 1 +
pc-bios/s390-ccw/secure-ipl.c | 412 +++++++++++++++++++++++++++++++++-
pc-bios/s390-ccw/secure-ipl.h | 110 +++++++++
6 files changed, 553 insertions(+), 5 deletions(-)
diff --git a/pc-bios/s390-ccw/iplb.h b/pc-bios/s390-ccw/iplb.h
index 11302e004d..41cec91a68 100644
--- a/pc-bios/s390-ccw/iplb.h
+++ b/pc-bios/s390-ccw/iplb.h
@@ -32,11 +32,19 @@ struct IplInfoReportBlockHeader {
} __attribute__ ((packed));
typedef struct IplInfoReportBlockHeader IplInfoReportBlockHeader;
+#define S390_IPL_INFO_IIEI_NO_SIGNED_COMP 0x8000 /* bit 0 */
+#define S390_IPL_INFO_IIEI_NO_SCLAB 0x4000 /* bit 1 */
+#define S390_IPL_INFO_IIEI_NO_GLOBAL_SCLAB 0x2000 /* bit 2 */
+#define S390_IPL_INFO_IIEI_MORE_GLOBAL_SCLAB 0x1000 /* bit 3 */
+#define S390_IPL_INFO_IIEI_FOUND_UNSIGNED_COMP 0x800 /* bit 4 */
+#define S390_IPL_INFO_IIEI_MORE_SIGNED_COMP 0x400 /* bit 5 */
+
struct IplInfoBlockHeader {
uint32_t len;
uint8_t ibt;
uint8_t reserved1[3];
- uint8_t reserved2[8];
+ uint16_t iiei;
+ uint8_t reserved2[6];
} __attribute__ ((packed));
typedef struct IplInfoBlockHeader IplInfoBlockHeader;
@@ -60,13 +68,27 @@ typedef struct IplSignatureCertificateList IplSignatureCertificateList;
#define S390_IPL_COMPONENT_FLAG_SC 0x80
#define S390_IPL_COMPONENT_FLAG_CSV 0x40
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB 0x80000000 /* bit 0 */
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN 0x40000000 /* bit 1 */
+#define S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT 0x20000000 /* bit 2 */
+#define S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR 0x10000000 /* bit 3 */
+#define S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_PSW 0x8000000 /* bit 4 */
+#define S390_IPL_COMPONENT_CEI_INVALID_LOAD_PSW 0x4000000 /* bit 5 */
+#define S390_IPL_COMPONENT_CEI_NUC_NOT_IN_GLOBAL_SCLA 0x2000000 /* bit 6 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE 0x1000000 /* bit 7 */
+#define S390_IPL_COMPONENT_CEI_SC_NOT_IN_GLOBAL_SCLAB 0x800000 /* bit 8 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO 0x400000 /* bit 9 */
+#define S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO 0x200000 /* bit 10 */
+#define S390_IPL_COMPONENT_CEI_INVALID_UNSIGNED_ADDR 0x100000 /* bit 11 */
+
struct IplDeviceComponentEntry {
uint64_t addr;
uint64_t len;
uint8_t flags;
uint8_t reserved1[5];
uint16_t cert_index;
- uint8_t reserved2[8];
+ uint32_t cei;
+ uint8_t reserved2[4];
} __attribute__ ((packed));
typedef struct IplDeviceComponentEntry IplDeviceComponentEntry;
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index c2ba40d067..6d51d07c90 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 8eab19cb09..cd798c1198 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -202,6 +202,12 @@ static bool secure_ipl_supported(void)
return false;
}
+ if (!sclp_is_sclaf_on()) {
+ puts("Secure IPL Code Loading Attributes Facility is not supported by" \
+ " the hypervisor!");
+ return false;
+ }
+
return true;
}
@@ -214,6 +220,393 @@ static void init_lists(IplDeviceComponentList *comps, IplSignatureCertificateLis
certs->ipl_info_header.len = sizeof(certs->ipl_info_header);
}
+static bool is_comp_overlap(SecureIplCompAddrRange *comp_addr_range, int addr_range_index,
+ uint64_t start_addr, uint64_t end_addr)
+{
+ /* neither a signed nor an unsigned component can overlap with a signed component */
+ for (int i = 0; i < addr_range_index; i++) {
+ if ((comp_addr_range[i].start_addr <= end_addr &&
+ start_addr <= comp_addr_range[i].end_addr) &&
+ comp_addr_range[i].is_signed) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void comp_addr_range_add(SecureIplCompAddrRange *comp_addr_range,
+ int addr_range_index, bool is_signed,
+ uint64_t start_addr, uint64_t end_addr)
+{
+ if (addr_range_index > MAX_CERTIFICATES - 1) {
+ return;
+ }
+
+ comp_addr_range[addr_range_index].is_signed = is_signed;
+ comp_addr_range[addr_range_index].start_addr = start_addr;
+ comp_addr_range[addr_range_index].end_addr = end_addr;
+}
+
+static void check_unsigned_addr(uint64_t load_addr, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ uint32_t flag;
+ const char *msg;
+ bool valid;
+
+ valid = validate_unsigned_addr(load_addr);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_INVALID_UNSIGNED_ADDR;
+ msg = "Load address is less than 0x2000";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+}
+
+static void addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
+ int *addr_range_index,
+ uint64_t start_addr, uint64_t end_addr, bool is_signed)
+{
+ bool overlap;
+
+ overlap = is_comp_overlap(comp_addr_range, *addr_range_index,
+ start_addr, end_addr);
+ if (!overlap) {
+ comp_addr_range_add(comp_addr_range, *addr_range_index, is_signed,
+ start_addr, end_addr);
+ *addr_range_index += 1;
+ } else {
+ zipl_secure_handle("Component addresses overlap");
+ }
+}
+
+static bool check_sclab_presence(uint8_t *sclab_magic,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ if (!validate_sclab_magic(sclab_magic)) {
+ comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+
+ /* a missing SCLAB will not be reported in audit mode */
+ return false;
+ }
+
+ return true;
+}
+
+static void check_sclab_length(uint16_t sclab_len,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ const char *msg;
+ uint32_t flag;
+ bool valid;
+
+ valid = validate_sclab_length(sclab_len);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN |
+ S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
+ msg = "Invalid SCLAB length";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+}
+
+static void check_sclab_format(uint8_t sclab_format,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ const char *msg;
+ uint32_t flag;
+ bool valid;
+
+ valid = validate_sclab_format(sclab_format);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT;
+ msg = "Format-0 SCLAB is not being use";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+}
+
+static void check_sclab_opsw(SecureCodeLoadingAttributesBlock *sclab,
+ SecureIplSclabInfo *sclab_info,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ const char *msg;
+ uint32_t flag;
+ bool is_opsw_set;
+ bool valid;
+
+ is_opsw_set = is_sclab_flag_set(sclab->flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
+ if (!is_opsw_set) {
+ valid = validate_sclab_opsw_zero(sclab->load_psw);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO;
+ msg = "Load PSW is not zero when Override PSW bit is zero";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+ } else {
+ /* OPSW = 1 indicating global SCLAB */
+ valid = validate_sclab_opsw_one(sclab->flags);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE;
+ msg = "Override Load Address bit is not set to one in the global SCLAB";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+
+ sclab_info->global_count += 1;
+ if (sclab_info->global_count == 1) {
+ sclab_info->load_psw = sclab->load_psw;
+ sclab_info->flags = sclab->flags;
+ }
+ }
+}
+
+static void check_sclab_ola(SecureCodeLoadingAttributesBlock *sclab,
+ uint64_t load_addr, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ const char *msg;
+ uint32_t flag;
+ bool is_ola_set;
+ bool valid;
+
+ is_ola_set = is_sclab_flag_set(sclab->flags, S390_SECURE_IPL_SCLAB_FLAG_OLA);
+ if (!is_ola_set) {
+ valid = validate_sclab_ola_zero(sclab->load_addr);
+ if (!(valid)) {
+ flag = S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO;
+ msg = "Load Address is not zero when Override Load Address bit is zero";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+
+ } else {
+ valid = validate_sclab_ola_one(sclab->load_addr, load_addr);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR;
+ msg = "Load Address does not match with component load address";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+ }
+}
+
+static void check_sclab_nuc(uint16_t sclab_flags, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ const char *msg;
+ uint32_t flag;
+ bool is_nuc_set;
+ bool is_global_sclab;
+
+ is_nuc_set = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_NUC);
+ is_global_sclab = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
+ if (is_nuc_set && !is_global_sclab) {
+ flag = S390_IPL_COMPONENT_CEI_NUC_NOT_IN_GLOBAL_SCLA;
+ msg = "No Unsigned Components bit is set, but not in the global SCLAB";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+}
+
+static void check_sclab_sc(uint16_t sclab_flags, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ const char *msg;
+ uint32_t flag;
+ bool is_sc_set;
+ bool is_global_sclab;
+
+ is_sc_set = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_SC);
+ is_global_sclab = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
+ if (is_sc_set && !is_global_sclab) {
+ flag = S390_IPL_COMPONENT_CEI_SC_NOT_IN_GLOBAL_SCLAB;
+ msg = "Single Component bit is set, but not in the global SCLAB";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+}
+
+static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
+ int range_index)
+{
+ uint32_t addr = psw & 0x3FFFFFFF;
+
+ /* PSW points within a signed binary code component */
+ for (int i = 0; i < range_index; i++) {
+ if (comp_addr_range[i].is_signed &&
+ addr >= comp_addr_range[i].start_addr &&
+ addr <= comp_addr_range[i].end_addr) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void check_load_psw(SecureIplCompAddrRange *comp_addr_range,
+ int addr_range_index, uint64_t sclab_load_psw,
+ uint64_t load_psw, IplDeviceComponentList *comps,
+ int comp_index)
+{
+ uint32_t flag;
+ const char *msg;
+ bool valid;
+
+ valid = is_psw_valid(sclab_load_psw, comp_addr_range, addr_range_index) &&
+ is_psw_valid(load_psw, comp_addr_range, addr_range_index);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_INVALID_LOAD_PSW;
+ msg = "Invalid PSW";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+
+ valid = validate_lpsw(sclab_load_psw, load_psw);
+ if (!valid) {
+ flag = S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_PSW;
+ msg = "Load PSW does not match with PSW in component";
+ set_cei_with_log(comps, comp_index, flag, msg);
+ }
+}
+
+static void check_nuc(uint16_t global_sclab_flags, int unsigned_count,
+ IplDeviceComponentList *comps)
+{
+ uint16_t flag;
+ const char *msg;
+ bool is_nuc_set;
+
+ is_nuc_set = is_sclab_flag_set(global_sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_NUC);
+ if (is_nuc_set && unsigned_count > 0) {
+ flag = S390_IPL_INFO_IIEI_FOUND_UNSIGNED_COMP;
+ msg = "Unsigned components are not allowed";
+ set_iiei_with_log(comps, flag, msg);
+ }
+}
+
+static void check_sc(uint16_t global_sclab_flags, int signed_count,
+ IplDeviceComponentList *comps)
+{
+ uint16_t flag;
+ const char *msg;
+ bool is_sc_set;
+
+ is_sc_set = is_sclab_flag_set(global_sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_SC);
+ if (is_sc_set && signed_count != 1) {
+ flag = S390_IPL_INFO_IIEI_MORE_SIGNED_COMP;
+ msg = "Only one signed component is allowed";
+ set_iiei_with_log(comps, flag, msg);
+ }
+}
+
+void check_global_sclab(SecureIplSclabInfo sclab_info,
+ SecureIplCompAddrRange *comp_addr_range,
+ int addr_range_index, uint64_t load_psw,
+ int unsigned_count, int signed_count,
+ IplDeviceComponentList *comps, int comp_index)
+{
+ uint16_t flag;
+ const char *msg;
+
+ if (sclab_info.count == 0) {
+ return;
+ }
+
+ if (sclab_info.global_count == 0) {
+ flag = S390_IPL_INFO_IIEI_NO_GLOBAL_SCLAB;
+ msg = "Global SCLAB does not exists";
+ set_iiei_with_log(comps, flag, msg);
+ return;
+ }
+
+ if (sclab_info.global_count > 1) {
+ flag = S390_IPL_INFO_IIEI_MORE_GLOBAL_SCLAB;
+ msg = "More than one global SCLAB";
+ set_iiei_with_log(comps, flag, msg);
+ return;
+ }
+
+ if (sclab_info.load_psw) {
+ /* Verify PSW from the final component entry with PSW from the global SCLAB */
+ check_load_psw(comp_addr_range, addr_range_index,
+ sclab_info.load_psw, load_psw,
+ comps, comp_index);
+ }
+
+ if (sclab_info.flags) {
+ /* Unsigned components are not allowed if NUC flag is set in the global SCLAB */
+ check_nuc(sclab_info.flags, unsigned_count, comps);
+
+ /* Only one signed component is allowed is SC flag is set in the global SCLAB */
+ check_sc(sclab_info.flags, signed_count, comps);
+ }
+}
+
+static void check_signed_comp(int signed_count, IplDeviceComponentList *comps)
+{
+ uint16_t flag;
+ const char *msg;
+
+ if (signed_count > 0) {
+ return;
+ }
+
+ flag = S390_IPL_INFO_IIEI_NO_SIGNED_COMP;
+ msg = "Secure boot is on, but components are not signed";
+ set_iiei_with_log(comps, flag, msg);
+}
+
+static void check_sclab_count(int count, IplDeviceComponentList *comps)
+{
+ uint16_t flag;
+ const char *msg;
+
+ if (count > 0) {
+ return;
+ }
+
+ flag = S390_IPL_INFO_IIEI_NO_SCLAB;
+ msg = "No recognizable SCLAB";
+ set_iiei_with_log(comps, flag, msg);
+}
+
+static void check_unsigned_comp(uint64_t comp_addr, IplDeviceComponentList *comps,
+ int comp_index, int cert_index, uint64_t comp_len)
+{
+ check_unsigned_addr(comp_addr, comps, comp_index);
+
+ comp_list_add(comps, comp_index, cert_index, comp_addr, comp_len, 0x00);
+}
+
+static void check_sclab(uint64_t comp_addr, IplDeviceComponentList *comps,
+ uint64_t comp_len, int comp_index, SecureIplSclabInfo *sclab_info)
+{
+ SclabOriginLocator *sclab_locator;
+ SecureCodeLoadingAttributesBlock *sclab;
+ bool exist;
+ bool valid;
+
+ sclab_locator = (SclabOriginLocator *)(comp_addr + comp_len - 8);
+
+ /* return early if sclab does not exist */
+ exist = check_sclab_presence(sclab_locator->magic, comps, comp_index);
+ if (!exist) {
+ return;
+ }
+
+ check_sclab_length(sclab_locator->len, comps, comp_index);
+
+ /* return early if sclab is invalid */
+ valid = (comps->device_entries[comp_index].cei &
+ S390_IPL_COMPONENT_CEI_INVALID_SCLAB) == 0;
+ if (!valid) {
+ return;
+ }
+
+ sclab_info->count += 1;
+ sclab = (SecureCodeLoadingAttributesBlock *)(comp_addr + comp_len -
+ sclab_locator->len);
+
+ check_sclab_format(sclab->format, comps, comp_index);
+ check_sclab_opsw(sclab, sclab_info, comps, comp_index);
+ check_sclab_ola(sclab, comp_addr, comps, comp_index);
+ check_sclab_nuc(sclab->flags, comps, comp_index);
+ check_sclab_sc(sclab->flags, comps, comp_index);
+}
+
static uint32_t zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)
{
uint32_t sig_len;
@@ -278,7 +671,11 @@ int zipl_run_secure(ComponentEntry **entry_ptr, 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};
+ SecureIplCompAddrRange comp_addr_range[MAX_CERTIFICATES];
+ int addr_range_index = 0;
int signed_count = 0;
+ int unsigned_count = 0;
+ SecureIplSclabInfo sclab_info = { 0 };
if (!secure_ipl_supported()) {
return -1;
@@ -308,10 +705,17 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
goto out;
}
+ addr_overlap_check(comp_addr_range, &addr_range_index,
+ comp_addr, comp_addr + comp_len, sig_len > 0);
+
if (!sig_len) {
+ check_unsigned_comp(comp_addr, &comps, comp_index, cert_index, comp_len);
+ unsigned_count += 1;
+ comp_index++;
break;
}
+ check_sclab(comp_addr, &comps, comp_len, comp_index, &sclab_info);
verified = verify_signature(comp_len, comp_addr, sig_len, (uint64_t)sig,
&cert_len, &cert_idx);
@@ -351,9 +755,11 @@ int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)
}
}
- if (signed_count == 0) {
- zipl_secure_handle("Secure boot is on, but components are not signed");
- }
+ check_signed_comp(signed_count, &comps);
+ check_sclab_count(sclab_info.count, &comps);
+ check_global_sclab(sclab_info, comp_addr_range, addr_range_index,
+ entry->compdat.load_psw, unsigned_count, signed_count,
+ &comps, comp_index);
if (update_iirb(&comps, &certs)) {
zipl_secure_handle("Failed to write IPL Information Report Block");
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index a264a44349..87aa6e1465 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -16,6 +16,42 @@
VCStorageSizeBlock *zipl_secure_get_vcssb(void);
int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);
+#define S390_SECURE_IPL_SCLAB_FLAG_OPSW 0x8000
+#define S390_SECURE_IPL_SCLAB_FLAG_OLA 0x4000
+#define S390_SECURE_IPL_SCLAB_FLAG_NUC 0x2000
+#define S390_SECURE_IPL_SCLAB_FLAG_SC 0x1000
+
+struct SecureCodeLoadingAttributesBlock {
+ uint8_t format;
+ uint8_t reserved1;
+ uint16_t flags;
+ uint8_t reserved2[4];
+ uint64_t load_psw;
+ uint64_t load_addr;
+ uint64_t reserved3[];
+} __attribute__ ((packed));
+typedef struct SecureCodeLoadingAttributesBlock SecureCodeLoadingAttributesBlock;
+
+struct SclabOriginLocator {
+ uint8_t reserved[2];
+ uint16_t len;
+ uint8_t magic[4];
+} __attribute__ ((packed));
+typedef struct SclabOriginLocator SclabOriginLocator;
+
+typedef struct SecureIplCompAddrRange {
+ bool is_signed;
+ uint64_t start_addr;
+ uint64_t end_addr;
+} SecureIplCompAddrRange;
+
+typedef struct SecureIplSclabInfo {
+ int count;
+ int global_count;
+ uint64_t load_psw;
+ uint16_t flags;
+} SecureIplSclabInfo;
+
static inline void zipl_secure_handle(const char *message)
{
switch (boot_mode) {
@@ -27,6 +63,80 @@ static inline void zipl_secure_handle(const char *message)
}
}
+static inline bool is_sclab_flag_set(uint16_t sclab_flags, uint16_t flag)
+{
+ return (sclab_flags & flag) != 0;
+}
+
+static inline bool validate_unsigned_addr(uint64_t comp_load_addr)
+{
+ /* usigned load address must be greater than or equal to 0x2000 */
+ return comp_load_addr >= 0x2000;
+}
+
+static inline bool validate_sclab_magic(uint8_t *sclab_magic)
+{
+ /* identifies the presence of SCLAB */
+ return magic_match(sclab_magic, ZIPL_MAGIC);
+}
+
+static inline bool validate_sclab_length(uint16_t sclab_len)
+{
+ /* minimum SCLAB length is 32 bytes */
+ return sclab_len >= 32;
+}
+
+static inline bool validate_sclab_format(uint8_t sclab_format)
+{
+ /* SCLAB format must set to zero, indicating a format-0 SCLAB being used */
+ return sclab_format == 0;
+}
+
+static inline bool validate_sclab_ola_zero(uint64_t sclab_load_addr)
+{
+ /* Load address field in SCLAB must contain zeros */
+ return sclab_load_addr == 0;
+}
+
+static inline bool validate_sclab_ola_one(uint64_t sclab_load_addr,
+ uint64_t comp_load_addr)
+{
+ /* Load address field must match storage address of the component */
+ return sclab_load_addr == comp_load_addr;
+}
+
+static inline bool validate_sclab_opsw_zero(uint64_t sclab_load_psw)
+{
+ /* Load PSW field in SCLAB must contain zeros */
+ return sclab_load_psw == 0;
+}
+
+static inline bool validate_sclab_opsw_one(uint16_t sclab_flags)
+{
+ /* OLA must set to one */
+ return is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OLA);
+}
+
+static inline bool validate_lpsw(uint64_t sclab_load_psw, uint64_t comp_load_psw)
+{
+ /* compare load PSW with the PSW specified in component */
+ return sclab_load_psw == comp_load_psw;
+}
+
+static inline void set_cei_with_log(IplDeviceComponentList *comps, int comp_index,
+ uint32_t flag, const char *message)
+{
+ comps->device_entries[comp_index].cei |= flag;
+ zipl_secure_handle(message);
+}
+
+static inline void set_iiei_with_log(IplDeviceComponentList *comps, uint16_t flag,
+ const char *message)
+{
+ comps->ipl_info_header.iiei |= flag;
+ zipl_secure_handle(message);
+}
+
static inline uint64_t diag320(void *data, unsigned long subcode)
{
register unsigned long addr asm("0") = (unsigned long)data;
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for secure boot
2025-09-17 23:21 ` [PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
@ 2025-09-29 13:30 ` Thomas Huth
2025-09-29 20:43 ` Zhuoying Cai
0 siblings, 1 reply; 89+ messages in thread
From: Thomas Huth @ 2025-09-29 13:30 UTC (permalink / raw)
To: Zhuoying Cai, berrange, david, jrossi, qemu-s390x, qemu-devel,
borntraeger
Cc: walling, jjherne, pasic, farman, mjrosato, iii, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Add additional checks to ensure that components do not overlap with
> signed components when loaded into memory.
>
> Add additional checks to ensure the load addresses of unsigned components
> are greater than or equal to 0x2000.
>
> When the secure IPL code loading attributes facility (SCLAF) is installed,
> all signed components must contain a secure code loading attributes block
> (SCLAB).
>
> The SCLAB provides further validation of information on where to load the
> signed binary code from the load device, and where to start the execution
> of the loaded OS code.
>
> When SCLAF is installed, its content must be evaluated during secure IPL.
> However, a missing SCLAB will not be reported in audit mode. The SCALB
> checking will be skipped in this case.
>
> Add IPL Information Error Indicators (IIEI) and Component Error
> Indicators (CEI) for IPL Information Report Block (IIRB).
>
> 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>
> ---
...
> diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
> index 8eab19cb09..cd798c1198 100644
> --- a/pc-bios/s390-ccw/secure-ipl.c
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -202,6 +202,12 @@ static bool secure_ipl_supported(void)
> return false;
> }
>
> + if (!sclp_is_sclaf_on()) {
> + puts("Secure IPL Code Loading Attributes Facility is not supported by" \
No need for the backslash here.
> + " the hypervisor!");
> + return false;
> + }
> +
> return true;
> }
>
> @@ -214,6 +220,393 @@ static void init_lists(IplDeviceComponentList *comps, IplSignatureCertificateLis
> certs->ipl_info_header.len = sizeof(certs->ipl_info_header);
> }
>
> +static bool is_comp_overlap(SecureIplCompAddrRange *comp_addr_range, int addr_range_index,
I'd suggest to move the second parameter to a separate line, to keep the
line length below 80 columns.
> + uint64_t start_addr, uint64_t end_addr)
> +{
> + /* neither a signed nor an unsigned component can overlap with a signed component */
> + for (int i = 0; i < addr_range_index; i++) {
> + if ((comp_addr_range[i].start_addr <= end_addr &&
> + start_addr <= comp_addr_range[i].end_addr) &&
> + comp_addr_range[i].is_signed) {
> + return true;
> + }
> + }
> +
> + return false;
> +}
> +
> +static void comp_addr_range_add(SecureIplCompAddrRange *comp_addr_range,
> + int addr_range_index, bool is_signed,
> + uint64_t start_addr, uint64_t end_addr)
> +{
> + if (addr_range_index > MAX_CERTIFICATES - 1) {
That error goes completely unnoticed. Should there be a log / warning here?
> + return;
> + }
> +
> + comp_addr_range[addr_range_index].is_signed = is_signed;
> + comp_addr_range[addr_range_index].start_addr = start_addr;
> + comp_addr_range[addr_range_index].end_addr = end_addr;
> +}
> +
> +static void check_unsigned_addr(uint64_t load_addr, IplDeviceComponentList *comps,
> + int comp_index)
> +{
> + uint32_t flag;
> + const char *msg;
> + bool valid;
> +
> + valid = validate_unsigned_addr(load_addr);
> + if (!valid) {
> + flag = S390_IPL_COMPONENT_CEI_INVALID_UNSIGNED_ADDR;
> + msg = "Load address is less than 0x2000";
> + set_cei_with_log(comps, comp_index, flag, msg);
I'd maybe rather pass the string directly as parameter, without the detour
through the "msg" variable.
> + }
> +}
> +
> +static void addr_overlap_check(SecureIplCompAddrRange *comp_addr_range,
> + int *addr_range_index,
> + uint64_t start_addr, uint64_t end_addr, bool is_signed)
> +{
> + bool overlap;
> +
> + overlap = is_comp_overlap(comp_addr_range, *addr_range_index,
> + start_addr, end_addr);
> + if (!overlap) {
> + comp_addr_range_add(comp_addr_range, *addr_range_index, is_signed,
> + start_addr, end_addr);
> + *addr_range_index += 1;
> + } else {
> + zipl_secure_handle("Component addresses overlap");
> + }
> +}
> +
> +static bool check_sclab_presence(uint8_t *sclab_magic,
> + IplDeviceComponentList *comps, int comp_index)
> +{
> + if (!validate_sclab_magic(sclab_magic)) {
> + comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
> +
> + /* a missing SCLAB will not be reported in audit mode */
> + return false;
> + }
> +
> + return true;
> +}
> +
> +static void check_sclab_length(uint16_t sclab_len,
> + IplDeviceComponentList *comps, int comp_index)
> +{
> + const char *msg;
> + uint32_t flag;
> + bool valid;
> +
> + valid = validate_sclab_length(sclab_len);
> + if (!valid) {
> + flag = S390_IPL_COMPONENT_CEI_INVALID_SCLAB_LEN |
> + S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
> + msg = "Invalid SCLAB length";
> + set_cei_with_log(comps, comp_index, flag, msg);
Again, pass string directly, without msg variable?
> + }
> +}
> +
> +static void check_sclab_format(uint8_t sclab_format,
> + IplDeviceComponentList *comps, int comp_index)
> +{
> + const char *msg;
> + uint32_t flag;
> + bool valid;
> +
> + valid = validate_sclab_format(sclab_format);
> + if (!valid) {
> + flag = S390_IPL_COMPONENT_CEI_INVALID_SCLAB_FORMAT;
> + msg = "Format-0 SCLAB is not being use";
> + set_cei_with_log(comps, comp_index, flag, msg);
dito
> + }
> +}
> +
> +static void check_sclab_opsw(SecureCodeLoadingAttributesBlock *sclab,
> + SecureIplSclabInfo *sclab_info,
> + IplDeviceComponentList *comps, int comp_index)
> +{
> + const char *msg;
> + uint32_t flag;
> + bool is_opsw_set;
> + bool valid;
> +
> + is_opsw_set = is_sclab_flag_set(sclab->flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
> + if (!is_opsw_set) {
> + valid = validate_sclab_opsw_zero(sclab->load_psw);
> + if (!valid) {
> + flag = S390_IPL_COMPONENT_CEI_SCLAB_LOAD_PSW_NOT_ZERO;
> + msg = "Load PSW is not zero when Override PSW bit is zero";
> + set_cei_with_log(comps, comp_index, flag, msg);
> + }
> + } else {
> + /* OPSW = 1 indicating global SCLAB */
> + valid = validate_sclab_opsw_one(sclab->flags);
> + if (!valid) {
> + flag = S390_IPL_COMPONENT_CEI_SCLAB_OLA_NOT_ONE;
> + msg = "Override Load Address bit is not set to one in the global SCLAB";
> + set_cei_with_log(comps, comp_index, flag, msg);
Is it ok here to continue with the code below, even if it was not valid? Or
should there be a return statement here?
> + }
> +
> + sclab_info->global_count += 1;
> + if (sclab_info->global_count == 1) {
> + sclab_info->load_psw = sclab->load_psw;
> + sclab_info->flags = sclab->flags;
> + }
> + }
> +}
> +
> +static void check_sclab_ola(SecureCodeLoadingAttributesBlock *sclab,
> + uint64_t load_addr, IplDeviceComponentList *comps,
> + int comp_index)
> +{
> + const char *msg;
> + uint32_t flag;
> + bool is_ola_set;
> + bool valid;
> +
> + is_ola_set = is_sclab_flag_set(sclab->flags, S390_SECURE_IPL_SCLAB_FLAG_OLA);
> + if (!is_ola_set) {
> + valid = validate_sclab_ola_zero(sclab->load_addr);
> + if (!(valid)) {
No need for the inner braces here.
> + flag = S390_IPL_COMPONENT_CEI_SCLAB_LOAD_ADDR_NOT_ZERO;
> + msg = "Load Address is not zero when Override Load Address bit is zero";
> + set_cei_with_log(comps, comp_index, flag, msg);
> + }
> +
> + } else {
> + valid = validate_sclab_ola_one(sclab->load_addr, load_addr);
> + if (!valid) {
> + flag = S390_IPL_COMPONENT_CEI_UNMATCHED_SCLAB_LOAD_ADDR;
> + msg = "Load Address does not match with component load address";
> + set_cei_with_log(comps, comp_index, flag, msg);
> + }
> + }
> +}
> +
> +static void check_sclab_nuc(uint16_t sclab_flags, IplDeviceComponentList *comps,
> + int comp_index)
> +{
> + const char *msg;
> + uint32_t flag;
> + bool is_nuc_set;
> + bool is_global_sclab;
> +
> + is_nuc_set = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_NUC);
> + is_global_sclab = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
> + if (is_nuc_set && !is_global_sclab) {
> + flag = S390_IPL_COMPONENT_CEI_NUC_NOT_IN_GLOBAL_SCLA;
> + msg = "No Unsigned Components bit is set, but not in the global SCLAB";
> + set_cei_with_log(comps, comp_index, flag, msg);
> + }
> +}
> +
> +static void check_sclab_sc(uint16_t sclab_flags, IplDeviceComponentList *comps,
> + int comp_index)
> +{
> + const char *msg;
> + uint32_t flag;
> + bool is_sc_set;
> + bool is_global_sclab;
> +
> + is_sc_set = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_SC);
> + is_global_sclab = is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OPSW);
> + if (is_sc_set && !is_global_sclab) {
> + flag = S390_IPL_COMPONENT_CEI_SC_NOT_IN_GLOBAL_SCLAB;
> + msg = "Single Component bit is set, but not in the global SCLAB";
> + set_cei_with_log(comps, comp_index, flag, msg);
> + }
> +}
> +
> +static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
> + int range_index)
> +{
> + uint32_t addr = psw & 0x3FFFFFFF;
Shouldn't that be 0x7fffffff instead?
> + /* PSW points within a signed binary code component */
> + for (int i = 0; i < range_index; i++) {
> + if (comp_addr_range[i].is_signed &&
> + addr >= comp_addr_range[i].start_addr &&
> + addr <= comp_addr_range[i].end_addr) {
is it still OK if the address points to the end_addr? Or should that be
end_addr - 2 instead (since an opcode has at least two bytes)?
> + return true;
> + }
> + }
> +
> + return false;
> +}
...
>
> +static inline bool is_sclab_flag_set(uint16_t sclab_flags, uint16_t flag)
> +{
> + return (sclab_flags & flag) != 0;
> +}
> +
> +static inline bool validate_unsigned_addr(uint64_t comp_load_addr)
> +{
> + /* usigned load address must be greater than or equal to 0x2000 */
> + return comp_load_addr >= 0x2000;
> +}
> +
> +static inline bool validate_sclab_magic(uint8_t *sclab_magic)
> +{
> + /* identifies the presence of SCLAB */
> + return magic_match(sclab_magic, ZIPL_MAGIC);
> +}
> +
> +static inline bool validate_sclab_length(uint16_t sclab_len)
> +{
> + /* minimum SCLAB length is 32 bytes */
> + return sclab_len >= 32;
> +}
> +
> +static inline bool validate_sclab_format(uint8_t sclab_format)
> +{
> + /* SCLAB format must set to zero, indicating a format-0 SCLAB being used */
> + return sclab_format == 0;
> +}
> +
> +static inline bool validate_sclab_ola_zero(uint64_t sclab_load_addr)
> +{
> + /* Load address field in SCLAB must contain zeros */
> + return sclab_load_addr == 0;
> +}
> +
> +static inline bool validate_sclab_ola_one(uint64_t sclab_load_addr,
> + uint64_t comp_load_addr)
> +{
> + /* Load address field must match storage address of the component */
> + return sclab_load_addr == comp_load_addr;
> +}
> +
> +static inline bool validate_sclab_opsw_zero(uint64_t sclab_load_psw)
> +{
> + /* Load PSW field in SCLAB must contain zeros */
> + return sclab_load_psw == 0;
> +}
>
> +static inline bool validate_sclab_opsw_one(uint16_t sclab_flags)
> +{
> + /* OLA must set to one */
> + return is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OLA);
> +}
> +
> +static inline bool validate_lpsw(uint64_t sclab_load_psw, uint64_t comp_load_psw)
> +{
> + /* compare load PSW with the PSW specified in component */
> + return sclab_load_psw == comp_load_psw;
> +}
Most of these inline functions just compare something with 0 or other values
here, and you only use them in one spot of the code ... So you need 5 lines
of code for something that could be done in two lines of code at the calling
sites instead, i.e. this looks like unnecessary code to me. Please inline
the comparisons (together with the comment that you've got here) in the
calling sites to get rid of this code bloat.
Thanks,
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for secure boot
2025-09-29 13:30 ` Thomas Huth
@ 2025-09-29 20:43 ` Zhuoying Cai
2025-09-30 5:14 ` Thomas Huth
0 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-29 20:43 UTC (permalink / raw)
To: Thomas Huth, berrange, david, jrossi, qemu-s390x, qemu-devel,
borntraeger
Cc: walling, jjherne, pasic, farman, mjrosato, iii, alifm
Thank you for all the feedback! I'll address the comments in the next
version.
On 9/29/25 9:30 AM, Thomas Huth wrote:
> On 18/09/2025 01.21, Zhuoying Cai wrote:
>> Add additional checks to ensure that components do not overlap with
>> signed components when loaded into memory.
>>
>> Add additional checks to ensure the load addresses of unsigned components
>> are greater than or equal to 0x2000.
>>
>> When the secure IPL code loading attributes facility (SCLAF) is installed,
>> all signed components must contain a secure code loading attributes block
>> (SCLAB).
>>
>> The SCLAB provides further validation of information on where to load the
>> signed binary code from the load device, and where to start the execution
>> of the loaded OS code.
>>
>> When SCLAF is installed, its content must be evaluated during secure IPL.
>> However, a missing SCLAB will not be reported in audit mode. The SCALB
>> checking will be skipped in this case.
>>
>> Add IPL Information Error Indicators (IIEI) and Component Error
>> Indicators (CEI) for IPL Information Report Block (IIRB).
>>
>> 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>
>> ---
> ...
>> diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
>> index 8eab19cb09..cd798c1198 100644
>> --- a/pc-bios/s390-ccw/secure-ipl.c
>> +++ b/pc-bios/s390-ccw/secure-ipl.c
[ ... ]
>> +
>> +static bool is_psw_valid(uint64_t psw, SecureIplCompAddrRange *comp_addr_range,
>> + int range_index)
>> +{
>> + uint32_t addr = psw & 0x3FFFFFFF;
>
> Shouldn't that be 0x7fffffff instead?
>
Thanks for pointing it out. It should be 0x7fffffff.
>> + /* PSW points within a signed binary code component */
>> + for (int i = 0; i < range_index; i++) {
>> + if (comp_addr_range[i].is_signed &&
>> + addr >= comp_addr_range[i].start_addr &&
>> + addr <= comp_addr_range[i].end_addr) {
>
> is it still OK if the address points to the end_addr? Or should that be
> end_addr - 2 instead (since an opcode has at least two bytes)?
>
Using end_addr - 2 seems correct, since it accounts for the minimum
instruction length.
Just to clarify: using end_addr - 2 should ensure that at least a 2-byte
instruction fits. Should longer instructions (e.g., 4 and 6 bytes) be a
concern in this context?
>> + return true;
>> + }
>> + }
>> +
>> + return false;
>> +}
> ...
>>
>> +static inline bool is_sclab_flag_set(uint16_t sclab_flags, uint16_t flag)
>> +{
>> + return (sclab_flags & flag) != 0;
>> +}
>> +
>> +static inline bool validate_unsigned_addr(uint64_t comp_load_addr)
>> +{
>> + /* usigned load address must be greater than or equal to 0x2000 */
>> + return comp_load_addr >= 0x2000;
>> +}
>> +
>> +static inline bool validate_sclab_magic(uint8_t *sclab_magic)
>> +{
>> + /* identifies the presence of SCLAB */
>> + return magic_match(sclab_magic, ZIPL_MAGIC);
>> +}
>> +
>> +static inline bool validate_sclab_length(uint16_t sclab_len)
>> +{
>> + /* minimum SCLAB length is 32 bytes */
>> + return sclab_len >= 32;
>> +}
>> +
>> +static inline bool validate_sclab_format(uint8_t sclab_format)
>> +{
>> + /* SCLAB format must set to zero, indicating a format-0 SCLAB being used */
>> + return sclab_format == 0;
>> +}
>> +
>> +static inline bool validate_sclab_ola_zero(uint64_t sclab_load_addr)
>> +{
>> + /* Load address field in SCLAB must contain zeros */
>> + return sclab_load_addr == 0;
>> +}
>> +
>> +static inline bool validate_sclab_ola_one(uint64_t sclab_load_addr,
>> + uint64_t comp_load_addr)
>> +{
>> + /* Load address field must match storage address of the component */
>> + return sclab_load_addr == comp_load_addr;
>> +}
>> +
>> +static inline bool validate_sclab_opsw_zero(uint64_t sclab_load_psw)
>> +{
>> + /* Load PSW field in SCLAB must contain zeros */
>> + return sclab_load_psw == 0;
>> +}
>>
>> +static inline bool validate_sclab_opsw_one(uint16_t sclab_flags)
>> +{
>> + /* OLA must set to one */
>> + return is_sclab_flag_set(sclab_flags, S390_SECURE_IPL_SCLAB_FLAG_OLA);
>> +}
>> +
>> +static inline bool validate_lpsw(uint64_t sclab_load_psw, uint64_t comp_load_psw)
>> +{
>> + /* compare load PSW with the PSW specified in component */
>> + return sclab_load_psw == comp_load_psw;
>> +}
>
> Most of these inline functions just compare something with 0 or other values
> here, and you only use them in one spot of the code ... So you need 5 lines
> of code for something that could be done in two lines of code at the calling
> sites instead, i.e. this looks like unnecessary code to me. Please inline
> the comparisons (together with the comment that you've got here) in the
> calling sites to get rid of this code bloat.
>
> Thanks,
> Thomas
>
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for secure boot
2025-09-29 20:43 ` Zhuoying Cai
@ 2025-09-30 5:14 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-30 5:14 UTC (permalink / raw)
To: Zhuoying Cai, berrange, david, jrossi, qemu-s390x, qemu-devel,
borntraeger
Cc: walling, jjherne, pasic, farman, mjrosato, iii, alifm
On 29/09/2025 22.43, Zhuoying Cai wrote:
> Thank you for all the feedback! I'll address the comments in the next
> version.
>
> On 9/29/25 9:30 AM, Thomas Huth wrote:
>> On 18/09/2025 01.21, Zhuoying Cai wrote:
...
>>> + /* PSW points within a signed binary code component */
>>> + for (int i = 0; i < range_index; i++) {
>>> + if (comp_addr_range[i].is_signed &&
>>> + addr >= comp_addr_range[i].start_addr &&
>>> + addr <= comp_addr_range[i].end_addr) {
>>
>> is it still OK if the address points to the end_addr? Or should that be
>> end_addr - 2 instead (since an opcode has at least two bytes)?
>>
>
> Using end_addr - 2 seems correct, since it accounts for the minimum
> instruction length.
>
> Just to clarify: using end_addr - 2 should ensure that at least a 2-byte
> instruction fits. Should longer instructions (e.g., 4 and 6 bytes) be a
> concern in this context?
No clue ... but I guess it should be fine if the instruction at least starts
in the signed area?
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 22/28] Add secure-boot to s390-ccw-virtio machine type option
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (20 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 21/28] pc-bios/s390-ccw: Add additional security checks for secure boot Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-29 14:05 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 23/28] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
` (6 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Add secure-boot as a parameter of s390-ccw-virtio machine type option.
The `secure-boot=on|off` parameter is implemented to enable secure IPL.
By default, secure-boot is set to false if not specified in
the command line.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/system/s390x/secure-ipl.rst | 20 ++++++++++++++++----
hw/s390x/s390-virtio-ccw.c | 22 ++++++++++++++++++++++
include/hw/s390x/s390-virtio-ccw.h | 1 +
qemu-options.hx | 6 +++++-
4 files changed, 44 insertions(+), 5 deletions(-)
diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
index 701594b9de..205de8bc02 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -20,19 +20,31 @@ Note: certificate files must have a .pem extension.
boot-certs.0.path=/.../qemu/certs, \
boot-certs.1.path=/another/path/cert.pem ...
+Enabling Secure IPL
+-------------------
+
+Secure IPL is enabled by explicitly setting ``secure-boot=on``; if not specified,
+secure boot is considered off.
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on|off
+
IPL Modes
=========
The concept of IPL Modes are introduced to differentiate between the IPL configurations.
-These modes are mutually exclusive and enabled based on the ``boot-certs`` option on the
-QEMU command line.
+These modes are mutually exclusive and enabled based on specific combinations of
+the ``secure-boot`` and ``boot-certs`` options on the QEMU command line.
Normal Mode
-----------
-The absence of certificates will attempt to IPL a guest without secure IPL operations.
-No checks are performed, and no warnings/errors are reported. This is the default mode.
+The absence of both certificates and the ``secure-boot`` option will attempt to
+IPL a guest without secure IPL operations. No checks are performed, and no
+warnings/errors are reported. This is the default mode, and can be explicitly
+enabled with ``secure-boot=off``.
Configuration:
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index b825f4cce1..5c15908b8f 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -823,6 +823,21 @@ static void machine_set_boot_certs(Object *obj, Visitor *v, const char *name,
ms->boot_certs = cert_list;
}
+static inline bool machine_get_secure_boot(Object *obj, Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ return ms->secure_boot;
+}
+
+static inline void machine_set_secure_boot(Object *obj, bool value,
+ Error **errp)
+{
+ S390CcwMachineState *ms = S390_CCW_MACHINE(obj);
+
+ ms->secure_boot = value;
+}
+
static void ccw_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -881,6 +896,13 @@ static void ccw_machine_class_init(ObjectClass *oc, const void *data)
machine_get_boot_certs, machine_set_boot_certs, NULL, NULL);
object_class_property_set_description(oc, "boot-certs",
"provide paths to a directory and/or a certificate file for secure boot");
+
+ object_class_property_add_bool(oc, "secure-boot",
+ machine_get_secure_boot,
+ machine_set_secure_boot);
+ object_class_property_set_description(oc, "secure-boot",
+ "enable/disable secure boot");
+
}
static inline void s390_machine_initfn(Object *obj)
diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
index 334b67ef05..1dba5ab37e 100644
--- a/include/hw/s390x/s390-virtio-ccw.h
+++ b/include/hw/s390x/s390-virtio-ccw.h
@@ -33,6 +33,7 @@ struct S390CcwMachineState {
uint64_t memory_limit;
uint64_t max_pagesize;
BootCertificateList *boot_certs;
+ bool secure_boot;
SCLPDevice *sclp;
};
diff --git a/qemu-options.hx b/qemu-options.hx
index ac497eb3a0..5d9cd0d0f1 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -45,7 +45,8 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \
" memory-backend='backend-id' specifies explicitly provided backend for main RAM (default=none)\n"
" cxl-fmw.0.targets.0=firsttarget,cxl-fmw.0.targets.1=secondtarget,cxl-fmw.0.size=size[,cxl-fmw.0.interleave-granularity=granularity]\n"
" smp-cache.0.cache=cachename,smp-cache.0.topology=topologylevel\n"
- " boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file provides paths to a directory and/or a certificate file\n",
+ " boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file provides paths to a directory and/or a certificate file\n"
+ " secure-boot=on|off enable/disable secure boot (default=off) \n",
QEMU_ARCH_ALL)
SRST
``-machine [type=]name[,prop=value[,...]]``
@@ -209,6 +210,9 @@ SRST
``boot-certs.0.path=/path/directory,boot-certs.1.path=/path/file``
Provide paths to a directory and/or a certificate file on the host [s390x only].
+
+ ``secure-boot=on|off``
+ Enables or disables secure boot on s390-ccw guest. The default is off.
ERST
DEF("M", HAS_ARG, QEMU_OPTION_M,
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 22/28] Add secure-boot to s390-ccw-virtio machine type option
2025-09-17 23:21 ` [PATCH v6 22/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-09-29 14:05 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-29 14:05 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Add secure-boot as a parameter of s390-ccw-virtio machine type option.
>
> The `secure-boot=on|off` parameter is implemented to enable secure IPL.
>
> By default, secure-boot is set to false if not specified in
> the command line.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
...
> diff --git a/include/hw/s390x/s390-virtio-ccw.h b/include/hw/s390x/s390-virtio-ccw.h
> index 334b67ef05..1dba5ab37e 100644
> --- a/include/hw/s390x/s390-virtio-ccw.h
> +++ b/include/hw/s390x/s390-virtio-ccw.h
> @@ -33,6 +33,7 @@ struct S390CcwMachineState {
> uint64_t memory_limit;
> uint64_t max_pagesize;
> BootCertificateList *boot_certs;
> + bool secure_boot;
Could you please move it next to the other "bool" variables in this struct?
(to avoid additional padding)
Thanks,
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 23/28] hw/s390x/ipl: Set IPIB flags for secure IPL
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (21 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 22/28] Add secure-boot to s390-ccw-virtio machine type option Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-17 23:21 ` [PATCH v6 24/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
` (5 subsequent siblings)
28 siblings, 0 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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 c1360905c4..42b25513a2 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -437,6 +437,11 @@ static bool s390_has_certificate(void)
return ipl->cert_store.count > 0;
}
+static bool s390_secure_boot_enabled(void)
+{
+ return S390_CCW_MACHINE(qdev_get_machine())->secure_boot;
+}
+
static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
{
CcwDevice *ccw_dev = NULL;
@@ -494,6 +499,17 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
s390_ipl_convert_loadparm((char *)lp, iplb->loadparm);
iplb->flags |= DIAG308_FLAGS_LP_VALID;
+ /*
+ * If secure-boot is enabled, then toggle the secure IPL flags to trigger
+ * secure boot in the s390 BIOS.
+ *
+ * Boot process will terminate if any error occurs during secure boot.
+ *
+ * If SIPL is on, IPLIR must also be on.
+ */
+ if (s390_secure_boot_enabled()) {
+ iplb->hdr_flags |= (DIAG308_IPIB_FLAGS_SIPL | DIAG308_IPIB_FLAGS_IPLIR);
+ }
/*
* Secure boot in audit mode will perform
* if certificate(s) exist in the key store.
@@ -503,7 +519,7 @@ static bool s390_build_iplb(DeviceState *dev_st, IplParameterBlock *iplb)
*
* Results of secure boot will be stored in IIRB.
*/
- if (s390_has_certificate()) {
+ else if (s390_has_certificate()) {
iplb->hdr_flags |= DIAG308_IPIB_FLAGS_IPLIR;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* [PATCH v6 24/28] pc-bios/s390-ccw: Handle true secure IPL mode
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (22 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 23/28] hw/s390x/ipl: Set IPIB flags for secure IPL Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-29 15:24 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 25/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
` (4 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
When secure boot is enabled (-secure-boot on) and certificate(s) are
provided, the boot operates in True Secure IPL mode.
Any verification error during True Secure IPL mode will cause the
entire boot process to terminate.
Secure IPL in audit mode requires at least one certificate provided in
the key store along with necessary facilities. If secure boot is enabled
but no certificate is provided, the boot process will also terminate, as
this is not a valid secure boot configuration.
Note: True Secure IPL mode is implemented for the SCSI scheme of
virtio-blk/virtio-scsi devices.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
docs/system/s390x/secure-ipl.rst | 16 ++++++++++++++++
pc-bios/s390-ccw/bootmap.c | 19 ++++++++++++++++---
pc-bios/s390-ccw/main.c | 7 ++++++-
pc-bios/s390-ccw/s390-ccw.h | 2 ++
pc-bios/s390-ccw/secure-ipl.c | 4 ++++
pc-bios/s390-ccw/secure-ipl.h | 3 +++
6 files changed, 47 insertions(+), 4 deletions(-)
diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
index 205de8bc02..579b7b4993 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -67,3 +67,19 @@ Configuration:
qemu-system-s390x -machine s390-ccw-virtio, \
boot-certs.0.path=/.../qemu/certs, \
boot-certs.1.path=/another/path/cert.pem ...
+
+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.
+
+Configuration:
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio, \
+ secure-boot=on, \
+ boot-certs.0.path=/.../qemu/certs, \
+ boot-certs.1.path=/another/path/cert.pem ...
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 3922e7cdde..3ab89b91fb 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -737,6 +737,9 @@ static int zipl_run(ScsiBlockPtr *pte)
entry = (ComponentEntry *)(&header[1]);
switch (boot_mode) {
+ case ZIPL_BOOT_MODE_INVALID:
+ return -1;
+ case ZIPL_BOOT_MODE_SECURE:
case ZIPL_BOOT_MODE_SECURE_AUDIT:
if (zipl_run_secure(&entry, tmp_sec)) {
return -1;
@@ -1118,9 +1121,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_BOOT_MODE_SECURE_AUDIT;
+ } else if (sipl_set && iplir_set) {
+ vcssb = zipl_secure_get_vcssb();
+ if (vcssb == NULL || vcssb->length == VCSSB_NO_VC) {
+ return ZIPL_BOOT_MODE_INVALID;
+ }
+ return ZIPL_BOOT_MODE_SECURE;
}
return ZIPL_BOOT_MODE_NORMAL;
@@ -1131,7 +1141,8 @@ void zipl_load(void)
VDev *vdev = virtio_get_device();
if (vdev->is_cdrom) {
- if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT ||
+ boot_mode == ZIPL_BOOT_MODE_SECURE) {
panic("Secure boot from ISO image is not supported!");
}
ipl_iso_el_torito();
@@ -1140,7 +1151,8 @@ void zipl_load(void)
}
if (virtio_get_device_type() == VIRTIO_ID_NET) {
- if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT ||
+ boot_mode == ZIPL_BOOT_MODE_SECURE) {
panic("Virtio net boot device does not support secure boot!");
}
netmain();
@@ -1153,7 +1165,8 @@ void zipl_load(void)
return;
}
- if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT) {
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT ||
+ boot_mode == ZIPL_BOOT_MODE_SECURE) {
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 668660e64d..c5b425209a 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -277,10 +277,15 @@ static void ipl_boot_device(void)
boot_mode = zipl_mode(iplb->hdr_flags);
}
+ if (boot_mode == ZIPL_BOOT_MODE_INVALID) {
+ 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_BOOT_MODE_SECURE_AUDIT) {
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT ||
+ boot_mode == ZIPL_BOOT_MODE_SECURE) {
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 6d51d07c90..389cc8ea7c 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -83,9 +83,11 @@ int virtio_read(unsigned long sector, void *load_addr);
void zipl_load(void);
typedef enum ZiplBootMode {
+ ZIPL_BOOT_MODE_INVALID = -1,
ZIPL_BOOT_MODE_UNSPECIFIED = 0,
ZIPL_BOOT_MODE_NORMAL = 1,
ZIPL_BOOT_MODE_SECURE_AUDIT = 2,
+ ZIPL_BOOT_MODE_SECURE = 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 cd798c1198..92e3e1e021 100644
--- a/pc-bios/s390-ccw/secure-ipl.c
+++ b/pc-bios/s390-ccw/secure-ipl.c
@@ -287,6 +287,10 @@ static bool check_sclab_presence(uint8_t *sclab_magic,
comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
/* a missing SCLAB will not be reported in audit mode */
+ if (boot_mode == ZIPL_BOOT_MODE_SECURE) {
+ zipl_secure_handle("Magic is not matched. SCLAB does not exist");
+ }
+
return false;
}
diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
index 87aa6e1465..d7786158c4 100644
--- a/pc-bios/s390-ccw/secure-ipl.h
+++ b/pc-bios/s390-ccw/secure-ipl.h
@@ -58,6 +58,9 @@ static inline void zipl_secure_handle(const char *message)
case ZIPL_BOOT_MODE_SECURE_AUDIT:
IPL_check(false, message);
break;
+ case ZIPL_BOOT_MODE_SECURE:
+ IPL_assert(false, message);
+ break;
default:
break;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 24/28] pc-bios/s390-ccw: Handle true secure IPL mode
2025-09-17 23:21 ` [PATCH v6 24/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
@ 2025-09-29 15:24 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-29 15:24 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> When secure boot is enabled (-secure-boot on) and certificate(s) are
> provided, the boot operates in True Secure IPL mode.
>
> Any verification error during True Secure IPL mode will cause the
> entire boot process to terminate.
>
> Secure IPL in audit mode requires at least one certificate provided in
> the key store along with necessary facilities. If secure boot is enabled
> but no certificate is provided, the boot process will also terminate, as
> this is not a valid secure boot configuration.
>
> Note: True Secure IPL mode is implemented for the SCSI scheme of
> virtio-blk/virtio-scsi devices.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> docs/system/s390x/secure-ipl.rst | 16 ++++++++++++++++
> pc-bios/s390-ccw/bootmap.c | 19 ++++++++++++++++---
> pc-bios/s390-ccw/main.c | 7 ++++++-
> pc-bios/s390-ccw/s390-ccw.h | 2 ++
> pc-bios/s390-ccw/secure-ipl.c | 4 ++++
> pc-bios/s390-ccw/secure-ipl.h | 3 +++
> 6 files changed, 47 insertions(+), 4 deletions(-)
>
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> index 205de8bc02..579b7b4993 100644
> --- a/docs/system/s390x/secure-ipl.rst
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -67,3 +67,19 @@ Configuration:
> qemu-system-s390x -machine s390-ccw-virtio, \
> boot-certs.0.path=/.../qemu/certs, \
> boot-certs.1.path=/another/path/cert.pem ...
> +
> +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
"it is understood" sounds weird to me here ... maybe rather:
If both, certificates are provided and the ``secure-boot=on`` option has
been set, a secure boot is performed with error reporting enabled, and the
boot process will abort on any error.
?
> +reported and boot will abort.
> +
> +Configuration:
> +
> +.. code-block:: shell
> +
> + qemu-system-s390x -machine s390-ccw-virtio, \
> + secure-boot=on, \
> + boot-certs.0.path=/.../qemu/certs, \
> + boot-certs.1.path=/another/path/cert.pem ...
...
> diff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c
> index cd798c1198..92e3e1e021 100644
> --- a/pc-bios/s390-ccw/secure-ipl.c
> +++ b/pc-bios/s390-ccw/secure-ipl.c
> @@ -287,6 +287,10 @@ static bool check_sclab_presence(uint8_t *sclab_magic,
> comps->device_entries[comp_index].cei |= S390_IPL_COMPONENT_CEI_INVALID_SCLAB;
>
> /* a missing SCLAB will not be reported in audit mode */
> + if (boot_mode == ZIPL_BOOT_MODE_SECURE) {
> + zipl_secure_handle("Magic is not matched. SCLAB does not exist");
I'm not a native speaker, but maybe rather "Magic does not match" ?
> + }
Indentation of the } is off by 1 here.
> return false;
> }
>
> diff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h
> index 87aa6e1465..d7786158c4 100644
> --- a/pc-bios/s390-ccw/secure-ipl.h
> +++ b/pc-bios/s390-ccw/secure-ipl.h
> @@ -58,6 +58,9 @@ static inline void zipl_secure_handle(const char *message)
> case ZIPL_BOOT_MODE_SECURE_AUDIT:
> IPL_check(false, message);
> break;
> + case ZIPL_BOOT_MODE_SECURE:
> + IPL_assert(false, message);
Using IPL_assert() with false looks weird. Why not simply panic(message)
instead?
> + break;
> default:
> break;
> }
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 25/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (23 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 24/28] pc-bios/s390-ccw: Handle true secure IPL mode Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-29 18:11 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 26/28] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
` (3 subsequent siblings)
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
The current approach to enable secure boot relies on providing
secure-boot and boot-certs parameters of s390-ccw-virtio machine
type option, which apply to all boot devices.
With the possibility of multiple boot devices, secure boot expects all
provided devices to be supported and eligible (e.g.,
virtio-blk/virtio-scsi using the SCSI scheme).
If multiple boot devices are provided and include an unsupported (e.g.,
ECKD, VFIO) or a non-eligible (e.g., Net) device, the boot process will
terminate with an error logged to the console.
Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
---
pc-bios/s390-ccw/bootmap.c | 31 ++++++++-------
pc-bios/s390-ccw/main.c | 75 ++++++++++++++++++++++++++++++++++---
pc-bios/s390-ccw/s390-ccw.h | 1 +
3 files changed, 88 insertions(+), 19 deletions(-)
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 3ab89b91fb..8297f22c3c 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -1136,25 +1136,35 @@ ZiplBootMode zipl_mode(uint8_t hdr_flags)
return ZIPL_BOOT_MODE_NORMAL;
}
+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_BOOT_MODE_SECURE_AUDIT ||
- boot_mode == ZIPL_BOOT_MODE_SECURE) {
- 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_BOOT_MODE_SECURE_AUDIT ||
- boot_mode == ZIPL_BOOT_MODE_SECURE) {
- panic("Virtio net boot device does not support secure boot!");
- }
netmain();
puts("Failed to IPL from this network!");
return;
@@ -1165,11 +1175,6 @@ void zipl_load(void)
return;
}
- if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT ||
- boot_mode == ZIPL_BOOT_MODE_SECURE) {
- 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 c5b425209a..228b52a37e 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 == ZIPL_BOOT_MODE_UNSPECIFIED) {
boot_mode = zipl_mode(iplb->hdr_flags);
}
@@ -281,14 +316,40 @@ static void ipl_boot_device(void)
panic("Need at least one certificate for secure boot!");
}
+ if (boot_mode == ZIPL_BOOT_MODE_NORMAL) {
+ 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_BOOT_MODE_SECURE_AUDIT ||
- boot_mode == ZIPL_BOOT_MODE_SECURE) {
- panic("Passthrough (vfio) device does not support secure boot!");
- }
-
dasd_ipl(blk_schid, cutype);
break;
case CU_TYPE_VIRTIO:
@@ -338,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 389cc8ea7c..3009104686 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -93,6 +93,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.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 25/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices
2025-09-17 23:21 ` [PATCH v6 25/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
@ 2025-09-29 18:11 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-29 18:11 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> The current approach to enable secure boot relies on providing
> secure-boot and boot-certs parameters of s390-ccw-virtio machine
> type option, which apply to all boot devices.
>
> With the possibility of multiple boot devices, secure boot expects all
> provided devices to be supported and eligible (e.g.,
> virtio-blk/virtio-scsi using the SCSI scheme).
>
> If multiple boot devices are provided and include an unsupported (e.g.,
> ECKD, VFIO) or a non-eligible (e.g., Net) device, the boot process will
> terminate with an error logged to the console.
>
> Signed-off-by: Zhuoying Cai <zycai@linux.ibm.com>
> ---
> pc-bios/s390-ccw/bootmap.c | 31 ++++++++-------
> pc-bios/s390-ccw/main.c | 75 ++++++++++++++++++++++++++++++++++---
> pc-bios/s390-ccw/s390-ccw.h | 1 +
> 3 files changed, 88 insertions(+), 19 deletions(-)
>
> diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
> index 3ab89b91fb..8297f22c3c 100644
> --- a/pc-bios/s390-ccw/bootmap.c
> +++ b/pc-bios/s390-ccw/bootmap.c
> @@ -1136,25 +1136,35 @@ ZiplBootMode zipl_mode(uint8_t hdr_flags)
> return ZIPL_BOOT_MODE_NORMAL;
> }
>
> +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_BOOT_MODE_SECURE_AUDIT ||
> - boot_mode == ZIPL_BOOT_MODE_SECURE) {
> - 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_BOOT_MODE_SECURE_AUDIT ||
> - boot_mode == ZIPL_BOOT_MODE_SECURE) {
> - panic("Virtio net boot device does not support secure boot!");
> - }
> netmain();
> puts("Failed to IPL from this network!");
> return;
> @@ -1165,11 +1175,6 @@ void zipl_load(void)
> return;
> }
>
> - if (boot_mode == ZIPL_BOOT_MODE_SECURE_AUDIT ||
> - boot_mode == ZIPL_BOOT_MODE_SECURE) {
> - 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 c5b425209a..228b52a37e 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 == ZIPL_BOOT_MODE_UNSPECIFIED) {
> boot_mode = zipl_mode(iplb->hdr_flags);
> }
> @@ -281,14 +316,40 @@ static void ipl_boot_device(void)
> panic("Need at least one certificate for secure boot!");
> }
>
> + if (boot_mode == ZIPL_BOOT_MODE_NORMAL) {
> + 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));
While IplParameterBlock is huge (4k), a malloc is justified for that. But
QemuIplParameters is just a bunch of bytes (28 if I counted correctly), so I
think it would be nicer to use a stack variable for that instead.
Thomas
> + 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);
> +}
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 26/28] hw/s390x/ipl: Handle secure boot without specifying a boot device
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (24 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 25/28] pc-bios/s390-ccw: Handle secure boot with multiple boot devices Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-17 23:21 ` [PATCH v6 27/28] docs/specs: Add secure IPL documentation Zhuoying Cai
` (2 subsequent siblings)
28 siblings, 0 replies; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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 42b25513a2..5edbc2451b 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -771,6 +771,16 @@ void s390_ipl_prepare_cpu(S390CPU *cpu)
}
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.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* [PATCH v6 27/28] docs/specs: Add secure IPL documentation
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (25 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 26/28] hw/s390x/ipl: Handle secure boot without specifying a boot device Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-10-07 11:40 ` Thomas Huth
2025-09-17 23:21 ` [PATCH v6 28/28] docs/system/s390x: " Zhuoying Cai
2025-09-26 12:38 ` [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Thomas Huth
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
Add documentation for secure IPL
Signed-off-by: Collin Walling <walling@linux.ibm.com>
---
docs/specs/s390x-secure-ipl.rst | 53 +++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
index a19b976e25..8238fad30a 100644
--- a/docs/specs/s390x-secure-ipl.rst
+++ b/docs/specs/s390x-secure-ipl.rst
@@ -1,5 +1,58 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
+s390 Secure IPL
+===============
+
+Secure IPL (a.k.a. secure boot) enables s390-ccw virtual machines to
+leverage qcrypto libraries and z/Architecture emulations to verify the
+integrity of signed kernels. The qcrypto libraries are used to perform
+certificate validation and signature-verification, whereas the
+z/Architecture emulations are used to ensure secure IPL data has not
+been tampered with, convey data between QEMU and userspace, and set up
+the relevant secure IPL data structures with verification results.
+
+To find out more about using this feature, see ``docs/system/s390x/secure-ipl.rst``.
+
+Note that "userspace" will refer to the s390-ccw BIOS unless stated
+otherwise.
+
+Both QEMU and userspace work in tandem to perform secure IPL. The Secure
+Loading Attributes Facility (SCLAF) is used to check the Secure Code
+Loading Attribute Block (SCLAB) and ensure that secure IPL data has not
+been tampered with. DIAGNOSE 'X'320' is invoked by userspace to query
+the certificate store info and retrieve specific certificates from QEMU.
+DIAGNOSE 'X'508' is used by userspace to leverage qcrypto libraries to
+perform signature-verification in QEMU. Lastly, userspace generates and
+appends an IPL Information Report Block (IIRB) at the end of the IPL
+Parameter Block, which is used by the kernel to store signed and
+verified entries.
+
+The logical steps are as follows:
+
+- Userspace reads data payload from disk (e.g. stage3 boot loader, kernel)
+- Userspace checks the validity of the SCLAB
+- Userspace invokes DIAG 508 subcode 1 and provides it the payload
+- QEMU handles DIAG 508 request by reading the payload and retrieving the
+ certificate store
+- QEMU DIAG 508 utilizes qcrypto libraries to perform signature-verification on
+ the payload, attempting with each cert in the store (until success or exhausted)
+- QEMU DIAG 508 returns:
+
+ - success: index of cert used to verify payload
+ - failure: error code
+
+- Userspace responds to this operation:
+
+ - success: retrieves cert from store via DIAG 320 using returned index
+ - failure: reports with warning (audit mode), aborts with error (secure mode)
+
+- Userspace appends IIRB at the end of the IPLB
+- Userspace kicks off IPL
+
+More information regarding the respective DIAGNOSE commands and IPL data
+structures are outlined within this document.
+
+
s390 Certificate Store and Functions
====================================
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 27/28] docs/specs: Add secure IPL documentation
2025-09-17 23:21 ` [PATCH v6 27/28] docs/specs: Add secure IPL documentation Zhuoying Cai
@ 2025-10-07 11:40 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-10-07 11:40 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
> Add documentation for secure IPL
>
> Signed-off-by: Collin Walling <walling@linux.ibm.com>
> ---
> docs/specs/s390x-secure-ipl.rst | 53 +++++++++++++++++++++++++++++++++
> 1 file changed, 53 insertions(+)
>
> diff --git a/docs/specs/s390x-secure-ipl.rst b/docs/specs/s390x-secure-ipl.rst
> index a19b976e25..8238fad30a 100644
> --- a/docs/specs/s390x-secure-ipl.rst
> +++ b/docs/specs/s390x-secure-ipl.rst
> @@ -1,5 +1,58 @@
> .. SPDX-License-Identifier: GPL-2.0-or-later
>
> +s390 Secure IPL
> +===============
> +
> +Secure IPL (a.k.a. secure boot) enables s390-ccw virtual machines to
> +leverage qcrypto libraries and z/Architecture emulations to verify the
> +integrity of signed kernels. The qcrypto libraries are used to perform
> +certificate validation and signature-verification, whereas the
> +z/Architecture emulations are used to ensure secure IPL data has not
> +been tampered with, convey data between QEMU and userspace, and set up
> +the relevant secure IPL data structures with verification results.
> +
> +To find out more about using this feature, see ``docs/system/s390x/secure-ipl.rst``.
> +
> +Note that "userspace" will refer to the s390-ccw BIOS unless stated
> +otherwise.
> +
> +Both QEMU and userspace work in tandem to perform secure IPL. The Secure
> +Loading Attributes Facility (SCLAF) is used to check the Secure Code
> +Loading Attribute Block (SCLAB) and ensure that secure IPL data has not
> +been tampered with. DIAGNOSE 'X'320' is invoked by userspace to query
> +the certificate store info and retrieve specific certificates from QEMU.
> +DIAGNOSE 'X'508' is used by userspace to leverage qcrypto libraries to
> +perform signature-verification in QEMU. Lastly, userspace generates and
> +appends an IPL Information Report Block (IIRB) at the end of the IPL
> +Parameter Block, which is used by the kernel to store signed and
> +verified entries.
> +
> +The logical steps are as follows:
> +
> +- Userspace reads data payload from disk (e.g. stage3 boot loader, kernel)
> +- Userspace checks the validity of the SCLAB
> +- Userspace invokes DIAG 508 subcode 1 and provides it the payload
> +- QEMU handles DIAG 508 request by reading the payload and retrieving the
> + certificate store
> +- QEMU DIAG 508 utilizes qcrypto libraries to perform signature-verification on
> + the payload, attempting with each cert in the store (until success or exhausted)
80 columns, please.
Thanks,
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH v6 28/28] docs/system/s390x: Add secure IPL documentation
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (26 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 27/28] docs/specs: Add secure IPL documentation Zhuoying Cai
@ 2025-09-17 23:21 ` Zhuoying Cai
2025-09-29 18:23 ` Thomas Huth
2025-09-26 12:38 ` [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Thomas Huth
28 siblings, 1 reply; 89+ messages in thread
From: Zhuoying Cai @ 2025-09-17 23:21 UTC (permalink / raw)
To: thuth, berrange, richard.henderson, david, jrossi, qemu-s390x,
qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, zycai, alifm
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 | 96 ++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
index 579b7b4993..110dea9fdd 100644
--- a/docs/system/s390x/secure-ipl.rst
+++ b/docs/system/s390x/secure-ipl.rst
@@ -1,5 +1,21 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
+s390 Secure IPL
+===============
+
+Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
+verify the integrity of guest kernels.
+
+For technical details of this feature, see ``docs/specs/s390x-secure-ipl.rst``.
+
+This document explains how to use secure IPL with s390x in QEMU. It covers
+new command line options for providing certificates and enabling secure IPL,
+the different IPL modes (Normal, Audit, and Secure), and system requirements.
+
+A quickstart guide is provided to demonstrate how to generate certificates,
+sign images, and start a guest in Secure Mode.
+
+
Secure IPL Command Line Options
===============================
@@ -83,3 +99,83 @@ Configuration:
secure-boot=on, \
boot-certs.0.path=/.../qemu/certs, \
boot-certs.1.path=/another/path/cert.pem ...
+
+
+Constraints
+===========
+
+The following constraints apply when attempting to secure IPL an s390 guest:
+
+- z16 CPU model
+- certificates must be in X.509 PEM format
+- only support for SCSI scheme of virtio-blk/virtio-scsi devices
+- a boot device must be specified
+- any unsupported devices (e.g., ECKD and VFIO) or non-eligible devices (e.g.,
+ Net) will cause the entire boot process terminating early with an error
+ logged to the console.
+
+
+Secure IPL Quickstart
+=====================
+
+Build QEMU with gnutls enabled
+-------------------------------
+
+.. code-block:: shell
+
+ ./configure … --enable-gnutls
+
+Generate certificate (e.g. via certtool)
+----------------------------------------
+
+A private key is required before generating a certificate. This key must be kept secure
+and confidential.
+
+Use an RSA private key for signing.
+
+.. code-block:: shell
+
+ certtool --generate-privkey > key.pem
+
+A self-signed certificate requires the organization name. Use the ``cert.info`` template
+to pre-fill values and avoid interactive prompts from certtool.
+
+.. code-block:: shell
+
+ cat > cert.info <<EOF
+ cn = "My Name"
+ expiration_days = 36500
+ cert_signing_key
+ EOF
+
+ certtool --generate-self-signed \
+ --load-privkey key.pem \
+ --template cert.info \
+ --hash=SHA256 \
+ --outfile cert.pem
+
+Sign Images (e.g. via sign-file)
+--------------------------------
+
+- signing must be performed on a KVM guest filesystem
+- sign-file script used in the example below is located within the kernel source
+ repo
+
+.. code-block:: shell
+
+ ./sign-file sha256 key.pem cert.pem /boot/vmlinuz-…
+ ./sign-file sha256 key.pem cert.pem /usr/lib/s390-tools/stage3.bin
+
+Run zipl with secure boot enabled
+---------------------------------
+
+.. code-block:: shell
+
+ zipl --secure 1 -V
+
+Start Guest with Cmd Options
+----------------------------
+
+.. code-block:: shell
+
+ qemu-system-s390x -machine s390-ccw-virtio,secure-boot=on,boot-certs.0.path=cert.pem ...
--
2.50.1
^ permalink raw reply related [flat|nested] 89+ messages in thread
* Re: [PATCH v6 28/28] docs/system/s390x: Add secure IPL documentation
2025-09-17 23:21 ` [PATCH v6 28/28] docs/system/s390x: " Zhuoying Cai
@ 2025-09-29 18:23 ` Thomas Huth
0 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-29 18:23 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, 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 | 96 ++++++++++++++++++++++++++++++++
> 1 file changed, 96 insertions(+)
>
> diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst
> index 579b7b4993..110dea9fdd 100644
> --- a/docs/system/s390x/secure-ipl.rst
> +++ b/docs/system/s390x/secure-ipl.rst
> @@ -1,5 +1,21 @@
> .. SPDX-License-Identifier: GPL-2.0-or-later
>
> +s390 Secure IPL
> +===============
> +
> +Secure IPL, also known as secure boot, enables s390-ccw virtual machines to
> +verify the integrity of guest kernels.
> +
> +For technical details of this feature, see ``docs/specs/s390x-secure-ipl.rst``.
> +
> +This document explains how to use secure IPL with s390x in QEMU. It covers
> +new command line options for providing certificates and enabling secure IPL,
> +the different IPL modes (Normal, Audit, and Secure), and system requirements.
> +
> +A quickstart guide is provided to demonstrate how to generate certificates,
> +sign images, and start a guest in Secure Mode.
> +
> +
> Secure IPL Command Line Options
> ===============================
>
> @@ -83,3 +99,83 @@ Configuration:
> secure-boot=on, \
> boot-certs.0.path=/.../qemu/certs, \
> boot-certs.1.path=/another/path/cert.pem ...
> +
> +
> +Constraints
> +===========
> +
> +The following constraints apply when attempting to secure IPL an s390 guest:
> +
> +- z16 CPU model
> +- certificates must be in X.509 PEM format
> +- only support for SCSI scheme of virtio-blk/virtio-scsi devices
> +- a boot device must be specified
> +- any unsupported devices (e.g., ECKD and VFIO) or non-eligible devices (e.g.,
> + Net) will cause the entire boot process terminating early with an error
> + logged to the console.
> +
> +
> +Secure IPL Quickstart
> +=====================
> +
> +Build QEMU with gnutls enabled
> +-------------------------------
> +
> +.. code-block:: shell
> +
> + ./configure … --enable-gnutls
> +
> +Generate certificate (e.g. via certtool)
> +----------------------------------------
> +
> +A private key is required before generating a certificate. This key must be kept secure
> +and confidential.
Please try to keep line length below 80 columns. Longer lines are ok if it
makes code more readable (or maybe necessary, as in "code-block" sections
later in this patch), but that's not the case here. See also "Line width" in
docs/devel/style.rst.
> +Use an RSA private key for signing.
> +
> +.. code-block:: shell
> +
> + certtool --generate-privkey > key.pem
> +
> +A self-signed certificate requires the organization name. Use the ``cert.info`` template
> +to pre-fill values and avoid interactive prompts from certtool.
dito.
Thanks,
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices
2025-09-17 23:21 [PATCH v6 00/28] Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices Zhuoying Cai
` (27 preceding siblings ...)
2025-09-17 23:21 ` [PATCH v6 28/28] docs/system/s390x: " Zhuoying Cai
@ 2025-09-26 12:38 ` Thomas Huth
28 siblings, 0 replies; 89+ messages in thread
From: Thomas Huth @ 2025-09-26 12:38 UTC (permalink / raw)
To: Zhuoying Cai, berrange, richard.henderson, david, jrossi,
qemu-s390x, qemu-devel
Cc: walling, jjherne, pasic, borntraeger, farman, mjrosato, iii,
eblake, armbru, alifm
On 18/09/2025 01.21, Zhuoying Cai wrote:
...
> crypto/meson.build | 5 +-
> crypto/x509-utils.c | 423 +++++++++++++++
> docs/specs/s390x-secure-ipl.rst | 165 ++++++
> docs/system/s390x/secure-ipl.rst | 181 +++++++
> hw/s390x/cert-store.c | 213 ++++++++
> hw/s390x/cert-store.h | 39 ++
> hw/s390x/ipl.c | 62 +++
> hw/s390x/ipl.h | 27 +-
> hw/s390x/meson.build | 1 +
> hw/s390x/s390-virtio-ccw.c | 52 ++
> hw/s390x/sclp.c | 2 +
> include/crypto/x509-utils.h | 131 +++++
> include/hw/s390x/ipl/diag308.h | 34 ++
> include/hw/s390x/ipl/diag320.h | 91 ++++
> include/hw/s390x/ipl/diag508.h | 38 ++
> include/hw/s390x/ipl/qipl.h | 7 +-
> include/hw/s390x/s390-virtio-ccw.h | 3 +
> include/hw/s390x/sclp.h | 4 +-
> pc-bios/s390-ccw/Makefile | 3 +-
> pc-bios/s390-ccw/bootmap.c | 107 +++-
> pc-bios/s390-ccw/bootmap.h | 11 +
> pc-bios/s390-ccw/iplb.h | 96 +++-
> pc-bios/s390-ccw/jump2ipl.c | 6 +-
> pc-bios/s390-ccw/main.c | 111 +++-
> pc-bios/s390-ccw/netmain.c | 8 +-
> pc-bios/s390-ccw/s390-ccw.h | 19 +
> pc-bios/s390-ccw/sclp.c | 52 ++
> pc-bios/s390-ccw/sclp.h | 7 +
> pc-bios/s390-ccw/secure-ipl.c | 781 ++++++++++++++++++++++++++++
> pc-bios/s390-ccw/secure-ipl.h | 212 ++++++++
> qapi/machine-s390x.json | 22 +
> qapi/pragma.json | 1 +
> qemu-options.hx | 10 +-
> target/s390x/cpu_features.c | 7 +
> target/s390x/cpu_features.h | 1 +
> target/s390x/cpu_features_def.h.inc | 5 +
> target/s390x/cpu_models.c | 7 +
> target/s390x/diag.c | 555 +++++++++++++++++++-
> target/s390x/gen-features.c | 7 +
> target/s390x/kvm/kvm.c | 34 ++
> target/s390x/s390x-internal.h | 4 +
> target/s390x/tcg/misc_helper.c | 14 +
> 42 files changed, 3488 insertions(+), 70 deletions(-)
> create mode 100644 docs/specs/s390x-secure-ipl.rst
> create mode 100644 docs/system/s390x/secure-ipl.rst
> create mode 100644 hw/s390x/cert-store.c
> create mode 100644 hw/s390x/cert-store.h
> create mode 100644 include/hw/s390x/ipl/diag308.h
> create mode 100644 include/hw/s390x/ipl/diag320.h
> create mode 100644 include/hw/s390x/ipl/diag508.h
> create mode 100644 pc-bios/s390-ccw/secure-ipl.c
> create mode 100644 pc-bios/s390-ccw/secure-ipl.h
Hi,
looking at the file list, there does not seem to be any test in this series
... could you please add some functional tests to make sure that the feature
is working as expected?
Thanks,
Thomas
^ permalink raw reply [flat|nested] 89+ messages in thread