X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig_hardened_check%2F__init__.py;h=bc0892aee602d17404b300db4a00bfdca96f8179;hb=922ac4d7aa4755aaad1721e910a02e06551e5dad;hp=b6ff953ad920912a8dd18294981fcba00ee285fc;hpb=33c6dcbf2563965112924607ad68aed31e490bca;p=kconfig-hardened-check.git diff --git a/kconfig_hardened_check/__init__.py b/kconfig_hardened_check/__init__.py index b6ff953..bc0892a 100644 --- a/kconfig_hardened_check/__init__.py +++ b/kconfig_hardened_check/__init__.py @@ -1,8 +1,8 @@ #!/usr/bin/python3 # -# This tool helps me to check the Linux kernel Kconfig option list -# against my security hardening preferences for X86_64, ARM64, X86_32, and ARM. +# This tool helps me to check Linux kernel options against +# my security hardening preferences for X86_64, ARM64, X86_32, and ARM. # Let the computers do their job! # # Author: Alexander Popov @@ -108,8 +108,12 @@ class OptCheck: class KconfigCheck(OptCheck): + @property + def type(self): + return "kconfig" + def table_print(self, _mode, with_results): - print('CONFIG_{:<33}|{:^11}|{:^10}|{:^18}'.format(self.name, self.expected, self.decision, self.reason), end='') + print('CONFIG_{:<33}|{:^7}|{:^12}|{:^10}|{:^18}'.format(self.name, self.type, self.expected, self.decision, self.reason), end='') if with_results: print('| {}'.format(self.result), end='') @@ -135,7 +139,7 @@ class VerCheck: def table_print(self, _mode, with_results): ver_req = 'kernel version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) - print('{:<82}'.format(ver_req), end='') + print('{:<91}'.format(ver_req), end='') if with_results: print('| {}'.format(self.result), end='') @@ -154,7 +158,7 @@ class PresenceCheck: return True def table_print(self, _mode, with_results): - print('CONFIG_{:<75}'.format(self.name + ' is present'), end='') + print('CONFIG_{:<84}'.format(self.name + ' is present'), end='') if with_results: print('| {}'.format(self.result), end='') @@ -172,6 +176,10 @@ class ComplexOptCheck: def name(self): return self.opts[0].name + @property + def type(self): + return self.opts[0].type + @property def expected(self): return self.opts[0].expected @@ -186,7 +194,7 @@ class ComplexOptCheck: def table_print(self, mode, with_results): if mode == 'verbose': - print(' {:78}'.format('<<< ' + self.__class__.__name__ + ' >>>'), end='') + print(' {:87}'.format('<<< ' + self.__class__.__name__ + ' >>>'), end='') if with_results: print('| {}'.format(self.result), end='') for o in self.opts: @@ -287,7 +295,7 @@ def detect_version(fname): return None, 'no kernel version detected' -def construct_checklist(l, arch): +def add_kconfig_checks(l, arch): # Calling the KconfigCheck class constructor: # KconfigCheck(reason, decision, name, expected) @@ -497,46 +505,46 @@ def construct_checklist(l, arch): if arch == 'X86_64': l += [KconfigCheck('cut_attack_surface', 'kspp', 'LEGACY_VSYSCALL_NONE', 'y')] # 'vsyscall=none' - # 'cut_attack_surface', 'grsecurity' - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'ZSMALLOC_STAT', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'PAGE_OWNER', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'DEBUG_KMEMLEAK', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'BINFMT_AOUT', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'KPROBE_EVENTS', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'UPROBE_EVENTS', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'GENERIC_TRACER', 'is not set')] # refers to LOCKDOWN - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'FUNCTION_TRACER', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'STACK_TRACER', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'HIST_TRIGGERS', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'BLK_DEV_IO_TRACE', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'PROC_VMCORE', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'PROC_PAGE_MONITOR', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'USELIB', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'CHECKPOINT_RESTORE', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'USERFAULTFD', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'HWPOISON_INJECT', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'MEM_SOFT_DIRTY', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'DEVPORT', 'is not set')] # refers to LOCKDOWN - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'DEBUG_FS', 'is not set')] # refers to LOCKDOWN - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'NOTIFIER_ERROR_INJECTION', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'FAIL_FUTEX', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'PUNIT_ATOM_DEBUG', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'ACPI_CONFIGFS', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'EDAC_DEBUG', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'DRM_I915_DEBUG', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'BCACHE_CLOSURES_DEBUG', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'DVB_C8SECTPFE', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'MTD_SLRAM', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'MTD_PHRAM', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'IO_URING', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'KCMP', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'RSEQ', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'LATENCYTOP', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'KCOV', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'PROVIDE_OHCI1394_DMA_INIT', 'is not set')] - l += [KconfigCheck('cut_attack_surface', 'grsecurity', 'SUNRPC_DEBUG', 'is not set')] - l += [AND(KconfigCheck('cut_attack_surface', 'grsecurity', 'PTDUMP_DEBUGFS', 'is not set'), - KconfigCheck('cut_attack_surface', 'grsecurity', 'X86_PTDUMP', 'is not set'))] + # 'cut_attack_surface', 'grsec' + l += [KconfigCheck('cut_attack_surface', 'grsec', 'ZSMALLOC_STAT', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'PAGE_OWNER', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'DEBUG_KMEMLEAK', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'BINFMT_AOUT', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'KPROBE_EVENTS', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'UPROBE_EVENTS', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'GENERIC_TRACER', 'is not set')] # refers to LOCKDOWN + l += [KconfigCheck('cut_attack_surface', 'grsec', 'FUNCTION_TRACER', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'STACK_TRACER', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'HIST_TRIGGERS', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'BLK_DEV_IO_TRACE', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'PROC_VMCORE', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'PROC_PAGE_MONITOR', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'USELIB', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'CHECKPOINT_RESTORE', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'USERFAULTFD', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'HWPOISON_INJECT', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'MEM_SOFT_DIRTY', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'DEVPORT', 'is not set')] # refers to LOCKDOWN + l += [KconfigCheck('cut_attack_surface', 'grsec', 'DEBUG_FS', 'is not set')] # refers to LOCKDOWN + l += [KconfigCheck('cut_attack_surface', 'grsec', 'NOTIFIER_ERROR_INJECTION', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'FAIL_FUTEX', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'PUNIT_ATOM_DEBUG', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'ACPI_CONFIGFS', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'EDAC_DEBUG', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'DRM_I915_DEBUG', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'BCACHE_CLOSURES_DEBUG', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'DVB_C8SECTPFE', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'MTD_SLRAM', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'MTD_PHRAM', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'IO_URING', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'KCMP', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'RSEQ', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'LATENCYTOP', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'KCOV', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'PROVIDE_OHCI1394_DMA_INIT', 'is not set')] + l += [KconfigCheck('cut_attack_surface', 'grsec', 'SUNRPC_DEBUG', 'is not set')] + l += [AND(KconfigCheck('cut_attack_surface', 'grsec', 'PTDUMP_DEBUGFS', 'is not set'), + KconfigCheck('cut_attack_surface', 'grsec', 'X86_PTDUMP', 'is not set'))] # 'cut_attack_surface', 'maintainer' l += [KconfigCheck('cut_attack_surface', 'maintainer', 'DRM_LEGACY', 'is not set')] # recommended by Daniel Vetter in /issues/38 @@ -618,7 +626,7 @@ def print_checklist(mode, checklist, with_results): if mode == 'json': opts = [] for o in checklist: - opt = ['CONFIG_'+o.name, o.expected, o.decision, o.reason] + opt = ['CONFIG_'+o.name, o.type, o.expected, o.decision, o.reason] if with_results: opt.append(o.result) opts.append(opt) @@ -630,7 +638,7 @@ def print_checklist(mode, checklist, with_results): if with_results: sep_line_len += 30 print('=' * sep_line_len) - print('{:^40}|{:^11}|{:^10}|{:^18}'.format('kconfig option name', 'desired val', 'decision', 'reason'), end='') + print('{:^40}|{:^7}|{:^12}|{:^10}|{:^18}'.format('option name', 'type', 'desired val', 'decision', 'reason'), end='') if with_results: print('| {}'.format('check result'), end='') print() @@ -665,13 +673,13 @@ 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_check(opt, parsed_options, kernel_version): +def populate_opt_with_data(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) + populate_opt_with_data(o, parsed_options, kernel_version) if hasattr(o, 'state'): o.state = parsed_options.get(o.name, None) if hasattr(o, 'ver'): @@ -681,15 +689,19 @@ def perform_check(opt, parsed_options, kernel_version): 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): +def populate_with_data(checklist, parsed_options, kernel_version): for opt in checklist: - perform_check(opt, parsed_options, kernel_version) + populate_opt_with_data(opt, parsed_options, kernel_version) -def parse_config_file(parsed_options, fname): +def perform_checks(checklist): + for opt in checklist: + opt.check() + + +def parse_kconfig_file(parsed_options, fname): with open(fname, 'r') as f: opt_is_on = re.compile("CONFIG_[a-zA-Z0-9_]*=[a-zA-Z0-9_\"]*") opt_is_off = re.compile("# CONFIG_[a-zA-Z0-9_]* is not set") @@ -704,10 +716,10 @@ def parse_config_file(parsed_options, fname): elif opt_is_off.match(line): option, value = line[9:].split(' ', 1) if value != 'is not set': - sys.exit('[!] ERROR: bad disabled config option "{}"'.format(line)) + sys.exit('[!] ERROR: bad disabled kconfig option "{}"'.format(line)) if option in parsed_options: - sys.exit('[!] ERROR: config option "{}" exists multiple times'.format(line)) + sys.exit('[!] ERROR: kconfig option "{}" exists multiple times'.format(line)) if option: parsed_options[option] = value @@ -718,7 +730,7 @@ def parse_config_file(parsed_options, fname): def main(): # Report modes: # * verbose mode for - # - reporting about unknown kernel options in the config + # - reporting about unknown kernel options in the kconfig # - verbose printing of ComplexOptCheck items # * json mode for printing the results in JSON format report_modes = ['verbose', 'json', 'show_ok', 'show_fail'] @@ -729,7 +741,7 @@ def main(): parser.add_argument('-p', '--print', choices=supported_archs, help='print security hardening preferences for the selected architecture') parser.add_argument('-c', '--config', - help='check the kernel config file against these preferences') + help='check the kernel kconfig file against these preferences') parser.add_argument('-m', '--mode', choices=report_modes, help='choose the report mode') args = parser.parse_args() @@ -744,7 +756,7 @@ def main(): if args.config: if mode != 'json': - print('[+] Config file to check: {}'.format(args.config)) + print('[+] Kconfig file to check: {}'.format(args.config)) arch, msg = detect_arch(args.config, supported_archs) if not arch: @@ -758,22 +770,29 @@ def main(): if mode != 'json': 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) + # add relevant kconfig checks to the checklist + add_kconfig_checks(config_checklist, arch) + + # populate the checklist with the parsed kconfig data + parsed_kconfig_options = OrderedDict() + parse_kconfig_file(parsed_kconfig_options, args.config) + populate_with_data(config_checklist, parsed_kconfig_options, kernel_version) + + # now everything is ready for performing the checks + perform_checks(config_checklist) + # finally print the results if mode == 'verbose': - print_unknown_options(config_checklist, parsed_options) + print_unknown_options(config_checklist, parsed_kconfig_options) print_checklist(mode, config_checklist, True) 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)) + sys.exit('[!] ERROR: wrong mode "{}" for --print'.format(mode)) arch = args.print - construct_checklist(config_checklist, arch) + add_kconfig_checks(config_checklist, arch) if mode != 'json': print('[+] Printing kernel security hardening preferences for {}...'.format(arch)) print_checklist(mode, config_checklist, False)