The goal of this series of patches is to start with the namespacing of
IMA and support auditing within an IMA namespace (IMA-ns) as the first
step.
In this series the IMA namespace is piggybacking on the user namespace
and therefore an IMA namespace is created when a user namespace is
created, although this is done late when SecurityFS is mounted inside
a user namespace. The advantage of piggybacking on the user namespace
is that the user namespace can provide the keys infrastructure that IMA
appraisal support will need later on.
We chose the goal of supporting auditing within an IMA namespace since it
requires the least changes to IMA. Following this series, auditing within
an IMA namespace can be activated by 'root' running the following lines
that rely on a statically linked busybox to be installed on the host for
execution within the minimal container environment. Note that root in
the user namespace, or when entering the namespace using nsenter, must have
the CAP_AUDIT_WRITE capability to be able to set the IMA audit rules in the
namespace.
As root (since audit rules may now only be set by root):
mkdir -p rootfs/{bin,mnt,proc}
cp /sbin/busybox rootfs/bin
cp /sbin/busybox rootfs/bin/busybox2
echo >> rootfs/bin/busybox2
PATH=/bin unshare --user --map-root-user --mount-proc --pid --fork \
--root rootfs busybox sh -c \
"busybox mount -t securityfs /mnt /mnt; \
busybox echo 1 > /mnt/ima/active; \
busybox echo 'audit func=BPRM_CHECK mask=MAY_EXEC' > /mnt/ima/policy; \
busybox2 cat /mnt/ima/policy"
[busybox2 is used to demonstrate 2 audit messages; see below]
Following the audit log on the host the last line cat'ing the IMA policy
inside the namespace would have been audited. Unfortunately the auditing
line is not distinguishable from one stemming from actions on the host.
The hope here is that Richard Brigg's container id support for auditing
would help resolve the problem.
In the above the writing of '1' to the 'active' file is used to activate
the IMA namespace. Future extensions to IMA namespaces will make use of
the configuration stage after the mounting of securityfs and before the
activation to for example choose the measurement log template.
The following lines added to a suitable IMA policy on the host would
cause the execution of the commands inside the container (by uid 1000)
to be measured and audited as well on the host, thus leading to two
auditing messages for the 'busybox2 cat' above and log entries in IMA's
system log.
echo -e "measure func=BPRM_CHECK mask=MAY_EXEC uid=1000\n" \
"audit func=BPRM_CHECK mask=MAY_EXEC uid=1000\n" \
> /sys/kernel/security/ima/policy
The goal of supporting measurement and auditing by the host, of actions
occurring within IMA namespaces, is that users, particularly root,
should not be able to evade the host's IMA policy just by spawning
new IMA namespaces, running programs there, and discarding the namespaces
again. This is achieved through 'hierarchical processing' of file
accesses that are evaluated against the policy of the namespace where
the action occurred and against all namespaces' and their policies leading
back to the root IMA namespace (init_ima_ns).
The patch series adds support for a virtualized SecurityFS with a few
new API calls that are used by IMA namespacing. Only the data relevant
to the IMA namespace are shown. The files and directories of other
security subsystems (TPM, evm, Tomoyo, safesetid) are not showing
up when secruityfs is mounted inside a user namespace.
Much of the code following the virtualization of SecurityFS deals
with moving IMA's variables from various files into the IMA namespace
structure called 'ima_namespace'. When it comes to determining the
current IMA namespace I took the approach to get the current IMA
namespace (get_current_ns()) on the top level and pass the pointer all
the way down to those functions that now need access to the ima_namespace
to get to their variables. This later on comes in handy once hierarchical
processing is implemented in this series where we walk the list of
namespaces backwards and again need to pass the pointer into functions.
This patch also introduces usage of CAP_MAC_ADMIN to allow access to the
IMA policy via reduced capabilities. We would again later on use this
capability to allow users to set file extended attributes for IMA
appraisal support.
My tree with these patches is here:
git fetch https://github.com/stefanberger/linux-ima-namespaces v6.2-rc7+imans.v15.posted
Regards,
Stefan
Links to previous postings:
v1: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v2: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v3: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v4: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v5: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v6: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v7: https://lore.kernel.org/linux-integrity/20211217100659.2iah5prshavjk6v6@wittgenstein/T/#t
v8: https://lore.kernel.org/all/[email protected]/#r
v9: https://lore.kernel.org/linux-integrity/?t=20220131234353
v10: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v11: https://lore.kernel.org/linux-integrity/[email protected]/T/#mcf159fd2132e27514b2089fbf32d6cfb2d363403
v12: https://lore.kernel.org/lkml/[email protected]/
v13: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v14: https://lore.kernel.org/linux-integrity/[email protected]/T/#t
v15:
- Applied fix for memory leak in error path (17/26)
- Gated setting of IMA audit rules in namespace with CAP_AUDIT_WRITE
- Rebased on v6.2-rc6; notes in individual patches
- Test suite now also with tests using UML:
https://github.com/stefanberger/ima-namespaces-tests
v14:
- Rebased on v6.0-rc5
v13:
- Applied Serge's tags and suggestions
v12:
- Fixed issues detected by kernel test robot
- Fixed other minor issues
- WIP test suite: https://github.com/stefanberger/ima-namespaces-tests
v11:
- Added Mimi's R-b's; addressed issues from v10
- Emission of informational audit messages is limited to init_ima_ns
- IMA policy audit rules can now only be set by root to avoid flooding of
audit log by users
- Switch to lazy lsm policy updates for better performance
- Use ima_ns_flags to set IMA_NS_ACTIVE flag indicating active namespace
rather than atomic_t
- Moved patch 'Setup securityfs for IMA namespace' back towards end again
- WIP test suite: https://github.com/stefanberger/ima-namespaces-tests
v10:
- Added A-b's; addressed issues from v9
- Added 2 patches to support freeing of iint after namespace deletion
- Added patch to return error code from securityfs functions
- Added patch to limit number of policy rules in IMA-ns to 1024
v9:
- Rearranged order of patch that adds IMA-ns pointer to user_ns to be before
hierarchical processing patch
- Renamed ns_status variables from status to ns_status to avoid clashes
- Added bug fixing patches to top
- Added patch 'Move arch_policy_entry into ima_namespace'
- Added patch 'Move ima_lsm_policy_notifier into ima_namespace'
- Addressed comments to v8
- Added change comments to individual patches
- Formatted code following checkpatch.pl --strict
v8:
- Rearranged patches to support lazy creation of IMA namespaces
- Fixed issue related to re-auditing of a modified file. This required the
introduction of ns_status structure connected to list starting on an iint
- Fixed issue related to display of uid and gid in IMA policy to show uid
and gid values relative to the user namespace
- Handling of error code during hierarchical processing
v7:
- Dropped 2 patches related to key queues; using &init_ima_ns for all calls
from functions related to key queues where calls need ima_namespace
- Moved ima_namespace to security/integrity/ima/ima.h
- Extended API descriptions with ns parameter where needed
- Using init_ima_ns in functions related to appraisal and xattrs
- SecurityFS: Using ima_ns_from_file() to get ns pointer
- Reformatted to 80 columns per line
v6:
- Removed kref and pointer to user_ns in ima_namespace (patch 1)
- Moved only the policy file dentry into ima_namespace; other dentries are on
stack now and can be discarded
- Merged James's patch simplifying securityfs_remove and dropping dget()
- Added patch with Christian's suggestion to tie opened SecurityFS file to
the user/IMA namespace it belongs to
- Passing missing ima_namespace parameter in functions in ima_kexec.c (ppc64)
- Reverted v5's change to patch 4 related to protection of ima_namespace
v5:
- Followed Christian's suggestions on patch 1. Also, reverted increased reference
counter on init_user_ns since ima_ns doesn't take reference to its user_ns.
- No addtional reference is taken on securityfs dentries for user_ns != init_user_ns.
Updated documentation and removed cleanup of dentries on superblock kill.
(patches 12 & 16)
- Moved else branch to earlier patch (patch 11)
- Protect ima_namespace by taking reference on user namespace for delayed work queue.
(patch 4)
v4:
- For consistency moved 'ns = get_current_ns()' to top of functions
- Merge in James's latest SecurityFS patch
v3:
- Further modifications to virtualized SecurityFS following James's posted patch
- Dropping of early teardown for user_namespaces since not needed anymore
v2:
- Followed Christian's suggestion to virtualize securitytfs; no more securityfs_ns
- Followed James's advice for late 'population' of securityfs for IMA namespaces
- Squashed 2 patches dealing with capabilities
- Added missing 'depends on USER_NS' to Kconfig
- Added missing 'static' to several functions
Christian Brauner (1):
securityfs: rework dentry creation
Mehmet Kayaalp (2):
integrity/ima: Define ns_status for storing namespaced iint data
ima: Namespace audit status flags
Stefan Berger (23):
securityfs: Extend securityfs with namespacing support
ima: Define ima_namespace struct and start moving variables into it
ima: Move arch_policy_entry into ima_namespace
ima: Move ima_htable into ima_namespace
ima: Move measurement list related variables into ima_namespace
ima: Move some IMA policy and filesystem related variables into
ima_namespace
ima: Move IMA securityfs files into ima_namespace or onto stack
ima: Move ima_lsm_policy_notifier into ima_namespace
ima: Switch to lazy lsm policy updates for better performance
ima: Define mac_admin_ns_capable() as a wrapper for ns_capable()
ima: Only accept AUDIT rules for non-init_ima_ns namespaces for now
userns: Add pointer to ima_namespace to user_namespace
ima: Implement hierarchical processing of file accesses
ima: Implement ima_free_policy_rules() for freeing of an ima_namespace
ima: Add functions for creating and freeing of an ima_namespace
integrity: Add optional callback function to integrity_inode_free()
ima: Remove unused iints from the integrity_iint_cache
ima: Setup securityfs for IMA namespace
ima: Introduce securityfs file to activate an IMA namespace
ima: Show owning user namespace's uid and gid when displaying policy
ima: Limit number of policy rules in non-init_ima_ns
ima: Restrict informational audit messages to init_ima_ns
ima: Enable IMA namespaces
include/linux/capability.h | 6 +
include/linux/fs.h | 5 +
include/linux/ima.h | 36 ++
include/linux/integrity.h | 8 +-
include/linux/user_namespace.h | 9 +
init/Kconfig | 14 +
kernel/user.c | 4 +
kernel/user_namespace.c | 2 +
security/inode.c | 83 +++-
security/integrity/iint.c | 26 +-
security/integrity/ima/Makefile | 3 +-
security/integrity/ima/ima.h | 256 ++++++++++--
security/integrity/ima/ima_api.c | 44 ++-
security/integrity/ima/ima_appraise.c | 46 ++-
security/integrity/ima/ima_asymmetric_keys.c | 8 +-
security/integrity/ima/ima_fs.c | 255 +++++++++---
security/integrity/ima/ima_init.c | 19 +-
security/integrity/ima/ima_init_ima_ns.c | 69 ++++
security/integrity/ima/ima_kexec.c | 15 +-
security/integrity/ima/ima_main.c | 238 +++++++++---
security/integrity/ima/ima_ns.c | 61 +++
security/integrity/ima/ima_ns_status.c | 385 +++++++++++++++++++
security/integrity/ima/ima_policy.c | 323 +++++++++++-----
security/integrity/ima/ima_queue.c | 63 ++-
security/integrity/ima/ima_queue_keys.c | 11 +-
security/integrity/ima/ima_template.c | 5 +-
security/integrity/integrity.h | 39 +-
security/security.c | 2 +-
28 files changed, 1672 insertions(+), 363 deletions(-)
create mode 100644 security/integrity/ima/ima_init_ima_ns.c
create mode 100644 security/integrity/ima/ima_ns.c
create mode 100644 security/integrity/ima/ima_ns_status.c
base-commit: 4ec5183ec48656cec489c49f989c508b68b518e3
--
2.37.3
Instead of calling ima_lsm_update_rules() for every namespace upon
invocation of the ima_lsm_policy_change() notification function,
only set a flag in a namespace and defer the call to
ima_lsm_update_rules() to before the policy is accessed the next time,
which is either in ima_policy_start(), when displaying the policy via
the policy file in securityfs, or when calling ima_match_policy().
The performance numbers before this change for a test enabling
and disabling an SELinux module was as follows with a given number
of IMA namespaces that each have a policy containing 2 rules with
SELinux labels:
2: ~9s
192: ~11s
1920: ~80s
With this change:
2: ~6.5s
192: ~7s
1920: ~8.3s
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
---
security/integrity/ima/ima.h | 4 ++++
security/integrity/ima/ima_policy.c | 15 ++++++++++++++-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index db28d6d222a6..4de8ec776611 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -123,6 +123,10 @@ struct ima_h_table {
};
struct ima_namespace {
+ unsigned long ima_ns_flags;
+/* Bit numbers for above flags; use BIT() to get flag */
+#define IMA_NS_LSM_UPDATE_RULES 0
+
/* policy rules */
struct list_head ima_default_rules; /* Kconfig, builtin & arch rules */
struct list_head ima_policy_rules; /* arch & custom rules */
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 8e71b28855a4..19aca18db323 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -252,6 +252,14 @@ static struct ima_rule_entry critical_data_rules[] __ro_after_init = {
{.action = MEASURE, .func = CRITICAL_DATA, .flags = IMA_FUNC},
};
+static void ima_lsm_update_rules(struct ima_namespace *ns);
+
+static inline void ima_lazy_lsm_update_rules(struct ima_namespace *ns)
+{
+ if (test_and_clear_bit(IMA_NS_LSM_UPDATE_RULES, &ns->ima_ns_flags))
+ ima_lsm_update_rules(ns);
+}
+
static int ima_policy __initdata;
static int __init default_measure_policy_setup(char *str)
@@ -498,7 +506,8 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
return NOTIFY_DONE;
ns = container_of(nb, struct ima_namespace, ima_lsm_policy_notifier);
- ima_lsm_update_rules(ns);
+
+ set_bit(IMA_NS_LSM_UPDATE_RULES, &ns->ima_ns_flags);
return NOTIFY_OK;
}
@@ -752,6 +761,8 @@ int ima_match_policy(struct ima_namespace *ns,
if (template_desc && !*template_desc)
*template_desc = ima_template_desc_current();
+ ima_lazy_lsm_update_rules(ns);
+
rcu_read_lock();
ima_rules_tmp = rcu_dereference(ns->ima_rules);
list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
@@ -2016,6 +2027,8 @@ void *ima_policy_start(struct seq_file *m, loff_t *pos)
struct ima_rule_entry *entry;
struct list_head *ima_rules_tmp;
+ ima_lazy_lsm_update_rules(ns);
+
rcu_read_lock();
ima_rules_tmp = rcu_dereference(ns->ima_rules);
list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
--
2.37.3
Move the variables ima_write_mutex, ima_fs_flag, and valid_policy, which
are related to updating the IMA policy, into the ima_namespace. This way
each IMA namespace can set these variables independently in its instance
of securityfs.
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
---
security/integrity/ima/ima.h | 5 ++++
security/integrity/ima/ima_fs.c | 32 +++++++++++-------------
security/integrity/ima/ima_init_ima_ns.c | 4 +++
3 files changed, 23 insertions(+), 18 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 9b751f796c77..4141a219bdd6 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -137,6 +137,11 @@ struct ima_namespace {
struct ima_h_table ima_htable;
struct list_head ima_measurements; /* list of all measurements */
unsigned long binary_runtime_size; /* used by init_ima_ns */
+
+ /* securityfs support related variables */
+ struct mutex ima_write_mutex;
+ unsigned long ima_fs_flags;
+ int valid_policy;
} __randomize_layout;
extern struct ima_namespace init_ima_ns;
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 5ef0e2b2cf64..4cf786f0bba8 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -24,8 +24,6 @@
#include "ima.h"
-static DEFINE_MUTEX(ima_write_mutex);
-
bool ima_canonical_fmt;
static int __init default_canonical_fmt_setup(char *str)
{
@@ -36,8 +34,6 @@ static int __init default_canonical_fmt_setup(char *str)
}
__setup("ima_canonical_fmt", default_canonical_fmt_setup);
-static int valid_policy = 1;
-
static ssize_t ima_show_htable_value(char __user *buf, size_t count,
loff_t *ppos, atomic_long_t *val)
{
@@ -338,7 +334,7 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf,
goto out;
}
- result = mutex_lock_interruptible(&ima_write_mutex);
+ result = mutex_lock_interruptible(&ns->ima_write_mutex);
if (result < 0)
goto out_free;
@@ -353,12 +349,12 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf,
} else {
result = ima_parse_add_rule(ns, data);
}
- mutex_unlock(&ima_write_mutex);
+ mutex_unlock(&ns->ima_write_mutex);
out_free:
kfree(data);
out:
if (result < 0)
- valid_policy = 0;
+ ns->valid_policy = 0;
return result;
}
@@ -375,8 +371,6 @@ enum ima_fs_flags {
IMA_FS_BUSY,
};
-static unsigned long ima_fs_flags;
-
#ifdef CONFIG_IMA_READ_POLICY
static const struct seq_operations ima_policy_seqops = {
.start = ima_policy_start,
@@ -391,6 +385,8 @@ static const struct seq_operations ima_policy_seqops = {
*/
static int ima_open_policy(struct inode *inode, struct file *filp)
{
+ struct ima_namespace *ns = &init_ima_ns;
+
if (!(filp->f_flags & O_WRONLY)) {
#ifndef CONFIG_IMA_READ_POLICY
return -EACCES;
@@ -402,7 +398,7 @@ static int ima_open_policy(struct inode *inode, struct file *filp)
return seq_open(filp, &ima_policy_seqops);
#endif
}
- if (test_and_set_bit(IMA_FS_BUSY, &ima_fs_flags))
+ if (test_and_set_bit(IMA_FS_BUSY, &ns->ima_fs_flags))
return -EBUSY;
return 0;
}
@@ -416,25 +412,25 @@ static int ima_open_policy(struct inode *inode, struct file *filp)
*/
static int ima_release_policy(struct inode *inode, struct file *file)
{
- const char *cause = valid_policy ? "completed" : "failed";
struct ima_namespace *ns = &init_ima_ns;
+ const char *cause = ns->valid_policy ? "completed" : "failed";
if ((file->f_flags & O_ACCMODE) == O_RDONLY)
return seq_release(inode, file);
- if (valid_policy && ima_check_policy(ns) < 0) {
+ if (ns->valid_policy && ima_check_policy(ns) < 0) {
cause = "failed";
- valid_policy = 0;
+ ns->valid_policy = 0;
}
pr_info("policy update %s\n", cause);
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL,
- "policy_update", cause, !valid_policy, 0);
+ "policy_update", cause, !ns->valid_policy, 0);
- if (!valid_policy) {
+ if (!ns->valid_policy) {
ima_delete_rules(ns);
- valid_policy = 1;
- clear_bit(IMA_FS_BUSY, &ima_fs_flags);
+ ns->valid_policy = 1;
+ clear_bit(IMA_FS_BUSY, &ns->ima_fs_flags);
return 0;
}
@@ -443,7 +439,7 @@ static int ima_release_policy(struct inode *inode, struct file *file)
securityfs_remove(ima_policy);
ima_policy = NULL;
#elif defined(CONFIG_IMA_WRITE_POLICY)
- clear_bit(IMA_FS_BUSY, &ima_fs_flags);
+ clear_bit(IMA_FS_BUSY, &ns->ima_fs_flags);
#elif defined(CONFIG_IMA_READ_POLICY)
inode->i_mode &= ~S_IWUSR;
#endif
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
index a7477072c587..425eed1c6838 100644
--- a/security/integrity/ima/ima_init_ima_ns.c
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -26,6 +26,10 @@ static int ima_init_namespace(struct ima_namespace *ns)
else
ns->binary_runtime_size = ULONG_MAX;
+ mutex_init(&ns->ima_write_mutex);
+ ns->valid_policy = 1;
+ ns->ima_fs_flags = 0;
+
return 0;
}
--
2.37.3
Implement ima_free_policy_rules() to free the current custom IMA policy's
rules. This function will be called when an ima_namespace is freed.
Signed-off-by: Stefan Berger <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Reviewed-by: Serge Hallyn <[email protected]>
---
v10:
- Not calling ima_delete_rules() anymore
- Move access check from ima_delete_rules into very last patch
v9:
- Only reset temp_ima_appraise when using init_ima_ns.
---
security/integrity/ima/ima.h | 1 +
security/integrity/ima/ima_policy.c | 14 ++++++++++++++
2 files changed, 15 insertions(+)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 5d647011429b..efe9c82b4396 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -333,6 +333,7 @@ void ima_update_policy_flags(struct ima_namespace *ns);
ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule);
void ima_delete_rules(struct ima_namespace *ns);
int ima_check_policy(struct ima_namespace *ns);
+void ima_free_policy_rules(struct ima_namespace *ns);
void *ima_policy_start(struct seq_file *m, loff_t *pos);
void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos);
void ima_policy_stop(struct seq_file *m, void *v);
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 2a2c88f7d135..10b85642188f 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -2014,6 +2014,20 @@ void ima_delete_rules(struct ima_namespace *ns)
}
}
+/**
+ * ima_free_policy_rules - free all policy rules
+ * @ns: IMA namespace that has the policy
+ */
+void ima_free_policy_rules(struct ima_namespace *ns)
+{
+ struct ima_rule_entry *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, &ns->ima_policy_rules, list) {
+ list_del(&entry->list);
+ ima_free_rule(entry);
+ }
+}
+
#define __ima_hook_stringify(func, str) (#func),
const char *const func_tokens[] = {
--
2.37.3
For non-init_ima_ns namespaces, only accept AUDIT rules for now. Reject
all rules that require support for measuring, appraisal, and hashing.
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
---
v9:
- Jump to err_audit when unsupported rules are detected
---
security/integrity/ima/ima_policy.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 19aca18db323..2a2c88f7d135 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1912,6 +1912,17 @@ static int ima_parse_rule(struct ima_namespace *ns,
result = -EINVAL;
break;
}
+
+ /* IMA namespace only accepts AUDIT rules */
+ if (ns != &init_ima_ns && result == 0) {
+ switch (entry->action) {
+ case MEASURE:
+ case APPRAISE:
+ case HASH:
+ result = -EINVAL;
+ goto err_audit;
+ }
+ }
}
if (!result && !ima_validate_rule(entry))
result = -EINVAL;
@@ -1933,6 +1944,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
"verity rules should include d-ngv2");
}
+err_audit:
audit_log_format(ab, "res=%d", !result);
audit_log_end(ab);
return result;
--
2.37.3
Add a pointer to ima_namespace to the user_namespace and initialize
the init_user_ns with a pointer to init_ima_ns. We need a pointer from
the user namespace to its associated IMA namespace since IMA namespaces
are piggybacking on user namespaces.
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
---
v13:
- Added comment to ima_namespace pointer in user_namespace structure
following Serge's suggestion
v11:
- Added lost A-b from Christian back
- Added sentence to patch description explaining why we need the pointer
v9:
- Deferred implementation of ima_ns_from_user_ns() to later patch
---
include/linux/ima.h | 2 ++
include/linux/user_namespace.h | 9 +++++++++
kernel/user.c | 4 ++++
3 files changed, 15 insertions(+)
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 5a0b2a285a18..2038e2e2a88d 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -14,6 +14,8 @@
#include <crypto/hash_info.h>
struct linux_binprm;
+extern struct ima_namespace init_ima_ns;
+
#ifdef CONFIG_IMA
extern enum hash_algo ima_get_current_hash_algo(void);
extern int ima_bprm_check(struct linux_binprm *bprm);
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 45f09bec02c4..c4b4f07b642e 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -36,6 +36,7 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */
#define USERNS_INIT_FLAGS USERNS_SETGROUPS_ALLOWED
struct ucounts;
+struct ima_namespace;
enum ucount_type {
UCOUNT_USER_NAMESPACES,
@@ -102,6 +103,14 @@ struct user_namespace {
struct ucounts *ucounts;
long ucount_max[UCOUNT_COUNTS];
long rlimit_max[UCOUNT_RLIMIT_COUNTS];
+#ifdef CONFIG_IMA_NS
+ /* Pointer to ima_ns which this user_ns created. Can be null. IMA's
+ * file access checks will walk the userns->parent chain and check
+ * against all active ima_ns's. Note that when the user_ns is
+ * freed, the ima_ns is guaranteed to be free-able.
+ */
+ struct ima_namespace *ima_ns;
+#endif
} __randomize_layout;
struct ucounts {
diff --git a/kernel/user.c b/kernel/user.c
index d667debeafd6..2c1419215ecc 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -19,6 +19,7 @@
#include <linux/export.h>
#include <linux/user_namespace.h>
#include <linux/proc_ns.h>
+#include <linux/ima.h>
/*
* userns count is 1 for root user, 1 for init_uts_ns,
@@ -67,6 +68,9 @@ struct user_namespace init_user_ns = {
.keyring_name_list = LIST_HEAD_INIT(init_user_ns.keyring_name_list),
.keyring_sem = __RWSEM_INITIALIZER(init_user_ns.keyring_sem),
#endif
+#ifdef CONFIG_IMA_NS
+ .ima_ns = &init_ima_ns,
+#endif
};
EXPORT_SYMBOL_GPL(init_user_ns);
--
2.37.3
Move the ima_lsm_policy_notifier into the ima_namespace. Each IMA
namespace can now register its own LSM policy change notifier callback.
The policy change notifier for the init_ima_ns still remains in init_ima()
and therefore handle the registration of the callback for all other
namespaces in init_ima_namespace().
Rate-limit the kernel warning 'rule for LSM <label> is undefined` for
IMA namespace to avoid flooding the kernel log with this type of message.
Signed-off-by: Stefan Berger <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
---
v15:
- Pass ima_namespace into ima_match_rules() to pass to ima_lsm_copy_rule()
v11:
- Renamed 'rc' to 'ret'
- Use pr_warn_ratelimited('rule for LSM...') for IMA namespaces
v10:
- Only call pr_warn('rule for LSM <label> is undefined`) for init_ima_ns
---
security/integrity/ima/ima.h | 2 ++
security/integrity/ima/ima_init_ima_ns.c | 14 ++++++++++
security/integrity/ima/ima_main.c | 6 +----
security/integrity/ima/ima_policy.c | 34 ++++++++++++++++--------
4 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 75993ad061fa..db28d6d222a6 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -144,6 +144,8 @@ struct ima_namespace {
int valid_policy;
struct dentry *ima_policy;
+
+ struct notifier_block ima_lsm_policy_notifier;
} __randomize_layout;
extern struct ima_namespace init_ima_ns;
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
index 425eed1c6838..c4fe8f3e9a73 100644
--- a/security/integrity/ima/ima_init_ima_ns.c
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -10,6 +10,8 @@
static int ima_init_namespace(struct ima_namespace *ns)
{
+ int ret;
+
INIT_LIST_HEAD(&ns->ima_default_rules);
INIT_LIST_HEAD(&ns->ima_policy_rules);
INIT_LIST_HEAD(&ns->ima_temp_rules);
@@ -30,6 +32,15 @@ static int ima_init_namespace(struct ima_namespace *ns)
ns->valid_policy = 1;
ns->ima_fs_flags = 0;
+ if (ns != &init_ima_ns) {
+ ns->ima_lsm_policy_notifier.notifier_call =
+ ima_lsm_policy_change;
+ ret = register_blocking_lsm_notifier
+ (&ns->ima_lsm_policy_notifier);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
@@ -39,5 +50,8 @@ int __init ima_ns_init(void)
}
struct ima_namespace init_ima_ns = {
+ .ima_lsm_policy_notifier = {
+ .notifier_call = ima_lsm_policy_change,
+ },
};
EXPORT_SYMBOL(init_ima_ns);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index e3a3a54dc79e..c7a5f47b50fc 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -38,10 +38,6 @@ int ima_appraise;
int __ro_after_init ima_hash_algo = HASH_ALGO_SHA1;
static int hash_setup_done;
-static struct notifier_block ima_lsm_policy_notifier = {
- .notifier_call = ima_lsm_policy_change,
-};
-
static int __init hash_setup(char *str)
{
struct ima_template_desc *template_desc = ima_template_desc_current();
@@ -1096,7 +1092,7 @@ static int __init init_ima(void)
if (error)
return error;
- error = register_blocking_lsm_notifier(&ima_lsm_policy_notifier);
+ error = register_blocking_lsm_notifier(&ns->ima_lsm_policy_notifier);
if (error)
pr_warn("Couldn't register LSM notifier, error %d\n", error);
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 502b1e8021a6..8e71b28855a4 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -392,7 +392,8 @@ static void ima_free_rule(struct ima_rule_entry *entry)
kfree(entry);
}
-static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_rule_entry *entry)
+static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_namespace *ns,
+ struct ima_rule_entry *entry)
{
struct ima_rule_entry *nentry;
int i;
@@ -417,19 +418,26 @@ static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_rule_entry *entry)
ima_filter_rule_init(nentry->lsm[i].type, Audit_equal,
nentry->lsm[i].args_p,
&nentry->lsm[i].rule);
- if (!nentry->lsm[i].rule)
- pr_warn("rule for LSM \'%s\' is undefined\n",
- nentry->lsm[i].args_p);
+ if (!nentry->lsm[i].rule) {
+ if (ns == &init_ima_ns)
+ pr_warn("rule for LSM \'%s\' is undefined\n",
+ nentry->lsm[i].args_p);
+ else
+ pr_warn_ratelimited
+ ("rule for LSM \'%s\' is undefined\n",
+ nentry->lsm[i].args_p);
+ }
}
return nentry;
}
-static int ima_lsm_update_rule(struct ima_rule_entry *entry)
+static int ima_lsm_update_rule(struct ima_namespace *ns,
+ struct ima_rule_entry *entry)
{
int i;
struct ima_rule_entry *nentry;
- nentry = ima_lsm_copy_rule(entry);
+ nentry = ima_lsm_copy_rule(ns, entry);
if (!nentry)
return -ENOMEM;
@@ -473,7 +481,7 @@ static void ima_lsm_update_rules(struct ima_namespace *ns)
if (!ima_rule_contains_lsm_cond(entry))
continue;
- result = ima_lsm_update_rule(entry);
+ result = ima_lsm_update_rule(ns, entry);
if (result) {
pr_err("lsm rule update error %d\n", result);
return;
@@ -484,12 +492,14 @@ static void ima_lsm_update_rules(struct ima_namespace *ns)
int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
void *lsm_data)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns;
if (event != LSM_POLICY_CHANGE)
return NOTIFY_DONE;
+ ns = container_of(nb, struct ima_namespace, ima_lsm_policy_notifier);
ima_lsm_update_rules(ns);
+
return NOTIFY_OK;
}
@@ -544,6 +554,7 @@ static bool ima_match_rule_data(struct ima_rule_entry *rule,
/**
* ima_match_rules - determine whether an inode matches the policy rule.
+ * @ns: IMA namespace that has the policy
* @rule: a pointer to a rule
* @mnt_userns: user namespace of the mount the inode was found from
* @inode: a pointer to an inode
@@ -555,7 +566,8 @@ static bool ima_match_rule_data(struct ima_rule_entry *rule,
*
* Returns true on rule match, false on failure.
*/
-static bool ima_match_rules(struct ima_rule_entry *rule,
+static bool ima_match_rules(struct ima_namespace *ns,
+ struct ima_rule_entry *rule,
struct user_namespace *mnt_userns,
struct inode *inode, const struct cred *cred,
u32 secid, enum ima_hooks func, int mask,
@@ -657,7 +669,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule,
}
if (rc == -ESTALE && !rule_reinitialized) {
- lsm_rule = ima_lsm_copy_rule(rule);
+ lsm_rule = ima_lsm_copy_rule(ns, rule);
if (lsm_rule) {
rule_reinitialized = true;
goto retry;
@@ -747,7 +759,7 @@ int ima_match_policy(struct ima_namespace *ns,
if (!(entry->action & actmask))
continue;
- if (!ima_match_rules(entry, mnt_userns, inode, cred, secid,
+ if (!ima_match_rules(ns, entry, mnt_userns, inode, cred, secid,
func, mask, func_data))
continue;
--
2.37.3
Implement hierarchical processing of file accesses in IMA namespaces by
walking the list of user namespaces towards the root. This way file
accesses can be audited in an IMA namespace and also be evaluated against
the IMA policies of parent IMA namespaces.
Pass the user_namespace into process_measurement since we will be walking
the hierarchy of user_namespaces towards the init_user_ns and we can easily
derive the ima_namespace from the user_namespace.
__process_measurement() returns either 0 or -EACCES. For hierarchical
processing remember the -EACCES returned by this function but continue
to the parent user namespace. At the end either return 0 or -EACCES
if an error occurred in one of the IMA namespaces.
Currently the ima_ns pointer of the user_namespace is always NULL except
at the init_user_ns, so test ima_ns for NULL pointer and skip the call to
__process_measurement() if it is NULL. Once IMA namespacing is fully
enabled, the pointer may still be NULL due to late initialization of the
IMA namespace.
Signed-off-by: Stefan Berger <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Reviewed-by: Serge Hallyn <[email protected]>
---
v10:
- Fixed compilation issue
v9:
- Switch callers to pass user_namespace rather than ima_namespace with
potential NULL pointer
- Add default case to switch statement and warn if this happens
- Implement ima_ns_from_user_ns() in this patch now
---
security/integrity/ima/ima.h | 8 ++++
security/integrity/ima/ima_main.c | 76 +++++++++++++++++++++++--------
2 files changed, 65 insertions(+), 19 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 69f95ed0b8c6..5d647011429b 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -498,4 +498,12 @@ struct user_namespace *ima_user_ns_from_file(const struct file *filp)
return file_sb_user_ns(filp);
}
+static inline struct ima_namespace
+*ima_ns_from_user_ns(struct user_namespace *user_ns)
+{
+ if (user_ns == &init_user_ns)
+ return &init_ima_ns;
+ return NULL;
+}
+
#endif /* __LINUX_IMA_H */
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index c7a5f47b50fc..55ddf2c8506a 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -196,10 +196,10 @@ void ima_file_free(struct file *file)
ima_check_last_writer(iint, inode, file);
}
-static int process_measurement(struct ima_namespace *ns,
- struct file *file, const struct cred *cred,
- u32 secid, char *buf, loff_t size, int mask,
- enum ima_hooks func)
+static int __process_measurement(struct ima_namespace *ns,
+ struct file *file, const struct cred *cred,
+ u32 secid, char *buf, loff_t size, int mask,
+ enum ima_hooks func)
{
struct inode *inode = file_inode(file);
struct integrity_iint_cache *iint = NULL;
@@ -393,6 +393,41 @@ static int process_measurement(struct ima_namespace *ns,
return 0;
}
+static int process_measurement(struct user_namespace *user_ns,
+ struct file *file, const struct cred *cred,
+ u32 secid, char *buf, loff_t size, int mask,
+ enum ima_hooks func)
+{
+ struct ima_namespace *ns;
+ int ret = 0;
+
+ while (user_ns) {
+ ns = ima_ns_from_user_ns(user_ns);
+ if (ns) {
+ int rc;
+
+ rc = __process_measurement(ns, file, cred, secid, buf,
+ size, mask, func);
+ switch (rc) {
+ case 0:
+ break;
+ case -EACCES:
+ /* return this error at the end but continue */
+ ret = -EACCES;
+ break;
+ default:
+ /* should not happen */
+ ret = -EACCES;
+ WARN_ON_ONCE(true);
+ }
+ }
+
+ user_ns = user_ns->parent;
+ }
+
+ return ret;
+}
+
/**
* ima_file_mmap - based on policy, collect/store measurement.
* @file: pointer to the file to be measured (May be NULL)
@@ -406,13 +441,14 @@ static int process_measurement(struct ima_namespace *ns,
*/
int ima_file_mmap(struct file *file, unsigned long prot)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct user_namespace *user_ns = current_user_ns();
u32 secid;
if (file && (prot & PROT_EXEC)) {
security_current_getsecid_subj(&secid);
- return process_measurement(ns, file, current_cred(), secid,
- NULL, 0, MAY_EXEC, MMAP_CHECK);
+ return process_measurement(user_ns, file, current_cred(),
+ secid, NULL, 0, MAY_EXEC,
+ MMAP_CHECK);
}
return 0;
@@ -488,19 +524,19 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot)
*/
int ima_bprm_check(struct linux_binprm *bprm)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct user_namespace *user_ns = current_user_ns();
int ret;
u32 secid;
security_current_getsecid_subj(&secid);
- ret = process_measurement(ns, bprm->file, current_cred(), secid, NULL,
- 0, MAY_EXEC, BPRM_CHECK);
+ ret = process_measurement(user_ns, bprm->file, current_cred(), secid,
+ NULL, 0, MAY_EXEC, BPRM_CHECK);
if (ret)
return ret;
security_cred_getsecid(bprm->cred, &secid);
- return process_measurement(ns, bprm->file, bprm->cred, secid, NULL, 0,
- MAY_EXEC, CREDS_CHECK);
+ return process_measurement(user_ns, bprm->file, bprm->cred, secid,
+ NULL, 0, MAY_EXEC, CREDS_CHECK);
}
/**
@@ -515,11 +551,12 @@ int ima_bprm_check(struct linux_binprm *bprm)
*/
int ima_file_check(struct file *file, int mask)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct user_namespace *user_ns = current_user_ns();
u32 secid;
security_current_getsecid_subj(&secid);
- return process_measurement(ns, file, current_cred(), secid, NULL, 0,
+ return process_measurement(user_ns, file, current_cred(), secid,
+ NULL, 0,
mask & (MAY_READ | MAY_WRITE | MAY_EXEC |
MAY_APPEND), FILE_CHECK);
}
@@ -725,7 +762,7 @@ void ima_post_path_mknod(struct user_namespace *mnt_userns,
int ima_read_file(struct file *file, enum kernel_read_file_id read_id,
bool contents)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct user_namespace *user_ns = current_user_ns();
enum ima_hooks func;
u32 secid;
@@ -748,7 +785,7 @@ int ima_read_file(struct file *file, enum kernel_read_file_id read_id,
/* Read entire file for all partial reads. */
func = read_idmap[read_id] ?: FILE_CHECK;
security_current_getsecid_subj(&secid);
- return process_measurement(ns, file, current_cred(), secid, NULL,
+ return process_measurement(user_ns, file, current_cred(), secid, NULL,
0, MAY_READ, func);
}
@@ -776,7 +813,8 @@ const int read_idmap[READING_MAX_ID] = {
int ima_post_read_file(struct file *file, void *buf, loff_t size,
enum kernel_read_file_id read_id)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct user_namespace *user_ns = current_user_ns();
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
enum ima_hooks func;
u32 secid;
@@ -793,8 +831,8 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size,
func = read_idmap[read_id] ?: FILE_CHECK;
security_current_getsecid_subj(&secid);
- return process_measurement(ns, file, current_cred(), secid, buf, size,
- MAY_READ, func);
+ return process_measurement(user_ns, file, current_cred(), secid,
+ buf, size, MAY_READ, func);
}
/**
--
2.37.3
Define mac_admin_ns_capable() as a wrapper for the combined ns_capable()
checks on CAP_MAC_ADMIN and CAP_SYS_ADMIN in a user namespace. Return
true on the check if either capability or both are available.
Use mac_admin_ns_capable() in place of capable(SYS_ADMIN). This will allow
an IMA namespace to read the policy with only CAP_MAC_ADMIN, which has
less privileges than CAP_SYS_ADMIN.
Since CAP_MAC_ADMIN is an additional capability added to an existing gate
avoid auditing in case it is not set.
Cc: Alexander Viro <[email protected]>
Cc: [email protected]
Signed-off-by: Denis Semakin <[email protected]>
Signed-off-by: Stefan Berger <[email protected]>
---
v13:
- implemented file_sb_user_ns(const struct file *); const is needed so it
can be called with seq_file's 'const struct file *file'
v11:
- use ns_capable_noaudit for CAP_MAC_ADMIN to avoid auditing in this case
---
include/linux/capability.h | 6 ++++++
include/linux/fs.h | 5 +++++
security/integrity/ima/ima.h | 6 ++++++
security/integrity/ima/ima_fs.c | 5 ++++-
4 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/include/linux/capability.h b/include/linux/capability.h
index 65efb74c3585..dc3e1230b365 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -270,6 +270,12 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns)
ns_capable(ns, CAP_SYS_ADMIN);
}
+static inline bool mac_admin_ns_capable(struct user_namespace *ns)
+{
+ return ns_capable_noaudit(ns, CAP_MAC_ADMIN) ||
+ ns_capable(ns, CAP_SYS_ADMIN);
+}
+
/* audit system wants to get cap info from files as well */
int get_vfs_caps_from_disk(struct user_namespace *mnt_userns,
const struct dentry *dentry,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index c1769a2c5d70..4662efed3171 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2730,6 +2730,11 @@ static inline struct user_namespace *file_mnt_user_ns(struct file *file)
return mnt_user_ns(file->f_path.mnt);
}
+static inline struct user_namespace *file_sb_user_ns(const struct file *file)
+{
+ return i_user_ns(file_inode(file));
+}
+
static inline struct mnt_idmap *file_mnt_idmap(struct file *file)
{
return mnt_idmap(file->f_path.mnt);
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 4de8ec776611..69f95ed0b8c6 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -492,4 +492,10 @@ static inline int ima_filter_rule_match(u32 secid, u32 field, u32 op,
#define POLICY_FILE_FLAGS S_IWUSR
#endif /* CONFIG_IMA_READ_POLICY */
+static inline
+struct user_namespace *ima_user_ns_from_file(const struct file *filp)
+{
+ return file_sb_user_ns(filp);
+}
+
#endif /* __LINUX_IMA_H */
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 89d3113ceda1..c41aa61b7393 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -377,6 +377,9 @@ static const struct seq_operations ima_policy_seqops = {
*/
static int ima_open_policy(struct inode *inode, struct file *filp)
{
+#ifdef CONFIG_IMA_READ_POLICY
+ struct user_namespace *user_ns = ima_user_ns_from_file(filp);
+#endif
struct ima_namespace *ns = &init_ima_ns;
if (!(filp->f_flags & O_WRONLY)) {
@@ -385,7 +388,7 @@ static int ima_open_policy(struct inode *inode, struct file *filp)
#else
if ((filp->f_flags & O_ACCMODE) != O_RDONLY)
return -EACCES;
- if (!capable(CAP_SYS_ADMIN))
+ if (!mac_admin_ns_capable(user_ns))
return -EPERM;
return seq_open(filp, &ima_policy_seqops);
#endif
--
2.37.3
Define the ima_namespace structure and the ima_namespace variable
init_ima_ns for the host's IMA namespace. Implement the basic functions
ima_ns_init() and ima_init_namespace() for namespacing support.
Move variables related to the IMA policy into the ima_namespace. This way
the IMA policy of an IMA namespace can be set and displayed using a
front-end like securityfs.
In preparation for IMA namespacing, update the existing functions to
pass the ima_namespace struct. For now, use &init_ima_ns as the current
ima_namespace when a function that is related to a policy rule is called.
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
---
v15:
- Adjustment to new function ima_inode_set_acl() after rebase.
v11:
- Updated commit text
- Added comments to some fields in the ima_namespace struct
v9:
- squashed patched 2 and 3 of v8
- ima_post_read_file: only access ima_appraise in case of init_ima_ns
---
security/integrity/ima/Makefile | 2 +-
security/integrity/ima/ima.h | 53 ++++---
security/integrity/ima/ima_api.c | 8 +-
security/integrity/ima/ima_appraise.c | 32 +++--
security/integrity/ima/ima_asymmetric_keys.c | 4 +-
security/integrity/ima/ima_fs.c | 16 ++-
security/integrity/ima/ima_init.c | 12 +-
security/integrity/ima/ima_init_ima_ns.c | 29 ++++
security/integrity/ima/ima_main.c | 88 +++++++-----
security/integrity/ima/ima_policy.c | 142 ++++++++++---------
security/integrity/ima/ima_queue_keys.c | 11 +-
11 files changed, 251 insertions(+), 146 deletions(-)
create mode 100644 security/integrity/ima/ima_init_ima_ns.c
diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile
index 2499f2485c04..f8a5e5f3975d 100644
--- a/security/integrity/ima/Makefile
+++ b/security/integrity/ima/Makefile
@@ -7,7 +7,7 @@
obj-$(CONFIG_IMA) += ima.o
ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \
- ima_policy.o ima_template.o ima_template_lib.o
+ ima_policy.o ima_template.o ima_template_lib.o ima_init_ima_ns.o
ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o
ima-$(CONFIG_IMA_APPRAISE_MODSIG) += ima_modsig.o
ima-$(CONFIG_HAVE_IMA_KEXEC) += ima_kexec.o
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 03b440921e61..61986e271c5c 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -20,6 +20,7 @@
#include <linux/hash.h>
#include <linux/tpm.h>
#include <linux/audit.h>
+#include <linux/user_namespace.h>
#include <crypto/hash_info.h>
#include "../integrity.h"
@@ -43,9 +44,6 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
#define NR_BANKS(chip) ((chip != NULL) ? chip->nr_allocated_banks : 0)
-/* current content of the policy */
-extern int ima_policy_flag;
-
/* bitset of digests algorithms allowed in the setxattr hook */
extern atomic_t ima_setxattr_allowed_hash_algorithms;
@@ -119,6 +117,17 @@ struct ima_kexec_hdr {
u64 count;
};
+struct ima_namespace {
+ /* policy rules */
+ struct list_head ima_default_rules; /* Kconfig, builtin & arch rules */
+ struct list_head ima_policy_rules; /* arch & custom rules */
+ struct list_head ima_temp_rules;
+
+ struct list_head __rcu *ima_rules; /* Pointer to the current policy */
+ int ima_policy_flag;
+} __randomize_layout;
+extern struct ima_namespace init_ima_ns;
+
extern const int read_idmap[];
#ifdef CONFIG_HAVE_IMA_KEXEC
@@ -136,6 +145,7 @@ extern bool ima_canonical_fmt;
/* Internal IMA function definitions */
int ima_init(void);
int ima_fs_init(void);
+int ima_ns_init(void);
int ima_add_template_entry(struct ima_template_entry *entry, int violation,
const char *op, struct inode *inode,
const unsigned char *filename);
@@ -243,18 +253,19 @@ void ima_init_key_queue(void);
bool ima_should_queue_key(void);
bool ima_queue_key(struct key *keyring, const void *payload,
size_t payload_len);
-void ima_process_queued_keys(void);
+void ima_process_queued_keys(struct ima_namespace *ns);
#else
static inline void ima_init_key_queue(void) {}
static inline bool ima_should_queue_key(void) { return false; }
static inline bool ima_queue_key(struct key *keyring,
const void *payload,
size_t payload_len) { return false; }
-static inline void ima_process_queued_keys(void) {}
+static inline void ima_process_queued_keys(struct ima_namespace *ns) {}
#endif /* CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS */
/* LIM API function definitions */
-int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode,
+int ima_get_action(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns, struct inode *inode,
const struct cred *cred, u32 secid, int mask,
enum ima_hooks func, int *pcr,
struct ima_template_desc **template_desc,
@@ -268,7 +279,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, const struct modsig *modsig, int pcr,
struct ima_template_desc *template_desc);
-int process_buffer_measurement(struct user_namespace *mnt_userns,
+int process_buffer_measurement(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns,
struct inode *inode, const void *buf, int size,
const char *eventname, enum ima_hooks func,
int pcr, const char *func_data,
@@ -285,17 +297,18 @@ void ima_free_template_entry(struct ima_template_entry *entry);
const char *ima_d_path(const struct path *path, char **pathbuf, char *filename);
/* IMA policy related functions */
-int ima_match_policy(struct user_namespace *mnt_userns, struct inode *inode,
+int ima_match_policy(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns, 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);
-void ima_init_policy(void);
-void ima_update_policy(void);
-void ima_update_policy_flags(void);
-ssize_t ima_parse_add_rule(char *);
-void ima_delete_rules(void);
-int ima_check_policy(void);
+void ima_init_policy(struct ima_namespace *ns);
+void ima_update_policy(struct ima_namespace *ns);
+void ima_update_policy_flags(struct ima_namespace *ns);
+ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule);
+void ima_delete_rules(struct ima_namespace *ns);
+int ima_check_policy(struct ima_namespace *ns);
void *ima_policy_start(struct seq_file *m, loff_t *pos);
void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos);
void ima_policy_stop(struct seq_file *m, void *v);
@@ -311,14 +324,16 @@ int ima_policy_show(struct seq_file *m, void *v);
#define IMA_APPRAISE_KEXEC 0x40
#ifdef CONFIG_IMA_APPRAISE
-int ima_check_blacklist(struct integrity_iint_cache *iint,
+int ima_check_blacklist(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
const struct modsig *modsig, int pcr);
int ima_appraise_measurement(enum ima_hooks func,
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 ima_must_appraise(struct user_namespace *mnt_userns, struct inode *inode,
+int ima_must_appraise(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns, struct inode *inode,
int mask, enum ima_hooks func);
void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file);
enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint,
@@ -329,7 +344,8 @@ int ima_read_xattr(struct dentry *dentry,
struct evm_ima_xattr_data **xattr_value, int xattr_len);
#else
-static inline int ima_check_blacklist(struct integrity_iint_cache *iint,
+static inline int ima_check_blacklist(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
const struct modsig *modsig, int pcr)
{
return 0;
@@ -346,7 +362,8 @@ static inline int ima_appraise_measurement(enum ima_hooks func,
return INTEGRITY_UNKNOWN;
}
-static inline int ima_must_appraise(struct user_namespace *mnt_userns,
+static inline int ima_must_appraise(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns,
struct inode *inode, int mask,
enum ima_hooks func)
{
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index c1e76282b5ee..0b6bc357768a 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -163,6 +163,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
/**
* ima_get_action - appraise & measure decision based on policy.
+ * @ns: IMA namespace that has the policy
* @mnt_userns: user namespace of the mount the inode was found from
* @inode: pointer to the inode associated with the object being validated
* @cred: pointer to credentials structure to validate
@@ -186,7 +187,8 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
* Returns IMA_MEASURE, IMA_APPRAISE mask.
*
*/
-int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode,
+int ima_get_action(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns, struct inode *inode,
const struct cred *cred, u32 secid, int mask,
enum ima_hooks func, int *pcr,
struct ima_template_desc **template_desc,
@@ -194,9 +196,9 @@ int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode,
{
int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE | IMA_HASH;
- flags &= ima_policy_flag;
+ flags &= ns->ima_policy_flag;
- return ima_match_policy(mnt_userns, inode, cred, secid, func, mask,
+ return ima_match_policy(ns, mnt_userns, inode, cred, secid, func, mask,
flags, pcr, template_desc, func_data,
allowed_algos);
}
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index ee6f7e237f2e..89c38ce0039e 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -70,7 +70,8 @@ bool is_ima_appraise_enabled(void)
*
* Return 1 to appraise or hash
*/
-int ima_must_appraise(struct user_namespace *mnt_userns, struct inode *inode,
+int ima_must_appraise(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns, struct inode *inode,
int mask, enum ima_hooks func)
{
u32 secid;
@@ -79,7 +80,7 @@ int ima_must_appraise(struct user_namespace *mnt_userns, struct inode *inode,
return 0;
security_current_getsecid_subj(&secid);
- return ima_match_policy(mnt_userns, inode, current_cred(), secid,
+ return ima_match_policy(ns, mnt_userns, inode, current_cred(), secid,
func, mask, IMA_APPRAISE | IMA_HASH, NULL,
NULL, NULL, NULL);
}
@@ -440,7 +441,8 @@ static int modsig_verify(enum ima_hooks func, const struct modsig *modsig,
*
* Returns -EPERM if the hash is blacklisted.
*/
-int ima_check_blacklist(struct integrity_iint_cache *iint,
+int ima_check_blacklist(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
const struct modsig *modsig, int pcr)
{
enum hash_algo hash_algo;
@@ -456,7 +458,8 @@ int ima_check_blacklist(struct integrity_iint_cache *iint,
rc = is_binary_blacklisted(digest, digestsize);
if ((rc == -EPERM) && (iint->flags & IMA_MEASURE))
- process_buffer_measurement(&init_user_ns, NULL, digest, digestsize,
+ process_buffer_measurement(ns, &init_user_ns, NULL,
+ digest, digestsize,
"blacklisted-hash", NONE,
pcr, NULL, false, NULL, 0);
}
@@ -634,14 +637,16 @@ void ima_inode_post_setattr(struct user_namespace *mnt_userns,
struct dentry *dentry)
{
struct inode *inode = d_backing_inode(dentry);
+ struct ima_namespace *ns = &init_ima_ns;
struct integrity_iint_cache *iint;
int action;
- if (!(ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode)
+ if (!(ns->ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode)
|| !(inode->i_opflags & IOP_XATTR))
return;
- action = ima_must_appraise(mnt_userns, inode, MAY_ACCESS, POST_SETATTR);
+ action = ima_must_appraise(ns, mnt_userns, inode, MAY_ACCESS,
+ POST_SETATTR);
iint = integrity_iint_find(inode);
if (iint) {
set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags);
@@ -666,11 +671,12 @@ static int ima_protect_xattr(struct dentry *dentry, const char *xattr_name,
return 0;
}
-static void ima_reset_appraise_flags(struct inode *inode, int digsig)
+static void ima_reset_appraise_flags(struct ima_namespace *ns,
+ struct inode *inode, int digsig)
{
struct integrity_iint_cache *iint;
- if (!(ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode))
+ if (!(ns->ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode))
return;
iint = integrity_iint_find(inode);
@@ -748,6 +754,7 @@ int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
const void *xattr_value, size_t xattr_value_len)
{
const struct evm_ima_xattr_data *xvalue = xattr_value;
+ struct ima_namespace *ns = &init_ima_ns;
int digsig = 0;
int result;
int err;
@@ -767,7 +774,7 @@ int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
digsig = (xvalue->type == EVM_XATTR_PORTABLE_DIGSIG);
}
if (result == 1 || evm_revalidate_status(xattr_name)) {
- ima_reset_appraise_flags(d_backing_inode(dentry), digsig);
+ ima_reset_appraise_flags(ns, d_backing_inode(dentry), digsig);
if (result == 1)
result = 0;
}
@@ -777,19 +784,22 @@ int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
int ima_inode_set_acl(struct user_namespace *mnt_userns, struct dentry *dentry,
const char *acl_name, struct posix_acl *kacl)
{
+ struct ima_namespace *ns = &init_ima_ns;
+
if (evm_revalidate_status(acl_name))
- ima_reset_appraise_flags(d_backing_inode(dentry), 0);
+ ima_reset_appraise_flags(ns, d_backing_inode(dentry), 0);
return 0;
}
int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name)
{
+ struct ima_namespace *ns = &init_ima_ns;
int result;
result = ima_protect_xattr(dentry, xattr_name, NULL, 0);
if (result == 1 || evm_revalidate_status(xattr_name)) {
- ima_reset_appraise_flags(d_backing_inode(dentry), 0);
+ ima_reset_appraise_flags(ns, d_backing_inode(dentry), 0);
if (result == 1)
result = 0;
}
diff --git a/security/integrity/ima/ima_asymmetric_keys.c b/security/integrity/ima/ima_asymmetric_keys.c
index f6aa0b47a772..70d87df26068 100644
--- a/security/integrity/ima/ima_asymmetric_keys.c
+++ b/security/integrity/ima/ima_asymmetric_keys.c
@@ -30,6 +30,7 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
const void *payload, size_t payload_len,
unsigned long flags, bool create)
{
+ struct ima_namespace *ns = &init_ima_ns;
bool queued = false;
/* Only asymmetric keys are handled by this hook. */
@@ -60,7 +61,8 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
* if the IMA policy is configured to measure a key linked
* to the given keyring.
*/
- process_buffer_measurement(&init_user_ns, NULL, payload, payload_len,
+ process_buffer_measurement(ns, &init_user_ns, NULL,
+ payload, payload_len,
keyring->description, KEY_CHECK, 0,
keyring->description, false, NULL, 0);
}
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index cd1683dad3bf..f7ad93a56982 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -271,7 +271,7 @@ static const struct file_operations ima_ascii_measurements_ops = {
.release = seq_release,
};
-static ssize_t ima_read_policy(char *path)
+static ssize_t ima_read_policy(struct ima_namespace *ns, char *path)
{
void *data = NULL;
char *datap;
@@ -296,7 +296,7 @@ static ssize_t ima_read_policy(char *path)
datap = data;
while (size > 0 && (p = strsep(&datap, "\n"))) {
pr_debug("rule: %s\n", p);
- rc = ima_parse_add_rule(p);
+ rc = ima_parse_add_rule(ns, p);
if (rc < 0)
break;
size -= rc;
@@ -314,6 +314,7 @@ static ssize_t ima_read_policy(char *path)
static ssize_t ima_write_policy(struct file *file, const char __user *buf,
size_t datalen, loff_t *ppos)
{
+ struct ima_namespace *ns = &init_ima_ns;
char *data;
ssize_t result;
@@ -336,7 +337,7 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf,
goto out_free;
if (data[0] == '/') {
- result = ima_read_policy(data);
+ result = ima_read_policy(ns, data);
} else if (ima_appraise & IMA_APPRAISE_POLICY) {
pr_err("signed policy file (specified as an absolute pathname) required\n");
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL,
@@ -344,7 +345,7 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf,
1, 0);
result = -EACCES;
} else {
- result = ima_parse_add_rule(data);
+ result = ima_parse_add_rule(ns, data);
}
mutex_unlock(&ima_write_mutex);
out_free:
@@ -410,11 +411,12 @@ static int ima_open_policy(struct inode *inode, struct file *filp)
static int ima_release_policy(struct inode *inode, struct file *file)
{
const char *cause = valid_policy ? "completed" : "failed";
+ struct ima_namespace *ns = &init_ima_ns;
if ((file->f_flags & O_ACCMODE) == O_RDONLY)
return seq_release(inode, file);
- if (valid_policy && ima_check_policy() < 0) {
+ if (valid_policy && ima_check_policy(ns) < 0) {
cause = "failed";
valid_policy = 0;
}
@@ -424,13 +426,13 @@ static int ima_release_policy(struct inode *inode, struct file *file)
"policy_update", cause, !valid_policy, 0);
if (!valid_policy) {
- ima_delete_rules();
+ ima_delete_rules(ns);
valid_policy = 1;
clear_bit(IMA_FS_BUSY, &ima_fs_flags);
return 0;
}
- ima_update_policy();
+ ima_update_policy(ns);
#if !defined(CONFIG_IMA_WRITE_POLICY) && !defined(CONFIG_IMA_READ_POLICY)
securityfs_remove(ima_policy);
ima_policy = NULL;
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index 63979aefc95f..7e5b4187035d 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -101,15 +101,15 @@ static int __init ima_add_boot_aggregate(void)
#ifdef CONFIG_IMA_LOAD_X509
void __init ima_load_x509(void)
{
- int unset_flags = ima_policy_flag & IMA_APPRAISE;
+ int unset_flags = init_ima_ns.ima_policy_flag & IMA_APPRAISE;
- ima_policy_flag &= ~unset_flags;
+ init_ima_ns.ima_policy_flag &= ~unset_flags;
integrity_load_x509(INTEGRITY_KEYRING_IMA, CONFIG_IMA_X509_PATH);
/* load also EVM key to avoid appraisal */
evm_load_x509();
- ima_policy_flag |= unset_flags;
+ init_ima_ns.ima_policy_flag |= unset_flags;
}
#endif
@@ -117,6 +117,10 @@ int __init ima_init(void)
{
int rc;
+ rc = ima_ns_init();
+ if (rc)
+ return rc;
+
ima_tpm_chip = tpm_default_chip();
if (!ima_tpm_chip)
pr_info("No TPM chip found, activating TPM-bypass!\n");
@@ -142,7 +146,7 @@ int __init ima_init(void)
if (rc != 0)
return rc;
- ima_init_policy();
+ ima_init_policy(&init_ima_ns);
rc = ima_fs_init();
if (rc != 0)
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
new file mode 100644
index 000000000000..c919a456b525
--- /dev/null
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016-2022 IBM Corporation
+ * Author:
+ * Yuqiong Sun <[email protected]>
+ * Stefan Berger <[email protected]>
+ */
+
+#include "ima.h"
+
+static int ima_init_namespace(struct ima_namespace *ns)
+{
+ INIT_LIST_HEAD(&ns->ima_default_rules);
+ INIT_LIST_HEAD(&ns->ima_policy_rules);
+ INIT_LIST_HEAD(&ns->ima_temp_rules);
+ ns->ima_rules = (struct list_head __rcu *)(&ns->ima_default_rules);
+ ns->ima_policy_flag = 0;
+
+ return 0;
+}
+
+int __init ima_ns_init(void)
+{
+ return ima_init_namespace(&init_ima_ns);
+}
+
+struct ima_namespace init_ima_ns = {
+};
+EXPORT_SYMBOL(init_ima_ns);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 377300973e6c..00b550a64209 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -185,10 +185,11 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint,
*/
void ima_file_free(struct file *file)
{
+ struct ima_namespace *ns = &init_ima_ns;
struct inode *inode = file_inode(file);
struct integrity_iint_cache *iint;
- if (!ima_policy_flag || !S_ISREG(inode->i_mode))
+ if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
return;
iint = integrity_iint_find(inode);
@@ -198,7 +199,8 @@ void ima_file_free(struct file *file)
ima_check_last_writer(iint, inode, file);
}
-static int process_measurement(struct file *file, const struct cred *cred,
+static int process_measurement(struct ima_namespace *ns,
+ struct file *file, const struct cred *cred,
u32 secid, char *buf, loff_t size, int mask,
enum ima_hooks func)
{
@@ -217,18 +219,18 @@ static int process_measurement(struct file *file, const struct cred *cred,
enum hash_algo hash_algo;
unsigned int allowed_algos = 0;
- if (!ima_policy_flag || !S_ISREG(inode->i_mode))
+ if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
return 0;
/* Return an IMA_MEASURE, IMA_APPRAISE, IMA_AUDIT action
* bitmask based on the appraise/audit/measurement policy.
* Included is the appraise submask.
*/
- action = ima_get_action(file_mnt_user_ns(file), inode, cred, secid,
+ action = ima_get_action(ns, file_mnt_user_ns(file), inode, cred, secid,
mask, func, &pcr, &template_desc, NULL,
&allowed_algos);
violation_check = ((func == FILE_CHECK || func == MMAP_CHECK) &&
- (ima_policy_flag & IMA_MEASURE));
+ (ns->ima_policy_flag & IMA_MEASURE));
if (!action && !violation_check)
return 0;
@@ -348,7 +350,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
xattr_value, xattr_len, modsig, pcr,
template_desc);
if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
- rc = ima_check_blacklist(iint, modsig, pcr);
+ rc = ima_check_blacklist(ns, iint, modsig, pcr);
if (rc != -EPERM) {
inode_lock(inode);
rc = ima_appraise_measurement(func, iint, file,
@@ -407,12 +409,13 @@ static int process_measurement(struct file *file, const struct cred *cred,
*/
int ima_file_mmap(struct file *file, unsigned long prot)
{
+ struct ima_namespace *ns = &init_ima_ns;
u32 secid;
if (file && (prot & PROT_EXEC)) {
security_current_getsecid_subj(&secid);
- return process_measurement(file, current_cred(), secid, NULL,
- 0, MAY_EXEC, MMAP_CHECK);
+ return process_measurement(ns, file, current_cred(), secid,
+ NULL, 0, MAY_EXEC, MMAP_CHECK);
}
return 0;
@@ -433,6 +436,7 @@ int ima_file_mmap(struct file *file, unsigned long prot)
*/
int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot)
{
+ struct ima_namespace *ns = &init_ima_ns;
struct ima_template_desc *template = NULL;
struct file *file;
char filename[NAME_MAX];
@@ -445,13 +449,13 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot)
int pcr;
/* Is mprotect making an mmap'ed file executable? */
- if (!(ima_policy_flag & IMA_APPRAISE) || !vma->vm_file ||
+ if (!(ns->ima_policy_flag & IMA_APPRAISE) || !vma->vm_file ||
!(prot & PROT_EXEC) || (vma->vm_flags & VM_EXEC))
return 0;
security_current_getsecid_subj(&secid);
inode = file_inode(vma->vm_file);
- action = ima_get_action(file_mnt_user_ns(vma->vm_file), inode,
+ action = ima_get_action(ns, file_mnt_user_ns(vma->vm_file), inode,
current_cred(), secid, MAY_EXEC, MMAP_CHECK,
&pcr, &template, NULL, NULL);
@@ -487,17 +491,18 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot)
*/
int ima_bprm_check(struct linux_binprm *bprm)
{
+ struct ima_namespace *ns = &init_ima_ns;
int ret;
u32 secid;
security_current_getsecid_subj(&secid);
- ret = process_measurement(bprm->file, current_cred(), secid, NULL, 0,
- MAY_EXEC, BPRM_CHECK);
+ ret = process_measurement(ns, bprm->file, current_cred(), secid, NULL,
+ 0, MAY_EXEC, BPRM_CHECK);
if (ret)
return ret;
security_cred_getsecid(bprm->cred, &secid);
- return process_measurement(bprm->file, bprm->cred, secid, NULL, 0,
+ return process_measurement(ns, bprm->file, bprm->cred, secid, NULL, 0,
MAY_EXEC, CREDS_CHECK);
}
@@ -513,22 +518,23 @@ int ima_bprm_check(struct linux_binprm *bprm)
*/
int ima_file_check(struct file *file, int mask)
{
+ struct ima_namespace *ns = &init_ima_ns;
u32 secid;
security_current_getsecid_subj(&secid);
- return process_measurement(file, current_cred(), secid, NULL, 0,
+ return process_measurement(ns, file, current_cred(), secid, NULL, 0,
mask & (MAY_READ | MAY_WRITE | MAY_EXEC |
MAY_APPEND), FILE_CHECK);
}
EXPORT_SYMBOL_GPL(ima_file_check);
-static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf,
- size_t buf_size)
+static int __ima_inode_hash(struct ima_namespace *ns, struct inode *inode,
+ struct file *file, char *buf, size_t buf_size)
{
struct integrity_iint_cache *iint = NULL, tmp_iint;
int rc, hash_algo;
- if (ima_policy_flag) {
+ if (ns->ima_policy_flag) {
iint = integrity_iint_find(inode);
if (iint)
mutex_lock(&iint->mutex);
@@ -602,10 +608,12 @@ static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf,
*/
int ima_file_hash(struct file *file, char *buf, size_t buf_size)
{
+ struct ima_namespace *ns = &init_ima_ns;
+
if (!file)
return -EINVAL;
- return __ima_inode_hash(file_inode(file), file, buf, buf_size);
+ return __ima_inode_hash(ns, file_inode(file), file, buf, buf_size);
}
EXPORT_SYMBOL_GPL(ima_file_hash);
@@ -629,10 +637,12 @@ EXPORT_SYMBOL_GPL(ima_file_hash);
*/
int ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
{
+ struct ima_namespace *ns = &init_ima_ns;
+
if (!inode)
return -EINVAL;
- return __ima_inode_hash(inode, NULL, buf, buf_size);
+ return __ima_inode_hash(ns, inode, NULL, buf, buf_size);
}
EXPORT_SYMBOL_GPL(ima_inode_hash);
@@ -648,13 +658,14 @@ EXPORT_SYMBOL_GPL(ima_inode_hash);
void ima_post_create_tmpfile(struct user_namespace *mnt_userns,
struct inode *inode)
{
+ struct ima_namespace *ns = &init_ima_ns;
struct integrity_iint_cache *iint;
int must_appraise;
- if (!ima_policy_flag || !S_ISREG(inode->i_mode))
+ if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
return;
- must_appraise = ima_must_appraise(mnt_userns, inode, MAY_ACCESS,
+ must_appraise = ima_must_appraise(ns, mnt_userns, inode, MAY_ACCESS,
FILE_CHECK);
if (!must_appraise)
return;
@@ -680,14 +691,15 @@ void ima_post_create_tmpfile(struct user_namespace *mnt_userns,
void ima_post_path_mknod(struct user_namespace *mnt_userns,
struct dentry *dentry)
{
+ struct ima_namespace *ns = &init_ima_ns;
struct integrity_iint_cache *iint;
struct inode *inode = dentry->d_inode;
int must_appraise;
- if (!ima_policy_flag || !S_ISREG(inode->i_mode))
+ if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
return;
- must_appraise = ima_must_appraise(mnt_userns, inode, MAY_ACCESS,
+ must_appraise = ima_must_appraise(ns, mnt_userns, inode, MAY_ACCESS,
FILE_CHECK);
if (!must_appraise)
return;
@@ -716,6 +728,7 @@ void ima_post_path_mknod(struct user_namespace *mnt_userns,
int ima_read_file(struct file *file, enum kernel_read_file_id read_id,
bool contents)
{
+ struct ima_namespace *ns = &init_ima_ns;
enum ima_hooks func;
u32 secid;
@@ -738,7 +751,7 @@ int ima_read_file(struct file *file, enum kernel_read_file_id read_id,
/* Read entire file for all partial reads. */
func = read_idmap[read_id] ?: FILE_CHECK;
security_current_getsecid_subj(&secid);
- return process_measurement(file, current_cred(), secid, NULL,
+ return process_measurement(ns, file, current_cred(), secid, NULL,
0, MAY_READ, func);
}
@@ -766,6 +779,7 @@ const int read_idmap[READING_MAX_ID] = {
int ima_post_read_file(struct file *file, void *buf, loff_t size,
enum kernel_read_file_id read_id)
{
+ struct ima_namespace *ns = &init_ima_ns;
enum ima_hooks func;
u32 secid;
@@ -774,14 +788,15 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size,
return 0;
if (!file || !buf || size == 0) { /* should never happen */
- if (ima_appraise & IMA_APPRAISE_ENFORCE)
+ if (ns == &init_ima_ns &&
+ (ima_appraise & IMA_APPRAISE_ENFORCE))
return -EACCES;
return 0;
}
func = read_idmap[read_id] ?: FILE_CHECK;
security_current_getsecid_subj(&secid);
- return process_measurement(file, current_cred(), secid, buf, size,
+ return process_measurement(ns, file, current_cred(), secid, buf, size,
MAY_READ, func);
}
@@ -869,6 +884,7 @@ int ima_post_load_data(char *buf, loff_t size,
/**
* process_buffer_measurement - Measure the buffer or the buffer data hash
+ * @ns: IMA namespace that has the policy
* @mnt_userns: user namespace of the mount the inode was found from
* @inode: inode associated with the object being measured (NULL for KEY_CHECK)
* @buf: pointer to the buffer that needs to be added to the log.
@@ -887,7 +903,8 @@ int ima_post_load_data(char *buf, loff_t size,
* has been written to the passed location but not added to a measurement entry,
* a negative value otherwise.
*/
-int process_buffer_measurement(struct user_namespace *mnt_userns,
+int process_buffer_measurement(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns,
struct inode *inode, const void *buf, int size,
const char *eventname, enum ima_hooks func,
int pcr, const char *func_data,
@@ -912,7 +929,7 @@ int process_buffer_measurement(struct user_namespace *mnt_userns,
if (digest && digest_len < digest_hash_len)
return -EINVAL;
- if (!ima_policy_flag && !digest)
+ if (!ns->ima_policy_flag && !digest)
return -ENOENT;
template = ima_template_desc_buf();
@@ -931,7 +948,7 @@ int process_buffer_measurement(struct user_namespace *mnt_userns,
*/
if (func) {
security_current_getsecid_subj(&secid);
- action = ima_get_action(mnt_userns, inode, current_cred(),
+ action = ima_get_action(ns, mnt_userns, inode, current_cred(),
secid, 0, func, &pcr, &template,
func_data, NULL);
if (!(action & IMA_MEASURE) && !digest)
@@ -968,7 +985,7 @@ int process_buffer_measurement(struct user_namespace *mnt_userns,
if (digest)
memcpy(digest, iint.ima_hash->digest, digest_hash_len);
- if (!ima_policy_flag || (func && !(action & IMA_MEASURE)))
+ if (!ns->ima_policy_flag || (func && !(action & IMA_MEASURE)))
return 1;
ret = ima_alloc_init_template(&event_data, &entry, template);
@@ -1002,6 +1019,7 @@ int process_buffer_measurement(struct user_namespace *mnt_userns,
*/
void ima_kexec_cmdline(int kernel_fd, const void *buf, int size)
{
+ struct ima_namespace *ns = &init_ima_ns;
struct fd f;
if (!buf || !size)
@@ -1011,7 +1029,8 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size)
if (!f.file)
return;
- process_buffer_measurement(file_mnt_user_ns(f.file), file_inode(f.file),
+ process_buffer_measurement(ns,
+ file_mnt_user_ns(f.file), file_inode(f.file),
buf, size, "kexec-cmdline", KEXEC_CMDLINE, 0,
NULL, false, NULL, 0);
fdput(f);
@@ -1041,10 +1060,12 @@ int ima_measure_critical_data(const char *event_label,
const void *buf, size_t buf_len,
bool hash, u8 *digest, size_t digest_len)
{
+ struct ima_namespace *ns = &init_ima_ns;
+
if (!event_name || !event_label || !buf || !buf_len)
return -ENOPARAM;
- return process_buffer_measurement(&init_user_ns, NULL, buf, buf_len,
+ return process_buffer_measurement(ns, &init_user_ns, NULL, buf, buf_len,
event_name, CRITICAL_DATA, 0,
event_label, hash, digest,
digest_len);
@@ -1053,6 +1074,7 @@ EXPORT_SYMBOL_GPL(ima_measure_critical_data);
static int __init init_ima(void)
{
+ struct ima_namespace *ns = &init_ima_ns;
int error;
ima_appraise_parse_cmdline();
@@ -1077,7 +1099,7 @@ static int __init init_ima(void)
pr_warn("Couldn't register LSM notifier, error %d\n", error);
if (!error)
- ima_update_policy_flags();
+ ima_update_policy_flags(ns);
return error;
}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 6a68ec270822..671994bd07cb 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -51,7 +51,6 @@
#define INVALID_PCR(a) (((a) < 0) || \
(a) >= (sizeof_field(struct integrity_iint_cache, measured_pcrs) * 8))
-int ima_policy_flag;
static int temp_ima_appraise;
static int build_ima_appraise __ro_after_init;
@@ -256,11 +255,6 @@ static struct ima_rule_entry critical_data_rules[] __ro_after_init = {
/* An array of architecture specific rules */
static struct ima_rule_entry *arch_policy_entry __ro_after_init;
-static LIST_HEAD(ima_default_rules);
-static LIST_HEAD(ima_policy_rules);
-static LIST_HEAD(ima_temp_rules);
-static struct list_head __rcu *ima_rules = (struct list_head __rcu *)(&ima_default_rules);
-
static int ima_policy __initdata;
static int __init default_measure_policy_setup(char *str)
@@ -473,12 +467,12 @@ static bool ima_rule_contains_lsm_cond(struct ima_rule_entry *entry)
* to the old, stale LSM policy. Update the IMA LSM based rules to reflect
* the reloaded LSM policy.
*/
-static void ima_lsm_update_rules(void)
+static void ima_lsm_update_rules(struct ima_namespace *ns)
{
struct ima_rule_entry *entry, *e;
int result;
- list_for_each_entry_safe(entry, e, &ima_policy_rules, list) {
+ list_for_each_entry_safe(entry, e, &ns->ima_policy_rules, list) {
if (!ima_rule_contains_lsm_cond(entry))
continue;
@@ -493,10 +487,12 @@ static void ima_lsm_update_rules(void)
int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
void *lsm_data)
{
+ struct ima_namespace *ns = &init_ima_ns;
+
if (event != LSM_POLICY_CHANGE)
return NOTIFY_DONE;
- ima_lsm_update_rules();
+ ima_lsm_update_rules(ns);
return NOTIFY_OK;
}
@@ -713,6 +709,7 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func)
/**
* ima_match_policy - decision based on LSM and other conditions
+ * @ns: IMA namespace that has the policy
* @mnt_userns: user namespace of the mount the inode was found from
* @inode: pointer to an inode for which the policy decision is being made
* @cred: pointer to a credentials structure for which the policy decision is
@@ -732,7 +729,8 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func)
* list when walking it. Reads are many orders of magnitude more numerous
* than writes so ima_match_policy() is classical RCU candidate.
*/
-int ima_match_policy(struct user_namespace *mnt_userns, struct inode *inode,
+int ima_match_policy(struct ima_namespace *ns,
+ struct user_namespace *mnt_userns, 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,
@@ -746,7 +744,7 @@ int ima_match_policy(struct user_namespace *mnt_userns, struct inode *inode,
*template_desc = ima_template_desc_current();
rcu_read_lock();
- ima_rules_tmp = rcu_dereference(ima_rules);
+ ima_rules_tmp = rcu_dereference(ns->ima_rules);
list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
if (!(entry->action & actmask))
@@ -790,8 +788,8 @@ int ima_match_policy(struct user_namespace *mnt_userns, struct inode *inode,
}
/**
- * ima_update_policy_flags() - Update global IMA variables
- *
+ * ima_update_policy_flags() - Update namespaced IMA variables
+ * @ns: IMA namespace that has the policy
* Update ima_policy_flag and ima_setxattr_allowed_hash_algorithms
* based on the currently loaded policy.
*
@@ -804,14 +802,14 @@ int ima_match_policy(struct user_namespace *mnt_userns, struct inode *inode,
*
* Context: called after a policy update and at system initialization.
*/
-void ima_update_policy_flags(void)
+void ima_update_policy_flags(struct ima_namespace *ns)
{
struct ima_rule_entry *entry;
int new_policy_flag = 0;
struct list_head *ima_rules_tmp;
rcu_read_lock();
- ima_rules_tmp = rcu_dereference(ima_rules);
+ ima_rules_tmp = rcu_dereference(ns->ima_rules);
list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
/*
* SETXATTR_CHECK rules do not implement a full policy check
@@ -841,7 +839,7 @@ void ima_update_policy_flags(void)
if (!ima_appraise)
new_policy_flag &= ~IMA_APPRAISE;
- ima_policy_flag = new_policy_flag;
+ ns->ima_policy_flag = new_policy_flag;
}
static int ima_appraise_flag(enum ima_hooks func)
@@ -857,7 +855,8 @@ static int ima_appraise_flag(enum ima_hooks func)
return 0;
}
-static void add_rules(struct ima_rule_entry *entries, int count,
+static void add_rules(struct ima_namespace *ns,
+ struct ima_rule_entry *entries, int count,
enum policy_rule_list policy_rule)
{
int i = 0;
@@ -866,7 +865,7 @@ static void add_rules(struct ima_rule_entry *entries, int count,
struct ima_rule_entry *entry;
if (policy_rule & IMA_DEFAULT_POLICY)
- list_add_tail(&entries[i].list, &ima_default_rules);
+ list_add_tail(&entries[i].list, &ns->ima_default_rules);
if (policy_rule & IMA_CUSTOM_POLICY) {
entry = kmemdup(&entries[i], sizeof(*entry),
@@ -874,7 +873,7 @@ static void add_rules(struct ima_rule_entry *entries, int count,
if (!entry)
continue;
- list_add_tail(&entry->list, &ima_policy_rules);
+ list_add_tail(&entry->list, &ns->ima_policy_rules);
}
if (entries[i].action == APPRAISE) {
if (entries != build_appraise_rules)
@@ -887,9 +886,10 @@ static void add_rules(struct ima_rule_entry *entries, int count,
}
}
-static int ima_parse_rule(char *rule, struct ima_rule_entry *entry);
+static int ima_parse_rule(struct ima_namespace *ns,
+ char *rule, struct ima_rule_entry *entry);
-static int __init ima_init_arch_policy(void)
+static int __init ima_init_arch_policy(struct ima_namespace *ns)
{
const char * const *arch_rules;
const char * const *rules;
@@ -917,7 +917,7 @@ static int __init ima_init_arch_policy(void)
result = strscpy(rule, *rules, sizeof(rule));
INIT_LIST_HEAD(&arch_policy_entry[i].list);
- result = ima_parse_rule(rule, &arch_policy_entry[i]);
+ result = ima_parse_rule(ns, rule, &arch_policy_entry[i]);
if (result) {
pr_warn("Skipping unknown architecture policy rule: %s\n",
rule);
@@ -932,26 +932,27 @@ static int __init ima_init_arch_policy(void)
/**
* ima_init_policy - initialize the default measure rules.
- *
+ * @ns: IMA namespace to which the policy belongs to
* ima_rules points to either the ima_default_rules or the new ima_policy_rules.
*/
-void __init ima_init_policy(void)
+void __init ima_init_policy(struct ima_namespace *ns)
{
int build_appraise_entries, arch_entries;
/* if !ima_policy, we load NO default rules */
if (ima_policy)
- add_rules(dont_measure_rules, ARRAY_SIZE(dont_measure_rules),
+ add_rules(ns, dont_measure_rules,
+ ARRAY_SIZE(dont_measure_rules),
IMA_DEFAULT_POLICY);
switch (ima_policy) {
case ORIGINAL_TCB:
- add_rules(original_measurement_rules,
+ add_rules(ns, original_measurement_rules,
ARRAY_SIZE(original_measurement_rules),
IMA_DEFAULT_POLICY);
break;
case DEFAULT_TCB:
- add_rules(default_measurement_rules,
+ add_rules(ns, default_measurement_rules,
ARRAY_SIZE(default_measurement_rules),
IMA_DEFAULT_POLICY);
break;
@@ -965,11 +966,11 @@ void __init ima_init_policy(void)
* and custom policies, prior to other appraise rules.
* (Highest priority)
*/
- arch_entries = ima_init_arch_policy();
+ arch_entries = ima_init_arch_policy(ns);
if (!arch_entries)
pr_info("No architecture policies found\n");
else
- add_rules(arch_policy_entry, arch_entries,
+ add_rules(ns, arch_policy_entry, arch_entries,
IMA_DEFAULT_POLICY | IMA_CUSTOM_POLICY);
/*
@@ -977,7 +978,7 @@ void __init ima_init_policy(void)
* signatures, prior to other appraise rules.
*/
if (ima_use_secure_boot)
- add_rules(secure_boot_rules, ARRAY_SIZE(secure_boot_rules),
+ add_rules(ns, secure_boot_rules, ARRAY_SIZE(secure_boot_rules),
IMA_DEFAULT_POLICY);
/*
@@ -989,39 +990,41 @@ void __init ima_init_policy(void)
build_appraise_entries = ARRAY_SIZE(build_appraise_rules);
if (build_appraise_entries) {
if (ima_use_secure_boot)
- add_rules(build_appraise_rules, build_appraise_entries,
+ add_rules(ns, build_appraise_rules,
+ build_appraise_entries,
IMA_CUSTOM_POLICY);
else
- add_rules(build_appraise_rules, build_appraise_entries,
+ add_rules(ns, build_appraise_rules,
+ build_appraise_entries,
IMA_DEFAULT_POLICY | IMA_CUSTOM_POLICY);
}
if (ima_use_appraise_tcb)
- add_rules(default_appraise_rules,
+ add_rules(ns, default_appraise_rules,
ARRAY_SIZE(default_appraise_rules),
IMA_DEFAULT_POLICY);
if (ima_use_critical_data)
- add_rules(critical_data_rules,
+ add_rules(ns, critical_data_rules,
ARRAY_SIZE(critical_data_rules),
IMA_DEFAULT_POLICY);
atomic_set(&ima_setxattr_allowed_hash_algorithms, 0);
- ima_update_policy_flags();
+ ima_update_policy_flags(ns);
}
/* Make sure we have a valid policy, at least containing some rules. */
-int ima_check_policy(void)
+int ima_check_policy(struct ima_namespace *ns)
{
- if (list_empty(&ima_temp_rules))
+ if (list_empty(&ns->ima_temp_rules))
return -EINVAL;
return 0;
}
/**
* ima_update_policy - update default_rules with new measure rules
- *
+ * @ns: IMA namespace that has the policy
* Called on file .release to update the default rules with a complete new
* policy. What we do here is to splice ima_policy_rules and ima_temp_rules so
* they make a queue. The policy may be updated multiple times and this is the
@@ -1030,16 +1033,17 @@ int ima_check_policy(void)
* Policy rules are never deleted so ima_policy_flag gets zeroed only once when
* we switch from the default policy to user defined.
*/
-void ima_update_policy(void)
+void ima_update_policy(struct ima_namespace *ns)
{
- struct list_head *policy = &ima_policy_rules;
+ struct list_head *policy = &ns->ima_policy_rules;
- list_splice_tail_init_rcu(&ima_temp_rules, policy, synchronize_rcu);
+ list_splice_tail_init_rcu(&ns->ima_temp_rules, policy,
+ synchronize_rcu);
- if (ima_rules != (struct list_head __rcu *)policy) {
- ima_policy_flag = 0;
+ if (ns->ima_rules != (struct list_head __rcu *)policy) {
+ ns->ima_policy_flag = 0;
- rcu_assign_pointer(ima_rules, policy);
+ rcu_assign_pointer(ns->ima_rules, policy);
/*
* IMA architecture specific policy rules are specified
* as strings and converted to an array of ima_entry_rules
@@ -1048,10 +1052,10 @@ void ima_update_policy(void)
*/
kfree(arch_policy_entry);
}
- ima_update_policy_flags();
+ ima_update_policy_flags(ns);
/* Custom IMA policy has been loaded */
- ima_process_queued_keys();
+ ima_process_queued_keys(ns);
}
/* Keep the enumeration in sync with the policy_tokens! */
@@ -1123,7 +1127,8 @@ static const match_table_t policy_tokens = {
{Opt_err, NULL}
};
-static int ima_lsm_rule_init(struct ima_rule_entry *entry,
+static int ima_lsm_rule_init(struct ima_namespace *ns,
+ struct ima_rule_entry *entry,
substring_t *args, int lsm_rule, int audit_type)
{
int result;
@@ -1143,7 +1148,8 @@ static int ima_lsm_rule_init(struct ima_rule_entry *entry,
pr_warn("rule for LSM \'%s\' is undefined\n",
entry->lsm[lsm_rule].args_p);
- if (ima_rules == (struct list_head __rcu *)(&ima_default_rules)) {
+ if (ns->ima_rules ==
+ (struct list_head __rcu *)&ns->ima_default_rules) {
kfree(entry->lsm[lsm_rule].args_p);
entry->lsm[lsm_rule].args_p = NULL;
result = -EINVAL;
@@ -1398,7 +1404,8 @@ static unsigned int ima_parse_appraise_algos(char *arg)
return res;
}
-static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
+static int ima_parse_rule(struct ima_namespace *ns,
+ char *rule, struct ima_rule_entry *entry)
{
struct audit_buffer *ab;
char *from;
@@ -1748,37 +1755,37 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
break;
case Opt_obj_user:
ima_log_string(ab, "obj_user", args[0].from);
- result = ima_lsm_rule_init(entry, args,
+ result = ima_lsm_rule_init(ns, entry, args,
LSM_OBJ_USER,
AUDIT_OBJ_USER);
break;
case Opt_obj_role:
ima_log_string(ab, "obj_role", args[0].from);
- result = ima_lsm_rule_init(entry, args,
+ result = ima_lsm_rule_init(ns, entry, args,
LSM_OBJ_ROLE,
AUDIT_OBJ_ROLE);
break;
case Opt_obj_type:
ima_log_string(ab, "obj_type", args[0].from);
- result = ima_lsm_rule_init(entry, args,
+ result = ima_lsm_rule_init(ns, entry, args,
LSM_OBJ_TYPE,
AUDIT_OBJ_TYPE);
break;
case Opt_subj_user:
ima_log_string(ab, "subj_user", args[0].from);
- result = ima_lsm_rule_init(entry, args,
+ result = ima_lsm_rule_init(ns, entry, args,
LSM_SUBJ_USER,
AUDIT_SUBJ_USER);
break;
case Opt_subj_role:
ima_log_string(ab, "subj_role", args[0].from);
- result = ima_lsm_rule_init(entry, args,
+ result = ima_lsm_rule_init(ns, entry, args,
LSM_SUBJ_ROLE,
AUDIT_SUBJ_ROLE);
break;
case Opt_subj_type:
ima_log_string(ab, "subj_type", args[0].from);
- result = ima_lsm_rule_init(entry, args,
+ result = ima_lsm_rule_init(ns, entry, args,
LSM_SUBJ_TYPE,
AUDIT_SUBJ_TYPE);
break;
@@ -1911,12 +1918,13 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
/**
* ima_parse_add_rule - add a rule to ima_policy_rules
+ * @ns: IMA namespace that has the policy
* @rule - ima measurement policy rule
*
* Avoid locking by allowing just one writer at a time in ima_write_policy()
* Returns the length of the rule parsed, an error code on failure
*/
-ssize_t ima_parse_add_rule(char *rule)
+ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule)
{
static const char op[] = "update_policy";
char *p;
@@ -1940,7 +1948,7 @@ ssize_t ima_parse_add_rule(char *rule)
INIT_LIST_HEAD(&entry->list);
- result = ima_parse_rule(p, entry);
+ result = ima_parse_rule(ns, p, entry);
if (result) {
ima_free_rule(entry);
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
@@ -1949,23 +1957,24 @@ ssize_t ima_parse_add_rule(char *rule)
return result;
}
- list_add_tail(&entry->list, &ima_temp_rules);
+ list_add_tail(&entry->list, &ns->ima_temp_rules);
return len;
}
/**
- * ima_delete_rules() called to cleanup invalid in-flight policy.
+ * ima_delete_rules - called to cleanup invalid in-flight policy.
+ * @ns: IMA namespace that has the policy
* We don't need locking as we operate on the temp list, which is
* different from the active one. There is also only one user of
* ima_delete_rules() at a time.
*/
-void ima_delete_rules(void)
+void ima_delete_rules(struct ima_namespace *ns)
{
struct ima_rule_entry *entry, *tmp;
temp_ima_appraise = 0;
- list_for_each_entry_safe(entry, tmp, &ima_temp_rules, list) {
+ list_for_each_entry_safe(entry, tmp, &ns->ima_temp_rules, list) {
list_del(&entry->list);
ima_free_rule(entry);
}
@@ -1991,12 +2000,13 @@ static const char *const mask_tokens[] = {
void *ima_policy_start(struct seq_file *m, loff_t *pos)
{
+ struct ima_namespace *ns = &init_ima_ns;
loff_t l = *pos;
struct ima_rule_entry *entry;
struct list_head *ima_rules_tmp;
rcu_read_lock();
- ima_rules_tmp = rcu_dereference(ima_rules);
+ ima_rules_tmp = rcu_dereference(ns->ima_rules);
list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
if (!l--) {
rcu_read_unlock();
@@ -2009,6 +2019,7 @@ void *ima_policy_start(struct seq_file *m, loff_t *pos)
void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos)
{
+ struct ima_namespace *ns = &init_ima_ns;
struct ima_rule_entry *entry = v;
rcu_read_lock();
@@ -2016,8 +2027,8 @@ void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos)
rcu_read_unlock();
(*pos)++;
- return (&entry->list == &ima_default_rules ||
- &entry->list == &ima_policy_rules) ? NULL : entry;
+ return (&entry->list == &ns->ima_default_rules ||
+ &entry->list == &ns->ima_policy_rules) ? NULL : entry;
}
void ima_policy_stop(struct seq_file *m, void *v)
@@ -2284,6 +2295,7 @@ int ima_policy_show(struct seq_file *m, void *v)
*/
bool ima_appraise_signature(enum kernel_read_file_id id)
{
+ struct ima_namespace *ns = &init_ima_ns;
struct ima_rule_entry *entry;
bool found = false;
enum ima_hooks func;
@@ -2299,7 +2311,7 @@ bool ima_appraise_signature(enum kernel_read_file_id id)
func = read_idmap[id] ?: FILE_CHECK;
rcu_read_lock();
- ima_rules_tmp = rcu_dereference(ima_rules);
+ ima_rules_tmp = rcu_dereference(ns->ima_rules);
list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
if (entry->action != APPRAISE)
continue;
diff --git a/security/integrity/ima/ima_queue_keys.c b/security/integrity/ima/ima_queue_keys.c
index 93056c03bf5a..e366a21dd8be 100644
--- a/security/integrity/ima/ima_queue_keys.c
+++ b/security/integrity/ima/ima_queue_keys.c
@@ -10,6 +10,7 @@
#include <linux/user_namespace.h>
#include <linux/workqueue.h>
+#include <linux/ima.h>
#include <keys/asymmetric-type.h>
#include "ima.h"
@@ -42,7 +43,7 @@ static bool timer_expired;
static void ima_keys_handler(struct work_struct *work)
{
timer_expired = true;
- ima_process_queued_keys();
+ ima_process_queued_keys(&init_ima_ns);
}
/*
@@ -130,11 +131,15 @@ bool ima_queue_key(struct key *keyring, const void *payload,
* This function sets ima_process_keys to true and processes queued keys.
* From here on keys will be processed right away (not queued).
*/
-void ima_process_queued_keys(void)
+void ima_process_queued_keys(struct ima_namespace *ns)
{
struct ima_key_entry *entry, *tmp;
bool process = false;
+ /* only applies to init_ima_ns */
+ if (ns != &init_ima_ns)
+ return;
+
if (ima_process_keys)
return;
@@ -159,7 +164,7 @@ void ima_process_queued_keys(void)
list_for_each_entry_safe(entry, tmp, &ima_keys, list) {
if (!timer_expired)
- process_buffer_measurement(&init_user_ns, NULL,
+ process_buffer_measurement(ns, &init_user_ns, NULL,
entry->payload,
entry->payload_len,
entry->keyring_name,
--
2.37.3
From: Christian Brauner <[email protected]>
When securityfs creates a new file or directory via
securityfs_create_dentry() it will take an additional reference on the
newly created dentry after it has attached the new inode to the new
dentry and added it to the hashqueues.
If we contrast this with debugfs, which has the same underlying logic as
securityfs, it uses a similar pairing as securityfs. Where securityfs
has the securityfs_create_dentry() and securityfs_remove() pairing,
debugfs has the __debugfs_create_file() and debugfs_remove() pairing.
In contrast to securityfs, debugfs doesn't take an additional reference
on the newly created dentry in __debugfs_create_file() which would need
to be put in debugfs_remove().
The additional dget() isn't a problem per se. In the current
implementation of securityfs each created dentry pins the filesystem via
securityfs_create_dentry() until it is removed. Since it is virtually
guaranteed that there is at least one user of securityfs that has created
dentries the initial securityfs mount cannot go away until all dentries
have been removed.
Since most of the users of the initial securityfs mount don't go away
until the system is shutdown the initial securityfs won't go away when
unmounted. Instead a mount will usually surface the same superblock as
before. The additional dget() doesn't matter in this scenario since it
is required that all dentries have been cleaned up by the respective
users before the superblock can be destroyed, i.e. superblock shutdown
is tied to the lifetime of the associated dentries.
However, in order to support ima namespaces we need to extend securityfs
to support being mounted outside of the initial user namespace. For
namespaced users the pinning logic doesn't make sense. Whereas in the
initial namespace the securityfs instance and the associated data
structures of its users can't go away for reason explained earlier users
of non-initial securityfs instances do go away when the last users of
the namespace are gone.
So for those users we neither want to duplicate the pinning logic nor
make the global securityfs instance display different information based
on the namespace. Both options would be really messy and hacky.
Instead we will simply give each namespace its own securityfs instance
similar to how each ipc namespace has its own mqueue instance and all
entries in there are cleaned up on umount or when the last user of the
associated namespace is gone.
This means that the superblock's lifetime isn't tied to the dentries.
Instead the last umount, without any fds kept open, will trigger a clean
shutdown. But now the additional dget() gets in the way. Instead of
being able to rely on the generic superblock shutdown logic we would
need to drop the additional dentry reference during superblock shutdown
for all associated users. That would force the use of a generic
coordination mechanism for current and future users of securityfs which
is unnecessary. Simply remove the additional dget() in
securityfs_dentry_create().
In securityfs_remove() we will call dget() to take an additional
reference on the dentry about to be removed. After simple_unlink() or
simple_rmdir() have dropped the dentry refcount we can call d_delete()
which will either turn the dentry into negative dentry if our earlier
dget() is the only reference to the dentry, i.e. it has no other users,
or remove it from the hashqueues in case there are additional users.
All of these changes should not have any effect on the userspace
semantics of the initial securityfs mount.
Signed-off-by: Christian Brauner <[email protected]>
Cc: John Johansen <[email protected]>
Cc: Matthew Garrett <[email protected]>
Cc: Micah Morton <[email protected]>
Cc: Kentaro Takeda <[email protected]>
Cc: James Morris <[email protected]>
Cc: Jarkko Sakkinen <[email protected]>
Signed-off-by: Stefan Berger <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Reviewed-by: Serge Hallyn <[email protected]>
---
v13:
- Slight improvements in 1st paragraph of commit message
---
security/inode.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/security/inode.c b/security/inode.c
index 6c326939750d..13e6780c4444 100644
--- a/security/inode.c
+++ b/security/inode.c
@@ -159,7 +159,6 @@ static struct dentry *securityfs_create_dentry(const char *name, umode_t mode,
inode->i_fop = fops;
}
d_instantiate(dentry, inode);
- dget(dentry);
inode_unlock(dir);
return dentry;
@@ -302,10 +301,12 @@ void securityfs_remove(struct dentry *dentry)
dir = d_inode(dentry->d_parent);
inode_lock(dir);
if (simple_positive(dentry)) {
+ dget(dentry);
if (d_is_dir(dentry))
simple_rmdir(dir, dentry);
else
simple_unlink(dir, dentry);
+ d_delete(dentry);
dput(dentry);
}
inode_unlock(dir);
--
2.37.3
When the rbtree of an IMA namespace is torn down, also remove those iints
that are completely unused since only the torn-down namespace stored data
about the associated inode in it.
An iint is unused when the following two conditions are met:
- Its ns_status list is empty which means that no IMA namespace
currently has auditing related state stored in it.
- The iint's flags don't contain any of the flags IMA_MEASURE,
IMA_APPRAISE or IMA_HASH that the host would still store there.
It doesn't need an ns_status list for these but also only for
IMA_AUDIT.
Introduce the #define IMA_IINT_FLAGS that represent the mask to test the
iint->flags with in this case. This test provides the reason to keep the
iint if any of these flags are set.
The IMA_IINT_FLAGS mask will loose its flags as more flags are namespaced
and can then be removed in the end and only the check for the empty list
will remain.
Process the list of garbage-collected ns_status outside the locking of
the ns_status tree and related lock-group and free any iint that was
previously found to be unused while walking the list. File accesses, that
may have happened in the meantime, could have re-activated the iint and
therefore pass along the test function to check whether the iint is still
unused.
Signed-off-by: Stefan Berger <[email protected]>
---
v11:
- change write_lock to read_lock in callback
---
security/integrity/iint.c | 4 +++
security/integrity/ima/ima.h | 2 ++
security/integrity/ima/ima_ns_status.c | 43 +++++++++++++++++++++++++-
security/integrity/integrity.h | 1 +
4 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index 4580df0e716e..b0996bd0ee67 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -158,6 +158,10 @@ void integrity_inode_free(struct inode *inode, iint_removable_cb check)
write_lock(&integrity_iint_lock);
iint = __integrity_iint_find(inode);
+ if (!iint) {
+ write_unlock(&integrity_iint_lock);
+ return;
+ }
if (check)
freeit = check(iint);
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 4ac937c2c039..dfbe1a59b9a1 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -502,6 +502,8 @@ static inline int ima_filter_rule_match(u32 secid, u32 field, u32 op,
#define IMA_NS_STATUS_ACTIONS IMA_AUDIT
#define IMA_NS_STATUS_FLAGS (IMA_AUDIT | IMA_AUDITED)
+#define IMA_IINT_FLAGS (IMA_MEASURE | IMA_APPRAISE | IMA_HASH)
+
static inline unsigned long iint_flags(struct integrity_iint_cache *iint,
struct ns_status *ns_status)
{
diff --git a/security/integrity/ima/ima_ns_status.c b/security/integrity/ima/ima_ns_status.c
index 9c753caad6ac..32d75dbd9c21 100644
--- a/security/integrity/ima/ima_ns_status.c
+++ b/security/integrity/ima/ima_ns_status.c
@@ -131,6 +131,26 @@ static void ns_status_free(struct ima_namespace *ns,
kmem_cache_free(ns->ns_status_cache, ns_status);
}
+/* Test whether an iint is unused due to empty ns_status list AND the
+ * not-yet namespaced flags are not set on it.
+ */
+static bool __iint_is_unused(struct integrity_iint_cache *iint)
+{
+ return list_empty(&iint->ns_list) &&
+ (iint_flags(iint, NULL) & IMA_IINT_FLAGS) == 0;
+}
+
+static bool iint_is_unused(struct integrity_iint_cache *iint)
+{
+ bool ret;
+
+ read_lock(&iint->ns_list_lock);
+ ret = __iint_is_unused(iint);
+ read_unlock(&iint->ns_list_lock);
+
+ return ret;
+}
+
/*
* ima_free_ns_status_tree - free all items on the ns_status_tree and take each
* one off the list; yield to ns_list free'ers
@@ -161,6 +181,18 @@ void ima_free_ns_status_tree(struct ima_namespace *ns)
if (!list_empty(&ns_status->ns_next)) {
list_del_init(&ns_status->ns_next);
llist_add(&ns_status->gc_llist, &garbage);
+
+ /*
+ * While ns_status->iint is guaranteed to be
+ * there, check whether the iint is still in
+ * use by anyone at this moment.
+ */
+ if (__iint_is_unused(ns_status->iint)) {
+ ns_status->inode_to_remove =
+ ns_status->iint->inode;
+ } else {
+ ns_status->inode_to_remove = NULL;
+ }
ctr++;
}
write_unlock(&ns_status->iint->ns_list_lock);
@@ -180,8 +212,17 @@ void ima_free_ns_status_tree(struct ima_namespace *ns)
} while (restart);
node = llist_del_all(&garbage);
- llist_for_each_entry_safe(ns_status, next, node, gc_llist)
+ llist_for_each_entry_safe(ns_status, next, node, gc_llist) {
+ if (ns_status->inode_to_remove) {
+ /*
+ * Pass along the test function in case inode is in
+ * use now.
+ */
+ integrity_inode_free(ns_status->inode_to_remove,
+ iint_is_unused);
+ }
ns_status_free(ns, ns_status);
+ }
kmem_cache_destroy(ns->ns_status_cache);
}
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 50a5d94593e1..0634320a6bc2 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -167,6 +167,7 @@ struct ns_status {
ino_t i_ino;
u32 i_generation;
struct llist_node gc_llist; /* used while freeing */
+ void *inode_to_remove; /* used while freeing */
#endif
};
--
2.37.3
Implement create_ima_ns() to create an empty ima_namespace. Defer its
initialization to a later point outside this function. Implement
free_ima_ns() to free it.
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
---
v9:
- Set user_ns->ims_ns = NULL in free_ima_ns()
- Refactored create_ima_ns() to defer initialization
- Removed pr_debug functions
---
include/linux/ima.h | 13 ++++++
security/integrity/ima/Makefile | 1 +
security/integrity/ima/ima.h | 15 +++++++
security/integrity/ima/ima_init_ima_ns.c | 2 +-
security/integrity/ima/ima_ns.c | 53 ++++++++++++++++++++++++
5 files changed, 83 insertions(+), 1 deletion(-)
create mode 100644 security/integrity/ima/ima_ns.c
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 2038e2e2a88d..1b1fc643b371 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -249,4 +249,17 @@ static inline bool ima_appraise_signature(enum kernel_read_file_id func)
return false;
}
#endif /* CONFIG_IMA_APPRAISE && CONFIG_INTEGRITY_TRUSTED_KEYRING */
+
+#ifdef CONFIG_IMA_NS
+
+void free_ima_ns(struct user_namespace *ns);
+
+#else
+
+static inline void free_ima_ns(struct user_namespace *user_ns)
+{
+}
+
+#endif /* CONFIG_IMA_NS */
+
#endif /* _LINUX_IMA_H */
diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile
index f8a5e5f3975d..b86a35fbed60 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_IMA_NS) += ima_ns.o
ifeq ($(CONFIG_EFI),y)
ima-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_efi.o
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index efe9c82b4396..9dc199a20fc0 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -171,6 +171,7 @@ extern bool ima_canonical_fmt;
int ima_init(void);
int ima_fs_init(void);
int ima_ns_init(void);
+int ima_init_namespace(struct ima_namespace *ns);
int ima_add_template_entry(struct ima_namespace *ns,
struct ima_template_entry *entry, int violation,
const char *op, struct inode *inode,
@@ -507,4 +508,18 @@ static inline struct ima_namespace
return NULL;
}
+#ifdef CONFIG_IMA_NS
+
+struct ima_namespace *create_ima_ns(void);
+
+#else
+
+static inline struct ima_namespace *create_ima_ns(void)
+{
+ WARN(1, "Cannot create an IMA namespace\n");
+ return ERR_PTR(-EFAULT);
+}
+
+#endif /* CONFIG_IMA_NS */
+
#endif /* __LINUX_IMA_H */
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
index c4fe8f3e9a73..b497062090cf 100644
--- a/security/integrity/ima/ima_init_ima_ns.c
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -8,7 +8,7 @@
#include "ima.h"
-static int ima_init_namespace(struct ima_namespace *ns)
+int ima_init_namespace(struct ima_namespace *ns)
{
int ret;
diff --git a/security/integrity/ima/ima_ns.c b/security/integrity/ima/ima_ns.c
new file mode 100644
index 000000000000..b3b81a1e313e
--- /dev/null
+++ b/security/integrity/ima/ima_ns.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016-2021 IBM Corporation
+ * Author:
+ * Yuqiong Sun <[email protected]>
+ * Stefan Berger <[email protected]>
+ */
+
+#include <linux/ima.h>
+
+#include "ima.h"
+
+static struct kmem_cache *imans_cachep;
+
+struct ima_namespace *create_ima_ns(void)
+{
+ struct ima_namespace *ns;
+
+ ns = kmem_cache_zalloc(imans_cachep, GFP_KERNEL);
+ if (!ns)
+ return ERR_PTR(-ENOMEM);
+
+ return ns;
+}
+
+/* destroy_ima_ns() must only be called after ima_init_namespace() was called */
+static void destroy_ima_ns(struct ima_namespace *ns)
+{
+ unregister_blocking_lsm_notifier(&ns->ima_lsm_policy_notifier);
+ kfree(ns->arch_policy_entry);
+ ima_free_policy_rules(ns);
+}
+
+void free_ima_ns(struct user_namespace *user_ns)
+{
+ struct ima_namespace *ns = user_ns->ima_ns;
+
+ if (!ns || WARN_ON(ns == &init_ima_ns))
+ return;
+
+ destroy_ima_ns(ns);
+
+ kmem_cache_free(imans_cachep, ns);
+
+ user_ns->ima_ns = NULL;
+}
+
+static int __init imans_cache_init(void)
+{
+ imans_cachep = KMEM_CACHE(ima_namespace, SLAB_PANIC);
+ return 0;
+}
+subsys_initcall(imans_cache_init)
--
2.37.3
Add an optional callback function to integrity_inode_free() that,
if provided, must be called and determines whether the iint can be
freed. Adjust all callers of this function to provide a NULL pointer
since none of the existing callers needs this check.
Signed-off-by: Stefan Berger <[email protected]>
---
include/linux/integrity.h | 8 ++++++--
security/integrity/iint.c | 15 +++++++++++++--
security/security.c | 2 +-
3 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/include/linux/integrity.h b/include/linux/integrity.h
index 2ea0f2f65ab6..9fe826b9146e 100644
--- a/include/linux/integrity.h
+++ b/include/linux/integrity.h
@@ -19,10 +19,13 @@ enum integrity_status {
INTEGRITY_UNKNOWN,
};
+struct integrity_iint_cache;
+typedef bool (*iint_removable_cb)(struct integrity_iint_cache *iint);
+
/* List of EVM protected security xattrs */
#ifdef CONFIG_INTEGRITY
extern struct integrity_iint_cache *integrity_inode_get(struct inode *inode);
-extern void integrity_inode_free(struct inode *inode);
+extern void integrity_inode_free(struct inode *inode, iint_removable_cb check);
extern void __init integrity_load_keys(void);
#else
@@ -32,7 +35,8 @@ static inline struct integrity_iint_cache *
return NULL;
}
-static inline void integrity_inode_free(struct inode *inode)
+static inline void integrity_inode_free(struct inode *inode,
+ iint_removable_cb check)
{
return;
}
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index 371cbceaf479..4580df0e716e 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -143,21 +143,32 @@ struct integrity_iint_cache *integrity_inode_get(struct inode *inode)
/**
* integrity_inode_free - called on security_inode_free
* @inode: pointer to the inode
+ * @check: optional callback function to check whether the iint can be freed
*
* Free the integrity information(iint) associated with an inode.
*/
-void integrity_inode_free(struct inode *inode)
+void integrity_inode_free(struct inode *inode, iint_removable_cb check)
{
struct integrity_iint_cache *iint;
+ bool freeit = true;
if (!IS_IMA(inode))
return;
write_lock(&integrity_iint_lock);
+
iint = __integrity_iint_find(inode);
- rb_erase(&iint->rb_node, &integrity_iint_tree);
+
+ if (check)
+ freeit = check(iint);
+ if (freeit)
+ rb_erase(&iint->rb_node, &integrity_iint_tree);
+
write_unlock(&integrity_iint_lock);
+ if (!freeit)
+ return;
+
ima_free_ns_status_list(&iint->ns_list, &iint->ns_list_lock);
iint_free(iint);
diff --git a/security/security.c b/security/security.c
index d1571900a8c7..2b1f9ec8dea9 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1071,7 +1071,7 @@ static void inode_free_by_rcu(struct rcu_head *head)
void security_inode_free(struct inode *inode)
{
- integrity_inode_free(inode);
+ integrity_inode_free(inode, NULL);
call_void_hook(inode_free_security, inode);
/*
* The inode may still be referenced in a path walk and
--
2.37.3
Setup securityfs with symlinks, directories, and files for IMA
namespacing support. The same directory structure that IMA uses on the
host is also created for the namespacing case.
The securityfs file and directory ownerships cannot be set when the
IMA namespace is initialized. Therefore, delay the setup of the file
system to a later point when securityfs is in securityfs_fill_super.
Introduce a variable ima_policy_removed in ima_namespace that is used to
remember whether the policy file has previously been removed and thus
should not be created again in case of unmounting and again mounting
securityfs inside an IMA namespace.
This filesystem can now be mounted as follows:
mount -t securityfs /sys/kernel/security/ /sys/kernel/security/
The following directories, symlinks, and files are available
when IMA namespacing is enabled, otherwise it will be empty:
$ ls -l sys/kernel/security/
total 0
lr--r--r--. 1 root root 0 Dec 2 00:18 ima -> integrity/ima
drwxr-xr-x. 3 root root 0 Dec 2 00:18 integrity
$ ls -l sys/kernel/security/ima/
total 0
-r--r-----. 1 root root 0 Dec 2 00:18 ascii_runtime_measurements
-r--r-----. 1 root root 0 Dec 2 00:18 binary_runtime_measurements
-rw-------. 1 root root 0 Dec 2 00:18 policy
-r--r-----. 1 root root 0 Dec 2 00:18 runtime_measurements_count
-r--r-----. 1 root root 0 Dec 2 00:18 violations
Signed-off-by: Stefan Berger <[email protected]>
Signed-off-by: James Bottomley <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
---
v9:
- rename policy_dentry_removed to ima_policy_removed
---
include/linux/ima.h | 13 ++++++++++
security/inode.c | 6 ++++-
security/integrity/ima/ima.h | 1 +
security/integrity/ima/ima_fs.c | 46 +++++++++++++++++++++++----------
4 files changed, 52 insertions(+), 14 deletions(-)
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 20f9b95090f4..5f5f190626d5 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -41,6 +41,7 @@ extern int ima_measure_critical_data(const char *event_label,
const char *event_name,
const void *buf, size_t buf_len,
bool hash, u8 *digest, size_t digest_len);
+extern int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root);
#ifdef CONFIG_IMA_APPRAISE_BOOTPARAM
extern void ima_appraise_parse_cmdline(void);
@@ -256,6 +257,12 @@ void free_ima_ns(struct user_namespace *ns);
void ima_free_ns_status_list(struct list_head *head, rwlock_t *ns_list_lock);
+static inline int ima_securityfs_init(struct user_namespace *user_ns,
+ struct dentry *root)
+{
+ return ima_fs_ns_init(user_ns, root);
+}
+
#else
static inline void free_ima_ns(struct user_namespace *user_ns)
@@ -267,6 +274,12 @@ static inline void ima_free_ns_status_list(struct list_head *head,
{
}
+static inline int ima_securityfs_init(struct user_namespace *ns,
+ struct dentry *root)
+{
+ return 0;
+}
+
#endif /* CONFIG_IMA_NS */
#endif /* _LINUX_IMA_H */
diff --git a/security/inode.c b/security/inode.c
index c7273fdbca4c..b473ee5cfb34 100644
--- a/security/inode.c
+++ b/security/inode.c
@@ -16,6 +16,7 @@
#include <linux/fs_context.h>
#include <linux/mount.h>
#include <linux/pagemap.h>
+#include <linux/ima.h>
#include <linux/init.h>
#include <linux/namei.h>
#include <linux/security.h>
@@ -83,7 +84,10 @@ static int securityfs_fill_super(struct super_block *sb, struct fs_context *fc)
sb->s_op = &securityfs_super_operations;
sb->s_root->d_inode->i_op = &securityfs_dir_inode_operations;
- return 0;
+ if (ns != &init_user_ns)
+ error = ima_securityfs_init(ns, sb->s_root);
+
+ return error;
}
static int securityfs_get_tree(struct fs_context *fc)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index dfbe1a59b9a1..29f1e9f95869 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -152,6 +152,7 @@ struct ima_namespace {
int valid_policy;
struct dentry *ima_policy;
+ bool ima_policy_removed;
struct notifier_block ima_lsm_policy_notifier;
} __randomize_layout;
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index c41aa61b7393..84cd02a9e19b 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -21,6 +21,7 @@
#include <linux/rcupdate.h>
#include <linux/parser.h>
#include <linux/vmalloc.h>
+#include <linux/ima.h>
#include "ima.h"
@@ -433,6 +434,7 @@ static int ima_release_policy(struct inode *inode, struct file *file)
#if !defined(CONFIG_IMA_WRITE_POLICY) && !defined(CONFIG_IMA_READ_POLICY)
securityfs_remove(ns->ima_policy);
ns->ima_policy = NULL;
+ ns->ima_policy_removed = true;
#elif defined(CONFIG_IMA_WRITE_POLICY)
clear_bit(IMA_FS_BUSY, &ns->ima_fs_flags);
#elif defined(CONFIG_IMA_READ_POLICY)
@@ -449,9 +451,11 @@ static const struct file_operations ima_measure_policy_ops = {
.llseek = generic_file_llseek,
};
-static int __init ima_fs_ns_init(struct ima_namespace *ns)
+int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root)
{
- struct dentry *ima_dir;
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
+ struct dentry *int_dir;
+ struct dentry *ima_dir = NULL;
struct dentry *ima_symlink = NULL;
struct dentry *binary_runtime_measurements = NULL;
struct dentry *ascii_runtime_measurements = NULL;
@@ -459,11 +463,22 @@ static int __init ima_fs_ns_init(struct ima_namespace *ns)
struct dentry *violations = NULL;
int ret;
- ima_dir = securityfs_create_dir("ima", integrity_dir);
- if (IS_ERR(ima_dir))
- return PTR_ERR(ima_dir);
+ /* FIXME: update when evm and integrity are namespaced */
+ if (user_ns != &init_user_ns) {
+ int_dir = securityfs_create_dir("integrity", root);
+ if (IS_ERR(int_dir))
+ return PTR_ERR(int_dir);
+ } else {
+ int_dir = integrity_dir;
+ }
- ima_symlink = securityfs_create_symlink("ima", NULL, "integrity/ima",
+ ima_dir = securityfs_create_dir("ima", int_dir);
+ if (IS_ERR(ima_dir)) {
+ ret = PTR_ERR(ima_dir);
+ goto out;
+ }
+
+ ima_symlink = securityfs_create_symlink("ima", root, "integrity/ima",
NULL);
if (IS_ERR(ima_symlink)) {
ret = PTR_ERR(ima_symlink);
@@ -505,12 +520,15 @@ static int __init ima_fs_ns_init(struct ima_namespace *ns)
goto out;
}
- ns->ima_policy = securityfs_create_file("policy", POLICY_FILE_FLAGS,
- ima_dir, NULL,
- &ima_measure_policy_ops);
- if (IS_ERR(ns->ima_policy)) {
- ret = PTR_ERR(ns->ima_policy);
- goto out;
+ if (!ns->ima_policy_removed) {
+ ns->ima_policy =
+ securityfs_create_file("policy", POLICY_FILE_FLAGS,
+ ima_dir, NULL,
+ &ima_measure_policy_ops);
+ if (IS_ERR(ns->ima_policy)) {
+ ret = PTR_ERR(ns->ima_policy);
+ goto out;
+ }
}
return 0;
@@ -522,11 +540,13 @@ static int __init ima_fs_ns_init(struct ima_namespace *ns)
securityfs_remove(binary_runtime_measurements);
securityfs_remove(ima_symlink);
securityfs_remove(ima_dir);
+ if (user_ns != &init_user_ns)
+ securityfs_remove(int_dir);
return ret;
}
int __init ima_fs_init(void)
{
- return ima_fs_ns_init(&init_ima_ns);
+ return ima_fs_ns_init(&init_user_ns, NULL);
}
--
2.37.3
Show the uid and gid values relative to the user namespace that opened
the IMA policy file. The effect of this change is that when one displays
the policy from the user namespace that originally set the policy,
the same uid and gid values are shown in the policy as those that were
used when the policy was set.
Signed-off-by: Stefan Berger <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
Reviewed-by: Serge Hallyn <[email protected]>
---
v13:
- Rephrased commit message following Serge's suggestion
v9:
- use seq_user_ns and from_k{g,u}id_munged()
---
security/integrity/ima/ima_policy.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 10b85642188f..bcd3227630bb 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -2127,6 +2127,7 @@ static void ima_policy_show_appraise_algos(struct seq_file *m,
int ima_policy_show(struct seq_file *m, void *v)
{
+ struct user_namespace *user_ns = seq_user_ns(m);
struct ima_rule_entry *entry = v;
int i;
char tbuf[64] = {0,};
@@ -2212,7 +2213,8 @@ int ima_policy_show(struct seq_file *m, void *v)
}
if (entry->flags & IMA_UID) {
- snprintf(tbuf, sizeof(tbuf), "%d", __kuid_val(entry->uid));
+ snprintf(tbuf, sizeof(tbuf),
+ "%d", from_kuid_munged(user_ns, entry->uid));
if (entry->uid_op == &uid_gt)
seq_printf(m, pt(Opt_uid_gt), tbuf);
else if (entry->uid_op == &uid_lt)
@@ -2223,7 +2225,8 @@ int ima_policy_show(struct seq_file *m, void *v)
}
if (entry->flags & IMA_EUID) {
- snprintf(tbuf, sizeof(tbuf), "%d", __kuid_val(entry->uid));
+ snprintf(tbuf, sizeof(tbuf),
+ "%d", from_kuid_munged(user_ns, entry->uid));
if (entry->uid_op == &uid_gt)
seq_printf(m, pt(Opt_euid_gt), tbuf);
else if (entry->uid_op == &uid_lt)
@@ -2234,7 +2237,8 @@ int ima_policy_show(struct seq_file *m, void *v)
}
if (entry->flags & IMA_GID) {
- snprintf(tbuf, sizeof(tbuf), "%d", __kgid_val(entry->gid));
+ snprintf(tbuf, sizeof(tbuf),
+ "%d", from_kgid_munged(user_ns, entry->gid));
if (entry->gid_op == &gid_gt)
seq_printf(m, pt(Opt_gid_gt), tbuf);
else if (entry->gid_op == &gid_lt)
@@ -2245,7 +2249,8 @@ int ima_policy_show(struct seq_file *m, void *v)
}
if (entry->flags & IMA_EGID) {
- snprintf(tbuf, sizeof(tbuf), "%d", __kgid_val(entry->gid));
+ snprintf(tbuf, sizeof(tbuf),
+ "%d", from_kgid_munged(user_ns, entry->gid));
if (entry->gid_op == &gid_gt)
seq_printf(m, pt(Opt_egid_gt), tbuf);
else if (entry->gid_op == &gid_lt)
@@ -2256,7 +2261,8 @@ int ima_policy_show(struct seq_file *m, void *v)
}
if (entry->flags & IMA_FOWNER) {
- snprintf(tbuf, sizeof(tbuf), "%d", __kuid_val(entry->fowner));
+ snprintf(tbuf, sizeof(tbuf),
+ "%d", from_kuid_munged(user_ns, entry->fowner));
if (entry->fowner_op == &vfsuid_gt_kuid)
seq_printf(m, pt(Opt_fowner_gt), tbuf);
else if (entry->fowner_op == &vfsuid_lt_kuid)
@@ -2267,7 +2273,8 @@ int ima_policy_show(struct seq_file *m, void *v)
}
if (entry->flags & IMA_FGROUP) {
- snprintf(tbuf, sizeof(tbuf), "%d", __kgid_val(entry->fgroup));
+ snprintf(tbuf, sizeof(tbuf),
+ "%d", from_kgid_munged(user_ns, entry->fgroup));
if (entry->fgroup_op == &vfsgid_gt_kgid)
seq_printf(m, pt(Opt_fgroup_gt), tbuf);
else if (entry->fgroup_op == &vfsgid_lt_kgid)
--
2.37.3
Introduce the IMA securityfs file 'active' that users now need to
write a "1" into (precisely a '1\0' or '1\n') to activate an IMA namespace.
When reading from the file, it shows either '0' or '1'.
The rationale for introducing a file to activate an IMA namespace is that
subsequent support for IMA-measurement and IMA-appraisal will add
configuration files for selecting for example the template that an IMA
namespace is supposed to use for logging measurements, which will add
an IMA namespace configuration stage (using securityfs files) before its
activation. Besides that it allows to create a user namespace with
securityfs mounted and an inactive IMA namespace.
Also, introduce ns_is_active() to be used in those places where the
ima_namespace pointer may either be NULL or where the active flag may not
have been set, yet. An inactive IMA namespace should never be accessed
since it has not been initialized, yet.
Set the init_ima_ns's active flag since it is considered active right from
the beginning.
Signed-off-by: Stefan Berger <[email protected]>
---
v12:
- Fixed uninitialized ret if IS_ERR(active) was true
v11:
- Move code from ima_fs_add_ns_files() into ima_fs_ns_init()
- Use ima_ns_flags and IMA_NS_ACTIVE instead of 'atomic_t active'
v10:
- use memdup_user_nul to copy input from user
- propagating error returned from ima_fs_add_ns_files()
---
security/integrity/ima/ima.h | 6 +++
security/integrity/ima/ima_fs.c | 64 ++++++++++++++++++++++++
security/integrity/ima/ima_init_ima_ns.c | 1 +
security/integrity/ima/ima_main.c | 2 +-
4 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 29f1e9f95869..450e7ec0a9d7 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -126,6 +126,7 @@ struct ima_namespace {
unsigned long ima_ns_flags;
/* Bit numbers for above flags; use BIT() to get flag */
#define IMA_NS_LSM_UPDATE_RULES 0
+#define IMA_NS_ACTIVE 1
struct rb_root ns_status_tree;
rwlock_t ns_tree_lock;
@@ -158,6 +159,11 @@ struct ima_namespace {
} __randomize_layout;
extern struct ima_namespace init_ima_ns;
+static inline bool ns_is_active(struct ima_namespace *ns)
+{
+ return (ns && test_bit(IMA_NS_ACTIVE, &ns->ima_ns_flags));
+}
+
extern const int read_idmap[];
#ifdef CONFIG_HAVE_IMA_KEXEC
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 84cd02a9e19b..301c717e029f 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -451,6 +451,57 @@ static const struct file_operations ima_measure_policy_ops = {
.llseek = generic_file_llseek,
};
+static ssize_t ima_show_active(struct file *filp,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ima_namespace *ns = &init_ima_ns;
+ char tmpbuf[2];
+ ssize_t len;
+
+ len = scnprintf(tmpbuf, sizeof(tmpbuf),
+ "%d\n", !!test_bit(IMA_NS_ACTIVE, &ns->ima_ns_flags));
+ return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
+}
+
+static ssize_t ima_write_active(struct file *filp,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ima_namespace *ns = &init_ima_ns;
+ unsigned int active;
+ char *kbuf;
+ int err;
+
+ if (ns_is_active(ns))
+ return -EBUSY;
+
+ /* accepting '1\n' and '1\0' and no partial writes */
+ if (count >= 3 || *ppos != 0)
+ return -EINVAL;
+
+ kbuf = memdup_user_nul(buf, count);
+ if (IS_ERR(kbuf))
+ return PTR_ERR(kbuf);
+
+ err = kstrtouint(kbuf, 10, &active);
+ kfree(kbuf);
+ if (err)
+ return err;
+
+ if (active != 1)
+ return -EINVAL;
+
+ set_bit(IMA_NS_ACTIVE, &ns->ima_ns_flags);
+
+ return count;
+}
+
+static const struct file_operations ima_active_ops = {
+ .read = ima_show_active,
+ .write = ima_write_active,
+};
+
int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root)
{
struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
@@ -461,6 +512,7 @@ int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root)
struct dentry *ascii_runtime_measurements = NULL;
struct dentry *runtime_measurements_count = NULL;
struct dentry *violations = NULL;
+ struct dentry *active = NULL;
int ret;
/* FIXME: update when evm and integrity are namespaced */
@@ -531,8 +583,20 @@ int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root)
}
}
+ if (ns != &init_ima_ns) {
+ active =
+ securityfs_create_file("active",
+ S_IRUSR | S_IWUSR | S_IRGRP, ima_dir,
+ NULL, &ima_active_ops);
+ if (IS_ERR(active)) {
+ ret = PTR_ERR(active);
+ goto out;
+ }
+ }
+
return 0;
out:
+ securityfs_remove(active);
securityfs_remove(ns->ima_policy);
securityfs_remove(violations);
securityfs_remove(runtime_measurements_count);
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
index e841302cada9..03d326cf82d4 100644
--- a/security/integrity/ima/ima_init_ima_ns.c
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -62,5 +62,6 @@ struct ima_namespace init_ima_ns = {
.ima_lsm_policy_notifier = {
.notifier_call = ima_lsm_policy_change,
},
+ .ima_ns_flags = BIT(IMA_NS_ACTIVE),
};
EXPORT_SYMBOL(init_ima_ns);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 7908acd57e9f..3bae652142f5 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -443,7 +443,7 @@ static int process_measurement(struct user_namespace *user_ns,
while (user_ns) {
ns = ima_ns_from_user_ns(user_ns);
- if (ns) {
+ if (ns_is_active(ns)) {
int rc;
rc = __process_measurement(ns, file, cred, secid, buf,
--
2.37.3
From: Mehmet Kayaalp <[email protected]>
The iint cache stores whether the file is measured, appraised, audited
etc. This patch moves the IMA_AUDIT and IMA_AUDITED flags into the
per-namespace ns_status, enabling IMA audit mechanism to audit the same
file each time it is accessed in a new namespace.
Read and write operations on the iint flags is replaced with function
calls. For reading, iint_flags() returns the bitwise OR of iint->flags
and ns_status->flags. The ns_status flags are masked with
IMA_NS_STATUS_FLAGS (currently only IMA_AUDIT & IMA_AUDITED) while the
iint flags are masked with ~IMA_NS_STATUS_FLAGS. Similarly,
set_iint_flags() writes the one masked portion to the ns_status flags,
while the iint flags receive the remaining flags. The ns_status
parameter added to ima_audit_measurement() is used with the above
functions to query and set the ns_status flags.
Replace all occurrences where the IMA_AUDITED flag is set with the
iint_flags() and set_iint_flags() operations so that the splitting
and merging of the flags works properly. Whenever the IMA_AUDITED flag is
tested, use iint_flags() to receive the merged version of the flags.
Since IMA_AUDITED is also part of the IMA_DONE_MASK, use the new
functions wherever the IMA_DONE_MASK is involved.
Move the IMA_AUDIT flag also into the ns_status flags since this flag
is dependent on policy rules that may be different per namespace.
Signed-off-by: Mehmet Kayaalp <[email protected]>
Signed-off-by: Stefan Berger <[email protected]>
---
v9:
- Use ns_status also in non-namespaced case and use same flag splitting
for namespaced and non-namespaced case, thus use one implementation
for iint_flags() and set_iint_flags()
- Merge-in patch 'Enable re-auditing of modified files'
- Use one implementation of mask_iint_ns_status_flags() for namespaced
and non-namespaced case
- Added IMA_AUDIT to IMA_NS_STATUS_FLAGS since it's a per namespace flag
---
security/integrity/ima/ima.h | 40 ++++++++++++++++++++--
security/integrity/ima/ima_api.c | 8 +++--
security/integrity/ima/ima_main.c | 56 ++++++++++++++++++++++++++-----
security/integrity/integrity.h | 3 ++
4 files changed, 94 insertions(+), 13 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 3206d8cb8ca4..4ac937c2c039 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -314,7 +314,8 @@ int process_buffer_measurement(struct ima_namespace *ns,
int pcr, const char *func_data,
bool buf_hash, u8 *digest, size_t digest_len);
void ima_audit_measurement(struct integrity_iint_cache *iint,
- const unsigned char *filename);
+ const unsigned char *filename,
+ struct ns_status *ns_status);
int ima_alloc_init_template(struct ima_event_data *event_data,
struct ima_template_entry **entry,
struct ima_template_desc *template_desc);
@@ -498,6 +499,34 @@ static inline int ima_filter_rule_match(u32 secid, u32 field, u32 op,
#define POLICY_FILE_FLAGS S_IWUSR
#endif /* CONFIG_IMA_READ_POLICY */
+#define IMA_NS_STATUS_ACTIONS IMA_AUDIT
+#define IMA_NS_STATUS_FLAGS (IMA_AUDIT | IMA_AUDITED)
+
+static inline unsigned long iint_flags(struct integrity_iint_cache *iint,
+ struct ns_status *ns_status)
+{
+ if (!ns_status)
+ return iint->flags;
+
+ return (iint->flags & ~IMA_NS_STATUS_FLAGS) |
+ (ns_status->flags & IMA_NS_STATUS_FLAGS);
+}
+
+static inline unsigned long set_iint_flags(struct integrity_iint_cache *iint,
+ struct ns_status *ns_status,
+ unsigned long flags)
+{
+ unsigned long ns_status_flags = flags & IMA_NS_STATUS_FLAGS;
+
+ WARN_ON(!ns_status && ns_status_flags);
+
+ iint->flags = flags & ~IMA_NS_STATUS_FLAGS;
+ if (ns_status)
+ ns_status->flags = ns_status_flags;
+
+ return flags;
+}
+
static inline
struct user_namespace *ima_user_ns_from_file(const struct file *filp)
{
@@ -535,7 +564,14 @@ static inline struct ns_status *ima_get_ns_status
struct inode *inode,
struct integrity_iint_cache *iint)
{
- return NULL;
+ struct ns_status *ns_status = &iint->ns_status;
+
+ if (list_empty(&iint->ns_list)) {
+ ns_status_init(ns_status);
+ list_add(&ns_status->ns_next, &iint->ns_list);
+ }
+
+ return ns_status;
}
#endif /* CONFIG_IMA_NS */
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 5ea55d172b44..6ca68f6cce01 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -387,14 +387,16 @@ void ima_store_measurement(struct ima_namespace *ns,
}
void ima_audit_measurement(struct integrity_iint_cache *iint,
- const unsigned char *filename)
+ const unsigned char *filename,
+ struct ns_status *ns_status)
{
struct audit_buffer *ab;
char *hash;
const char *algo_name = hash_algo_name[iint->ima_hash->algo];
int i;
+ unsigned long flags = iint_flags(iint, ns_status);
- if (iint->flags & IMA_AUDITED)
+ if (flags & IMA_AUDITED)
return;
hash = kzalloc((iint->ima_hash->length * 2) + 1, GFP_KERNEL);
@@ -417,7 +419,7 @@ void ima_audit_measurement(struct integrity_iint_cache *iint,
audit_log_task_info(ab);
audit_log_end(ab);
- iint->flags |= IMA_AUDITED;
+ set_iint_flags(iint, ns_status, flags | IMA_AUDITED);
out:
kfree(hash);
return;
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 55ddf2c8506a..7908acd57e9f 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -149,6 +149,31 @@ static void ima_rdwr_violation_check(struct ima_namespace *ns,
"invalid_pcr", "open_writers");
}
+static void mask_iint_ns_status_flags(struct integrity_iint_cache *iint,
+ unsigned long mask)
+{
+ struct ns_status *ns_status;
+ unsigned long flags;
+
+ read_lock(&iint->ns_list_lock);
+ if (list_empty(&iint->ns_list)) {
+ /*
+ * An empty list is possible due to __process_measurement only
+ * creating ns_status for IMA_NS_STATUS_ACTIONS.
+ * No ns_status being used implies that for example IMA_AUDIT
+ * was never used and thus IMA_AUDITED cannot have been set.
+ */
+ flags = iint_flags(iint, NULL) & mask;
+ set_iint_flags(iint, NULL, flags);
+ } else {
+ list_for_each_entry(ns_status, &iint->ns_list, ns_next) {
+ flags = iint_flags(iint, ns_status) & mask;
+ set_iint_flags(iint, ns_status, flags);
+ }
+ }
+ read_unlock(&iint->ns_list_lock);
+}
+
static void ima_check_last_writer(struct integrity_iint_cache *iint,
struct inode *inode, struct file *file)
{
@@ -165,8 +190,11 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint,
if (!IS_I_VERSION(inode) ||
!inode_eq_iversion(inode, iint->version) ||
(iint->flags & IMA_NEW_FILE)) {
- iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE);
+ mask_iint_ns_status_flags
+ (iint,
+ ~(IMA_DONE_MASK | IMA_NEW_FILE));
iint->measured_pcrs = 0;
+
if (update)
ima_update_xattr(iint, file);
}
@@ -203,6 +231,7 @@ static int __process_measurement(struct ima_namespace *ns,
{
struct inode *inode = file_inode(file);
struct integrity_iint_cache *iint = NULL;
+ struct ns_status *ns_status = NULL;
struct ima_template_desc *template_desc = NULL;
char *pathbuf = NULL;
char filename[NAME_MAX];
@@ -215,6 +244,7 @@ static int __process_measurement(struct ima_namespace *ns,
bool violation_check;
enum hash_algo hash_algo;
unsigned int allowed_algos = 0;
+ unsigned long flags;
if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
return 0;
@@ -243,6 +273,14 @@ static int __process_measurement(struct ima_namespace *ns,
iint = integrity_inode_get(inode);
if (!iint)
rc = -ENOMEM;
+
+ if (!rc && (action & IMA_NS_STATUS_ACTIONS)) {
+ ns_status = ima_get_ns_status(ns, inode, iint);
+ if (IS_ERR(ns_status)) {
+ rc = PTR_ERR(ns_status);
+ ns_status = NULL;
+ }
+ }
}
if (!rc && violation_check)
@@ -258,11 +296,13 @@ static int __process_measurement(struct ima_namespace *ns,
mutex_lock(&iint->mutex);
+ flags = iint_flags(iint, ns_status);
+
if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags))
/* reset appraisal flags if ima_inode_post_setattr was called */
- iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED |
- IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK |
- IMA_NONACTION_FLAGS);
+ flags &= ~(IMA_APPRAISE | IMA_APPRAISED |
+ IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK |
+ IMA_NONACTION_FLAGS);
/*
* Re-evaulate the file if either the xattr has changed or the
@@ -273,7 +313,7 @@ static int __process_measurement(struct ima_namespace *ns,
((inode->i_sb->s_iflags & SB_I_IMA_UNVERIFIABLE_SIGNATURE) &&
!(inode->i_sb->s_iflags & SB_I_UNTRUSTED_MOUNTER) &&
!(action & IMA_FAIL_UNVERIFIABLE_SIGS))) {
- iint->flags &= ~IMA_DONE_MASK;
+ flags &= ~IMA_DONE_MASK;
iint->measured_pcrs = 0;
}
@@ -281,9 +321,9 @@ static int __process_measurement(struct ima_namespace *ns,
* (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED,
* IMA_AUDIT, IMA_AUDITED)
*/
- iint->flags |= action;
+ flags = set_iint_flags(iint, ns_status, flags | action);
action &= IMA_DO_MASK;
- action &= ~((iint->flags & (IMA_DONE_MASK ^ IMA_MEASURED)) >> 1);
+ action &= ~((flags & (IMA_DONE_MASK ^ IMA_MEASURED)) >> 1);
/* If target pcr is already measured, unset IMA_MEASURE action */
if ((action & IMA_MEASURE) && (iint->measured_pcrs & (0x1 << pcr)))
@@ -360,7 +400,7 @@ static int __process_measurement(struct ima_namespace *ns,
&pathname, filename);
}
if (action & IMA_AUDIT)
- ima_audit_measurement(iint, pathname);
+ ima_audit_measurement(iint, pathname, ns_status);
if ((file->f_flags & O_DIRECT) && (iint->flags & IMA_PERMIT_DIRECTIO))
rc = 0;
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 112836d9b33a..50a5d94593e1 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -204,6 +204,9 @@ struct integrity_iint_cache {
*/
rwlock_t ns_list_lock;
struct list_head ns_list;
+#ifndef CONFIG_IMA_NS
+ struct ns_status ns_status;
+#endif
};
/* rbtree tree calls to lookup, insert, delete
--
2.37.3
Since non-root users can create an IMA namespace they have indirect access
to the host's audit log. To avoid abuse, restrict the creation of those
informational audit messages that can currently be caused by IMA
namespacings to init_ima_ns.
Signed-off-by: Stefan Berger <[email protected]>
---
v12:
- Added missing 'struct ima_namespace' to ima_update_xattr() inline func
---
security/integrity/ima/ima.h | 9 ++++++---
security/integrity/ima/ima_api.c | 10 ++++++----
security/integrity/ima/ima_appraise.c | 6 ++++--
security/integrity/ima/ima_fs.c | 6 ++++--
security/integrity/ima/ima_main.c | 12 +++++++-----
security/integrity/ima/ima_policy.c | 20 ++++++++++++--------
6 files changed, 39 insertions(+), 24 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 450e7ec0a9d7..a2bb7cbd41be 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -305,7 +305,8 @@ int ima_get_action(struct ima_namespace *ns,
struct ima_template_desc **template_desc,
const char *func_data, unsigned int *allowed_algos);
int ima_must_measure(struct inode *inode, int mask, enum ima_hooks func);
-int ima_collect_measurement(struct integrity_iint_cache *iint,
+int ima_collect_measurement(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
struct file *file, void *buf, loff_t size,
enum hash_algo algo, struct modsig *modsig);
void ima_store_measurement(struct ima_namespace *ns,
@@ -373,7 +374,8 @@ int ima_appraise_measurement(enum ima_hooks func,
int ima_must_appraise(struct ima_namespace *ns,
struct user_namespace *mnt_userns, struct inode *inode,
int mask, enum ima_hooks func);
-void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file);
+void ima_update_xattr(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint, struct file *file);
enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint,
enum ima_hooks func);
enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value,
@@ -408,7 +410,8 @@ static inline int ima_must_appraise(struct ima_namespace *ns,
return 0;
}
-static inline void ima_update_xattr(struct integrity_iint_cache *iint,
+static inline void ima_update_xattr(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
struct file *file)
{
}
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 6ca68f6cce01..e5c49fc00402 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -160,8 +160,9 @@ void ima_add_violation(struct ima_namespace *ns,
if (result < 0)
ima_free_template_entry(entry);
err_out:
- integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename,
- op, cause, result, 0);
+ if (ns == &init_ima_ns)
+ integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename,
+ op, cause, result, 0);
}
/**
@@ -242,7 +243,8 @@ static int ima_get_verity_digest(struct integrity_iint_cache *iint,
*
* Return 0 on success, error code otherwise
*/
-int ima_collect_measurement(struct integrity_iint_cache *iint,
+int ima_collect_measurement(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
struct file *file, void *buf, loff_t size,
enum hash_algo algo, struct modsig *modsig)
{
@@ -315,7 +317,7 @@ int ima_collect_measurement(struct integrity_iint_cache *iint,
if (!result)
iint->flags |= IMA_COLLECTED;
out:
- if (result) {
+ if (result && ns == &init_ima_ns) {
if (file->f_flags & O_DIRECT)
audit_cause = "failed(directio)";
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 89c38ce0039e..950f1353c6a1 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -601,7 +601,8 @@ int ima_appraise_measurement(enum ima_hooks func,
/*
* ima_update_xattr - update 'security.ima' hash value
*/
-void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file)
+void ima_update_xattr(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint, struct file *file)
{
struct dentry *dentry = file_dentry(file);
int rc = 0;
@@ -614,7 +615,8 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file)
!(iint->flags & IMA_HASH))
return;
- rc = ima_collect_measurement(iint, file, NULL, 0, ima_hash_algo, NULL);
+ rc = ima_collect_measurement(ns, iint, file, NULL, 0, ima_hash_algo,
+ NULL);
if (rc < 0)
return;
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 301c717e029f..14dffeee727d 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -420,8 +420,10 @@ static int ima_release_policy(struct inode *inode, struct file *file)
}
pr_info("policy update %s\n", cause);
- integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL,
- "policy_update", cause, !ns->valid_policy, 0);
+ if (ns == &init_ima_ns)
+ integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL,
+ "policy_update", cause, !ns->valid_policy,
+ 0);
if (!ns->valid_policy) {
ima_delete_rules(ns);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 3bae652142f5..61442eb8339c 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -174,7 +174,8 @@ static void mask_iint_ns_status_flags(struct integrity_iint_cache *iint,
read_unlock(&iint->ns_list_lock);
}
-static void ima_check_last_writer(struct integrity_iint_cache *iint,
+static void ima_check_last_writer(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
struct inode *inode, struct file *file)
{
fmode_t mode = file->f_mode;
@@ -196,7 +197,7 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint,
iint->measured_pcrs = 0;
if (update)
- ima_update_xattr(iint, file);
+ ima_update_xattr(ns, iint, file);
}
}
mutex_unlock(&iint->mutex);
@@ -221,7 +222,7 @@ void ima_file_free(struct file *file)
if (!iint)
return;
- ima_check_last_writer(iint, inode, file);
+ ima_check_last_writer(ns, iint, inode, file);
}
static int __process_measurement(struct ima_namespace *ns,
@@ -375,7 +376,8 @@ static int __process_measurement(struct ima_namespace *ns,
hash_algo = ima_get_hash_algo(xattr_value, xattr_len);
- rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig);
+ rc = ima_collect_measurement(ns, iint, file, buf, size, hash_algo,
+ modsig);
if (rc == -ENOMEM)
goto out_locked;
@@ -622,7 +624,7 @@ static int __ima_inode_hash(struct ima_namespace *ns, struct inode *inode,
tmp_iint.inode = inode;
mutex_init(&tmp_iint.mutex);
- rc = ima_collect_measurement(&tmp_iint, file, NULL, 0,
+ rc = ima_collect_measurement(ns, &tmp_iint, file, NULL, 0,
ima_hash_algo, NULL);
if (rc < 0) {
/* ima_hash could be allocated in case of failure. */
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index cdcc2a1506ab..05ffd7b03ee3 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1442,15 +1442,16 @@ static unsigned int ima_parse_appraise_algos(char *arg)
static int ima_parse_rule(struct ima_namespace *ns,
char *rule, struct ima_rule_entry *entry)
{
- struct audit_buffer *ab;
+ struct audit_buffer *ab = NULL;
char *from;
char *p;
bool eid_token; /* either euid or egid */
struct ima_template_desc *template_desc;
int result = 0;
- ab = integrity_audit_log_start(audit_context(), GFP_KERNEL,
- AUDIT_INTEGRITY_POLICY_RULE);
+ if (ns == &init_ima_ns)
+ ab = integrity_audit_log_start(audit_context(), GFP_KERNEL,
+ AUDIT_INTEGRITY_POLICY_RULE);
entry->uid = INVALID_UID;
entry->gid = INVALID_GID;
@@ -1988,8 +1989,10 @@ ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule)
entry = kzalloc(sizeof(*entry), GFP_KERNEL_ACCOUNT);
if (!entry) {
- integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
- NULL, op, "-ENOMEM", -ENOMEM, audit_info);
+ if (ns == &init_ima_ns)
+ integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
+ NULL, op, "-ENOMEM", -ENOMEM,
+ audit_info);
return -ENOMEM;
}
@@ -1998,9 +2001,10 @@ ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule)
result = ima_parse_rule(ns, p, entry);
if (result) {
ima_free_rule(entry);
- integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
- NULL, op, "invalid-policy", result,
- audit_info);
+ if (ns == &init_ima_ns)
+ integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
+ NULL, op, "invalid-policy", result,
+ audit_info);
return result;
}
--
2.37.3
Limit the number of policy rules a user can set in non-init_ima_ns to a
hardcoded 1024 rules. This allows to restrict the amount of kernel memory
used for IMA's policy since now any user can create an IMA namespace and
could try to waste kernel memory.
Ignore added rules if the user attempts to exceed this limit by setting
too many additional rules.
Switch the accounting for the memory allocated for IMA policy rules to
GFP_KERNEL_ACCOUNT so that cgroups kernel memory accounting can take
effect. This switch has no effect on the init_ima_ns.
Signed-off-by: Stefan Berger <[email protected]>
---
v11:
- roll back changes to auditing too-many-rules since not auditing from
IMA namespaces
---
security/integrity/ima/ima_policy.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index bcd3227630bb..cdcc2a1506ab 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -336,7 +336,8 @@ static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src)
return ERR_PTR(-EINVAL);
}
- opt_list = kzalloc(struct_size(opt_list, items, count), GFP_KERNEL);
+ opt_list = kzalloc(struct_size(opt_list, items, count),
+ GFP_KERNEL_ACCOUNT);
if (!opt_list) {
kfree(src_copy);
return ERR_PTR(-ENOMEM);
@@ -410,7 +411,7 @@ static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_namespace *ns,
* Immutable elements are copied over as pointers and data; only
* lsm rules can change
*/
- nentry = kmemdup(entry, sizeof(*nentry), GFP_KERNEL);
+ nentry = kmemdup(entry, sizeof(*nentry), GFP_KERNEL_ACCOUNT);
if (!nentry)
return NULL;
@@ -889,7 +890,7 @@ static void add_rules(struct ima_namespace *ns,
if (policy_rule & IMA_CUSTOM_POLICY) {
entry = kmemdup(&entries[i], sizeof(*entry),
- GFP_KERNEL);
+ GFP_KERNEL_ACCOUNT);
if (!entry)
continue;
@@ -926,7 +927,7 @@ static int __init ima_init_arch_policy(struct ima_namespace *ns)
ns->arch_policy_entry = kcalloc(arch_entries + 1,
sizeof(*ns->arch_policy_entry),
- GFP_KERNEL);
+ GFP_KERNEL_ACCOUNT);
if (!ns->arch_policy_entry)
return 0;
@@ -1038,8 +1039,20 @@ void __init ima_init_policy(struct ima_namespace *ns)
/* Make sure we have a valid policy, at least containing some rules. */
int ima_check_policy(struct ima_namespace *ns)
{
+ struct ima_rule_entry *entry;
+ size_t len1 = 0;
+ size_t len2 = 0;
+
if (list_empty(&ns->ima_temp_rules))
return -EINVAL;
+ if (ns != &init_ima_ns) {
+ list_for_each_entry(entry, &ns->ima_temp_rules, list)
+ len1++;
+ list_for_each_entry(entry, &ns->ima_policy_rules, list)
+ len2++;
+ if (len1 + len2 > 1024)
+ return -ENOSPC;
+ }
return 0;
}
@@ -1973,7 +1986,7 @@ ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule)
if (*p == '#' || *p == '\0')
return len;
- entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL_ACCOUNT);
if (!entry) {
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
NULL, op, "-ENOMEM", -ENOMEM, audit_info);
--
2.37.3
Introduce the IMA_NS in Kconfig for IMA namespace enablement.
Enable the lazy initialization of an IMA namespace when a user mounts
SecurityFS and writes '1' into IMA's 'active' securityfs file. A
user_namespace will now get a pointer to an ima_namespace and therefore
implement get_current_ns() for the namespacing case that returns this
pointer. Use get_current_ns() in those places that require access to the
current IMA namespace. In some places, primarily those related to
IMA-appraisal and changes to file attributes, keep the pointer to
init_ima_ns, since there flags related to file measurements may be
affected, which are not supported in IMA namespaces, yet.
Before using the ima_namespace pointer test it with ns_is_active()
to check whether it is NULL and whether the ima_namespace is active.
If it's not active, it cannot be used, yet. Therefore, return early
from those functions that may now get either get a NULL pointer from
this call or where ns->active is still 0. The init_ima_ns is always
set to be active, thus passing the check.
Implement ima_ns_from_file() for SecurityFS-related files where we can
now get the IMA namespace via the user namespace pointer associated
with the superblock of the SecurityFS filesystem instance.
Return -EACCES to IMA's securityfs files, except for the 'active' file,
until the IMA namespace has been set to active.
Switch access to userns->ima_ns to use acquire/release semantics to ensure
that a newly created ima_namespace structure is fully visible upon access.
Only emit the kernel log message 'policy update completed' for the
init_ima_ns.
When parsing an IMA policy rule use the user namespace of the opener
to translate uid and gid values to kernel values rather than the user
namespace of the writer.
Gate access to ima_appraise variable to init_ima_ns in ima_load_data()
and ima_write_policy().
Gate access to temp_ima_appraise variable to init_ima_ns in
ima_delete_rules().
In ima_file_free remove the check on the ima_policy_flag so that
ima_check_last_writer() is called if there's an iint and therefore a
namespace is interested in the file.
Only update the xattr hash if a namespace has a hash policy.
Gate setting of AUDIT rules to only allow host root with CAP_WRITE_AUDIT
inside a namespace or host root with CAP_WRITE_AUDIT entering a namespace
to set them. This prevents normal users, who created an IMA namespace and
have CAP_WRITE_AUDIT in their user namespace, from setting these rules
and flooding the host's audit log.
Automatically select IMA_CONFIG_READ when selecting IMA_NS.
Signed-off-by: Stefan Berger <[email protected]>
---
v15:
- Require CAP_AUDIT_WRITE instead of CAP_SYS_ADMIN for host root to
set IMA audit rules in an IMA namespace
v12:
- replace set_bit() by clear_bit() to clear IMA_NS_ACTIVE
v11:
- Remove check on ima_policy_flag so that any namespace modifying a
file will cause re-auditing of a file if there's an existing iint
- Only update the hash on a file if a namespace has a hash policy
- Use ima_ns_from_user_ns() in ima_ns_from_file()
- Kconfig: Select IMA_READ_POLICY when IMA_NS is selected
v10:
- dropped ima_ns_to_user_ns(); using current_user_ns() instead
- Pass user_namespace of file opener into ima_parse_rule and propagate
this parameter back all the way to the initial caller in the chain
- Gate access to ima_appraise to init_ima_ns in ima_write_policy()
v9:
- ima_post_key_create_or_update: Only handle key if in init_ima_ns
- Removed ns == NULL checks where user_namespace is now passed
- Defer setting of user_ns->ima_ns until end of ima_fs_ns_init();
required new ima_free_imans() and new user_ns_set_ima_ns()
- Only emit log message 'policy update completed' for init_ima_ns
- Introduce get_current_ns() only in this patch
- Check for ns == &init_ima_ns in ima_load_data()
---
include/linux/ima.h | 1 +
init/Kconfig | 14 +++
kernel/user_namespace.c | 2 +
security/integrity/ima/ima.h | 57 +++++++++++--
security/integrity/ima/ima_appraise.c | 8 ++
security/integrity/ima/ima_asymmetric_keys.c | 6 +-
security/integrity/ima/ima_fs.c | 89 +++++++++++++++-----
security/integrity/ima/ima_init.c | 2 +-
security/integrity/ima/ima_init_ima_ns.c | 2 +
security/integrity/ima/ima_main.c | 40 +++++----
security/integrity/ima/ima_ns.c | 15 +++-
security/integrity/ima/ima_policy.c | 63 +++++++++-----
12 files changed, 234 insertions(+), 65 deletions(-)
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 5f5f190626d5..541205b5a25f 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -11,6 +11,7 @@
#include <linux/fs.h>
#include <linux/security.h>
#include <linux/kexec.h>
+#include <linux/user_namespace.h>
#include <crypto/hash_info.h>
struct linux_binprm;
diff --git a/init/Kconfig b/init/Kconfig
index 44e90b28a30f..eadd44bc5e60 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1269,6 +1269,20 @@ config NET_NS
Allow user space to create what appear to be multiple instances
of the network stack.
+config IMA_NS
+ bool "IMA namespace"
+ depends on USER_NS
+ depends on IMA
+ select IMA_READ_POLICY
+ default n
+ help
+ Allow the creation of an IMA namespace for each user namespace.
+ Namespaced IMA enables having IMA features work separately
+ in each IMA namespace.
+ Currently, only the audit status flags are stored in the namespace,
+ which allows the same file to be audited each time it is accessed
+ in a new namespace.
+
endif # NAMESPACES
config CHECKPOINT_RESTORE
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 54211dbd516c..e1141f7b3a60 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -21,6 +21,7 @@
#include <linux/fs_struct.h>
#include <linux/bsearch.h>
#include <linux/sort.h>
+#include <linux/ima.h>
static struct kmem_cache *user_ns_cachep __read_mostly;
static DEFINE_MUTEX(userns_state_mutex);
@@ -213,6 +214,7 @@ static void free_user_ns(struct work_struct *work)
kfree(ns->projid_map.forward);
kfree(ns->projid_map.reverse);
}
+ free_ima_ns(ns);
retire_userns_sysctls(ns);
key_free_user_ns(ns);
ns_free_inum(&ns->ns);
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index a2bb7cbd41be..fa2b96957a68 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -341,10 +341,10 @@ int ima_match_policy(struct ima_namespace *ns,
int mask, int flags, int *pcr,
struct ima_template_desc **template_desc,
const char *func_data, unsigned int *allowed_algos);
-void ima_init_policy(struct ima_namespace *ns);
+void ima_init_policy(struct user_namespace *user_ns);
void ima_update_policy(struct ima_namespace *ns);
void ima_update_policy_flags(struct ima_namespace *ns);
-ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule);
+ssize_t ima_parse_add_rule(struct user_namespace *user_ns, char *rule);
void ima_delete_rules(struct ima_namespace *ns);
int ima_check_policy(struct ima_namespace *ns);
void ima_free_policy_rules(struct ima_namespace *ns);
@@ -545,32 +545,72 @@ struct user_namespace *ima_user_ns_from_file(const struct file *filp)
return file_sb_user_ns(filp);
}
+#ifdef CONFIG_IMA_NS
+
static inline struct ima_namespace
*ima_ns_from_user_ns(struct user_namespace *user_ns)
{
- if (user_ns == &init_user_ns)
- return &init_ima_ns;
- return NULL;
+ /* Pairs with smp_store_releases() in user_ns_set_ima_ns(). */
+ return smp_load_acquire(&user_ns->ima_ns);
}
-#ifdef CONFIG_IMA_NS
+static inline void user_ns_set_ima_ns(struct user_namespace *user_ns,
+ struct ima_namespace *ns)
+{
+ /* Pairs with smp_load_acquire() in ima_ns_from_user_ns() */
+ smp_store_release(&user_ns->ima_ns, ns);
+}
+
+static inline struct ima_namespace *get_current_ns(void)
+{
+ return ima_ns_from_user_ns(current_user_ns());
+}
struct ima_namespace *create_ima_ns(void);
+void ima_free_ima_ns(struct ima_namespace *ns);
+
struct ns_status *ima_get_ns_status(struct ima_namespace *ns,
struct inode *inode,
struct integrity_iint_cache *iint);
void ima_free_ns_status_tree(struct ima_namespace *ns);
+static inline struct ima_namespace *ima_ns_from_file(const struct file *filp)
+{
+ struct user_namespace *user_ns = ima_user_ns_from_file(filp);
+
+ return ima_ns_from_user_ns(user_ns);
+}
+
#else
+static inline struct ima_namespace
+*ima_ns_from_user_ns(struct user_namespace *user_ns)
+{
+ if (user_ns == &init_user_ns)
+ return &init_ima_ns;
+ return NULL;
+}
+
+static inline void user_ns_set_ima_ns(struct user_namespace *user_ns,
+ struct ima_namespace *ns)
+{
+}
+
+static inline struct ima_namespace *get_current_ns(void)
+{
+ return &init_ima_ns;
+}
+
static inline struct ima_namespace *create_ima_ns(void)
{
WARN(1, "Cannot create an IMA namespace\n");
return ERR_PTR(-EFAULT);
}
+static inline void ima_free_ima_ns(struct ima_namespace *ns) {}
+
static inline struct ns_status *ima_get_ns_status
(struct ima_namespace *ns,
struct inode *inode,
@@ -586,6 +626,11 @@ static inline struct ns_status *ima_get_ns_status
return ns_status;
}
+static inline struct ima_namespace *ima_ns_from_file(const struct file *filp)
+{
+ return &init_ima_ns;
+}
+
#endif /* CONFIG_IMA_NS */
#endif /* __LINUX_IMA_H */
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 950f1353c6a1..d1ac48c972df 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -76,6 +76,9 @@ int ima_must_appraise(struct ima_namespace *ns,
{
u32 secid;
+ if (ns != &init_ima_ns)
+ return 0;
+
if (!ima_appraise)
return 0;
@@ -600,6 +603,8 @@ int ima_appraise_measurement(enum ima_hooks func,
/*
* ima_update_xattr - update 'security.ima' hash value
+ *
+ * Only a namespace with a hash policy would ever update the file.
*/
void ima_update_xattr(struct ima_namespace *ns,
struct integrity_iint_cache *iint, struct file *file)
@@ -607,6 +612,9 @@ void ima_update_xattr(struct ima_namespace *ns,
struct dentry *dentry = file_dentry(file);
int rc = 0;
+ if (!ns_is_active(ns) || !(ns->ima_policy_flag & IMA_HASH))
+ return;
+
/* do not collect and update hash for digital signatures */
if (test_bit(IMA_DIGSIG, &iint->atomic_flags))
return;
diff --git a/security/integrity/ima/ima_asymmetric_keys.c b/security/integrity/ima/ima_asymmetric_keys.c
index 70d87df26068..0d2cc1e23cde 100644
--- a/security/integrity/ima/ima_asymmetric_keys.c
+++ b/security/integrity/ima/ima_asymmetric_keys.c
@@ -30,9 +30,13 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
const void *payload, size_t payload_len,
unsigned long flags, bool create)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
bool queued = false;
+ /* only handle key if related to init_ima_ns */
+ if (ns != &init_ima_ns)
+ return;
+
/* Only asymmetric keys are handled by this hook. */
if (key->type != &key_type_asymmetric)
return;
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 14dffeee727d..ab4d2b63ab57 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -49,7 +49,10 @@ static ssize_t ima_show_htable_violations(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(filp);
+
+ if (!ns_is_active(ns))
+ return -EACCES;
return ima_show_htable_value(buf, count, ppos,
&ns->ima_htable.violations);
@@ -64,7 +67,10 @@ static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(filp);
+
+ if (!ns_is_active(ns))
+ return -EACCES;
return ima_show_htable_value(buf, count, ppos, &ns->ima_htable.len);
}
@@ -77,7 +83,7 @@ static const struct file_operations ima_measurements_count_ops = {
/* returns pointer to hlist_node */
static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(m->file);
loff_t l = *pos;
struct ima_queue_entry *qe;
@@ -95,7 +101,7 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(m->file);
struct ima_queue_entry *qe = v;
/* lock protects when reading beyond last element
@@ -198,6 +204,11 @@ static const struct seq_operations ima_measurments_seqops = {
static int ima_measurements_open(struct inode *inode, struct file *file)
{
+ struct ima_namespace *ns = ima_ns_from_file(file);
+
+ if (!ns_is_active(ns))
+ return -EACCES;
+
return seq_open(file, &ima_measurments_seqops);
}
@@ -264,6 +275,11 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
{
+ struct ima_namespace *ns = ima_ns_from_file(file);
+
+ if (!ns_is_active(ns))
+ return -EACCES;
+
return seq_open(file, &ima_ascii_measurements_seqops);
}
@@ -274,7 +290,7 @@ static const struct file_operations ima_ascii_measurements_ops = {
.release = seq_release,
};
-static ssize_t ima_read_policy(struct ima_namespace *ns, char *path)
+static ssize_t ima_read_policy(struct user_namespace *user_ns, char *path)
{
void *data = NULL;
char *datap;
@@ -299,7 +315,7 @@ static ssize_t ima_read_policy(struct ima_namespace *ns, char *path)
datap = data;
while (size > 0 && (p = strsep(&datap, "\n"))) {
pr_debug("rule: %s\n", p);
- rc = ima_parse_add_rule(ns, p);
+ rc = ima_parse_add_rule(user_ns, p);
if (rc < 0)
break;
size -= rc;
@@ -317,10 +333,14 @@ static ssize_t ima_read_policy(struct ima_namespace *ns, char *path)
static ssize_t ima_write_policy(struct file *file, const char __user *buf,
size_t datalen, loff_t *ppos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct user_namespace *user_ns = ima_user_ns_from_file(file);
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
char *data;
ssize_t result;
+ if (!ns_is_active(ns))
+ return -EACCES;
+
if (datalen >= PAGE_SIZE)
datalen = PAGE_SIZE - 1;
@@ -340,15 +360,16 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf,
goto out_free;
if (data[0] == '/') {
- result = ima_read_policy(ns, data);
- } else if (ima_appraise & IMA_APPRAISE_POLICY) {
+ result = ima_read_policy(user_ns, data);
+ } else if (ns == &init_ima_ns &&
+ (ima_appraise & IMA_APPRAISE_POLICY)) {
pr_err("signed policy file (specified as an absolute pathname) required\n");
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL,
"policy_update", "signed policy required",
1, 0);
result = -EACCES;
} else {
- result = ima_parse_add_rule(ns, data);
+ result = ima_parse_add_rule(user_ns, data);
}
mutex_unlock(&ns->ima_write_mutex);
out_free:
@@ -381,7 +402,10 @@ static int ima_open_policy(struct inode *inode, struct file *filp)
#ifdef CONFIG_IMA_READ_POLICY
struct user_namespace *user_ns = ima_user_ns_from_file(filp);
#endif
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(filp);
+
+ if (!ns_is_active(ns))
+ return -EACCES;
if (!(filp->f_flags & O_WRONLY)) {
#ifndef CONFIG_IMA_READ_POLICY
@@ -408,7 +432,7 @@ static int ima_open_policy(struct inode *inode, struct file *filp)
*/
static int ima_release_policy(struct inode *inode, struct file *file)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(file);
const char *cause = ns->valid_policy ? "completed" : "failed";
if ((file->f_flags & O_ACCMODE) == O_RDONLY)
@@ -419,11 +443,12 @@ static int ima_release_policy(struct inode *inode, struct file *file)
ns->valid_policy = 0;
}
- pr_info("policy update %s\n", cause);
- if (ns == &init_ima_ns)
+ if (ns == &init_ima_ns) {
+ pr_info("policy update %s\n", cause);
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL,
"policy_update", cause, !ns->valid_policy,
0);
+ }
if (!ns->valid_policy) {
ima_delete_rules(ns);
@@ -457,7 +482,7 @@ static ssize_t ima_show_active(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(filp);
char tmpbuf[2];
ssize_t len;
@@ -470,7 +495,7 @@ static ssize_t ima_write_active(struct file *filp,
const char __user *buf,
size_t count, loff_t *ppos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(filp);
unsigned int active;
char *kbuf;
int err;
@@ -494,7 +519,9 @@ static ssize_t ima_write_active(struct file *filp,
if (active != 1)
return -EINVAL;
- set_bit(IMA_NS_ACTIVE, &ns->ima_ns_flags);
+ err = ima_init_namespace(ns);
+ if (err)
+ return -EINVAL;
return count;
}
@@ -517,11 +544,28 @@ int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root)
struct dentry *active = NULL;
int ret;
+ /*
+ * While multiple superblocks can exist they are keyed by userns in
+ * s_fs_info for securityfs. The first time a userns mounts a
+ * securityfs instance we lazily allocate the ima_namespace for the
+ * userns since that's the only way a userns can meaningfully use ima.
+ * The vfs ensures we're the only one to call fill_super() and hence
+ * ima_fs_ns_init(), so we don't need any memory barriers here, i.e.
+ * user_ns->ima_ns can't change while we're in here.
+ */
+ if (!ns) {
+ ns = create_ima_ns();
+ if (IS_ERR(ns))
+ return PTR_ERR(ns);
+ }
+
/* FIXME: update when evm and integrity are namespaced */
if (user_ns != &init_user_ns) {
int_dir = securityfs_create_dir("integrity", root);
- if (IS_ERR(int_dir))
- return PTR_ERR(int_dir);
+ if (IS_ERR(int_dir)) {
+ ret = PTR_ERR(int_dir);
+ goto free_ns;
+ }
} else {
int_dir = integrity_dir;
}
@@ -596,6 +640,9 @@ int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root)
}
}
+ if (!ima_ns_from_user_ns(user_ns))
+ user_ns_set_ima_ns(user_ns, ns);
+
return 0;
out:
securityfs_remove(active);
@@ -609,6 +656,10 @@ int ima_fs_ns_init(struct user_namespace *user_ns, struct dentry *root)
if (user_ns != &init_user_ns)
securityfs_remove(int_dir);
+free_ns:
+ if (!ima_ns_from_user_ns(user_ns))
+ ima_free_ima_ns(ns);
+
return ret;
}
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index 47c9d561532e..da2a37ff431d 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -147,7 +147,7 @@ int __init ima_init(void)
if (rc != 0)
return rc;
- ima_init_policy(&init_ima_ns);
+ ima_init_policy(&init_user_ns);
rc = ima_fs_init();
if (rc != 0)
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
index 03d326cf82d4..fc339051c3c5 100644
--- a/security/integrity/ima/ima_init_ima_ns.c
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -46,6 +46,8 @@ int ima_init_namespace(struct ima_namespace *ns)
goto err_destroy_cache;
}
+ set_bit(IMA_NS_ACTIVE, &ns->ima_ns_flags);
+
return 0;
err_destroy_cache:
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 61442eb8339c..3f5b88a23185 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -211,11 +211,11 @@ static void ima_check_last_writer(struct ima_namespace *ns,
*/
void ima_file_free(struct file *file)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
struct inode *inode = file_inode(file);
struct integrity_iint_cache *iint;
- if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
+ if (!S_ISREG(inode->i_mode))
return;
iint = integrity_iint_find(inode);
@@ -511,7 +511,7 @@ int ima_file_mmap(struct file *file, unsigned long prot)
*/
int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
struct ima_template_desc *template = NULL;
struct file *file;
char filename[NAME_MAX];
@@ -524,7 +524,8 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot)
int pcr;
/* Is mprotect making an mmap'ed file executable? */
- if (!(ns->ima_policy_flag & IMA_APPRAISE) || !vma->vm_file ||
+ if (!ns_is_active(ns) ||
+ !(ns->ima_policy_flag & IMA_APPRAISE) || !vma->vm_file ||
!(prot & PROT_EXEC) || (vma->vm_flags & VM_EXEC))
return 0;
@@ -684,9 +685,9 @@ static int __ima_inode_hash(struct ima_namespace *ns, struct inode *inode,
*/
int ima_file_hash(struct file *file, char *buf, size_t buf_size)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
- if (!file)
+ if (!ns_is_active(ns) || !file)
return -EINVAL;
return __ima_inode_hash(ns, file_inode(file), file, buf, buf_size);
@@ -713,9 +714,9 @@ EXPORT_SYMBOL_GPL(ima_file_hash);
*/
int ima_inode_hash(struct inode *inode, char *buf, size_t buf_size)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
- if (!inode)
+ if (!ns_is_active(ns) || !inode)
return -EINVAL;
return __ima_inode_hash(ns, inode, NULL, buf, buf_size);
@@ -734,11 +735,12 @@ EXPORT_SYMBOL_GPL(ima_inode_hash);
void ima_post_create_tmpfile(struct user_namespace *mnt_userns,
struct inode *inode)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
struct integrity_iint_cache *iint;
int must_appraise;
- if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
+ if (!ns_is_active(ns) || !ns->ima_policy_flag ||
+ !S_ISREG(inode->i_mode))
return;
must_appraise = ima_must_appraise(ns, mnt_userns, inode, MAY_ACCESS,
@@ -767,12 +769,13 @@ void ima_post_create_tmpfile(struct user_namespace *mnt_userns,
void ima_post_path_mknod(struct user_namespace *mnt_userns,
struct dentry *dentry)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
struct integrity_iint_cache *iint;
struct inode *inode = dentry->d_inode;
int must_appraise;
- if (!ns->ima_policy_flag || !S_ISREG(inode->i_mode))
+ if (!ns_is_active(ns) || !ns->ima_policy_flag ||
+ !S_ISREG(inode->i_mode))
return;
must_appraise = ima_must_appraise(ns, mnt_userns, inode, MAY_ACCESS,
@@ -891,8 +894,12 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size,
*/
int ima_load_data(enum kernel_load_data_id id, bool contents)
{
+ struct ima_namespace *ns = get_current_ns();
bool ima_enforce, sig_enforce;
+ if (ns != &init_ima_ns)
+ return 0;
+
ima_enforce =
(ima_appraise & IMA_APPRAISE_ENFORCE) == IMA_APPRAISE_ENFORCE;
@@ -1097,10 +1104,10 @@ int process_buffer_measurement(struct ima_namespace *ns,
*/
void ima_kexec_cmdline(int kernel_fd, const void *buf, int size)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
struct fd f;
- if (!buf || !size)
+ if (!ns_is_active(ns) || !buf || !size)
return;
f = fdget(kernel_fd);
@@ -1138,7 +1145,10 @@ int ima_measure_critical_data(const char *event_label,
const void *buf, size_t buf_len,
bool hash, u8 *digest, size_t digest_len)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = get_current_ns();
+
+ if (!ns_is_active(ns))
+ return -EINVAL;
if (!event_name || !event_label || !buf || !buf_len)
return -ENOPARAM;
diff --git a/security/integrity/ima/ima_ns.c b/security/integrity/ima/ima_ns.c
index 29af6fea2d74..be7f50b1def8 100644
--- a/security/integrity/ima/ima_ns.c
+++ b/security/integrity/ima/ima_ns.c
@@ -26,22 +26,29 @@ struct ima_namespace *create_ima_ns(void)
/* destroy_ima_ns() must only be called after ima_init_namespace() was called */
static void destroy_ima_ns(struct ima_namespace *ns)
{
+ clear_bit(IMA_NS_ACTIVE, &ns->ima_ns_flags);
unregister_blocking_lsm_notifier(&ns->ima_lsm_policy_notifier);
kfree(ns->arch_policy_entry);
ima_free_policy_rules(ns);
ima_free_ns_status_tree(ns);
}
-void free_ima_ns(struct user_namespace *user_ns)
+void ima_free_ima_ns(struct ima_namespace *ns)
{
- struct ima_namespace *ns = user_ns->ima_ns;
-
if (!ns || WARN_ON(ns == &init_ima_ns))
return;
- destroy_ima_ns(ns);
+ if (ns_is_active(ns))
+ destroy_ima_ns(ns);
kmem_cache_free(imans_cachep, ns);
+}
+
+void free_ima_ns(struct user_namespace *user_ns)
+{
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
+
+ ima_free_ima_ns(ns);
user_ns->ima_ns = NULL;
}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 05ffd7b03ee3..1be322e84ab6 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -507,8 +507,8 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
return NOTIFY_DONE;
ns = container_of(nb, struct ima_namespace, ima_lsm_policy_notifier);
-
- set_bit(IMA_NS_LSM_UPDATE_RULES, &ns->ima_ns_flags);
+ if (ns_is_active(ns))
+ set_bit(IMA_NS_LSM_UPDATE_RULES, &ns->ima_ns_flags);
return NOTIFY_OK;
}
@@ -907,11 +907,12 @@ static void add_rules(struct ima_namespace *ns,
}
}
-static int ima_parse_rule(struct ima_namespace *ns,
+static int ima_parse_rule(struct user_namespace *user_ns,
char *rule, struct ima_rule_entry *entry);
-static int __init ima_init_arch_policy(struct ima_namespace *ns)
+static int __init ima_init_arch_policy(struct user_namespace *user_ns)
{
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
const char * const *arch_rules;
const char * const *rules;
int arch_entries = 0;
@@ -939,7 +940,8 @@ static int __init ima_init_arch_policy(struct ima_namespace *ns)
result = strscpy(rule, *rules, sizeof(rule));
INIT_LIST_HEAD(&ns->arch_policy_entry[i].list);
- result = ima_parse_rule(ns, rule, &ns->arch_policy_entry[i]);
+ result = ima_parse_rule(user_ns, rule,
+ &ns->arch_policy_entry[i]);
if (result) {
pr_warn("Skipping unknown architecture policy rule: %s\n",
rule);
@@ -954,11 +956,12 @@ static int __init ima_init_arch_policy(struct ima_namespace *ns)
/**
* ima_init_policy - initialize the default measure rules.
- * @ns: IMA namespace to which the policy belongs to
+ * @user_ns: User namespace pointig to IMA namespace to which the policy belongs
* ima_rules points to either the ima_default_rules or the new ima_policy_rules.
*/
-void __init ima_init_policy(struct ima_namespace *ns)
+void __init ima_init_policy(struct user_namespace *user_ns)
{
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
int build_appraise_entries, arch_entries;
/* if !ima_policy, we load NO default rules */
@@ -988,7 +991,7 @@ void __init ima_init_policy(struct ima_namespace *ns)
* and custom policies, prior to other appraise rules.
* (Highest priority)
*/
- arch_entries = ima_init_arch_policy(ns);
+ arch_entries = ima_init_arch_policy(user_ns);
if (!arch_entries)
pr_info("No architecture policies found\n");
else
@@ -1439,9 +1442,22 @@ static unsigned int ima_parse_appraise_algos(char *arg)
return res;
}
-static int ima_parse_rule(struct ima_namespace *ns,
+/*
+ * Either host root with CAP_AUDIT_WRITE in current user namespace or
+ * root with CAP_AUDIT_WRITE on the host entering a namespace may set
+ * audit rules inside a namespace.
+ */
+static bool may_set_audit_rule_in_ns(kuid_t uid, struct user_namespace *user_ns)
+{
+ return (uid_eq(uid, GLOBAL_ROOT_UID) &&
+ ns_capable(user_ns, CAP_AUDIT_WRITE)) ||
+ capable(CAP_AUDIT_WRITE);
+}
+
+static int ima_parse_rule(struct user_namespace *user_ns,
char *rule, struct ima_rule_entry *entry)
{
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
struct audit_buffer *ab = NULL;
char *from;
char *p;
@@ -1692,7 +1708,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
result = kstrtoul(args[0].from, 10, &lnum);
if (!result) {
- entry->uid = make_kuid(current_user_ns(),
+ entry->uid = make_kuid(user_ns,
(uid_t) lnum);
if (!uid_valid(entry->uid) ||
(uid_t)lnum != lnum)
@@ -1727,7 +1743,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
result = kstrtoul(args[0].from, 10, &lnum);
if (!result) {
- entry->gid = make_kgid(current_user_ns(),
+ entry->gid = make_kgid(user_ns,
(gid_t)lnum);
if (!gid_valid(entry->gid) ||
(((gid_t)lnum) != lnum))
@@ -1754,7 +1770,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
result = kstrtoul(args[0].from, 10, &lnum);
if (!result) {
- entry->fowner = make_kuid(current_user_ns(),
+ entry->fowner = make_kuid(user_ns,
(uid_t)lnum);
if (!uid_valid(entry->fowner) ||
(((uid_t)lnum) != lnum))
@@ -1780,7 +1796,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
result = kstrtoul(args[0].from, 10, &lnum);
if (!result) {
- entry->fgroup = make_kgid(current_user_ns(),
+ entry->fgroup = make_kgid(user_ns,
(gid_t)lnum);
if (!gid_valid(entry->fgroup) ||
(((gid_t)lnum) != lnum))
@@ -1935,6 +1951,12 @@ static int ima_parse_rule(struct ima_namespace *ns,
case HASH:
result = -EINVAL;
goto err_audit;
+ case AUDIT:
+ if (!may_set_audit_rule_in_ns(current_uid(),
+ user_ns)) {
+ result = -EPERM;
+ goto err_audit;
+ }
}
}
}
@@ -1966,14 +1988,15 @@ static int ima_parse_rule(struct ima_namespace *ns,
/**
* ima_parse_add_rule - add a rule to ima_policy_rules
- * @ns: IMA namespace that has the policy
+ * @user_ns: User namespace referencing the IMA namespace that has the policy
* @rule - ima measurement policy rule
*
* Avoid locking by allowing just one writer at a time in ima_write_policy()
* Returns the length of the rule parsed, an error code on failure
*/
-ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule)
+ssize_t ima_parse_add_rule(struct user_namespace *user_ns, char *rule)
{
+ struct ima_namespace *ns = ima_ns_from_user_ns(user_ns);
static const char op[] = "update_policy";
char *p;
struct ima_rule_entry *entry;
@@ -1998,7 +2021,7 @@ ssize_t ima_parse_add_rule(struct ima_namespace *ns, char *rule)
INIT_LIST_HEAD(&entry->list);
- result = ima_parse_rule(ns, p, entry);
+ result = ima_parse_rule(user_ns, p, entry);
if (result) {
ima_free_rule(entry);
if (ns == &init_ima_ns)
@@ -2024,7 +2047,9 @@ void ima_delete_rules(struct ima_namespace *ns)
{
struct ima_rule_entry *entry, *tmp;
- temp_ima_appraise = 0;
+ if (ns == &init_ima_ns)
+ temp_ima_appraise = 0;
+
list_for_each_entry_safe(entry, tmp, &ns->ima_temp_rules, list) {
list_del(&entry->list);
ima_free_rule(entry);
@@ -2065,7 +2090,7 @@ static const char *const mask_tokens[] = {
void *ima_policy_start(struct seq_file *m, loff_t *pos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(m->file);
loff_t l = *pos;
struct ima_rule_entry *entry;
struct list_head *ima_rules_tmp;
@@ -2086,7 +2111,7 @@ void *ima_policy_start(struct seq_file *m, loff_t *pos)
void *ima_policy_next(struct seq_file *m, void *v, loff_t *pos)
{
- struct ima_namespace *ns = &init_ima_ns;
+ struct ima_namespace *ns = ima_ns_from_file(m->file);
struct ima_rule_entry *entry = v;
rcu_read_lock();
--
2.37.3
From: Mehmet Kayaalp <[email protected]>
Add an rbtree to the IMA namespace structure that stores a namespaced
version of iint->flags in ns_status struct. Similar to the
integrity_iint_cache, both the iint and ns_status are looked up using the
inode pointer value. The lookup, allocate, and insertion code is also
similar.
In subsequent patches we will have to find all ns_status entries an iint
is being used in and reset flags there. To do this, connect a list of
ns_status to the integrity_iint_cache and provide a reader-writer
lock in the integrity_iint_cache to lock access to the list.
To simplify the code in the non-namespaces case embed an ns_status in
the integrity_iint_cache and have it linked into the iint's ns_status list
when calling ima_get_ns_status().
When getting an ns_status first try to find it in the RB tree. Here we can
run into the situation that an ns_status found in the RB tree has a
different iint associated with it for the same inode. In this case we need
to delete the ns_status structure and get a new one.
There are two cases for freeing:
- when the iint is freed (inode deletion): Walk the list of ns_status
entries and disconnect each ns_status from the list; take the
writer lock to protect access to the list; also, take the item off the
per-namespace rbtree
- when the ima_namepace is freed: While walking the rbtree, remove the
ns_status from the list while also holding the iint's writer lock;
to be able to grab the lock we have to have a pointer to the iint on
the ns_status structure.
To avoid an ns_status to be freed by the two cases concurrently, prevent
these two cases to run concurrently. Therefore, groups of threads
deleting either inodes or ima_namespaces are allowed to run concurrently
but no two threads may run and one delete an inode and the other an
ima_namespace.
Signed-off-by: Mehmet Kayaalp <[email protected]>
Signed-off-by: Stefan Berger <[email protected]>
Signed-off-by: Denis Semakin <[email protected]>
Signed-off-by: Ilya Hanov <[email protected]>
---
Design discussion:
- https://lore.kernel.org/linux-integrity/[email protected]/T/#m500a621141c52dc0e34cf11ba37b4032a2de84fd
v15:
- Free ns_status_cache in case of error
v9:
- Move ns_status into integrity.h and embedded it into integrity_iint_cache
for the non-CONFIG_IMA_NS case
---
include/linux/ima.h | 7 +
security/integrity/iint.c | 7 +
security/integrity/ima/Makefile | 2 +-
security/integrity/ima/ima.h | 18 ++
security/integrity/ima/ima_init_ima_ns.c | 11 +-
security/integrity/ima/ima_ns.c | 1 +
security/integrity/ima/ima_ns_status.c | 344 +++++++++++++++++++++++
security/integrity/integrity.h | 35 ++-
8 files changed, 422 insertions(+), 3 deletions(-)
create mode 100644 security/integrity/ima/ima_ns_status.c
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 1b1fc643b371..20f9b95090f4 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -254,12 +254,19 @@ static inline bool ima_appraise_signature(enum kernel_read_file_id func)
void free_ima_ns(struct user_namespace *ns);
+void ima_free_ns_status_list(struct list_head *head, rwlock_t *ns_list_lock);
+
#else
static inline void free_ima_ns(struct user_namespace *user_ns)
{
}
+static inline void ima_free_ns_status_list(struct list_head *head,
+ rwlock_t *ns_list_lock)
+{
+}
+
#endif /* CONFIG_IMA_NS */
#endif /* _LINUX_IMA_H */
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index 8638976f7990..371cbceaf479 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -19,6 +19,7 @@
#include <linux/uaccess.h>
#include <linux/security.h>
#include <linux/lsm_hooks.h>
+#include <linux/ima.h>
#include "integrity.h"
static struct rb_root integrity_iint_tree = RB_ROOT;
@@ -82,6 +83,8 @@ static void iint_free(struct integrity_iint_cache *iint)
iint->ima_creds_status = INTEGRITY_UNKNOWN;
iint->evm_status = INTEGRITY_UNKNOWN;
iint->measured_pcrs = 0;
+ rwlock_init(&iint->ns_list_lock);
+ INIT_LIST_HEAD(&iint->ns_list);
kmem_cache_free(iint_cache, iint);
}
@@ -155,6 +158,8 @@ void integrity_inode_free(struct inode *inode)
rb_erase(&iint->rb_node, &integrity_iint_tree);
write_unlock(&integrity_iint_lock);
+ ima_free_ns_status_list(&iint->ns_list, &iint->ns_list_lock);
+
iint_free(iint);
}
@@ -170,6 +175,8 @@ static void init_once(void *foo)
iint->ima_creds_status = INTEGRITY_UNKNOWN;
iint->evm_status = INTEGRITY_UNKNOWN;
mutex_init(&iint->mutex);
+ rwlock_init(&iint->ns_list_lock);
+ INIT_LIST_HEAD(&iint->ns_list);
}
static int __init integrity_iintcache_init(void)
diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile
index b86a35fbed60..edfb0c30a063 100644
--- a/security/integrity/ima/Makefile
+++ b/security/integrity/ima/Makefile
@@ -14,7 +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_IMA_NS) += ima_ns.o
+ima-$(CONFIG_IMA_NS) += ima_ns.o ima_ns_status.o
ifeq ($(CONFIG_EFI),y)
ima-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_efi.o
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 9dc199a20fc0..3206d8cb8ca4 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -127,6 +127,10 @@ struct ima_namespace {
/* Bit numbers for above flags; use BIT() to get flag */
#define IMA_NS_LSM_UPDATE_RULES 0
+ struct rb_root ns_status_tree;
+ rwlock_t ns_tree_lock;
+ struct kmem_cache *ns_status_cache;
+
/* policy rules */
struct list_head ima_default_rules; /* Kconfig, builtin & arch rules */
struct list_head ima_policy_rules; /* arch & custom rules */
@@ -512,6 +516,12 @@ static inline struct ima_namespace
struct ima_namespace *create_ima_ns(void);
+struct ns_status *ima_get_ns_status(struct ima_namespace *ns,
+ struct inode *inode,
+ struct integrity_iint_cache *iint);
+
+void ima_free_ns_status_tree(struct ima_namespace *ns);
+
#else
static inline struct ima_namespace *create_ima_ns(void)
@@ -520,6 +530,14 @@ static inline struct ima_namespace *create_ima_ns(void)
return ERR_PTR(-EFAULT);
}
+static inline struct ns_status *ima_get_ns_status
+ (struct ima_namespace *ns,
+ struct inode *inode,
+ struct integrity_iint_cache *iint)
+{
+ return NULL;
+}
+
#endif /* CONFIG_IMA_NS */
#endif /* __LINUX_IMA_H */
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
index b497062090cf..e841302cada9 100644
--- a/security/integrity/ima/ima_init_ima_ns.c
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -12,6 +12,11 @@ int ima_init_namespace(struct ima_namespace *ns)
{
int ret;
+ ns->ns_status_tree = RB_ROOT;
+ rwlock_init(&ns->ns_tree_lock);
+ /* Use KMEM_CACHE for simplicity */
+ ns->ns_status_cache = KMEM_CACHE(ns_status, SLAB_PANIC);
+
INIT_LIST_HEAD(&ns->ima_default_rules);
INIT_LIST_HEAD(&ns->ima_policy_rules);
INIT_LIST_HEAD(&ns->ima_temp_rules);
@@ -38,10 +43,14 @@ int ima_init_namespace(struct ima_namespace *ns)
ret = register_blocking_lsm_notifier
(&ns->ima_lsm_policy_notifier);
if (ret)
- return ret;
+ goto err_destroy_cache;
}
return 0;
+
+err_destroy_cache:
+ kmem_cache_destroy(ns->ns_status_cache);
+ return ret;
}
int __init ima_ns_init(void)
diff --git a/security/integrity/ima/ima_ns.c b/security/integrity/ima/ima_ns.c
index b3b81a1e313e..29af6fea2d74 100644
--- a/security/integrity/ima/ima_ns.c
+++ b/security/integrity/ima/ima_ns.c
@@ -29,6 +29,7 @@ static void destroy_ima_ns(struct ima_namespace *ns)
unregister_blocking_lsm_notifier(&ns->ima_lsm_policy_notifier);
kfree(ns->arch_policy_entry);
ima_free_policy_rules(ns);
+ ima_free_ns_status_tree(ns);
}
void free_ima_ns(struct user_namespace *user_ns)
diff --git a/security/integrity/ima/ima_ns_status.c b/security/integrity/ima/ima_ns_status.c
new file mode 100644
index 000000000000..9c753caad6ac
--- /dev/null
+++ b/security/integrity/ima/ima_ns_status.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016-2021 IBM Corporation
+ * Author:
+ * Yuqiong Sun <[email protected]>
+ * Stefan Berger <[email protected]>
+ */
+
+#include <linux/user_namespace.h>
+#include <linux/ima.h>
+
+#include "ima.h"
+
+/*
+ * An ns_status must be on a per-namespace rbtree and on a per-iint list.
+ *
+ * Locking order for ns_status:
+ * 1) ns->ns_tree_lock : Lock the rbtree
+ * 2) iint->ns_list_lock: Lock the list
+ *
+ * An ns_status can be freed either by walking the rbtree (namespace deletion)
+ * or by walking the linked list of ns_status (inode/iint deletion). There are
+ * two functions that implement each one of the cases. To avoid concurrent
+ * freeing of the same ns_status, the two freeing paths cannot be run
+ * concurrently but each path can be run by multiple threads since no two
+ * threads will free the same inode/iint and no two threads will free the same
+ * namespace. Grouping threads like this ensures that:
+ * - while walking the rbtree: all ns_status will be on their list and the iint
+ * will still exist
+ * - while walking the list: all ns_status will be on their rbtree
+ */
+enum lk_group {
+ GRP_NS_STATUS_LIST = 0,
+ GRP_NS_STATUS_TREE
+};
+
+static atomic_t lg_ctr[2] = {
+ ATOMIC_INIT(0),
+ ATOMIC_INIT(0)
+};
+
+static DEFINE_SPINLOCK(lg_ctr_lock);
+
+static struct wait_queue_head lg_wq[2] = {
+ __WAIT_QUEUE_HEAD_INITIALIZER(lg_wq[0]),
+ __WAIT_QUEUE_HEAD_INITIALIZER(lg_wq[1])
+};
+
+static atomic_t ns_list_waiters = ATOMIC_INIT(0);
+
+/*
+ * Any number of concurrent threads may free ns_status's in either one of the
+ * groups but the groups must not run concurrently. The GRP_NS_STATUS_TREE
+ * group yields to waiters in the GRP_NS_STATUS_LIST group since namespace
+ * deletion has more time.
+ */
+static void lock_group(enum lk_group group)
+{
+ unsigned long flags;
+ bool done = false;
+ int announced = 0;
+
+ while (1) {
+ spin_lock_irqsave(&lg_ctr_lock, flags);
+
+ switch (group) {
+ case GRP_NS_STATUS_LIST:
+ if (atomic_read(&lg_ctr[GRP_NS_STATUS_TREE]) == 0) {
+ if (announced)
+ atomic_dec(&ns_list_waiters);
+ done = true;
+ atomic_inc(&lg_ctr[GRP_NS_STATUS_LIST]);
+ } else {
+ /* rbtree being deleted; announce waiting */
+ if (!announced) {
+ atomic_inc(&ns_list_waiters);
+ announced = 1;
+ }
+ }
+ break;
+ case GRP_NS_STATUS_TREE:
+ if (atomic_read(&lg_ctr[GRP_NS_STATUS_LIST]) == 0 &&
+ atomic_read(&ns_list_waiters) == 0) {
+ done = true;
+ atomic_inc(&lg_ctr[GRP_NS_STATUS_TREE]);
+ }
+ break;
+ }
+
+ spin_unlock_irqrestore(&lg_ctr_lock, flags);
+
+ if (done)
+ break;
+
+ /* wait until opposite group is done */
+ switch (group) {
+ case GRP_NS_STATUS_LIST:
+ wait_event_interruptible
+ (lg_wq[GRP_NS_STATUS_LIST],
+ atomic_read(&lg_ctr[GRP_NS_STATUS_TREE]) == 0);
+ break;
+ case GRP_NS_STATUS_TREE:
+ wait_event_interruptible
+ (lg_wq[GRP_NS_STATUS_TREE],
+ atomic_read(&lg_ctr[GRP_NS_STATUS_LIST]) == 0 &&
+ atomic_read(&ns_list_waiters) == 0);
+ break;
+ }
+ }
+}
+
+static void unlock_group(enum lk_group group)
+{
+ switch (group) {
+ case GRP_NS_STATUS_LIST:
+ if (atomic_dec_and_test(&lg_ctr[GRP_NS_STATUS_LIST]))
+ wake_up_interruptible_all(&lg_wq[GRP_NS_STATUS_TREE]);
+ break;
+ case GRP_NS_STATUS_TREE:
+ if (atomic_dec_and_test(&lg_ctr[GRP_NS_STATUS_TREE]))
+ wake_up_interruptible_all(&lg_wq[GRP_NS_STATUS_LIST]);
+ break;
+ }
+}
+
+static void ns_status_free(struct ima_namespace *ns,
+ struct ns_status *ns_status)
+{
+ pr_debug("FREE ns_status: %p\n", ns_status);
+
+ kmem_cache_free(ns->ns_status_cache, ns_status);
+}
+
+/*
+ * ima_free_ns_status_tree - free all items on the ns_status_tree and take each
+ * one off the list; yield to ns_list free'ers
+ *
+ * This function is called when an ima_namespace is freed. All entries in the
+ * rbtree will be taken off their list and collected in a garbage collection
+ * list and freed at the end. This allows to walk the rbtree again.
+ */
+void ima_free_ns_status_tree(struct ima_namespace *ns)
+{
+ struct ns_status *ns_status, *next;
+ struct llist_node *node;
+ LLIST_HEAD(garbage);
+ unsigned int ctr;
+ bool restart;
+
+ do {
+ ctr = 0;
+ restart = false;
+
+ lock_group(GRP_NS_STATUS_TREE);
+ write_lock(&ns->ns_tree_lock);
+
+ rbtree_postorder_for_each_entry_safe(ns_status, next,
+ &ns->ns_status_tree,
+ rb_node) {
+ write_lock(&ns_status->iint->ns_list_lock);
+ if (!list_empty(&ns_status->ns_next)) {
+ list_del_init(&ns_status->ns_next);
+ llist_add(&ns_status->gc_llist, &garbage);
+ ctr++;
+ }
+ write_unlock(&ns_status->iint->ns_list_lock);
+
+ /*
+ * After some progress yield to any waiting ns_list
+ * free'ers.
+ */
+ if (atomic_read(&ns_list_waiters) > 0 && ctr >= 5) {
+ restart = true;
+ break;
+ }
+ }
+
+ write_unlock(&ns->ns_tree_lock);
+ unlock_group(GRP_NS_STATUS_TREE);
+ } while (restart);
+
+ node = llist_del_all(&garbage);
+ llist_for_each_entry_safe(ns_status, next, node, gc_llist)
+ ns_status_free(ns, ns_status);
+
+ kmem_cache_destroy(ns->ns_status_cache);
+}
+
+/*
+ * ima_free_ns_status_list: free the list of ns_status items and take
+ * each one off its namespace rbtree
+ */
+void ima_free_ns_status_list(struct list_head *head, rwlock_t *ns_list_lock)
+{
+ struct ns_status *ns_status;
+
+ lock_group(GRP_NS_STATUS_LIST);
+
+ while (1) {
+ write_lock(ns_list_lock);
+ ns_status = list_first_entry_or_null(head, struct ns_status,
+ ns_next);
+ if (ns_status)
+ list_del_init(&ns_status->ns_next);
+ write_unlock(ns_list_lock);
+
+ if (!ns_status)
+ break;
+
+ write_lock(&ns_status->ns->ns_tree_lock);
+
+ rb_erase(&ns_status->rb_node, &ns_status->ns->ns_status_tree);
+ RB_CLEAR_NODE(&ns_status->rb_node);
+
+ write_unlock(&ns_status->ns->ns_tree_lock);
+
+ ns_status_free(ns_status->ns, ns_status);
+ }
+
+ unlock_group(GRP_NS_STATUS_LIST);
+}
+
+/*
+ * ns_status_find - return the ns_status associated with an inode;
+ * caller must hold lock for tree
+ */
+static struct ns_status *ns_status_find(struct ima_namespace *ns,
+ struct inode *inode)
+{
+ struct ns_status *ns_status;
+ struct rb_node *n = ns->ns_status_tree.rb_node;
+
+ while (n) {
+ ns_status = rb_entry(n, struct ns_status, rb_node);
+
+ if (inode < ns_status->inode)
+ n = n->rb_left;
+ else if (inode > ns_status->inode)
+ n = n->rb_right;
+ else
+ break;
+ }
+ if (!n)
+ return NULL;
+
+ return ns_status;
+}
+
+static void insert_ns_status(struct ima_namespace *ns, struct inode *inode,
+ struct ns_status *ns_status)
+{
+ struct rb_node **p;
+ struct rb_node *node, *parent = NULL;
+ struct ns_status *test_status;
+
+ p = &ns->ns_status_tree.rb_node;
+ while (*p) {
+ parent = *p;
+ test_status = rb_entry(parent, struct ns_status, rb_node);
+ if (inode < test_status->inode)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+ node = &ns_status->rb_node;
+ rb_link_node(node, parent, p);
+ rb_insert_color(node, &ns->ns_status_tree);
+}
+
+static void ns_status_unlink(struct ima_namespace *ns,
+ struct ns_status *ns_status)
+{
+ write_lock(&ns_status->iint->ns_list_lock);
+ if (!list_empty(&ns_status->ns_next))
+ list_del_init(&ns_status->ns_next);
+ write_unlock(&ns_status->iint->ns_list_lock);
+
+ rb_erase(&ns_status->rb_node, &ns->ns_status_tree);
+ RB_CLEAR_NODE(&ns_status->rb_node);
+}
+
+struct ns_status *ima_get_ns_status(struct ima_namespace *ns,
+ struct inode *inode,
+ struct integrity_iint_cache *iint)
+{
+ struct ns_status *ns_status;
+ bool get_new = true;
+
+ /*
+ * Prevent finding the status via the list (inode/iint deletion) since
+ * we may free it.
+ */
+ lock_group(GRP_NS_STATUS_TREE);
+
+ write_lock(&ns->ns_tree_lock);
+
+ ns_status = ns_status_find(ns, inode);
+ if (ns_status) {
+ if (unlikely(ns_status->iint != iint)) {
+ /* Same inode but stale iint: free it and get new */
+ ns_status_unlink(ns, ns_status);
+ ns_status_free(ns, ns_status);
+ } else if (inode->i_ino == ns_status->i_ino &&
+ inode->i_generation == ns_status->i_generation) {
+ goto unlock;
+ } else {
+ /* Reuse of ns_status is possible but need reset */
+ ns_status_reset(ns_status);
+ get_new = false;
+ }
+ }
+
+ if (get_new) {
+ ns_status = kmem_cache_alloc(ns->ns_status_cache, GFP_NOFS);
+ if (!ns_status) {
+ ns_status = ERR_PTR(-ENOMEM);
+ goto unlock;
+ }
+
+ pr_debug("NEW ns_status: %p\n", ns_status);
+
+ ns_status_init(ns_status);
+ insert_ns_status(ns, inode, ns_status);
+ }
+
+ ns_status->iint = iint;
+ ns_status->inode = inode;
+ ns_status->ns = ns;
+ ns_status->i_ino = inode->i_ino;
+ ns_status->i_generation = inode->i_generation;
+
+ /* make visible on list */
+ write_lock(&iint->ns_list_lock);
+ if (list_empty(&ns_status->ns_next))
+ list_add_tail(&ns_status->ns_next, &iint->ns_list);
+ write_unlock(&iint->ns_list_lock);
+
+unlock:
+ write_unlock(&ns->ns_tree_lock);
+
+ unlock_group(GRP_NS_STATUS_TREE);
+
+ return ns_status;
+}
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 7167a6e99bdc..112836d9b33a 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -155,13 +155,39 @@ struct ima_file_id {
__u8 hash[HASH_MAX_DIGESTSIZE];
} __packed;
+/* integrity status of an inode in a namespace */
+struct ns_status {
+ struct list_head ns_next;
+ unsigned long flags; /* flags split with iint */
+#ifdef CONFIG_IMA_NS
+ struct rb_node rb_node;
+ struct integrity_iint_cache *iint;
+ struct inode *inode;
+ struct ima_namespace *ns;
+ ino_t i_ino;
+ u32 i_generation;
+ struct llist_node gc_llist; /* used while freeing */
+#endif
+};
+
+static inline void ns_status_reset(struct ns_status *ns_status)
+{
+ ns_status->flags = 0;
+}
+
+static inline void ns_status_init(struct ns_status *ns_status)
+{
+ INIT_LIST_HEAD(&ns_status->ns_next);
+ ns_status_reset(ns_status);
+}
+
/* 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 */
- unsigned long flags;
+ unsigned long flags; /* flags split with ns_status */
unsigned long measured_pcrs;
unsigned long atomic_flags;
enum integrity_status ima_file_status:4;
@@ -171,6 +197,13 @@ struct integrity_iint_cache {
enum integrity_status ima_creds_status:4;
enum integrity_status evm_status:4;
struct ima_digest_data *ima_hash;
+
+ /*
+ * Lock and list of ns_status for files shared by different
+ * namespaces
+ */
+ rwlock_t ns_list_lock;
+ struct list_head ns_list;
};
/* rbtree tree calls to lookup, insert, delete
--
2.37.3
On Mon, Feb 06, 2023 at 09:02:28AM -0500, Stefan Berger wrote:
> From: Christian Brauner <[email protected]>
>
> When securityfs creates a new file or directory via
> securityfs_create_dentry() it will take an additional reference on the
> newly created dentry after it has attached the new inode to the new
> dentry and added it to the hashqueues.
> If we contrast this with debugfs, which has the same underlying logic as
nit: "Constrating this with debugfs"
Passive form is better when there is no well-defined definition of "we".
> securityfs, it uses a similar pairing as securityfs. Where securityfs
> has the securityfs_create_dentry() and securityfs_remove() pairing,
> debugfs has the __debugfs_create_file() and debugfs_remove() pairing.
>
> In contrast to securityfs, debugfs doesn't take an additional reference
> on the newly created dentry in __debugfs_create_file() which would need
> to be put in debugfs_remove().
>
> The additional dget() isn't a problem per se. In the current
> implementation of securityfs each created dentry pins the filesystem via
> securityfs_create_dentry() until it is removed. Since it is virtually
> guaranteed that there is at least one user of securityfs that has created
> dentries the initial securityfs mount cannot go away until all dentries
> have been removed.
>
> Since most of the users of the initial securityfs mount don't go away
> until the system is shutdown the initial securityfs won't go away when
> unmounted. Instead a mount will usually surface the same superblock as
> before. The additional dget() doesn't matter in this scenario since it
> is required that all dentries have been cleaned up by the respective
> users before the superblock can be destroyed, i.e. superblock shutdown
> is tied to the lifetime of the associated dentries.
>
> However, in order to support ima namespaces we need to extend securityfs
> to support being mounted outside of the initial user namespace. For
> namespaced users the pinning logic doesn't make sense. Whereas in the
> initial namespace the securityfs instance and the associated data
> structures of its users can't go away for reason explained earlier users
> of non-initial securityfs instances do go away when the last users of
> the namespace are gone.
"for reason explained earlier" ?
> So for those users we neither want to duplicate the pinning logic nor
> make the global securityfs instance display different information based
> on the namespace. Both options would be really messy and hacky.
>
> Instead we will simply give each namespace its own securityfs instance
> similar to how each ipc namespace has its own mqueue instance and all
> entries in there are cleaned up on umount or when the last user of the
> associated namespace is gone.
>
> This means that the superblock's lifetime isn't tied to the dentries.
> Instead the last umount, without any fds kept open, will trigger a clean
> shutdown. But now the additional dget() gets in the way. Instead of
> being able to rely on the generic superblock shutdown logic we would
> need to drop the additional dentry reference during superblock shutdown
> for all associated users. That would force the use of a generic
> coordination mechanism for current and future users of securityfs which
> is unnecessary. Simply remove the additional dget() in
> securityfs_dentry_create().
>
> In securityfs_remove() we will call dget() to take an additional
> reference on the dentry about to be removed. After simple_unlink() or
> simple_rmdir() have dropped the dentry refcount we can call d_delete()
> which will either turn the dentry into negative dentry if our earlier
> dget() is the only reference to the dentry, i.e. it has no other users,
> or remove it from the hashqueues in case there are additional users.
>
> All of these changes should not have any effect on the userspace
> semantics of the initial securityfs mount.
>
> Signed-off-by: Christian Brauner <[email protected]>
> Cc: John Johansen <[email protected]>
> Cc: Matthew Garrett <[email protected]>
> Cc: Micah Morton <[email protected]>
> Cc: Kentaro Takeda <[email protected]>
> Cc: James Morris <[email protected]>
> Cc: Jarkko Sakkinen <[email protected]>
> Signed-off-by: Stefan Berger <[email protected]>
> Reviewed-by: Mimi Zohar <[email protected]>
> Reviewed-by: Serge Hallyn <[email protected]>
>
> ---
> v13:
> - Slight improvements in 1st paragraph of commit message
> ---
> security/inode.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/security/inode.c b/security/inode.c
> index 6c326939750d..13e6780c4444 100644
> --- a/security/inode.c
> +++ b/security/inode.c
> @@ -159,7 +159,6 @@ static struct dentry *securityfs_create_dentry(const char *name, umode_t mode,
> inode->i_fop = fops;
> }
> d_instantiate(dentry, inode);
> - dget(dentry);
> inode_unlock(dir);
> return dentry;
>
> @@ -302,10 +301,12 @@ void securityfs_remove(struct dentry *dentry)
> dir = d_inode(dentry->d_parent);
> inode_lock(dir);
> if (simple_positive(dentry)) {
> + dget(dentry);
> if (d_is_dir(dentry))
> simple_rmdir(dir, dentry);
> else
> simple_unlink(dir, dentry);
> + d_delete(dentry);
> dput(dentry);
> }
> inode_unlock(dir);
> --
> 2.37.3
>
BR, Jarkko