From dc68441b86d36554134c202e955de48ba21f9193 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Sun, 9 Jul 2023 00:18:28 +0300 Subject: [PATCH] Add the basic infrastructure for checking sysctl Refers to #65 --- kconfig_hardened_check/__init__.py | 33 ++++++++++++++++++++++++++---- kconfig_hardened_check/checks.py | 9 +++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/kconfig_hardened_check/__init__.py b/kconfig_hardened_check/__init__.py index fa32725..a423358 100644 --- a/kconfig_hardened_check/__init__.py +++ b/kconfig_hardened_check/__init__.py @@ -17,7 +17,7 @@ from collections import OrderedDict import re import json from .__about__ import __version__ -from .checks import add_kconfig_checks, add_cmdline_checks, normalize_cmdline_options +from .checks import add_kconfig_checks, add_cmdline_checks, normalize_cmdline_options, add_sysctl_checks from .engine import populate_with_data, perform_checks, override_expected_value @@ -197,6 +197,10 @@ def parse_cmdline_file(parsed_options, fname): parsed_options[name] = value +def parse_sysctl_file(parsed_options, fname): + print('parse_sysctl_file: TODO') + + def main(): # Report modes: # * verbose mode for @@ -214,11 +218,14 @@ def main(): help='check the security hardening options in the kernel Kconfig file (also supports *.gz files)') parser.add_argument('-l', '--cmdline', help='check the security hardening options in the kernel cmdline file (contents of /proc/cmdline)') +# parser.add_argument('-s', '--sysctl', +# help='check the security hardening options in the sysctl output file (`sudo sysctl -a > file`)') parser.add_argument('-p', '--print', choices=supported_archs, help='print the security hardening recommendations for the selected microarchitecture') parser.add_argument('-g', '--generate', choices=supported_archs, help='generate a Kconfig fragment with the security hardening options for the selected microarchitecture') args = parser.parse_args() + args.sysctl = None # FIXME mode = None if args.mode: @@ -239,6 +246,8 @@ def main(): print(f'[+] Kconfig file to check: {args.config}') if args.cmdline: print(f'[+] Kernel cmdline file to check: {args.cmdline}') + if args.sysctl: + print(f'[+] Kernel sysctl output file to check: {args.sysctl}') arch, msg = detect_arch(args.config, supported_archs) if arch is None: @@ -266,6 +275,10 @@ def main(): # add relevant cmdline checks to the checklist add_cmdline_checks(config_checklist, arch) + if args.sysctl: + # add relevant sysctl checks to the checklist + add_sysctl_checks(config_checklist, arch) + # populate the checklist with the parsed Kconfig data parsed_kconfig_options = OrderedDict() parse_kconfig_file(parsed_kconfig_options, args.config) @@ -280,6 +293,12 @@ def main(): parse_cmdline_file(parsed_cmdline_options, args.cmdline) populate_with_data(config_checklist, parsed_cmdline_options, 'cmdline') + if args.sysctl: + # populate the checklist with the parsed sysctl data + parsed_sysctl_options = OrderedDict() + parse_sysctl_file(parsed_sysctl_options, args.sysctl) + populate_with_data(config_checklist, parsed_sysctl_options, 'sysctl') + # hackish refinement of the CONFIG_ARCH_MMAP_RND_BITS check mmap_rnd_bits_max = parsed_kconfig_options.get('CONFIG_ARCH_MMAP_RND_BITS_MAX', None) if mmap_rnd_bits_max: @@ -293,6 +312,8 @@ def main(): all_parsed_options = parsed_kconfig_options # assignment does not copy if args.cmdline: all_parsed_options.update(parsed_cmdline_options) + if args.sysctl: + all_parsed_options.update(parsed_sysctl_options) print_unknown_options(config_checklist, all_parsed_options) # finally print the results @@ -300,22 +321,26 @@ def main(): sys.exit(0) elif args.cmdline: - sys.exit('[!] ERROR: checking cmdline doesn\'t work without checking Kconfig') + sys.exit('[!] ERROR: checking cmdline depends on checking Kconfig') + elif args.sysctl: + # TODO: sysctl check should also work separately + sys.exit('[!] ERROR: checking sysctl depends on checking Kconfig') if args.print: - assert(args.config is None and args.cmdline is None), 'unexpected args' + assert(args.config is None and args.cmdline is None and args.sysctl is None), 'unexpected args' if mode and mode not in ('verbose', 'json'): sys.exit(f'[!] ERROR: wrong mode "{mode}" for --print') arch = args.print add_kconfig_checks(config_checklist, arch) add_cmdline_checks(config_checklist, arch) + add_sysctl_checks(config_checklist, arch) if mode != 'json': print(f'[+] Printing kernel security hardening options for {arch}...') print_checklist(mode, config_checklist, False) sys.exit(0) if args.generate: - assert(args.config is None and args.cmdline is None), 'unexpected args' + assert(args.config is None and args.cmdline is None and args.sysctl is None), 'unexpected args' if mode: sys.exit(f'[!] ERROR: wrong mode "{mode}" for --generate') arch = args.generate diff --git a/kconfig_hardened_check/checks.py b/kconfig_hardened_check/checks.py index d857ad1..4a994e7 100644 --- a/kconfig_hardened_check/checks.py +++ b/kconfig_hardened_check/checks.py @@ -11,7 +11,7 @@ This module contains knowledge for checks. # pylint: disable=missing-function-docstring,line-too-long,invalid-name # pylint: disable=too-many-branches,too-many-statements -from .engine import KconfigCheck, CmdlineCheck, VersionCheck, OR, AND +from .engine import KconfigCheck, CmdlineCheck, SysctlCheck, VersionCheck, OR, AND def add_kconfig_checks(l, arch): @@ -574,10 +574,9 @@ def normalize_cmdline_options(option, value): return value -# def add_sysctl_checks(l, arch): +def add_sysctl_checks(l, arch): # TODO: draft of security hardening sysctls: # kernel.kptr_restrict=2 (or 1?) -# kernel.dmesg_restrict=1 (also see the kconfig option) # 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 @@ -605,3 +604,7 @@ def normalize_cmdline_options(option, value): # kernel.oops_limit (think about a proper value) # kernel.warn_limit (think about a proper value) # net.ipv4.tcp_syncookies=1 (?) +# +# Calling the SysctlCheck class constructor: +# SysctlCheck(reason, decision, name, expected) + l += [SysctlCheck('self_protection', 'kspp', 'kernel.dmesg_restrict', '1')] -- 2.31.1