IMA caches an appraisal result per inode and hook, but does not record the
policy rule which produced it.  A file accepted through a weaker subject
rule can therefore retain PASS when a later, stronger subject matches a
rule which requires a signature.  Replacing LSM-backed rules on policy
reload leaves the same stale result usable as well.

Assign stable rule IDs and maintain a monotonic appraisal policy
generation.  Record both with the inode's appraisal result, and clear
appraisal state when the next matching rule or generation differs.  This
preserves same-rule cache reuse while preventing PASS from crossing rule
and policy identities.

On ff47652a4b66, complete IMA policy publication and the one-reload and
two-reload cases each reproduced the stale allow in three fresh boots.
With this change, the stronger consumer was denied in all nine
corresponding runs.  Same-rule reuse, unprimed and new-inode denial,
content and xattr invalidation, and normal policy reload controls passed.
All touched source blobs are byte-identical and the four changed IMA
objects build successfully at 47324d3a5b3a.  This is an appraisal
authorization bypass; no RCE or LPE primitive is claimed.

This issue was found through manual source inspection assisted by an
AI/LLM, then evaluated with local production-path dynamic validation.

Fixes: d79d72e02485 ("ima: per hook cache integrity appraisal status")
Cc: [email protected]
Assisted-by: LLM
Signed-off-by: Sung Byeongchan <[email protected]>
---
 security/integrity/ima/ima.h          | 13 +++++++++++--
 security/integrity/ima/ima_api.c      |  5 +++--
 security/integrity/ima/ima_appraise.c |  2 +-
 security/integrity/ima/ima_main.c     | 25 +++++++++++++++++++++----
 security/integrity/ima/ima_policy.c   | 21 ++++++++++++++++++++-
 5 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 10214f73ca1ed..4ed21e731fe7d 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -229,6 +229,8 @@ struct ima_iint_cache {
        struct mutex mutex;     /* protects: version, flags, digest */
        struct integrity_inode_attributes real_inode;
        unsigned long flags;
+       u64 appraisal_rule_id;
+       u64 appraisal_policy_generation;
        unsigned long measured_pcrs;
        unsigned long atomic_flags;
        enum integrity_status ima_file_status:4;
@@ -239,6 +241,11 @@ struct ima_iint_cache {
        struct ima_digest_data *ima_hash;
 };
 
+struct ima_appraisal_rule_id {
+       u64 rule_id;
+       u64 policy_generation;
+};
+
 extern struct lsm_blob_sizes ima_blob_sizes;
 
 static inline struct ima_iint_cache *
@@ -427,7 +434,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode 
*inode,
                   const struct cred *cred, struct lsm_prop *prop, int mask,
                   enum ima_hooks func, int *pcr,
                   struct ima_template_desc **template_desc,
-                  const char *func_data, unsigned int *allowed_algos);
+                  const char *func_data, unsigned int *allowed_algos,
+                  struct ima_appraisal_rule_id *appraisal_rule);
 int ima_must_measure(struct inode *inode, int mask, enum ima_hooks func);
 int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file,
                            void *buf, loff_t size, enum hash_algo algo,
@@ -458,7 +466,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode 
*inode,
                     const struct cred *cred, struct lsm_prop *prop,
                     enum ima_hooks func, int mask, int flags, int *pcr,
                     struct ima_template_desc **template_desc,
-                    const char *func_data, unsigned int *allowed_algos);
+                    const char *func_data, unsigned int *allowed_algos,
+                    struct ima_appraisal_rule_id *appraisal_rule);
 void ima_init_policy(void);
 void ima_update_policy(void);
 void ima_update_policy_flags(void);
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 122d127e108dc..bc049d257db95 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -192,7 +192,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode 
*inode,
                   const struct cred *cred, struct lsm_prop *prop, int mask,
                   enum ima_hooks func, int *pcr,
                   struct ima_template_desc **template_desc,
-                  const char *func_data, unsigned int *allowed_algos)
+                  const char *func_data, unsigned int *allowed_algos,
+                  struct ima_appraisal_rule_id *appraisal_rule)
 {
        int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE | IMA_HASH;
 
@@ -200,7 +201,7 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode 
*inode,
 
        return ima_match_policy(idmap, inode, cred, prop, func, mask,
                                flags, pcr, template_desc, func_data,
-                               allowed_algos);
+                               allowed_algos, appraisal_rule);
 }
 
 static bool ima_get_verity_digest(struct ima_iint_cache *iint,
diff --git a/security/integrity/ima/ima_appraise.c 
b/security/integrity/ima/ima_appraise.c
index b280488e15fcb..1a3cde20a733c 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -82,7 +82,7 @@ int ima_must_appraise(struct mnt_idmap *idmap, struct inode 
*inode,
        security_current_getlsmprop_subj(&prop);
        return ima_match_policy(idmap, inode, current_cred(), &prop,
                                func, mask, IMA_APPRAISE | IMA_HASH, NULL,
-                               NULL, NULL, NULL);
+                               NULL, NULL, NULL, NULL);
 }
 
 static int ima_fix_xattr(struct dentry *dentry, struct ima_iint_cache *iint)
diff --git a/security/integrity/ima/ima_main.c 
b/security/integrity/ima/ima_main.c
index ab1e53b3210d3..b85732bc1285a 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -272,6 +272,7 @@ static int process_measurement(struct file *file, const 
struct cred *cred,
        bool violation_check;
        enum hash_algo hash_algo;
        unsigned int allowed_algos = 0;
+       struct ima_appraisal_rule_id appraisal_rule = {};
 
        if (!ima_policy_flag || !S_ISREG(inode->i_mode))
                return 0;
@@ -282,7 +283,7 @@ static int process_measurement(struct file *file, const 
struct cred *cred,
         */
        action = ima_get_action(file_mnt_idmap(file), inode, cred, prop,
                                mask, func, &pcr, &template_desc, NULL,
-                               &allowed_algos);
+                               &allowed_algos, &appraisal_rule);
        violation_check = ((func == FILE_CHECK || func == MMAP_CHECK ||
                            func == MMAP_CHECK_REQPROT) &&
                           (ima_policy_flag & IMA_MEASURE) &&
@@ -364,6 +365,22 @@ static int process_measurement(struct file *file, const 
struct cred *cred,
                                         IMA_APPRAISED_SUBMASK);
        }
 
+       /*
+        * An appraisal result is reusable only for the policy rule which
+        * produced it.  A weaker rule must not prime the inode cache for a
+        * later, stronger rule matching another subject or policy context.
+        */
+       if (must_appraise &&
+           (iint->appraisal_rule_id != appraisal_rule.rule_id ||
+            iint->appraisal_policy_generation !=
+                                       appraisal_rule.policy_generation)) {
+               iint->flags &= ~(IMA_APPRAISED | IMA_APPRAISED_SUBMASK |
+                                IMA_NONACTION_RULE_FLAGS);
+               iint->appraisal_rule_id = appraisal_rule.rule_id;
+               iint->appraisal_policy_generation =
+                                       appraisal_rule.policy_generation;
+       }
+
        /* Determine if already appraised/measured based on bitmask
         * (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED,
         *  IMA_AUDIT, IMA_AUDITED)
@@ -563,11 +580,11 @@ static int ima_file_mprotect(struct vm_area_struct *vma, 
unsigned long reqprot,
        inode = file_inode(vma->vm_file);
        action = ima_get_action(file_mnt_idmap(vma->vm_file), inode,
                                current_cred(), &prop, MAY_EXEC, MMAP_CHECK,
-                               &pcr, &template, NULL, NULL);
+                               &pcr, &template, NULL, NULL, NULL);
        action |= ima_get_action(file_mnt_idmap(vma->vm_file), inode,
                                 current_cred(), &prop, MAY_EXEC,
                                 MMAP_CHECK_REQPROT, &pcr, &template, NULL,
-                                NULL);
+                                NULL, NULL);
 
        /* Is the mmap'ed file in policy? */
        if (!(action & (IMA_MEASURE | IMA_APPRAISE_SUBMASK)))
@@ -1143,7 +1160,7 @@ int process_buffer_measurement(struct mnt_idmap *idmap,
                security_current_getlsmprop_subj(&prop);
                action = ima_get_action(idmap, inode, current_cred(),
                                        &prop, 0, func, &pcr, &template,
-                                       func_data, NULL);
+                                       func_data, NULL, NULL);
                if (!(action & IMA_MEASURE) && !digest)
                        return -ENOENT;
        }
diff --git a/security/integrity/ima/ima_policy.c 
b/security/integrity/ima/ima_policy.c
index 68d9a5e6c232e..5e88e6798a78f 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -59,6 +59,9 @@ int ima_policy_flag;
 static int temp_ima_appraise;
 static int build_ima_appraise __ro_after_init;
 
+static atomic64_t ima_rule_id = ATOMIC64_INIT(0);
+static atomic64_t ima_policy_generation = ATOMIC64_INIT(1);
+
 atomic_t ima_setxattr_allowed_hash_algorithms;
 
 #define MAX_LSM_RULES 6
@@ -101,6 +104,7 @@ static inline bool vfsgid_lt_kgid(vfsgid_t vfsgid, kgid_t 
kgid)
 
 struct ima_rule_entry {
        struct list_head list;
+       u64 rule_id;
        int action;
        unsigned int flags;
        enum ima_hooks func;
@@ -511,6 +515,8 @@ int ima_lsm_policy_change(struct notifier_block *nb, 
unsigned long event,
        if (event != LSM_POLICY_CHANGE)
                return NOTIFY_DONE;
 
+       /* Readers using either old or replacement rules must not reuse a PASS. 
*/
+       atomic64_inc(&ima_policy_generation);
        ima_lsm_update_rules();
        return NOTIFY_OK;
 }
@@ -754,6 +760,7 @@ static int get_subaction(struct ima_rule_entry *rule, enum 
ima_hooks func)
  * @template_desc: the template that should be used for this rule
  * @func_data: func specific data, may be NULL
  * @allowed_algos: allowlist of hash algorithms for the IMA xattr
+ * @appraisal_rule: identity of the matched appraisal rule
  *
  * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type)
  * conditions.
@@ -766,7 +773,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode 
*inode,
                     const struct cred *cred, struct lsm_prop *prop,
                     enum ima_hooks func, int mask, int flags, int *pcr,
                     struct ima_template_desc **template_desc,
-                    const char *func_data, unsigned int *allowed_algos)
+                    const char *func_data, unsigned int *allowed_algos,
+                    struct ima_appraisal_rule_id *appraisal_rule)
 {
        struct ima_rule_entry *entry;
        int action = 0, actmask = flags | (flags << 1);
@@ -790,6 +798,11 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode 
*inode,
 
                action |= entry->action & IMA_DO_MASK;
                if (entry->action & IMA_APPRAISE) {
+                       if (appraisal_rule) {
+                               appraisal_rule->rule_id = entry->rule_id;
+                               appraisal_rule->policy_generation =
+                                       atomic64_read(&ima_policy_generation);
+                       }
                        action |= get_subaction(entry, func);
                        action &= ~IMA_HASH;
                        if (ima_fail_unverifiable_sigs)
@@ -895,6 +908,9 @@ static void add_rules(struct ima_rule_entry *entries, int 
count,
        for (i = 0; i < count; i++) {
                struct ima_rule_entry *entry;
 
+               if (!entries[i].rule_id)
+                       entries[i].rule_id = atomic64_inc_return(&ima_rule_id);
+
                if (policy_rule & IMA_DEFAULT_POLICY)
                        list_add_tail(&entries[i].list, &ima_default_rules);
 
@@ -1065,6 +1081,8 @@ void ima_update_policy(void)
 {
        struct list_head *policy = &ima_policy_rules;
 
+       /* Invalidate appraisal results before publishing appended rules. */
+       atomic64_inc(&ima_policy_generation);
        list_splice_tail_init_rcu(&ima_temp_rules, policy, synchronize_rcu);
 
        if (ima_rules != (struct list_head __rcu *)policy) {
@@ -2006,6 +2024,7 @@ ssize_t ima_parse_add_rule(char *rule)
                                    audit_info);
                return result;
        }
+       entry->rule_id = atomic64_inc_return(&ima_rule_id);
 
        list_add_tail(&entry->list, &ima_temp_rules);
 

base-commit: 47324d3a5b3abd781295044d01d92d09f184e872
-- 
2.43.0


Reply via email to