X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig_hardened_check%2F__init__.py;h=1ca05301efe69c1fcc2f0d24c8733ad2d5e7151c;hb=e165289160f071b9e1016af366b2a0e3948566ad;hp=fe69e1e8d0bbaa69b00741bd63de90f80ee0e8b5;hpb=4c7a125eb7a2fe97c5b3e81dad4768b45d915d0a;p=kconfig-hardened-check.git diff --git a/kconfig_hardened_check/__init__.py b/kconfig_hardened_check/__init__.py index fe69e1e..1ca0530 100644 --- a/kconfig_hardened_check/__init__.py +++ b/kconfig_hardened_check/__init__.py @@ -146,6 +146,10 @@ class PresenceCheck: class ComplexOptCheck: def __init__(self, *opts): self.opts = opts + if not self.opts: + sys.exit('[!] ERROR: empty {} check'.format(self.__class__.__name__)) + if not isinstance(opts[0], OptCheck): + sys.exit('[!] ERROR: invalid {} check: {}'.format(self.__class__.__name__, opts)) self.result = None @property @@ -192,9 +196,13 @@ class OR(ComplexOptCheck): for i, opt in enumerate(self.opts): ret = opt.check() if ret: - if i == 0 or not hasattr(opt, 'expected'): + if opt.result != 'OK' or i == 0: + # Preserve additional explanation of this OK result. + # Simple OK is enough only for the main option that + # this OR-check is about. self.result = opt.result else: + # Simple OK is not enough for additional checks. self.result = 'OK: CONFIG_{} "{}"'.format(opt.name, opt.expected) return True self.result = self.opts[0].result @@ -215,9 +223,15 @@ class AND(ComplexOptCheck): self.result = opt.result return ret if not ret: - if hasattr(opt, 'expected'): + # This FAIL is caused by additional checks, + # and not by the main option that this AND-check is about. + # Describe the reason of the FAIL. + if opt.result.startswith('FAIL: \"') or opt.result == 'FAIL: not found': self.result = 'FAIL: CONFIG_{} not "{}"'.format(opt.name, opt.expected) + elif opt.result == 'FAIL: not present': + self.result = 'FAIL: CONFIG_{} not present'.format(opt.name) else: + # This FAIL message is self-explaining. self.result = opt.result return False @@ -293,7 +307,9 @@ def construct_checklist(l, arch): if arch == 'ARM64': l += [OptCheck('self_protection', 'defconfig', 'ARM64_PAN', 'y')] l += [OptCheck('self_protection', 'defconfig', 'UNMAP_KERNEL_AT_EL0', 'y')] - l += [OptCheck('self_protection', 'defconfig', 'HARDEN_EL2_VECTORS', 'y')] + l += [OR(OptCheck('self_protection', 'defconfig', 'HARDEN_EL2_VECTORS', 'y'), + AND(OptCheck('self_protection', 'defconfig', 'RANDOMIZE_BASE', 'y'), + VerCheck((5, 9))))] # HARDEN_EL2_VECTORS was included in RANDOMIZE_BASE in v5.9 l += [OptCheck('self_protection', 'defconfig', 'RODATA_FULL_DEFAULT_ENABLED', 'y')] l += [OptCheck('self_protection', 'defconfig', 'ARM64_PTR_AUTH', 'y')] if arch in ('X86_64', 'ARM64'): @@ -338,7 +354,7 @@ def construct_checklist(l, arch): l += [OR(OptCheck('self_protection', 'kspp', 'INIT_STACK_ALL', 'y'), OptCheck('self_protection', 'kspp', 'GCC_PLUGIN_STRUCTLEAK_BYREF_ALL', 'y'))] l += [OR(OptCheck('self_protection', 'kspp', 'INIT_ON_FREE_DEFAULT_ON', 'y'), - OptCheck('self_protection', 'kspp', 'PAGE_POISONING', 'y'))] # before v5.3 + OptCheck('self_protection', 'kspp', 'PAGE_POISONING_ZERO', 'y'))] # before v5.3 if arch in ('X86_64', 'ARM64', 'X86_32'): stackleak_is_set = OptCheck('self_protection', 'kspp', 'GCC_PLUGIN_STACKLEAK', 'y') l += [stackleak_is_set] @@ -379,7 +395,10 @@ def construct_checklist(l, arch): iommu_support_is_set)] # 'self_protection', 'my' - l += [OptCheck('self_protection', 'my', 'SLUB_DEBUG_ON', 'y')] + l += [AND(OptCheck('self_protection', 'my', 'UBSAN_BOUNDS', 'y'), + OptCheck('self_protection', 'my', 'UBSAN_MISC', 'is not set'), + OptCheck('self_protection', 'my', 'UBSAN_TRAP', 'y'))] + l += [OptCheck('self_protection', 'my', 'SLUB_DEBUG_ON', 'y')] # TODO: is it better to set that via kernel cmd? l += [OptCheck('self_protection', 'my', 'RESET_ATTACK_MITIGATION', 'y')] # needs userspace support (systemd) if arch == 'X86_64': l += [AND(OptCheck('self_protection', 'my', 'AMD_IOMMU_V2', 'y'), @@ -476,13 +495,14 @@ def construct_checklist(l, arch): 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 += [OptCheck('cut_attack_surface', 'clipos', 'ACPI_TABLE_UPGRADE', 'is not set')] # refers to LOCKDOWN + l += [OptCheck('cut_attack_surface', 'clipos', 'EFI_CUSTOM_SSDT_OVERLAYS', 'is not set')] l += [AND(OptCheck('cut_attack_surface', 'clipos', 'LDISC_AUTOLOAD', 'is not set'), PresenceCheck('LDISC_AUTOLOAD'))] if arch in ('X86_64', 'X86_32'): l += [OptCheck('cut_attack_surface', 'clipos', 'X86_INTEL_TSX_MODE_OFF', 'y')] # tsx=off # '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', '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 @@ -576,23 +596,30 @@ def print_checklist(mode, checklist, with_results): print('[+] Config check is finished: \'OK\' - {}{} / \'FAIL\' - {}{}'.format(ok_count, ok_suppressed, fail_count, fail_suppressed)) -def perform_checks(checklist, parsed_options, kernel_version): - for opt in checklist: +def perform_check(opt, parsed_options, kernel_version): if hasattr(opt, 'opts'): # prepare ComplexOptCheck for o in opt.opts: + if hasattr(o, 'opts'): + # Recursion for nested ComplexOptChecks + perform_check(o, parsed_options, kernel_version) if hasattr(o, 'state'): o.state = parsed_options.get(o.name, None) if hasattr(o, 'ver'): o.ver = kernel_version else: - # prepare simple check + # prepare simple check, opt.state is mandatory if not hasattr(opt, 'state'): sys.exit('[!] ERROR: bad simple check {}'.format(vars(opt))) opt.state = parsed_options.get(opt.name, None) opt.check() +def perform_checks(checklist, parsed_options, kernel_version): + for opt in checklist: + perform_check(opt, parsed_options, kernel_version) + + def parse_config_file(parsed_options, fname): with open(fname, 'r') as f: opt_is_on = re.compile("CONFIG_[a-zA-Z0-9_]*=[a-zA-Z0-9_\"]*")