X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig_hardened_check%2F__init__.py;h=986750919d1f0b6738088e4370f0e4067abd9ef8;hb=4abb78818fc77db635c1545db63a0bf147f4a798;hp=548774526fc88ff78ccb19f8b89037a6f3684cce;hpb=eeb0b9ee58eec087b0bd7b5b3428b4c9d1078c4a;p=kconfig-hardened-check.git diff --git a/kconfig_hardened_check/__init__.py b/kconfig_hardened_check/__init__.py index 5487745..9867509 100644 --- a/kconfig_hardened_check/__init__.py +++ b/kconfig_hardened_check/__init__.py @@ -64,17 +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'] - -kernel_version = None - class OptCheck: def __init__(self, reason, decision, name, expected): @@ -109,16 +98,17 @@ class OptCheck: class VerCheck: def __init__(self, ver_expected): self.ver_expected = ver_expected + self.ver = None self.result = None def check(self): - if kernel_version[0] > self.ver_expected[0]: + if self.ver[0] > self.ver_expected[0]: self.result = 'OK: version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) return True - if kernel_version[0] < self.ver_expected[0]: + if self.ver[0] < self.ver_expected[0]: self.result = 'FAIL: version < ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) return False - if kernel_version[1] >= self.ver_expected[1]: + if self.ver[1] >= self.ver_expected[1]: self.result = 'OK: version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) return True self.result = 'FAIL: version < ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) @@ -188,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: @@ -210,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))): @@ -221,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 @@ -229,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: @@ -363,6 +355,7 @@ def construct_checklist(l, arch): l += [OptCheck('self_protection', 'clipos', 'SECURITY_DMESG_RESTRICT', 'y')] l += [OptCheck('self_protection', 'clipos', 'DEBUG_VIRTUAL', 'y')] l += [OptCheck('self_protection', 'clipos', 'STATIC_USERMODEHELPER', 'y')] # needs userspace support + l += [OptCheck('self_protection', 'clipos', 'EFI_DISABLE_PCI_DMA', 'y')] l += [OptCheck('self_protection', 'clipos', 'SLAB_MERGE_DEFAULT', 'is not set')] # slab_nomerge l += [OptCheck('self_protection', 'clipos', 'RANDOM_TRUST_BOOTLOADER', 'is not set')] l += [OptCheck('self_protection', 'clipos', 'RANDOM_TRUST_CPU', 'is not set')] @@ -439,7 +432,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')] @@ -457,6 +449,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')] @@ -465,7 +459,6 @@ def construct_checklist(l, arch): # 'cut_attack_surface', 'lockdown' l += [OptCheck('cut_attack_surface', 'lockdown', 'ACPI_TABLE_UPGRADE', 'is not set')] # refers to LOCKDOWN - l += [OptCheck('cut_attack_surface', 'lockdown', 'X86_IOPL_IOPERM', 'is not set')] # refers to LOCKDOWN l += [OptCheck('cut_attack_surface', 'lockdown', 'EFI_TEST', 'is not set')] # refers to LOCKDOWN l += [OptCheck('cut_attack_surface', 'lockdown', 'BPF_SYSCALL', 'is not set')] # refers to LOCKDOWN l += [OptCheck('cut_attack_surface', 'lockdown', 'MMIOTRACE_TEST', 'is not set')] # refers to LOCKDOWN @@ -481,6 +474,8 @@ def construct_checklist(l, arch): l += [OptCheck('cut_attack_surface', 'clipos', 'USER_NS', 'is not set')] # user.max_user_namespaces=0 l += [OptCheck('cut_attack_surface', 'clipos', 'X86_MSR', 'is not set')] # refers to LOCKDOWN l += [OptCheck('cut_attack_surface', 'clipos', 'X86_CPUID', 'is not set')] + l += [OptCheck('cut_attack_surface', 'clipos', 'IO_URING', 'is not set')] + l += [OptCheck('cut_attack_surface', 'clipos', 'X86_IOPL_IOPERM', 'is not set')] # refers to LOCKDOWN l += [AND(OptCheck('cut_attack_surface', 'clipos', 'LDISC_AUTOLOAD', 'is not set'), PresenceCheck('LDISC_AUTOLOAD'))] if arch in ('X86_64', 'X86_32'): @@ -500,7 +495,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'): @@ -549,6 +547,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': @@ -557,19 +562,27 @@ 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): +def perform_checks(checklist, parsed_options, kernel_version): for opt in checklist: if hasattr(opt, 'opts'): # prepare ComplexOptCheck for o in opt.opts: if hasattr(o, 'state'): o.state = parsed_options.get(o.name, None) + if hasattr(o, 'ver'): + o.ver = kernel_version else: # prepare simple check if not hasattr(opt, 'state'): @@ -605,33 +618,37 @@ def parse_config_file(parsed_options, fname): def main(): - global kernel_version - - mode = 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': @@ -644,8 +661,9 @@ 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) + perform_checks(config_checklist, parsed_options, kernel_version) if mode == 'verbose': print_unknown_options(config_checklist, parsed_options) @@ -654,6 +672,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':