[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-Id: <20260130-xfs-ima-fixup-v5-3-57e84ea91712@cloudflare.com>
Date: Fri, 30 Jan 2026 16:39:56 -0600
From: Frederick Lawler <fred@...udflare.com>
To: Mimi Zohar <zohar@...ux.ibm.com>,
Roberto Sassu <roberto.sassu@...wei.com>,
Dmitry Kasatkin <dmitry.kasatkin@...il.com>,
Eric Snowberg <eric.snowberg@...cle.com>, Paul Moore <paul@...l-moore.com>,
James Morris <jmorris@...ei.org>, "Serge E. Hallyn" <serge@...lyn.com>,
"Darrick J. Wong" <djwong@...nel.org>,
Christian Brauner <brauner@...nel.org>, Josef Bacik <josef@...icpanda.com>,
Jeff Layton <jlayton@...nel.org>
Cc: linux-kernel@...r.kernel.org, linux-integrity@...r.kernel.org,
linux-security-module@...r.kernel.org, kernel-team@...udflare.com,
Frederick Lawler <fred@...udflare.com>
Subject: [PATCH v5 3/3] ima: Use kstat.ctime as a fallback change detection
for stacked fs
IMA performs unnecessary measurements on files in stacked file systems
that do not set kstat.change_cookie to an inode's i_version.
For example: TMPFS (upper) is stacked onto XFS (lower).
Actions files result in re-measurement because commit 1cf7e834a6fb
("xfs: switch to multigrain timestamps") introduced multigrain
timestamps into XFS which changed the kstat.change_cookie semantics
to no longer supply an i_version to compare against in
integrity_inode_attributes_changed(). Once the inode is in TMPFS,
the change detection behavior operates as normal because TMPFS updates
kstat.change_cookie to the i_version.
Instead, fall back onto a ctime comparison. This also gives file systems
that do not support i_version an opportunity avoid the same behavior,
as they're more likely to have ctime support.
timespec64_to_ns() is chosen to avoid adding extra storage to
integrity_inode_attributes by leveraging the existing version field.
Link: https://lore.kernel.org/all/aTspr4_h9IU4EyrR@CMGLRV3
Fixes: 1cf7e834a6fb ("xfs: switch to multigrain timestamps")
Suggested-by: Jeff Layton <jlayton@...nel.org>
Signed-off-by: Frederick Lawler <fred@...udflare.com>
---
include/linux/integrity.h | 6 +++++-
security/integrity/ima/ima_api.c | 11 ++++++++---
security/integrity/ima/ima_main.c | 2 +-
3 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/include/linux/integrity.h b/include/linux/integrity.h
index 382c783f0fa3ae4a938cdf9559291ba1903a378e..ec2c94907f417c4a71ecce29ac79edac9bc2c6f8 100644
--- a/include/linux/integrity.h
+++ b/include/linux/integrity.h
@@ -10,6 +10,7 @@
#include <linux/fs.h>
#include <linux/iversion.h>
#include <linux/kernel.h>
+#include <linux/time64.h>
enum integrity_status {
INTEGRITY_PASS = 0,
@@ -58,6 +59,9 @@ integrity_inode_attrs_stat_changed
if (stat->result_mask & STATX_CHANGE_COOKIE)
return stat->change_cookie != attrs->version;
+ if (stat->result_mask & STATX_CTIME)
+ return timespec64_to_ns(&stat->ctime) != (s64)attrs->version;
+
return true;
}
@@ -84,7 +88,7 @@ integrity_inode_attrs_changed(const struct integrity_inode_attributes *attrs,
* only for IMA if vfs_getattr_nosec() fails.
*/
if (!file || vfs_getattr_nosec(&file->f_path, &stat,
- STATX_CHANGE_COOKIE,
+ STATX_CHANGE_COOKIE | STATX_CTIME,
AT_STATX_SYNC_AS_STAT))
return !IS_I_VERSION(inode) ||
!inode_eq_iversion(inode, attrs->version);
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index c35ea613c9f8d404ba4886e3b736c3bab29d1668..e47d6281febc15a0ac1bd2ea1d28fea4d0cd5c58 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -272,10 +272,15 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file,
* to an initial measurement/appraisal/audit, but was modified to
* assume the file changed.
*/
- result = vfs_getattr_nosec(&file->f_path, &stat, STATX_CHANGE_COOKIE,
+ result = vfs_getattr_nosec(&file->f_path, &stat,
+ STATX_CHANGE_COOKIE | STATX_CTIME,
AT_STATX_SYNC_AS_STAT);
- if (!result && (stat.result_mask & STATX_CHANGE_COOKIE))
- i_version = stat.change_cookie;
+ if (!result) {
+ if (stat.result_mask & STATX_CHANGE_COOKIE)
+ i_version = stat.change_cookie;
+ else if (stat.result_mask & STATX_CTIME)
+ i_version = timespec64_to_ns(&stat.ctime);
+ }
hash.hdr.algo = algo;
hash.hdr.length = hash_digest_size[algo];
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 8cb17c9d446caaa5a98f5ec8f027c17ba7babca8..776db158b0bd8a0d053729ac0cc15af8b6020a98 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -199,7 +199,7 @@ static void ima_check_last_writer(struct ima_iint_cache *iint,
&iint->atomic_flags);
if ((iint->flags & IMA_NEW_FILE) ||
vfs_getattr_nosec(&file->f_path, &stat,
- STATX_CHANGE_COOKIE,
+ STATX_CHANGE_COOKIE | STATX_CTIME,
AT_STATX_SYNC_AS_STAT) ||
integrity_inode_attrs_stat_changed(&iint->real_inode,
&stat)) {
--
2.43.0
Powered by blists - more mailing lists