[PATCH 28/28] integrity: Switch from rbtree to LSM-managed blob for integrity_iint_cache

From: Roberto Sassu
Date: Fri Mar 03 2023 - 13:29:17 EST


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

Before the security field of kernel objects could be shared among LSMs with
the LSM stacking feature, IMA and EVM had to rely on an alternative storage
of inode metadata. The association between inode metadata and inode is
maintained through an rbtree.

With the reservation mechanism offered by the LSM infrastructure, the
rbtree is no longer necessary, as each LSM could reserve a space in the
security blob for each inode. Thus, request from the 'integrity' LSM a
space in the security blob for the pointer of inode metadata
(integrity_iint_cache structure).

Prefer this to allocating the integrity_iint_cache structure directly, as
IMA would require it only for a subset of inodes. Always allocating it
would cause a waste of memory.

Introduce two primitives for getting and setting the pointer of
integrity_iint_cache in the security blob, respectively
integrity_inode_get_iint() and integrity_inode_set_iint(). This would make
the code more understandable, as they directly replace rbtree operations.

Locking is not needed, as access to inode metadata is not shared, it is per
inode.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
security/integrity/iint.c | 64 ++++------------------------------
security/integrity/integrity.h | 20 ++++++++++-
2 files changed, 25 insertions(+), 59 deletions(-)

diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index b12215d8b13..1610380de2f 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -14,58 +14,25 @@
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
-#include <linux/rbtree.h>
#include <linux/file.h>
#include <linux/uaccess.h>
#include <linux/security.h>
#include <linux/lsm_hooks.h>
#include "integrity.h"

-static struct rb_root integrity_iint_tree = RB_ROOT;
-static DEFINE_RWLOCK(integrity_iint_lock);
static struct kmem_cache *iint_cache __read_mostly;

struct dentry *integrity_dir;

-/*
- * __integrity_iint_find - return the iint associated with an inode
- */
-static struct integrity_iint_cache *__integrity_iint_find(struct inode *inode)
-{
- struct integrity_iint_cache *iint;
- struct rb_node *n = integrity_iint_tree.rb_node;
-
- while (n) {
- iint = rb_entry(n, struct integrity_iint_cache, rb_node);
-
- if (inode < iint->inode)
- n = n->rb_left;
- else if (inode > iint->inode)
- n = n->rb_right;
- else
- break;
- }
- if (!n)
- return NULL;
-
- return iint;
-}
-
/*
* integrity_iint_find - return the iint associated with an inode
*/
struct integrity_iint_cache *integrity_iint_find(struct inode *inode)
{
- struct integrity_iint_cache *iint;
-
if (!IS_IMA(inode))
return NULL;

- read_lock(&integrity_iint_lock);
- iint = __integrity_iint_find(inode);
- read_unlock(&integrity_iint_lock);
-
- return iint;
+ return integrity_inode_get_iint(inode);
}

static void iint_free(struct integrity_iint_cache *iint)
@@ -94,9 +61,7 @@ static void iint_free(struct integrity_iint_cache *iint)
*/
struct integrity_iint_cache *integrity_inode_get(struct inode *inode)
{
- struct rb_node **p;
- struct rb_node *node, *parent = NULL;
- struct integrity_iint_cache *iint, *test_iint;
+ struct integrity_iint_cache *iint;

/*
* The integrity's "iint_cache" is initialized at security_init(),
@@ -114,26 +79,10 @@ struct integrity_iint_cache *integrity_inode_get(struct inode *inode)
if (!iint)
return NULL;

- write_lock(&integrity_iint_lock);
-
- p = &integrity_iint_tree.rb_node;
- while (*p) {
- parent = *p;
- test_iint = rb_entry(parent, struct integrity_iint_cache,
- rb_node);
- if (inode < test_iint->inode)
- p = &(*p)->rb_left;
- else
- p = &(*p)->rb_right;
- }
-
iint->inode = inode;
- node = &iint->rb_node;
inode->i_flags |= S_IMA;
- rb_link_node(node, parent, p);
- rb_insert_color(node, &integrity_iint_tree);
+ integrity_inode_set_iint(inode, iint);

- write_unlock(&integrity_iint_lock);
return iint;
}

@@ -150,10 +99,8 @@ static void integrity_inode_free(struct inode *inode)
if (!IS_IMA(inode))
return;

- write_lock(&integrity_iint_lock);
- iint = __integrity_iint_find(inode);
- rb_erase(&iint->rb_node, &integrity_iint_tree);
- write_unlock(&integrity_iint_lock);
+ iint = integrity_iint_find(inode);
+ integrity_inode_set_iint(inode, NULL);

iint_free(iint);
}
@@ -193,6 +140,7 @@ static int __init integrity_lsm_init(void)
}

struct lsm_blob_sizes integrity_blob_sizes __lsm_ro_after_init = {
+ .lbs_inode = sizeof(struct integrity_iint_cache *),
.lbs_xattr = 1,
};

diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index a3cbc65f9c6..720c2f183e4 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -18,6 +18,7 @@
#include <crypto/hash.h>
#include <linux/key.h>
#include <linux/audit.h>
+#include <linux/lsm_hooks.h>

/* iint action cache flags */
#define IMA_MEASURE 0x00000001
@@ -157,7 +158,6 @@ struct ima_file_id {

/* integrity data associated with an inode */
struct integrity_iint_cache {
- struct rb_node rb_node; /* rooted in integrity_iint_tree */
struct mutex mutex; /* protects: version, flags, digest */
struct inode *inode; /* back pointer to inode in question */
u64 version; /* track inode changes */
@@ -191,6 +191,24 @@ int integrity_kernel_read(struct file *file, loff_t offset,
extern struct dentry *integrity_dir;
extern struct lsm_blob_sizes integrity_blob_sizes;

+static inline struct integrity_iint_cache *
+integrity_inode_get_iint(const struct inode *inode)
+{
+ struct integrity_iint_cache **iint_sec;
+
+ iint_sec = inode->i_security + integrity_blob_sizes.lbs_inode;
+ return *iint_sec;
+}
+
+static inline void integrity_inode_set_iint(const struct inode *inode,
+ struct integrity_iint_cache *iint)
+{
+ struct integrity_iint_cache **iint_sec;
+
+ iint_sec = inode->i_security + integrity_blob_sizes.lbs_inode;
+ *iint_sec = iint;
+}
+
struct modsig;

#ifdef CONFIG_IMA
--
2.25.1