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 a 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:
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 v5.18-rc3+imans.v12.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:
- 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/ima.h | 36 ++
include/linux/integrity.h | 8 +-
include/linux/user_namespace.h | 4 +
init/Kconfig | 14 +
kernel/user.c | 4 +
kernel/user_namespace.c | 2 +
security/inode.c | 82 +++-
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 | 42 +-
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 | 65 ++++
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 | 314 ++++++++++-----
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 +-
27 files changed, 1648 insertions(+), 359 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: b2d229d4ddb17db541098b83524d901257e93845
--
2.34.1
Earlier we simplified how dentry creation and deletion is manged in
securityfs. This allows us to move IMA securityfs files from global
variables directly into ima_fs_ns_init() itself. We can now rely on
those dentries to be cleaned up when the securityfs instance is cleaned
when the last reference to it is dropped.
Things are slightly different for the initial IMA namespace. In contrast
to non-initial IMA namespaces it has pinning logic binding the lifetime
of the securityfs superblock to created dentries. We need to keep this
behavior to not regress userspace. Since IMA never removes most of the
securityfs files the initial securityfs instance stays pinned. This also
means even for the initial IMA namespace we don't need to keep
references to these dentries anywhere.
The ima_policy file is the exception since IMA can end up removing it
on systems that don't allow reading or extending the IMA custom policy.
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
---
v9:
- Revert renaming of ima_policy to policy_dentry
---
security/integrity/ima/ima.h | 2 ++
security/integrity/ima/ima_fs.c | 37 ++++++++++++++++++---------------
2 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index a144edfdb9a1..b35c8504ef87 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -142,6 +142,8 @@ struct ima_namespace {
struct mutex ima_write_mutex;
unsigned long ima_fs_flags;
int valid_policy;
+
+ struct dentry *ima_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 4cf786f0bba8..89d3113ceda1 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -359,14 +359,6 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf,
return result;
}
-static struct dentry *ima_dir;
-static struct dentry *ima_symlink;
-static struct dentry *binary_runtime_measurements;
-static struct dentry *ascii_runtime_measurements;
-static struct dentry *runtime_measurements_count;
-static struct dentry *violations;
-static struct dentry *ima_policy;
-
enum ima_fs_flags {
IMA_FS_BUSY,
};
@@ -436,8 +428,8 @@ static int ima_release_policy(struct inode *inode, struct file *file)
ima_update_policy(ns);
#if !defined(CONFIG_IMA_WRITE_POLICY) && !defined(CONFIG_IMA_READ_POLICY)
- securityfs_remove(ima_policy);
- ima_policy = NULL;
+ securityfs_remove(ns->ima_policy);
+ ns->ima_policy = NULL;
#elif defined(CONFIG_IMA_WRITE_POLICY)
clear_bit(IMA_FS_BUSY, &ns->ima_fs_flags);
#elif defined(CONFIG_IMA_READ_POLICY)
@@ -454,8 +446,14 @@ static const struct file_operations ima_measure_policy_ops = {
.llseek = generic_file_llseek,
};
-int __init ima_fs_init(void)
+static int __init ima_fs_ns_init(struct ima_namespace *ns)
{
+ struct dentry *ima_dir;
+ struct dentry *ima_symlink = NULL;
+ struct dentry *binary_runtime_measurements = NULL;
+ struct dentry *ascii_runtime_measurements = NULL;
+ struct dentry *runtime_measurements_count = NULL;
+ struct dentry *violations = NULL;
int ret;
ima_dir = securityfs_create_dir("ima", integrity_dir);
@@ -504,17 +502,17 @@ int __init ima_fs_init(void)
goto out;
}
- ima_policy = securityfs_create_file("policy", POLICY_FILE_FLAGS,
- ima_dir, NULL,
- &ima_measure_policy_ops);
- if (IS_ERR(ima_policy)) {
- ret = PTR_ERR(ima_policy);
+ 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;
out:
- securityfs_remove(ima_policy);
+ securityfs_remove(ns->ima_policy);
securityfs_remove(violations);
securityfs_remove(runtime_measurements_count);
securityfs_remove(ascii_runtime_measurements);
@@ -524,3 +522,8 @@ int __init ima_fs_init(void)
return ret;
}
+
+int __init ima_fs_init(void)
+{
+ return ima_fs_ns_init(&init_ima_ns);
+}
--
2.34.1
Move ima_htable into ima_namespace. This way a front-end like
securityfs can show the number of measurement records and number of
violations of an IMA namespace.
Signed-off-by: Stefan Berger <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Reviewed-by: Mimi Zohar <[email protected]>
---
security/integrity/ima/ima.h | 33 +++++++++++++---------
security/integrity/ima/ima_api.c | 18 +++++++-----
security/integrity/ima/ima_fs.c | 8 ++++--
security/integrity/ima/ima_init.c | 7 +++--
security/integrity/ima/ima_init_ima_ns.c | 4 +++
security/integrity/ima/ima_kexec.c | 3 +-
security/integrity/ima/ima_main.c | 14 +++++----
security/integrity/ima/ima_queue.c | 36 ++++++++++++------------
security/integrity/ima/ima_template.c | 5 ++--
9 files changed, 76 insertions(+), 52 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 2305bf223a98..78798cfcf46c 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -117,6 +117,12 @@ struct ima_kexec_hdr {
u64 count;
};
+struct ima_h_table {
+ atomic_long_t len; /* number of stored measurements in the list */
+ atomic_long_t violations;
+ struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
+};
+
struct ima_namespace {
/* policy rules */
struct list_head ima_default_rules; /* Kconfig, builtin & arch rules */
@@ -128,6 +134,8 @@ struct ima_namespace {
/* An array of architecture specific rules */
struct ima_rule_entry *arch_policy_entry;
+
+ struct ima_h_table ima_htable;
} __randomize_layout;
extern struct ima_namespace init_ima_ns;
@@ -149,7 +157,8 @@ extern bool ima_canonical_fmt;
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,
+int ima_add_template_entry(struct ima_namespace *ns,
+ struct ima_template_entry *entry, int violation,
const char *op, struct inode *inode,
const unsigned char *filename);
int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash);
@@ -158,7 +167,8 @@ int ima_calc_buffer_hash(const void *buf, loff_t len,
int ima_calc_field_array_hash(struct ima_field_data *field_data,
struct ima_template_entry *entry);
int ima_calc_boot_aggregate(struct ima_digest_data *hash);
-void ima_add_violation(struct file *file, const unsigned char *filename,
+void ima_add_violation(struct ima_namespace *ns,
+ struct file *file, const unsigned char *filename,
struct integrity_iint_cache *iint,
const char *op, const char *cause);
int ima_init_crypto(void);
@@ -171,8 +181,10 @@ struct ima_template_desc *ima_template_desc_current(void);
struct ima_template_desc *ima_template_desc_buf(void);
struct ima_template_desc *lookup_template_desc(const char *name);
bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
-int ima_restore_measurement_entry(struct ima_template_entry *entry);
-int ima_restore_measurement_list(loff_t bufsize, void *buf);
+int ima_restore_measurement_entry(struct ima_namespace *ns,
+ struct ima_template_entry *entry);
+int ima_restore_measurement_list(struct ima_namespace *ns,
+ loff_t bufsize, void *buf);
int ima_measurements_show(struct seq_file *m, void *v);
unsigned long ima_get_binary_runtime_size(void);
int ima_init_template(void);
@@ -186,13 +198,6 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
*/
extern spinlock_t ima_queue_lock;
-struct ima_h_table {
- atomic_long_t len; /* number of stored measurements in the list */
- atomic_long_t violations;
- struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
-};
-extern struct ima_h_table ima_htable;
-
static inline unsigned int ima_hash_key(u8 *digest)
{
/* there is no point in taking a hash of part of a digest */
@@ -277,7 +282,8 @@ int ima_must_measure(struct inode *inode, int mask, enum ima_hooks func);
int ima_collect_measurement(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 integrity_iint_cache *iint, struct file *file,
+void ima_store_measurement(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, const struct modsig *modsig, int pcr,
@@ -293,7 +299,8 @@ void ima_audit_measurement(struct integrity_iint_cache *iint,
int ima_alloc_init_template(struct ima_event_data *event_data,
struct ima_template_entry **entry,
struct ima_template_desc *template_desc);
-int ima_store_template(struct ima_template_entry *entry, int violation,
+int ima_store_template(struct ima_namespace *ns,
+ struct ima_template_entry *entry, int violation,
struct inode *inode,
const unsigned char *filename, int pcr);
void ima_free_template_entry(struct ima_template_entry *entry);
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 90ef246a9f43..1b6c3e6174cd 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -99,7 +99,8 @@ int ima_alloc_init_template(struct ima_event_data *event_data,
*
* Returns 0 on success, error code otherwise
*/
-int ima_store_template(struct ima_template_entry *entry,
+int ima_store_template(struct ima_namespace *ns,
+ struct ima_template_entry *entry,
int violation, struct inode *inode,
const unsigned char *filename, int pcr)
{
@@ -119,7 +120,8 @@ int ima_store_template(struct ima_template_entry *entry,
}
}
entry->pcr = pcr;
- result = ima_add_template_entry(entry, violation, op, inode, filename);
+ result = ima_add_template_entry(ns, entry, violation, op, inode,
+ filename);
return result;
}
@@ -130,7 +132,8 @@ int ima_store_template(struct ima_template_entry *entry,
* By extending the PCR with 0xFF's instead of with zeroes, the PCR
* value is invalidated.
*/
-void ima_add_violation(struct file *file, const unsigned char *filename,
+void ima_add_violation(struct ima_namespace *ns,
+ struct file *file, const unsigned char *filename,
struct integrity_iint_cache *iint,
const char *op, const char *cause)
{
@@ -144,14 +147,14 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
int result;
/* can overflow, only indicator */
- atomic_long_inc(&ima_htable.violations);
+ atomic_long_inc(&ns->ima_htable.violations);
result = ima_alloc_init_template(&event_data, &entry, NULL);
if (result < 0) {
result = -ENOMEM;
goto err_out;
}
- result = ima_store_template(entry, violation, inode,
+ result = ima_store_template(ns, entry, violation, inode,
filename, CONFIG_IMA_MEASURE_PCR_IDX);
if (result < 0)
ima_free_template_entry(entry);
@@ -297,7 +300,8 @@ int ima_collect_measurement(struct integrity_iint_cache *iint,
*
* Must be called with iint->mutex held.
*/
-void ima_store_measurement(struct integrity_iint_cache *iint,
+void ima_store_measurement(struct ima_namespace *ns,
+ struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, const struct modsig *modsig, int pcr,
@@ -332,7 +336,7 @@ void ima_store_measurement(struct integrity_iint_cache *iint,
return;
}
- result = ima_store_template(entry, violation, inode, filename, pcr);
+ result = ima_store_template(ns, entry, violation, inode, filename, pcr);
if ((!result || result == -EEXIST) && !(file->f_flags & O_DIRECT)) {
iint->flags |= IMA_MEASURED;
iint->measured_pcrs |= (0x1 << pcr);
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index f7ad93a56982..dca7fe32d65e 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -52,7 +52,10 @@ static ssize_t ima_show_htable_violations(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.violations);
+ struct ima_namespace *ns = &init_ima_ns;
+
+ return ima_show_htable_value(buf, count, ppos,
+ &ns->ima_htable.violations);
}
static const struct file_operations ima_htable_violations_ops = {
@@ -64,8 +67,9 @@ static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.len);
+ struct ima_namespace *ns = &init_ima_ns;
+ return ima_show_htable_value(buf, count, ppos, &ns->ima_htable.len);
}
static const struct file_operations ima_measurements_count_ops = {
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index 7e5b4187035d..47c9d561532e 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -39,7 +39,7 @@ struct tpm_chip *ima_tpm_chip;
* a different value.) Violations add a zero entry to the measurement
* list and extend the aggregate PCR value with ff...ff's.
*/
-static int __init ima_add_boot_aggregate(void)
+static int __init ima_add_boot_aggregate(struct ima_namespace *ns)
{
static const char op[] = "add_boot_aggregate";
const char *audit_cause = "ENOMEM";
@@ -83,7 +83,7 @@ static int __init ima_add_boot_aggregate(void)
goto err_out;
}
- result = ima_store_template(entry, violation, NULL,
+ result = ima_store_template(ns, entry, violation, NULL,
boot_aggregate_name,
CONFIG_IMA_MEASURE_PCR_IDX);
if (result < 0) {
@@ -142,7 +142,8 @@ int __init ima_init(void)
rc = ima_init_digests();
if (rc != 0)
return rc;
- rc = ima_add_boot_aggregate(); /* boot aggregate must be first entry */
+ /* boot aggregate must be first entry */
+ rc = ima_add_boot_aggregate(&init_ima_ns);
if (rc != 0)
return rc;
diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
index ae33621c3955..1945fa8cfc4d 100644
--- a/security/integrity/ima/ima_init_ima_ns.c
+++ b/security/integrity/ima/ima_init_ima_ns.c
@@ -17,6 +17,10 @@ static int ima_init_namespace(struct ima_namespace *ns)
ns->ima_policy_flag = 0;
ns->arch_policy_entry = NULL;
+ atomic_long_set(&ns->ima_htable.len, 0);
+ atomic_long_set(&ns->ima_htable.violations, 0);
+ memset(&ns->ima_htable.queue, 0, sizeof(ns->ima_htable.queue));
+
return 0;
}
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 13753136f03f..d7cc5cca6f84 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -146,7 +146,8 @@ void ima_load_kexec_buffer(void)
rc = ima_get_kexec_buffer(&kexec_buffer, &kexec_buffer_size);
switch (rc) {
case 0:
- rc = ima_restore_measurement_list(kexec_buffer_size,
+ rc = ima_restore_measurement_list(&init_ima_ns,
+ kexec_buffer_size,
kexec_buffer);
if (rc != 0)
pr_err("Failed to restore the measurement list: %d\n",
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 400865c521dd..005f9e784e7b 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -112,7 +112,8 @@ static int mmap_violation_check(enum ima_hooks func, struct file *file,
* could result in a file measurement error.
*
*/
-static void ima_rdwr_violation_check(struct file *file,
+static void ima_rdwr_violation_check(struct ima_namespace *ns,
+ struct file *file,
struct integrity_iint_cache *iint,
int must_measure,
char **pathbuf,
@@ -145,10 +146,10 @@ static void ima_rdwr_violation_check(struct file *file,
*pathname = ima_d_path(&file->f_path, pathbuf, filename);
if (send_tomtou)
- ima_add_violation(file, *pathname, iint,
+ ima_add_violation(ns, file, *pathname, iint,
"invalid_pcr", "ToMToU");
if (send_writers)
- ima_add_violation(file, *pathname, iint,
+ ima_add_violation(ns, file, *pathname, iint,
"invalid_pcr", "open_writers");
}
@@ -249,7 +250,7 @@ static int process_measurement(struct ima_namespace *ns,
}
if (!rc && violation_check)
- ima_rdwr_violation_check(file, iint, action & IMA_MEASURE,
+ ima_rdwr_violation_check(ns, file, iint, action & IMA_MEASURE,
&pathbuf, &pathname, filename);
inode_unlock(inode);
@@ -344,7 +345,7 @@ static int process_measurement(struct ima_namespace *ns,
pathname = ima_d_path(&file->f_path, &pathbuf, filename);
if (action & IMA_MEASURE)
- ima_store_measurement(iint, file, pathname,
+ ima_store_measurement(ns, iint, file, pathname,
xattr_value, xattr_len, modsig, pcr,
template_desc);
if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
@@ -987,7 +988,8 @@ int process_buffer_measurement(struct ima_namespace *ns,
goto out;
}
- ret = ima_store_template(entry, violation, NULL, event_data.buf, pcr);
+ ret = ima_store_template(ns, entry, violation, NULL, event_data.buf,
+ pcr);
if (ret < 0) {
audit_cause = "store_entry";
ima_free_template_entry(entry);
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 532da87ce519..43961d5cd2ef 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -31,13 +31,6 @@ static unsigned long binary_runtime_size;
static unsigned long binary_runtime_size = ULONG_MAX;
#endif
-/* key: inode (before secure-hashing a file) */
-struct ima_h_table ima_htable = {
- .len = ATOMIC_LONG_INIT(0),
- .violations = ATOMIC_LONG_INIT(0),
- .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
-};
-
/* mutex protects atomicity of extending measurement list
* and extending the TPM PCR aggregate. Since tpm_extend can take
* long (and the tpm driver uses a mutex), we can't use the spinlock.
@@ -45,8 +38,10 @@ struct ima_h_table ima_htable = {
static DEFINE_MUTEX(ima_extend_list_mutex);
/* lookup up the digest value in the hash table, and return the entry */
-static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value,
- int pcr)
+static struct ima_queue_entry *ima_lookup_digest_entry
+ (struct ima_namespace *ns,
+ u8 *digest_value,
+ int pcr)
{
struct ima_queue_entry *qe, *ret = NULL;
unsigned int key;
@@ -54,7 +49,7 @@ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value,
key = ima_hash_key(digest_value);
rcu_read_lock();
- hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) {
+ hlist_for_each_entry_rcu(qe, &ns->ima_htable.queue[key], hnext) {
rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest,
digest_value, hash_digest_size[ima_hash_algo]);
if ((rc == 0) && (qe->entry->pcr == pcr)) {
@@ -90,7 +85,8 @@ static int get_binary_runtime_size(struct ima_template_entry *entry)
*
* (Called with ima_extend_list_mutex held.)
*/
-static int ima_add_digest_entry(struct ima_template_entry *entry,
+static int ima_add_digest_entry(struct ima_namespace *ns,
+ struct ima_template_entry *entry,
bool update_htable)
{
struct ima_queue_entry *qe;
@@ -106,10 +102,12 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
INIT_LIST_HEAD(&qe->later);
list_add_tail_rcu(&qe->later, &ima_measurements);
- atomic_long_inc(&ima_htable.len);
+ atomic_long_inc(&ns->ima_htable.len);
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
- hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
+ hlist_add_head_rcu(&qe->hnext, &ns->ima_htable.queue[key]);
+ } else {
+ INIT_HLIST_NODE(&qe->hnext);
}
if (binary_runtime_size != ULONG_MAX) {
@@ -156,7 +154,8 @@ static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr)
* kexec, maintain the total memory size required for serializing the
* binary_runtime_measurements.
*/
-int ima_add_template_entry(struct ima_template_entry *entry, int violation,
+int ima_add_template_entry(struct ima_namespace *ns,
+ struct ima_template_entry *entry, int violation,
const char *op, struct inode *inode,
const unsigned char *filename)
{
@@ -169,14 +168,14 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
mutex_lock(&ima_extend_list_mutex);
if (!violation && !IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
- if (ima_lookup_digest_entry(digest, entry->pcr)) {
+ if (ima_lookup_digest_entry(ns, digest, entry->pcr)) {
audit_cause = "hash_exists";
result = -EEXIST;
goto out;
}
}
- result = ima_add_digest_entry(entry,
+ result = ima_add_digest_entry(ns, entry,
!IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE));
if (result < 0) {
audit_cause = "ENOMEM";
@@ -201,12 +200,13 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
return result;
}
-int ima_restore_measurement_entry(struct ima_template_entry *entry)
+int ima_restore_measurement_entry(struct ima_namespace *ns,
+ struct ima_template_entry *entry)
{
int result = 0;
mutex_lock(&ima_extend_list_mutex);
- result = ima_add_digest_entry(entry, 0);
+ result = ima_add_digest_entry(ns, entry, 0);
mutex_unlock(&ima_extend_list_mutex);
return result;
}
diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c
index db1ad6d7a57f..e5c33a3f0296 100644
--- a/security/integrity/ima/ima_template.c
+++ b/security/integrity/ima/ima_template.c
@@ -404,7 +404,8 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc,
}
/* Restore the serialized binary measurement list without extending PCRs. */
-int ima_restore_measurement_list(loff_t size, void *buf)
+int ima_restore_measurement_list(struct ima_namespace *ns,
+ loff_t size, void *buf)
{
char template_name[MAX_TEMPLATE_NAME_LEN];
unsigned char zero[TPM_DIGEST_SIZE] = { 0 };
@@ -520,7 +521,7 @@ int ima_restore_measurement_list(loff_t size, void *buf)
entry->pcr = !ima_canonical_fmt ? *(u32 *)(hdr[HDR_PCR].data) :
le32_to_cpu(*(__le32 *)(hdr[HDR_PCR].data));
- ret = ima_restore_measurement_entry(entry);
+ ret = ima_restore_measurement_entry(ns, entry);
if (ret < 0)
break;
--
2.34.1
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 the setting of AUDIT rules to only allow host root with CAP_SYS_ADMIN
inside a namespace or host root with CAP_SYS_AMDIN entering a namespace to
set them. This prevents normal users, who created an IMA namespace and
have CAP_SYS_ADMIN 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]>
---
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 | 62 +++++++++-----
12 files changed, 233 insertions(+), 65 deletions(-)
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 0cbf0434bc93..c6dc02b09850 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 ddcbefe535e9..f47b89994ddd 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1251,6 +1251,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 5481ba44a8d6..d2f2eb790d95 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -20,6 +20,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);
@@ -208,6 +209,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 78af02e4814e..c3f4ab1e20de 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);
@@ -544,32 +544,72 @@ struct user_namespace *ima_user_ns_from_file(const struct file *filp)
return file_inode(filp)->i_sb->s_user_ns;
}
+#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,
@@ -585,6 +625,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 666cf0e77e1f..376aa08525ad 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -74,6 +74,9 @@ int ima_must_appraise(struct ima_namespace *ns,
{
u32 secid;
+ if (ns != &init_ima_ns)
+ return 0;
+
if (!ima_appraise)
return 0;
@@ -493,6 +496,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)
@@ -500,6 +505,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 5c57abfc70ea..1eaa6ceee2ee 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)
return ret;
}
+ set_bit(IMA_NS_ACTIVE, &ns->ima_ns_flags);
+
return 0;
}
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index fc05f0088f79..2024950a5a7d 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);
@@ -509,7 +509,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 = vma->vm_file;
char filename[NAME_MAX];
@@ -522,7 +522,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;
@@ -677,9 +678,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);
@@ -706,9 +707,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);
@@ -727,11 +728,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,
@@ -760,12 +762,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,
@@ -884,8 +887,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;
@@ -1090,10 +1097,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);
@@ -1131,7 +1138,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 2a4e37a5a64d..a265c9751370 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -487,8 +487,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;
}
@@ -860,11 +860,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;
@@ -892,7 +893,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);
@@ -907,11 +909,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 */
@@ -941,7 +944,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
@@ -1362,9 +1365,21 @@ 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_SYS_ADMIN in current user namespace or
+ * root with CAP_SYS_ADMIN 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_SYS_ADMIN))
+ || capable(CAP_SYS_ADMIN);
+}
+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;
@@ -1615,7 +1630,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)
@@ -1650,7 +1665,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))
@@ -1677,7 +1692,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))
@@ -1703,7 +1718,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))
@@ -1835,6 +1850,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;
+ }
}
}
}
@@ -1857,14 +1878,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;
@@ -1889,7 +1911,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)
@@ -1915,7 +1937,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);
@@ -1956,7 +1980,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;
@@ -1977,7 +2001,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.34.1
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]>
---
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 fcb60a44e05f..76d19995ab89 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -220,4 +220,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 054b8f67be04..3e77738aec2c 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,
@@ -506,4 +507,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.34.1
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]>
---
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 | 4 ++++
kernel/user.c | 4 ++++
3 files changed, 10 insertions(+)
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 426b1744215e..fcb60a44e05f 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 33a4240e6a6f..019e8cf7b633 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,
@@ -99,6 +100,9 @@ struct user_namespace {
#endif
struct ucounts *ucounts;
long ucount_max[UCOUNT_COUNTS];
+#ifdef CONFIG_IMA_NS
+ struct ima_namespace *ima_ns;
+#endif
} __randomize_layout;
struct ucounts {
diff --git a/kernel/user.c b/kernel/user.c
index e2cf8c22b539..e5d1f4b9b8ba 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.34.1
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]>
---
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 | 28 ++--
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, 248 insertions(+), 145 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 be965a8715e4..9bcde1a24e74 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);
#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 c6805af46211..90ef246a9f43 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -162,6 +162,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
@@ -185,7 +186,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,
@@ -193,9 +195,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 17232bbfb9f9..f1b99b895c68 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -68,7 +68,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;
@@ -77,7 +78,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);
}
@@ -341,7 +342,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;
@@ -357,7 +359,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);
}
@@ -527,14 +530,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);
@@ -559,11 +564,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);
@@ -641,6 +647,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;
@@ -658,18 +665,19 @@ int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
if (result)
return result;
- ima_reset_appraise_flags(d_backing_inode(dentry), digsig);
+ ima_reset_appraise_flags(ns, d_backing_inode(dentry), digsig);
}
return result;
}
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 3d3f8c5c502b..400865c521dd 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;
@@ -346,7 +348,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,
@@ -405,12 +407,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;
@@ -431,6 +434,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 = vma->vm_file;
char filename[NAME_MAX];
@@ -443,13 +447,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);
@@ -485,17 +489,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);
}
@@ -511,22 +516,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);
@@ -595,10 +601,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);
@@ -622,10 +630,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);
@@ -641,13 +651,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;
@@ -673,14 +684,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;
@@ -709,6 +721,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;
@@ -731,7 +744,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);
}
@@ -759,6 +772,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;
@@ -767,14 +781,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);
}
@@ -862,6 +877,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.
@@ -880,7 +896,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,
@@ -905,7 +922,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();
@@ -924,7 +941,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)
@@ -961,7 +978,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);
@@ -995,6 +1012,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)
@@ -1004,7 +1022,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);
@@ -1034,10 +1053,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);
@@ -1046,6 +1067,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();
@@ -1070,7 +1092,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 eea6e92500b8..69b19f4d5fee 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;
@@ -232,11 +231,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)
@@ -453,12 +447,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;
@@ -473,10 +467,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;
}
@@ -668,6 +664,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
@@ -687,7 +684,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,
@@ -701,7 +699,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))
@@ -745,8 +743,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.
*
@@ -759,14 +757,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
@@ -796,7 +794,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)
@@ -812,7 +810,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;
@@ -821,7 +820,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),
@@ -829,7 +828,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)
@@ -842,9 +841,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;
@@ -872,7 +872,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);
@@ -887,26 +887,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;
@@ -920,11 +921,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);
/*
@@ -932,7 +933,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);
/*
@@ -944,39 +945,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
@@ -985,16 +988,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
@@ -1003,10 +1007,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! */
@@ -1076,7 +1080,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;
@@ -1096,7 +1101,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;
@@ -1323,7 +1329,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;
@@ -1673,37 +1680,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;
@@ -1804,12 +1811,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;
@@ -1833,7 +1841,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,
@@ -1842,23 +1850,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);
}
@@ -1884,12 +1893,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();
@@ -1902,6 +1912,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();
@@ -1909,8 +1920,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)
@@ -2173,6 +2184,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;
@@ -2184,7 +2196,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.34.1
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 b7e1f4b5eb30..cb48fc1d5b80 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -501,6 +501,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 33084ab2addd..b3460517344a 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -144,6 +144,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.34.1
Only accept AUDIT rules for non-init_ima_ns namespaces 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 59e4ae5a6361..45a997709200 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1812,6 +1812,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;
@@ -1824,6 +1835,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
check_template_modsig(template_desc);
}
+err_audit:
audit_log_format(ab, "res=%d", !result);
audit_log_end(ab);
return result;
--
2.34.1
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]>
---
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 2775a6d89e6d..054b8f67be04 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 45a997709200..eb10d895923d 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1905,6 +1905,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.34.1
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]>
---
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 | 26 ++++++++++++++++--------
4 files changed, 35 insertions(+), 13 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index b35c8504ef87..c68b5117d034 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 005f9e784e7b..d44faf1c065d 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();
@@ -1089,7 +1085,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 0a7c61ca3265..23c559c8fae9 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -368,7 +368,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;
@@ -399,18 +400,25 @@ 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)
{
struct ima_rule_entry *nentry;
- nentry = ima_lsm_copy_rule(entry);
+ nentry = ima_lsm_copy_rule(ns, entry);
if (!nentry)
return -ENOMEM;
@@ -453,7 +461,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;
@@ -464,12 +472,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;
}
--
2.34.1
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 4f8c50ddb777..a0bd8ffca88e 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -312,7 +312,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);
@@ -386,7 +387,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;
@@ -842,7 +843,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;
@@ -879,7 +880,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;
@@ -991,8 +992,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;
}
@@ -1864,7 +1877,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.34.1
Show the uid and gid values relative to the user namespace that is
currently active. The effect of this changes 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]>
---
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 eb10d895923d..4f8c50ddb777 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -2018,6 +2018,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,};
@@ -2103,7 +2104,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)
@@ -2114,7 +2116,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)
@@ -2125,7 +2128,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)
@@ -2136,7 +2140,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)
@@ -2147,7 +2152,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 == &uid_gt)
seq_printf(m, pt(Opt_fowner_gt), tbuf);
else if (entry->fowner_op == &uid_lt)
@@ -2158,7 +2164,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 == &gid_gt)
seq_printf(m, pt(Opt_fgroup_gt), tbuf);
else if (entry->fgroup_op == &gid_lt)
--
2.34.1
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
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]>
---
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.34.1
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]>
---
Design discussion:
- https://lore.kernel.org/linux-integrity/[email protected]/T/#m500a621141c52dc0e34cf11ba37b4032a2de84fd
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 | 5 +
security/integrity/ima/ima_ns.c | 1 +
security/integrity/ima/ima_ns_status.c | 344 +++++++++++++++++++++++
security/integrity/integrity.h | 35 ++-
8 files changed, 417 insertions(+), 2 deletions(-)
create mode 100644 security/integrity/ima/ima_ns_status.c
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 76d19995ab89..b9301e2aaa8b 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -225,12 +225,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 3e77738aec2c..7a08fdce7ebc 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 */
@@ -511,6 +515,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)
@@ -519,6 +529,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..41e7db0c9749 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);
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 3510e413ea17..ae0da8f1ba7e 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -132,13 +132,39 @@ struct signature_v2_hdr {
uint8_t sig[]; /* signature payload */
} __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;
@@ -148,6 +174,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.34.1
On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> 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
Is 'via' an extra word here or is there a missing word?
I'll delay the rest of my response as the missing word may answer my
remaining question :)
> 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]>
> ---
> 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.34.1
On Mon, May 09, 2022 at 02:54:14PM -0500, Serge E. Hallyn wrote:
> On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > 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
>
> Is 'via' an extra word here or is there a missing word?
>
> I'll delay the rest of my response as the missing word may answer my
> remaining question :)
>
> > 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]>
> > ---
> > 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);
Hm, so I realize your patch isn't introducing this, but is the
fact that we ignore the possible -ENOTEMPTY return value of
simple_rmdir() not a problem?
> > else
> > simple_unlink(dir, dentry);
> > + d_delete(dentry);
I'm mostly trying to convince myself that the fact that there is not
a matching dput being removed (to match the dget being removed above)
is ok. I do think it is, but that belief seems to dictate that right
now dentries must never be being released.
Otherwise, it seems like there must be cases where the next dput could
be called on a dentry that has been freed.
> > dput(dentry);
> > }
> > inode_unlock(dir);
> > --
> > 2.34.1
On Mon, May 9, 2022 at 11:36 PM Serge E. Hallyn <[email protected]> wrote:
>
> On Mon, May 09, 2022 at 02:54:14PM -0500, Serge E. Hallyn wrote:
> > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > 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
> >
> > Is 'via' an extra word here or is there a missing word?
> >
> > I'll delay the rest of my response as the missing word may answer my
> > remaining question :)
> >
> > > 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.
> > >
The first case (turn negative) cannot happen because the function is
entered with at least 1 refcount and increments it by 1.
So you can follow commit 46c46f8df9aa ("devpts_pty_kill(): don't bother
with d_delete()") and use d_drop() instead.
> > > 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]>
> > > ---
> > > 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);
>
> Hm, so I realize your patch isn't introducing this, but is the
> fact that we ignore the possible -ENOTEMPTY return value of
> simple_rmdir() not a problem?
As long as we are using debugfs as a reference code, wouldn't
securityfs need to use simple_recursive_removal()?
Can we guaranty that modules always cleanup all entries in
correct order?
>
> > > else
> > > simple_unlink(dir, dentry);
> > > + d_delete(dentry);
>
d_drop() (see comment above)
> I'm mostly trying to convince myself that the fact that there is not
> a matching dput being removed (to match the dget being removed above)
> is ok. I do think it is, but that belief seems to dictate that right
> now dentries must never be being released.
>
> Otherwise, it seems like there must be cases where the next dput could
> be called on a dentry that has been freed.
>
> > > dput(dentry);
Huh? There must be a ref to dentry when entering this function
and there is dget() added above so balance is not lost.
Thanks,
Amir.
On Mon, May 09, 2022 at 02:54:14PM -0500, Serge Hallyn wrote:
> On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > 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
>
> Is 'via' an extra word here or is there a missing word?
>
> I'll delay the rest of my response as the missing word may answer my
> remaining question :)
It can be both. It should either be removed or it should be followed by
"securityfs_create_dentry()". securityfs_create_dentry() takes two
references one in lookup_one_len() and another one explicitly via
dget(). The latter one isn't needed. Some of that has been covered in an
earlier thread:
https://lore.kernel.org/lkml/20220105101815.ldsm4s5yx7pmuiil@wittgenstein
On Mon, May 09, 2022 at 03:36:18PM -0500, Serge Hallyn wrote:
> On Mon, May 09, 2022 at 02:54:14PM -0500, Serge E. Hallyn wrote:
> > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > 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
> >
> > Is 'via' an extra word here or is there a missing word?
> >
> > I'll delay the rest of my response as the missing word may answer my
> > remaining question :)
> >
> > > 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]>
> > > ---
> > > 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);
>
> Hm, so I realize your patch isn't introducing this, but is the
> fact that we ignore the possible -ENOTEMPTY return value of
> simple_rmdir() not a problem?
>
> > > else
> > > simple_unlink(dir, dentry);
> > > + d_delete(dentry);
>
> I'm mostly trying to convince myself that the fact that there is not
> a matching dput being removed (to match the dget being removed above)
> is ok. I do think it is, but that belief seems to dictate that right
> now dentries must never be being released.
>
> Otherwise, it seems like there must be cases where the next dput could
> be called on a dentry that has been freed.
I think that's answered by Amir in the next mail already. So I'm
skipping to that part of the thread.
On Tue, May 10, 2022 at 11:43:13AM +0300, Amir Goldstein wrote:
> On Mon, May 9, 2022 at 11:36 PM Serge E. Hallyn <[email protected]> wrote:
> >
> > On Mon, May 09, 2022 at 02:54:14PM -0500, Serge E. Hallyn wrote:
> > > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > > 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
> > >
> > > Is 'via' an extra word here or is there a missing word?
> > >
> > > I'll delay the rest of my response as the missing word may answer my
> > > remaining question :)
> > >
> > > > 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.
> > > >
>
> The first case (turn negative) cannot happen because the function is
> entered with at least 1 refcount and increments it by 1.
> So you can follow commit 46c46f8df9aa ("devpts_pty_kill(): don't bother
> with d_delete()") and use d_drop() instead.
>
> > > > 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]>
> > > > ---
> > > > 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);
> >
> > Hm, so I realize your patch isn't introducing this, but is the
> > fact that we ignore the possible -ENOTEMPTY return value of
> > simple_rmdir() not a problem?
>
> As long as we are using debugfs as a reference code, wouldn't
> securityfs need to use simple_recursive_removal()?
> Can we guaranty that modules always cleanup all entries in
> correct order?
We could but that seems like a separate cleanup patch.
This patch became part of the series because we want non-initial ima
namespaces to guarantee cleanup on securityfs umount. That's different
for the initial securityfs mount which is alwasy going to be around. The
patch is intended to this a little cleaner to implement.
On 5/10/22 06:25, Christian Brauner wrote:
> On Mon, May 09, 2022 at 02:54:14PM -0500, Serge Hallyn wrote:
>> On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
>>> 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
>>
>> Is 'via' an extra word here or is there a missing word?
>>
>> I'll delay the rest of my response as the missing word may answer my
>> remaining question :)
>
> It can be both. It should either be removed or it should be followed by
> "securityfs_create_dentry()". securityfs_create_dentry() takes two
I am adding "securityfs_create_dentry()" to the text.
On Tue, May 10, 2022 at 09:10:25AM -0500, Serge Hallyn wrote:
> On Tue, May 10, 2022 at 12:25:25PM +0200, Christian Brauner wrote:
> > On Mon, May 09, 2022 at 02:54:14PM -0500, Serge Hallyn wrote:
> > > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > > 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
> > >
> > > Is 'via' an extra word here or is there a missing word?
> > >
> > > I'll delay the rest of my response as the missing word may answer my
> > > remaining question :)
> >
> > It can be both. It should either be removed or it should be followed by
> > "securityfs_create_dentry()". securityfs_create_dentry() takes two
> > references one in lookup_one_len() and another one explicitly via
> > dget(). The latter one isn't needed. Some of that has been covered in an
> > earlier thread:
> > https://lore.kernel.org/lkml/20220105101815.ldsm4s5yx7pmuiil@wittgenstein
>
> Yes, I saw that two references were being taken. And near as I can tell,
> the second one was never being dropped. So if you tell me that before this
> patch the dentries are never freed, then I'm happy. Otherwise, I'm
> bothered the fact that no matching dput is being deleted in the code (to
> match the extra dget being removed). So where is the code where the final
> dput was happening, and is it the d_delete() you're adding which is making
> it so that that dput won't be called now?
* So consider mounting securityfs _without this patch applied_:
mount -t securityfs /sfs
and assume we only have a single user that creates a file "foo" via
securityfs_create_file()
{
lookup_one_len(); // first dget()
dget(); // second dget()
}
now assume that user at some point calls
void securityfs_remove()
{
if (d_is_dir(dentry))
simple_rmdir(dir, dentry); // first dput()
else
simple_unlink(dir, dentry); // first dput()
dput(dentry); // second dput()
}
* Now consider mounting securityfs _with this patch applied_:
securityfs_create_file()
{
lookup_one_len(); // first dget()
}
void securityfs_remove()
{
dget(); // second dget()
if (d_is_dir(dentry))
simple_rmdir(dir, dentry); // first dput()
else
simple_unlink(dir, dentry); // first dput()
dput(dentry); // second dput()
}
On Tue, May 10, 2022 at 11:43:13AM +0300, Amir Goldstein wrote:
> On Mon, May 9, 2022 at 11:36 PM Serge E. Hallyn <[email protected]> wrote:
> >
> > On Mon, May 09, 2022 at 02:54:14PM -0500, Serge E. Hallyn wrote:
> > > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > > 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
> > >
> > > Is 'via' an extra word here or is there a missing word?
> > >
> > > I'll delay the rest of my response as the missing word may answer my
> > > remaining question :)
> > >
> > > > 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.
> > > >
>
> The first case (turn negative) cannot happen because the function is
> entered with at least 1 refcount and increments it by 1.
> So you can follow commit 46c46f8df9aa ("devpts_pty_kill(): don't bother
> with d_delete()") and use d_drop() instead.
>
> > > > 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]>
> > > > ---
> > > > 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);
> >
> > Hm, so I realize your patch isn't introducing this, but is the
> > fact that we ignore the possible -ENOTEMPTY return value of
> > simple_rmdir() not a problem?
>
> As long as we are using debugfs as a reference code, wouldn't
> securityfs need to use simple_recursive_removal()?
> Can we guaranty that modules always cleanup all entries in
> correct order?
>
> >
> > > > else
> > > > simple_unlink(dir, dentry);
> > > > + d_delete(dentry);
> >
>
> d_drop() (see comment above)
>
> > I'm mostly trying to convince myself that the fact that there is not
> > a matching dput being removed (to match the dget being removed above)
> > is ok. I do think it is, but that belief seems to dictate that right
> > now dentries must never be being released.
> >
> > Otherwise, it seems like there must be cases where the next dput could
> > be called on a dentry that has been freed.
> >
> > > > dput(dentry);
>
> Huh? There must be a ref to dentry when entering this function
> and there is dget() added above so balance is not lost.
Like, I said, i think the answer is that before this patch there the
dentry counts never reach 0. But we are removing a dget and not
removing any matching dput, so if they reached 0 before, then my
question is where was that happening and is that code still safe after
this patch.
On Tue, May 10, 2022 at 12:25:25PM +0200, Christian Brauner wrote:
> On Mon, May 09, 2022 at 02:54:14PM -0500, Serge Hallyn wrote:
> > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > 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
> >
> > Is 'via' an extra word here or is there a missing word?
> >
> > I'll delay the rest of my response as the missing word may answer my
> > remaining question :)
>
> It can be both. It should either be removed or it should be followed by
> "securityfs_create_dentry()". securityfs_create_dentry() takes two
> references one in lookup_one_len() and another one explicitly via
> dget(). The latter one isn't needed. Some of that has been covered in an
> earlier thread:
> https://lore.kernel.org/lkml/20220105101815.ldsm4s5yx7pmuiil@wittgenstein
Yes, I saw that two references were being taken. And near as I can tell,
the second one was never being dropped. So if you tell me that before this
patch the dentries are never freed, then I'm happy. Otherwise, I'm
bothered the fact that no matching dput is being deleted in the code (to
match the extra dget being removed). So where is the code where the final
dput was happening, and is it the d_delete() you're adding which is making
it so that that dput won't be called now?
On Tue, May 10, 2022 at 12:38:17PM +0200, Christian Brauner wrote:
> On Tue, May 10, 2022 at 11:43:13AM +0300, Amir Goldstein wrote:
> > On Mon, May 9, 2022 at 11:36 PM Serge E. Hallyn <[email protected]> wrote:
> > >
> > > On Mon, May 09, 2022 at 02:54:14PM -0500, Serge E. Hallyn wrote:
> > > > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > > > 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
> > > >
> > > > Is 'via' an extra word here or is there a missing word?
> > > >
> > > > I'll delay the rest of my response as the missing word may answer my
> > > > remaining question :)
> > > >
> > > > > 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.
> > > > >
> >
> > The first case (turn negative) cannot happen because the function is
> > entered with at least 1 refcount and increments it by 1.
> > So you can follow commit 46c46f8df9aa ("devpts_pty_kill(): don't bother
> > with d_delete()") and use d_drop() instead.
> >
> > > > > 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]>
> > > > > ---
> > > > > 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);
> > >
> > > Hm, so I realize your patch isn't introducing this, but is the
> > > fact that we ignore the possible -ENOTEMPTY return value of
> > > simple_rmdir() not a problem?
> >
> > As long as we are using debugfs as a reference code, wouldn't
> > securityfs need to use simple_recursive_removal()?
> > Can we guaranty that modules always cleanup all entries in
> > correct order?
>
> We could but that seems like a separate cleanup patch.
Yes, I'm not saying this set should fix it, just something that
caught my eye. Thanks.
> This patch became part of the series because we want non-initial ima
> namespaces to guarantee cleanup on securityfs umount. That's different
> for the initial securityfs mount which is alwasy going to be around. The
> patch is intended to this a little cleaner to implement.
On Tue, May 10, 2022 at 05:51:07PM +0200, Christian Brauner wrote:
> On Tue, May 10, 2022 at 09:10:25AM -0500, Serge Hallyn wrote:
> > On Tue, May 10, 2022 at 12:25:25PM +0200, Christian Brauner wrote:
> > > On Mon, May 09, 2022 at 02:54:14PM -0500, Serge Hallyn wrote:
> > > > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > > > 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
> > > >
> > > > Is 'via' an extra word here or is there a missing word?
> > > >
> > > > I'll delay the rest of my response as the missing word may answer my
> > > > remaining question :)
> > >
> > > It can be both. It should either be removed or it should be followed by
> > > "securityfs_create_dentry()". securityfs_create_dentry() takes two
> > > references one in lookup_one_len() and another one explicitly via
> > > dget(). The latter one isn't needed. Some of that has been covered in an
> > > earlier thread:
> > > https://lore.kernel.org/lkml/20220105101815.ldsm4s5yx7pmuiil@wittgenstein
> >
> > Yes, I saw that two references were being taken. And near as I can tell,
> > the second one was never being dropped. So if you tell me that before this
> > patch the dentries are never freed, then I'm happy. Otherwise, I'm
> > bothered the fact that no matching dput is being deleted in the code (to
> > match the extra dget being removed). So where is the code where the final
> > dput was happening, and is it the d_delete() you're adding which is making
> > it so that that dput won't be called now?
>
> * So consider mounting securityfs _without this patch applied_:
>
> mount -t securityfs /sfs
>
> and assume we only have a single user that creates a file "foo" via
>
> securityfs_create_file()
> {
> lookup_one_len(); // first dget()
> dget(); // second dget()
> }
>
> now assume that user at some point calls
>
> void securityfs_remove()
> {
> if (d_is_dir(dentry))
> simple_rmdir(dir, dentry); // first dput()
> else
> simple_unlink(dir, dentry); // first dput()
> dput(dentry); // second dput()
> }
>
> * Now consider mounting securityfs _with this patch applied_:
>
> securityfs_create_file()
> {
> lookup_one_len(); // first dget()
> }
>
> void securityfs_remove()
> {
> dget(); // second dget()
> if (d_is_dir(dentry))
> simple_rmdir(dir, dentry); // first dput()
> else
> simple_unlink(dir, dentry); // first dput()
> dput(dentry); // second dput()
> }
Oh - I was thinking about the new d_delete, but I guess that doesn't matter.
thanks,
-serge
On Tue, May 10, 2022 at 05:51:07PM +0200, Christian Brauner wrote:
> On Tue, May 10, 2022 at 09:10:25AM -0500, Serge Hallyn wrote:
> > On Tue, May 10, 2022 at 12:25:25PM +0200, Christian Brauner wrote:
> > > On Mon, May 09, 2022 at 02:54:14PM -0500, Serge Hallyn wrote:
> > > > On Wed, Apr 20, 2022 at 10:06:08AM -0400, 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
> > > > > 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
> > > >
> > > > Is 'via' an extra word here or is there a missing word?
> > > >
> > > > I'll delay the rest of my response as the missing word may answer my
> > > > remaining question :)
> > >
> > > It can be both. It should either be removed or it should be followed by
> > > "securityfs_create_dentry()". securityfs_create_dentry() takes two
> > > references one in lookup_one_len() and another one explicitly via
> > > dget(). The latter one isn't needed. Some of that has been covered in an
> > > earlier thread:
> > > https://lore.kernel.org/lkml/20220105101815.ldsm4s5yx7pmuiil@wittgenstein
> >
> > Yes, I saw that two references were being taken. And near as I can tell,
> > the second one was never being dropped. So if you tell me that before this
> > patch the dentries are never freed, then I'm happy. Otherwise, I'm
> > bothered the fact that no matching dput is being deleted in the code (to
> > match the extra dget being removed). So where is the code where the final
> > dput was happening, and is it the d_delete() you're adding which is making
> > it so that that dput won't be called now?
>
> * So consider mounting securityfs _without this patch applied_:
>
> mount -t securityfs /sfs
>
> and assume we only have a single user that creates a file "foo" via
>
> securityfs_create_file()
> {
> lookup_one_len(); // first dget()
> dget(); // second dget()
> }
>
> now assume that user at some point calls
>
> void securityfs_remove()
> {
> if (d_is_dir(dentry))
> simple_rmdir(dir, dentry); // first dput()
> else
> simple_unlink(dir, dentry); // first dput()
> dput(dentry); // second dput()
> }
>
> * Now consider mounting securityfs _with this patch applied_:
>
> securityfs_create_file()
> {
> lookup_one_len(); // first dget()
> }
>
> void securityfs_remove()
> {
> dget(); // second dget()
> if (d_is_dir(dentry))
> simple_rmdir(dir, dentry); // first dput()
> else
> simple_unlink(dir, dentry); // first dput()
> dput(dentry); // second dput()
> }
Thanks, I get it now
Reviewed-by: Serge Hallyn <[email protected]>
On Wed, Apr 20, 2022 at 10:06:19AM -0400, Stefan Berger wrote:
> Only accept AUDIT rules for non-init_ima_ns namespaces for now. Reject
This sentence gives me trouble - i keep thinking you mean that you'll
reject AUDIT rules for init_ima_ns :) Can you rephrase it as something
like
For non-init_ima_ns namespaces, only accept AUDIT rules for now.
:)
> 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 59e4ae5a6361..45a997709200 100644
> --- a/security/integrity/ima/ima_policy.c
> +++ b/security/integrity/ima/ima_policy.c
> @@ -1812,6 +1812,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:
So... what about DONT_MEASURE and DONT_APPRAISE?
> + result = -EINVAL;
> + goto err_audit;
> + }
> + }
> }
> if (!result && !ima_validate_rule(entry))
> result = -EINVAL;
> @@ -1824,6 +1835,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
> check_template_modsig(template_desc);
> }
>
> +err_audit:
> audit_log_format(ab, "res=%d", !result);
> audit_log_end(ab);
> return result;
> --
> 2.34.1
On Wed, Apr 20, 2022 at 10:06:10AM -0400, Stefan Berger wrote:
> 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]>
>
> ---
>
> 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 | 28 ++--
> 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, 248 insertions(+), 145 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 be965a8715e4..9bcde1a24e74 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);
>
> #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 c6805af46211..90ef246a9f43 100644
> --- a/security/integrity/ima/ima_api.c
> +++ b/security/integrity/ima/ima_api.c
> @@ -162,6 +162,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
> @@ -185,7 +186,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,
> @@ -193,9 +195,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 17232bbfb9f9..f1b99b895c68 100644
> --- a/security/integrity/ima/ima_appraise.c
> +++ b/security/integrity/ima/ima_appraise.c
> @@ -68,7 +68,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;
> @@ -77,7 +78,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);
> }
> @@ -341,7 +342,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;
> @@ -357,7 +359,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);
> }
> @@ -527,14 +530,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);
> @@ -559,11 +564,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);
> @@ -641,6 +647,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;
>
> @@ -658,18 +665,19 @@ int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
> if (result)
> return result;
>
> - ima_reset_appraise_flags(d_backing_inode(dentry), digsig);
> + ima_reset_appraise_flags(ns, d_backing_inode(dentry), digsig);
> }
> return result;
> }
>
> 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 3d3f8c5c502b..400865c521dd 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;
>
> @@ -346,7 +348,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,
> @@ -405,12 +407,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;
> @@ -431,6 +434,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 = vma->vm_file;
> char filename[NAME_MAX];
> @@ -443,13 +447,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);
>
> @@ -485,17 +489,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);
> }
>
> @@ -511,22 +516,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);
> @@ -595,10 +601,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);
>
> @@ -622,10 +630,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);
>
> @@ -641,13 +651,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;
> @@ -673,14 +684,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;
> @@ -709,6 +721,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;
>
> @@ -731,7 +744,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);
> }
>
> @@ -759,6 +772,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;
>
> @@ -767,14 +781,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);
> }
>
> @@ -862,6 +877,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.
> @@ -880,7 +896,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,
> @@ -905,7 +922,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();
> @@ -924,7 +941,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)
> @@ -961,7 +978,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);
> @@ -995,6 +1012,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)
> @@ -1004,7 +1022,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);
> @@ -1034,10 +1053,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);
> @@ -1046,6 +1067,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();
> @@ -1070,7 +1092,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 eea6e92500b8..69b19f4d5fee 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;
>
> @@ -232,11 +231,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)
> @@ -453,12 +447,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;
>
> @@ -473,10 +467,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;
> }
>
> @@ -668,6 +664,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
> @@ -687,7 +684,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,
> @@ -701,7 +699,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))
> @@ -745,8 +743,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.
> *
> @@ -759,14 +757,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
> @@ -796,7 +794,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)
> @@ -812,7 +810,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;
> @@ -821,7 +820,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),
> @@ -829,7 +828,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)
> @@ -842,9 +841,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;
> @@ -872,7 +872,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);
> @@ -887,26 +887,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;
> @@ -920,11 +921,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);
>
> /*
> @@ -932,7 +933,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);
>
> /*
> @@ -944,39 +945,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
> @@ -985,16 +988,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
> @@ -1003,10 +1007,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! */
> @@ -1076,7 +1080,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;
> @@ -1096,7 +1101,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;
> @@ -1323,7 +1329,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;
> @@ -1673,37 +1680,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;
> @@ -1804,12 +1811,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;
> @@ -1833,7 +1841,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,
> @@ -1842,23 +1850,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);
> }
> @@ -1884,12 +1893,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();
> @@ -1902,6 +1912,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();
> @@ -1909,8 +1920,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)
> @@ -2173,6 +2184,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;
> @@ -2184,7 +2196,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 */
Hm, yes, it seems to, but it should be unreachable with
ns != &init_ima_ns, ever, right?
So it seems better to either not have this hunk at all, (both
here and at ima_keys_handler()) or to actually have a BUG_ON.
Or am I completely misreading the situation?
> + 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.34.1
On Wed, Apr 20, 2022 at 10:06:16AM -0400, Stefan Berger wrote:
> 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]>
>
> ---
> 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 | 26 ++++++++++++++++--------
> 4 files changed, 35 insertions(+), 13 deletions(-)
>
> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> index b35c8504ef87..c68b5117d034 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 005f9e784e7b..d44faf1c065d 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();
> @@ -1089,7 +1085,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 0a7c61ca3265..23c559c8fae9 100644
> --- a/security/integrity/ima/ima_policy.c
> +++ b/security/integrity/ima/ima_policy.c
> @@ -368,7 +368,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;
> @@ -399,18 +400,25 @@ 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)
> {
> struct ima_rule_entry *nentry;
>
> - nentry = ima_lsm_copy_rule(entry);
> + nentry = ima_lsm_copy_rule(ns, entry);
> if (!nentry)
> return -ENOMEM;
>
> @@ -453,7 +461,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;
> @@ -464,12 +472,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;
> }
>
> --
> 2.34.1
On Wed, Apr 20, 2022 at 10:06:12AM -0400, Stefan Berger wrote:
> Move ima_htable into ima_namespace. This way a front-end like
> securityfs can show the number of measurement records and number of
> violations of an IMA namespace.
>
> Signed-off-by: Stefan Berger <[email protected]>
> Acked-by: Christian Brauner <[email protected]>
> Reviewed-by: Mimi Zohar <[email protected]>
Reviewed-by: Serge Hallyn <[email protected]>
> ---
> security/integrity/ima/ima.h | 33 +++++++++++++---------
> security/integrity/ima/ima_api.c | 18 +++++++-----
> security/integrity/ima/ima_fs.c | 8 ++++--
> security/integrity/ima/ima_init.c | 7 +++--
> security/integrity/ima/ima_init_ima_ns.c | 4 +++
> security/integrity/ima/ima_kexec.c | 3 +-
> security/integrity/ima/ima_main.c | 14 +++++----
> security/integrity/ima/ima_queue.c | 36 ++++++++++++------------
> security/integrity/ima/ima_template.c | 5 ++--
> 9 files changed, 76 insertions(+), 52 deletions(-)
>
> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> index 2305bf223a98..78798cfcf46c 100644
> --- a/security/integrity/ima/ima.h
> +++ b/security/integrity/ima/ima.h
> @@ -117,6 +117,12 @@ struct ima_kexec_hdr {
> u64 count;
> };
>
> +struct ima_h_table {
> + atomic_long_t len; /* number of stored measurements in the list */
> + atomic_long_t violations;
> + struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
> +};
> +
> struct ima_namespace {
> /* policy rules */
> struct list_head ima_default_rules; /* Kconfig, builtin & arch rules */
> @@ -128,6 +134,8 @@ struct ima_namespace {
>
> /* An array of architecture specific rules */
> struct ima_rule_entry *arch_policy_entry;
> +
> + struct ima_h_table ima_htable;
> } __randomize_layout;
> extern struct ima_namespace init_ima_ns;
>
> @@ -149,7 +157,8 @@ extern bool ima_canonical_fmt;
> 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,
> +int ima_add_template_entry(struct ima_namespace *ns,
> + struct ima_template_entry *entry, int violation,
> const char *op, struct inode *inode,
> const unsigned char *filename);
> int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash);
> @@ -158,7 +167,8 @@ int ima_calc_buffer_hash(const void *buf, loff_t len,
> int ima_calc_field_array_hash(struct ima_field_data *field_data,
> struct ima_template_entry *entry);
> int ima_calc_boot_aggregate(struct ima_digest_data *hash);
> -void ima_add_violation(struct file *file, const unsigned char *filename,
> +void ima_add_violation(struct ima_namespace *ns,
> + struct file *file, const unsigned char *filename,
> struct integrity_iint_cache *iint,
> const char *op, const char *cause);
> int ima_init_crypto(void);
> @@ -171,8 +181,10 @@ struct ima_template_desc *ima_template_desc_current(void);
> struct ima_template_desc *ima_template_desc_buf(void);
> struct ima_template_desc *lookup_template_desc(const char *name);
> bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
> -int ima_restore_measurement_entry(struct ima_template_entry *entry);
> -int ima_restore_measurement_list(loff_t bufsize, void *buf);
> +int ima_restore_measurement_entry(struct ima_namespace *ns,
> + struct ima_template_entry *entry);
> +int ima_restore_measurement_list(struct ima_namespace *ns,
> + loff_t bufsize, void *buf);
> int ima_measurements_show(struct seq_file *m, void *v);
> unsigned long ima_get_binary_runtime_size(void);
> int ima_init_template(void);
> @@ -186,13 +198,6 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
> */
> extern spinlock_t ima_queue_lock;
>
> -struct ima_h_table {
> - atomic_long_t len; /* number of stored measurements in the list */
> - atomic_long_t violations;
> - struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
> -};
> -extern struct ima_h_table ima_htable;
> -
> static inline unsigned int ima_hash_key(u8 *digest)
> {
> /* there is no point in taking a hash of part of a digest */
> @@ -277,7 +282,8 @@ int ima_must_measure(struct inode *inode, int mask, enum ima_hooks func);
> int ima_collect_measurement(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 integrity_iint_cache *iint, struct file *file,
> +void ima_store_measurement(struct ima_namespace *ns,
> + struct integrity_iint_cache *iint, struct file *file,
> const unsigned char *filename,
> struct evm_ima_xattr_data *xattr_value,
> int xattr_len, const struct modsig *modsig, int pcr,
> @@ -293,7 +299,8 @@ void ima_audit_measurement(struct integrity_iint_cache *iint,
> int ima_alloc_init_template(struct ima_event_data *event_data,
> struct ima_template_entry **entry,
> struct ima_template_desc *template_desc);
> -int ima_store_template(struct ima_template_entry *entry, int violation,
> +int ima_store_template(struct ima_namespace *ns,
> + struct ima_template_entry *entry, int violation,
> struct inode *inode,
> const unsigned char *filename, int pcr);
> void ima_free_template_entry(struct ima_template_entry *entry);
> diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
> index 90ef246a9f43..1b6c3e6174cd 100644
> --- a/security/integrity/ima/ima_api.c
> +++ b/security/integrity/ima/ima_api.c
> @@ -99,7 +99,8 @@ int ima_alloc_init_template(struct ima_event_data *event_data,
> *
> * Returns 0 on success, error code otherwise
> */
> -int ima_store_template(struct ima_template_entry *entry,
> +int ima_store_template(struct ima_namespace *ns,
> + struct ima_template_entry *entry,
> int violation, struct inode *inode,
> const unsigned char *filename, int pcr)
> {
> @@ -119,7 +120,8 @@ int ima_store_template(struct ima_template_entry *entry,
> }
> }
> entry->pcr = pcr;
> - result = ima_add_template_entry(entry, violation, op, inode, filename);
> + result = ima_add_template_entry(ns, entry, violation, op, inode,
> + filename);
> return result;
> }
>
> @@ -130,7 +132,8 @@ int ima_store_template(struct ima_template_entry *entry,
> * By extending the PCR with 0xFF's instead of with zeroes, the PCR
> * value is invalidated.
> */
> -void ima_add_violation(struct file *file, const unsigned char *filename,
> +void ima_add_violation(struct ima_namespace *ns,
> + struct file *file, const unsigned char *filename,
> struct integrity_iint_cache *iint,
> const char *op, const char *cause)
> {
> @@ -144,14 +147,14 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
> int result;
>
> /* can overflow, only indicator */
> - atomic_long_inc(&ima_htable.violations);
> + atomic_long_inc(&ns->ima_htable.violations);
>
> result = ima_alloc_init_template(&event_data, &entry, NULL);
> if (result < 0) {
> result = -ENOMEM;
> goto err_out;
> }
> - result = ima_store_template(entry, violation, inode,
> + result = ima_store_template(ns, entry, violation, inode,
> filename, CONFIG_IMA_MEASURE_PCR_IDX);
> if (result < 0)
> ima_free_template_entry(entry);
> @@ -297,7 +300,8 @@ int ima_collect_measurement(struct integrity_iint_cache *iint,
> *
> * Must be called with iint->mutex held.
> */
> -void ima_store_measurement(struct integrity_iint_cache *iint,
> +void ima_store_measurement(struct ima_namespace *ns,
> + struct integrity_iint_cache *iint,
> struct file *file, const unsigned char *filename,
> struct evm_ima_xattr_data *xattr_value,
> int xattr_len, const struct modsig *modsig, int pcr,
> @@ -332,7 +336,7 @@ void ima_store_measurement(struct integrity_iint_cache *iint,
> return;
> }
>
> - result = ima_store_template(entry, violation, inode, filename, pcr);
> + result = ima_store_template(ns, entry, violation, inode, filename, pcr);
> if ((!result || result == -EEXIST) && !(file->f_flags & O_DIRECT)) {
> iint->flags |= IMA_MEASURED;
> iint->measured_pcrs |= (0x1 << pcr);
> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> index f7ad93a56982..dca7fe32d65e 100644
> --- a/security/integrity/ima/ima_fs.c
> +++ b/security/integrity/ima/ima_fs.c
> @@ -52,7 +52,10 @@ static ssize_t ima_show_htable_violations(struct file *filp,
> char __user *buf,
> size_t count, loff_t *ppos)
> {
> - return ima_show_htable_value(buf, count, ppos, &ima_htable.violations);
> + struct ima_namespace *ns = &init_ima_ns;
> +
> + return ima_show_htable_value(buf, count, ppos,
> + &ns->ima_htable.violations);
> }
>
> static const struct file_operations ima_htable_violations_ops = {
> @@ -64,8 +67,9 @@ static ssize_t ima_show_measurements_count(struct file *filp,
> char __user *buf,
> size_t count, loff_t *ppos)
> {
> - return ima_show_htable_value(buf, count, ppos, &ima_htable.len);
> + struct ima_namespace *ns = &init_ima_ns;
>
> + return ima_show_htable_value(buf, count, ppos, &ns->ima_htable.len);
> }
>
> static const struct file_operations ima_measurements_count_ops = {
> diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
> index 7e5b4187035d..47c9d561532e 100644
> --- a/security/integrity/ima/ima_init.c
> +++ b/security/integrity/ima/ima_init.c
> @@ -39,7 +39,7 @@ struct tpm_chip *ima_tpm_chip;
> * a different value.) Violations add a zero entry to the measurement
> * list and extend the aggregate PCR value with ff...ff's.
> */
> -static int __init ima_add_boot_aggregate(void)
> +static int __init ima_add_boot_aggregate(struct ima_namespace *ns)
> {
> static const char op[] = "add_boot_aggregate";
> const char *audit_cause = "ENOMEM";
> @@ -83,7 +83,7 @@ static int __init ima_add_boot_aggregate(void)
> goto err_out;
> }
>
> - result = ima_store_template(entry, violation, NULL,
> + result = ima_store_template(ns, entry, violation, NULL,
> boot_aggregate_name,
> CONFIG_IMA_MEASURE_PCR_IDX);
> if (result < 0) {
> @@ -142,7 +142,8 @@ int __init ima_init(void)
> rc = ima_init_digests();
> if (rc != 0)
> return rc;
> - rc = ima_add_boot_aggregate(); /* boot aggregate must be first entry */
> + /* boot aggregate must be first entry */
> + rc = ima_add_boot_aggregate(&init_ima_ns);
> if (rc != 0)
> return rc;
>
> diff --git a/security/integrity/ima/ima_init_ima_ns.c b/security/integrity/ima/ima_init_ima_ns.c
> index ae33621c3955..1945fa8cfc4d 100644
> --- a/security/integrity/ima/ima_init_ima_ns.c
> +++ b/security/integrity/ima/ima_init_ima_ns.c
> @@ -17,6 +17,10 @@ static int ima_init_namespace(struct ima_namespace *ns)
> ns->ima_policy_flag = 0;
> ns->arch_policy_entry = NULL;
>
> + atomic_long_set(&ns->ima_htable.len, 0);
> + atomic_long_set(&ns->ima_htable.violations, 0);
> + memset(&ns->ima_htable.queue, 0, sizeof(ns->ima_htable.queue));
> +
> return 0;
> }
>
> diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
> index 13753136f03f..d7cc5cca6f84 100644
> --- a/security/integrity/ima/ima_kexec.c
> +++ b/security/integrity/ima/ima_kexec.c
> @@ -146,7 +146,8 @@ void ima_load_kexec_buffer(void)
> rc = ima_get_kexec_buffer(&kexec_buffer, &kexec_buffer_size);
> switch (rc) {
> case 0:
> - rc = ima_restore_measurement_list(kexec_buffer_size,
> + rc = ima_restore_measurement_list(&init_ima_ns,
> + kexec_buffer_size,
> kexec_buffer);
> if (rc != 0)
> pr_err("Failed to restore the measurement list: %d\n",
> diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
> index 400865c521dd..005f9e784e7b 100644
> --- a/security/integrity/ima/ima_main.c
> +++ b/security/integrity/ima/ima_main.c
> @@ -112,7 +112,8 @@ static int mmap_violation_check(enum ima_hooks func, struct file *file,
> * could result in a file measurement error.
> *
> */
> -static void ima_rdwr_violation_check(struct file *file,
> +static void ima_rdwr_violation_check(struct ima_namespace *ns,
> + struct file *file,
> struct integrity_iint_cache *iint,
> int must_measure,
> char **pathbuf,
> @@ -145,10 +146,10 @@ static void ima_rdwr_violation_check(struct file *file,
> *pathname = ima_d_path(&file->f_path, pathbuf, filename);
>
> if (send_tomtou)
> - ima_add_violation(file, *pathname, iint,
> + ima_add_violation(ns, file, *pathname, iint,
> "invalid_pcr", "ToMToU");
> if (send_writers)
> - ima_add_violation(file, *pathname, iint,
> + ima_add_violation(ns, file, *pathname, iint,
> "invalid_pcr", "open_writers");
> }
>
> @@ -249,7 +250,7 @@ static int process_measurement(struct ima_namespace *ns,
> }
>
> if (!rc && violation_check)
> - ima_rdwr_violation_check(file, iint, action & IMA_MEASURE,
> + ima_rdwr_violation_check(ns, file, iint, action & IMA_MEASURE,
> &pathbuf, &pathname, filename);
>
> inode_unlock(inode);
> @@ -344,7 +345,7 @@ static int process_measurement(struct ima_namespace *ns,
> pathname = ima_d_path(&file->f_path, &pathbuf, filename);
>
> if (action & IMA_MEASURE)
> - ima_store_measurement(iint, file, pathname,
> + ima_store_measurement(ns, iint, file, pathname,
> xattr_value, xattr_len, modsig, pcr,
> template_desc);
> if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
> @@ -987,7 +988,8 @@ int process_buffer_measurement(struct ima_namespace *ns,
> goto out;
> }
>
> - ret = ima_store_template(entry, violation, NULL, event_data.buf, pcr);
> + ret = ima_store_template(ns, entry, violation, NULL, event_data.buf,
> + pcr);
> if (ret < 0) {
> audit_cause = "store_entry";
> ima_free_template_entry(entry);
> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> index 532da87ce519..43961d5cd2ef 100644
> --- a/security/integrity/ima/ima_queue.c
> +++ b/security/integrity/ima/ima_queue.c
> @@ -31,13 +31,6 @@ static unsigned long binary_runtime_size;
> static unsigned long binary_runtime_size = ULONG_MAX;
> #endif
>
> -/* key: inode (before secure-hashing a file) */
> -struct ima_h_table ima_htable = {
> - .len = ATOMIC_LONG_INIT(0),
> - .violations = ATOMIC_LONG_INIT(0),
> - .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
> -};
> -
> /* mutex protects atomicity of extending measurement list
> * and extending the TPM PCR aggregate. Since tpm_extend can take
> * long (and the tpm driver uses a mutex), we can't use the spinlock.
> @@ -45,8 +38,10 @@ struct ima_h_table ima_htable = {
> static DEFINE_MUTEX(ima_extend_list_mutex);
>
> /* lookup up the digest value in the hash table, and return the entry */
> -static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value,
> - int pcr)
> +static struct ima_queue_entry *ima_lookup_digest_entry
> + (struct ima_namespace *ns,
> + u8 *digest_value,
> + int pcr)
> {
> struct ima_queue_entry *qe, *ret = NULL;
> unsigned int key;
> @@ -54,7 +49,7 @@ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value,
>
> key = ima_hash_key(digest_value);
> rcu_read_lock();
> - hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) {
> + hlist_for_each_entry_rcu(qe, &ns->ima_htable.queue[key], hnext) {
> rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest,
> digest_value, hash_digest_size[ima_hash_algo]);
> if ((rc == 0) && (qe->entry->pcr == pcr)) {
> @@ -90,7 +85,8 @@ static int get_binary_runtime_size(struct ima_template_entry *entry)
> *
> * (Called with ima_extend_list_mutex held.)
> */
> -static int ima_add_digest_entry(struct ima_template_entry *entry,
> +static int ima_add_digest_entry(struct ima_namespace *ns,
> + struct ima_template_entry *entry,
> bool update_htable)
> {
> struct ima_queue_entry *qe;
> @@ -106,10 +102,12 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> INIT_LIST_HEAD(&qe->later);
> list_add_tail_rcu(&qe->later, &ima_measurements);
>
> - atomic_long_inc(&ima_htable.len);
> + atomic_long_inc(&ns->ima_htable.len);
> if (update_htable) {
> key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
> - hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
> + hlist_add_head_rcu(&qe->hnext, &ns->ima_htable.queue[key]);
> + } else {
> + INIT_HLIST_NODE(&qe->hnext);
> }
>
> if (binary_runtime_size != ULONG_MAX) {
> @@ -156,7 +154,8 @@ static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr)
> * kexec, maintain the total memory size required for serializing the
> * binary_runtime_measurements.
> */
> -int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> +int ima_add_template_entry(struct ima_namespace *ns,
> + struct ima_template_entry *entry, int violation,
> const char *op, struct inode *inode,
> const unsigned char *filename)
> {
> @@ -169,14 +168,14 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
>
> mutex_lock(&ima_extend_list_mutex);
> if (!violation && !IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
> - if (ima_lookup_digest_entry(digest, entry->pcr)) {
> + if (ima_lookup_digest_entry(ns, digest, entry->pcr)) {
> audit_cause = "hash_exists";
> result = -EEXIST;
> goto out;
> }
> }
>
> - result = ima_add_digest_entry(entry,
> + result = ima_add_digest_entry(ns, entry,
> !IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE));
> if (result < 0) {
> audit_cause = "ENOMEM";
> @@ -201,12 +200,13 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> return result;
> }
>
> -int ima_restore_measurement_entry(struct ima_template_entry *entry)
> +int ima_restore_measurement_entry(struct ima_namespace *ns,
> + struct ima_template_entry *entry)
> {
> int result = 0;
>
> mutex_lock(&ima_extend_list_mutex);
> - result = ima_add_digest_entry(entry, 0);
> + result = ima_add_digest_entry(ns, entry, 0);
> mutex_unlock(&ima_extend_list_mutex);
> return result;
> }
> diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c
> index db1ad6d7a57f..e5c33a3f0296 100644
> --- a/security/integrity/ima/ima_template.c
> +++ b/security/integrity/ima/ima_template.c
> @@ -404,7 +404,8 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc,
> }
>
> /* Restore the serialized binary measurement list without extending PCRs. */
> -int ima_restore_measurement_list(loff_t size, void *buf)
> +int ima_restore_measurement_list(struct ima_namespace *ns,
> + loff_t size, void *buf)
> {
> char template_name[MAX_TEMPLATE_NAME_LEN];
> unsigned char zero[TPM_DIGEST_SIZE] = { 0 };
> @@ -520,7 +521,7 @@ int ima_restore_measurement_list(loff_t size, void *buf)
>
> entry->pcr = !ima_canonical_fmt ? *(u32 *)(hdr[HDR_PCR].data) :
> le32_to_cpu(*(__le32 *)(hdr[HDR_PCR].data));
> - ret = ima_restore_measurement_entry(entry);
> + ret = ima_restore_measurement_entry(ns, entry);
> if (ret < 0)
> break;
>
> --
> 2.34.1
On Sun, May 22, 2022 at 01:24:26PM -0500, Serge Hallyn wrote:
> On Wed, Apr 20, 2022 at 10:06:20AM -0400, Stefan Berger wrote:
> > 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]>
> >
> > ---
> > 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 | 4 ++++
> > kernel/user.c | 4 ++++
> > 3 files changed, 10 insertions(+)
> >
> > diff --git a/include/linux/ima.h b/include/linux/ima.h
> > index 426b1744215e..fcb60a44e05f 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 33a4240e6a6f..019e8cf7b633 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,
> > @@ -99,6 +100,9 @@ struct user_namespace {
> > #endif
> > struct ucounts *ucounts;
> > long ucount_max[UCOUNT_COUNTS];
> > +#ifdef CONFIG_IMA_NS
>
> It's probably worth putting a comment here saying that user_ns does not
> pin ima_ns.
>
> That the only time the ima_ns will be freed is when user_ns is freed,
> and only time it will be changed is when user_ns is freed, or during
> ima_fs_ns_init() (under smp_load_acquire) during a new mount.
>
> > + struct ima_namespace *ima_ns;
>
> So, if I create a new user_ns with a new ima_ns, and in there I
> create a new user_ns again, it looks like ima_ns will be NULL in
> the new user_ns? Should it not be set to the parent->ima_ns?
> (which would cause trouble for the way it's currently being
> freed...)
Would also work and wouldn't be difficult to do imho.
On 5/23/22 05:59, Christian Brauner wrote:
> On Sun, May 22, 2022 at 01:24:26PM -0500, Serge Hallyn wrote:
>> On Wed, Apr 20, 2022 at 10:06:20AM -0400, Stefan Berger wrote:
>>> 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]>
>>>
>>> ---
>>> 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 | 4 ++++
>>> kernel/user.c | 4 ++++
>>> 3 files changed, 10 insertions(+)
>>>
>>> diff --git a/include/linux/ima.h b/include/linux/ima.h
>>> index 426b1744215e..fcb60a44e05f 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 33a4240e6a6f..019e8cf7b633 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,
>>> @@ -99,6 +100,9 @@ struct user_namespace {
>>> #endif
>>> struct ucounts *ucounts;
>>> long ucount_max[UCOUNT_COUNTS];
>>> +#ifdef CONFIG_IMA_NS
>>
>> It's probably worth putting a comment here saying that user_ns does not
>> pin ima_ns.
>>
>> That the only time the ima_ns will be freed is when user_ns is freed,
>> and only time it will be changed is when user_ns is freed, or during
>> ima_fs_ns_init() (under smp_load_acquire) during a new mount.
>>
>>> + struct ima_namespace *ima_ns;
>>
>> So, if I create a new user_ns with a new ima_ns, and in there I
>> create a new user_ns again, it looks like ima_ns will be NULL in
>> the new user_ns? Should it not be set to the parent->ima_ns?
>> (which would cause trouble for the way it's currently being
>> freed...)
>
> Would also work and wouldn't be difficult to do imho.
We previously decide to create an ima_namespace when securityfs is
mounted. This now also applies to nested containers where an IMA
namespace is first configured with the hash and template to use in a
particular container and then activated. If no configuration is done it
will inherit the hash and template from the first ancestor that has been
configure when it is activated. So the same steps and behavior applies
to *all* containers, no difference at any depth of nesting. Besides
that, we don't want nested containers to share policy and logs but keep
them isolated from each other, or do we not?
Further, how should it work if we were to apply this even to the first
container? Should it just inherit the &init_ima_namespace and we'd have
no isolation at all? Why would we start treating containers at deeper
nesting levels differently?
On Mon, May 23, 2022 at 07:31:29AM -0400, Stefan Berger wrote:
>
>
> On 5/23/22 05:59, Christian Brauner wrote:
> > On Sun, May 22, 2022 at 01:24:26PM -0500, Serge Hallyn wrote:
> > > On Wed, Apr 20, 2022 at 10:06:20AM -0400, Stefan Berger wrote:
> > > > 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]>
> > > >
> > > > ---
> > > > 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 | 4 ++++
> > > > kernel/user.c | 4 ++++
> > > > 3 files changed, 10 insertions(+)
> > > >
> > > > diff --git a/include/linux/ima.h b/include/linux/ima.h
> > > > index 426b1744215e..fcb60a44e05f 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 33a4240e6a6f..019e8cf7b633 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,
> > > > @@ -99,6 +100,9 @@ struct user_namespace {
> > > > #endif
> > > > struct ucounts *ucounts;
> > > > long ucount_max[UCOUNT_COUNTS];
> > > > +#ifdef CONFIG_IMA_NS
> > >
> > > It's probably worth putting a comment here saying that user_ns does not
> > > pin ima_ns.
> > >
> > > That the only time the ima_ns will be freed is when user_ns is freed,
> > > and only time it will be changed is when user_ns is freed, or during
> > > ima_fs_ns_init() (under smp_load_acquire) during a new mount.
> > >
> > > > + struct ima_namespace *ima_ns;
> > >
> > > So, if I create a new user_ns with a new ima_ns, and in there I
> > > create a new user_ns again, it looks like ima_ns will be NULL in
> > > the new user_ns? Should it not be set to the parent->ima_ns?
> > > (which would cause trouble for the way it's currently being
> > > freed...)
> >
> > Would also work and wouldn't be difficult to do imho.
>
> We previously decide to create an ima_namespace when securityfs is mounted.
> This now also applies to nested containers where an IMA namespace is first
> configured with the hash and template to use in a particular container and
> then activated. If no configuration is done it will inherit the hash and
> template from the first ancestor that has been configure when it is
> activated. So the same steps and behavior applies to *all* containers, no
> difference at any depth of nesting. Besides that, we don't want nested
> containers to share policy and logs but keep them isolated from each other,
> or do we not?
>
> Further, how should it work if we were to apply this even to the first
> container? Should it just inherit the &init_ima_namespace and we'd have no
> isolation at all? Why would we start treating containers at deeper nesting
> levels differently?
Valid points. I understood Serge as suggesting an implementation detail
change not a design change but might misunderstand him here.
# Currently
1. create new userns -> imans set to NULL
2. mount securityfs and configure imans -> set imans to &new_ima_ns
When 2. hasn't been done then we find the relevant imans by walking
the userns hierarchy upwards finding the first parent userns that has a
non-NULL imans.
# Serge's suggestion
1. create new userns -> imans is set to parent imans
2. mount securityfs and configure imans -> replace parent with &new_ima_ns
So when 2. hasn't been done we don't need to walk the userns hierarchy
upwards. We always find the relevant imans directly. Some massaging
would be needed in process_measurement() probably but it wouldn't need
to change semantics per se.
But I think I misunderstood something in any case. So looking at an
example like ima_post_path_mknod(). You seem to not call into
ima_must_appraise() if the caller's userns doesn't have an imans
enabled. I somehow had thought that the same logic applied as in
process_measurement. But if it isn't then it might make sense to keep
the current implementation.
On 5/23/22 08:41, Christian Brauner wrote:
> On Mon, May 23, 2022 at 07:31:29AM -0400, Stefan Berger wrote:
>>
>>
>> On 5/23/22 05:59, Christian Brauner wrote:
>>> On Sun, May 22, 2022 at 01:24:26PM -0500, Serge Hallyn wrote:
>>>> On Wed, Apr 20, 2022 at 10:06:20AM -0400, Stefan Berger wrote:
>>>>> 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]>
>>>>>
>>>>> ---
>>>>> 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 | 4 ++++
>>>>> kernel/user.c | 4 ++++
>>>>> 3 files changed, 10 insertions(+)
>>>>>
>>>>> diff --git a/include/linux/ima.h b/include/linux/ima.h
>>>>> index 426b1744215e..fcb60a44e05f 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 33a4240e6a6f..019e8cf7b633 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,
>>>>> @@ -99,6 +100,9 @@ struct user_namespace {
>>>>> #endif
>>>>> struct ucounts *ucounts;
>>>>> long ucount_max[UCOUNT_COUNTS];
>>>>> +#ifdef CONFIG_IMA_NS
>>>>
>>>> It's probably worth putting a comment here saying that user_ns does not
>>>> pin ima_ns.
>>>>
>>>> That the only time the ima_ns will be freed is when user_ns is freed,
>>>> and only time it will be changed is when user_ns is freed, or during
>>>> ima_fs_ns_init() (under smp_load_acquire) during a new mount.
>>>>
>>>>> + struct ima_namespace *ima_ns;
>>>>
>>>> So, if I create a new user_ns with a new ima_ns, and in there I
>>>> create a new user_ns again, it looks like ima_ns will be NULL in
>>>> the new user_ns? Should it not be set to the parent->ima_ns?
>>>> (which would cause trouble for the way it's currently being
>>>> freed...)
>>>
>>> Would also work and wouldn't be difficult to do imho.
>>
>> We previously decide to create an ima_namespace when securityfs is mounted.
>> This now also applies to nested containers where an IMA namespace is first
>> configured with the hash and template to use in a particular container and
>> then activated. If no configuration is done it will inherit the hash and
>> template from the first ancestor that has been configure when it is
>> activated. So the same steps and behavior applies to *all* containers, no
>> difference at any depth of nesting. Besides that, we don't want nested
>> containers to share policy and logs but keep them isolated from each other,
>> or do we not?
>>
>> Further, how should it work if we were to apply this even to the first
>> container? Should it just inherit the &init_ima_namespace and we'd have no
>> isolation at all? Why would we start treating containers at deeper nesting
>> levels differently?
>
> Valid points. I understood Serge as suggesting an implementation detail
> change not a design change but might misunderstand him here.
>
> # Currently
>
> 1. create new userns -> imans set to NULL
> 2. mount securityfs and configure imans -> set imans to &new_ima_ns
>
> When 2. hasn't been done then we find the relevant imans by walking
> the userns hierarchy upwards finding the first parent userns that has a
> non-NULL imans.
>
> # Serge's suggestion
>
> 1. create new userns -> imans is set to parent imans
> 2. mount securityfs and configure imans -> replace parent with &new_ima_ns
>
> So when 2. hasn't been done we don't need to walk the userns hierarchy
> upwards. We always find the relevant imans directly. Some massaging
In my understanding we *always* have to walk the hierarchy upwards
independent of nesting depth and visit all the parent namespaces so we
don't miss anything that has happened in a child namespace and can log
the action in the parent namespaces depending on that namespace's
policy. It's this command line that I am concerned about that must not
lead to concealing of running of programs in nested user namespaces from
the init_ima_ns for example:
unshare --user --map-root-user \
unshare --user --map-root-user \
unshare --user --map-root-user \
... \
<malware>
The malware would obviously run in the init_(pid|mnt|...)_namespace's.
Similarly this applies to running nested user namespaces that root could
nsenter into to try to conceal the execution of programs.
So I would rather want to prevent having to walk namespaces backwards
and encountering an ima_namespace pointer multiple times and having to
figure out whether the evaluation of the policy of that namespace
already occurred. I'd rather skip over user namespaces with NULL
pointers to IMA namespaces because those have not been configured and/or
activated, yet.
> would be needed in process_measurement() probably but it wouldn't need
> to change semantics per se.
>
> But I think I misunderstood something in any case. So looking at an
> example like ima_post_path_mknod(). You seem to not call into
> ima_must_appraise() if the caller's userns doesn't have an imans
> enabled. I somehow had thought that the same logic applied as in
> process_measurement. But if it isn't then it might make sense to keep
> the current implementation.
On 5/22/22 13:54, Serge E. Hallyn wrote:
> On Wed, Apr 20, 2022 at 10:06:30AM -0400, Stefan Berger wrote:
>> Show the uid and gid values relative to the user namespace that is
>> currently active. The effect of this changes is that when one displays
>
> When you say "is currently active", in my mind it's not clear whether you
> mean in the process which opened the seq_file, or is active in the ima_ns,
> or the reader (which might I guess be differenet still). The code of
> course does make it clear. Can you change it to say "the user namespace
> which opened the policy_show file" or something like that?
>
> Also, s/The effect of this changes/The effect of this change/.
>
>> 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]>
I modified the text above now to state:
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.
Thanks.
Stefan
>
>> ---
>> 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 eb10d895923d..4f8c50ddb777 100644
>> --- a/security/integrity/ima/ima_policy.c
>> +++ b/security/integrity/ima/ima_policy.c
>> @@ -2018,6 +2018,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,};
>> @@ -2103,7 +2104,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)
>> @@ -2114,7 +2116,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)
>> @@ -2125,7 +2128,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)
>> @@ -2136,7 +2140,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)
>> @@ -2147,7 +2152,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 == &uid_gt)
>> seq_printf(m, pt(Opt_fowner_gt), tbuf);
>> else if (entry->fowner_op == &uid_lt)
>> @@ -2158,7 +2164,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 == &gid_gt)
>> seq_printf(m, pt(Opt_fgroup_gt), tbuf);
>> else if (entry->fgroup_op == &gid_lt)
>> --
>> 2.34.1
On 5/20/22 22:33, Serge E. Hallyn wrote:
>> 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 */
>
> Hm, yes, it seems to, but it should be unreachable with
> ns != &init_ima_ns, ever, right?
>
> So it seems better to either not have this hunk at all, (both
> here and at ima_keys_handler()) or to actually have a BUG_ON.
>
> Or am I completely misreading the situation?
No, you are right. This function is only calledwith ns = &init_ima_ns at
the moment. How about changing it to this here?
if (WARN_ON(ns != &init_ima_ns))
return;
>
>> + 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.34.1
On 5/20/22 22:33, Serge E. Hallyn wrote:
>> * they make a queue. The policy may be updated multiple times and this is the
>> @@ -985,16 +988,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
>> @@ -1003,10 +1007,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);
>> }
>>
So this is a caller that may enter that function with ns != init_ima_ns
and in that case that function should do nothing. So, also the WARN_ON()
is not appropriate then.
Stefan
On Tue, May 24, 2022 at 10:57:13AM -0400, Stefan Berger wrote:
>
>
> On 5/20/22 22:33, Serge E. Hallyn wrote:
>
> > > 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 */
> >
> > Hm, yes, it seems to, but it should be unreachable with
> > ns != &init_ima_ns, ever, right?
> >
> > So it seems better to either not have this hunk at all, (both
> > here and at ima_keys_handler()) or to actually have a BUG_ON.
> >
> > Or am I completely misreading the situation?
>
> No, you are right. This function is only calledwith ns = &init_ima_ns at the
> moment. How about changing it to this here?
>
> if (WARN_ON(ns != &init_ima_ns))
> return;
Sounds good, thanks.
On 5/22/22 13:38, Serge E. Hallyn wrote:
> On Wed, Apr 20, 2022 at 10:06:19AM -0400, Stefan Berger wrote:
>> Only accept AUDIT rules for non-init_ima_ns namespaces for now. Reject
>
> This sentence gives me trouble - i keep thinking you mean that you'll
> reject AUDIT rules for init_ima_ns :) Can you rephrase it as something
> like
>
> For non-init_ima_ns namespaces, only accept AUDIT rules for now.
>
> :)
>
>> all rules that require support for measuring, appraisal, and hashing.
>
I kept the title of the patch but the text now states:
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 59e4ae5a6361..45a997709200 100644
>> --- a/security/integrity/ima/ima_policy.c
>> +++ b/security/integrity/ima/ima_policy.c
>> @@ -1812,6 +1812,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:
>
> So... what about DONT_MEASURE and DONT_APPRAISE?
They don't cause IMA to do anything that is not supported at this point
so I let them pass. If you set these you still don't get a measurements
or appraisal and that's good at this point..
>
>> + result = -EINVAL;
>> + goto err_audit;
>> + }
>> + }
>> }
>> if (!result && !ima_validate_rule(entry))
>> result = -EINVAL;
>> @@ -1824,6 +1835,7 @@ static int ima_parse_rule(struct ima_namespace *ns,
>> check_template_modsig(template_desc);
>> }
>>
>> +err_audit:
>> audit_log_format(ab, "res=%d", !result);
>> audit_log_end(ab);
>> return result;
>> --
>> 2.34.1
On Wed, Apr 20, 2022 at 10:06:23AM -0400, Stefan Berger wrote:
> 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 fcb60a44e05f..76d19995ab89 100644
> --- a/include/linux/ima.h
> +++ b/include/linux/ima.h
> @@ -220,4 +220,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 054b8f67be04..3e77738aec2c 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,
> @@ -506,4 +507,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.34.1
>
Hi James,
On Tue, 2022-05-10 at 15:41 -0500, Serge E. Hallyn wrote:
<snip>
> Reviewed-by: Serge Hallyn <[email protected]>
b4 properly applies Serge's tag. Any chance you could take this patch
and, probably, 2/26 via the security tree?
thanks,
Mimi
On 5/23/22 10:25, Serge E. Hallyn wrote:
> On Mon, May 23, 2022 at 02:41:59PM +0200, Christian Brauner wrote:
>> On Mon, May 23, 2022 at 07:31:29AM -0400, Stefan Berger wrote:
>>>
>>>
>>> On 5/23/22 05:59, Christian Brauner wrote:
>>>> On Sun, May 22, 2022 at 01:24:26PM -0500, Serge Hallyn wrote:
>>>>> On Wed, Apr 20, 2022 at 10:06:20AM -0400, Stefan Berger wrote:
>>>>>> 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]>
>>>>>>
>>>>>> ---
>>>>>> 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 | 4 ++++
>>>>>> kernel/user.c | 4 ++++
>>>>>> 3 files changed, 10 insertions(+)
>>>>>>
>>>>>> diff --git a/include/linux/ima.h b/include/linux/ima.h
>>>>>> index 426b1744215e..fcb60a44e05f 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 33a4240e6a6f..019e8cf7b633 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,
>>>>>> @@ -99,6 +100,9 @@ struct user_namespace {
>>>>>> #endif
>>>>>> struct ucounts *ucounts;
>>>>>> long ucount_max[UCOUNT_COUNTS];
>>>>>> +#ifdef CONFIG_IMA_NS
>>>>>
>>>>> It's probably worth putting a comment here saying that user_ns does not
>>>>> pin ima_ns.
>>>>>
>>>>> That the only time the ima_ns will be freed is when user_ns is freed,
>>>>> and only time it will be changed is when user_ns is freed, or during
>>>>> ima_fs_ns_init() (under smp_load_acquire) during a new mount.
>>>>>
>>>>>> + struct ima_namespace *ima_ns;
>>>>>
>>>>> So, if I create a new user_ns with a new ima_ns, and in there I
>>>>> create a new user_ns again, it looks like ima_ns will be NULL in
>>>>> the new user_ns? Should it not be set to the parent->ima_ns?
>>>>> (which would cause trouble for the way it's currently being
>>>>> freed...)
>>>>
>>>> Would also work and wouldn't be difficult to do imho.
>>>
>>> We previously decide to create an ima_namespace when securityfs is mounted.
>>> This now also applies to nested containers where an IMA namespace is first
>>> configured with the hash and template to use in a particular container and
>>> then activated. If no configuration is done it will inherit the hash and
>>> template from the first ancestor that has been configure when it is
>>> activated. So the same steps and behavior applies to *all* containers, no
>>> difference at any depth of nesting. Besides that, we don't want nested
>>> containers to share policy and logs but keep them isolated from each other,
>>> or do we not?
>>>
>>> Further, how should it work if we were to apply this even to the first
>>> container? Should it just inherit the &init_ima_namespace and we'd have no
>>> isolation at all? Why would we start treating containers at deeper nesting
>>> levels differently?
>>
>> Valid points. I understood Serge as suggesting an implementation detail
>> change not a design change but might misunderstand him here.
>>
>> # Currently
>>
>> 1. create new userns -> imans set to NULL
>> 2. mount securityfs and configure imans -> set imans to &new_ima_ns
>>
>> When 2. hasn't been done then we find the relevant imans by walking
>> the userns hierarchy upwards finding the first parent userns that has a
>> non-NULL imans.
>
> Ah, right, thanks Christian.
>
> But so the code - I think where the ima_ns is defined in the user_ns
> struct, needs to make this clear. Just something like
>
> // Pointer to ima_ns which this user_ns created. Can be null.
> // 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;
I added this comment now. Thanks.
>
>> # Serge's suggestion
>>
>> 1. create new userns -> imans is set to parent imans
>> 2. mount securityfs and configure imans -> replace parent with &new_ima_ns
>>
>> So when 2. hasn't been done we don't need to walk the userns hierarchy
>> upwards. We always find the relevant imans directly. Some massaging
>> would be needed in process_measurement() probably but it wouldn't need
>> to change semantics per se.
>>
>> But I think I misunderstood something in any case. So looking at an
>> example like ima_post_path_mknod(). You seem to not call into
>> ima_must_appraise() if the caller's userns doesn't have an imans
>> enabled. I somehow had thought that the same logic applied as in
>> process_measurement. But if it isn't then it might make sense to keep
>> the current implementation.