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 <[email protected]>
Signed-off-by: Frederick Lawler <[email protected]>
---
 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


Reply via email to