From 50013b3e0aade22e3f9b1520c262d9a98f7a94b8 Mon Sep 17 00:00:00 2001 From: R Nageswara Sastry Date: Mon, 1 Apr 2024 15:45:02 +0530 Subject: [PATCH] testcases/OpTestKexec.py: Add unsigned kernel test case Add unsigned kernel test when secure boot enabled. Add additional file ie extract-module-sig.pl, taken from linux kernel source. This file helps in extracting an unsigned kernel from signed kernel. Signed-off-by: R Nageswara Sastry --- test_binaries/extract-module-sig.pl | 138 ++++++++++++++++++++++++++++ testcases/OpTestKexec.py | 42 +++++++++ 2 files changed, 180 insertions(+) create mode 100755 test_binaries/extract-module-sig.pl diff --git a/test_binaries/extract-module-sig.pl b/test_binaries/extract-module-sig.pl new file mode 100755 index 000000000..36a2f59c4 --- /dev/null +++ b/test_binaries/extract-module-sig.pl @@ -0,0 +1,138 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# extract-mod-sig +# +# Reads the module file and writes out some or all of the signature +# section to stdout. Part is the bit to be written and is one of: +# +# -0: The unsigned module, no signature data at all +# -a: All of the signature data, including magic number +# -d: Just the descriptor values as a sequence of numbers +# -n: Just the signer's name +# -k: Just the key ID +# -s: Just the crypto signature or PKCS#7 message +# +use warnings; +use strict; + +die "Format: $0 -[0adnks] module-file >out\n" + if ($#ARGV != 1); + +my $part = $ARGV[0]; +my $modfile = $ARGV[1]; + +my $magic_number = "~Module signature appended~\n"; + +# +# Read the module contents +# +open FD, "<$modfile" || die $modfile; +binmode(FD); +my @st = stat(FD); +die "$modfile" unless (@st); +my $buf = ""; +my $len = sysread(FD, $buf, $st[7]); +die "$modfile" unless (defined($len)); +die "Short read on $modfile\n" unless ($len == $st[7]); +close(FD) || die $modfile; + +print STDERR "Read ", $len, " bytes from module file\n"; + +die "The file is too short to have a sig magic number and descriptor\n" + if ($len < 12 + length($magic_number)); + +# +# Check for the magic number and extract the information block +# +my $p = $len - length($magic_number); +my $raw_magic = substr($buf, $p); + +die "Magic number not found at $len\n" + if ($raw_magic ne $magic_number); +print STDERR "Found magic number at $len\n"; + +$p -= 12; +my $raw_info = substr($buf, $p, 12); + +my @info = unpack("CCCCCxxxN", $raw_info); +my ($algo, $hash, $id_type, $name_len, $kid_len, $sig_len) = @info; + +if ($id_type == 0) { + print STDERR "Found PGP key identifier\n"; +} elsif ($id_type == 1) { + print STDERR "Found X.509 cert identifier\n"; +} elsif ($id_type == 2) { + print STDERR "Found PKCS#7/CMS encapsulation\n"; +} else { + print STDERR "Found unsupported identifier type $id_type\n"; +} + +# +# Extract the three pieces of info data +# +die "Insufficient name+kid+sig data in file\n" + unless ($p >= $name_len + $kid_len + $sig_len); + +$p -= $sig_len; +my $raw_sig = substr($buf, $p, $sig_len); +$p -= $kid_len; +my $raw_kid = substr($buf, $p, $kid_len); +$p -= $name_len; +my $raw_name = substr($buf, $p, $name_len); + +my $module_len = $p; + +if ($sig_len > 0) { + print STDERR "Found $sig_len bytes of signature ["; + my $n = $sig_len > 16 ? 16 : $sig_len; + foreach my $i (unpack("C" x $n, substr($raw_sig, 0, $n))) { + printf STDERR "%02x", $i; + } + print STDERR "]\n"; +} + +if ($kid_len > 0) { + print STDERR "Found $kid_len bytes of key identifier ["; + my $n = $kid_len > 16 ? 16 : $kid_len; + foreach my $i (unpack("C" x $n, substr($raw_kid, 0, $n))) { + printf STDERR "%02x", $i; + } + print STDERR "]\n"; +} + +if ($name_len > 0) { + print STDERR "Found $name_len bytes of signer's name [$raw_name]\n"; +} + +# +# Produce the requested output +# +if ($part eq "-0") { + # The unsigned module, no signature data at all + binmode(STDOUT); + print substr($buf, 0, $module_len); +} elsif ($part eq "-a") { + # All of the signature data, including magic number + binmode(STDOUT); + print substr($buf, $module_len); +} elsif ($part eq "-d") { + # Just the descriptor values as a sequence of numbers + print join(" ", @info), "\n"; +} elsif ($part eq "-n") { + # Just the signer's name + print STDERR "No signer's name for PKCS#7 message type sig\n" + if ($id_type == 2); + binmode(STDOUT); + print $raw_name; +} elsif ($part eq "-k") { + # Just the key identifier + print STDERR "No key ID for PKCS#7 message type sig\n" + if ($id_type == 2); + binmode(STDOUT); + print $raw_kid; +} elsif ($part eq "-s") { + # Just the crypto signature or PKCS#7 message + binmode(STDOUT); + print $raw_sig; +} diff --git a/testcases/OpTestKexec.py b/testcases/OpTestKexec.py index 3b68e25cf..0f842af67 100644 --- a/testcases/OpTestKexec.py +++ b/testcases/OpTestKexec.py @@ -48,6 +48,7 @@ kexec, except for the kexec in loop test. ''' +import os import unittest import OpTestLogger @@ -58,6 +59,7 @@ log = OpTestLogger.optest_logger_glob.get_logger(__name__) + class OpTestKexec(unittest.TestCase): """kexec test class""" @@ -449,6 +451,45 @@ def test_kexec_in_loop(self): self.assertTrue(ret, "kexec failed, at iteration cnt: " + str(i)) log.info("Completed kexec iteration cnt %s." % str(i)) + def test_kexec_unsigned_kernel(self): + """ + Tests the unsigned kernel when secure boot is enabled. + + From the signed kernel image, create unsigned kernel image. The same + used for 'kexec' when secure boot is enabled. Expected failure while + loading kexec. + """ + if self.os_level_secureboot: + if self.op_test_util.check_kernel_signature(): + self.cv_HOST.copy_test_file_to_host("extract-module-sig.pl") + self.kernel_image = "/boot/%s" % self.kernel_image + cmd = "/tmp/extract-module-sig.pl -0 %s > %s.unsigned" % \ + (self.kernel_image, self.kernel_image) + out = self.cv_HOST.host_run_command(cmd) + if '0' not in out[3]: + self.skipTest("Can not create unsigned binary. Using- %s" + % cmd) + cmd = "kexec -s -l %s.unsigned" % self.kernel_image + ret = self.execute_kexec_cmd(cmd) + if ret: + self.fail("kexec loaded unsigned kernel when secure boot" + " is enabled.") + else: + log.info("kexec loading with unsigned kernel failed. " + "Which is expected.") + else: + self.skipTest("secure boot is disabled.") + + def tearDown(self): + unsigned_kernel = "%s.unsigned" % self.kernel_image + if 'boot' not in unsigned_kernel: + unsigned_kernel = "/boot/%s" % unsigned_kernel + if os.path.exists(unsigned_kernel): + os.remove(unsigned_kernel) + sign_file = "/tmp/extract-module-sig.pl" + if os.path.exists(sign_file): + os.remove(sign_file) + def kexec_suite(): """kexec test suite""" @@ -461,4 +502,5 @@ def kexec_suite(): suite.addTest(OpTestKexec('test_file_load_and_exec')) suite.addTest(OpTestKexec('test_syscall_load_and_exec')) suite.addTest(OpTestKexec('test_kexec_in_loop')) + suite.addTest(OpTestKexec('test_kexec_unsigned_kernel')) return suite