Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow redefining rules and expanding rule sets #50

Open
petervanvugt opened this issue Feb 20, 2021 · 5 comments
Open

Allow redefining rules and expanding rule sets #50

petervanvugt opened this issue Feb 20, 2021 · 5 comments
Labels
enhancement New feature or request

Comments

@petervanvugt
Copy link

petervanvugt commented Feb 20, 2021

I have found this tool quite helpful for quickly auditing embedded kernel configs. However, I've been finding that on embedded systems, I often have unique, application-specific security requirements:

  • Embedded SoC vendors often have drivers that haven't made it into mainline that need to be checked (e.g. special HW RNG drivers, TZ drivers, PMIC drivers)
  • The application may want to even further prioritize the correct operation of the system over performance or reliability (i.e. be willing to sacrifice battery life, CPU bandwidth, or resistance to DoS attacks to increase hardness)
  • Since the required kernel functionality is fully defined (e.g. we know we'll never need FAT filesystem support, don't want UART or kernel console driver, don't want USB gadget drivers, etc.), specify that unused drivers must be removed, lest they be leveraged by an attacker

I propose moving the config tests currently hard-coded in __init__ into a set of yaml configs that can be included by a top-level config, like this:

# Includes are optional. Recursively walk through them, each test/error will be tagged with the source yaml
# Last included definition for a CONFIG_ is used
includes:
  - kspp.yaml
  - clipos.yaml
  - my.yaml
  - soc_a.yaml
# Tests
tests: !!seq [
  # Description of test
  RANDOMIZE_BASE: {
    # Test passes if CONFIG=value
    require: value,
    # Test passes if config not found, or "is not set"
    # require: is not set,
    # Optional: only test if other config is set to something
    if_config: MODULES,
    # Optional: only test specific kernel versions
    if_kernel_ver_gt_eq: 5.9,
    if_kernel_ver_lt: 5.8,
    # Optional: only test specific architectures
    if_arch: [X86_64, ARM64, X86_32],
  },
  # Example: require CONFIG_BUG=y
  BUG: {
    require: y,
  },
]

This would enable the config requirements to be layered, similar to the way kernel defconfigs can be layered (i.e. arch | Android | SoC vendor | device). I have some free time next week to implement this if you're open to it.

@a13xp0p0v a13xp0p0v added the enhancement New feature or request label Feb 21, 2021
@a13xp0p0v
Copy link
Owner

Hello @petervanvugt,

Thanks for your initiative!

May I ask you to describe your use-case in details?
Which new requirements to kconfig-hardened-check behavior does it have?

Maybe a layered yaml that you propose is not a single solution for your use-case.

Moreover, I see that your use-case relates to this discussion: #9 (comment)
I think we can define some common solution.

Now about the syntax of check definitions.

  • Currently all checks are grouped together in kconfig_hardened_check/__init__.py.
  • The check definitions are very short.

So I can observe them altogether. That helps me to understand and maintain these checks, which is not an easy task.
That is my main rationale.

Here you propose a completely different syntax.
I think we should discuss it before we start coding.
My thoughts:

  1. Can we separate changing check definition syntax from changing kconfig-hardened-check behavior?
  2. The given syntax example doesn't cover all check types that we have. Could you please write all current checks in your new syntax? I think we need that for making the decision.

(I'm travelling till the beginning of March, excuse me for delayed replies)

Best regards,
Alexander

@petervanvugt
Copy link
Author

petervanvugt commented Feb 23, 2021

Hi @a13xp0p0v ,

My use essentially falls into three cases:

  1. My system has kconfigs not in mainline that must always be set.

For example, I might want to verify PANIC_ON_DATA_CORRUPTION from Android is enabled, as a defensive measure, because I'd rather the system immediately reboot at the first sign things are going off the rails, rather than risk being exploited by an attacker.

  1. My system has kconfigs that are in mainline, which are only in play for my hardware.

For example, I may want to verify that my chip's CONFIG_<HWVENDOR>_HWRANDOM is enabled, because I'm using it as a cryptographically secure source of enropy.

  1. My system has kconfigs that are in mainline, which many/most users want enabled, but I want disabled, because they add no benefit, and some nonzero risk.

For example, if I'm building an embedded system that uses NXP's i.MX line, I may want to verify CONFIG_SERIAL_IMX and CONFIG_SERIAL_IMX_CONSOLE are not enabled, because I want to be absolutely certain that the serial drivers and associated kernel console drivers haven't been included. Or, in a similar vein to (1), I may want to enable CONFIG_PANIC_ON_OOPS because I prioritize the correctness of my system over its availability.

[EDIT] Another, potentially stronger example I have run into recently is PROC_PAGE_MONITOR. The grsecurity patch set removes it for good reason, because access to /proc/<pid>/smaps can leak memory mapping information defeating ASLR. While there are mitigations all recent versions of the kernel to prevent insufficiently privileged processes from reading the map of a more privileged process, there have been a few race conditions and side channels that have been shown to circumvent this. So, it is reasonable that many users will want to disable this altogether. However, Android's libmeminfo needs to read this entry to compute process memory utilization, which is pretty hard to live without in some applications.

Can we serve all these use cases?

Clearly, there a few paths that could be taken here. We could add these requirements to the very compact representation in kconfig_hardened_check/__init__.py. And for (1) and (2), we could likely produce some combination of AND/OR kconfig checks (albeit sometimes non-trivial) that keeps the check from generating unnecessarily noisy output/false positives when run on configs for non-applicable hardware, or for kernels that don't fully track mainline. But this wouldn't solve for (3), unless we require the tool be specially patched for such cases, or we add runtime args that turn on each of these checks.

If we want to be able to specify additional requirements at runtime and/or override requirements at runtime, we need a way to specify alternate requirements. This is why I am proposing representing the requirements as runtime configuration, rather than code. As to how we would represent some of the more complex requirements, I am proposing we break them down into requirements that each only check one config each, optionally only checked for some combination of specific architectures/kernel versions/CONFIG_s.

We could take configs whose names changed, such as this:

282     l += [OR(OptCheck('self_protection', 'defconfig', 'STACKPROTECTOR_STRONG', 'y'),
283              OptCheck('self_protection', 'defconfig', 'CC_STACKPROTECTOR_STRONG', 'y'))]

and split them into two separate requirements, the first one for kernels >= 4.18, and the second one for kernels >= 3.14 and < 4.18.

The most complex requirement I see is this one:

307     if arch == 'ARM64':
...
310         l += [OR(OptCheck('self_protection', 'defconfig', 'HARDEN_EL2_VECTORS', 'y'),
311                  AND(OptCheck('self_protection', 'defconfig', 'RANDOMIZE_BASE', 'y'),
312                      VerCheck((5, 9))))] # HARDEN_EL2_VECTORS was included in RANDOMIZE_BASE in v5.9

which could be split into two requirements: one for RANDOMIZE_BASE on kernels >= 5.9 for ARM64, and a second check for HARDEN_EL2_VECTORS on older kernels >= 4.17 and < 5.9, also for ARM64. This would keep the requirements more readable in the long run.

What do you think?

@a13xp0p0v
Copy link
Owner

@petervanvugt thanks a lot for describing your use-cases.
I think they match with this one.
I want to make them possible.

I think kconfig-hardened-check should allow to override the default checks and append custom checks.
As a first step, we need some simple solution without changing the check description syntax.
Then we can ponder over the check description syntax.

I will experiment with that.
If you create any prototype, please share!

@egberts
Copy link

egberts commented Aug 31, 2021

other use case is prevent leakage of kernel pointers to log file, /proc directory files, or terminal output.

Which is just a bunch of debugs and dmesg turned off.

another one is the one provided by Whonix.org (a KSPP variant) which is more rigorous form of kernel security.

Another one is for Spectre, et. al., mitigation and that has a bunch of config s as well.

@a13xp0p0v a13xp0p0v changed the title Increase flexibility of test definitions Allow redefining rules and expanding rule sets Jul 2, 2022
@a13xp0p0v
Copy link
Owner

I implemented a part of this feature in override_expected_value().

  1. Implementation: c109072

  2. Unit-test: 7194de8

  3. Refinement of the CONFIG_ARCH_MMAP_RND_BITS check using this feature: 9bbea5b

a13xp0p0v added a commit that referenced this issue Mar 24, 2024
'my' checks look like the checks created by a user of the tool.
Let's fix that and take the responsibility :)

Refers to #50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants