#!/usr/bin/python3
-# 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>
-#
-# Please don't cry if my Python code looks like C.
+"""
+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!
-# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
-# pylint: disable=line-too-long,invalid-name,too-many-branches,too-many-statements
+Author: Alexander Popov <alex.popov@linux.com>
+This module performs input/output.
+"""
+
+# pylint: disable=missing-function-docstring,line-too-long,invalid-name,too-many-branches,too-many-statements
+
+import gzip
import sys
from argparse import ArgumentParser
from collections import OrderedDict
from .engine import populate_with_data, perform_checks
+def _open(file: str, *args, **kwargs):
+ open_method = open
+ if file.endswith(".gz"):
+ open_method = gzip.open
+
+ return open_method(file, *args, **kwargs)
+
+
def detect_arch(fname, archs):
- with open(fname, 'r', encoding='utf-8') as f:
+ with _open(fname, 'rt', encoding='utf-8') as f:
arch_pattern = re.compile("CONFIG_[a-zA-Z0-9_]*=y")
arch = None
for line in f.readlines():
if arch_pattern.match(line):
option, _ = line[7:].split('=', 1)
if option in archs:
- if not arch:
+ if arch is None:
arch = option
else:
return None, 'more than one supported architecture is detected'
- if not arch:
+ if arch is None:
return None, 'failed to detect architecture'
return arch, 'OK'
def detect_kernel_version(fname):
- with open(fname, 'r', encoding='utf-8') as f:
+ with _open(fname, 'rt', encoding='utf-8') as f:
ver_pattern = re.compile("# Linux/.* Kernel Configuration")
for line in f.readlines():
if ver_pattern.match(line):
ver_str = parts[2]
ver_numbers = ver_str.split('.')
if len(ver_numbers) < 3 or not ver_numbers[0].isdigit() or not ver_numbers[1].isdigit():
- msg = 'failed to parse the version "' + ver_str + '"'
+ msg = f'failed to parse the version "{ver_str}"'
return None, msg
return (int(ver_numbers[0]), int(ver_numbers[1])), None
return None, 'no kernel version detected'
def detect_compiler(fname):
gcc_version = None
clang_version = None
- with open(fname, 'r', encoding='utf-8') as f:
+ with _open(fname, 'rt', encoding='utf-8') as f:
gcc_version_pattern = re.compile("CONFIG_GCC_VERSION=[0-9]*")
clang_version_pattern = re.compile("CONFIG_CLANG_VERSION=[0-9]*")
for line in f.readlines():
gcc_version = line[19:-1]
if clang_version_pattern.match(line):
clang_version = line[21:-1]
- if not gcc_version or not clang_version:
+ if gcc_version is None or clang_version is None:
return None, 'no CONFIG_GCC_VERSION or CONFIG_CLANG_VERSION'
if gcc_version == '0' and clang_version != '0':
return 'CLANG ' + clang_version, 'OK'
def print_checklist(mode, checklist, with_results):
if mode == 'json':
output = []
- for o in checklist:
- output.append(o.json_dump(with_results))
+ for opt in checklist:
+ output.append(opt.json_dump(with_results))
print(json.dumps(output))
return
if with_results:
sep_line_len += 30
print('=' * sep_line_len)
- print(f"{'option name':^40}|{'type':^7}|{'desired val':^12}|{'decision':^10}|{'reason':^18}", end='')
+ print(f'{"option name":^40}|{"type":^7}|{"desired val":^12}|{"decision":^10}|{"reason":^18}', end='')
if with_results:
print('| check result', end='')
print()
fail_suppressed = ' (suppressed in output)'
if mode == 'show_fail':
ok_suppressed = ' (suppressed in output)'
- if mode != 'json':
- print(f'[+] Config check is finished: \'OK\' - {ok_count}{ok_suppressed} / \'FAIL\' - {fail_count}{fail_suppressed}')
+ print(f'[+] Config check is finished: \'OK\' - {ok_count}{ok_suppressed} / \'FAIL\' - {fail_count}{fail_suppressed}')
def parse_kconfig_file(parsed_options, fname):
- with open(fname, 'r', encoding='utf-8') as f:
+ with _open(fname, 'rt', encoding='utf-8') 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")
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 kconfig file against these preferences')
+ help='check the kernel kconfig file against these preferences (also supports *.gz files)')
parser.add_argument('-l', '--cmdline',
help='check the kernel cmdline file against these preferences')
parser.add_argument('-m', '--mode', choices=report_modes,
print(f'[+] Kernel cmdline file to check: {args.cmdline}')
arch, msg = detect_arch(args.config, supported_archs)
- if not arch:
+ if arch is None:
sys.exit(f'[!] ERROR: {msg}')
if mode != 'json':
print(f'[+] Detected architecture: {arch}')
kernel_version, msg = detect_kernel_version(args.config)
- if not kernel_version:
+ if kernel_version is None:
sys.exit(f'[!] ERROR: {msg}')
if mode != 'json':
print(f'[+] Detected kernel version: {kernel_version[0]}.{kernel_version[1]}')
parsed_kconfig_options = OrderedDict()
parse_kconfig_file(parsed_kconfig_options, args.config)
populate_with_data(config_checklist, parsed_kconfig_options, 'kconfig')
+
+ # populate the checklist with the kernel version data
populate_with_data(config_checklist, kernel_version, 'version')
if args.cmdline:
- # populate the checklist with the parsed kconfig data
+ # populate the checklist with the parsed cmdline data
parsed_cmdline_options = OrderedDict()
parse_cmdline_file(parsed_cmdline_options, args.cmdline)
populate_with_data(config_checklist, parsed_cmdline_options, 'cmdline')