diff mbox series

[autotest-client-tests] UBUNTU: SAUCE: ubuntu_boot: implement revocation list checks

Message ID 20210728162716.79507-1-dimitri.ledkov@canonical.com
State New
Headers show
Series [autotest-client-tests] UBUNTU: SAUCE: ubuntu_boot: implement revocation list checks | expand

Commit Message

Dimitri John Ledkov July 28, 2021, 4:27 p.m. UTC
Implement revocation list checks. If kernel supports revocation lists,
check that 2012 canonical signing key is revoked.

Most kernels will skip this test, those kernels that have support for
revocation lists will check that it is correctly configured and
visible at runtime.

Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
---

 Note, tested the function in question partially on v5.10 and v5.13
 kernels. I failed at using the test harness directly to partially
 execute this test case alone. Thus I am not sure if it runs with
 python3 or python2, as I was getting exceptions raised from autotest
 itself.

 ubuntu_boot/control.ubuntu |  1 +
 ubuntu_boot/ubuntu_boot.py | 30 +++++++++++++++++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

Comments

Sean Feole July 29, 2021, 1:39 a.m. UTC | #1
This feature looks like a great addition, however, giving how critical 
the ubuntu_boot a-c-t is (This test is now a gate for initial kernel 
builds), we need to be careful how we update it.

Upon testing this patch against a test harness using Focal amd64 vm:


01:08:17 INFO | START ubuntu_boot.kernel_revocation_list 
ubuntu_boot.kernel_revocation_list timestamp=1627520897 timeout=300 
localtime=Jul 29 01:08:17 01:08:17 DEBUG| Persistent state 
client._record_indent now set to 2 01:08:17 DEBUG| Persistent state 
client.unexpected_reboot now set to 
('ubuntu_boot.kernel_revocation_list', 
'ubuntu_boot.kernel_revocation_list') 01:08:17 DEBUG| Waiting for pid 
2357 for 300 seconds 01:08:17 WARNI| System python is too old, crash 
handling disabled 01:08:17 ERROR| Exception escaping from test: 
Traceback (most recent call last): File 
"/home/ubuntu/autotest/client/shared/test.py", line 411, in _exec 
_call_test_function(self.execute, *p_args, **p_dargs) File 
"/home/ubuntu/autotest/client/shared/test.py", line 830, in 
_call_test_function raise error.UnhandledTestFail(e) UnhandledTestFail: 
Unhandled AttributeError: 'tuple' object has no attribute 'release' 
Traceback (most recent call last): File 
"/home/ubuntu/autotest/client/shared/test.py", line 823, in 
_call_test_function return func(*args, **dargs) File 
"/home/ubuntu/autotest/client/shared/test.py", line 291, in execute 
postprocess_profiled_run, args, dargs) File 
"/home/ubuntu/autotest/client/shared/test.py", line 212, in 
_call_run_once self.run_once(*args, **dargs) File 
"/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line 
97, in run_once self.kernel_revocation_list() File 
"/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line 
60, in kernel_revocation_list config_file = "/boot/config-" + 
os.uname().release AttributeError: 'tuple' object has no attribute 'release'

Full output here: https://pastebin.canonical.com/p/Zr2jjzsNcr/

Unfortunately autotest only supports python2, which is why this dumped 
an Exception, there is no os.uname().release. I think the best way to 
handle this:

1.) Re-write it all for python2 (no one wants to do that)

2.) Isolate the function into it's own py3 script and call it from 
ubuntu_boot.py using something like, utils.system('python3 
<my_python3_script>' ).

This is probably the easiest, any errors that are returned from the 
python3 script can be handled in "ubuntu_boot.py" using the 
autotest.client library.

-Sean


On 7/28/21 12:27 PM, Dimitri John Ledkov wrote:
> Implement revocation list checks. If kernel supports revocation lists,
> check that 2012 canonical signing key is revoked.
>
> Most kernels will skip this test, those kernels that have support for
> revocation lists will check that it is correctly configured and
> visible at runtime.
>
> Signed-off-by: Dimitri John Ledkov <dimitri.ledkov@canonical.com>
> ---
>
>   Note, tested the function in question partially on v5.10 and v5.13
>   kernels. I failed at using the test harness directly to partially
>   execute this test case alone. Thus I am not sure if it runs with
>   python3 or python2, as I was getting exceptions raised from autotest
>   itself.
>
>   ubuntu_boot/control.ubuntu |  1 +
>   ubuntu_boot/ubuntu_boot.py | 30 +++++++++++++++++++++++++++++-
>   2 files changed, 30 insertions(+), 1 deletion(-)
>
> diff --git a/ubuntu_boot/control.ubuntu b/ubuntu_boot/control.ubuntu
> index f73d68c2d3..5f4e3a29bd 100644
> --- a/ubuntu_boot/control.ubuntu
> +++ b/ubuntu_boot/control.ubuntu
> @@ -11,3 +11,4 @@ DOC = '''
>   job.run_test_detail('ubuntu_boot', test_name='log_check', tag='log_check', timeout=60*5)
>   job.run_test_detail('ubuntu_boot', test_name='boot_smoke_test', tag='boot_smoke_test', timeout=60*5)
>   job.run_test_detail('ubuntu_boot', test_name='kernel_tainted', tag='kernel_tainted', timeout=60*5)
> +job.run_test_detail('ubuntu_boot', test_name='kernel_revocation_list', tag='kernel_revocation_list', timeout=60*5)
> diff --git a/ubuntu_boot/ubuntu_boot.py b/ubuntu_boot/ubuntu_boot.py
> index a67f21d49f..a986210ad3 100644
> --- a/ubuntu_boot/ubuntu_boot.py
> +++ b/ubuntu_boot/ubuntu_boot.py
> @@ -8,7 +8,7 @@ from autotest.client.shared import error
>   class ubuntu_boot(test.test):
>       version = 1
>       def setup(self):
> -        pkgs = [ 'python3' ]
> +        pkgs = [ 'python3', 'keyutils' ]
>           cmd = 'yes "" | DEBIAN_FRONTEND=noninteractive apt-get install --yes --force-yes ' + ' '.join(pkgs)
>           self.results = utils.system_output(cmd, retain_output=True)
>   
> @@ -58,6 +58,31 @@ class ubuntu_boot(test.test):
>           result = utils.system('python3 %s/kernel_taint_test.py' % self.bindir, ignore_status=True)
>           return result
>   
> +    def kernel_revocation_list(self):
> +        '''Test for kernel builtin revoked keys'''
> +        config_file = "/boot/config-" + os.uname().release
> +        revocation_list_available = False
> +        for line in open(config_file):
> +            if re.search("CONFIG_SYSTEM_REVOCATION_LIST", line):
> +                revocation_list_available = True
> +                break
> +        if not revocation_list_available:
> +            print('SKIP: Kernel Revocation List NA.')
> +            raise error.TestNAError()
> +        revocations = utils.system_output("keyctl list %:.blacklist", retain_output=True)
> +        patterns = [
> +            b'.* asymmetric: Canonical Ltd. Secure Boot Signing: 61482aa2830d0ab2ad5af10b7250da9033ddcef0',
> +        ]
> +        missing_patterns = False
> +        for pat in patterns:
> +            print('Scanning for pattern "{}"'.format(pat))
> +            if not re.search(pat, revocations):
> +                print('Pattern not found.')
> +                missing_patterns = True
> +        if missing_patterns:
> +            raise error.TestFail()
> +        print('GOOD: Kernel Revocation List.')
> +
>       def run_once(self, test_name, exit_on_error=True):
>           if test_name == 'log_check':
>               if not self.log_check():
> @@ -71,6 +96,9 @@ class ubuntu_boot(test.test):
>               else:
>                   print('GOOD: Kernel not tainted.')
>               return
> +        elif test_name == 'kernel_revocation_list':
> +            self.kernel_revocation_list()
> +            return
>   
>           cmd = "uname -a"
>           utils.system(cmd)
Krzysztof Kozlowski July 29, 2021, 6:53 a.m. UTC | #2
On 29/07/2021 03:39, Sean Feole wrote:
> This feature looks like a great addition, however, giving how critical
> the ubuntu_boot a-c-t is (This test is now a gate for initial kernel
> builds), we need to be careful how we update it.
> 
> Upon testing this patch against a test harness using Focal amd64 vm:
> 
> 
> 01:08:17 INFO | START ubuntu_boot.kernel_revocation_list
> ubuntu_boot.kernel_revocation_list timestamp=1627520897 timeout=300
> localtime=Jul 29 01:08:17 01:08:17 DEBUG| Persistent state
> client._record_indent now set to 2 01:08:17 DEBUG| Persistent state
> client.unexpected_reboot now set to
> ('ubuntu_boot.kernel_revocation_list',
> 'ubuntu_boot.kernel_revocation_list') 01:08:17 DEBUG| Waiting for pid
> 2357 for 300 seconds 01:08:17 WARNI| System python is too old, crash
> handling disabled 01:08:17 ERROR| Exception escaping from test:
> Traceback (most recent call last): File
> "/home/ubuntu/autotest/client/shared/test.py", line 411, in _exec
> _call_test_function(self.execute, *p_args, **p_dargs) File
> "/home/ubuntu/autotest/client/shared/test.py", line 830, in
> _call_test_function raise error.UnhandledTestFail(e) UnhandledTestFail:
> Unhandled AttributeError: 'tuple' object has no attribute 'release'
> Traceback (most recent call last): File
> "/home/ubuntu/autotest/client/shared/test.py", line 823, in
> _call_test_function return func(*args, **dargs) File
> "/home/ubuntu/autotest/client/shared/test.py", line 291, in execute
> postprocess_profiled_run, args, dargs) File
> "/home/ubuntu/autotest/client/shared/test.py", line 212, in
> _call_run_once self.run_once(*args, **dargs) File
> "/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line
> 97, in run_once self.kernel_revocation_list() File
> "/home/ubuntu/autotest/client/tests/ubuntu_boot/ubuntu_boot.py", line
> 60, in kernel_revocation_list config_file = "/boot/config-" +
> os.uname().release AttributeError: 'tuple' object has no attribute 'release'
> 
> Full output here: https://pastebin.canonical.com/p/Zr2jjzsNcr/
> 
> Unfortunately autotest only supports python2, which is why this dumped
> an Exception, there is no os.uname().release. I think the best way to
> handle this:
> 
> 1.) Re-write it all for python2 (no one wants to do that)
> 
> 2.) Isolate the function into it's own py3 script and call it from
> ubuntu_boot.py using something like, utils.system('python3
> <my_python3_script>' ).
> 
> This is probably the easiest, any errors that are returned from the
> python3 script can be handled in "ubuntu_boot.py" using the
> autotest.client library.
> 

"release" as part of tuple is provided in Python2 and Python3 for
backwards compatibility also provides a tuple, so this can be simply old
Python2 code.

Other popular option for such older code is to wrap it and handle both
cases:

get_os_uname_release():
    try:
        return os.uname().release
    except AttributeError:
        # Do the old style

This is quite common pattern for imports which are different between 2
and 3.

Best regards,
Krzysztof
diff mbox series

Patch

diff --git a/ubuntu_boot/control.ubuntu b/ubuntu_boot/control.ubuntu
index f73d68c2d3..5f4e3a29bd 100644
--- a/ubuntu_boot/control.ubuntu
+++ b/ubuntu_boot/control.ubuntu
@@ -11,3 +11,4 @@  DOC = '''
 job.run_test_detail('ubuntu_boot', test_name='log_check', tag='log_check', timeout=60*5)
 job.run_test_detail('ubuntu_boot', test_name='boot_smoke_test', tag='boot_smoke_test', timeout=60*5)
 job.run_test_detail('ubuntu_boot', test_name='kernel_tainted', tag='kernel_tainted', timeout=60*5)
+job.run_test_detail('ubuntu_boot', test_name='kernel_revocation_list', tag='kernel_revocation_list', timeout=60*5)
diff --git a/ubuntu_boot/ubuntu_boot.py b/ubuntu_boot/ubuntu_boot.py
index a67f21d49f..a986210ad3 100644
--- a/ubuntu_boot/ubuntu_boot.py
+++ b/ubuntu_boot/ubuntu_boot.py
@@ -8,7 +8,7 @@  from autotest.client.shared import error
 class ubuntu_boot(test.test):
     version = 1
     def setup(self):
-        pkgs = [ 'python3' ]
+        pkgs = [ 'python3', 'keyutils' ]
         cmd = 'yes "" | DEBIAN_FRONTEND=noninteractive apt-get install --yes --force-yes ' + ' '.join(pkgs)
         self.results = utils.system_output(cmd, retain_output=True)
 
@@ -58,6 +58,31 @@  class ubuntu_boot(test.test):
         result = utils.system('python3 %s/kernel_taint_test.py' % self.bindir, ignore_status=True)
         return result
 
+    def kernel_revocation_list(self):
+        '''Test for kernel builtin revoked keys'''
+        config_file = "/boot/config-" + os.uname().release
+        revocation_list_available = False
+        for line in open(config_file):
+            if re.search("CONFIG_SYSTEM_REVOCATION_LIST", line):
+                revocation_list_available = True
+                break
+        if not revocation_list_available:
+            print('SKIP: Kernel Revocation List NA.')
+            raise error.TestNAError()
+        revocations = utils.system_output("keyctl list %:.blacklist", retain_output=True)
+        patterns = [
+            b'.* asymmetric: Canonical Ltd. Secure Boot Signing: 61482aa2830d0ab2ad5af10b7250da9033ddcef0',
+        ]
+        missing_patterns = False
+        for pat in patterns:
+            print('Scanning for pattern "{}"'.format(pat))
+            if not re.search(pat, revocations):
+                print('Pattern not found.')
+                missing_patterns = True
+        if missing_patterns:
+            raise error.TestFail()
+        print('GOOD: Kernel Revocation List.')
+
     def run_once(self, test_name, exit_on_error=True):
         if test_name == 'log_check':
             if not self.log_check():
@@ -71,6 +96,9 @@  class ubuntu_boot(test.test):
             else:
                 print('GOOD: Kernel not tainted.')
             return
+        elif test_name == 'kernel_revocation_list':
+            self.kernel_revocation_list()
+            return
 
         cmd = "uname -a"
         utils.system(cmd)