The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [RFC] IMA: periodic runtime re-measurement of process .text/GOT
@ 2026-08-17 10:23 Nicolai Kuntze
  2026-08-19 17:45 ` Mimi Zohar
  0 siblings, 1 reply; 2+ messages in thread
From: Nicolai Kuntze @ 2026-08-17 10:23 UTC (permalink / raw)
  To: linux-integrity
  Cc: Mimi Zohar, Roberto Sassu, Dmitry Kasatkin, Eric Snowberg,
	Paul Moore, James Morris, Serge E. Hallyn, linux-security-module,
	linux-kernel

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

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-19 17:46 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 10:23 [RFC] IMA: periodic runtime re-measurement of process .text/GOT Nicolai Kuntze
2026-08-19 17:45 ` Mimi Zohar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox