Add the X86_KERNEL_IBT check
[kconfig-hardened-check.git] / kconfig_hardened_check / __init__.py
index b1a316e783c6c588907ad8ad9266231007170d4b..cdb08288fa0f3ccf2b938cf88fb92462070da61f 100644 (file)
@@ -12,6 +12,7 @@ This module performs input/output.
 
 # pylint: disable=missing-function-docstring,line-too-long,invalid-name,too-many-branches,too-many-statements
 
+import gzip
 import sys
 from argparse import ArgumentParser
 from collections import OrderedDict
@@ -19,28 +20,36 @@ import re
 import json
 from .__about__ import __version__
 from .checks import add_kconfig_checks, add_cmdline_checks, normalize_cmdline_options
-from .engine import populate_with_data, perform_checks
+from .engine import populate_with_data, perform_checks, override_expected_value
+
+
+def _open(file: str, *args, **kwargs):
+    open_method = open
+    if file.endswith(".gz"):
+        open_method = gzip.open
+
+    return open_method(file, *args, **kwargs)
 
 
 def detect_arch(fname, archs):
-    with open(fname, 'r', encoding='utf-8') as f:
+    with _open(fname, 'rt', encoding='utf-8') as f:
         arch_pattern = re.compile("CONFIG_[a-zA-Z0-9_]*=y")
         arch = None
         for line in f.readlines():
             if arch_pattern.match(line):
                 option, _ = line[7:].split('=', 1)
                 if option in archs:
-                    if not arch:
+                    if arch is None:
                         arch = option
                     else:
                         return None, 'more than one supported architecture is detected'
-        if not arch:
+        if arch is None:
             return None, 'failed to detect architecture'
         return arch, 'OK'
 
 
 def detect_kernel_version(fname):
-    with open(fname, 'r', encoding='utf-8') as f:
+    with _open(fname, 'rt', encoding='utf-8') as f:
         ver_pattern = re.compile("# Linux/.* Kernel Configuration")
         for line in f.readlines():
             if ver_pattern.match(line):
@@ -49,7 +58,7 @@ def detect_kernel_version(fname):
                 ver_str = parts[2]
                 ver_numbers = ver_str.split('.')
                 if len(ver_numbers) < 3 or not ver_numbers[0].isdigit() or not ver_numbers[1].isdigit():
-                    msg = 'failed to parse the version "' + ver_str + '"'
+                    msg = f'failed to parse the version "{ver_str}"'
                     return None, msg
                 return (int(ver_numbers[0]), int(ver_numbers[1])), None
         return None, 'no kernel version detected'
@@ -58,7 +67,7 @@ def detect_kernel_version(fname):
 def detect_compiler(fname):
     gcc_version = None
     clang_version = None
-    with open(fname, 'r', encoding='utf-8') as f:
+    with _open(fname, 'rt', encoding='utf-8') as f:
         gcc_version_pattern = re.compile("CONFIG_GCC_VERSION=[0-9]*")
         clang_version_pattern = re.compile("CONFIG_CLANG_VERSION=[0-9]*")
         for line in f.readlines():
@@ -66,7 +75,7 @@ def detect_compiler(fname):
                 gcc_version = line[19:-1]
             if clang_version_pattern.match(line):
                 clang_version = line[21:-1]
-    if not gcc_version or not clang_version:
+    if gcc_version is None or clang_version is None:
         return None, 'no CONFIG_GCC_VERSION or CONFIG_CLANG_VERSION'
     if gcc_version == '0' and clang_version != '0':
         return 'CLANG ' + clang_version, 'OK'
@@ -101,8 +110,8 @@ def print_unknown_options(checklist, parsed_options):
 def print_checklist(mode, checklist, with_results):
     if mode == 'json':
         output = []
-        for o in checklist:
-            output.append(o.json_dump(with_results))
+        for opt in checklist:
+            output.append(opt.json_dump(with_results))
         print(json.dumps(output))
         return
 
@@ -111,7 +120,7 @@ def print_checklist(mode, checklist, with_results):
     if with_results:
         sep_line_len += 30
     print('=' * sep_line_len)
-    print(f"{'option name':^40}|{'type':^7}|{'desired val':^12}|{'decision':^10}|{'reason':^18}", end='')
+    print(f'{"option name":^40}|{"type":^7}|{"desired val":^12}|{"decision":^10}|{"reason":^18}', end='')
     if with_results:
         print('| check result', end='')
     print()
@@ -142,12 +151,11 @@ def print_checklist(mode, checklist, with_results):
             fail_suppressed = ' (suppressed in output)'
         if mode == 'show_fail':
             ok_suppressed = ' (suppressed in output)'
-        if mode != 'json':
-            print(f'[+] Config check is finished: \'OK\' - {ok_count}{ok_suppressed} / \'FAIL\' - {fail_count}{fail_suppressed}')
+        print(f'[+] Config check is finished: \'OK\' - {ok_count}{ok_suppressed} / \'FAIL\' - {fail_count}{fail_suppressed}')
 
 
 def parse_kconfig_file(parsed_options, fname):
-    with open(fname, 'r', encoding='utf-8') as f:
+    with _open(fname, 'rt', encoding='utf-8') as f:
         opt_is_on = re.compile("CONFIG_[a-zA-Z0-9_]*=[a-zA-Z0-9_\"]*")
         opt_is_off = re.compile("# CONFIG_[a-zA-Z0-9_]* is not set")
 
@@ -205,7 +213,7 @@ def main():
     parser.add_argument('-p', '--print', choices=supported_archs,
                         help='print security hardening preferences for the selected architecture')
     parser.add_argument('-c', '--config',
-                        help='check the kernel kconfig file against these preferences')
+                        help='check the kernel kconfig file against these preferences (also supports *.gz files)')
     parser.add_argument('-l', '--cmdline',
                         help='check the kernel cmdline file against these preferences')
     parser.add_argument('-m', '--mode', choices=report_modes,
@@ -230,13 +238,13 @@ def main():
                 print(f'[+] Kernel cmdline file to check: {args.cmdline}')
 
         arch, msg = detect_arch(args.config, supported_archs)
-        if not arch:
+        if arch is None:
             sys.exit(f'[!] ERROR: {msg}')
         if mode != 'json':
             print(f'[+] Detected architecture: {arch}')
 
         kernel_version, msg = detect_kernel_version(args.config)
-        if not kernel_version:
+        if kernel_version is None:
             sys.exit(f'[!] ERROR: {msg}')
         if mode != 'json':
             print(f'[+] Detected kernel version: {kernel_version[0]}.{kernel_version[1]}')
@@ -259,14 +267,21 @@ def main():
         parsed_kconfig_options = OrderedDict()
         parse_kconfig_file(parsed_kconfig_options, args.config)
         populate_with_data(config_checklist, parsed_kconfig_options, 'kconfig')
+
+        # populate the checklist with the kernel version data
         populate_with_data(config_checklist, kernel_version, 'version')
 
         if args.cmdline:
-            # populate the checklist with the parsed kconfig data
+            # populate the checklist with the parsed cmdline data
             parsed_cmdline_options = OrderedDict()
             parse_cmdline_file(parsed_cmdline_options, args.cmdline)
             populate_with_data(config_checklist, parsed_cmdline_options, 'cmdline')
 
+        # hackish refinement of the CONFIG_ARCH_MMAP_RND_BITS check
+        mmap_rnd_bits_max = parsed_kconfig_options.get('CONFIG_ARCH_MMAP_RND_BITS_MAX', None)
+        if mmap_rnd_bits_max:
+            override_expected_value(config_checklist, 'CONFIG_ARCH_MMAP_RND_BITS', mmap_rnd_bits_max)
+
         # now everything is ready, perform the checks
         perform_checks(config_checklist)