X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig_hardened_check%2F__init__.py;h=a4853b9dbfad9e837ecdffa69daa87dd294fb515;hb=3b162ae527a3fb6662cc0db3f204fa56dc09ac38;hp=37bc1fb52a26b3ecea69a38d659de42e9e0635fa;hpb=271e6bf01d9f854a696bb0b547194d90690c35d5;p=kconfig-hardened-check.git diff --git a/kconfig_hardened_check/__init__.py b/kconfig_hardened_check/__init__.py index 37bc1fb..a4853b9 100644 --- a/kconfig_hardened_check/__init__.py +++ b/kconfig_hardened_check/__init__.py @@ -64,15 +64,6 @@ from .__about__ import __version__ # pylint: disable=line-too-long,bad-whitespace,too-many-branches # pylint: disable=too-many-statements,global-statement -# Report modes: -# * verbose mode for -# - reporting about unknown kernel options in the config -# - verbose printing of ComplexOptCheck items -# * json mode for printing the results in JSON format -report_modes = ['verbose', 'json'] - -supported_archs = ['X86_64', 'X86_32', 'ARM64', 'ARM'] - class OptCheck: def __init__(self, reason, decision, name, expected): @@ -187,9 +178,9 @@ class ComplexOptCheck: class OR(ComplexOptCheck): # self.opts[0] is the option that this OR-check is about. - # Use case: + # Use cases: # OR(, ) - # OR(, ) + # OR(, ) def check(self): if not self.opts: @@ -209,8 +200,10 @@ class OR(ComplexOptCheck): class AND(ComplexOptCheck): # self.opts[0] is the option that this AND-check is about. - # Use case: AND(, ) - # Suboption is not checked if checking of the main_option is failed. + # Use cases: + # AND(, ) + # Suboption is not checked if checking of the main_option is failed. + # AND(, ) def check(self): for i, opt in reversed(list(enumerate(self.opts))): @@ -220,7 +213,7 @@ class AND(ComplexOptCheck): return ret if not ret: if hasattr(opt, 'expected'): - self.result = 'FAIL: CONFIG_{} is needed'.format(opt.name) + self.result = 'FAIL: CONFIG_{} not "{}"'.format(opt.name, opt.expected) else: self.result = opt.result return False @@ -228,14 +221,14 @@ class AND(ComplexOptCheck): sys.exit('[!] ERROR: invalid AND check') -def detect_arch(fname): +def detect_arch(fname, archs): with open(fname, 'r') 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 supported_archs: + if option in archs: if not arch: arch = option else: @@ -438,7 +431,6 @@ def construct_checklist(l, arch): l += [OptCheck('cut_attack_surface', 'kspp', 'LEGACY_VSYSCALL_NONE', 'y')] # 'vsyscall=none' # 'cut_attack_surface', 'grsecurity' - l += [OptCheck('cut_attack_surface', 'grsecurity', 'X86_PTDUMP', 'is not set')] l += [OptCheck('cut_attack_surface', 'grsecurity', 'ZSMALLOC_STAT', 'is not set')] l += [OptCheck('cut_attack_surface', 'grsecurity', 'PAGE_OWNER', 'is not set')] l += [OptCheck('cut_attack_surface', 'grsecurity', 'DEBUG_KMEMLEAK', 'is not set')] @@ -456,6 +448,8 @@ def construct_checklist(l, arch): l += [OptCheck('cut_attack_surface', 'grsecurity', 'DEVPORT', 'is not set')] # refers to LOCKDOWN l += [OptCheck('cut_attack_surface', 'grsecurity', 'DEBUG_FS', 'is not set')] # refers to LOCKDOWN l += [OptCheck('cut_attack_surface', 'grsecurity', 'NOTIFIER_ERROR_INJECTION','is not set')] + l += [AND(OptCheck('cut_attack_surface', 'grsecurity', 'X86_PTDUMP', 'is not set'), + OptCheck('cut_attack_surface', 'my', 'PTDUMP_DEBUGFS', 'is not set'))] # 'cut_attack_surface', 'maintainer' l += [OptCheck('cut_attack_surface', 'maintainer', 'DRM_LEGACY', 'is not set')] @@ -499,7 +493,10 @@ def construct_checklist(l, arch): l += [OptCheck('cut_attack_surface', 'my', 'INPUT_EVBUG', 'is not set')] # Can be used as a keylogger # 'userspace_hardening' - l += [OptCheck('userspace_hardening', 'defconfig', 'INTEGRITY', 'y')] + if arch in ('X86_64', 'ARM64', 'X86_32'): + l += [OptCheck('userspace_hardening', 'defconfig', 'INTEGRITY', 'y')] + if arch == 'ARM': + l += [OptCheck('userspace_hardening', 'my', 'INTEGRITY', 'y')] if arch in ('ARM', 'X86_32'): l += [OptCheck('userspace_hardening', 'defconfig', 'VMSPLIT_3G', 'y')] if arch in ('X86_64', 'ARM64'): @@ -548,6 +545,13 @@ def print_checklist(mode, checklist, with_results): # table contents for opt in checklist: + if with_results: + if mode == 'show_ok': + if not opt.result.startswith('OK'): + continue + if mode == 'show_fail': + if not opt.result.startswith('FAIL'): + continue opt.table_print(mode, with_results) print() if mode == 'verbose': @@ -556,10 +560,16 @@ def print_checklist(mode, checklist, with_results): # final score if with_results: - error_count = len(list(filter(lambda opt: opt.result.startswith('FAIL'), checklist))) + fail_count = len(list(filter(lambda opt: opt.result.startswith('FAIL'), checklist))) + fail_suppressed = '' ok_count = len(list(filter(lambda opt: opt.result.startswith('OK'), checklist))) + ok_suppressed = '' + if mode == 'show_ok': + fail_suppressed = ' (suppressed in output)' + if mode == 'show_fail': + ok_suppressed = ' (suppressed in output)' if mode != 'json': - print('[+] Config check is finished: \'OK\' - {} / \'FAIL\' - {}'.format(ok_count, error_count)) + print('[+] Config check is finished: \'OK\' - {}{} / \'FAIL\' - {}{}'.format(ok_count, ok_suppressed, fail_count, fail_suppressed)) def perform_checks(checklist, parsed_options, kernel_version): @@ -606,33 +616,37 @@ def parse_config_file(parsed_options, fname): def main(): - mode = None - arch = None - kernel_version = None - config_checklist = [] - parsed_options = OrderedDict() - + # Report modes: + # * verbose mode for + # - reporting about unknown kernel options in the config + # - verbose printing of ComplexOptCheck items + # * json mode for printing the results in JSON format + report_modes = ['verbose', 'json', 'show_ok', 'show_fail'] + supported_archs = ['X86_64', 'X86_32', 'ARM64', 'ARM'] parser = ArgumentParser(prog='kconfig-hardened-check', description='Checks the hardening options in the Linux kernel config') parser.add_argument('--version', action='version', version='%(prog)s ' + __version__) parser.add_argument('-p', '--print', choices=supported_archs, help='print hardening preferences for selected architecture') parser.add_argument('-c', '--config', - help='check the config_file against these preferences') + help='check the kernel config file against these preferences') parser.add_argument('-m', '--mode', choices=report_modes, help='choose the report mode') args = parser.parse_args() + mode = None if args.mode: mode = args.mode if mode != 'json': print("[+] Special report mode: {}".format(mode)) + config_checklist = [] + if args.config: if mode != 'json': print('[+] Config file to check: {}'.format(args.config)) - arch, msg = detect_arch(args.config) + arch, msg = detect_arch(args.config, supported_archs) if not arch: sys.exit('[!] ERROR: {}'.format(msg)) if mode != 'json': @@ -645,6 +659,7 @@ def main(): print('[+] Detected kernel version: {}.{}'.format(kernel_version[0], kernel_version[1])) construct_checklist(config_checklist, arch) + parsed_options = OrderedDict() parse_config_file(parsed_options, args.config) perform_checks(config_checklist, parsed_options, kernel_version) @@ -655,6 +670,8 @@ def main(): sys.exit(0) if args.print: + if mode in ('show_ok', 'show_fail'): + sys.exit('[!] ERROR: please use "{}" mode for checking the kernel config'.format(mode)) arch = args.print construct_checklist(config_checklist, arch) if mode != 'json':