# N.B. Hardening sysctls:
# kernel.kptr_restrict=2 (or 1?)
# kernel.dmesg_restrict=1 (also see the kconfig option)
-# kernel.perf_event_paranoid=3
+# kernel.perf_event_paranoid=2 (or 3 with a custom patch, see https://lwn.net/Articles/696216/)
# kernel.kexec_load_disabled=1
# kernel.yama.ptrace_scope=3
# user.max_user_namespaces=0
class OptCheck:
def __init__(self, reason, decision, name, expected):
assert(name and name == name.strip() and len(name.split()) == 1), \
- 'invalid name "{}" for {}'.format(name, self.__class__.__name__)
+ f'invalid name "{name}" for {self.__class__.__name__}'
self.name = name
assert(decision and decision == decision.strip() and len(decision.split()) == 1), \
- 'invalid decision "{}" for "{}" check'.format(decision, name)
+ f'invalid decision "{decision}" for "{name}" check'
self.decision = decision
assert(reason and reason == reason.strip() and len(reason.split()) == 1), \
- 'invalid reason "{}" for "{}" check'.format(reason, name)
+ f'invalid reason "{reason}" for "{name}" check'
self.reason = reason
assert(expected and expected == expected.strip()), \
- 'invalid expected value "{}" for "{}" check (1)'.format(expected, name)
+ f'invalid expected value "{expected}" for "{name}" check (1)'
val_len = len(expected.split())
if val_len == 3:
- assert(expected == 'is not set' or expected == 'is not off'), \
- 'invalid expected value "{}" for "{}" check (2)'.format(expected, name)
+ assert(expected in ('is not set', 'is not off')), \
+ f'invalid expected value "{expected}" for "{name}" check (2)'
elif val_len == 2:
assert(expected == 'is present'), \
- 'invalid expected value "{}" for "{}" check (3)'.format(expected, name)
+ f'invalid expected value "{expected}" for "{name}" check (3)'
else:
assert(val_len == 1), \
- 'invalid expected value "{}" for "{}" check (4)'.format(expected, name)
+ f'invalid expected value "{expected}" for "{name}" check (4)'
self.expected = expected
self.state = None
self.result = 'FAIL: "' + self.state + '"'
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='')
+ print(f'{self.name:<40}|{self.type:^7}|{self.expected:^12}|{self.decision:^10}|{self.reason:^18}', end='')
if with_results:
- print('| {}'.format(self.result), end='')
+ print(f'| {self.result}', end='')
def json_dump(self, with_results):
dump = [self.name, self.type, self.expected, self.decision, self.reason]
class VersionCheck:
def __init__(self, ver_expected):
assert(ver_expected and isinstance(ver_expected, tuple) and len(ver_expected) == 2), \
- 'invalid version "{}" for VersionCheck'.format(ver_expected)
+ f'invalid version "{ver_expected}" for VersionCheck'
self.ver_expected = ver_expected
self.ver = ()
self.result = None
def table_print(self, _mode, with_results):
ver_req = 'kernel version >= ' + str(self.ver_expected[0]) + '.' + str(self.ver_expected[1])
- print('{:<91}'.format(ver_req), end='')
+ print(f'{ver_req:<91}', end='')
if with_results:
- print('| {}'.format(self.result), end='')
+ print(f'| {self.result}', end='')
class ComplexOptCheck:
def __init__(self, *opts):
self.opts = opts
assert(self.opts), \
- 'empty {} check'.format(self.__class__.__name__)
+ f'empty {self.__class__.__name__} check'
assert(len(self.opts) != 1), \
- 'useless {} check: {}'.format(self.__class__.__name__, opts)
+ f'useless {self.__class__.__name__} check: {opts}'
assert(isinstance(opts[0], (KconfigCheck, CmdlineCheck))), \
- 'invalid {} check: {}'.format(self.__class__.__name__, opts)
+ f'invalid {self.__class__.__name__} check: {opts}'
self.result = None
@property
def table_print(self, mode, with_results):
if mode == 'verbose':
- print(' {:87}'.format('<<< ' + self.__class__.__name__ + ' >>>'), end='')
+ print(f" {'<<< ' + self.__class__.__name__ + ' >>>':87}", end='')
if with_results:
- print('| {}'.format(self.result), end='')
+ print(f'| {self.result}', end='')
for o in self.opts:
print()
o.table_print(mode, with_results)
o = self.opts[0]
o.table_print(mode, False)
if with_results:
- print('| {}'.format(self.result), end='')
+ print(f'| {self.result}', end='')
def json_dump(self, with_results):
dump = self.opts[0].json_dump(False)
# Add more info for additional checks:
if i != 0:
if opt.result == 'OK':
- self.result = 'OK: {} is "{}"'.format(opt.name, opt.expected)
+ self.result = f'OK: {opt.name} is "{opt.expected}"'
elif opt.result == 'OK: is not found':
- self.result = 'OK: {} is not found'.format(opt.name)
+ self.result = f'OK: {opt.name} is not found'
elif opt.result == 'OK: is present':
- self.result = 'OK: {} is present'.format(opt.name)
+ self.result = f'OK: {opt.name} is present'
elif opt.result.startswith('OK: is not off'):
- self.result = 'OK: {} is not off'.format(opt.name)
+ self.result = f'OK: {opt.name} is not off'
else:
# VersionCheck provides enough info
assert(opt.result.startswith('OK: version')), \
- 'unexpected OK description "{}"'.format(opt.result)
+ f'unexpected OK description "{opt.result}"'
return
self.result = self.opts[0].result
# 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: is not found':
- self.result = 'FAIL: {} is not "{}"'.format(opt.name, opt.expected)
+ self.result = f'FAIL: {opt.name} is not "{opt.expected}"'
elif opt.result == 'FAIL: is not present':
- self.result = 'FAIL: {} is not present'.format(opt.name)
- elif opt.result == 'FAIL: is off' or opt.result == 'FAIL: is off, "0"':
- self.result = 'FAIL: {} is off'.format(opt.name)
+ self.result = f'FAIL: {opt.name} is not present'
+ elif opt.result in ('FAIL: is off', 'FAIL: is off, "0"'):
+ self.result = f'FAIL: {opt.name} is off'
elif opt.result == 'FAIL: is off, not found':
- self.result = 'FAIL: {} is off, not found'.format(opt.name)
+ self.result = f'FAIL: {opt.name} is off, not found'
else:
# VersionCheck provides enough info
self.result = opt.result
assert(opt.result.startswith('FAIL: version')), \
- 'unexpected FAIL description "{}"'.format(opt.result)
+ f'unexpected FAIL description "{opt.result}"'
return
l += [KconfigCheck('cut_attack_surface', 'maintainer', 'VT', 'is not set')] # recommended by Daniel Vetter in /issues/38
l += [KconfigCheck('cut_attack_surface', 'maintainer', 'BLK_DEV_FD', 'is not set')] # recommended by Denis Efremov in /pull/54
l += [KconfigCheck('cut_attack_surface', 'maintainer', 'BLK_DEV_FD_RAWCMD', 'is not set')] # recommended by Denis Efremov in /pull/62
+ l += [KconfigCheck('cut_attack_surface', 'maintainer', 'NOUVEAU_LEGACY_CTX_SUPPORT', 'is not set')]
+ # recommended by Dave Airlie in kernel commit b30a43ac7132cdda
# 'cut_attack_surface', 'clipos'
l += [KconfigCheck('cut_attack_surface', 'clipos', 'STAGING', 'is not set')]
l += [KconfigCheck('cut_attack_surface', 'my', 'VIDEO_VIVID', 'is not set')]
l += [KconfigCheck('cut_attack_surface', 'my', 'INPUT_EVBUG', 'is not set')] # Can be used as a keylogger
l += [KconfigCheck('cut_attack_surface', 'my', 'KGDB', 'is not set')]
+ l += [KconfigCheck('cut_attack_surface', 'my', 'AIO', 'is not set')]
l += [OR(KconfigCheck('cut_attack_surface', 'my', 'TRIM_UNUSED_KSYMS', 'y'),
modules_not_set)]
l += [CmdlineCheck('self_protection', 'defconfig', 'arm64.nobti', 'is not set')]
l += [CmdlineCheck('self_protection', 'defconfig', 'arm64.nopauth', 'is not set')]
l += [CmdlineCheck('self_protection', 'defconfig', 'arm64.nomte', 'is not set')]
- l += [OR(CmdlineCheck('self_protection', 'defconfig', 'mitigations', 'is not off'),
- CmdlineCheck('self_protection', 'defconfig', 'mitigations', 'is not set'))]
l += [OR(CmdlineCheck('self_protection', 'defconfig', 'spectre_v2', 'is not off'),
CmdlineCheck('self_protection', 'defconfig', 'spectre_v2', 'is not set'))]
l += [OR(CmdlineCheck('self_protection', 'defconfig', 'spectre_v2_user', 'is not off'),
CmdlineCheck('self_protection', 'defconfig', 'retbleed', 'is not set'))]
l += [OR(CmdlineCheck('self_protection', 'defconfig', 'kpti', 'is not off'),
CmdlineCheck('self_protection', 'defconfig', 'kpti', 'is not set'))]
+ l += [OR(CmdlineCheck('self_protection', 'defconfig', 'kvm.nx_huge_pages', 'is not off'),
+ CmdlineCheck('self_protection', 'defconfig', 'kvm.nx_huge_pages', 'is not set'))]
if arch == 'ARM64':
l += [OR(CmdlineCheck('self_protection', 'defconfig', 'ssbd', 'kernel'),
CmdlineCheck('self_protection', 'my', 'ssbd', 'force-on'),
# 'self_protection', 'kspp'
l += [CmdlineCheck('self_protection', 'kspp', 'nosmt', 'is present')]
+ l += [CmdlineCheck('self_protection', 'kspp', 'mitigations', 'auto,nosmt')] # 'nosmt' by kspp + 'auto' by defconfig
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', 'slab_nomerge', 'is present'),
AND(KconfigCheck('self_protection', 'clipos', 'SLAB_MERGE_DEFAULT', 'is not set'),
CmdlineCheck('self_protection', 'kspp', 'slab_merge', 'is not set'),
- CmdlineCheck('self_protection', 'kspp', 'slub_merge', 'is not set')))]
+ CmdlineCheck('self_protection', 'clipos', 'slub_merge', 'is not set')))]
l += [OR(CmdlineCheck('self_protection', 'kspp', 'iommu.strict', '1'),
AND(KconfigCheck('self_protection', 'kspp', 'IOMMU_DEFAULT_DMA_STRICT', 'y'),
CmdlineCheck('self_protection', 'kspp', 'iommu.strict', 'is not set')))]
continue
for o3 in o2.opts:
assert(o3.type != 'complex'), \
- 'unexpected ComplexOptCheck inside {}'.format(o2.name)
+ f'unexpected ComplexOptCheck inside {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 check for option {} ({})'.format(option, value))
+ print(f'[?] No check for option {option} ({value})')
def print_checklist(mode, checklist, with_results):
if with_results:
sep_line_len += 30
print('=' * sep_line_len)
- print('{:^40}|{:^7}|{:^12}|{:^10}|{:^18}'.format('option name', 'type', 'desired val', 'decision', 'reason'), end='')
+ print(f"{'option name':^40}|{'type':^7}|{'desired val':^12}|{'decision':^10}|{'reason':^18}", end='')
if with_results:
- print('| {}'.format('check result'), end='')
+ print('| check result', end='')
print()
print('=' * sep_line_len)
if mode == 'show_fail':
ok_suppressed = ' (suppressed in output)'
if mode != 'json':
- print('[+] Config check is finished: \'OK\' - {}{} / \'FAIL\' - {}{}'.format(ok_count, ok_suppressed, fail_count, fail_suppressed))
+ print(f'[+] Config check is finished: \'OK\' - {ok_count}{ok_suppressed} / \'FAIL\' - {fail_count}{fail_suppressed}')
def populate_simple_opt_with_data(opt, data, data_type):
assert(opt.type != 'complex'), \
- 'unexpected ComplexOptCheck "{}"'.format(opt.name)
+ f'unexpected ComplexOptCheck "{opt.name}"'
assert(opt.type in SIMPLE_OPTION_TYPES), \
- 'invalid opt type "{}"'.format(opt.type)
+ f'invalid opt type "{opt.type}"'
assert(data_type in SIMPLE_OPTION_TYPES), \
- 'invalid data type "{}"'.format(data_type)
+ f'invalid data type "{data_type}"'
if data_type != opt.type:
return
opt.state = data.get(opt.name, None)
else:
assert(data_type == 'version'), \
- 'unexpected data type "{}"'.format(data_type)
+ f'unexpected data type "{data_type}"'
opt.ver = data
populate_simple_opt_with_data(o, data, data_type)
else:
assert(opt.type in ('kconfig', 'cmdline')), \
- 'bad type "{}" for a simple check'.format(opt.type)
+ f'bad type "{opt.type}" for a simple check'
populate_simple_opt_with_data(opt, data, data_type)
if args.mode:
mode = args.mode
if mode != 'json':
- print('[+] Special report mode: {}'.format(mode))
+ print(f'[+] Special report mode: {mode}')
config_checklist = []
sys.exit('[!] ERROR: --config and --print can\'t be used together')
if mode != 'json':
- print('[+] Kconfig file to check: {}'.format(args.config))
+ print(f'[+] Kconfig file to check: {args.config}')
if args.cmdline:
- print('[+] Kernel cmdline file to check: {}'.format(args.cmdline))
+ print(f'[+] Kernel cmdline file to check: {args.cmdline}')
arch, msg = detect_arch(args.config, supported_archs)
if not arch:
sys.exit('[!] ERROR: {}'.format(msg))
if mode != 'json':
- print('[+] Detected architecture: {}'.format(arch))
+ print(f'[+] Detected architecture: {arch}')
kernel_version, msg = detect_kernel_version(args.config)
if not kernel_version:
sys.exit('[!] ERROR: {}'.format(msg))
if mode != 'json':
- print('[+] Detected kernel version: {}.{}'.format(kernel_version[0], kernel_version[1]))
+ print(f'[+] Detected kernel version: {kernel_version[0]}.{kernel_version[1]}')
compiler, msg = detect_compiler(args.config)
if mode != 'json':
if compiler:
- print('[+] Detected compiler: {}'.format(compiler))
+ print(f'[+] Detected compiler: {compiler}')
else:
- print('[-] Can\'t detect the compiler: {}'.format(msg))
+ print(f'[-] Can\'t detect the compiler: {msg}')
# add relevant kconfig checks to the checklist
add_kconfig_checks(config_checklist, arch)
if mode == 'verbose':
# print the parsed options without the checks (for debugging)
all_parsed_options = parsed_kconfig_options # assignment does not copy
- all_parsed_options.update(parsed_cmdline_options)
+ if args.cmdline:
+ all_parsed_options.update(parsed_cmdline_options)
print_unknown_options(config_checklist, all_parsed_options)
# finally print the results
add_kconfig_checks(config_checklist, arch)
add_cmdline_checks(config_checklist, arch)
if mode != 'json':
- print('[+] Printing kernel security hardening preferences for {}...'.format(arch))
+ print(f'[+] Printing kernel security hardening preferences for {arch}...')
print_checklist(mode, config_checklist, False)
sys.exit(0)