[RFC][PATCH 08/12] ima: Use digest cache for measurement

From: Roberto Sassu
Date: Fri Jul 21 2023 - 12:36:45 EST


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

If a measure rule contains 'digest_cache=content', get the digest cache (if
available) associated to the file being measured.

AND the digest list mask from the IMA policy with the digest list mask of
the digest cache (set depending on the actions done on the digest lists),
to determine if the digest cache can be used for the measurement action.

If the digest cache is enabled, lookup the calculated digest of the file
being accessed and if found, pass the ANDed masks to
ima_store_measurement(). Otherwise, reset the mask to zero.

Finally, if the DIGEST_CACHE_MEASURE flag is set in the mask, mark the file
as measured for the supplied PCR (which cannot be the default one).

At the first digest list accessed, iterate over all digest lists in the
same directory, and measure them to make the PCR predictable. However,
don't parse those digest lists except the requested one, to avoid too much
memory pressure.

Skipping the measurement of cached digests causes less information to be
available to remote verifiers. In particular, they would know that a subset
or all files in the measured digest list could have been accessed, but they
won't know if and when.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
security/integrity/ima/ima.h | 3 ++-
security/integrity/ima/ima_api.c | 16 +++++++++++++++-
security/integrity/ima/ima_main.c | 27 +++++++++++++++++++++++++--
3 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index bdf03525e15..4f40e07954d 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -270,7 +270,8 @@ void ima_store_measurement(struct integrity_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,
+ unsigned int digest_cache_mask);
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 bc25675264c..591d388158b 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -339,7 +339,8 @@ void ima_store_measurement(struct integrity_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,
+ unsigned int digest_cache_mask)
{
static const char op[] = "add_template_measure";
static const char audit_cause[] = "ENOMEM";
@@ -363,6 +364,19 @@ void ima_store_measurement(struct integrity_iint_cache *iint,
if (iint->measured_pcrs & (0x1 << pcr) && !modsig)
return;

+ /*
+ * If the file digest was found in the digest cache, the digest cache
+ * is enabled for measurement, and the digest list was measured, mark
+ * the file as measured, so that it does not appear in the measurement
+ * list (known digest), and the same action is not repeated at the next
+ * access.
+ */
+ if (digest_cache_mask & DIGEST_CACHE_MEASURE) {
+ 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_main.c b/security/integrity/ima/ima_main.c
index 4fdfc399fa6..7a5148ac3af 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -221,6 +221,9 @@ static int process_measurement(struct file *file, const struct cred *cred,
bool violation_check;
enum hash_algo hash_algo;
unsigned int allowed_algos = 0;
+ u8 digest_cache_mask = 0;
+ struct digest_cache *digest_cache = NULL;
+ struct path digest_list_path;

if (!ima_policy_flag || !S_ISREG(inode->i_mode))
return 0;
@@ -231,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_mask);
violation_check = ((func == FILE_CHECK || func == MMAP_CHECK ||
func == MMAP_CHECK_REQPROT) &&
(ima_policy_flag & IMA_MEASURE));
@@ -263,6 +266,18 @@ static int process_measurement(struct file *file, const struct cred *cred,
if (!action)
goto out;

+ if (digest_cache_mask) {
+ if (digest_cache_mask & DIGEST_CACHE_MEASURE)
+ digest_cache_iter_dir(file_dentry(file));
+
+ digest_cache = digest_cache_get(file_dentry(file),
+ &digest_list_path);
+ if (digest_cache)
+ digest_cache_mask &= digest_cache->mask;
+ else
+ digest_cache_mask = 0;
+ }
+
mutex_lock(&iint->mutex);

if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags))
@@ -349,10 +364,17 @@ 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);

+ if (rc == 0 && digest_cache_mask) {
+ if (digest_cache_lookup(digest_cache, iint->ima_hash->digest,
+ iint->ima_hash->algo, pathname))
+ /* Reset the mask, the file digest was not found. */
+ digest_cache_mask = 0;
+ }
+
if (action & IMA_MEASURE)
ima_store_measurement(iint, file, pathname,
xattr_value, xattr_len, modsig, pcr,
- template_desc);
+ template_desc, digest_cache_mask);
if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
rc = ima_check_blacklist(iint, modsig, pcr);
if (rc != -EPERM) {
@@ -391,6 +413,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
out:
if (pathbuf)
__putname(pathbuf);
+ digest_cache_put(digest_cache, &digest_list_path);
if (must_appraise) {
if (rc && (ima_appraise & IMA_APPRAISE_ENFORCE))
return -EACCES;
--
2.34.1