X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig_hardened_check%2Fengine.py;h=4fdc222e7e8ac3d9fec6a2cf0a3c60c411f8c918;hb=f8e47e12ddf6b5c7b7562af6b85b8f65481e4b07;hp=b458d29ac35870196d6e2ead615d98fe2f473a08;hpb=328a89c7703915bc9c8eb6e66eebb6944caf94bb;p=kconfig-hardened-check.git diff --git a/kconfig_hardened_check/engine.py b/kconfig_hardened_check/engine.py index b458d29..4fdc222 100644 --- a/kconfig_hardened_check/engine.py +++ b/kconfig_hardened_check/engine.py @@ -1,7 +1,30 @@ #!/usr/bin/python3 -# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring -# pylint: disable=line-too-long,invalid-name,too-many-branches,too-many-statements +""" +This tool is for checking the security hardening options of the Linux kernel. + +Author: Alexander Popov + +This module is the engine of checks. +""" + +# pylint: disable=missing-class-docstring,missing-function-docstring +# pylint: disable=line-too-long,invalid-name,too-many-branches + +GREEN_COLOR = '\x1b[32m' +RED_COLOR = '\x1b[31m' +COLOR_END = '\x1b[0m' + +def colorize_result(input_text): + if input_text is None: + return input_text + if input_text.startswith('OK'): + color = GREEN_COLOR + elif input_text.startswith('FAIL:'): + color = RED_COLOR + else: + assert(False), f'unexpected result "{input_text}"' + return f'{color}{input_text}{COLOR_END}' class OptCheck: @@ -35,10 +58,6 @@ class OptCheck: self.state = None self.result = None - @property - def type(self): - return None - def check(self): # handle the 'is present' check if self.expected == 'is present': @@ -52,12 +71,12 @@ class OptCheck: if self.expected == 'is not off': if self.state == 'off': self.result = 'FAIL: is off' - if self.state == '0': + elif self.state == '0': self.result = 'FAIL: is off, "0"' elif self.state is None: self.result = 'FAIL: is off, not found' else: - self.result = 'OK: is not off, "' + self.state + '"' + self.result = f'OK: is not off, "{self.state}"' return # handle the option value check @@ -69,12 +88,12 @@ class OptCheck: else: self.result = 'FAIL: is not found' else: - self.result = 'FAIL: "' + self.state + '"' + self.result = f'FAIL: "{self.state}"' def table_print(self, _mode, with_results): print(f'{self.name:<40}|{self.type:^7}|{self.expected:^12}|{self.decision:^10}|{self.reason:^18}', end='') if with_results: - print(f'| {self.result}', end='') + print(f'| {colorize_result(self.result)}', end='') def json_dump(self, with_results): dump = [self.name, self.type, self.expected, self.decision, self.reason] @@ -99,6 +118,12 @@ class CmdlineCheck(OptCheck): return 'cmdline' +class SysctlCheck(OptCheck): + @property + def type(self): + return 'sysctl' + + class VersionCheck: def __init__(self, ver_expected): assert(ver_expected and isinstance(ver_expected, tuple) and len(ver_expected) == 2), \ @@ -113,21 +138,21 @@ class VersionCheck: def check(self): if self.ver[0] > self.ver_expected[0]: - self.result = 'OK: version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + self.result = f'OK: version >= {self.ver_expected[0]}.{self.ver_expected[1]}' return if self.ver[0] < self.ver_expected[0]: - self.result = 'FAIL: version < ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + self.result = f'FAIL: version < {self.ver_expected[0]}.{self.ver_expected[1]}' return if self.ver[1] >= self.ver_expected[1]: - self.result = 'OK: version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + self.result = f'OK: version >= {self.ver_expected[0]}.{self.ver_expected[1]}' return - self.result = 'FAIL: version < ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + self.result = f'FAIL: version < {self.ver_expected[0]}.{self.ver_expected[1]}' def table_print(self, _mode, with_results): - ver_req = 'kernel version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1]) + ver_req = f'kernel version >= {self.ver_expected[0]}.{self.ver_expected[1]}' print(f'{ver_req:<91}', end='') if with_results: - print(f'| {self.result}', end='') + print(f'| {colorize_result(self.result)}', end='') class ComplexOptCheck: @@ -137,7 +162,7 @@ class ComplexOptCheck: f'empty {self.__class__.__name__} check' assert(len(self.opts) != 1), \ f'useless {self.__class__.__name__} check: {opts}' - assert(isinstance(opts[0], (KconfigCheck, CmdlineCheck))), \ + assert(isinstance(opts[0], (KconfigCheck, CmdlineCheck, SysctlCheck))), \ f'invalid {self.__class__.__name__} check: {opts}' self.result = None @@ -155,9 +180,9 @@ class ComplexOptCheck: def table_print(self, mode, with_results): if mode == 'verbose': - print(f" {'<<< ' + self.__class__.__name__ + ' >>>':87}", end='') + print(f' {"<<< " + self.__class__.__name__ + " >>>":87}', end='') if with_results: - print(f'| {self.result}', end='') + print(f'| {colorize_result(self.result)}', end='') for o in self.opts: print() o.table_print(mode, with_results) @@ -165,7 +190,7 @@ class ComplexOptCheck: o = self.opts[0] o.table_print(mode, False) if with_results: - print(f'| {self.result}', end='') + print(f'| {colorize_result(self.result)}', end='') def json_dump(self, with_results): dump = self.opts[0].json_dump(False) @@ -234,7 +259,7 @@ class AND(ComplexOptCheck): return -SIMPLE_OPTION_TYPES = ('kconfig', 'version', 'cmdline') +SIMPLE_OPTION_TYPES = ('kconfig', 'cmdline', 'sysctl', 'version') def populate_simple_opt_with_data(opt, data, data_type): @@ -244,11 +269,13 @@ def populate_simple_opt_with_data(opt, data, data_type): f'invalid opt type "{opt.type}"' assert(data_type in SIMPLE_OPTION_TYPES), \ f'invalid data type "{data_type}"' + assert(data), \ + 'empty data' if data_type != opt.type: return - if data_type in ('kconfig', 'cmdline'): + if data_type in ('kconfig', 'cmdline', 'sysctl'): opt.state = data.get(opt.name, None) else: assert(data_type == 'version'), \ @@ -257,17 +284,16 @@ def populate_simple_opt_with_data(opt, data, data_type): def populate_opt_with_data(opt, data, data_type): - if opt.type == 'complex': + assert(opt.type != 'version'), 'a single VersionCheck is useless' + if opt.type != 'complex': + populate_simple_opt_with_data(opt, data, data_type) + else: for o in opt.opts: - if o.type == 'complex': + if o.type != 'complex': + populate_simple_opt_with_data(o, data, data_type) + else: # Recursion for nested ComplexOptCheck objects populate_opt_with_data(o, data, data_type) - else: - populate_simple_opt_with_data(o, data, data_type) - else: - assert(opt.type in ('kconfig', 'cmdline')), \ - f'bad type "{opt.type}" for a simple check' - populate_simple_opt_with_data(opt, data, data_type) def populate_with_data(checklist, data, data_type): @@ -275,6 +301,14 @@ def populate_with_data(checklist, data, data_type): populate_opt_with_data(opt, data, data_type) +def override_expected_value(checklist, name, new_val): + for opt in checklist: + if opt.name == name: + assert(opt.type in ('kconfig', 'cmdline', 'sysctl')), \ + f'overriding an expected value for "{opt.type}" checks is not supported yet' + opt.expected = new_val + + def perform_checks(checklist): for opt in checklist: opt.check()