Merge branch 'master' into typing
[kconfig-hardened-check.git] / kernel_hardening_checker / __init__.py
index 2fa789d4fbd2c1883e9b1b46157a21041ada74b7..f4b7525af266163f12b85be854d5ed843165b7de 100644 (file)
@@ -14,6 +14,7 @@ import gzip
 import sys
 from argparse import ArgumentParser
 from collections import OrderedDict
+from typing import List, Tuple
 import re
 import json
 from .__about__ import __version__
@@ -29,7 +30,7 @@ def _open(file: str, *args, **kwargs):
     return open_method(file, *args, **kwargs)
 
 
-def detect_arch(fname, archs):
+def detect_arch(fname: str, archs: List[str]) -> Tuple:
     with _open(fname, 'rt', encoding='utf-8') as f:
         arch_pattern = re.compile(r"CONFIG_[a-zA-Z0-9_]+=y$")
         arch = None
@@ -46,23 +47,24 @@ def detect_arch(fname, archs):
         return arch, 'OK'
 
 
-def detect_kernel_version(fname):
+def detect_kernel_version(fname: str) -> Tuple:
     with _open(fname, 'rt', encoding='utf-8') as f:
         ver_pattern = re.compile(r"^# Linux/.+ Kernel Configuration$|^Linux version .+")
         for line in f.readlines():
             if ver_pattern.match(line):
                 line = line.strip()
                 parts = line.split()
-                ver_str = parts[2]
+                ver_str = parts[2].split('-', 1)[0]
                 ver_numbers = ver_str.split('.')
-                if len(ver_numbers) < 3 or not ver_numbers[0].isdigit() or not ver_numbers[1].isdigit():
-                    msg = f'failed to parse the version "{ver_str}"'
-                    return None, msg
-                return (int(ver_numbers[0]), int(ver_numbers[1])), None
+                if len(ver_numbers) >= 3:
+                    if all(map(lambda x: x.isdigit(), ver_numbers)):
+                        return tuple(map(int, ver_numbers)), None
+                msg = f'failed to parse the version "{parts[2]}"'
+                return None, msg
         return None, 'no kernel version detected'
 
 
-def detect_compiler(fname):
+def detect_compiler(fname: str):
     gcc_version = None
     clang_version = None
     with _open(fname, 'rt', encoding='utf-8') as f:
@@ -84,16 +86,16 @@ def print_unknown_options(checklist, parsed_options, opt_type):
     known_options = []
 
     for o1 in checklist:
-        if o1.type != 'complex':
+        if o1.opt_type != 'complex':
             known_options.append(o1.name)
             continue
         for o2 in o1.opts:
-            if o2.type != 'complex':
+            if o2.opt_type != 'complex':
                 if hasattr(o2, 'name'):
                     known_options.append(o2.name)
                 continue
             for o3 in o2.opts:
-                assert(o3.type != 'complex'), \
+                assert(o3.opt_type != 'complex'), \
                        f'unexpected ComplexOptCheck inside {o2.name}'
                 if hasattr(o3, 'name'):
                     known_options.append(o3.name)
@@ -103,7 +105,7 @@ def print_unknown_options(checklist, parsed_options, opt_type):
             print(f'[?] No check for {opt_type} option {option} ({value})')
 
 
-def print_checklist(mode, checklist, with_results):
+def print_checklist(mode: str, checklist, with_results: bool):
     if mode == 'json':
         output = []
         for opt in checklist:
@@ -116,9 +118,9 @@ 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('| check_result', end='')
     print()
     print('=' * sep_line_len)
 
@@ -150,7 +152,7 @@ def print_checklist(mode, checklist, with_results):
         print(f'[+] Config check is finished: \'OK\' - {ok_count}{ok_suppressed} / \'FAIL\' - {fail_count}{fail_suppressed}')
 
 
-def parse_kconfig_file(_mode, parsed_options, fname):
+def parse_kconfig_file(_mode, parsed_options, fname: str):
     with _open(fname, 'rt', encoding='utf-8') as f:
         opt_is_on = re.compile(r"CONFIG_[a-zA-Z0-9_]+=.+$")
         opt_is_off = re.compile(r"# CONFIG_[a-zA-Z0-9_]+ is not set$")
@@ -218,8 +220,8 @@ def parse_sysctl_file(mode, parsed_options, fname):
         sys.exit(f'[!] ERROR: {fname} doesn\'t look like a sysctl output file, please try `sudo sysctl -a > {fname}`')
 
     # let's check the presence of a sysctl option available for root
-    if 'net.core.bpf_jit_harden' not in parsed_options and mode != 'json':
-        print(f'[!] WARNING: sysctl option "net.core.bpf_jit_harden" available for root is not found in {fname}, please try `sudo sysctl -a > {fname}`')
+    if 'kernel.cad_pid' not in parsed_options and mode != 'json':
+        print(f'[!] WARNING: sysctl option "kernel.cad_pid" available for root is not found in {fname}, please try `sudo sysctl -a > {fname}`')
 
 
 def main():
@@ -285,7 +287,7 @@ def main():
                 print('[!] Hint: provide the kernel version file through --kernel-version option')
             sys.exit(f'[!] ERROR: {msg}')
         if mode != 'json':
-            print(f'[+] Detected kernel version: {kernel_version[0]}.{kernel_version[1]}')
+            print(f'[+] Detected kernel version: {kernel_version}')
 
         compiler, msg = detect_compiler(args.config)
         if mode != 'json':