`_
62 |
--------------------------------------------------------------------------------
/kcc/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clearlinux/kernel-config-checker/96ec8b558d2cb7451b249873c17b4f3123642454/kcc/__init__.py
--------------------------------------------------------------------------------
/kcc/cli.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright (C) 2018 Intel Corporation
3 |
4 | SPDX-License-Identifier: GPL-3.0
5 |
6 | cli for kernel config checker
7 | '''
8 | import sys
9 | import argparse
10 | from typing import Iterator
11 |
12 | from .kconfig import Kconfig
13 | from .query import KernelSelfProtectionProject
14 | from .defaults import MUST_BE_SET, MUST_BE_SET_OR_MODULE, MUST_BE_UNSET
15 |
16 | def run(*,
17 | config_file: str = '',
18 | query: bool = False):
19 | if query:
20 | must_be_set, must_be_set_or_module, must_be_unset = \
21 | KernelSelfProtectionProject()
22 | must_be_set.update(MUST_BE_SET)
23 | must_be_set_or_module.update(MUST_BE_SET_OR_MODULE)
24 | must_be_unset.update(MUST_BE_UNSET)
25 | kconfig = Kconfig(must_be_set=must_be_set,
26 | must_be_set_or_module=must_be_set_or_module,
27 | must_be_unset=must_be_unset)
28 | else:
29 | kconfig = Kconfig.default()
30 |
31 | results = kconfig.check(config_file)
32 | if not results:
33 | return
34 | for opt, msg in results.items():
35 | print(opt, msg)
36 | return 1
37 |
38 | def cli() -> None:
39 | parser = argparse.ArgumentParser()
40 | parser.add_argument('config_file', nargs='?', type=argparse.FileType('r'),
41 | default=sys.stdin)
42 | parser.add_argument('--query', default=False, action='store_true')
43 | args = parser.parse_args()
44 | ret = run(**vars(args))
45 | args.config_file.close()
46 | if isinstance(ret, int):
47 | sys.exit(ret)
48 |
--------------------------------------------------------------------------------
/kcc/defaults.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright (C) 2018 Intel Corporation
3 |
4 | SPDX-License-Identifier: GPL-3.0
5 |
6 | Kernel config checker defaults for what must be set and what must not be set
7 | '''
8 | from typing import Dict
9 |
10 | MUST_BE_SET: Dict[str, str] = {}
11 | MUST_BE_SET_OR_MODULE: Dict[str, str] = {}
12 | MUST_BE_UNSET: Dict[str, str] = {}
13 |
14 | MUST_BE_SET["CONFIG_RANDOMIZE_BASE"] = "KASLR is required as a basic security hardening"
15 | MUST_BE_SET["CONFIG_RANDOMIZE_MEMORY"] = "KASLR is required as a basic security hardening"
16 | MUST_BE_SET["CONFIG_STRICT_KERNEL_RWX"] = "NX is important for buffer overflow exploit hardening"
17 | MUST_BE_SET["CONFIG_CC_STACKPROTECTOR"] = "Stack Protector is for buffer overflow detection and hardening"
18 | MUST_BE_SET["CONFIG_STACKPROTECTOR"] = "Stack Protector is for buffer overflow detection and hardening"
19 |
20 | MUST_BE_UNSET["CONFIG_DEVMEM"] = "/dev/mem is dangerous and has no legitimate users anymore"
21 |
22 | MUST_BE_SET["CONFIG_DEBUG_CREDENTIALS"] = "Needed to protect against targeted corruption by rootkits"
23 | MUST_BE_SET["CONFIG_DEBUG_NOTIFIERS"] = "Needed to protect against targeted corruption by rootkits"
24 | MUST_BE_SET["CONFIG_DEBUG_LIST"] = "Needed to protect against targeted corruption by rootkits"
25 | MUST_BE_SET["CONFIG_DEBUG_SG"] = "Needed to protect against targeted corruption by rootkits"
26 | MUST_BE_SET["CONFIG_SCHED_STACK_END_CHECK"] = "Needed to protect against targeted corruption by rootkits"
27 | MUST_BE_SET["CONFIG_RETPOLINE"] = "Needed to protect against Spectre V2"
28 |
29 |
30 | MUST_BE_SET["CONFIG_SECCOMP"] = "Seccomp is a security feature needed by systemd"
31 | MUST_BE_SET["CONFIG_SECCOMP_FILTER"] = "Seccomp is a security feature needed by systemd"
32 |
33 | MUST_BE_SET["CONFIG_HARDENED_USERCOPY"] = "Protect against ioctl buffer overflows"
34 | MUST_BE_UNSET["CONFIG_HARDENED_USERCOPY_FALLBACK"] = "Protect against ioctl buffer overflows"
35 |
36 | MUST_BE_SET["CONFIG_SLAB_FREELIST_RANDOM"] = "Harden the slab free list with randomization"
37 | MUST_BE_SET["CONFIG_SLAB_FREELIST_HARDENED"] = "Harden the slab free list with randomization"
38 |
39 | MUST_BE_SET["CONFIG_VMAP_STACK"] = "Guard pages for kernel stacks"
40 |
41 | MUST_BE_SET["CONFIG_REFCOUNT_FULL"] = "Perform extensive checks on reference counting"
42 | MUST_BE_SET["CONFIG_FORTIFY_SOURCE"]= "Check for memory copies that might overflow a structure in str*() and mem*() functions both at build-time and run-time."
43 | MUST_BE_UNSET["CONFIG_ACPI_CUSTOM_METHOD"] = "Dangerous; enabling this allows direct physical memory writing"
44 | MUST_BE_UNSET["CONFIG_COMPAT_BRK"] = "Dangerous; enabling this disables brk ASLR"
45 | MUST_BE_UNSET["CONFIG_DEVKMEM"] = "Dangerous; enabling this allows direct kernel memory writing."
46 | MUST_BE_UNSET["CONFIG_PROC_KCORE"] = "Dangerous; exposes kernel text image layout"
47 | MUST_BE_UNSET["CONFIG_COMPAT_VDSO"] = "Dangerous; enabling this disables VDSO ASLR"
48 | MUST_BE_UNSET["CONFIG_INET_DIAG"] = "Prior to v4.1, assists heap memory attacks; best to keep interface disabled"
49 | MUST_BE_UNSET["CONFIG_LEGACY_PTYS"] = "Use the modern PTY interface (devpts) only"
50 | MUST_BE_SET["CONFIG_DEBUG_SET_MODULE_RONX"] = "Ensure modules have NX enabled"
51 | MUST_BE_SET["CONFIG_STRICT_MODULE_RWX"] = "Ensure modules have NX enabled"
52 | MUST_BE_SET["CONFIG_MODULE_SIG"] = "Signing of kernel modules is required"
53 | MUST_BE_SET["CONFIG_MODULE_SIG_FORCE"] = "Enforce module signing"
54 | MUST_BE_SET["CONFIG_MODULE_SIG_SHA512"] = "Use SHA512 for kernel module signing"
55 |
56 | MUST_BE_SET["CONFIG_LEGACY_VSYSCALL_NONE"] = "Modern libc no longer needs a fixed-position mapping in userspace, remove it as a possible target."
57 | MUST_BE_SET["CONFIG_PAGE_TABLE_ISOLATION"] = "Enable Kernel Page Table Isolation to remove an entire class of cache timing side-channels."
58 | MUST_BE_UNSET["CONFIG_X86_X32"] = "X32 is rarely used and provides only attack surface"
59 | MUST_BE_UNSET["CONFIG_MODIFY_LDT_SYSCALL"] = "Unused dangerous option"
60 |
--------------------------------------------------------------------------------
/kcc/kconfig.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright (C) 2018 Intel Corporation
3 |
4 | SPDX-License-Identifier: GPL-3.0
5 |
6 | Check a kernel config file against a set of best known settings
7 | See also https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Recommended_Settings
8 | '''
9 |
10 | import re
11 | import sys
12 | from typing import Dict, Iterable
13 |
14 | from .defaults import MUST_BE_SET, MUST_BE_SET_OR_MODULE, MUST_BE_UNSET
15 |
16 | class Kconfig(object):
17 | '''
18 | Reads and checks a kernel config file.
19 | '''
20 |
21 | N_MUST_Y = "is not set but is required to be set to y (%s)"
22 | N_MUST_Y_OR_M = "is not set but is required to be set to y or m (%s)"
23 | Y_MUST_N = "is set but is required to be not set (%s)"
24 | M_MUST_Y = "is set as =m but is required to be set to y (%s)"
25 | M_MUST_N = "is set as =m but is required to be not set (%s)"
26 |
27 | def __init__(self, *,
28 | must_be_set: Dict[str, str] = {},
29 | must_be_set_or_module : Dict[str, str] = {},
30 | must_be_unset: Dict[str, str] = {}) -> None:
31 | self.must_be_set = must_be_set
32 | self.must_be_set_or_module = must_be_set_or_module
33 | self.must_be_unset = must_be_unset
34 |
35 | @classmethod
36 | def default(cls):
37 | return cls(must_be_set=MUST_BE_SET,
38 | must_be_set_or_module=MUST_BE_SET_OR_MODULE,
39 | must_be_unset=MUST_BE_UNSET)
40 |
41 | def check(self, stream: Iterable[str]) -> Dict[str, str]:
42 | results: Dict[str, str] = {}
43 | for line in stream:
44 | results.update(self.check_line(line))
45 | return results
46 |
47 | def check_line(self, line: str) -> Dict[str, str]:
48 | line = line.strip()
49 | match = re.search("^# (CONFIG_.*) is not set", line)
50 | if match:
51 | notset = match.group(1)
52 | if notset in self.must_be_set:
53 | return {notset: self.N_MUST_Y % \
54 | (self.must_be_set[notset])}
55 | if notset in self.must_be_set_or_module:
56 | return {notset: self.N_MUST_Y_OR_M % \
57 | ('', self.must_be_set_or_module[notset])}
58 | match = re.search("^(CONFIG_.*)=y", line)
59 | if match:
60 | notset = match.group(1)
61 | if notset in self.must_be_unset:
62 | return {notset: self.Y_MUST_N % \
63 | (self.must_be_unset[notset])}
64 | match = re.search("^(CONFIG_.*)=m", line)
65 | if match:
66 | notset = match.group(1)
67 | if notset in self.must_be_set:
68 | return {notset: self.M_MUST_Y % \
69 | (self.must_be_set[notset])}
70 | if notset in self.must_be_unset:
71 | return {notset: self.M_MUST_N % \
72 | (self.must_be_unset[notset])}
73 | return {}
74 |
--------------------------------------------------------------------------------
/kcc/query.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright (C) 2018 Intel Corporation
3 |
4 | SPDX-License-Identifier: GPL-3.0
5 |
6 | Scrape best known kernel config settings from the KSPP WiKi.
7 | '''
8 |
9 | import re
10 | import urllib.request
11 | from typing import Tuple, Dict
12 |
13 | KSPP_URL = 'https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Recommended_Settings'
14 |
15 | def KernelSelfProtectionProject():
16 | with urllib.request.urlopen(KSPP_URL) as f:
17 | if f.code != 200:
18 | raise ValueError('Failed to retrieve secure config options')
19 | text = f.read().decode()
20 | # Map id's from right before the tag to their contents
21 | return kspp_gen_config(dict(zip(
22 | [block.split('id=')[-1].split('"')[1].strip() \
23 | for block in text.split('>\n')],
24 | [block[:-2] for block in text.split('pre>') \
25 | if block.endswith('')])))
26 |
27 | def kspp_gen_config(sections_and_configs: Dict[str, str]) \
28 | -> Tuple[Dict[str, str], Dict[str, str], Dict[str, str]]:
29 | must_be_set: Dict[str, str] = {}
30 | must_be_set_or_module: Dict[str, str] = {}
31 | must_be_unset: Dict[str, str] = {}
32 | data = sections_and_configs['CONFIGs'] + sections_and_configs['x86_64']
33 | lines = data.split('\n')
34 | comment = 0
35 | for i in range(0, len(lines)):
36 | line = lines[i]
37 | if line.startswith('#') \
38 | and not line.startswith('# CONFIG_'):
39 | comment = i
40 | if i == comment:
41 | continue
42 | if 'is not set' in line:
43 | opt = 'CONFIG_' + line.split('CONFIG_')[1].split()[0]
44 | must_be_unset[opt] = lines[comment].replace('#', '').strip()
45 | elif line.endswith('=y'):
46 | opt = 'CONFIG_' + line.split('CONFIG_')[1].split('=')[0]
47 | must_be_set[opt] = lines[comment].replace('#', '').strip()
48 | elif line.endswith('=m'):
49 | opt = 'CONFIG_' + line.split('CONFIG_')[1].split('=')[0]
50 | must_be_set_or_module[opt] = lines[comment].replace('#',
51 | '').strip()
52 | # KSPP Recommends disabling modules. Which is not realistic.
53 | if 'CONFIG_MODULES' in must_be_unset:
54 | del must_be_unset['CONFIG_MODULES']
55 | return must_be_set, must_be_set_or_module, must_be_unset
56 |
--------------------------------------------------------------------------------
/kcc/version.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright (C) 2018 Intel Corporation
3 |
4 | SPDX-License-Identifier: GPL-3.0
5 |
6 | Version of Kernel Config Checker
7 | '''
8 | VERSION = '0.0.8'
9 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import ast
2 | from io import open
3 |
4 | from setuptools import find_packages, setup
5 |
6 | with open('kcc/version.py', 'r') as f:
7 | for line in f:
8 | if line.startswith('VERSION'):
9 | version = ast.literal_eval(line.strip().split('=')[-1].strip())
10 | break
11 |
12 | with open('README.rst', 'r', encoding='utf-8') as f:
13 | readme = f.read()
14 |
15 | setup(
16 | name='kcc',
17 | version=version,
18 | description='Check kernel config for security issues',
19 | long_description=readme,
20 | author='Arjan van de Ven',
21 | author_email='arjan@linux.intel.com',
22 | url='https://github.com/clearlinux/kernel-config-checker',
23 | license='GPL-3.0',
24 |
25 | keywords=[
26 | '',
27 | ],
28 |
29 | classifiers=[
30 | 'Development Status :: 4 - Beta',
31 | 'Intended Audience :: Developers',
32 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
33 | 'Natural Language :: English',
34 | 'Operating System :: OS Independent',
35 | 'Programming Language :: Python :: 3.6',
36 | 'Programming Language :: Python :: 3.7',
37 | 'Programming Language :: Python :: Implementation :: CPython',
38 | 'Programming Language :: Python :: Implementation :: PyPy',
39 | ],
40 |
41 | tests_require=['pytest'],
42 |
43 | packages=find_packages(),
44 | entry_points={
45 | 'console_scripts': [
46 | 'kcc = kcc.cli:cli',
47 | ],
48 | },
49 | )
50 |
--------------------------------------------------------------------------------