Linux Kernel Runtime Guard reaches its 1.0 release
The Linux Kernel Runtime Guard (LKRG) is a out-of-tree loadable kernel module that attempts to detect and report violations of the kernel's internal invariants, such as might be caused by an in-progress security exploit or a rootkit. LKRG has been experimental since its initial release in 2018. In September 2025, the project announced the 1.0 version. With the promises of stability that version brings, users might want more information to decide whether to include it in their kernel.
Security through diversity
LKRG's mission is mildly hopeless — if an attacker has already compromised a running kernel, then there is no theoretical reason that the attacker could not also recognize and block or subvert the LKRG kernel module. In practice, however, an attacker would need to actually know to do that, and do it quickly enough to escape the periodic sweeps that LKRG performs. Using LKRG, therefore, raises the bar for attacks on the Linux kernel.
The project's documentation calls this "security through diversity
" — if
attackers haven't specifically addressed LKRG, running it can help spot attacks
that would otherwise pass unnoticed. Of course, there is a cost to using LKRG as
well, which makes the choice of whether to use it a tradeoff.
Running it
LKRG is packaged for Arch, Gentoo, NixOS, and Rocky Linux. On other systems, it can be built from source as a DKMS module. Once loaded, it provides a number of sysctl settings to control exactly what it should check for. LKRG supports two general categories of checks: those on the global state of the kernel and those on the state of a process.
The kernel has a lot of global state; right now, LKRG specifically checks a small but important subset of it. Specifically, it verifies whether code and read-only data within the kernel (including loaded modules) has the right permissions in the page table, whether the code or data has been modified (including whether the global SELinux settings have been tampered with), and some CPU-specific state registers. These last include the Supervisor Mode Execution Protection (SMEP) and Supervisor Mode Access Prevention (SMAP) settings, which prevent kernel code from accessing user-space memory. SMEP prevents the CPU from executing user-space code while in the kernel, and SMAP prevents other accesses. That protection must be disabled when the kernel is deliberately copying data to or from user-space memory, but the rest of the time it prevents an exploit in the kernel from making use of data stored in user space.
The SMEP and SMAP settings are trivial to check, but ensuring that the kernel's data has not been tampered with is more involved. When it first loads, LKRG takes a hash of all the data that should not change and stores this hash in its own memory. Then the data can be periodically re-hashed and checked against the stored hash. Some parts of the kernel can't be covered by the hash, because they do change during normal operation, but can still be checked for consistency against other parts of the kernel. For example, LKRG scans kernel memory to find loaded modules, and compares this to the kernel's own list of loaded modules. If a module is present but not in the list, it is probably trying to hide, and therefore likely to be nefarious. Each kernel module also gets its own hash calculated on first load and then checked periodically.
Kprobes also pose problems: they are dynamically inserted into otherwise-unchanging kernel code. LKRG can't ensure that these changes are correct using a hash, but it can check that the changes only show up when kprobes are actually enabled, and that the "enabled" setting is what it's expected to be.
By default, LKRG checks these global properties at 15-second intervals, but it can be configured to do so more or less frequently, or only when asked to do so. If it does find something suspicious, it can print a log message, restore the previous SELinux "enforcing" setting and SMEP/SMAP state, or panic the kernel.
The other set of checks it runs are per-process. These are also run periodically, but can additionally run just before any process attempts to do some privileged operation. LKRG verifies that the process has the credentials for the operation that it is about to perform, and that the stack pointer (and optionally the frames on the stack) make sense.
Each of these checks can be configured individually, but it is also possible to use LKRG "profiles". It offers several pre-configured levels of detection and enforcement. These range from "detect things only when requested" to "double-check everything whenever possible" (set via the lkrg.profile_validate sysctl) and from "when a violation is detected, log it" to "panic the kernel" (set via the lkrg.profile_enforce sysctl). The default is a reasonable middle ground: check kernel integrity without too much performance overhead, log for suspicious things, and crash the kernel when there's a definite problem.
Is it worth it?
On the one hand,
LKRG comes with some admitted downsides: LKRG's default enforcement level is incompatible
with running virtual machines VirtualBox; it works fine from within a virtual machine, but
causes spurious alarms on the host when the guest CPU has a state that doesn't
match the host. [An LKRG maintainer wrote in to point out that the problem exists only on VirtualBox, and that KVM works in the default configuration.] That's not a fundamental limitation of LKRG — it could be extended to
track enters and exits from virtual machines, and adjust its expectations
accordingly — but it does make it hard to recommend LKRG by default, given how
many workloads involve some amount of virtualization.
There is also the matter of performance. A
comparison
of the Phoronix benchmark suite from 2020 with and without LKRG suggests that it
introduced a performance overhead of about 4.4%. Since then, the LKRG developers
have
gotten this down to around 2.5%, but the error in the measurements makes
it hard to be certain of the exact performance penalty, especially since it may
be workload-dependent. There is also the fact that, as an
out-of-tree module, it does not always support new kernel versions immediately.
At the time of writing, it has been tested up through 6.17.0. [The project uses continuous integration testing, and keeps up to date with in-development kernels, but has somewhat infrequent releases.] The
project's
README
suggests that the best use for LKRG would be systems that cannot always update
to the latest kernel in response to disclosed vulnerabilities, for whatever
reason.
Many users would welcome a small slowdown in the name of security, but there are easier ways to obtain some of the same benefits. For example, the kernel's built-in support for control flow integrity provides much the same benefit as LKRG's stack checking, through well-supported compiler and CPU features. The other things that it validates are less easily replaced, but the kernel has a long history of adopting new built-in ways to protect page-tables. There is always the possibility of bugs in the implementations of these ideas, but it's hard to judge whether LKRG would detect exploitation of those bugs reliably enough to be worthwhile.
On the other hand, the Singularity rootkit has plans to work around LKRG. The Metasploit framework has special code to deal with it and disable exploits that it would detect. It's hard to argue that LKRG is not worth using when it is demonstrably making attackers' lives harder.
Ultimately, LKRG has a relatively niche use case: when the performance overhead
is acceptable, the computer's workload doesn't involve virtualization VirtualBox, and the
system doesn't run bleeding-edge kernels [or the user is happy to run pre-release versions of LKRG], LKRG can provide defense in depth.
Perhaps LKRG's data-validating features will one day make their way into the
kernel proper; that does not currently seem to be a priority for the developers.
Until LKRG's ideas do make their way into the kernel, users will have to make a
judgment call about whether LKRG is right for their use case.
