|
|
Log in / Subscribe / Register

A "runtime guard" for the kernel

By Jake Edge
March 21, 2018

While updating kernels frequently is generally considered a security best practice, there are many installations that are unable to do so for a variety of reasons. That means running with some number of known vulnerabilities (along with an unknown number of unknown vulnerabilities, of course), so some way to detect and stop exploits for those flaws may be desired. That is exactly what the Linux Kernel Runtime Guard (LKRG) is meant to do.

LKRG comes out of the Openwall project that is perhaps best known for its security-enhanced Linux distribution. Alexander Peslyak, or "Solar Designer", who is Openwall's founder and leader is prominent in security circles as well. He announced LKRG at the end of January as "our most controversial project ever". The 0.0 release that was announced was "quite sloppy", Peslyak said in a LKRG 0.1 release announcement on February 9; principal developer Adam "pi3" Zabrocki cleaned things up and added some new features based on ten days of feedback.

At its core, LKRG is a loadable kernel module that tries to detect changes to the running kernel that would indicate that some kind of exploit is being used against it. Beyond that, it checks processes running on the system to look for unauthorized modifications to credentials of various sorts in order to prevent those changes from granting additional access—something that exploits will try to do. The initial LKRG announcement describes the goals this way:

While LKRG defeats many pre-existing exploits of Linux kernel vulnerabilities, and will likely defeat many future exploits (including of yet unknown vulnerabilities) that do not specifically attempt to bypass LKRG, it is bypassable by design (albeit sometimes at the expense of more complicated and/or less reliable exploits). Thus, it can be said that LKRG provides security through diversity, much like running an uncommon OS kernel would, yet without the usability drawbacks of actually running an uncommon OS.

As noted, LKRG can be bypassed, so it is really only another line of defense in a defense-in-depth strategy, rather than a panacea of any sort. In addition, it currently is in an experimental stage (as the version numbers might indicate), so it only logs any kernel modifications that it finds. The kernel is replete with various types of self-modifying code, from tracepoints and other debugging features to optimizations of various sorts, so protecting the integrity of the running kernel is not a straightforward task.

To track the running kernel, LKRG creates a database of hashes of various types of information about the system and the kernel running on it. It tracks the CPUs available and active in the system, along with the location and contents of their interrupt descriptor tables (IDTs) and model-specific registers (MSRs). Since the kernel may modify itself due to changes in the number of CPUs hotplugged into (or unplugged from) the system, LKRG must be ready to recalculate some of its hashes based on those events.

For the kernel, LKRG tracks the hashes of the .text section, the .rodata section (which should never change), and the exception table. Beyond that, each loaded module is tracked, including information like its struct module pointer, name, size and hash of its .text section, and some other module-specific information. The details of that are described on the LKRG wiki.

In order to detect modifications, the values stored need to be validated regularly. This is done via a number of mechanisms, starting with a timer that checks at regular intervals; the period can be set via the sysctl interface. It also runs the check whenever module-loading or CPU-hotplug activity is detected and can be triggered manually by way of another sysctl. Other events in the system (e.g. CPU idle, network activity, USB change, etc.) will trigger the validation, though only a certain percentage of the time to reduce the performance impact. For example, CPU idle will trigger validation 0.005% of the time while a USB change will do so 50% of time.

All of that is meant to protect the integrity of the running kernel itself, but exploits often target the processes running on the system in order to elevate privileges and the like; that information lives in the kernel's read-write memory. So LKRG also tracks a whole bunch of different attributes of each process and maintains its own task list that can be used to validate the kernel's list. If the two diverge, affected processes are killed; the intent is to do so before they can take advantage of the differences.

The tracking consists of task attributes like the address of the task_struct, process name and ID, the addresses of the cred and real_cred credential structures, the various user and group IDs associated with it, SELinux settings, and "secure computing" (seccomp) settings and configuration. Various other things are tracked currently (e.g. capabilities information) but not validated.

All of that information is validated every time certain system calls (e.g. setuid(), execve()) or other events happen in the system (e.g. when permissions are checked prior to opening a file). In addition, the process-list validation is done every time the kernel validation is run. All processes are validated each time, not just the one making the system call, and any discrepancy results in killing any process that has differences.

The wiki page shows tests of LKRG to detect exploits of some known kernel vulnerabilities (e.g. CVE-2014-9322, CVE-2017-6074); both of those were detected, as were a few others. The performance impact has been measured in a rudimentary way: a system running LKRG 0.0 was about 6.5% slower building Openwall's John the Ripper password cracker. Performance optimizations have not been a focus yet, but: "We find this performance impact significant (especially for a security measure bypassable by design), and are likely to make adjustments to reduce it."

There are certain kinds of kernel vulnerabilities that LKRG cannot detect. If the exploit functions entirely in user space (perhaps by exploiting a kernel race condition like Dirty COW), it won't modify the parts of the kernel that are being tracked, thus won't trigger LKRG. The home page describes it this way:

However, it wouldn't be expected to detect exploits of CVE-2016-5195 (Dirty COW) since those directly target the userspace even if via the kernel. While in case of Dirty COW the LKRG "bypass" happened due to the nature of the bug and this being the way to exploit it, it's also a way for future exploits to bypass LKRG by similarly directly targeting userspace. It remains to be seen whether such exploits become common (unlikely unless LKRG or similar become popular?) and what (negative?) effect on their reliability this will have (for kernel vulnerabilities where directly targeting the userspace isn't essential and possibly not straightforward).

LKRG is available for x86 and x86-64 and, because it is a kernel module rather than a set of patches, it will build for a wide variety of kernel versions. It can be built for the RHEL 7 kernel, which is based on 3.10, and it will also build for the mainline (4.15). The project has a mailing list for questions and support, though it is rather quiet; there are only a few postings from January and February at this point.

It is clearly a niche project and one that may not really find many users. For some installations, it could provide another level of defense, but it means those users are probably not keeping up with their kernel updates. Given that LKRG can be bypassed and that it certainly can't detect all kinds of kernel exploits, it may provide a false sense of security. But for organizations that carefully consider the threat model for LKRG and their own needs, there is value to be found in LKRG. Whether there is enough value to sustain a project (and perhaps allow Openwall to provide a non-free "LKRG Pro" version) remains to be seen.


Index entries for this article
KernelSecurity/Kernel hardening
SecurityLinux kernel/Hardening
SecurityLinux Security Modules (LSM)


to post comments

A "runtime guard" for the kernel

Posted Mar 23, 2018 19:20 UTC (Fri) by flussence (guest, #85566) [Link]

The way this is implemented - checksum interesting bits of memory and report on those that change - heavily reminded me of those Datel video game “cheat cartridges” (poor man's debuggers) sold in the mid-90s. It's cool to see the same technique being used now to stop us being cheated :-)

A "runtime guard" for the kernel

Posted Mar 24, 2018 1:39 UTC (Sat) by pi3 (guest, #123274) [Link]

At the beginning I'd like to thank the author for taking his time to analyze LKRG and write a fair review.
The only comment I'd like to add is that performance impact in the latest LKRG version (we're on the edge of releasing v0.2) has been greatly improved. We have introduced a new sysctl to control whether LKRG performs code integrity checks on random events (or only at regular intervals). This can be translated to the following performance impact scenarios:
-> Average cost of running a fully enabled LKRG => 2.5%
-> Average cost of running LKRG without the code integrity checks on random events (disabled with the new sysctl) => 0.7%


Copyright © 2018, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds