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

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

On Mon, 2026-08-17 at 12:23 +0200, Nicolai Kuntze wrote:
> 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.

There are two separate issues that need to be addressed:

- Systems that aren't rebooted frequently
- Long-running processes whose mmap'ed executable memory may have been tampered
with after load

The first issue can be addressed by periodically clearing the iint cached info.
It's something that should be added to IMA and is a relatively simple addition.
It would be a good first kernel project.

The second is verifying a long-running process's mmap'ed executable memory
(.text, GOT) hasn't diverged from what was loaded — via ptrace(), a kernel
exploit through /proc/pid/mem, or similar. DRIVE addresses this via periodic re-
hashing against the on-disk ELF, TPM-anchored.

Worth noting: W^X, hardware CFI, and page-fault-based monitoring all fully
prevent tampering that goes through normal page-table-enforced writes — no
window, no cost of periodic checking. None of them, however, cover an attacker
with an arbitrary physical-memory write or the ability to rewrite page tables
directly, since that bypasses MMU enforcement entirely. DRIVE is the one
mechanism here that says anything about that residual case — but only as
bounded-time detection (via the re-hash interval), not prevention. On bare
metal, nothing closes that gap outright; DRIVE narrows it rather than solving
it. The RFC should be explicit about that distinction.

I'm not convinced this feature belongs in IMA. IMA's existing model is event-
driven, once: measure at open/exec/mmap. This feature is continuous/periodic — a
timer walking live process state on its own schedule — which is a different
execution model, not an incremental addition to a hook-based architecture. The
reference implementation shows this mismatch concretely: it bypasses IMA's
measurement list and dedup entirely, because IMA's "measured once, log stays
valid" model doesn't fit a periodic check that needs to prove this specific tick
still matched.

Mimi (with AI's help)

> 
> 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