X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig_hardened_check%2F__init__.py;h=9f4bbb2771d4614f216ff2a045a13c082af6a0d2;hb=6c2257f40b60ae5d04c6ab98aea9cfbe43c2ea00;hp=7ac709fa328e76852fe601706244c3d8142c7146;hpb=62433053ef5a70f7295881813bbfe93f4646eb81;p=kconfig-hardened-check.git diff --git a/kconfig_hardened_check/__init__.py b/kconfig_hardened_check/__init__.py index 7ac709f..9f4bbb2 100644 --- a/kconfig_hardened_check/__init__.py +++ b/kconfig_hardened_check/__init__.py @@ -11,14 +11,12 @@ # # # N.B Hardening command line parameters: -# slab_nomerge # page_alloc.shuffle=1 # iommu=force (does it help against DMA attacks?) # iommu.passthrough=0 # iommu.strict=1 # slub_debug=FZ (slow) -# init_on_alloc=1 (since v5.3) -# init_on_free=1 (since v5.3, otherwise slub_debug=P and page_poison=1) +# slub_debug=P # loadpin.enforce=1 # debugfs=no-mount (or off if possible) # @@ -27,7 +25,6 @@ # mitigations=auto,nosmt (nosmt is slow) # X86: # spectre_v2=on -# pti=on # spec_store_bypass_disable=on # l1tf=full,force # l1d_flush=on (a part of the l1tf option) @@ -89,8 +86,8 @@ SIMPLE_OPTION_TYPES = ('kconfig', 'version', 'cmdline') class OptCheck: # Constructor without the 'expected' parameter is for option presence checks (any value is OK) def __init__(self, reason, decision, name, expected=None): - if not reason or not decision or not name: - sys.exit('[!] ERROR: invalid {} check for "{}"'.format(self.__class__.__name__, name)) + assert(reason and decision and name), \ + 'invalid {} check for "{}"'.format(self.__class__.__name__, name) self.name = name self.expected = expected self.decision = decision @@ -186,33 +183,25 @@ class VersionCheck: class ComplexOptCheck: def __init__(self, *opts): self.opts = opts - if not self.opts: - sys.exit('[!] ERROR: empty {} check'.format(self.__class__.__name__)) - if len(self.opts) == 1: - sys.exit('[!] ERROR: useless {} check'.format(self.__class__.__name__)) - if not isinstance(opts[0], KconfigCheck) and not isinstance(opts[0], CmdlineCheck): - sys.exit('[!] ERROR: invalid {} check: {}'.format(self.__class__.__name__, opts)) + assert(self.opts), \ + 'empty {} check'.format(self.__class__.__name__) + assert(len(self.opts) != 1), \ + 'useless {} check: {}'.format(self.__class__.__name__, opts) + assert(isinstance(opts[0], (KconfigCheck, CmdlineCheck))), \ + 'invalid {} check: {}'.format(self.__class__.__name__, opts) self.result = None - @property - def name(self): - return self.opts[0].name - @property def type(self): return 'complex' @property - def expected(self): - return self.opts[0].expected - - @property - def decision(self): - return self.opts[0].decision + def name(self): + return self.opts[0].name @property - def reason(self): - return self.opts[0].reason + def expected(self): + return self.opts[0].expected def table_print(self, mode, with_results): if mode == 'verbose': @@ -241,8 +230,6 @@ class OR(ComplexOptCheck): # OR(, ) # OR(, ) def check(self): - if not self.opts: - sys.exit('[!] ERROR: invalid OR check') for i, opt in enumerate(self.opts): opt.check() if opt.result.startswith('OK'): @@ -255,9 +242,10 @@ class OR(ComplexOptCheck): self.result = 'OK: {} not found'.format(opt.name) elif opt.result == 'OK: is present': self.result = 'OK: {} is present'.format(opt.name) - # VersionCheck provides enough info - elif not opt.result.startswith('OK: version'): - sys.exit('[!] ERROR: unexpected OK description "{}"'.format(opt.result)) + else: + # VersionCheck provides enough info + assert(opt.result.startswith('OK: version')), \ + 'unexpected OK description "{}"'.format(opt.result) return self.result = self.opts[0].result @@ -285,10 +273,9 @@ class AND(ComplexOptCheck): else: # VersionCheck provides enough info self.result = opt.result - if not opt.result.startswith('FAIL: version'): - sys.exit('[!] ERROR: unexpected FAIL description "{}"'.format(opt.result)) + assert(opt.result.startswith('FAIL: version')), \ + 'unexpected FAIL description "{}"'.format(opt.result) return - sys.exit('[!] ERROR: invalid AND check') def detect_arch(fname, archs): @@ -429,7 +416,8 @@ def add_kconfig_checks(l, arch): l += [OR(KconfigCheck('self_protection', 'kspp', 'INIT_STACK_ALL_ZERO', 'y'), KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_STRUCTLEAK_BYREF_ALL', 'y'))] l += [OR(KconfigCheck('self_protection', 'kspp', 'INIT_ON_FREE_DEFAULT_ON', 'y'), - KconfigCheck('self_protection', 'kspp', 'PAGE_POISONING_ZERO', 'y'))] + AND(KconfigCheck('self_protection', 'kspp', 'PAGE_POISONING_ZERO', 'y'), + CmdlineCheck('self_protection', 'kspp', 'page_poison', '1')))] # CONFIG_INIT_ON_FREE_DEFAULT_ON was added in v5.3. # CONFIG_PAGE_POISONING_ZERO was removed in v5.11. # Starting from v5.11 CONFIG_PAGE_POISONING unconditionally checks @@ -438,8 +426,7 @@ def add_kconfig_checks(l, arch): if arch in ('X86_64', 'ARM64', 'X86_32'): stackleak_is_set = KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_STACKLEAK', 'y') l += [stackleak_is_set] - l += [OR(KconfigCheck('self_protection', 'kspp', 'RANDOMIZE_KSTACK_OFFSET_DEFAULT', 'y'), - CmdlineCheck('self_protection', 'kspp', 'randomize_kstack_offset', '1'))] + l += [KconfigCheck('self_protection', 'kspp', 'RANDOMIZE_KSTACK_OFFSET_DEFAULT', 'y')] if arch in ('X86_64', 'X86_32'): l += [KconfigCheck('self_protection', 'kspp', 'SCHED_CORE', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'DEFAULT_MMAP_MIN_ADDR', '65536')] @@ -467,7 +454,7 @@ def add_kconfig_checks(l, arch): l += [KconfigCheck('self_protection', 'clipos', 'STATIC_USERMODEHELPER', 'y')] # needs userspace support l += [OR(KconfigCheck('self_protection', 'clipos', 'EFI_DISABLE_PCI_DMA', 'y'), efi_not_set)] - l += [KconfigCheck('self_protection', 'clipos', 'SLAB_MERGE_DEFAULT', 'is not set')] # slab_nomerge + l += [KconfigCheck('self_protection', 'clipos', 'SLAB_MERGE_DEFAULT', 'is not set')] l += [KconfigCheck('self_protection', 'clipos', 'RANDOM_TRUST_BOOTLOADER', 'is not set')] l += [KconfigCheck('self_protection', 'clipos', 'RANDOM_TRUST_CPU', 'is not set')] l += [AND(KconfigCheck('self_protection', 'clipos', 'GCC_PLUGIN_RANDSTRUCT_PERFORMANCE', 'is not set'), @@ -660,6 +647,21 @@ def add_cmdline_checks(l, arch): # Calling the CmdlineCheck class constructor: # CmdlineCheck(reason, decision, name, expected) + l += [OR(CmdlineCheck('self_protection', 'kspp', 'init_on_alloc', '1'), + AND(KconfigCheck('self_protection', 'kspp', 'INIT_ON_ALLOC_DEFAULT_ON', 'y'), + CmdlineCheck('self_protection', 'kspp', 'init_on_alloc', 'is not set')))] + l += [OR(CmdlineCheck('self_protection', 'kspp', 'init_on_free', '1'), + AND(KconfigCheck('self_protection', 'kspp', 'INIT_ON_FREE_DEFAULT_ON', 'y'), + CmdlineCheck('self_protection', 'kspp', 'init_on_free', 'is not set')))] + l += [OR(CmdlineCheck('self_protection', 'kspp', 'slab_nomerge'), + AND(KconfigCheck('self_protection', 'clipos', 'SLAB_MERGE_DEFAULT', 'is not set'), + CmdlineCheck('self_protection', 'kspp', 'slab_merge', 'is not set')))] # option presence check + if arch in ('X86_64', 'ARM64', 'X86_32'): + l += [OR(CmdlineCheck('self_protection', 'kspp', 'randomize_kstack_offset', '1'), + AND(KconfigCheck('self_protection', 'kspp', 'RANDOMIZE_KSTACK_OFFSET_DEFAULT', 'y'), + CmdlineCheck('self_protection', 'kspp', 'randomize_kstack_offset', 'is not set')))] + if arch in ('X86_64', 'X86_32'): + l += [CmdlineCheck('self_protection', 'kspp', 'pti', 'on')] # TODO: add other @@ -676,8 +678,8 @@ def print_unknown_options(checklist, parsed_options): known_options.append(o2.name) continue for o3 in o2.opts: - if o3.type == 'complex': - sys.exit('[!] ERROR: unexpected ComplexOptCheck inside {}'.format(o2.name)) + assert(o3.type != 'complex'), \ + 'unexpected ComplexOptCheck inside {}'.format(o2.name) if hasattr(o3, 'name'): known_options.append(o3.name) @@ -735,22 +737,22 @@ def print_checklist(mode, checklist, with_results): def populate_simple_opt_with_data(opt, data, data_type): - if opt.type == 'complex': - sys.exit('[!] ERROR: unexpected ComplexOptCheck {}: {}'.format(opt.name, vars(opt))) - if opt.type not in SIMPLE_OPTION_TYPES: - sys.exit('[!] ERROR: invalid opt type "{}" for {}'.format(opt.type, opt.name)) - if data_type not in SIMPLE_OPTION_TYPES: - sys.exit('[!] ERROR: invalid data type "{}"'.format(data_type)) + assert(opt.type != 'complex'), \ + 'unexpected ComplexOptCheck "{}"'.format(opt.name) + assert(opt.type in SIMPLE_OPTION_TYPES), \ + 'invalid opt type "{}"'.format(opt.type) + assert(data_type in SIMPLE_OPTION_TYPES), \ + 'invalid data type "{}"'.format(data_type) if data_type != opt.type: return if data_type in ('kconfig', 'cmdline'): opt.state = data.get(opt.name, None) - elif data_type == 'version': - opt.ver = data else: - sys.exit('[!] ERROR: unexpected data type "{}"'.format(data_type)) + assert(data_type == 'version'), \ + 'unexpected data type "{}"'.format(data_type) + opt.ver = data def populate_opt_with_data(opt, data, data_type): @@ -762,8 +764,8 @@ def populate_opt_with_data(opt, data, data_type): else: populate_simple_opt_with_data(o, data, data_type) else: - if opt.type not in ('kconfig', 'cmdline'): - sys.exit('[!] ERROR: bad type "{}" for a simple check {}'.format(opt.type, opt.name)) + assert(opt.type in ('kconfig', 'cmdline')), \ + 'bad type "{}" for a simple check'.format(opt.type) populate_simple_opt_with_data(opt, data, data_type) @@ -789,6 +791,8 @@ def parse_kconfig_file(parsed_options, fname): if opt_is_on.match(line): option, value = line.split('=', 1) + if value == 'is not set': + sys.exit('[!] ERROR: bad enabled kconfig option "{}"'.format(line)) elif opt_is_off.match(line): option, value = line[2:].split(' ', 1) if value != 'is not set': @@ -849,6 +853,9 @@ def main(): config_checklist = [] if args.config: + if args.print: + sys.exit('[!] ERROR: --config and --print can\'t be used together') + if mode != 'json': print('[+] Kconfig file to check: {}'.format(args.config)) if args.cmdline: @@ -869,6 +876,10 @@ def main(): # add relevant kconfig checks to the checklist add_kconfig_checks(config_checklist, arch) + if args.cmdline: + # add relevant cmdline checks to the checklist + add_cmdline_checks(config_checklist, arch) + # populate the checklist with the parsed kconfig data parsed_kconfig_options = OrderedDict() parse_kconfig_file(parsed_kconfig_options, args.config) @@ -876,9 +887,6 @@ def main(): populate_with_data(config_checklist, kernel_version, 'version') if args.cmdline: - # add relevant cmdline checks to the checklist - add_cmdline_checks(config_checklist, arch) - # populate the checklist with the parsed kconfig data parsed_cmdline_options = OrderedDict() parse_cmdline_file(parsed_cmdline_options, args.cmdline) @@ -909,6 +917,3 @@ def main(): parser.print_help() sys.exit(0) - -if __name__ == '__main__': - main()