Hi, This is a design RFC, not a patch submission -- I'd like feedback on the approach before polishing the patch series further. A working reference implementation exists today for demonstration; details and links below.
Problem Every existing IMA hook (FILE_CHECK, MMAP_CHECK, BPRM_CHECK, MODULE_CHECK, ...) measures once, at load time: file open, exec, mmap, module load, or an explicit ima_measure_critical_data() call. Nothing in IMA today re-measures an object after it's already resident. A process's .text or GOT can be modified in memory after load (ptrace(), a kernel exploit writing through /proc/pid/mem, or similar) without IMA ever re-examining it -- the load-time measurement stays valid in the log forever, even after the live content has diverged from it. This is exactly the gap Andre Rein's DRIVE paper (ASIA CCS '17, https://doi.org/10.1145/3052973.3052975) addresses: periodic re-hashing of a process's executable memory against reference values derived from its on-disk ELF, anchored in a TPM PCR via the same extend-chain model IMA already uses. This RFC proposes bringing that capability into IMA's own policy-driven measurement model, rather than as a separate, out-of-tree mechanism. Scenario / Rational Load-time-only measurement is a materially weaker guarantee on systems that stay up for a long time between reboots than on systems that cycle power regularly. Every reboot is itself a fresh measured-boot event -- a natural bound on how long an in-memory tamper that bypassed load-time measurement can persist undetected, since the next boot re-measures everything from scratch. Railway signalling and interlocking equipment is a concrete example of the opposite case: field devices are routinely designed to run for months or years without a reboot, both for safety-availability reasons (a signalling function restarting is itself an operationally disruptive event) and because physical access for a controlled maintenance reboot is often scheduled, not on-demand. On that kind of system, load-time IMA measurement establishes a trustworthy baseline at boot and then provides no further guarantee for the entire multi-month uptime that follows -- an attacker who achieves a one-time in-memory modification (no on-disk change, so FILE_CHECK never fires again) can persist for as long as the device stays up, which on this class of hardware is the normal case, not an edge case. The same shape of argument applies to other long-uptime industrial-control and embedded contexts more generally; railway is simply where this is currently being evaluated. Periodic re-measurement turns "trustworthy at boot" into "trustworthy for as long as it keeps checking," which is the property this class of system actually needs. Proposed design Two pieces: 1. Policy grammar: a new interval= option (milliseconds), together with three new func= hook names -- RUNTIME_TEXT_CHECK, RUNTIME_GOT_CHECK, KERNEL_TEXT_CHECK -- added to the existing __ima_hooks() X-macro in ima.h, so the enum, the measuring-string table, and the policy-readback token array all stay in sync automatically, the same as every existing hook. interval= is required for, and only accepted with, those three hooks, enforced in ima_validate_rule() the same way IMA already enforces every other func-specific option. Example rule this grammar accepts: measure func=RUNTIME_TEXT_CHECK interval=30000 pcr=16 2. A dispatch engine: a new security/integrity/ima/ima_runtime.c, compiled into ima.o (not a separate loadable module), holding a delayed_work-based periodic worker that ima_update_policy() starts, reconfigures, or stops to track whatever RUNTIME_TEXT_CHECK rule is currently active. On each tick it walks every live task's file-backed executable VMAs under a locked snapshot, then hashes unlocked (a two-pass split that exists specifically to avoid a nested-mmap_read_lock self- deadlock hit during prototyping -- see "What's already been tested" below), reuses ima_calc_buffer_hash() for the hash itself, and PCR-extends via tpm_pcr_extend(ima_tpm_chip, ...) directly, deliberately bypassing ima_add_template_entry()'s measurement-list/htable path. That path dedups by (digest, pcr) alone and skips the PCR extend on a repeat digest -- which would silently break external verification, since an auditor replaying the log's extend chain needs to reproduce exactly what the TPM really did, including repeats of unchanged content across ticks. Log-growth from repeatedly re-measuring unchanged content is instead bounded by a small (pid, address)-keyed dedup cache, checked after every hash (never instead of hashing), so nothing is ever skipped without being freshly examined first. RUNTIME_GOT_CHECK and KERNEL_TEXT_CHECK are accepted by the grammar but not yet wired to the dispatch engine -- v1 measures .text only. Kernel-image .text re-measurement in particular needs its own reference-value handling (a statically-linked kernel image has link-time-relocated content, unlike the userspace PIC/PIE case this prototype already handles) and is intentionally left for a follow-up rather than folded into this first round. What's already been tested (reference implementation) Public repo, with everything below runnable, not just described: https://gitlab.rlp.net/nicolai.kuntze/memory_attestation - The full DRIVE pipeline -- runtime re-measurement, DML/SMAF chain, ELF-derived reference values (.text and per-GOT-slot), and an external OBS/VS verification split with TPM-Quote cross-checking -- is prototyped and validated end-to-end as a standalone out-of-tree module (drive_ima/) plus a Rust verification toolchain (drive-rs/), including real ptrace()-based tamper injection correctly detected in every case, and real PCR extends confirmed against a swtpm-backed TPM via tpm2_pcrread. - The two mainline patches this RFC describes (kernel-patches/0001-ima-policy-add-interval-and-runtime-hook- funcs.patch and 0002-ima-runtime-dispatch-engine-v1.patch), plus a follow-on 0003-ima-runtime-per-mapping-dedup.patch adding the dedup cache, apply cleanly against v6.8, pass checkpatch.pl --no-tree with 0 errors/0 warnings, compile clean under W=1, link into a full vmlinux, and boot and dispatch for real under QEMU/KVM: writing a RUNTIME_TEXT_CHECK rule to /sys/kernel/security/ima/policy in a running kernel causes the worker to fire on the configured interval, correctly measure every live process each cycle, and correctly reconfigure after a policy rewrite -- all empirically timed, not just log-inspected. - One thing not yet exercised: a genuine hardware PCR extend from this dispatch engine specifically. The test environment (QEMU 8.2.2 + SeaBIOS 1.16.3) hits a reproducible ACPI early-boot fault whenever any TPM device is attached, confirmed unrelated to this patch (fault is at PID 0, well before IMA runs). With no TPM present, the code correctly takes its documented -ENODEV fallback rather than crashing; that path is tested, a real PCR write from this specific code path is not, in this environment. (The extend call itself, and the extend-chain format, are the same ones already confirmed working against real hardware in the standalone prototype above.) Threat-model note, already accounted for Read-write access to /sys/kernel/security/ima/policy at runtime (CONFIG_IMA_WRITE_POLICY=on) lets anyone with CAP_SYS_ADMIN silently remove or weaken an active RUNTIME_TEXT_CHECK rule without touching any signed code -- which would defeat the "isolated from a post-boot adversary" property this is meant to provide, and matters more, not less, on the long-uptime deployments described above, since there's no near-term reboot to force a policy re-load. This isn't a gap in the proposed patches; it's a statement about deployment configuration, and it's already documented as a hard requirement for production use of this extension: CONFIG_IMA_WRITE_POLICY should be off, making the policy write-once at boot the same way it already is for every other IMA policy today. Questions for the list - Is a delayed_work periodic worker compiled into ima.o the right shape for a non-event-triggered hook class, or would the list prefer this live behind a separate mechanism (a kthread, an LSM-adjacent facility, something eBPF-driven) rather than inside IMA's own hook dispatch? - Is bypassing ima_add_template_entry()'s measurement-list htable (for the reason given above -- external-verifier replay needs every real extend, not a deduped view) an acceptable precedent, or is there a preferred way to get an un-deduped extend through the existing measurement-list path instead of calling tpm_pcr_extend() directly? - Any objection to the interval= grammar addition / the X-macro- based hook additions as scoped here, ahead of a full patch series? - Kernel-image .text re-measurement (KERNEL_TEXT_CHECK) is deliberately left unimplemented in this round -- is that the right place to split the series, or would the list rather see it land together with RUNTIME_TEXT_CHECK? Thanks, Nicolai

