X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=kconfig_hardened_check%2F__init__.py;h=7523590a5c907992feec21fc2db75a708d2f6a95;hb=65b2684a586c34ca07944b88b1e3fa877961b1f2;hp=1b26a252f6f6253866d30fc900ac60107a1df387;hpb=2b7a15dd055e30baeed5d285711415cebaf39086;p=kconfig-hardened-check.git diff --git a/kconfig_hardened_check/__init__.py b/kconfig_hardened_check/__init__.py index 1b26a25..7523590 100644 --- a/kconfig_hardened_check/__init__.py +++ b/kconfig_hardened_check/__init__.py @@ -11,17 +11,12 @@ # # # N.B Hardening command line parameters: -# page_alloc.shuffle=1 # iommu=force (does it help against DMA attacks?) -# slub_debug=FZ (slow) -# loadpin.enforce=1 -# debugfs=no-mount (or off if possible) # # Mitigations of CPU vulnerabilities: # Аrch-independent: # mitigations=auto,nosmt (nosmt is slow) # X86: -# spectre_v2=on # spec_store_bypass_disable=on # l1tf=full,force # l1d_flush=on (a part of the l1tf option) @@ -32,7 +27,6 @@ # ssbd=force-on # # Should NOT be set: -# nokaslr # sysrq_always_enabled # arm64.nobti # arm64.nopauth @@ -56,6 +50,7 @@ # vm.unprivileged_userfaultfd=0 # (at first, it disabled unprivileged userfaultfd, # and since v5.11 it enables unprivileged userfaultfd for user-mode only) +# vm.mmap_min_addr has a good value # dev.tty.ldisc_autoload=0 # fs.protected_symlinks=1 # fs.protected_hardlinks=1 @@ -291,7 +286,7 @@ def detect_arch(fname, archs): return arch, 'OK' -def detect_version(fname): +def detect_kernel_version(fname): with open(fname, 'r') as f: ver_pattern = re.compile("# Linux/.* Kernel Configuration") for line in f.readlines(): @@ -307,19 +302,46 @@ def detect_version(fname): return None, 'no kernel version detected' +def detect_compiler(fname): + gcc_version = None + clang_version = None + with open(fname, 'r') 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(): + if gcc_version_pattern.match(line): + gcc_version = line[19:-1] + if clang_version_pattern.match(line): + clang_version = line[21:-1] + if not gcc_version or not clang_version: + return None, 'no CONFIG_GCC_VERSION or CONFIG_CLANG_VERSION' + if gcc_version == '0' and clang_version != '0': + return 'CLANG ' + clang_version, 'OK' + if gcc_version != '0' and clang_version == '0': + return 'GCC ' + gcc_version, 'OK' + sys.exit('[!] ERROR: invalid GCC_VERSION and CLANG_VERSION: {} {}'.format(gcc_version, clang_version)) + + def add_kconfig_checks(l, arch): # Calling the KconfigCheck class constructor: # KconfigCheck(reason, decision, name, expected) + # + # [!] Don't add CmdlineChecks in add_kconfig_checks() to avoid wrong results + # when the tool doesn't check the cmdline. + + efi_not_set = KconfigCheck('-', '-', 'EFI', 'is not set') + cc_is_gcc = KconfigCheck('-', '-', 'CC_IS_GCC', 'y') # exists since v4.18 + cc_is_clang = KconfigCheck('-', '-', 'CC_IS_CLANG', 'y') # exists since v4.18 modules_not_set = KconfigCheck('cut_attack_surface', 'kspp', 'MODULES', 'is not set') devmem_not_set = KconfigCheck('cut_attack_surface', 'kspp', 'DEVMEM', 'is not set') # refers to LOCKDOWN bpf_syscall_not_set = KconfigCheck('cut_attack_surface', 'lockdown', 'BPF_SYSCALL', 'is not set') # refers to LOCKDOWN - efi_not_set = KconfigCheck('cut_attack_surface', 'my', 'EFI', 'is not set') # 'self_protection', 'defconfig' l += [KconfigCheck('self_protection', 'defconfig', 'BUG', 'y')] l += [KconfigCheck('self_protection', 'defconfig', 'SLUB_DEBUG', 'y')] - l += [KconfigCheck('self_protection', 'defconfig', 'GCC_PLUGINS', 'y')] + gcc_plugins_support_is_set = KconfigCheck('self_protection', 'defconfig', 'GCC_PLUGINS', 'y') + l += [gcc_plugins_support_is_set] l += [OR(KconfigCheck('self_protection', 'defconfig', 'STACKPROTECTOR', 'y'), KconfigCheck('self_protection', 'defconfig', 'CC_STACKPROTECTOR', 'y'), KconfigCheck('self_protection', 'defconfig', 'CC_STACKPROTECTOR_REGULAR', 'y'), @@ -342,9 +364,13 @@ def add_kconfig_checks(l, arch): if arch in ('X86_64', 'ARM64'): l += [KconfigCheck('self_protection', 'defconfig', 'VMAP_STACK', 'y')] if arch in ('X86_64', 'X86_32'): + l += [KconfigCheck('self_protection', 'defconfig', 'X86_MCE', 'y')] + l += [KconfigCheck('self_protection', 'defconfig', 'X86_MCE_INTEL', 'y')] + l += [KconfigCheck('self_protection', 'defconfig', 'X86_MCE_AMD', 'y')] l += [KconfigCheck('self_protection', 'defconfig', 'MICROCODE', 'y')] # is needed for mitigating CPU bugs l += [KconfigCheck('self_protection', 'defconfig', 'RETPOLINE', 'y')] - l += [KconfigCheck('self_protection', 'defconfig', 'X86_SMAP', 'y')] + l += [OR(KconfigCheck('self_protection', 'defconfig', 'X86_SMAP', 'y'), + VersionCheck((5, 19)))] # X86_SMAP is enabled by default since v5.19 l += [KconfigCheck('self_protection', 'defconfig', 'SYN_COOKIES', 'y')] # another reason? l += [OR(KconfigCheck('self_protection', 'defconfig', 'X86_UMIP', 'y'), KconfigCheck('self_protection', 'defconfig', 'X86_INTEL_UMIP', 'y'))] @@ -361,6 +387,7 @@ def add_kconfig_checks(l, arch): l += [KconfigCheck('self_protection', 'defconfig', 'ARM64_PAN', 'y')] l += [KconfigCheck('self_protection', 'defconfig', 'ARM64_EPAN', 'y')] l += [KconfigCheck('self_protection', 'defconfig', 'UNMAP_KERNEL_AT_EL0', 'y')] + l += [KconfigCheck('self_protection', 'defconfig', 'ARM64_E0PD', 'y')] l += [OR(KconfigCheck('self_protection', 'defconfig', 'HARDEN_EL2_VECTORS', 'y'), AND(KconfigCheck('self_protection', 'defconfig', 'RANDOMIZE_BASE', 'y'), VersionCheck((5, 9))))] # HARDEN_EL2_VECTORS was included in RANDOMIZE_BASE in v5.9 @@ -386,18 +413,27 @@ def add_kconfig_checks(l, arch): l += [KconfigCheck('self_protection', 'kspp', 'SHUFFLE_PAGE_ALLOCATOR', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'FORTIFY_SOURCE', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'DEBUG_LIST', 'y')] + l += [KconfigCheck('self_protection', 'kspp', 'DEBUG_VIRTUAL', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'DEBUG_SG', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'DEBUG_CREDENTIALS', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'DEBUG_NOTIFIERS', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'INIT_ON_ALLOC_DEFAULT_ON', 'y')] - l += [KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_LATENT_ENTROPY', 'y')] + l += [AND(KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_LATENT_ENTROPY', 'y'), + gcc_plugins_support_is_set)] l += [KconfigCheck('self_protection', 'kspp', 'KFENCE', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'WERROR', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'IOMMU_DEFAULT_DMA_STRICT', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'IOMMU_DEFAULT_PASSTHROUGH', 'is not set')] # true if IOMMU_DEFAULT_DMA_STRICT is set l += [KconfigCheck('self_protection', 'kspp', 'ZERO_CALL_USED_REGS', 'y')] - randstruct_is_set = KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_RANDSTRUCT', 'y') + l += [KconfigCheck('self_protection', 'kspp', 'HW_RANDOM_TPM', 'y')] + l += [KconfigCheck('self_protection', 'kspp', 'STATIC_USERMODEHELPER', 'y')] # needs userspace support + l += [KconfigCheck('self_protection', 'kspp', 'SCHED_CORE', 'y')] + randstruct_is_set = OR(KconfigCheck('self_protection', 'kspp', 'RANDSTRUCT_FULL', 'y'), + KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_RANDSTRUCT', 'y')) l += [randstruct_is_set] + l += [AND(KconfigCheck('self_protection', 'kspp', 'RANDSTRUCT_PERFORMANCE', 'is not set'), + KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_RANDSTRUCT_PERFORMANCE', 'is not set'), + randstruct_is_set)] hardened_usercopy_is_set = KconfigCheck('self_protection', 'kspp', 'HARDENED_USERCOPY', 'y') l += [hardened_usercopy_is_set] l += [AND(KconfigCheck('self_protection', 'kspp', 'HARDENED_USERCOPY_FALLBACK', 'is not set'), @@ -421,71 +457,66 @@ def add_kconfig_checks(l, arch): # Starting from v5.11 CONFIG_PAGE_POISONING unconditionally checks # the 0xAA poison pattern on allocation. # That brings higher performance penalty. + l += [OR(KconfigCheck('self_protection', 'kspp', 'EFI_DISABLE_PCI_DMA', 'y'), + efi_not_set)] + l += [OR(KconfigCheck('self_protection', 'kspp', 'RESET_ATTACK_MITIGATION', 'y'), + efi_not_set)] # needs userspace support (systemd) + ubsan_bounds_is_set = KconfigCheck('self_protection', 'kspp', 'UBSAN_BOUNDS', 'y') + l += [ubsan_bounds_is_set] + l += [OR(KconfigCheck('self_protection', 'kspp', 'UBSAN_LOCAL_BOUNDS', 'y'), + AND(ubsan_bounds_is_set, + cc_is_gcc))] + l += [AND(KconfigCheck('self_protection', 'kspp', 'UBSAN_TRAP', 'y'), + ubsan_bounds_is_set, + KconfigCheck('self_protection', 'kspp', 'UBSAN_SHIFT', 'is not set'), + KconfigCheck('self_protection', 'kspp', 'UBSAN_DIV_ZERO', 'is not set'), + KconfigCheck('self_protection', 'kspp', 'UBSAN_UNREACHABLE', 'is not set'), + KconfigCheck('self_protection', 'kspp', 'UBSAN_BOOL', 'is not set'), + KconfigCheck('self_protection', 'kspp', 'UBSAN_ENUM', 'is not set'), + KconfigCheck('self_protection', 'kspp', 'UBSAN_ALIGNMENT', 'is not set'))] # only array index bounds checking with traps if arch in ('X86_64', 'ARM64', 'X86_32'): + l += [AND(KconfigCheck('self_protection', 'kspp', 'UBSAN_SANITIZE_ALL', 'y'), + ubsan_bounds_is_set)] # ARCH_HAS_UBSAN_SANITIZE_ALL is not enabled for ARM stackleak_is_set = KconfigCheck('self_protection', 'kspp', 'GCC_PLUGIN_STACKLEAK', 'y') - l += [stackleak_is_set] + l += [AND(stackleak_is_set, gcc_plugins_support_is_set)] + l += [AND(KconfigCheck('self_protection', 'kspp', 'STACKLEAK_METRICS', 'is not set'), + stackleak_is_set, + gcc_plugins_support_is_set)] + l += [AND(KconfigCheck('self_protection', 'kspp', 'STACKLEAK_RUNTIME_DISABLE', 'is not set'), + stackleak_is_set, + gcc_plugins_support_is_set)] l += [KconfigCheck('self_protection', 'kspp', 'RANDOMIZE_KSTACK_OFFSET_DEFAULT', 'y')] + if arch in ('X86_64', 'ARM64'): + cfi_clang_is_set = KconfigCheck('self_protection', 'kspp', 'CFI_CLANG', 'y') + l += [cfi_clang_is_set] + l += [AND(KconfigCheck('self_protection', 'kspp', 'CFI_PERMISSIVE', 'is not set'), + cfi_clang_is_set)] 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 += [AND(KconfigCheck('self_protection', 'kspp', 'INTEL_IOMMU_DEFAULT_ON', 'y'), + iommu_support_is_set)] if arch in ('ARM64', 'ARM'): l += [KconfigCheck('self_protection', 'kspp', 'DEFAULT_MMAP_MIN_ADDR', '32768')] l += [KconfigCheck('self_protection', 'kspp', 'SYN_COOKIES', 'y')] # another reason? + if arch == 'X86_64': + l += [KconfigCheck('self_protection', 'kspp', 'SLS', 'y')] # vs CVE-2021-26341 in Straight-Line-Speculation + l += [AND(KconfigCheck('self_protection', 'kspp', 'INTEL_IOMMU_SVM', 'y'), + iommu_support_is_set)] + l += [AND(KconfigCheck('self_protection', 'kspp', 'AMD_IOMMU_V2', 'y'), + iommu_support_is_set)] if arch == 'ARM64': l += [KconfigCheck('self_protection', 'kspp', 'ARM64_SW_TTBR0_PAN', 'y')] + l += [KconfigCheck('self_protection', 'kspp', 'SHADOW_CALL_STACK', 'y')] + l += [KconfigCheck('self_protection', 'kspp', 'KASAN_HW_TAGS', 'y')] if arch == 'X86_32': l += [KconfigCheck('self_protection', 'kspp', 'PAGE_TABLE_ISOLATION', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'HIGHMEM64G', 'y')] l += [KconfigCheck('self_protection', 'kspp', 'X86_PAE', 'y')] - - # 'self_protection', 'maintainer' - ubsan_bounds_is_set = KconfigCheck('self_protection', 'maintainer', 'UBSAN_BOUNDS', 'y') # only array index bounds checking - l += [ubsan_bounds_is_set] # recommended by Kees Cook in /issues/53 - if arch in ('X86_64', 'ARM64', 'X86_32'): # ARCH_HAS_UBSAN_SANITIZE_ALL is not enabled for ARM - l += [AND(KconfigCheck('self_protection', 'maintainer', 'UBSAN_SANITIZE_ALL', 'y'), - ubsan_bounds_is_set)] # recommended by Kees Cook in /issues/53 - l += [AND(KconfigCheck('self_protection', 'maintainer', 'UBSAN_TRAP', 'y'), - ubsan_bounds_is_set)] # recommended by Kees Cook in /issues/53 + l += [AND(KconfigCheck('self_protection', 'kspp', 'INTEL_IOMMU', 'y'), + iommu_support_is_set)] # 'self_protection', 'clipos' - l += [KconfigCheck('self_protection', 'clipos', 'DEBUG_VIRTUAL', 'y')] - 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')] - 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'), - randstruct_is_set)] - if arch in ('X86_64', 'ARM64', 'X86_32'): - l += [AND(KconfigCheck('self_protection', 'clipos', 'STACKLEAK_METRICS', 'is not set'), - stackleak_is_set)] - l += [AND(KconfigCheck('self_protection', 'clipos', 'STACKLEAK_RUNTIME_DISABLE', 'is not set'), - stackleak_is_set)] - if arch in ('X86_64', 'X86_32'): - l += [AND(KconfigCheck('self_protection', 'clipos', 'INTEL_IOMMU_DEFAULT_ON', 'y'), - iommu_support_is_set)] - if arch == 'X86_64': - l += [AND(KconfigCheck('self_protection', 'clipos', 'INTEL_IOMMU_SVM', 'y'), - iommu_support_is_set)] - if arch == 'X86_32': - l += [AND(KconfigCheck('self_protection', 'clipos', 'INTEL_IOMMU', 'y'), - iommu_support_is_set)] - - # 'self_protection', 'my' - l += [OR(KconfigCheck('self_protection', 'my', 'RESET_ATTACK_MITIGATION', 'y'), - efi_not_set)] # needs userspace support (systemd) - if arch == 'X86_64': - l += [KconfigCheck('self_protection', 'my', 'SLS', 'y')] # vs CVE-2021-26341 in Straight-Line-Speculation - l += [AND(KconfigCheck('self_protection', 'my', 'AMD_IOMMU_V2', 'y'), - iommu_support_is_set)] - if arch == 'ARM64': - l += [KconfigCheck('self_protection', 'my', 'SHADOW_CALL_STACK', 'y')] # depends on clang, maybe it's alternative to STACKPROTECTOR_STRONG - l += [KconfigCheck('self_protection', 'my', 'KASAN_HW_TAGS', 'y')] - cfi_clang_is_set = KconfigCheck('self_protection', 'my', 'CFI_CLANG', 'y') - l += [cfi_clang_is_set] - l += [AND(KconfigCheck('self_protection', 'my', 'CFI_PERMISSIVE', 'is not set'), - cfi_clang_is_set)] # 'security_policy' if arch in ('X86_64', 'ARM64', 'X86_32'): @@ -493,16 +524,14 @@ def add_kconfig_checks(l, arch): if arch == 'ARM': l += [KconfigCheck('security_policy', 'kspp', 'SECURITY', 'y')] # and choose your favourite LSM l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_YAMA', 'y')] + l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_LANDLOCK', 'y')] l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_SELINUX_DISABLE', 'is not set')] - l += [KconfigCheck('security_policy', 'clipos', 'SECURITY_LOCKDOWN_LSM', 'y')] - l += [KconfigCheck('security_policy', 'clipos', 'SECURITY_LOCKDOWN_LSM_EARLY', 'y')] - l += [KconfigCheck('security_policy', 'clipos', 'LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY', 'y')] - l += [KconfigCheck('security_policy', 'my', 'SECURITY_WRITABLE_HOOKS', 'is not set')] # refers to SECURITY_SELINUX_DISABLE - l += [KconfigCheck('security_policy', 'my', 'SECURITY_SAFESETID', 'y')] - loadpin_is_set = KconfigCheck('security_policy', 'my', 'SECURITY_LOADPIN', 'y') - l += [loadpin_is_set] # needs userspace support - l += [AND(KconfigCheck('security_policy', 'my', 'SECURITY_LOADPIN_ENFORCE', 'y'), - loadpin_is_set)] + l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_SELINUX_BOOTPARAM', 'is not set')] + l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_SELINUX_DEVELOP', 'is not set')] + l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_LOCKDOWN_LSM', 'y')] + l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_LOCKDOWN_LSM_EARLY', 'y')] + l += [KconfigCheck('security_policy', 'kspp', 'LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY', 'y')] + l += [KconfigCheck('security_policy', 'kspp', 'SECURITY_WRITABLE_HOOKS', 'is not set')] # refers to SECURITY_SELINUX_DISABLE # 'cut_attack_surface', 'defconfig' l += [OR(KconfigCheck('cut_attack_surface', 'defconfig', 'BPF_UNPRIV_DEFAULT_OFF', 'y'), @@ -534,6 +563,8 @@ def add_kconfig_checks(l, arch): l += [devmem_not_set] l += [OR(KconfigCheck('cut_attack_surface', 'kspp', 'IO_STRICT_DEVMEM', 'y'), devmem_not_set)] # refers to LOCKDOWN + l += [AND(KconfigCheck('cut_attack_surface', 'kspp', 'LDISC_AUTOLOAD', 'is not set'), + KconfigCheck('cut_attack_surface', 'kspp', 'LDISC_AUTOLOAD'))] # option presence check if arch == 'ARM': l += [OR(KconfigCheck('cut_attack_surface', 'kspp', 'STRICT_DEVMEM', 'y'), devmem_not_set)] # refers to LOCKDOWN @@ -604,8 +635,7 @@ def add_kconfig_checks(l, arch): l += [KconfigCheck('cut_attack_surface', 'clipos', 'X86_IOPL_IOPERM', 'is not set')] # refers to LOCKDOWN l += [KconfigCheck('cut_attack_surface', 'clipos', 'ACPI_TABLE_UPGRADE', 'is not set')] # refers to LOCKDOWN l += [KconfigCheck('cut_attack_surface', 'clipos', 'EFI_CUSTOM_SSDT_OVERLAYS', 'is not set')] - l += [AND(KconfigCheck('cut_attack_surface', 'clipos', 'LDISC_AUTOLOAD', 'is not set'), - KconfigCheck('cut_attack_surface', 'clipos', 'LDISC_AUTOLOAD'))] # option presence check + l += [KconfigCheck('cut_attack_surface', 'clipos', 'COREDUMP', 'is not set')] # cut userspace attack surface if arch in ('X86_64', 'X86_32'): l += [KconfigCheck('cut_attack_surface', 'clipos', 'X86_INTEL_TSX_MODE_OFF', 'y')] # tsx=off @@ -646,14 +676,39 @@ def add_kconfig_checks(l, arch): def add_cmdline_checks(l, arch): # Calling the CmdlineCheck class constructor: # CmdlineCheck(reason, decision, name, expected) - # Don't add CmdlineChecks in add_kconfig_checks() to avoid wrong results - # when the tool doesn't check the cmdline. + # + # [!] Don't add CmdlineChecks in add_kconfig_checks() to avoid wrong results + # when the tool doesn't check the cmdline. + # + # [!] Make sure that values of the options in CmdlineChecks need normalization. + # For more info see normalize_cmdline_options(). + # + # A common pattern for checking the 'param_x' cmdline parameter + # that __overrides__ the 'PARAM_X_DEFAULT' kconfig option: + # l += [OR(CmdlineCheck(reason, decision, 'param_x', '1'), + # AND(KconfigCheck(reason, decision, 'PARAM_X_DEFAULT_ON', 'y'), + # CmdlineCheck(reason, decision, 'param_x, 'is not set')))] + # + # Here we don't check the kconfig options or minimal kernel version + # required for the cmdline parameters. That would make the checks + # very complex and not give a 100% guarantee anyway. + # 'self_protection', 'defconfig' + l += [CmdlineCheck('self_protection', 'defconfig', 'nosmep', 'is not set')] + l += [CmdlineCheck('self_protection', 'defconfig', 'nosmap', 'is not set')] + l += [CmdlineCheck('self_protection', 'defconfig', 'nokaslr', 'is not set')] + l += [CmdlineCheck('self_protection', 'defconfig', 'nopti', 'is not set')] + l += [CmdlineCheck('self_protection', 'defconfig', 'nospectre_v1', 'is not set')] + l += [CmdlineCheck('self_protection', 'defconfig', 'nospectre_v2', 'is not set')] if arch == 'ARM64': l += [OR(CmdlineCheck('self_protection', 'defconfig', 'rodata', 'full'), AND(KconfigCheck('self_protection', 'defconfig', 'RODATA_FULL_DEFAULT_ENABLED', 'y'), CmdlineCheck('self_protection', 'defconfig', 'rodata', 'is not set')))] + else: + l += [OR(CmdlineCheck('self_protection', 'defconfig', 'rodata', '1'), + CmdlineCheck('self_protection', 'defconfig', 'rodata', 'is not set'))] + # 'self_protection', 'kspp' 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')))] @@ -672,22 +727,38 @@ def add_cmdline_checks(l, arch): l += [OR(CmdlineCheck('self_protection', 'kspp', 'iommu.passthrough', '0'), AND(KconfigCheck('self_protection', 'kspp', 'IOMMU_DEFAULT_PASSTHROUGH', 'is not set'), CmdlineCheck('self_protection', 'kspp', 'iommu.passthrough', 'is not set')))] + # The cmdline checks compatible with the kconfig recommendations of the KSPP project... + l += [OR(CmdlineCheck('self_protection', 'kspp', 'hardened_usercopy', '1'), + AND(KconfigCheck('self_protection', 'kspp', 'HARDENED_USERCOPY', 'y'), + CmdlineCheck('self_protection', 'kspp', 'hardened_usercopy', 'is not set')))] l += [OR(CmdlineCheck('self_protection', 'kspp', 'slab_common.usercopy_fallback', '0'), AND(KconfigCheck('self_protection', 'kspp', 'HARDENED_USERCOPY_FALLBACK', 'is not set'), CmdlineCheck('self_protection', 'kspp', 'slab_common.usercopy_fallback', 'is not set')))] + # ... the end if arch in ('X86_64', 'ARM64', 'X86_32'): l += [OR(CmdlineCheck('self_protection', 'kspp', 'randomize_kstack_offset', '1'), AND(KconfigCheck('self_protection', 'kspp', 'RANDOMIZE_KSTACK_OFFSET_DEFAULT', 'y'), CmdlineCheck('self_protection', 'kspp', 'randomize_kstack_offset', 'is not set')))] if arch in ('X86_64', 'X86_32'): - l += [CmdlineCheck('self_protection', 'kspp', 'pti', 'on')] + l += [AND(CmdlineCheck('self_protection', 'kspp', 'pti', 'on'), + CmdlineCheck('self_protection', 'defconfig', 'nopti', 'is not set'))] + # 'self_protection', 'clipos' + l += [CmdlineCheck('self_protection', 'clipos', 'page_alloc.shuffle', '1')] + if arch in ('X86_64', 'X86_32'): + l += [AND(CmdlineCheck('self_protection', 'clipos', 'spectre_v2', 'on'), + CmdlineCheck('self_protection', 'defconfig', 'nospectre_v2', 'is not set'))] + + # 'cut_attack_surface', 'kspp' if arch == 'X86_64': l += [OR(CmdlineCheck('cut_attack_surface', 'kspp', 'vsyscall', 'none'), AND(KconfigCheck('cut_attack_surface', 'kspp', 'LEGACY_VSYSCALL_NONE', 'y'), CmdlineCheck('cut_attack_surface', 'kspp', 'vsyscall', 'is not set')))] - # TODO: add other + # 'cut_attack_surface', 'grsec' + # The cmdline checks compatible with the kconfig options disabled by grsecurity... + l += [OR(CmdlineCheck('cut_attack_surface', 'grsec', 'debugfs', 'off'), + KconfigCheck('cut_attack_surface', 'grsec', 'DEBUG_FS', 'is not set'))] # ... the end def print_unknown_options(checklist, parsed_options): @@ -830,6 +901,29 @@ def parse_kconfig_file(parsed_options, fname): parsed_options[option] = value +def normalize_cmdline_options(option, value): + # Don't normalize the cmdline option values if + # the Linux kernel doesn't use kstrtobool() for them + if option == 'pti': + # See pti_check_boottime_disable() in linux/arch/x86/mm/pti.c + return value + if option == 'spectre_v2': + # See spectre_v2_parse_cmdline() in linux/arch/x86/kernel/cpu/bugs.c + return value + if option == 'debugfs': + # See debugfs_kernel() in fs/debugfs/inode.c + return value + + # Implement a limited part of the kstrtobool() logic + if value in ('1', 'on', 'On', 'ON', 'y', 'Y', 'yes', 'Yes', 'YES'): + return '1' + if value in ('0', 'off', 'Off', 'OFF', 'n', 'N', 'no', 'No', 'NO'): + return '0' + + # Preserve unique values + return value + + def parse_cmdline_file(parsed_options, fname): with open(fname, 'r') as f: line = f.readline() @@ -845,6 +939,7 @@ def parse_cmdline_file(parsed_options, fname): else: name = opt value = '' # '' is not None + value = normalize_cmdline_options(name, value) parsed_options[name] = value @@ -892,12 +987,19 @@ def main(): if mode != 'json': print('[+] Detected architecture: {}'.format(arch)) - kernel_version, msg = detect_version(args.config) + 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])) + compiler, msg = detect_compiler(args.config) + if mode != 'json': + if compiler: + print('[+] Detected compiler: {}'.format(compiler)) + else: + print('[-] Can\'t detect the compiler: {}'.format(msg)) + # add relevant kconfig checks to the checklist add_kconfig_checks(config_checklist, arch)