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?
> 
> >  }
> >  
> >  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?).

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


> > +
> > +        # 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()

Reply via email to