#
#
# N.B Hardening command line parameters:
-# slab_nomerge
# page_alloc.shuffle=1
# iommu=force (does it help against DMA attacks?)
# iommu.passthrough=0
# init_on_free=1 (since v5.3, otherwise slub_debug=P and page_poison=1)
# loadpin.enforce=1
# debugfs=no-mount (or off if possible)
-# randomize_kstack_offset=1
#
# Mitigations of CPU vulnerabilities:
# Аrch-independent:
# 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)
# ssbd=force-on
#
# Should NOT be set:
+# slab_merge
# nokaslr
# rodata=off
# sysrq_always_enabled
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
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':
# OR(<X_is_hardened>, <X_is_disabled>)
# OR(<X_is_hardened>, <old_X_is_hardened>)
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'):
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
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):
if arch in ('X86_64', 'ARM64', 'X86_32'):
stackleak_is_set = KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_STACKLEAK', 'y')
l += [stackleak_is_set]
- l += [KconfigCheck('self_protection', 'kspp', 'RANDOMIZE_KSTACK_OFFSET_DEFAULT', 'y')]
+ l += [OR(KconfigCheck('self_protection', 'kspp', 'RANDOMIZE_KSTACK_OFFSET_DEFAULT', 'y'),
+ CmdlineCheck('self_protection', 'kspp', 'randomize_kstack_offset', '1'))]
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')]
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 += [OR(KconfigCheck('self_protection', 'clipos', 'SLAB_MERGE_DEFAULT', 'is not set'),
+ CmdlineCheck('self_protection', 'kspp', 'slab_nomerge'))] # option presence check
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'),
# Calling the CmdlineCheck class constructor:
# CmdlineCheck(reason, decision, name, expected)
- l += [CmdlineCheck('self_protection', 'kspp', 'randomize_kstack_offset', 'on')]
+ if arch in ('X86_64', 'X86_32'):
+ l += [CmdlineCheck('self_protection', 'kspp', 'pti', 'on')]
# TODO: add other
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)
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):
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)
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:
# 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)
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)
parser.print_help()
sys.exit(0)
-
-if __name__ == '__main__':
- main()