X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig-hardened-check.py;h=2084ea8bbb63a7b7d4421e984fc067d533c103d1;hb=918b12cf6f652ad148c885d1a802459e73d20c48;hp=56c52ecc1b3c154a5f7350d29676ab6191b36661;hpb=6fa9f8b627ff568561d03ff75022bfad516642d7;p=kconfig-hardened-check.git diff --git a/kconfig-hardened-check.py b/kconfig-hardened-check.py index 56c52ec..2084ea8 100755 --- a/kconfig-hardened-check.py +++ b/kconfig-hardened-check.py @@ -55,6 +55,8 @@ debug_mode = False # set it to True to print the unknown options from the confi json_mode = False # if True, print results in JSON format supported_archs = [ 'X86_64', 'X86_32', 'ARM64', 'ARM' ] +config_checklist = [] +kernel_version = None class OptCheck: @@ -86,6 +88,26 @@ class OptCheck: return '{} = {}'.format(self.name, self.state) +class VerCheck: + def __init__(self, ver_expected): + self.ver_expected = ver_expected + self.result = None + + def check(self): + if kernel_version[0] > self.ver_expected[0]: + self.result = 'OK: version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + return True, self.result + if kernel_version[0] < self.ver_expected[0]: + self.result = 'FAIL: version < ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + return False, self.result + if kernel_version[1] >= self.ver_expected[1]: + self.result = 'OK: version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + return True, self.result + else: + self.result = 'FAIL: version < ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + return False, self.result + + class ComplexOptCheck: def __init__(self, *opts): self.opts = opts @@ -125,7 +147,7 @@ class OR(ComplexOptCheck): for i, opt in enumerate(self.opts): ret, msg = opt.check() if ret: - if i == 0: + if i == 0 or not hasattr(opt, 'name'): self.result = opt.result else: self.result = 'OK: CONFIG_{} "{}"'.format(opt.name, opt.expected) @@ -146,7 +168,10 @@ class AND(ComplexOptCheck): self.result = opt.result return ret, self.result elif not ret: - self.result = 'FAIL: CONFIG_{} is needed'.format(opt.name) + if hasattr(opt, 'name'): + self.result = 'FAIL: CONFIG_{} is needed'.format(opt.name) + else: + self.result = opt.result return False, self.result sys.exit('[!] ERROR: invalid AND check') @@ -156,7 +181,6 @@ def detect_arch(fname): with open(fname, 'r') as f: arch_pattern = re.compile("CONFIG_[a-zA-Z0-9_]*=y") arch = None - msg = None if not json_mode: print('[+] Trying to detect architecture in "{}"...'.format(fname)) for line in f.readlines(): @@ -173,6 +197,27 @@ def detect_arch(fname): return arch, 'OK' +def detect_version(fname): + with open(fname, 'r') as f: + ver_pattern = re.compile("# Linux/.* Kernel Configuration") + if not json_mode: + print('[+] Trying to detect kernel version in "{}"...'.format(fname)) + for line in f.readlines(): + if ver_pattern.match(line): + line = line.strip() + if not json_mode: + print('[+] Found version line: "{}"'.format(line)) + parts = line.split() + 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 + '"' + return None, msg + else: + return (int(ver_numbers[0]), int(ver_numbers[1])), None + return None, 'no kernel version detected' + + def construct_checklist(checklist, arch): modules_not_set = OptCheck('MODULES', 'is not set', 'kspp', 'cut_attack_surface') devmem_not_set = OptCheck('DEVMEM', 'is not set', 'kspp', 'cut_attack_surface') # refers to LOCK_DOWN_KERNEL @@ -417,17 +462,18 @@ def print_checklist(checklist, with_results): print() -def get_option_state(options, name): - return options.get(name, None) - - def perform_checks(checklist, parsed_options): for opt in checklist: if hasattr(opt, 'opts'): + # prepare ComplexOptCheck for o in opt.opts: - o.state = get_option_state(parsed_options, o.name) + if hasattr(o, 'name'): + o.state = parsed_options.get(o.name, None) else: - opt.state = get_option_state(parsed_options, opt.name) + # prepare simple OptCheck + if not hasattr(opt, 'name'): + sys.exit('[!] ERROR: bad OptCheck {}'.format(vars(opt))) + opt.state = parsed_options.get(opt.name, None) opt.check() @@ -469,8 +515,6 @@ def check_config_file(checklist, fname): if __name__ == '__main__': - config_checklist = [] - parser = ArgumentParser(description='Checks the hardening options in the Linux kernel config') parser.add_argument('-p', '--print', choices=supported_archs, help='print hardening preferences for selected architecture') @@ -496,6 +540,12 @@ if __name__ == '__main__': elif not json_mode: print('[+] Detected architecture: {}'.format(arch)) + kernel_version, msg = detect_version(args.config) + if not kernel_version: + sys.exit('[!] ERROR: {}'.format(msg)) + elif not json_mode: + print('[+] Detected kernel version: {}.{}'.format(kernel_version[0], kernel_version[1])) + construct_checklist(config_checklist, arch) check_config_file(config_checklist, args.config) error_count = len(list(filter(lambda opt: opt.result.startswith('FAIL'), config_checklist)))