2024-04-15 16:12:13

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 0/9] ima: Integrate with digest_cache LSM

From: Roberto Sassu <[email protected]>

One of the IMA shortcomings over the years has been the availability of
reference digest values for appraisal. Recently, the situation improved
and some Linux distributions are including file signatures.

The digest_cache LSM takes a different approach. Instead of requiring
Linux distributions to include file signatures in their packages, it parses
the digests from signed RPM package headers and exposes an API for
integrity providers to query a digest.

That enables Linux distributions to immediately gain the ability to do
integrity checks with the existing packages, lowering the burden for
software vendors.

In addition, integrating IMA with the digest_cache LSMs has even more
benefits.

First, it allows generating a new-style masurement list including the RPM
package headers and the unknown files, which improves system performance
due to the lower usage of the TPM. The cost is the less accuracy of the
information reported, which might not suitable for everyone.

Second, performance improve for appraisal too. It has been found that
verifying the signatures of only the RPM package headers and doing a digest
lookup is much less computationally expensive than verifying individual
file signatures.

For reference, a preliminary performance evaluation has been published
here:

https://lore.kernel.org/linux-integrity/[email protected]/


Third, it makes a PCR predictable and suitable for TPM key sealing
policies.

Finally, it allows IMA to maintain a predictable PCR and to perform
appraisal from the very beginning of the boot, in the initial ram disk
(of course, it won't recognize automatically generated files, that don't
exist in the RPM packages).


Integration of IMA with the digest_cache LSM is straightforward.

Patch 1 lets IMA know when the digest_cache LSM is reading a digest list,
to populate a digest cache.

Patch 2 allows nested IMA verification of digest lists read by the
digest_cache LSM.

Patch 3 allows the usage of digest caches with the IMA policy.

Patch 4 introduces new boot-time built-in policies, to use digest caches
from the very beginning (it allows measurement/appraisal from the initial
ram disk).

Patch 5 modifies existing boot-time built-in policies if the digest_cache
LSM-specific policies have been selected at boot.

Patch 6 attaches the verification result of the digest list to the digest
cache being populated with that digest list.

Patch 7-8 enable the usage of digest caches respectively for measurement
and appraisal, at the condition that it is authorized with the IMA policy
and that the digest list itself was measured and appraised too.

Patch 9 subscribes to digest cache events and invalidates cached integrity
results on digest cache reset (file or directory modification).

Open points:
- Mimi prefers to extend flags in ima_iint_cache, rather than passing the
parameter down to process_measurement() - will do in a next version
- Prefetching of digest lists should not be done if there is no
measurement rule (not relevant for appraisal)

Changelog

v1:
- Change digest_cache= policy keyword value from 'content' to 'data'
(suggested by Mimi)
- Move digest_cache LSM integration code to ima_digest_cache.c (suggested
by Mimi)
- Don't store digest cache pointer in integrity metadata
- Rename 'digest_cache_mask' parameter of ima_get_action() and
ima_match_policy() to 'digest_cache_usage'
- Rename 'digest_cache_mask' parameter of ima_store_measurement() and
ima_appraise_measurement() to 'allowed_usage'
- Try digest cache method as first in ima_appraise_measurement() (suggested
by Mimi)
- Introduce ima_digest_cache_change() to be called on digest cache reset
- Subscribe to digest cache events
- Add forgotten modification in ima_iint_lockdep_annotate() (reported by
Mimi)
- Replace 'digest_cache_mask' member of the ima_rule_entry structure with
'digest_cache_usage' (suggested by Mimi)
- Split patch introducing digest_cache LSM-specific boot-time built-in
policies and modifying existing rules
- Add digest_cache LSM-specific boot-time built-in policies if the
digest_cache LSM is enabled in the kernel configuration
- Rename IMA_DIGEST_CACHE_MEASURE_CONTENT and
IMA_DIGEST_CACHE_APPRAISE_CONTENT to IMA_DIGEST_CACHE_MEASURE_DATA and
IMA_DIGEST_CACHE_APPRAISE_DATA

Roberto Sassu (9):
ima: Introduce hook DIGEST_LIST_CHECK
ima: Nest iint mutex for DIGEST_LIST_CHECK hook
ima: Add digest_cache policy keyword
ima: Add digest_cache_measure/appraise boot-time built-in policies
ima: Modify existing boot-time built-in policies with digest cache
policies
ima: Store allowed usage in digest cache based on integrity metadata
flags
ima: Use digest caches for measurement
ima: Use digest caches for appraisal
ima: Register to the digest_cache LSM notifier and process events

Documentation/ABI/testing/ima_policy | 6 +-
.../admin-guide/kernel-parameters.txt | 15 ++-
security/integrity/ima/Kconfig | 10 ++
security/integrity/ima/Makefile | 1 +
security/integrity/ima/ima.h | 22 +++-
security/integrity/ima/ima_api.c | 21 ++-
security/integrity/ima/ima_appraise.c | 32 +++--
security/integrity/ima/ima_digest_cache.c | 123 ++++++++++++++++++
security/integrity/ima/ima_digest_cache.h | 36 +++++
security/integrity/ima/ima_iint.c | 17 ++-
security/integrity/ima/ima_main.c | 46 +++++--
security/integrity/ima/ima_policy.c | 122 ++++++++++++++++-
12 files changed, 413 insertions(+), 38 deletions(-)
create mode 100644 security/integrity/ima/ima_digest_cache.c
create mode 100644 security/integrity/ima/ima_digest_cache.h

--
2.34.1



2024-04-15 16:13:05

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 3/9] ima: Add digest_cache policy keyword

From: Roberto Sassu <[email protected]>

Add the 'digest_cache=' policy keyword, to enable the usage of digest
caches for specific IMA actions and purpose.

At the moment, it accepts only 'data' as value, as digest caches can be
used only for measurement and appraisal of file data. In the future, it
might be possible to use them for file metadata too.

The 'digest_cache=' keyword can be specified for the subset of IMA hooks
listed in ima_digest_cache_func_allowed(). In case the function is not
specified in the policy, digest caches might not be available.

POLICY_CHECK has been excluded for measurement, because policy changes must
be visible in the IMA measurement list. For appraisal, instead, it might be
useful to load custom policies in the initial ram disk (no security.ima
xattr).

Add the digest_cache_usage member to the ima_rule_entry structure, and set
the flag IMA_DIGEST_CACHE_MEASURE_DATA if 'digest_cache=data' was specified
for a measure rule, IMA_DIGEST_CACHE_APPRAISE_DATA for an appraise rule.

Propagate the usage down to ima_match_policy() and ima_get_action(), so
that process_measurement() can make the final decision on whether or not
digest caches should be used to measure/appraise the file being evaluated.

Since using digest caches changes the meaning of the IMA measurement list,
which will include only digest lists and unknown files, enforce specifying
'pcr=' with a non-standard value, when 'digest_cache=data' is specified in
a measure rule.

This removes the ambiguity on the meaning of the IMA measurement list.

Signed-off-by: Roberto Sassu <[email protected]>
---
Documentation/ABI/testing/ima_policy | 5 +-
security/integrity/ima/ima.h | 10 +++-
security/integrity/ima/ima_api.c | 6 ++-
security/integrity/ima/ima_appraise.c | 2 +-
security/integrity/ima/ima_main.c | 8 +--
security/integrity/ima/ima_policy.c | 70 ++++++++++++++++++++++++++-
6 files changed, 89 insertions(+), 12 deletions(-)

diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy
index 22237fec5532..887ac79f66eb 100644
--- a/Documentation/ABI/testing/ima_policy
+++ b/Documentation/ABI/testing/ima_policy
@@ -29,7 +29,7 @@ Description:
[obj_user=] [obj_role=] [obj_type=]]
option: [digest_type=] [template=] [permit_directio]
[appraise_type=] [appraise_flag=]
- [appraise_algos=] [keyrings=]
+ [appraise_algos=] [keyrings=] [digest_cache=]
base:
func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK]
[FIRMWARE_CHECK]
@@ -77,6 +77,9 @@ Description:
For example, "sha256,sha512" to only accept to appraise
files where the security.ima xattr was hashed with one
of these two algorithms.
+ digest_cache:= [data]
+ "data" means that the digest cache is used only
+ for file data measurement and/or appraisal.

default policy:
# PROC_SUPER_MAGIC
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index c9140a57b591..337b3b76b28d 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -43,6 +43,10 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };

#define NR_BANKS(chip) ((chip != NULL) ? chip->nr_allocated_banks : 0)

+/* Digest cache usage flags. */
+#define IMA_DIGEST_CACHE_MEASURE_DATA 0x0000000000000001
+#define IMA_DIGEST_CACHE_APPRAISE_DATA 0x0000000000000002
+
/* current content of the policy */
extern int ima_policy_flag;

@@ -367,7 +371,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode,
const struct cred *cred, u32 secid, 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,
+ u64 *digest_cache_usage);
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,
@@ -398,7 +403,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode,
const struct cred *cred, u32 secid, 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,
+ u64 *digest_cache_usage);
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 b37d043d5748..f0a1ce10afe8 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -173,6 +173,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
* @template_desc: pointer filled in if matched measure policy sets template=
* @func_data: func specific data, may be NULL
* @allowed_algos: allowlist of hash algorithms for the IMA xattr
+ * @digest_cache_usage: Actions and purpose for which digest cache is allowed
*
* The policy is defined in terms of keypairs:
* subj=, obj=, type=, func=, mask=, fsmagic=
@@ -190,7 +191,8 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode,
const struct cred *cred, u32 secid, 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,
+ u64 *digest_cache_usage)
{
int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE | IMA_HASH;

@@ -198,7 +200,7 @@ int ima_get_action(struct mnt_idmap *idmap, struct inode *inode,

return ima_match_policy(idmap, inode, cred, secid, func, mask,
flags, pcr, template_desc, func_data,
- allowed_algos);
+ allowed_algos, digest_cache_usage);
}

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 3497741caea9..27ccc9a2c09f 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -81,7 +81,7 @@ int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode,
security_current_getsecid_subj(&secid);
return ima_match_policy(idmap, inode, current_cred(), secid,
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 18285fc8ac07..e3ca80098c4c 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -232,7 +232,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
*/
action = ima_get_action(file_mnt_idmap(file), inode, cred, secid,
mask, func, &pcr, &template_desc, NULL,
- &allowed_algos);
+ &allowed_algos, NULL);
violation_check = ((func == FILE_CHECK || func == MMAP_CHECK ||
func == MMAP_CHECK_REQPROT) &&
(ima_policy_flag & IMA_MEASURE));
@@ -489,11 +489,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(), secid, 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(), secid, 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)))
@@ -972,7 +972,7 @@ int process_buffer_measurement(struct mnt_idmap *idmap,
security_current_getsecid_subj(&secid);
action = ima_get_action(idmap, inode, current_cred(),
secid, 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 7cfd1860791f..9e13b88b0ed5 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -122,6 +122,7 @@ struct ima_rule_entry {
struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */
struct ima_rule_opt_list *label; /* Measure data grouped under this label */
struct ima_template_desc *template;
+ u64 digest_cache_usage; /* Actions and purpose for which digest cache is allowed */
};

/*
@@ -726,6 +727,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
+ * @digest_cache_usage: Actions and purpose for which digest cache is allowed
*
* Measure decision based on func/mask/fsmagic and LSM(subj/obj/type)
* conditions.
@@ -738,7 +740,8 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode,
const struct cred *cred, u32 secid, 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,
+ u64 *digest_cache_usage)
{
struct ima_rule_entry *entry;
int action = 0, actmask = flags | (flags << 1);
@@ -783,6 +786,9 @@ int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode,
if (template_desc && entry->template)
*template_desc = entry->template;

+ if (digest_cache_usage)
+ *digest_cache_usage |= entry->digest_cache_usage;
+
if (!actmask)
break;
}
@@ -859,6 +865,30 @@ static int ima_appraise_flag(enum ima_hooks func)
return 0;
}

+static bool ima_digest_cache_func_allowed(struct ima_rule_entry *entry)
+{
+ switch (entry->func) {
+ case NONE:
+ case FILE_CHECK:
+ case MMAP_CHECK:
+ case MMAP_CHECK_REQPROT:
+ case BPRM_CHECK:
+ case CREDS_CHECK:
+ case FIRMWARE_CHECK:
+ case POLICY_CHECK:
+ case MODULE_CHECK:
+ case KEXEC_KERNEL_CHECK:
+ case KEXEC_INITRAMFS_CHECK:
+ /* Exception: always add policy updates to measurement list! */
+ if (entry->action == MEASURE && entry->func == POLICY_CHECK)
+ return false;
+
+ return true;
+ default:
+ return false;
+ }
+}
+
static void add_rules(struct ima_rule_entry *entries, int count,
enum policy_rule_list policy_rule)
{
@@ -1073,7 +1103,7 @@ enum policy_opt {
Opt_digest_type,
Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos,
Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings,
- Opt_label, Opt_err
+ Opt_label, Opt_digest_cache, Opt_err
};

static const match_table_t policy_tokens = {
@@ -1122,6 +1152,7 @@ static const match_table_t policy_tokens = {
{Opt_template, "template=%s"},
{Opt_keyrings, "keyrings=%s"},
{Opt_label, "label=%s"},
+ {Opt_digest_cache, "digest_cache=%s"},
{Opt_err, NULL}
};

@@ -1245,6 +1276,18 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
if (entry->action != MEASURE && entry->flags & IMA_PCR)
return false;

+ /* New-style measurements with digest cache cannot be on default PCR. */
+ if (entry->action == MEASURE &&
+ (entry->digest_cache_usage & IMA_DIGEST_CACHE_MEASURE_DATA)) {
+ if (!(entry->flags & IMA_PCR) ||
+ entry->pcr == CONFIG_IMA_MEASURE_PCR_IDX)
+ return false;
+ }
+
+ /* Digest caches can be used only for a subset of the IMA hooks. */
+ if (entry->digest_cache_usage && !ima_digest_cache_func_allowed(entry))
+ return false;
+
if (entry->action != APPRAISE &&
entry->flags & (IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED |
IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS))
@@ -1881,6 +1924,26 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
&(template_desc->num_fields));
entry->template = template_desc;
break;
+ case Opt_digest_cache:
+ ima_log_string(ab, "digest_cache", args[0].from);
+
+ result = -EINVAL;
+
+ if (!strcmp(args[0].from, "data")) {
+ switch (entry->action) {
+ case MEASURE:
+ entry->digest_cache_usage |= IMA_DIGEST_CACHE_MEASURE_DATA;
+ result = 0;
+ break;
+ case APPRAISE:
+ entry->digest_cache_usage |= IMA_DIGEST_CACHE_APPRAISE_DATA;
+ result = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
case Opt_err:
ima_log_string(ab, "UNKNOWN", p);
result = -EINVAL;
@@ -2271,6 +2334,9 @@ int ima_policy_show(struct seq_file *m, void *v)
seq_puts(m, "digest_type=verity ");
if (entry->flags & IMA_PERMIT_DIRECTIO)
seq_puts(m, "permit_directio ");
+ if ((entry->digest_cache_usage & IMA_DIGEST_CACHE_MEASURE_DATA) ||
+ (entry->digest_cache_usage & IMA_DIGEST_CACHE_APPRAISE_DATA))
+ seq_puts(m, "digest_cache=data ");
rcu_read_unlock();
seq_puts(m, "\n");
return 0;
--
2.34.1


2024-04-15 16:13:07

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 1/9] ima: Introduce hook DIGEST_LIST_CHECK

From: Roberto Sassu <[email protected]>

Introduce a new hook to check the integrity of digest lists.

The new hook is invoked during a kernel read with file type
READING_DIGEST LIST, which is done by the digest_cache LSM when it is
populating a digest cache with a digest list.

Signed-off-by: Roberto Sassu <[email protected]>
---
Documentation/ABI/testing/ima_policy | 1 +
security/integrity/ima/ima.h | 1 +
security/integrity/ima/ima_main.c | 3 ++-
security/integrity/ima/ima_policy.c | 3 +++
4 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy
index c2385183826c..22237fec5532 100644
--- a/Documentation/ABI/testing/ima_policy
+++ b/Documentation/ABI/testing/ima_policy
@@ -36,6 +36,7 @@ Description:
[KEXEC_KERNEL_CHECK] [KEXEC_INITRAMFS_CHECK]
[KEXEC_CMDLINE] [KEY_CHECK] [CRITICAL_DATA]
[SETXATTR_CHECK][MMAP_CHECK_REQPROT]
+ [DIGEST_LIST_CHECK]
mask:= [[^]MAY_READ] [[^]MAY_WRITE] [[^]MAY_APPEND]
[[^]MAY_EXEC]
fsmagic:= hex value
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 11d7c0332207..cea4517e73ab 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -310,6 +310,7 @@ static inline unsigned int ima_hash_key(u8 *digest)
hook(KEY_CHECK, key) \
hook(CRITICAL_DATA, critical_data) \
hook(SETXATTR_CHECK, setxattr_check) \
+ hook(DIGEST_LIST_CHECK, digest_list_check) \
hook(MAX_CHECK, none)

#define __ima_hook_enumify(ENUM, str) ENUM,
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index c84e8c55333d..780627b0cde7 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -785,7 +785,8 @@ const int read_idmap[READING_MAX_ID] = {
[READING_MODULE] = MODULE_CHECK,
[READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK,
[READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK,
- [READING_POLICY] = POLICY_CHECK
+ [READING_POLICY] = POLICY_CHECK,
+ [READING_DIGEST_LIST] = DIGEST_LIST_CHECK,
};

/**
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index c0556907c2e6..7cfd1860791f 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1287,6 +1287,7 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
case MODULE_CHECK:
case KEXEC_KERNEL_CHECK:
case KEXEC_INITRAMFS_CHECK:
+ case DIGEST_LIST_CHECK:
if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC |
IMA_UID | IMA_FOWNER | IMA_FSUUID |
IMA_INMASK | IMA_EUID | IMA_PCR |
@@ -1530,6 +1531,8 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
entry->func = CRITICAL_DATA;
else if (strcmp(args[0].from, "SETXATTR_CHECK") == 0)
entry->func = SETXATTR_CHECK;
+ else if (strcmp(args[0].from, "DIGEST_LIST_CHECK") == 0)
+ entry->func = DIGEST_LIST_CHECK;
else
result = -EINVAL;
if (!result)
--
2.34.1


2024-04-15 16:13:22

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 2/9] ima: Nest iint mutex for DIGEST_LIST_CHECK hook

From: Roberto Sassu <[email protected]>

Invoking digest_cache_get() inside the iint->mutex critical region can
cause deadlocks due to the fact that IMA can be recursively invoked for
reading the digest list. The deadlock would occur if the digest_cache LSM
attempts to read the same inode that is already locked by IMA.

However, since the digest_cache LSM makes sure that the above situation
never happens, as it checks the inodes, it is safe to call
digest_cache_get() inside the critical region and nest the iint->mutex
when the DIGEST_LIST_CHECK hook is executed.

Add a lockdep subclass to the iint->mutex, that is 0 if the IMA hook
executed is not DIGEST_LIST_CHECK, and 1 when it is. Since lockdep allows
nesting with higher classes and subclasses, that effectively eliminates the
warning about the unsafe lock.

Pass the new lockdep subclass (nested variable) from ima_inode_get() to
ima_iint_init_always() and ima_iint_lockdep_annotate().

Signed-off-by: Roberto Sassu <[email protected]>
---
security/integrity/ima/ima.h | 2 +-
security/integrity/ima/ima_iint.c | 17 +++++++++++------
security/integrity/ima/ima_main.c | 6 +++---
3 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index cea4517e73ab..c9140a57b591 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -216,7 +216,7 @@ static inline void ima_inode_set_iint(const struct inode *inode,
}

struct ima_iint_cache *ima_iint_find(struct inode *inode);
-struct ima_iint_cache *ima_inode_get(struct inode *inode);
+struct ima_iint_cache *ima_inode_get(struct inode *inode, bool nested);
void ima_inode_free(struct inode *inode);
void __init ima_iintcache_init(void);

diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c
index e7c9c216c1c6..c98a30815c8a 100644
--- a/security/integrity/ima/ima_iint.c
+++ b/security/integrity/ima/ima_iint.c
@@ -39,9 +39,12 @@ struct ima_iint_cache *ima_iint_find(struct inode *inode)
* files both on overlayfs and on underlying fs, we need to annotate the iint
* mutex to avoid lockdep false positives related to IMA + overlayfs.
* See ovl_lockdep_annotate_inode_mutex_key() for more details.
+ *
+ * In addition to that, safely ignored nested locks for digest lists, since
+ * the digest_cache LSM prevents circular dependencies.
*/
static inline void ima_iint_lockdep_annotate(struct ima_iint_cache *iint,
- struct inode *inode)
+ struct inode *inode, bool nested)
{
#ifdef CONFIG_LOCKDEP
static struct lock_class_key ima_iint_mutex_key[IMA_MAX_NESTING];
@@ -51,12 +54,13 @@ static inline void ima_iint_lockdep_annotate(struct ima_iint_cache *iint,
if (WARN_ON_ONCE(depth < 0 || depth >= IMA_MAX_NESTING))
depth = 0;

- lockdep_set_class(&iint->mutex, &ima_iint_mutex_key[depth]);
+ lockdep_set_class_and_subclass(&iint->mutex, &ima_iint_mutex_key[depth],
+ nested);
#endif
}

static void ima_iint_init_always(struct ima_iint_cache *iint,
- struct inode *inode)
+ struct inode *inode, bool nested)
{
iint->ima_hash = NULL;
iint->version = 0;
@@ -69,7 +73,7 @@ static void ima_iint_init_always(struct ima_iint_cache *iint,
iint->ima_creds_status = INTEGRITY_UNKNOWN;
iint->measured_pcrs = 0;
mutex_init(&iint->mutex);
- ima_iint_lockdep_annotate(iint, inode);
+ ima_iint_lockdep_annotate(iint, inode, nested);
}

static void ima_iint_free(struct ima_iint_cache *iint)
@@ -82,13 +86,14 @@ static void ima_iint_free(struct ima_iint_cache *iint)
/**
* ima_inode_get - Find or allocate an iint associated with an inode
* @inode: Pointer to the inode
+ * @nested: Whether or not the iint->mutex lock can be nested
*
* Find an iint associated with an inode, and allocate a new one if not found.
* Caller must lock i_mutex.
*
* Return: An iint on success, NULL on error.
*/
-struct ima_iint_cache *ima_inode_get(struct inode *inode)
+struct ima_iint_cache *ima_inode_get(struct inode *inode, bool nested)
{
struct ima_iint_cache *iint;

@@ -100,7 +105,7 @@ struct ima_iint_cache *ima_inode_get(struct inode *inode)
if (!iint)
return NULL;

- ima_iint_init_always(iint, inode);
+ ima_iint_init_always(iint, inode, nested);

inode->i_flags |= S_IMA;
ima_inode_set_iint(inode, iint);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 780627b0cde7..18285fc8ac07 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -248,7 +248,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
inode_lock(inode);

if (action) {
- iint = ima_inode_get(inode);
+ iint = ima_inode_get(inode, func == DIGEST_LIST_CHECK);
if (!iint)
rc = -ENOMEM;
}
@@ -699,7 +699,7 @@ static void ima_post_create_tmpfile(struct mnt_idmap *idmap,
return;

/* Nothing to do if we can't allocate memory */
- iint = ima_inode_get(inode);
+ iint = ima_inode_get(inode, false);
if (!iint)
return;

@@ -731,7 +731,7 @@ static void ima_post_path_mknod(struct mnt_idmap *idmap, struct dentry *dentry)
return;

/* Nothing to do if we can't allocate memory */
- iint = ima_inode_get(inode);
+ iint = ima_inode_get(inode, false);
if (!iint)
return;

--
2.34.1


2024-04-15 16:13:47

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 4/9] ima: Add digest_cache_measure/appraise boot-time built-in policies

From: Roberto Sassu <[email protected]>

Specify the 'digest_cache_measure' boot-time policy with 'ima_policy=' in
the kernel command line to add the following rule at the beginning of the
IMA policy, before other rules:

measure func=DIGEST_LIST_CHECK pcr=12

which will measure digest lists into PCR 12 (or the value in
CONFIG_IMA_DIGEST_CACHE_MEASURE_PCR_IDX).

Specify 'digest_cache_appraise' to add the following rule at the beginning,
before other rules:

appraise func=DIGEST_LIST_CHECK appraise_type=imasig|modsig

which will appraise digest lists with IMA signatures or module-style
appended signatures.

Adding those rule at the beginning rather than at the end is necessary to
ensure that digest lists are measured and appraised in the initial ram
disk, which would be otherwise prevented by the dont_ rules.

Signed-off-by: Roberto Sassu <[email protected]>
---
.../admin-guide/kernel-parameters.txt | 10 +++++-
security/integrity/ima/Kconfig | 10 ++++++
security/integrity/ima/ima_policy.c | 35 +++++++++++++++++++
3 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 902ecd92a29f..df877588decc 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2050,7 +2050,8 @@
ima_policy= [IMA]
The builtin policies to load during IMA setup.
Format: "tcb | appraise_tcb | secure_boot |
- fail_securely | critical_data"
+ fail_securely | critical_data |
+ digest_cache_measure | digest_cache_appraise"

The "tcb" policy measures all programs exec'd, files
mmap'd for exec, and all files opened with the read
@@ -2072,6 +2073,13 @@
The "critical_data" policy measures kernel integrity
critical data.

+ The "digest_cache_measure" policy measures digest lists
+ into PCR 12 (can be changed with kernel config).
+
+ The "digest_cache_appraise" policy appraises digest
+ lists with IMA signatures or module-style appended
+ signatures.
+
ima_tcb [IMA] Deprecated. Use ima_policy= instead.
Load a policy which meets the needs of the Trusted
Computing Base. This means IMA will measure all
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 475c32615006..6a481019fb6e 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -321,4 +321,14 @@ config IMA_DISABLE_HTABLE
help
This option disables htable to allow measurement of duplicate records.

+config IMA_DIGEST_CACHE_MEASURE_PCR_IDX
+ int
+ range 8 14
+ default 12
+ help
+ This option determines the TPM PCR register index that IMA uses to
+ maintain the integrity aggregate of the measurement list, when the
+ digest_cache LSM is used (different measurement style). If unsure,
+ use the default 12.
+
endif
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 9e13b88b0ed5..f049543f6b64 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -254,6 +254,21 @@ static struct ima_rule_entry critical_data_rules[] __ro_after_init = {
{.action = MEASURE, .func = CRITICAL_DATA, .flags = IMA_FUNC},
};

+static struct ima_rule_entry measure_digest_cache_rule __ro_after_init = {
+#ifdef CONFIG_SECURITY_DIGEST_CACHE
+ .action = MEASURE, .func = DIGEST_LIST_CHECK,
+ .pcr = CONFIG_IMA_DIGEST_CACHE_MEASURE_PCR_IDX,
+ .flags = IMA_FUNC | IMA_PCR
+#endif
+};
+
+static struct ima_rule_entry appraise_digest_cache_rule __ro_after_init = {
+#ifdef CONFIG_SECURITY_DIGEST_CACHE
+ .action = APPRAISE, .func = DIGEST_LIST_CHECK,
+ .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED,
+#endif
+};
+
/* An array of architecture specific rules */
static struct ima_rule_entry *arch_policy_entry __ro_after_init;

@@ -278,6 +293,8 @@ static bool ima_use_appraise_tcb __initdata;
static bool ima_use_secure_boot __initdata;
static bool ima_use_critical_data __initdata;
static bool ima_fail_unverifiable_sigs __ro_after_init;
+static bool ima_digest_cache_measure __ro_after_init;
+static bool ima_digest_cache_appraise __ro_after_init;
static int __init policy_setup(char *str)
{
char *p;
@@ -295,6 +312,10 @@ static int __init policy_setup(char *str)
ima_use_critical_data = true;
else if (strcmp(p, "fail_securely") == 0)
ima_fail_unverifiable_sigs = true;
+ else if (strcmp(p, "digest_cache_measure") == 0)
+ ima_digest_cache_measure = true;
+ else if (strcmp(p, "digest_cache_appraise") == 0)
+ ima_digest_cache_appraise = true;
else
pr_err("policy \"%s\" not found", p);
}
@@ -971,6 +992,20 @@ void __init ima_init_policy(void)
{
int build_appraise_entries, arch_entries;

+ /*
+ * We need to load digest cache rules at the beginning, to avoid dont_
+ * rules causing ours to not be reached.
+ */
+ if (IS_ENABLED(CONFIG_SECURITY_DIGEST_CACHE)) {
+ if (ima_digest_cache_measure)
+ add_rules(&measure_digest_cache_rule, 1,
+ IMA_DEFAULT_POLICY);
+
+ if (ima_digest_cache_appraise)
+ add_rules(&appraise_digest_cache_rule, 1,
+ IMA_DEFAULT_POLICY);
+ }
+
/* if !ima_policy, we load NO default rules */
if (ima_policy)
add_rules(dont_measure_rules, ARRAY_SIZE(dont_measure_rules),
--
2.34.1


2024-04-15 16:14:16

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 5/9] ima: Modify existing boot-time built-in policies with digest cache policies

From: Roberto Sassu <[email protected]>

Setting the boot-time built-in policies 'digest_cache_measure' and
'digest_cache_appraise' is not sufficient to use the digest_cache LSM to
measure and appraise files, since their effect is only to measure and
appraise digest lists.

Modify existing measurement rules if the 'digest_cache_measure' built-in
policy is specified by adding to them:

digest_cache=data pcr=12

Other than enabling the usage of the digest_cache LSM for measurement, the
additional keywords also store measurements in the PCR 12, to not confuse
new style measurements with the original ones still stored on PCR 10.

Modify existing appraisal rules if the 'digest_cache_appraise' built-in
policy is specified by adding to them:

digest_cache=data

The additional keyword enables the usage of digest_cache LSM for appraisal.

Signed-off-by: Roberto Sassu <[email protected]>
---
Documentation/admin-guide/kernel-parameters.txt | 9 +++++++--
security/integrity/ima/ima_policy.c | 14 ++++++++++++++
2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index df877588decc..dc96e6f4eb40 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2074,11 +2074,16 @@
critical data.

The "digest_cache_measure" policy measures digest lists
- into PCR 12 (can be changed with kernel config).
+ into PCR 12 (can be changed with kernel config), enables
+ the digest cache to be used for the other selected
+ measure rules (if compatible), and measures the files
+ with digest not found in the digest list into PCR 12
+ (changeable).

The "digest_cache_appraise" policy appraises digest
lists with IMA signatures or module-style appended
- signatures.
+ signatures, and enables the digest cache to be used for
+ the other selected appraise rules (if compatible).

ima_tcb [IMA] Deprecated. Use ima_policy= instead.
Load a policy which meets the needs of the Trusted
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index f049543f6b64..21bd7a123548 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -918,6 +918,20 @@ static void add_rules(struct ima_rule_entry *entries, int count,
for (i = 0; i < count; i++) {
struct ima_rule_entry *entry;

+ if (IS_ENABLED(CONFIG_SECURITY_DIGEST_CACHE) &&
+ entries[i].action == MEASURE && ima_digest_cache_measure &&
+ ima_digest_cache_func_allowed(&entries[i])) {
+ entries[i].digest_cache_usage |= IMA_DIGEST_CACHE_MEASURE_DATA;
+ entries[i].pcr = CONFIG_IMA_DIGEST_CACHE_MEASURE_PCR_IDX;
+ entries[i].flags |= IMA_PCR;
+ }
+
+ if (IS_ENABLED(CONFIG_SECURITY_DIGEST_CACHE) &&
+ entries[i].action == APPRAISE &&
+ ima_digest_cache_appraise &&
+ ima_digest_cache_func_allowed(&entries[i]))
+ entries[i].digest_cache_usage |= IMA_DIGEST_CACHE_APPRAISE_DATA;
+
if (policy_rule & IMA_DEFAULT_POLICY)
list_add_tail(&entries[i].list, &ima_default_rules);

--
2.34.1


2024-04-15 16:14:18

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 6/9] ima: Store allowed usage in digest cache based on integrity metadata flags

From: Roberto Sassu <[email protected]>

The digest_cache LSM allows integrity providers to record how the digest
list being used to populate the digest cache was verified.

Integrity providers can register a kernel_post_read_file LSM hook
implementation, and call digest_cache_verif_set() providing the result of
the digest list verification, together with the digest list file
descriptor.

IMA implements ima_digest_cache_store_allowed_usage(), storing allowed
usage of the digest cache based on whether or not the digest list the
digest cache is being populated from was measured/appraised.

If the digest list was measured (IMA_MEASURED set in iint->flags),
ima_digest_cache_store_allowed_usage() sets the
IMA_DIGEST_CACHE_MEASURE_DATA in the allowed usage. If the digest list was
appraised (IMA_APPRAISED_SUBMASK), ima_digest_cache_store_allowed_usage()
sets the IMA_DIGEST_CACHE_APPRAISE_DATA in the allowed usage.

Allowed usage based on integrity metadata will be ANDed with the allowed
usage from the IMA policy. Then, the final decision will ultimately depend
on whether or not the calculated digest of the accessed file was found in
the digest cache.

ANDing the usage prevents remote verifiers from receiving an incomplete
IMA measurement list, where measurements are skipped, but there isn't the
digest list the calculated file digest was searched into. It also prevents
successful appraisal without appraising the digest list itself.

Signed-off-by: Roberto Sassu <[email protected]>
---
security/integrity/ima/Makefile | 1 +
security/integrity/ima/ima_digest_cache.c | 45 +++++++++++++++++++++++
security/integrity/ima/ima_digest_cache.h | 21 +++++++++++
security/integrity/ima/ima_main.c | 3 ++
4 files changed, 70 insertions(+)
create mode 100644 security/integrity/ima/ima_digest_cache.c
create mode 100644 security/integrity/ima/ima_digest_cache.h

diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile
index b376d38b4ee6..edd74a7374de 100644
--- a/security/integrity/ima/Makefile
+++ b/security/integrity/ima/Makefile
@@ -14,6 +14,7 @@ ima-$(CONFIG_HAVE_IMA_KEXEC) += ima_kexec.o
ima-$(CONFIG_IMA_BLACKLIST_KEYRING) += ima_mok.o
ima-$(CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS) += ima_asymmetric_keys.o
ima-$(CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS) += ima_queue_keys.o
+ima-$(CONFIG_SECURITY_DIGEST_CACHE) += ima_digest_cache.o

ifeq ($(CONFIG_EFI),y)
ima-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_efi.o
diff --git a/security/integrity/ima/ima_digest_cache.c b/security/integrity/ima/ima_digest_cache.c
new file mode 100644
index 000000000000..0b0fd26cc0d7
--- /dev/null
+++ b/security/integrity/ima/ima_digest_cache.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <[email protected]>
+ *
+ * Integrate with the digest_cache LSM.
+ */
+
+#include <linux/digest_cache.h>
+
+#include "ima_digest_cache.h"
+
+/**
+ * ima_digest_cache_store_allowed_usage - Store allowed usage in digest cache
+ * @file: Digest list file descriptor
+ * @iint: Inode integrity metadata
+ *
+ * Set digest cache allowed usage in the digest cache associated to the
+ * digest list file descriptor. Allowed usage is based on whether or not the
+ * digest list was measured/appraised.
+ */
+void ima_digest_cache_store_allowed_usage(struct file *file,
+ struct ima_iint_cache *iint)
+{
+ u64 allowed_usage = 0;
+ int rc;
+
+ if (iint->flags & IMA_MEASURED)
+ allowed_usage |= IMA_DIGEST_CACHE_MEASURE_DATA;
+ if (iint->flags & IMA_APPRAISED_SUBMASK)
+ allowed_usage |= IMA_DIGEST_CACHE_APPRAISE_DATA;
+
+ /*
+ * Set digest cache allowed usage from integrity metadata flags for
+ * later use.
+ */
+ rc = digest_cache_verif_set(file, "ima", &allowed_usage,
+ sizeof(allowed_usage));
+
+ /* Ignore if fd doesn't have digest cache set (prefetching). */
+ if (rc && rc != -ENOENT)
+ pr_debug("Cannot set verification mask for %s, ret: %d, ignoring\n",
+ file_dentry(file)->d_name.name, rc);
+}
diff --git a/security/integrity/ima/ima_digest_cache.h b/security/integrity/ima/ima_digest_cache.h
new file mode 100644
index 000000000000..f2534a01bb18
--- /dev/null
+++ b/security/integrity/ima/ima_digest_cache.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <[email protected]>
+ *
+ * Header file of ima_digest_cache.c.
+ */
+
+#include "ima.h"
+
+#ifdef CONFIG_SECURITY_DIGEST_CACHE
+void ima_digest_cache_store_allowed_usage(struct file *file,
+ struct ima_iint_cache *iint);
+#else
+static inline void
+ima_digest_cache_store_allowed_usage(struct file *file,
+ struct ima_iint_cache *iint)
+{ }
+
+#endif /* CONFIG_SECURITY_DIGEST_CACHE */
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index e3ca80098c4c..7c968cdb5678 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -28,6 +28,7 @@
#include <linux/iversion.h>

#include "ima.h"
+#include "ima_digest_cache.h"

#ifdef CONFIG_IMA_APPRAISE
int ima_appraise = IMA_APPRAISE_ENFORCE;
@@ -399,6 +400,8 @@ static int process_measurement(struct file *file, const struct cred *cred,
if ((mask & MAY_WRITE) && test_bit(IMA_DIGSIG, &iint->atomic_flags) &&
!(iint->flags & IMA_NEW_FILE))
rc = -EACCES;
+ if (!rc && func == DIGEST_LIST_CHECK)
+ ima_digest_cache_store_allowed_usage(file, iint);
mutex_unlock(&iint->mutex);
kfree(xattr_value);
ima_free_modsig(modsig);
--
2.34.1


2024-04-15 16:14:46

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 7/9] ima: Use digest caches for measurement

From: Roberto Sassu <[email protected]>

Introduce a new measurement style using digest caches, which can be
performed exclusively on non-standard PCRs, to avoid ambiguity.

While a measurement on the standard PCR means that a file was accessed and
had the measured data, a measurement with the digest cache means only that
the calculated file digest was not found in any of the measured digest
lists (any digest list used for the search must be measured, otherwise IMA
wouldn't use it).

The new measurement style does not tell: whether or not the file was
actually accessed (since its measurement is skipped even if it was); in
which sequence files were accessed. So, one has to guess that the system
might have possibly accessed any of the files whose digest is in the
measured digest lists, in any order.

However, it has the following benefits: the IMA measurement list can be
much shorter, system performance can be much better due to less PCR extend
operations (see the performance evaluation in the digest_cache LSM
documentation); the PCR can be predictable as long as the set of measured
digest lists does not change (which obviously happens during software
updates).

The PCR can be predictable because the digest_cache LSM has a prefetching
mechanism that reads digest lists in a deterministic order, until it
finds the digest list containing the digest calculated by IMA from an
accessed file. If IMA measures digest lists, the PCR is extended in a
deterministic order too.

Predictable PCR means that a TPM key can be made dependent on specific PCR
values (or a OR of them, depending on the key policy). Accessing a file
with an unknown digest immediately makes that TPM key unusable, requiring a
reboot to use it again.

This mechanism can be used for the so called implicit remote attestation,
where the ability of a system to respond to challenges based on the private
part of the TPM key means that the system has the expected PCR values
(which would mean that the integrity of the system is ok). This is opposed
to the explicit remote attestation, where a system has to send all its
measurements, to prove to a remote party about its integrity.

If the IMA policy allows the usage of digest caches for the current file
access (except for DIGEST_LIST_CHECK hook, not supported), call the newly
introduced function ima_digest_cache_update_allowed_usage(), to make a
final decision on whether or not a digest cache can be used for measurement
and/or appraisal.

First, call digest_cache_get() to get a digest cache from the file being
accessed. Second, perform a lookup of the calculated file digest in the
digest cache. Third, retrieve the allowed usage from the integrity metadata
flags of the digest list and AND it with the allowed usage from the policy.

If any of the previous step fails, set the allowed usage to zero.

Finally, pass the allowed usage to ima_store_measurement() and, if it has
the IMA_DIGEST_CACHE_MEASURE_DATA flag set, behave as if the file was
successfully added to the IMA measurement list (i.e. set the IMA_MEASURED
flag and the PCR flag from the value specified in the matching policy
rule), but actually don't do it.

Finally, release the digest cache reference acquired with
digest_cache_get(), by calling digest_cache_put().

Signed-off-by: Roberto Sassu <[email protected]>
---
security/integrity/ima/ima.h | 3 +-
security/integrity/ima/ima_api.c | 15 +++++++-
security/integrity/ima/ima_digest_cache.c | 47 +++++++++++++++++++++++
security/integrity/ima/ima_digest_cache.h | 9 +++++
security/integrity/ima/ima_main.c | 14 ++++++-
5 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 337b3b76b28d..865137dfcf22 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -381,7 +381,8 @@ void ima_store_measurement(struct ima_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, const struct modsig *modsig, int pcr,
- struct ima_template_desc *template_desc);
+ struct ima_template_desc *template_desc,
+ u64 allowed_usage);
int process_buffer_measurement(struct mnt_idmap *idmap,
struct inode *inode, const void *buf, int size,
const char *eventname, enum ima_hooks func,
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index f0a1ce10afe8..0bbe19e33584 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -345,7 +345,8 @@ void ima_store_measurement(struct ima_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, const struct modsig *modsig, int pcr,
- struct ima_template_desc *template_desc)
+ struct ima_template_desc *template_desc,
+ u64 allowed_usage)
{
static const char op[] = "add_template_measure";
static const char audit_cause[] = "ENOMEM";
@@ -369,6 +370,18 @@ void ima_store_measurement(struct ima_iint_cache *iint, struct file *file,
if (iint->measured_pcrs & (0x1 << pcr) && !modsig)
return;

+ /*
+ * If digest cache usage was authorized with the IMA policy, the digest
+ * list the digest cache was populated from was measured, and the file
+ * digest was found in the digest cache, mark the file as successfully
+ * measured.
+ */
+ if (allowed_usage & IMA_DIGEST_CACHE_MEASURE_DATA) {
+ iint->flags |= IMA_MEASURED;
+ iint->measured_pcrs |= (0x1 << pcr);
+ return;
+ }
+
result = ima_alloc_init_template(&event_data, &entry, template_desc);
if (result < 0) {
integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename,
diff --git a/security/integrity/ima/ima_digest_cache.c b/security/integrity/ima/ima_digest_cache.c
index 0b0fd26cc0d7..013c69f265d8 100644
--- a/security/integrity/ima/ima_digest_cache.c
+++ b/security/integrity/ima/ima_digest_cache.c
@@ -43,3 +43,50 @@ void ima_digest_cache_store_allowed_usage(struct file *file,
pr_debug("Cannot set verification mask for %s, ret: %d, ignoring\n",
file_dentry(file)->d_name.name, rc);
}
+
+/**
+ * ima_digest_cache_update_allowed_usage - Update digest cache allowed usage
+ * @file: Digest list file descriptor
+ * @iint: Inode integrity metadata
+ * @allowed_usage: Digest cache allowed usage to update
+ *
+ * Update the digest cache allowed usage obtained from the IMA policy. First,
+ * retrieve the digest cache for the passed inode, and do a lookup of the
+ * calculated digest. If the digest is found, update the digest cache allowed
+ * usage with the allowed usage from integrity metadata flags, previously stored
+ * in the digest cache itself with ima_digest_cache_store_allowed_usage().
+ */
+void ima_digest_cache_update_allowed_usage(struct file *file,
+ struct ima_iint_cache *iint,
+ u64 *allowed_usage)
+{
+ struct digest_cache *digest_cache, *found_cache;
+ u64 *iint_allowed_usage;
+ digest_cache_found_t found;
+
+ digest_cache = digest_cache_get(file_dentry(file));
+ if (!digest_cache) {
+ *allowed_usage = 0;
+ return;
+ }
+
+ found = digest_cache_lookup(file_dentry(file), digest_cache,
+ iint->ima_hash->digest,
+ iint->ima_hash->algo);
+ if (!found) {
+ *allowed_usage = 0;
+ goto out;
+ }
+
+ /* AND what is allowed by the policy, and what IMA verified. */
+ found_cache = digest_cache_from_found_t(found);
+ iint_allowed_usage = digest_cache_verif_get(found_cache, "ima");
+ if (!iint_allowed_usage) {
+ *allowed_usage = 0;
+ goto out;
+ }
+
+ *allowed_usage &= *iint_allowed_usage;
+out:
+ digest_cache_put(digest_cache);
+}
diff --git a/security/integrity/ima/ima_digest_cache.h b/security/integrity/ima/ima_digest_cache.h
index f2534a01bb18..cb47c15e975d 100644
--- a/security/integrity/ima/ima_digest_cache.h
+++ b/security/integrity/ima/ima_digest_cache.h
@@ -12,10 +12,19 @@
#ifdef CONFIG_SECURITY_DIGEST_CACHE
void ima_digest_cache_store_allowed_usage(struct file *file,
struct ima_iint_cache *iint);
+void ima_digest_cache_update_allowed_usage(struct file *file,
+ struct ima_iint_cache *iint,
+ u64 *allowed_usage);
#else
static inline void
ima_digest_cache_store_allowed_usage(struct file *file,
struct ima_iint_cache *iint)
{ }

+static inline void
+ima_digest_cache_update_allowed_usage(struct file *file,
+ struct ima_iint_cache *iint,
+ u64 *allowed_usage)
+{ }
+
#endif /* CONFIG_SECURITY_DIGEST_CACHE */
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 7c968cdb5678..0ff5de9bef70 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -223,6 +223,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;
+ u64 digest_cache_usage = 0;

if (!ima_policy_flag || !S_ISREG(inode->i_mode))
return 0;
@@ -233,7 +234,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
*/
action = ima_get_action(file_mnt_idmap(file), inode, cred, secid,
mask, func, &pcr, &template_desc, NULL,
- &allowed_algos, NULL);
+ &allowed_algos, &digest_cache_usage);
violation_check = ((func == FILE_CHECK || func == MMAP_CHECK ||
func == MMAP_CHECK_REQPROT) &&
(ima_policy_flag & IMA_MEASURE));
@@ -364,10 +365,19 @@ static int process_measurement(struct file *file, const struct cred *cred,
if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */
pathname = ima_d_path(&file->f_path, &pathbuf, filename);

+ /*
+ * For now we don't support nested verification with digest caches.
+ * Since we allow IMA policy rules without func=, we have to enforce
+ * this restriction here.
+ */
+ if (rc == 0 && digest_cache_usage && func != DIGEST_LIST_CHECK)
+ ima_digest_cache_update_allowed_usage(file, iint,
+ &digest_cache_usage);
+
if (action & IMA_MEASURE)
ima_store_measurement(iint, file, pathname,
xattr_value, xattr_len, modsig, pcr,
- template_desc);
+ template_desc, digest_cache_usage);
if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
rc = ima_check_blacklist(iint, modsig, pcr);
if (rc != -EPERM) {
--
2.34.1


2024-04-15 16:15:25

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 9/9] ima: Register to the digest_cache LSM notifier and process events

From: Roberto Sassu <[email protected]>

A digest cache used for measurement/appraisal might change over the time
(due to file modification, directory changes). When that happens, IMA
should invalidate the cached integrity result for affected inodes and
evaluate those inodes again.

Implement ima_digest_cache_change(), to be invoked at every notification by
the digest_cache LSM, and register it as a callback with
digest_cache_register_notifier().

For every notification, and if the type of event is DIGEST_CACHE_RESET,
retrieve the inode integrity metadata (if any), and set the
IMA_CHANGE_XATTR atomic flag, so that IMA fully reevaluates the inode in
process_measurement().

Signed-off-by: Roberto Sassu <[email protected]>
---
security/integrity/ima/ima_digest_cache.c | 31 +++++++++++++++++++++++
security/integrity/ima/ima_digest_cache.h | 6 +++++
security/integrity/ima/ima_main.c | 11 +++++++-
3 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/security/integrity/ima/ima_digest_cache.c b/security/integrity/ima/ima_digest_cache.c
index 013c69f265d8..0ab35575ff7c 100644
--- a/security/integrity/ima/ima_digest_cache.c
+++ b/security/integrity/ima/ima_digest_cache.c
@@ -90,3 +90,34 @@ void ima_digest_cache_update_allowed_usage(struct file *file,
out:
digest_cache_put(digest_cache);
}
+
+static int ima_digest_cache_change(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct ima_iint_cache *iint;
+ struct digest_cache_event_data *event_data = data;
+
+ if (event != DIGEST_CACHE_RESET)
+ return NOTIFY_DONE;
+
+ iint = ima_iint_find(event_data->inode);
+ if (!iint) {
+ pr_debug("Integrity metadata not found for inode %lu\n",
+ event_data->inode->i_ino);
+ return NOTIFY_OK;
+ }
+
+ set_bit(IMA_CHANGE_XATTR, &iint->atomic_flags);
+ pr_debug("Integrity metadata of inode %lu successfully reset\n",
+ event_data->inode->i_ino);
+ return NOTIFY_OK;
+}
+
+static struct notifier_block digest_cache_notifier = {
+ .notifier_call = ima_digest_cache_change,
+};
+
+int ima_digest_cache_register_notifier(void)
+{
+ return digest_cache_register_notifier(&digest_cache_notifier);
+}
diff --git a/security/integrity/ima/ima_digest_cache.h b/security/integrity/ima/ima_digest_cache.h
index cb47c15e975d..44c188c2fb93 100644
--- a/security/integrity/ima/ima_digest_cache.h
+++ b/security/integrity/ima/ima_digest_cache.h
@@ -15,6 +15,7 @@ void ima_digest_cache_store_allowed_usage(struct file *file,
void ima_digest_cache_update_allowed_usage(struct file *file,
struct ima_iint_cache *iint,
u64 *allowed_usage);
+int ima_digest_cache_register_notifier(void);
#else
static inline void
ima_digest_cache_store_allowed_usage(struct file *file,
@@ -27,4 +28,9 @@ ima_digest_cache_update_allowed_usage(struct file *file,
u64 *allowed_usage)
{ }

+static inline int ima_digest_cache_register_notifier(void)
+{
+ return 0;
+}
+
#endif /* CONFIG_SECURITY_DIGEST_CACHE */
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 7ae2bd888d41..fe826755acd1 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -1159,8 +1159,17 @@ static int __init init_ima(void)
return error;

error = register_blocking_lsm_notifier(&ima_lsm_policy_notifier);
- if (error)
+ if (error) {
pr_warn("Couldn't register LSM notifier, error %d\n", error);
+ return error;
+ }
+
+ error = ima_digest_cache_register_notifier();
+ if (error) {
+ pr_warn("Couldn't register digest cache notifier, error %d\n",
+ error);
+ unregister_blocking_lsm_notifier(&ima_lsm_policy_notifier);
+ }

if (!error)
ima_update_policy_flags();
--
2.34.1


2024-04-15 16:22:13

by Roberto Sassu

[permalink] [raw]
Subject: [RFC][PATCH v2 8/9] ima: Use digest caches for appraisal

From: Roberto Sassu <[email protected]>

Similarly to measurement, enable the new appraisal style too using digest
caches.

Instead of verifying individual file signatures, verify the signature of
lists of digests and search calculated file digests in those lists.

The benefits are that signed lists of digests already exist (e.g. RPM
package headers), although their format needs to be supported by the
digest_cache LSM, and appraisal with digest lists is computationally much
less expensive than with individual file signatures (see the performance
evaluation of the digest_cache LSM).

Take the allowed usage after the call to
ima_digest_cache_update_allowed_usage(), and pass it to
ima_appraise_measurement().

If EVM is disabled or the file does not have any protected xattr
(evm_verifyxattr() returns INTEGRITY_UNKNOWN or INTEGRITY_NOXATTRS) and the
allowed usage has the IMA_DIGEST_CACHE_APPRAISE_DATA flag set, mark the
file as successfully appraised (i.e. set the integrity status to
INTEGRITY_PASS and return zero).

The digest cache method is tried first, for performance reasons, in the
event there are for example IMA signatures in the system (which are
computationally more expensive to verify). The usage of the digest_cache
LSM has anyway to be authorized in the IMA policy.

Signed-off-by: Roberto Sassu <[email protected]>
---
security/integrity/ima/ima.h | 6 ++++--
security/integrity/ima/ima_appraise.c | 30 ++++++++++++++++++++-------
security/integrity/ima/ima_main.c | 3 ++-
3 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 865137dfcf22..1f0810be9f8a 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -432,7 +432,8 @@ int ima_check_blacklist(struct ima_iint_cache *iint,
int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
- int xattr_len, const struct modsig *modsig);
+ int xattr_len, const struct modsig *modsig,
+ u64 allowed_usage);
int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode,
int mask, enum ima_hooks func);
void ima_update_xattr(struct ima_iint_cache *iint, struct file *file);
@@ -457,7 +458,8 @@ static inline int ima_appraise_measurement(enum ima_hooks func,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len,
- const struct modsig *modsig)
+ const struct modsig *modsig,
+ u64 allowed_usage)
{
return INTEGRITY_UNKNOWN;
}
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 27ccc9a2c09f..3b4d6491e69e 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -478,7 +478,8 @@ int ima_check_blacklist(struct ima_iint_cache *iint,
int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
- int xattr_len, const struct modsig *modsig)
+ int xattr_len, const struct modsig *modsig,
+ u64 allowed_usage)
{
static const char op[] = "appraise_data";
const char *cause = "unknown";
@@ -488,12 +489,18 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
int rc = xattr_len;
bool try_modsig = iint->flags & IMA_MODSIG_ALLOWED && modsig;

- /* If not appraising a modsig, we need an xattr. */
- if (!(inode->i_opflags & IOP_XATTR) && !try_modsig)
+ /*
+ * If not appraising a modsig/there is no digest cache match, we need
+ * an xattr.
+ */
+ if (!(inode->i_opflags & IOP_XATTR) && !try_modsig && !allowed_usage)
return INTEGRITY_UNKNOWN;

- /* If reading the xattr failed and there's no modsig, error out. */
- if (rc <= 0 && !try_modsig) {
+ /*
+ * If reading the xattr failed and there's no modsig/digest cache match,
+ * error out.
+ */
+ if (rc <= 0 && !try_modsig && !allowed_usage) {
if (rc && rc != -ENODATA)
goto out;

@@ -524,8 +531,11 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
case INTEGRITY_UNKNOWN:
break;
case INTEGRITY_NOXATTRS: /* No EVM protected xattrs. */
- /* It's fine not to have xattrs when using a modsig. */
- if (try_modsig)
+ /*
+ * It's fine not to have xattrs when using a modsig or the
+ * digest cache.
+ */
+ if (try_modsig || allowed_usage)
break;
fallthrough;
case INTEGRITY_NOLABEL: /* No security.evm xattr. */
@@ -542,6 +552,12 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
WARN_ONCE(true, "Unexpected integrity status %d\n", status);
}

+ if (allowed_usage & IMA_DIGEST_CACHE_APPRAISE_DATA) {
+ status = INTEGRITY_PASS;
+ rc = 0;
+ goto out;
+ }
+
if (xattr_value)
rc = xattr_verify(func, iint, xattr_value, xattr_len, &status,
&cause);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 0ff5de9bef70..7ae2bd888d41 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -384,7 +384,8 @@ static int process_measurement(struct file *file, const struct cred *cred,
inode_lock(inode);
rc = ima_appraise_measurement(func, iint, file,
pathname, xattr_value,
- xattr_len, modsig);
+ xattr_len, modsig,
+ digest_cache_usage);
inode_unlock(inode);
}
if (!rc)
--
2.34.1