Lock Down Linux: Enforcing File Integrity at the Kernel with IMA/EVM

Your server runs a critical workload. Someone compromises it — quietly, surgically. They replace a binary, patch a shared library, or swap out a config file. A tripwire tool running in userspace might catch it… or might not, because the attacker already has the privileges to silence it.

This is the problem IMA and EVM exist to solve. They operate at the kernel level, below the reach of most attackers, and they hook into every file open, exec, and mmap call the system makes. By the time a malicious binary gets a chance to run, the kernel has already checked whether it’s supposed to be there.

This guide is for anyone running Linux on hardware they need to actually trust — servers, edge nodes, workstations with sensitive data. We’ll go from zero to a working IMA/EVM setup, including the parts the documentation glosses over.


What IMA and EVM Actually Are

IMA (Integrity Measurement Architecture) was merged into the mainline kernel in 2.6.30. It does two things:

  1. Measurement — before a file is executed or read, the kernel hashes it and records that hash in a tamper-evident log (optionally sealed inside a TPM). This is the foundation of remote attestation: an external party can verify what your system loaded since boot.

  2. Appraisal — the kernel compares the current file hash against a known-good hash stored in the file’s extended attributes (security.ima). If they don’t match, access is denied.

EVM (Extended Verification Module) protects the metadata that IMA depends on. File hashes live in xattrs — but xattrs themselves can be tampered with offline if someone pulls the disk and mounts it on another machine. EVM uses an HMAC (keyed with a kernel keyring secret) or a digital signature to protect those xattrs. Touch the security.ima xattr without the EVM key, and EVM rejects the file.

Together, IMA appraisal + EVM gives you runtime enforcement: files that weren’t explicitly blessed can’t run, and files that were blessed can’t be silently modified.

The official kernel documentation lives here: https://www.kernel.org/doc/html/latest/security/IMA-templates.html


Prerequisites

A few things need to be true before any of this is useful:

  • Kernel 5.x+ — most features work from 4.x, but policy syntax and signature support are much more complete on 5.x. Use uname -r to check.
  • Filesystem with xattr support — ext4, xfs, and btrfs all work. tmpfs is limited. NFS requires special configuration and isn’t worth the headache until you have local working first.
  • CONFIG_IMA=y and CONFIG_EVM=y — check with grep -E "CONFIG_IMA|CONFIG_EVM" /boot/config-$(uname -r). Major distros (RHEL, Fedora, Ubuntu, Debian) ship with this enabled.
  • TPM (optional but strongly recommended) for anything beyond a lab setup. Without a TPM, the measurement log has no hardware root of trust.

Check all at once:

# Verify IMA/EVM kernel support
grep -E "CONFIG_(IMA|EVM)" /boot/config-$(uname -r)

# Check if IMA is active
ls /sys/kernel/security/ima/

# Check TPM presence
ls /dev/tpm* 2>/dev/null && echo "TPM found" || echo "No TPM"

If /sys/kernel/security/ima/ is empty or missing, IMA isn’t running. You’ll need to add ima=on to your kernel command line.


Step 1: Enable IMA Measurement (Read-Only First)

Never go straight to enforcement. Enable measurement mode first, let it run for a few days, and build a baseline. Jumping straight to appraisal mode on a live system is how you lock yourself out.

Edit your bootloader config. On a GRUB system:

# /etc/default/grub — add to GRUB_CMDLINE_LINUX
GRUB_CMDLINE_LINUX="... ima=on ima_template=ima-ng ima_hash=sha256"

Then regenerate GRUB config:

# Debian/Ubuntu
sudo update-grub

# RHEL/Fedora/Rocky
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

ima-ng is the modern template that stores both the hash algorithm name and the hash itself. The old ima template uses a fixed SHA1, which you don’t want in 2026.

Reboot and verify:

# Should show a growing list of measured files
sudo cat /sys/kernel/security/ima/ascii_runtime_measurements | head -20

Each line: PCR index, SHA1 of the log entry, template name, file hash, and filename. If you see output, IMA is measuring.


Step 2: Write a Real IMA Policy

The default IMA policy measures everything. That’s noisy and not useful for appraisal. You need a targeted policy that focuses on what matters: executables, kernel modules, shared libraries, and critical config.

IMA policies are loaded from a file or compiled into the kernel. Loading from a file at boot is the flexible approach.

Create /etc/ima/ima-policy:

# IMA Policy — measurement and appraisal
# Order matters: rules are evaluated top-to-bottom, first match wins

# Don't measure pseudo-filesystems — noise with zero value
dont_measure fsmagic=0x9fa0        # procfs
dont_measure fsmagic=0x62656572    # sysfs
dont_measure fsmagic=0x64626720    # debugfs
dont_measure fsmagic=0x1021994     # tmpfs
dont_measure fsmagic=0xadf5        # adfs (rare, but avoids log spam)
dont_measure fsmagic=0x6e736673    # nsfs

# Skip devtmpfs — block devices aren't files
dont_measure fsmagic=0x1373

# Measure all executables
measure func=BPRM_CHECK

# Measure kernel modules
measure func=MODULE_CHECK

# Measure firmware
measure func=FIRMWARE_CHECK

# Measure files opened by root (catches admin activity)
measure func=FILE_CHECK uid=0 mask=^MAY_READ

# Measure shared libraries
measure func=MMAP_CHECK

# Appraisal rules (once you're ready to enforce)
# appraise func=BPRM_CHECK appraise_type=imasig
# appraise func=MODULE_CHECK appraise_type=imasig

Load this policy at boot via a systemd unit:

# /etc/systemd/system/ima-load-policy.service
[Unit]
Description=Load IMA policy
DefaultDependencies=no
Before=sysinit.target
ConditionPathExists=/etc/ima/ima-policy

[Service]
Type=oneshot
ExecStart=/bin/sh -c "cat /etc/ima/ima-policy > /sys/kernel/security/ima/policy"
RemainAfterExit=yes

[Install]
WantedBy=sysinit.target
sudo systemctl enable --now ima-load-policy.service

Gotcha: the policy file is write-once per boot. Once you’ve written to /sys/kernel/security/ima/policy, you can’t change it until the next reboot. Test your policy syntax carefully before committing. A malformed line causes the entire policy load to fail silently (or with a cryptic kernel message).


Step 3: Label Files with IMA Hashes

Before appraisal can work, every file that appraisal rules cover must have a security.ima xattr containing its hash. This is the labeling step.

The ima_appraise=fix kernel parameter tells the kernel to generate and write hashes on access instead of verifying them. This is your bootstrapping mode.

# Temporarily boot with fix mode — add to kernel cmdline
# ima_appraise=fix

Then force a hash update across the system:

# This walks the filesystem and reads every file, triggering IMA to write hashes
# Run as root — this takes a while on a loaded system
sudo find / -fstype ext4 -type f -uid 0 \
  \( -perm /0111 -o -name "*.so" -o -name "*.so.*" \) \
  -exec dd if={} of=/dev/null count=0 status=none \; 2>/dev/null

For a more targeted approach, evmctl (from the ima-evm-utils package) handles labeling properly:

# Install ima-evm-utils
sudo apt install ima-evm-utils    # Debian/Ubuntu
sudo dnf install ima-evm-utils    # Fedora/RHEL

# Hash a specific directory tree
sudo evmctl ima_hash -r /usr/bin/ --key /etc/keys/privkey_evm.pem

# Verify labels on a file
sudo evmctl ima_verify /usr/bin/ssh

Gotcha: if you have NFS mounts, network drives, or FUSE filesystems, exclude them from appraisal rules explicitly. IMA trying to hash a network file over a slow link at boot is a great way to add 40 seconds to your startup time.


Step 4: Set Up EVM

Without EVM, an attacker who gets physical access (or disk access) can copy xattrs from a blessed file onto a malicious one. EVM seals the xattrs.

EVM uses the kernel keyring. You need to generate a key, load it into the kernel’s trusted keyring at boot, and then enable EVM.

# Generate the EVM HMAC key
sudo mkdir -p /etc/keys
sudo openssl rand -hex 32 | sudo tee /etc/keys/evm-hmac.key
sudo chmod 400 /etc/keys/evm-hmac.key

Load it at boot via a systemd service that runs before IMA policy load:

# Create EVM keyring entry
sudo evmctl import --x509 /etc/keys/x509_evm.der $(keyctl show @s | grep "\.ima" | awk '{print $1}')

For HMAC-based EVM (simpler, no public-key crypto):

# Load HMAC key into the kernel keyring
sudo keyctl add encrypted evm-key "new default user:kmk 64" @u

# Or from a pre-existing key file
sudo keyctl padd encrypted evm-key @u < /etc/keys/evm-hmac.key

Add EVM enablement to your kernel cmdline:

evm=fix        # bootstrapping mode — writes EVM HMACs
# Later, switch to:
evm=enforce    # enforcement mode

Label existing files with EVM metadata after IMA hashing:

sudo evmctl sign --imahash --key /etc/keys/privkey_evm.pem -r /usr/

Gotcha: EVM with HMAC ties your files to the specific machine’s key. If you clone a disk or restore a backup, EVM will reject every file until you re-label with the new machine’s key. Plan your backup/restore procedures around this. IMA with signatures (RSA/EC keys) scales better across fleets.


Step 5: Enable Appraisal (The Point of No Return)

Only do this after you’ve:

  • Run in measurement mode for at least a few days
  • Verified all critical files have security.ima xattrs (getfattr -n security.ima /usr/bin/bash)
  • Tested EVM labeling
  • Have a recovery plan (live CD, secondary boot entry without appraisal)

Switch the kernel cmdline from ima_appraise=fix to ima_appraise=enforce. Uncomment the appraisal rules in your IMA policy:

# Enable these in /etc/ima/ima-policy
appraise func=BPRM_CHECK appraise_type=imasig
appraise func=MODULE_CHECK appraise_type=imasig
appraise func=FIRMWARE_CHECK

Reboot. If a file fails appraisal, you’ll see it in the kernel log:

sudo dmesg | grep -i "ima:"
# Example:
# ima: appraise failed: /usr/bin/wget (hash mismatch)

After any package update, you need to re-label the changed files. The cleanest way is a post-install hook:

# /etc/apt/apt.conf.d/99-ima-relabel (Debian/Ubuntu)
DPkg::Post-Invoke {"evmctl ima_hash -r /usr/ --key /etc/keys/privkey_evm.pem 2>/dev/null || true";};

TPM Integration

If you have a TPM, extend the trust chain by sealing the IMA measurement log into PCR registers. This enables remote attestation — an external service can challenge your system and verify cryptographically that specific software loaded at boot.

# Check which PCRs IMA uses (default: PCR 10)
sudo cat /sys/kernel/security/ima/ascii_runtime_measurements | head -1
# First column is the PCR index

# Read current PCR values
sudo tpm2_pcrread sha256:10

For remote attestation, you need a TPM attestation service. Keylime is the reference implementation for Linux: https://github.com/keylime/keylime

# Quick keylime install for testing
sudo dnf install keylime   # Fedora
sudo apt install keylime   # Ubuntu 22.04+

# The verifier and registrar run on a separate machine
# The agent runs on the machine being attested
sudo systemctl enable --now keylime_agent

Keylime checks that:

  • The boot measured the expected bootloader, kernel, and initrd
  • IMA’s runtime measurements match your allowlist
  • No unexpected binaries ran since boot

This is what turns IMA from "log what happened" to "alert when something wrong happened, remotely."


Gotchas and Production Pitfalls

The package manager problem. Every time apt or dnf updates a binary, its hash changes. IMA appraisal will block the new binary until it’s re-labeled. Automate re-labeling in post-install hooks, or run updates in a maintenance window where you temporarily drop to ima_appraise=log mode.

Containers and namespaces. Container runtimes (Docker, containerd) unpack image layers at runtime. Those files don’t have IMA xattrs by default. You either need to pre-label base images, use IMA policy rules to exclude the container storage paths, or use namespace-aware IMA policies (kernel 5.13+, still evolving). Don’t assume your container workloads will survive IMA appraisal without testing.

Memory-mapped files. IMA measures at mmap() time for MMAP_CHECK. Shared memory segments and anonymous mappings aren’t file-backed and aren’t measured. IMA doesn’t protect everything — it protects file-backed executable content.

Log growth. The IMA measurement log lives in memory and grows indefinitely. On a busy system executing many unique files, it can consume hundreds of MB of RAM. Monitor with wc -l /sys/kernel/security/ima/ascii_runtime_measurements. The TPM helps here — it accumulates measurements into PCRs, but you still need the log for verification.

EVM and overlayfs. OverlayFS (used by container runtimes) has complicated interactions with xattrs. security.ima xattrs may not propagate correctly through overlay layers. Test thoroughly before relying on this combination.

SELinux/AppArmor co-existence. IMA plays fine alongside SELinux and AppArmor — they’re complementary. SELinux controls access based on policy labels; IMA verifies the file content hasn’t changed. Both can be active. Just make sure your SELinux policy allows the IMA kernel threads to access files they need to hash.


Production-Ready Deployment Strategy

Don’t enable this on production systems in a single change. The safe rollout:

  1. Lab first. Reproduce your exact production stack in a VM. Run measurement mode for a week. Audit the log.

  2. Canary node. Pick one production node. Enable measurement + logging only. Watch for hash mismatches in dmesg without enforcing.

  3. Build a file allowlist. Use the measurement log to enumerate exactly which files your system runs. This becomes your known-good baseline.

  4. Label with signatures, not HMACs. HMAC-based EVM is machine-specific. For a fleet, use RSA/EC signed IMA labels (evmctl sign) with a central signing key stored in an HSM. Labeling happens at package build time, not at deployment.

  5. Automate re-labeling. Wire evmctl into your package manager hooks and your CI/CD pipeline. Any artifact that ships to production gets signed before it leaves the build system.

  6. Separate signing from deployment. The private signing key should never be on a production host. Sign in the build pipeline, ship signed artifacts, verify on the host.

  7. Keep a recovery entry. Always have a bootloader entry without IMA enforcement. If something goes wrong — and it will, at least once — you need a way back that doesn’t involve a live CD.

# Verification script — run periodically to catch drift
#!/bin/bash
FAILURES=0
while IFS= read -r -d '' file; do
    evmctl ima_verify "$file" 2>/dev/null || {
        echo "FAIL: $file"
        FAILURES=$((FAILURES + 1))
    }
done < <(find /usr/bin /usr/sbin /usr/lib -type f -print0)

echo "Total failures: $FAILURES"
exit $FAILURES

IMA and EVM are not plug-and-play. They require careful bootstrapping, ongoing maintenance around package updates, and a solid understanding of your system’s file access patterns. But once they’re working, you have something that most security tools can’t offer: kernel-enforced, cryptographically verified assurance that the code running on your machine is the code you put there. No rootkit running in userspace can lie to the kernel about what a file contains.

For a fleet, combine this with Keylime for remote attestation, a proper HSM for your signing key, and automated re-labeling in your CI/CD pipeline. That’s the architecture that serious compliance environments (FIPS, CC EAL, government workloads) are built around — and it’s all open-source, running on commodity hardware.

👁 Views: 202,778 · Unique visitors: 57,387