#!/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 <alex.popov@linux.com>
return True
return False
+ def table_print(self, _mode, with_results):
+ print('{:<40}|{:^7}|{:^12}|{:^10}|{:^18}'.format(self.name, self.type, self.expected, self.decision, self.reason), end='')
+ if with_results:
+ print('| {}'.format(self.result), end='')
+
class KconfigCheck(OptCheck):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.name = 'CONFIG_' + self.name
+
@property
def type(self):
- return "kconfig"
-
- def table_print(self, _mode, with_results):
- 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='')
+ return 'kconfig'
class VerCheck:
class PresenceCheck:
- def __init__(self, name):
- self.name = name
+ def __init__(self, name, type):
+ if type == 'kconfig':
+ self.name = 'CONFIG_' + name
+ else:
+ self.name = name
self.state = None
self.result = None
return True
def table_print(self, _mode, with_results):
- print('CONFIG_{:<84}'.format(self.name + ' is present'), end='')
+ print('{:<91}'.format(self.name + ' is present'), end='')
if with_results:
print('| {}'.format(self.result), end='')
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):
sys.exit('[!] ERROR: invalid {} check: {}'.format(self.__class__.__name__, opts))
self.result = None
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
for i, opt in enumerate(self.opts):
ret = opt.check()
if ret:
- 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
+ if opt.result == 'OK' and i != 0:
+ # Simple OK is not enough for additional checks, add more info:
+ self.result = 'OK: {} "{}"'.format(opt.name, opt.expected)
else:
- # Simple OK is not enough for additional checks.
- self.result = 'OK: CONFIG_{} "{}"'.format(opt.name, opt.expected)
+ self.result = opt.result
return True
self.result = self.opts[0].result
return False
# 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)
+ self.result = 'FAIL: {} not "{}"'.format(opt.name, opt.expected)
elif opt.result == 'FAIL: not present':
- self.result = 'FAIL: CONFIG_{} not present'.format(opt.name)
+ self.result = 'FAIL: {} not present'.format(opt.name)
else:
# This FAIL message is self-explaining.
self.result = opt.result
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)
l += [KconfigCheck('cut_attack_surface', 'clipos', 'ACPI_TABLE_UPGRADE', 'is not set')] # refers to LOCKDOWN
l += [KconfigCheck('cut_attack_surface', 'clipos', 'EFI_CUSTOM_SSDT_OVERLAYS', 'is not set')]
l += [AND(KconfigCheck('cut_attack_surface', 'clipos', 'LDISC_AUTOLOAD', 'is not set'),
- PresenceCheck('LDISC_AUTOLOAD'))]
+ PresenceCheck('LDISC_AUTOLOAD', 'kconfig'))]
if arch in ('X86_64', 'X86_32'):
l += [KconfigCheck('cut_attack_surface', 'clipos', 'X86_INTEL_TSX_MODE_OFF', 'y')] # tsx=off
def print_unknown_options(checklist, parsed_options):
known_options = []
- for opt in checklist:
- if hasattr(opt, 'opts'):
- for o in opt.opts:
- if hasattr(o, 'name'):
- known_options.append(o.name)
- else:
- known_options.append(opt.name)
+
+ for o1 in checklist:
+ if not hasattr(o1, 'opts'):
+ known_options.append(o1.name)
+ continue
+ for o2 in o1.opts:
+ if not hasattr(o2, 'opts'):
+ if hasattr(o2, 'name'):
+ known_options.append(o2.name)
+ continue
+ for o3 in o2.opts:
+ if hasattr(o3, 'opts'):
+ sys.exit('[!] ERROR: unexpected ComplexOptCheck inside {}'.format(o2.name))
+ if hasattr(o3, 'name'):
+ known_options.append(o3.name)
+
for option, value in parsed_options.items():
if option not in known_options:
- print('[?] No rule for option {} ({})'.format(option, value))
+ print('[?] No check for option {} ({})'.format(option, value))
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 = [o.name, o.type, o.expected, o.decision, o.reason]
if with_results:
opt.append(o.result)
opts.append(opt)
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'):
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:
+ populate_opt_with_data(opt, parsed_options, kernel_version)
+
+
+def perform_checks(checklist):
for opt in checklist:
- perform_check(opt, parsed_options, kernel_version)
+ opt.check()
-def parse_config_file(parsed_options, fname):
+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")
value = None
if opt_is_on.match(line):
- option, value = line[7:].split('=', 1)
+ option, value = line.split('=', 1)
elif opt_is_off.match(line):
- option, value = line[9:].split(' ', 1)
+ option, value = line[2:].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
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']
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()
if args.mode:
mode = args.mode
if mode != 'json':
- print("[+] Special report mode: {}".format(mode))
+ print('[+] Special report mode: {}'.format(mode))
config_checklist = []
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:
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)