Integrity Measurement Architecture
The Integrity Measurement Architecture (IMA) is the Linux kernel subsystem that answers two related questions about a file: what was loaded? (measurement) and was it the thing I expected? (appraisal). On every security-relevant file access — a binary executed, a library
mmap’d, a kernel module or firmware blob read — IMA computes a cryptographic hash of the file’s contents, records that hash in an append-only runtime measurement list, and extends it into a hardware Trusted Platform Module (TPM) Platform Configuration Register (PCR). Because a PCR can only be extended (folded forward by hashing), never set, the resulting list is tamper-evident: a remote verifier can replay the list, recompute the expected PCR value, and detect any insertion or alteration — this is the basis of remote attestation. Optionally, IMA also appraises each file against a known-good hash or digital signature stored in the file’ssecurity.imaextended attribute, refusing access if it does not match. IMA lives atsecurity/integrity/ima/and registers as a Linux Security Module (LSM) hooking file-open,execve,mmap, and kernel-read paths (perima_main.c, Linux v6.12).
This note covers IMA’s mechanism — measurement, the boot aggregate, appraisal, and TPM/PCR integration. The policy language that decides which files get measured or appraised (the func=, mask=, fsmagic=, appraise_type= rule grammar) is deep enough to live in its own note: see IMA Appraisal and Measurement Policy. The companion subsystem that protects file metadata rather than contents is Extended Verification Module.
Mental Model
The cleanest way to think about IMA is as a tamper-evident ledger backed by a one-way accumulator. Each time a watched file is touched, IMA writes one line into a ledger (the measurement list) and folds that same line’s hash into a register that physically cannot be rewound (the TPM PCR). An attacker who later edits the ledger to hide an entry cannot also rewind the register, so the register and the ledger will disagree — and a remote party who knows the correct register value catches the lie. Measurement is therefore about detection after the fact; appraisal is the stricter sibling that adds prevention up front by comparing the file’s hash to a stored reference before allowing the access at all.
flowchart TB OPEN["Process opens / execs / mmaps a file"] --> HOOK["LSM hook<br/>ima_file_check / ima_bprm_check / ima_file_mmap"] HOOK --> PM["process_measurement()"] PM --> POL{"ima_get_action():<br/>policy says measure?<br/>appraise? audit?"} POL -->|"no action"| ALLOW["access proceeds"] POL -->|"action set"| COLLECT["ima_collect_measurement()<br/>hash file contents into iint cache"] COLLECT --> MEASURE["ima_store_measurement():<br/>append entry to ima_measurements list"] MEASURE --> EXTEND["ima_pcr_extend():<br/>tpm_pcr_extend(PCR 10) = H(PCR || entry_digest)"] COLLECT --> APPRAISE{"ima_appraise_measurement():<br/>hash == security.ima xattr?"} APPRAISE -->|"match / no policy"| ALLOW APPRAISE -->|"mismatch + enforce"| DENY["return -EACCES<br/>access denied"] EXTEND --> LIST["/sys/kernel/security/ima/<br/>ascii_runtime_measurements"] LIST -.->|"replayed by"| ATTEST["remote attestation server<br/>recomputes expected PCR"]
The IMA access pipeline. What it shows: a single file access fans into two independent outcomes — a measurement (append to the list + extend the PCR, never blocking) and an appraisal (compare against the stored reference, which can block with -EACCES). The insight: measurement and appraisal are decoupled — a system can measure-only for attestation, appraise-only for enforcement, or both; and the PCR extension is what makes the otherwise-mutable list trustworthy to a remote party.
Mechanical Walk-through
The single funnel: process_measurement()
Every IMA file decision flows through one core function, process_measurement() in ima_main.c. It is reached from several LSM hooks, each tagging the access with a function identifier the policy can match on:
ima_file_check()— registered on thefile_post_openhook; tags the accessFILE_CHECK.ima_bprm_check()— on theexecvepath; runsprocess_measurement()twice, once asBPRM_CHECK(the binary) and once asCREDS_CHECK.ima_file_mmap()— when a file is mapped executable; tags itMMAP_CHECK(orMMAP_CHECK_REQPROT).ima_post_read_file()— after the kernel itself reads a file (firmware, a module, a kexec image), tagged from aread_idmaptable into values likeMODULE_CHECK,FIRMWARE_CHECK,KEXEC_KERNEL_CHECK.
Inside process_measurement() the sequence is:
- Policy decision.
ima_get_action()walks the loaded policy and returns the bitmask of actions required for this file under this function identifier — some combination of measure, appraise, and audit. If the policy matches nothing, IMA does nothing and the access proceeds untouched. (The grammar that drives this is the subject of IMA Appraisal and Measurement Policy.) - iint cache lookup. IMA keeps a per-inode integrity cache, the
ima_iint_cachestructure, fetched viaima_inode_get(). This cache is the performance keystone: hashing a file is expensive, so once a file is hashed the result is stored on the inode and theIMA_COLLECTEDflag is set. A later access checks the flag and skips re-hashing. Likewise the per-inodemeasured_pcrsbitmap andIMA_MEASUREDflag prevent re-appending an unchanged file to the list for the same PCR (the code testsiint->measured_pcrs & (0x1 << pcr)). - TOCTOU guard.
ima_rdwr_violation_check()detects a time-of-check/time-of-use race — a file open for reading while another writer holds it open for writing — and, if found, records a violation entry (discussed below) rather than a clean measurement. - Collect.
ima_collect_measurement()reads the file and computes its digest with the configured algorithm (ima_get_hash_algo()), storing the result iniint->ima_hash. - Store. If the
IMA_MEASUREaction is set,ima_store_measurement()builds a template entry and appends it to the measurement list. - Appraise. If an
IMA_APPRAISEaction is set,ima_appraise_measurement()compares the collected hash against the file’ssecurity.imaxattr and decides pass/fail.
Building the measurement list and extending the PCR
The list-and-PCR machinery is in ima_queue.c. ima_add_template_entry() does three things under the ima_extend_list_mutex, whose comment states it “protects atomicity of extending measurement list and extending the TPM PCR aggregate”:
- Deduplicate. Unless the entry is a violation or the hash table is disabled, it calls
ima_lookup_digest_entry(digest, entry->pcr). An identical, already-recorded measurement returns-EEXISTand is dropped — there is no point recording the same binary a thousand times. - Append.
ima_add_digest_entry()allocates a queue entry, appends it to the globalLIST_HEAD(ima_measurements)withlist_add_tail_rcu(), and (unlessCONFIG_IMA_DISABLE_HTABLE) also inserts it into a hash table viahlist_add_head_rcu()for the dedup lookup above. - Extend the PCR.
ima_pcr_extend(digests_arg, entry->pcr)callstpm_pcr_extend(ima_tpm_chip, pcr, digests_arg). The default PCR isCONFIG_IMA_MEASURE_PCR_IDX, conventionally PCR 10.
The critical property here is what “extend” means on a TPM. A PCR is never written; it is extended by the operation PCR_new = HASH(PCR_old || incoming_digest). Because the new value depends on the old value and the new digest, the only way to reach a given final PCR value is to feed in exactly the right sequence of digests in exactly the right order. This is a one-way accumulator: the measurement list in RAM is mutable (it’s just a kernel linked list, lost on reboot), but the PCR is a hardware register that an attacker with kernel access still cannot rewind. A remote verifier holds the list, replays every entry’s digest through the same hash-fold, and checks that the computed value equals the PCR value the TPM signs in a quote. Any tampering with the list breaks the match.
The boot aggregate — anchoring the list to the boot chain
The very first entry in every measurement list is special: boot_aggregate, created by ima_add_boot_aggregate() in ima_init.c during IMA initialization (the comment is explicit: “boot aggregate must be first entry”). Its purpose is to fold the pre-IMA boot measurements into the IMA list so the chain of trust is unbroken from firmware onward.
ima_calc_boot_aggregate() in ima_crypto.c computes “a hash over tpm registers 0-7” — these are the PCRs that firmware, the bootloader, and the kernel image populate during a measured boot. The code loops for (i = TPM_PCR0; i < TPM_PCR8; i++), reading each with ima_pcrread(i, &d) and folding it in with crypto_shash_update(). Notably, for non-SHA-1 banks the loop also extends over PCRs 8–9 (for (i = TPM_PCR8; i < TPM_PCR10; i++)), because those banks were defined to cover a wider range. The resulting digest is recorded as the boot_aggregate entry and extended into PCR 10 like any other measurement — so the IMA PCR’s history begins with a commitment to the boot-time PCR state.
If there is no TPM, ima_add_boot_aggregate() deliberately produces a degenerate aggregate (zeroes), signaling that “the core root of trust is not hardware based” — i.e., the chain has no hardware anchor and attestation cannot be trusted.
Violations
When the TOCTOU check fires, IMA cannot vouch for what was actually read, so it records a violation: it adds a zero-digest entry to the list and, per ima_init.c, extends the aggregate PCR “with ff…ff’s.” Extending with all-ones permanently poisons the PCR for that boot — a verifier sees a value that cannot correspond to any legitimate measurement sequence, which is exactly the intended “something raced; do not trust this” signal.
The runtime measurement list files
IMA exposes the list through securityfs at /sys/kernel/security/ima/:
ascii_runtime_measurements— human-readable, one entry per line.binary_runtime_measurements— the canonical binary form a verifier parses; each field is length-prefixed so variable-length data (long pathnames, arbitrary-size hashes) can be parsed unambiguously.
A typical ascii_runtime_measurements line has four whitespace-separated columns: the PCR index the entry was extended into; the template-data hash (the digest actually folded into the PCR); the template name (e.g. ima-ng); and then the template-specific fields — for ima-ng, the file content digest (prefixed with its algorithm, e.g. sha256:...) and the file’s pathname. The first line is always boot_aggregate.
Templates — the format of a measurement entry
What goes into each entry is governed by a template, documented in IMA-templates.rst. A template is “a template descriptor, to determine which information should be included in the measurement list; a template field, to generate and display data of a given type.”
The original ima template was rigid: per the docs it “is fixed length, containing the filedata hash and pathname. The filedata hash is limited to 20 bytes (md5/sha1). The pathname is a null terminated string, limited to 255 characters.” Those limits — a 20-byte hash and a 255-char path — are exactly why it was superseded. The modern default, ima-ng (“next generation”), uses fields d-ng|n-ng: an arbitrary-algorithm digest and an unbounded filename.
The predefined descriptors in v6.12 and their formats:
| Descriptor | Format | Purpose |
|---|---|---|
ima | d|n | legacy fixed-length (SHA-1, 255-char path) |
ima-ng (default) | d-ng|n-ng | arbitrary hash + full pathname |
ima-ngv2 | d-ngv2|n-ng | digest prefixed with a type indicator |
ima-sig | d-ng|n-ng|sig | adds the file’s IMA/EVM signature |
ima-sigv2 | d-ngv2|n-ng|sig | v2 digest + signature |
ima-buf | d-ng|n-ng|buf | measures a kernel buffer (e.g. a key blob) |
ima-modsig | d-ng|n-ng|sig|d-modsig|modsig | appended (PKCS#7) module signatures |
evm-sig | d-ng|n-ng|evmsig|xattrnames|xattrlengths|xattrvalues|iuid|igid|imode | includes EVM signature + protected metadata |
Key field meanings: d is a SHA-1/MD5 digest capped at 20 bytes; d-ng is “digest with arbitrary hash algorithm”; n-ng is “filename without size restrictions”; sig is “file or EVM signature”; d-modsig is the “digest excluding appended signature” (needed because an appended signature changes the file’s bytes); buf is “buffer data generating the hash.” The template is chosen at build time (default ima-ng), or at boot with ima_template=ima-sig, or as a free-form ima_template_fmt= string.
Appraisal — turning detection into enforcement
Measurement only records; appraisal gates. ima_appraise_measurement() in ima_appraise.c reads the file’s security.ima extended attribute and compares it to the freshly collected hash. Before trusting that xattr it calls evm_verifyxattr() — i.e. it asks EVM “has this security.ima xattr itself been tampered with?” This is the crucial handoff: IMA verifies file contents, but the reference it compares against lives in an xattr, and that xattr is only trustworthy if EVM vouches for it.
The security.ima xattr can hold several formats, distinguished by a leading type byte in enum evm_ima_xattr_type (from integrity.h):
IMA_XATTR_DIGEST— a bare hash (legacy MD5/SHA-1).IMA_XATTR_DIGEST_NG— a hash whose “first byte contains algorithm id,” so SHA-256 and friends fit.EVM_IMA_XATTR_DIGSIG— a digital signature (RSA/EC) over the file hash, verified against a public key on a kernel keyring (see Kernel Keyrings). The on-disk format isstruct signature_v2_hdr { uint8_t type; uint8_t version; uint8_t hash_algo; __be32 keyid; __be16 sig_size; uint8_t sig[]; }.IMA_VERITY_DIGSIG— a signature over an fs-verity root hash (thesigv3appraise type).
The verdict is one of the kernel’s integrity-status values: INTEGRITY_PASS, INTEGRITY_PASS_IMMUTABLE, INTEGRITY_FAIL, INTEGRITY_NOLABEL (no security.ima present), or INTEGRITY_UNKNOWN. What happens on failure depends on the mode set via the ima_appraise= kernel parameter:
enforce— the default for a locked-down system; a failing or missing appraisal returns-EACCESand the access is denied.log— failures are logged but allowed through (audit/observability without breakage).fix— used during provisioning: the kernel writes a correctsecurity.imaxattr for files lacking one, so an administrator can label an existing filesystem. (enforceis explicitly disabled while infixorlogmode.)
The setxattr path is itself guarded: ima_inode_setxattr() calls ima_protect_xattr(), which requires CAP_SYS_ADMIN to write security.ima and runs validate_hash_algo() on the supplied value — so an unprivileged process cannot forge a known-good label for a tampered file.
Configuration — bringing IMA up
# /etc/default/grub → kernel command line
ima_policy=tcb # use a built-in policy: measure executables, mmap'd
# libs, modules, firmware run/read by root
ima_template=ima-ng # variable-length hash + full path (the default)
ima_hash=sha256 # hashing algorithm for measurement (default sha256
# on modern kernels)
ima_appraise=fix # one-time: write security.ima labels for all filesAfter booting with ima_appraise=fix and a policy that appraises, label the filesystem and flip to enforcement:
# Recursively populate security.ima with file-content hashes
find / -fstype ext4 -type f -uid 0 -exec head -c 0 {} \; # touch to trigger fix
# (Each open under a 'fix' policy writes the correct security.ima xattr.)
# Inspect a label
getfattr -m security.ima -e hex -d /usr/bin/bash
# security.ima=0x040... (leading 04 = IMA_XATTR_DIGEST_NG, then algo id + hash)
# Read the live measurement list — first line is always boot_aggregate
head -1 /sys/kernel/security/ima/ascii_runtime_measurements
# 10 <template-hash> ima-ng sha256:0000... boot_aggregateLine-by-line: ima_policy=tcb selects a built-in “trusted computing base” rule set rather than a custom one (custom policy is written to /sys/kernel/security/ima/policy, covered in IMA Appraisal and Measurement Policy). ima_template=ima-ng and ima_hash=sha256 set the entry format and digest. ima_appraise=fix is the provisioning mode; once labeling is done, the value is changed to enforce (and ideally locked by secure boot + lockdown so it cannot be downgraded). The getfattr output shows the type byte (04 = IMA_XATTR_DIGEST_NG) that tells the kernel how to parse the rest.
Failure Modes and Common Misunderstandings
- “Measurement alone stops malware.” It does not. Measurement is passive — it records what ran and lets a verifier notice afterward. Only appraisal in
enforcemode blocks a bad file. A measure-only deployment with no remote attestation server consuming the list provides essentially zero protection; the list just grows in RAM. - No TPM ⇒ no trust anchor. Without a TPM, the boot aggregate is zeroed and the PCR cannot be quoted by hardware. The list is still built and appraisal still works, but a remote verifier has nothing cryptographically signed to compare against — an attacker with kernel access could fabricate the list wholesale. IMA-without-TPM is for local enforcement, not attestation.
- Forgetting EVM ⇒ the reference is forgeable. IMA compares a file’s hash to its
security.imaxattr. If nothing protects that xattr, an offline attacker who can mount the disk simply edits both the file and itssecurity.imato match. This is the exact gap Extended Verification Module closes — andima_appraise_measurement()only trusts the xattr afterevm_verifyxattr()succeeds. - PCR poisoning by violations. A legitimate file opened read-write by two processes triggers a TOCTOU violation, extends the PCR with
0xff..., and permanently breaks attestation for that boot. On a busy system, sloppy policy that measures frequently-rewritten files produces spurious violations and an un-attestable PCR. Scope the policy to immutable files. fixmode left enabled. Shipping a system inima_appraise=fixmeans any tampered file simply gets a fresh, valid label on next access — enforcement is silently defeated.fixis a provisioning-only mode.- Algorithm mismatch. A
security.imaxattr written with one hash algorithm cannot be appraised under a kernel configured for another;ima_get_hash_algo()reads the algorithm from the xattr’s type byte for*_NGformats, but legacyIMA_XATTR_DIGESTassumes SHA-1.
Alternatives and When to Choose Them
- fs-verity — a per-file Merkle-tree integrity mechanism built into ext4/f2fs/btrfs. Unlike IMA it verifies blocks lazily on read (so it scales to huge files and tolerates partial reads) and is enforced by the filesystem, not an LSM. IMA can consume fs-verity (the
IMA_VERITY_DIGSIG/sigv3path), measuring/signing the verity root hash. Choose fs-verity for large read-mostly files (e.g. Android APKs); choose IMA appraisal for whole-file gating across arbitrary filesystems. - dm-verity — block-device-level integrity for an entire read-only volume via a Merkle tree, used by Android Verified Boot and many appliance images. It protects a whole partition rather than individual files and requires the volume be immutable. Choose it when the root filesystem is a sealed image; choose IMA when files are mutable and individually labeled.
- Secure Boot + module signing alone — Secure Boot and the Kernel Trust Chain and Kernel Module Signing establish trust up to the kernel, but stop there: they say nothing about userspace binaries. IMA extends measured/appraised integrity past the kernel into userspace files. They are complementary layers, not substitutes.
- AIDE / Tripwire (userspace integrity scanners) — periodically hash files and diff against a database. They run in userspace (so a rooted kernel can hide from them), scan on a schedule (so they miss transient tampering), and have no hardware anchor. IMA’s in-kernel, on-access, TPM-backed model is strictly stronger for the threats those tools target.
Production Notes
IMA originated at IBM Research; the design was published as “Design and Implementation of a TCG-based Integrity Measurement Architecture” (Sailer, Zhang, Jaeger, van Doorn, USENIX Security 2004) and merged into mainline Linux around the 2.6.30 era.
Uncertain
Verify: that IMA’s measurement code was merged in Linux 2.6.30 (2009) and that the authorship/venue of the founding paper is exactly “Sailer, Zhang, Jaeger, van Doorn, USENIX Security 2004.” Reason: the Wikipedia page I attempted to fetch returned HTTP 404, so these historical facts rest on training knowledge, not a primary source consulted in this task. To resolve: confirm against the USENIX 2004 proceedings and the Linux git history (
git log --oneline security/integrity/ima/). The mechanism described in this note is verified against v6.12 source; only the origin-date and citation are unverified. uncertain
In practice IMA is deployed in two distinct postures. Attestation deployments (cloud/confidential-computing, telco NFV) run measure-only with a TPM and ship the measurement list to a verifier (e.g. Keylime, which periodically pulls the list and a TPM quote, replays the list, and alarms on mismatch) — here the value is continuous remote evidence of what ran. Enforcement deployments (locked-down appliances, regulated endpoints) run appraise in enforce mode with signed security.ima xattrs, refusing to execute anything not signed by a trusted key on the [[Kernel Keyrings|.ima keyring]] — here the value is prevention. The strongest posture combines both, anchored by secure boot, protected by EVM, and frozen by lockdown so the running policy cannot be relaxed.
A recurring operational pain point is the interaction with package updates: an rpm/dpkg upgrade rewrites binaries, which under an appraise-everything policy must arrive with valid signatures or get re-labeled — distributions that ship IMA signatures in their packages (e.g. via rpm’s file-signing) make this seamless; ad-hoc systems must integrate signing into their build pipeline or they will brick on the first update.
See Also
- IMA Appraisal and Measurement Policy — the policy language (rules,
func=,mask=,appraise_type=) that decides what IMA measures and appraises; the natural companion to this mechanism note - Extended Verification Module — protects the
security.imaxattr (and other metadata) so IMA’s reference value can be trusted; IMA callsevm_verifyxattr()before trusting any label - Kernel Keyrings — where IMA appraisal’s trusted public keys (the
.imakeyring) and EVM’s HMAC key live - Secure Boot and the Kernel Trust Chain — establishes the boot-time PCRs (0–7) that the IMA boot aggregate folds in; the hardware root of trust IMA extends past the kernel
- The Linux Security Module Framework — IMA registers as an LSM; its hooks are the file-open/exec/mmap/read interception points
- Kernel Lockdown Mode — complements IMA by preventing the running kernel (and IMA policy) from being relaxed at runtime
- Linux Security MOC — parent map (section G, “Integrity and Keys”)