Thanks for the feedback!
On 6/5/26 12:25 PM, Eric Farman wrote:
> On Wed, 2026-06-03 at 14:50 -0400, Eric Farman wrote:
>> On Tue, 2026-05-05 at 16:19 -0400, Zhuoying Cai wrote:
>>> Add functional test for secure IPL.
>>>
>>> Signed-off-by: Zhuoying Cai <[email protected]>
>>> ---
>>> tests/functional/s390x/meson.build | 2 +
>>> tests/functional/s390x/test_secure_ipl.py | 148 ++++++++++++++++++++++
>>> 2 files changed, 150 insertions(+)
>>> create mode 100755 tests/functional/s390x/test_secure_ipl.py
>>>
>>> diff --git a/tests/functional/s390x/meson.build
>>> b/tests/functional/s390x/meson.build
>>> index b065b666bc..01cb2d1d4c 100644
>>> --- a/tests/functional/s390x/meson.build
>>> +++ b/tests/functional/s390x/meson.build
>>> @@ -2,6 +2,7 @@
>>>
>>> test_s390x_timeouts = {
>>> 'ccw_virtio' : 420,
>>> + 'secure_ipl' : 280,
>>
>> How'd you come up with this value?
>>
I based it on previous test runs where the test consistently completed
within 200 seconds, so I set the timeout to 280 seconds to provide some
buffer.
However, I’ve noticed that when the cached qcow2 file is deleted and the
test is run repeatedly, the download time can vary significantly and
sometimes exceed this limit, leading to timeouts.
I’m not sure what the best approach is here — whether to increase the
timeout further or remove it entirely. Do you have any recommendations?
>>> }
>>>
>>> tests_s390x_system_quick = [
>>> @@ -14,6 +15,7 @@ tests_s390x_system_thorough = [
>>> 'ccw_virtio',
>>> 'pxelinux',
>>> 'replay',
>>> + 'secure_ipl',
>>> 'topology',
>>> 'tuxrun',
>>> ]
>>> diff --git a/tests/functional/s390x/test_secure_ipl.py
>>> b/tests/functional/s390x/test_secure_ipl.py
>>> new file mode 100755
>>> index 0000000000..0980daace1
>>> --- /dev/null
>>> +++ b/tests/functional/s390x/test_secure_ipl.py
>>> @@ -0,0 +1,148 @@
>>> +#!/usr/bin/env python3
>>> +#
>>> +# s390x Secure IPL functional test: validates secure-boot verification
>>> results
>>> +#
>>> +# SPDX-License-Identifier: GPL-2.0-or-later
>>
>> A general comment, you'll need some form of docstring for the entire file,
>> and any classes/methods
>> defined later on.
>>
>>> +
>>> +from subprocess import check_call, DEVNULL
>>> +
>>> +from qemu_test import QemuSystemTest, Asset, get_qemu_img
>>> +from qemu_test import exec_command_and_wait_for_pattern, exec_command
>>> +from qemu_test import wait_for_console_pattern, skipBigDataTest
>>> +
>>> +class S390xSecureIpl(QemuSystemTest):
>>> + ASSET_F40_QCOW2 = Asset(
>>> + ('https://archives.fedoraproject.org/pub/archive/'
>>> + 'fedora-secondary/releases/40/Server/s390x/images/'
>>> + 'Fedora-Server-KVM-40-1.14.s390x.qcow2'),
>>> + '091c232a7301be14e19c76ce9a0c1cbd2be2c4157884a731e1fc4f89e7455a5f')
>>> +
>>> + def __init__(self, *args, **kwargs):
>>> + super().__init__(*args, **kwargs)
>>> + self.root_password = None
>>> + self.qcow2_path = None
>>> + self.cert_path = None
>>> + self.prompt = None
>>> +
>>> + # Boot a temporary VM to set up secure IPL image:
>>> + # - Create certificate
>>> + # - Sign stage3 binary and kernel
>>> + # - Run zipl
>>> + # - Extract certificate
>>
>> Can we break out those four chunks into standalone routines, so that we can
>> identify them
>> individually rather than as part of the novella that follows?
>>
>>> + def setup_s390x_secure_ipl(self):
>>> + temp_vm = self.get_vm(name='sipl_setup')
>>> + temp_vm.set_machine('s390-ccw-virtio')
>>> +
>>> + asset_path = self.ASSET_F40_QCOW2.fetch()
>>> + self.qcow2_path = self.scratch_file('f40.qcow2')
>>> + qemu_img = get_qemu_img(self)
>>> + check_call([qemu_img, 'create', '-f', 'qcow2', '-b', asset_path,
>>> + '-F', 'qcow2', self.qcow2_path], stdout=DEVNULL,
>>> stderr=DEVNULL)
>>> +
>>> + temp_vm.set_console()
>>> + temp_vm.add_args('-nographic',
>>> + '-accel', 'kvm',
>>> + '-m', '1024',
>>> + '-drive',
>>> +
>>> f'id=drive0,if=none,format=qcow2,file={self.qcow2_path}',
>>> + '-device',
>>> 'virtio-blk-ccw,drive=drive0,bootindex=1')
>>> + temp_vm.launch()
>>> +
>>> + # Initial root account setup (Fedora first boot screen)
>>> + self.root_password = 'fedora40password'
>>> + wait_for_console_pattern(self, 'Please make a selection from the
>>> above',
>>> + vm=temp_vm)
>>> + exec_command_and_wait_for_pattern(self, '4', 'Password:',
>>> vm=temp_vm)
>>> + exec_command_and_wait_for_pattern(self, self.root_password,
>>> + 'Password (confirm):',
>>> vm=temp_vm)
>>> + exec_command_and_wait_for_pattern(self, self.root_password,
>>> + 'Please make a selection from the
>>> above',
>>> + vm=temp_vm)
>>> +
>>> + # Login as root
>>> + self.prompt = '[root@localhost ~]#'
>>> + exec_command_and_wait_for_pattern(self, 'c', 'localhost login:',
>>> vm=temp_vm)
>>> + exec_command_and_wait_for_pattern(self, 'root', 'Password:',
>>> vm=temp_vm)
>>> + exec_command_and_wait_for_pattern(self, self.root_password,
>>> self.prompt,
>>> + vm=temp_vm)
>>> +
>>> + # Certificate generation
>>> + exec_command_and_wait_for_pattern(self,
>>> + 'openssl version', 'OpenSSL
>>> 3.2.1 30',
>>> + vm=temp_vm)
>>> + exec_command_and_wait_for_pattern(self,
>>> + 'openssl req -new -x509 -newkey rsa:2048 '
>>> + '-keyout mykey.pem -outform PEM -out
>>> mycert.pem '
>>> + '-days 36500 -subj "/CN=My Name/" -nodes
>>> -verbose',
>>> + 'Writing private key to \'mykey.pem\'',
>>> vm=temp_vm)
>>> +
>>> + # Install kernel-devel (needed for sign-file)
>>> + exec_command_and_wait_for_pattern(self,
>>> + 'sudo dnf install kernel-devel-$(uname -r)
>>> -y',
>>> + 'Complete!', vm=temp_vm)
>>
>> This is the last thing I see in base.log when running this test on my
>> system. Looking at console
>> log, this errors out:
>>
>> 2026-06-03 20:08:33,829: ^[[?2004h[root@localhost ~]# sudo dnf install
>> kernel-devel-$(uname -r) -y
>> 2026-06-03 20:08:34,147: ^[[?2004l^MFedora 40 - s390x [===
>> ] --- B/s | 0
>> B --:-- ETA^MFedora 40 - s390x 0.0 B/s |
>> 0 B 00:00
>> 2026-06-03 20:08:34,147: Errors during downloading metadata for repository
>> 'fedora':
>> 2026-06-03 20:08:34,147: - Curl error (6): Couldn't resolve host name for
>> https://mirrors.fedoraproject.org/metalink?repo=fedora-40&arch=s390x [Could
>> not resolve host:
>> mirrors.fedoraproject.org]
>> 2026-06-03 20:08:34,207: Error: Failed to download metadata for repo
>> 'fedora': Cannot prepare
>> internal mirrorlist: Curl error (6): Couldn't resolve host name for
>> https://mirrors.fedoraproject.org/metalink?repo=fedora-40&arch=s390x [Could
>> not resolve host:
>> mirrors.fedoraproject.org]
>>
>>
>> Surrounding this, I'm noting that this is surrounded by messages from
>> NetworkManager waiting for it
>> to come online, so maybe there needs to be a check that there's actually a
>> network here, so we can
>> bail out rather than timeout.
>
> Josh suggested (thank you!!) that perhaps I didn't have slirp enabled in my
> build when I tried this,
> which is indeed the cause for my hangup. Easy fix on my side, but it's a
> dependency that maybe you
> can account for in this test (or maybe -net none, if you dont need to connect
> it to anything?).
>
slirp is a dependency since the guest needs network access to download
packages. Thanks to Josh for pointing that out.
I’ll add a check during test setup to ensure slirp is enabled so this
dependency is handled explicitly.
> Of course, remedying THAT problem gives me yet another error which I haven't
> chased down. Looks like
> the first of these [*] failed later on. This is a particularly crusty system
> I'm using, so maybe
> it's simply another thing that is missing.
>
>>
>>> + wait_for_console_pattern(self, self.prompt, vm=temp_vm)
>>> + exec_command_and_wait_for_pattern(self,
>>> + 'ls /usr/src/kernels/$(uname
>>> -r)/scripts/',
>>> + 'sign-file', vm=temp_vm)
>>> +
>>> + # Sign stage3 binary and kernel
>>> + exec_command(self, '/usr/src/kernels/$(uname -r)/scripts/sign-file
>>> '
>>> + 'sha256 mykey.pem mycert.pem
>>> /lib/s390-tools/stage3.bin',
>>> + vm=temp_vm)
>>> + wait_for_console_pattern(self, self.prompt, vm=temp_vm)
>>> + exec_command(self, '/usr/src/kernels/$(uname -r)/scripts/sign-file
>>> '
>>> + 'sha256 mykey.pem mycert.pem /boot/vmlinuz-$(uname
>>> -r)',
>>> + vm=temp_vm)
>>> + wait_for_console_pattern(self, self.prompt, vm=temp_vm)
>>> +
>>> + # Run zipl to prepare for secure boot
>>> + exec_command_and_wait_for_pattern(self, 'zipl --secure 1 -VV',
>>> 'Done.',
>>> + vm=temp_vm)
>>> +
>>> + # Extract certificate to host
>>> + out = exec_command_and_wait_for_pattern(self, 'cat mycert.pem',
>>> + '-----END
>>> CERTIFICATE-----',
>>> + vm=temp_vm)
>>> + # strip first line to avoid console echo artifacts
>>> + cert = "\n".join(out.decode("utf-8").splitlines()[1:])
>>> + self.log.info("%s", cert)
>>> +
>>> + self.cert_path = self.scratch_file("mycert.pem")
>>> +
>>> + with open(self.cert_path, 'w', encoding="utf-8") as file_object:
>>> + file_object.write(cert)
>>> +
>>> + # Shutdown temp vm
>>> + temp_vm.shutdown()
>>> +
>>> + @skipBigDataTest()
>>> + def test_s390x_secure_ipl(self):
>>> + self.require_accelerator('kvm')
>>> + self.setup_s390x_secure_ipl()
>>> +
>>> + self.set_machine('s390-ccw-virtio')
>>> +
>>> + self.vm.set_console()
>>> + self.vm.add_args('-nographic',
>>> + '-machine', 's390-ccw-virtio,secure-boot=on,'
>>> + f'boot-certs.0.path={self.cert_path}',
>>> + '-accel', 'kvm',
>>> + '-m', '1024',
>>> + '-drive',
>>> +
>>> f'id=drive1,if=none,format=qcow2,file={self.qcow2_path}',
>>> + '-device',
>>> 'virtio-blk-ccw,drive=drive1,bootindex=1')
>>> + self.vm.launch()
>>> +
>>> + # Expect two verified components
>>> + verified_output = "Verified component"
>>> + wait_for_console_pattern(self, verified_output)
>>> + wait_for_console_pattern(self, verified_output)
>
> [*] Failure occurs here, because guest panicked. Surely it's something not
> set up correctly on the
> guest that was created, because it's happening as soon as it tries to kick
> over to the guest kernel,
> but I don't see any errors in the log that suggests anything amiss:
>
> console.log:
> ...
> 2026-06-05 17:51:03,344: -----END CERTIFICATE-----
> 2026-06-05 17:51:03,398: LOADPARM=[ ]
> 2026-06-05 17:51:03,398:
> 2026-06-05 17:51:03,398: Using virtio-blk.
> 2026-06-05 17:51:03,399: Using SCSI scheme.
> 2026-06-05 17:51:03,408: ..........
>
> base.log:
> ...
> 2026-06-05 17:51:03,398 - DEBUG: qemu.qmp.protocol.default._set_state
> Transitioning from
> 'Runstate.CONNECTING' to 'Runstate.RUNNING'.
> 2026-06-05 17:51:03,398 - DEBUG: qemu-test._console_interaction Console
> interaction:
> success_msg='Verified component' failure_msg='None' send_string='None'
> 2026-06-05 17:51:03,398 - DEBUG: qemu.machine.machine.console_socket Opening
> console socket
> 2026-06-05 17:51:03,410 - DEBUG: qemu.machine.machine.shutdown Shutting down
> VM appliance;
> timeout=30
> 2026-06-05 17:51:03,410 - DEBUG: qemu.machine.machine._soft_shutdown
> Attempting graceful termination
> 2026-06-05 17:51:03,410 - DEBUG: qemu.machine.machine._early_cleanup Closing
> console socket
> 2026-06-05 17:51:03,411 - DEBUG: qemu.machine.machine._soft_shutdown Politely
> asking QEMU to
> terminate
> 2026-06-05 17:51:03,411 - DEBUG: qemu.qmp.protocol.default._cb_outbound --> {
> "execute": "quit"
> }
> 2026-06-05 17:51:03,411 - DEBUG: qemu.qmp.protocol.default._cb_inbound <-- {
> "timestamp": {
> "seconds": 1780674663,
> "microseconds": 409153
> },
> "event": "GUEST_PANICKED",
> "data": {
> "action": "poweroff",
> "info": {
> "core": 0,
> "psw-addr": 17682,
> "reason": "disabled-wait",
> "psw-mask": 562952100904960,
> "type": "s390"
> }
> }
> }
>
>
The console.log should look like this when the guest components are
successfully verified:
...
2026-06-05 20:34:33,447: -----END CERTIFICATE-----
2026-06-05 20:34:33,851: LOADPARM=[ ]
2026-06-05 20:34:33,851:
2026-06-05 20:34:33,852: Using virtio-blk.
2026-06-05 20:34:33,854: Using SCSI scheme.
2026-06-05 20:34:33,856: ....Verified component
2026-06-05 20:34:33,856:
2026-06-05 20:34:34,307: ....Verified component
2026-06-05 20:34:34,307:
2026-06-05 20:34:34,316: ....
In your console.log, it looks like the components are being loaded, but
the verification step is not performed. This might be because the test
is not using the built BIOS image?
For reference, here are the build steps I used:
# ninja -C build
# make -C build/pc-bios/s390-ccw
# cp build/pc-bios/s390-ccw/s390-ccw.img pc-bios
# make install
>>> +
>>> + # Login and verify the vm is booted using secure boot
>>> + wait_for_console_pattern(self, 'localhost login:')
>>> + exec_command_and_wait_for_pattern(self, 'root', 'Password:')
>>> + exec_command_and_wait_for_pattern(self, self.root_password,
>>> self.prompt)
>>> + exec_command_and_wait_for_pattern(self, 'cat
>>> /sys/firmware/ipl/secure', '1')
>>> +
>>> +if __name__ == '__main__':
>>> + QemuSystemTest.main()