* [RFC PATCH v5 04/11] ipe: add property for trust of boot volume
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add a property for IPE policy to express trust of the first superblock
where a file would be evaluated to determine trust.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
security/ipe/Kconfig | 2 +
security/ipe/Makefile | 4 ++
security/ipe/ipe-engine.c | 4 ++
security/ipe/ipe-hooks.c | 19 +++++
security/ipe/ipe-hooks.h | 2 +
security/ipe/ipe-pin.c | 93 +++++++++++++++++++++++++
security/ipe/ipe-pin.h | 36 ++++++++++
security/ipe/ipe.c | 28 +++++++-
security/ipe/properties/Kconfig | 15 ++++
security/ipe/properties/Makefile | 11 +++
security/ipe/properties/boot-verified.c | 82 ++++++++++++++++++++++
security/ipe/properties/prop-entry.h | 20 ++++++
security/ipe/utility.h | 22 ++++++
13 files changed, 337 insertions(+), 1 deletion(-)
create mode 100644 security/ipe/ipe-pin.c
create mode 100644 security/ipe/ipe-pin.h
create mode 100644 security/ipe/properties/Kconfig
create mode 100644 security/ipe/properties/Makefile
create mode 100644 security/ipe/properties/boot-verified.c
create mode 100644 security/ipe/properties/prop-entry.h
create mode 100644 security/ipe/utility.h
diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
index 665524fc3ca4..469ef78c2f4f 100644
--- a/security/ipe/Kconfig
+++ b/security/ipe/Kconfig
@@ -43,4 +43,6 @@ config SECURITY_IPE_PERMISSIVE_SWITCH
If unsure, answer Y.
+source "security/ipe/properties/Kconfig"
+
endif
diff --git a/security/ipe/Makefile b/security/ipe/Makefile
index 7d6da33dd0c4..7e98982c5035 100644
--- a/security/ipe/Makefile
+++ b/security/ipe/Makefile
@@ -26,3 +26,7 @@ obj-$(CONFIG_SECURITY_IPE) += \
ipe-secfs.o \
clean-files := ipe-bp.c
+
+obj-$(CONFIG_IPE_BOOT_PROP) += ipe-pin.o
+
+obj-$(CONFIG_SECURITY_IPE) += properties/
diff --git a/security/ipe/ipe-engine.c b/security/ipe/ipe-engine.c
index ac526d4ea5e6..0291ced99d64 100644
--- a/security/ipe/ipe-engine.c
+++ b/security/ipe/ipe-engine.c
@@ -9,6 +9,8 @@
#include "ipe-policy.h"
#include "ipe-engine.h"
#include "ipe-audit.h"
+#include "ipe-pin.h"
+#include "utility.h"
#include <linux/types.h>
#include <linux/fs.h>
@@ -197,6 +199,8 @@ int ipe_process_event(const struct file *file, enum ipe_op op,
if (IS_ERR(ctx))
goto cleanup;
+ ipe_pin_superblock(ctx->file);
+
rc = evaluate(ctx);
cleanup:
diff --git a/security/ipe/ipe-hooks.c b/security/ipe/ipe-hooks.c
index 071c4af23a3d..45efe022be04 100644
--- a/security/ipe/ipe-hooks.c
+++ b/security/ipe/ipe-hooks.c
@@ -6,6 +6,7 @@
#include "ipe.h"
#include "ipe-hooks.h"
#include "ipe-engine.h"
+#include "ipe-pin.h"
#include <linux/types.h>
#include <linux/fs.h>
@@ -147,3 +148,21 @@ int ipe_on_kernel_load_data(enum kernel_load_data_id id)
ipe_hook_kernel_load);
}
}
+
+/**
+ * ipe_sb_free_security: LSM hook called on sb_free_security.
+ * @mnt_sb: Super block that is being freed.
+ *
+ * IPE does not currently utilize the super block security hook,
+ * it utilizes this hook to invalidate the saved super block for
+ * the boot_verified property.
+ *
+ * For more information, see the LSM hook, sb_free_security.
+ *
+ * Return:
+ * 0 - OK
+ */
+void ipe_sb_free_security(struct super_block *mnt_sb)
+{
+ ipe_invalidate_pinned_sb(mnt_sb);
+}
diff --git a/security/ipe/ipe-hooks.h b/security/ipe/ipe-hooks.h
index 806659b7cdbe..5e46726f2562 100644
--- a/security/ipe/ipe-hooks.h
+++ b/security/ipe/ipe-hooks.h
@@ -58,4 +58,6 @@ int ipe_on_kernel_read(struct file *file, enum kernel_read_file_id id);
int ipe_on_kernel_load_data(enum kernel_load_data_id id);
+void ipe_sb_free_security(struct super_block *mnt_sb);
+
#endif /* IPE_HOOK_H */
diff --git a/security/ipe/ipe-pin.c b/security/ipe/ipe-pin.c
new file mode 100644
index 000000000000..a963be8e5321
--- /dev/null
+++ b/security/ipe/ipe-pin.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file has been heavily adapted from the source code of the
+ * loadpin LSM. The source code for loadpin is co-located in the linux
+ * tree under security/loadpin/loadpin.c.
+ *
+ * Please see loadpin.c for up-to-date information about
+ * loadpin.
+ */
+
+#include "ipe.h"
+
+#include <linux/types.h>
+#include <linux/spinlock_types.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/magic.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+
+static DEFINE_SPINLOCK(pinned_sb_spinlock);
+
+static struct super_block *pinned_sb;
+
+/**
+ * ipe_is_from_pinned_sb: Determine if @file originates from the initial
+ * super block that a file was executed from.
+ * @file: File to check if it originates from the super block.
+ *
+ * Return:
+ * true - File originates from the initial super block
+ * false - File does not originate from the initial super block
+ */
+bool ipe_is_from_pinned_sb(const struct file *file)
+{
+ bool rv = false;
+
+ spin_lock(&pinned_sb_spinlock);
+
+ /*
+ * Check if pinned_sb is set:
+ * NULL == not set -> exit
+ * ERR == was once set (and has been unmounted) -> exit
+ * AND check that the pinned sb is the same as the file's.
+ */
+ if (!IS_ERR_OR_NULL(pinned_sb) &&
+ file->f_path.mnt->mnt_sb == pinned_sb) {
+ rv = true;
+ goto cleanup;
+ }
+
+cleanup:
+ spin_unlock(&pinned_sb_spinlock);
+ return rv;
+}
+
+/**
+ * ipe_pin_superblock: Attempt to save a file's super block address to later
+ * determine if a file originates from a super block.
+ * @file: File to source the super block from.
+ */
+void ipe_pin_superblock(const struct file *file)
+{
+ spin_lock(&pinned_sb_spinlock);
+
+ /* if set, return */
+ if (pinned_sb || !file)
+ goto cleanup;
+
+ pinned_sb = file->f_path.mnt->mnt_sb;
+cleanup:
+ spin_unlock(&pinned_sb_spinlock);
+}
+
+/**
+ * ipe_invalidate_pinned_sb: Invalidate the saved super block.
+ * @mnt_sb: Super block to compare against the saved super block.
+ *
+ * This avoids authorizing a file when the super block does not exist anymore.
+ */
+void ipe_invalidate_pinned_sb(const struct super_block *mnt_sb)
+{
+ spin_lock(&pinned_sb_spinlock);
+
+ /*
+ * On pinned sb unload - invalidate the pinned address
+ * by setting the pinned_sb to ERR_PTR(-EIO)
+ */
+ if (!IS_ERR_OR_NULL(pinned_sb) && mnt_sb == pinned_sb)
+ pinned_sb = ERR_PTR(-EIO);
+
+ spin_unlock(&pinned_sb_spinlock);
+}
diff --git a/security/ipe/ipe-pin.h b/security/ipe/ipe-pin.h
new file mode 100644
index 000000000000..b707e6253c33
--- /dev/null
+++ b/security/ipe/ipe-pin.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+#ifndef IPE_PIN_H
+#define IPE_PIN_H
+
+#include <linux/types.h>
+#include <linux/fs.h>
+
+#ifdef CONFIG_IPE_BOOT_PROP
+
+bool ipe_is_from_pinned_sb(const struct file *file);
+
+void ipe_pin_superblock(const struct file *file);
+
+void ipe_invalidate_pinned_sb(const struct super_block *mnt_sb);
+
+#else /* CONFIG_IPE_BOOT_PROP */
+
+static inline bool ipe_is_from_pinned_sb(const struct file *file)
+{
+ return false;
+}
+
+static inline void ipe_pin_superblock(const struct file *file)
+{
+}
+
+static inline void ipe_invalidate_pinned_sb(const struct super_block *mnt_sb)
+{
+}
+
+#endif /* !CONFIG_IPE_BOOT_PROP */
+
+#endif /* IPE_PIN_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 6e3b9a10813c..706ff38083c6 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -6,6 +6,7 @@
#include "ipe.h"
#include "ipe-policy.h"
#include "ipe-hooks.h"
+#include "properties/prop-entry.h"
#include <linux/module.h>
#include <linux/lsm_hooks.h>
@@ -21,8 +22,27 @@ static struct security_hook_list ipe_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(kernel_read_file, ipe_on_kernel_read),
LSM_HOOK_INIT(kernel_load_data, ipe_on_kernel_load_data),
LSM_HOOK_INIT(file_mprotect, ipe_on_mprotect),
+ LSM_HOOK_INIT(sb_free_security, ipe_sb_free_security),
};
+/**
+ * ipe_load_properties: Call the property entry points for all the IPE modules
+ * that were selected at kernel build-time.
+ *
+ * Return:
+ * 0 - OK
+ */
+static int __init ipe_load_properties(void)
+{
+ int rc = 0;
+
+ rc = ipe_init_bootv();
+ if (rc != 0)
+ return rc;
+
+ return rc;
+}
+
/**
* ipe_init: Entry point of IPE.
*
@@ -38,12 +58,18 @@ static struct security_hook_list ipe_hooks[] __lsm_ro_after_init = {
*/
static int __init ipe_init(void)
{
+ int rc;
+
+ rc = ipe_load_properties();
+ if (rc != 0)
+ panic("IPE: properties failed to load");
+
pr_info("mode=%s", (ipe_enforce == 1) ? IPE_MODE_ENFORCE :
IPE_MODE_PERMISSIVE);
security_add_hooks(ipe_hooks, ARRAY_SIZE(ipe_hooks), "IPE");
- return 0;
+ return rc;
}
DEFINE_LSM(ipe) = {
diff --git a/security/ipe/properties/Kconfig b/security/ipe/properties/Kconfig
new file mode 100644
index 000000000000..75c6c6ff6cd8
--- /dev/null
+++ b/security/ipe/properties/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Integrity Policy Enforcement (IPE) configuration
+#
+
+config IPE_BOOT_PROP
+ bool "Enable trust for boot volume"
+ help
+ This option enables the property "boot_verified" in IPE policy.
+ This property 'pins' the initial superblock when something is
+ evaluated as an execution. This property will evaluate to true
+ when the file being evaluated originates from the initial
+ superblock.
+
+ if unsure, answer N.
diff --git a/security/ipe/properties/Makefile b/security/ipe/properties/Makefile
new file mode 100644
index 000000000000..e3e7fe17cf58
--- /dev/null
+++ b/security/ipe/properties/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) Microsoft Corporation. All rights reserved.
+#
+# Makefile for building the properties that IPE uses
+# as part of the kernel tree.
+#
+
+obj-$(CONFIG_SECURITY_IPE) += properties.o
+
+properties-$(CONFIG_IPE_BOOT_PROP) += boot-verified.o
diff --git a/security/ipe/properties/boot-verified.c b/security/ipe/properties/boot-verified.c
new file mode 100644
index 000000000000..eb9e6ebe34fa
--- /dev/null
+++ b/security/ipe/properties/boot-verified.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "../ipe.h"
+#include "../ipe-pin.h"
+#include "../ipe-property.h"
+#include "../utility.h"
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/audit.h>
+
+#define PROPERTY_NAME "boot_verified"
+
+static void audit(struct audit_buffer *ab, bool value)
+{
+ audit_log_format(ab, "%s", (value) ? "TRUE" : "FALSE");
+}
+
+static inline void audit_rule_value(struct audit_buffer *ab,
+ const void *value)
+{
+ audit(ab, (bool)value);
+}
+
+static inline void audit_ctx(struct audit_buffer *ab,
+ const struct ipe_engine_ctx *ctx)
+{
+ bool b = has_sb(ctx->file) && ipe_is_from_pinned_sb(ctx->file);
+
+ audit(ab, b);
+}
+
+static bool evaluate(const struct ipe_engine_ctx *ctx,
+ const void *value)
+{
+ bool expect = (bool)value;
+
+ if (!ctx->file || !has_sb(ctx->file))
+ return false;
+
+ return ipe_is_from_pinned_sb(ctx->file) == expect;
+}
+
+static int parse(const char *val_str, void **value)
+{
+ if (strcmp("TRUE", val_str) == 0)
+ *value = (void *)true;
+ else if (strcmp("FALSE", val_str) == 0)
+ *value = (void *)false;
+ else
+ return -EBADMSG;
+
+ return 0;
+}
+
+static inline int duplicate(const void *src, void **dest)
+{
+ *dest = (void *)(bool)src;
+
+ return 0;
+}
+
+static const struct ipe_property boot_verified = {
+ .property_name = PROPERTY_NAME,
+ .version = 1,
+ .eval = evaluate,
+ .rule_audit = audit_rule_value,
+ .ctx_audit = audit_ctx,
+ .parse = parse,
+ .dup = duplicate,
+ .free_val = NULL,
+};
+
+int ipe_init_bootv(void)
+{
+ return ipe_register_property(&boot_verified);
+}
diff --git a/security/ipe/properties/prop-entry.h b/security/ipe/properties/prop-entry.h
new file mode 100644
index 000000000000..f598dd9608b9
--- /dev/null
+++ b/security/ipe/properties/prop-entry.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include <linux/types.h>
+
+#ifndef IPE_PROP_ENTRY_H
+#define IPE_PROP_ENTRY_H
+
+#ifndef CONFIG_IPE_BOOT_PROP
+static inline int __init ipe_init_bootv(void)
+{
+ return 0;
+}
+#else
+int __init ipe_init_bootv(void);
+#endif /* CONFIG_IPE_BOOT_PROP */
+
+#endif /* IPE_PROP_ENTRY_H */
diff --git a/security/ipe/utility.h b/security/ipe/utility.h
new file mode 100644
index 000000000000..a13089bb0d8f
--- /dev/null
+++ b/security/ipe/utility.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#ifndef IPE_UTILITY_H
+#define IPE_UTILITY_H
+
+#include <linux/types.h>
+#include <linux/fs.h>
+
+static inline bool has_mount(const struct file *file)
+{
+ return file && file->f_path.mnt;
+}
+
+static inline bool has_sb(const struct file *file)
+{
+ return has_mount(file) && file->f_path.mnt->mnt_sb;
+}
+
+#endif /* IPE_UTILITY_H */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 07/11] dm-verity: add bdev_setsecurity hook for dm-verity signature
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add a security hook call to set a security property of a block_device
in dm-verity with the results of a verified, signed root-hash.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
drivers/md/dm-verity-verify-sig.c | 7 +++++++
include/linux/device-mapper.h | 2 ++
2 files changed, 9 insertions(+)
diff --git a/drivers/md/dm-verity-verify-sig.c b/drivers/md/dm-verity-verify-sig.c
index 27dac8aa2e5a..242e2421d3c8 100644
--- a/drivers/md/dm-verity-verify-sig.c
+++ b/drivers/md/dm-verity-verify-sig.c
@@ -8,7 +8,10 @@
#include <linux/device-mapper.h>
#include <linux/verification.h>
#include <keys/user-type.h>
+#include <linux/security.h>
+#include <linux/list.h>
#include <linux/module.h>
+#include "dm-core.h"
#include "dm-verity.h"
#include "dm-verity-verify-sig.h"
@@ -182,6 +185,10 @@ int verity_verify_root_hash(const struct dm_verity *v)
goto cleanup;
sig_target->passed = true;
+
+ ret = security_bdev_setsecurity(dm_table_get_md(v->ti->table)->bdev,
+ DM_VERITY_SIGNATURE_SEC_NAME,
+ v->sig->sig, v->sig->sig_size);
cleanup:
kfree(root_hash);
return ret;
diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h
index 8750f2dc5613..02be0be21d38 100644
--- a/include/linux/device-mapper.h
+++ b/include/linux/device-mapper.h
@@ -624,4 +624,6 @@ static inline unsigned long to_bytes(sector_t n)
return (n << SECTOR_SHIFT);
}
+#define DM_VERITY_SIGNATURE_SEC_NAME DM_NAME ".verity-sig"
+
#endif /* _LINUX_DEVICE_MAPPER_H */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 05/11] fs: add security blob and hooks for block_device
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add a security blob and associated allocation, deallocation and set hooks
for a block_device structure.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
fs/block_dev.c | 8 ++++
include/linux/fs.h | 1 +
include/linux/lsm_hook_defs.h | 5 +++
include/linux/lsm_hooks.h | 12 ++++++
include/linux/security.h | 22 +++++++++++
security/security.c | 74 +++++++++++++++++++++++++++++++++++
6 files changed, 122 insertions(+)
diff --git a/fs/block_dev.c b/fs/block_dev.c
index 0ae656e022fd..8602dd62c3e2 100644
--- a/fs/block_dev.c
+++ b/fs/block_dev.c
@@ -34,6 +34,7 @@
#include <linux/falloc.h>
#include <linux/uaccess.h>
#include <linux/suspend.h>
+#include <linux/security.h>
#include "internal.h"
struct bdev_inode {
@@ -768,11 +769,18 @@ static struct inode *bdev_alloc_inode(struct super_block *sb)
struct bdev_inode *ei = kmem_cache_alloc(bdev_cachep, GFP_KERNEL);
if (!ei)
return NULL;
+
+ if (unlikely(security_bdev_alloc(&ei->bdev))) {
+ kmem_cache_free(bdev_cachep, ei);
+ return NULL;
+ }
+
return &ei->vfs_inode;
}
static void bdev_free_inode(struct inode *inode)
{
+ security_bdev_free(&BDEV_I(inode)->bdev);
kmem_cache_free(bdev_cachep, BDEV_I(inode));
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f5abba86107d..42d7e3ce7712 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -509,6 +509,7 @@ struct block_device {
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
+ void *security;
} __randomize_layout;
/* XArray tags, for tagging dirty and writeback pages in the pagecache. */
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index af998f93d256..f3c0da0db4e8 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -391,3 +391,8 @@ LSM_HOOK(void, LSM_RET_VOID, perf_event_free, struct perf_event *event)
LSM_HOOK(int, 0, perf_event_read, struct perf_event *event)
LSM_HOOK(int, 0, perf_event_write, struct perf_event *event)
#endif /* CONFIG_PERF_EVENTS */
+
+LSM_HOOK(int, 0, bdev_alloc_security, struct block_device *bdev)
+LSM_HOOK(void, LSM_RET_VOID, bdev_free_security, struct block_device *bdev)
+LSM_HOOK(int, 0, bdev_setsecurity, struct block_device *bdev, const char *name,
+ const void *value, size_t size)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 95b7c1d32062..8670c19a8cef 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1507,6 +1507,17 @@
*
* @what: kernel feature being accessed
*
+ * @bdev_alloc_security:
+ * Initialize the security field inside a block_device structure.
+ *
+ * @bdev_free_security:
+ * Cleanup the security information stored inside a block_device structure.
+ *
+ * @bdev_setsecurity:
+ * Set a security property associated with @name for @bdev with
+ * value @value. @size indicates the size of @value in bytes.
+ * If a @name is not implemented, return -ENOSYS.
+ *
* Security hooks for perf events
*
* @perf_event_open:
@@ -1553,6 +1564,7 @@ struct lsm_blob_sizes {
int lbs_ipc;
int lbs_msg_msg;
int lbs_task;
+ int lbs_bdev;
};
/*
diff --git a/include/linux/security.h b/include/linux/security.h
index 0a0a03b36a3b..8f83fdc6c65d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -451,6 +451,11 @@ int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
int security_locked_down(enum lockdown_reason what);
+int security_bdev_alloc(struct block_device *bdev);
+void security_bdev_free(struct block_device *bdev);
+int security_bdev_setsecurity(struct block_device *bdev,
+ const char *name, const void *value,
+ size_t size);
#else /* CONFIG_SECURITY */
static inline int call_blocking_lsm_notifier(enum lsm_event event, void *data)
@@ -1291,6 +1296,23 @@ static inline int security_locked_down(enum lockdown_reason what)
{
return 0;
}
+
+static inline int security_bdev_alloc(struct block_device *bdev)
+{
+ return 0;
+}
+
+static inline void security_bdev_free(struct block_device *bdev)
+{
+}
+
+static inline int security_bdev_setsecurity(struct block_device *bdev,
+ const char *name,
+ const void *value, size_t size)
+{
+ return 0;
+}
+
#endif /* CONFIG_SECURITY */
#if defined(CONFIG_SECURITY) && defined(CONFIG_WATCH_QUEUE)
diff --git a/security/security.c b/security/security.c
index 70a7ad357bc6..fff445eba400 100644
--- a/security/security.c
+++ b/security/security.c
@@ -28,6 +28,7 @@
#include <linux/string.h>
#include <linux/msg.h>
#include <net/flow.h>
+#include <linux/fs.h>
#define MAX_LSM_EVM_XATTR 2
@@ -202,6 +203,7 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc);
lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task);
+ lsm_set_blob_size(&needed->lbs_bdev, &blob_sizes.lbs_bdev);
}
/* Prepare LSM for initialization. */
@@ -337,6 +339,7 @@ static void __init ordered_lsm_init(void)
init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
init_debug("task blob size = %d\n", blob_sizes.lbs_task);
+ init_debug("bdev blob size = %d\n", blob_sizes.lbs_bdev);
/*
* Create any kmem_caches needed for blobs
@@ -654,6 +657,28 @@ static int lsm_msg_msg_alloc(struct msg_msg *mp)
return 0;
}
+/**
+ * lsm_bdev_alloc - allocate a composite block_device blob
+ * @bdev: the block_device that needs a blob
+ *
+ * Allocate the block_device blob for all the modules
+ *
+ * Returns 0, or -ENOMEM if memory can't be allocated.
+ */
+static int lsm_bdev_alloc(struct block_device *bdev)
+{
+ if (blob_sizes.lbs_bdev == 0) {
+ bdev->security = NULL;
+ return 0;
+ }
+
+ bdev->security = kzalloc(blob_sizes.lbs_bdev, GFP_KERNEL);
+ if (!bdev->security)
+ return -ENOMEM;
+
+ return 0;
+}
+
/**
* lsm_early_task - during initialization allocate a composite task blob
* @task: the task that needs a blob
@@ -2516,6 +2541,55 @@ int security_locked_down(enum lockdown_reason what)
}
EXPORT_SYMBOL(security_locked_down);
+int security_bdev_alloc(struct block_device *bdev)
+{
+ int rc = 0;
+
+ rc = lsm_bdev_alloc(bdev);
+ if (unlikely(rc))
+ return rc;
+
+ rc = call_int_hook(bdev_alloc_security, 0, bdev);
+ if (unlikely(rc))
+ security_bdev_free(bdev);
+
+ return 0;
+}
+EXPORT_SYMBOL(security_bdev_alloc);
+
+void security_bdev_free(struct block_device *bdev)
+{
+ if (!bdev->security)
+ return;
+
+ call_void_hook(bdev_free_security, bdev);
+
+ kfree(bdev->security);
+ bdev->security = NULL;
+}
+EXPORT_SYMBOL(security_bdev_free);
+
+int security_bdev_setsecurity(struct block_device *bdev,
+ const char *name, const void *value,
+ size_t size)
+{
+ int rc = 0;
+ struct security_hook_list *p;
+
+ hlist_for_each_entry(p, &security_hook_heads.bdev_setsecurity, list) {
+ rc = p->hook.bdev_setsecurity(bdev, name, value, size);
+
+ if (rc == -ENOSYS)
+ rc = 0;
+
+ if (rc != 0)
+ break;
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL(security_bdev_setsecurity);
+
#ifdef CONFIG_PERF_EVENTS
int security_perf_event_open(struct perf_event_attr *attr, int type)
{
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 11/11] cleanup: uapi/linux/audit.h
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Remove trailing whitespaces and align the integrity #defines in
linux/uapi/audit.h
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
include/uapi/linux/audit.h | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 5a634cca1d42..609b4a5e8a80 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -48,7 +48,7 @@
* 2500 - 2999 future user space (maybe integrity labels and related events)
*
* Messages from 1000-1199 are bi-directional. 1200-1299 & 2100 - 2999 are
- * exclusively user space. 1300-2099 is kernel --> user space
+ * exclusively user space. 1300-2099 is kernel --> user space
* communication.
*/
#define AUDIT_GET 1000 /* Get status */
@@ -78,7 +78,7 @@
#define AUDIT_LAST_USER_MSG 1199
#define AUDIT_FIRST_USER_MSG2 2100 /* More user space messages */
#define AUDIT_LAST_USER_MSG2 2999
-
+
#define AUDIT_DAEMON_START 1200 /* Daemon startup record */
#define AUDIT_DAEMON_END 1201 /* Daemon normal stop record */
#define AUDIT_DAEMON_ABORT 1202 /* Daemon error stop record */
@@ -140,20 +140,20 @@
#define AUDIT_MAC_CALIPSO_ADD 1418 /* NetLabel: add CALIPSO DOI entry */
#define AUDIT_MAC_CALIPSO_DEL 1419 /* NetLabel: del CALIPSO DOI entry */
-#define AUDIT_FIRST_KERN_ANOM_MSG 1700
-#define AUDIT_LAST_KERN_ANOM_MSG 1799
-#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */
-#define AUDIT_ANOM_ABEND 1701 /* Process ended abnormally */
-#define AUDIT_ANOM_LINK 1702 /* Suspicious use of file links */
-#define AUDIT_ANOM_CREAT 1703 /* Suspicious file creation */
-#define AUDIT_INTEGRITY_DATA 1800 /* Data integrity verification */
-#define AUDIT_INTEGRITY_METADATA 1801 /* Metadata integrity verification */
-#define AUDIT_INTEGRITY_STATUS 1802 /* Integrity enable status */
-#define AUDIT_INTEGRITY_HASH 1803 /* Integrity HASH type */
-#define AUDIT_INTEGRITY_PCR 1804 /* PCR invalidation msgs */
-#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */
-#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */
-#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */
+#define AUDIT_FIRST_KERN_ANOM_MSG 1700
+#define AUDIT_LAST_KERN_ANOM_MSG 1799
+#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */
+#define AUDIT_ANOM_ABEND 1701 /* Process ended abnormally */
+#define AUDIT_ANOM_LINK 1702 /* Suspicious use of file links */
+#define AUDIT_ANOM_CREAT 1703 /* Suspicious file creation */
+#define AUDIT_INTEGRITY_DATA 1800 /* Data integrity verification */
+#define AUDIT_INTEGRITY_METADATA 1801 /* Metadata integrity verification */
+#define AUDIT_INTEGRITY_STATUS 1802 /* Integrity enable status */
+#define AUDIT_INTEGRITY_HASH 1803 /* Integrity HASH type */
+#define AUDIT_INTEGRITY_PCR 1804 /* PCR invalidation msgs */
+#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */
+#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */
+#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */
#define AUDIT_INTEGRITY_POLICY_LOAD 1808 /* IPE Policy Load */
#define AUDIT_INTEGRITY_POLICY_ACTIVATE 1809 /* IPE Policy Activation */
#define AUDIT_INTEGRITY_EVENT 1810 /* IPE Evaluation Event */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 00/11] Integrity Policy Enforcement LSM (IPE)
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
Overview:
------------------------------------
IPE is a Linux Security Module which allows for a configurable
policy to enforce integrity requirements on the whole system. It
attempts to solve the issue of Code Integrity: that any code being
executed (or files being read), are identical to the version that
was built by a trusted source.
The type of system for which IPE is designed for use is an embedded device
with a specific purpose (e.g. network firewall device in a data center),
where all software and configuration is built and provisioned by the owner.
Specifically, a system which leverages IPE is not intended for general
purpose computing and does not utilize any software or configuration
built by a third party. An ideal system to leverage IPE has both mutable
and immutable components, however, all binary executable code is immutable.
The scope of IPE is constrained to the OS. It is assumed that platform
firmware verifies the the kernel and optionally the root filesystem (e.g.
via U-Boot verified boot). IPE then utilizes LSM hooks to enforce a
flexible, kernel-resident integrity verification policy.
IPE differs from other LSMs which provide integrity checking (for instance,
IMA), as it has no dependency on the filesystem metadata itself. The
attributes that IPE checks are deterministic properties that exist solely
in the kernel. Additionally, IPE provides no additional mechanisms of
verifying these files (e.g. IMA Signatures) - all of the attributes of
verifying files are existing features within the kernel, such as dm-verity
or fsverity.
IPE provides a policy that allows owners of the system to easily specify
integrity requirements and uses dm-verity signatures to simplify the
authentication of allowed objects like authorized code and data.
IPE supports two modes, permissive (similar to SELinux's permissive mode)
and enforce. Permissive mode performs the same checks, and logs policy
violations as enforce mode, but will not enforce the policy. This allows
users to test policies before enforcing them.
The default mode is enforce, and can be changed via the kernel commandline
parameter `ipe.enforce=(0|1)`, or the securityfs node
`/sys/kernel/security/ipe/enforce`. The ability to switch modes can be
compiled out of the LSM via setting the config
CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH to N.
IPE additionally supports success auditing. When enabled, all events
that pass IPE policy and are not blocked will emit an audit event. This
is disabled by default, and can be enabled via the kernel commandline
`ipe.success_audit=(0|1)` or the securityfs node
`/sys/kernel/security/ipe/success_audit`.
Policies can be staged at runtime through securityfs and activated through
sysfs. Please see the Deploying Policies section of this cover letter for
more information.
The IPE LSM is compiled under CONFIG_SECURITY_IPE.
Policy:
------------------------------------
IPE policy is designed to be both forward compatible and backwards
compatible. There is one required line, at the top of the policy,
indicating the policy name, and the policy version, for instance:
policy_name="Ex Policy" policy_version=0.0.0
The policy version indicates the current version of the policy (NOT the
policy syntax version). This is used to prevent roll-back of policy to
potentially insecure previous versions of the policy.
The next portion of IPE policy, are rules. Rules are formed by key=value
pairs, known as properties. IPE rules require two properties: "action",
which determines what IPE does when it encounters a match against the
policy, and "op", which determines when that rule should be evaluated.
Thus, a minimal rule is:
op=EXECUTE action=ALLOW
This example will allow any execution. Additional properties are used to
restrict attributes about the files being evaluated. These properties are
intended to be deterministic attributes that are resident in the kernel.
Available properties for IPE described in the properties section of this
cover-letter, the repository available in Appendix A, and the kernel
documentation page.
Order does not matter for the rule's properties - they can be listed in
any order, however it is encouraged to have the "op" property be first,
and the "action" property be last, for readability.
Additionally, rules are evaluated top-to-bottom. As a result, any
revocation rules, or denies should be placed early in the file to ensure
that these rules are evaluated before a rule with "action=ALLOW" is hit.
Any unknown syntax in IPE policy will result in a fatal error to parse
the policy. User mode can interrogate the kernel to understand what
properties and the associated versions through the securityfs node,
$securityfs/ipe/property_config, which will return a string of form:
key1=version1
key2=version2
.
.
.
keyN=versionN
User-mode should correlate these versions with the supported values
identified in the documentation to determine whether a policy should
be accepted by the system.
Additionally, a DEFAULT operation must be set for all understood
operations within IPE. For policies to remain completely forwards
compatible, it is recommended that users add a "DEFAULT action=ALLOW"
and override the defaults on a per-operation basis.
For more information about the policy syntax, please see Appendix A or
the kernel documentation page.
Early Usermode Protection:
--------------------------
IPE can be provided with a policy at startup to load and enforce.
This is intended to be a minimal policy to get the system to a state
where userland is setup and ready to receive commands, at which
point a policy can be deployed via securityfs. This "boot policy" can be
specified via the config, SECURITY_IPE_BOOT_POLICY, which accepts a path
to a plain-text version of the IPE policy to apply. This policy will be
compiled into the kernel. If not specified, IPE will be disabled until a
policy is deployed and activated through the method above.
Policy Examples:
------------------------------------
Allow all:
policy_name="Allow All" policy_version=0.0.0
DEFAULT action=ALLOW
Allow only initial superblock:
policy_name="Allow All Initial SB" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
Allow any signed dm-verity volume and the initial superblock:
policy_name="AllowSignedAndInitial" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_signature=TRUE action=ALLOW
Prohibit execution from a specific dm-verity volume:
policy_name="AllowSignedAndInitial" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_signature=TRUE action=ALLOW
Allow only a specific dm-verity volume:
policy_name="AllowSignedAndInitial" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=ALLOW
Deploying Policies:
-------------------
Deploying policies is simple. First sign a plain text policy, with a
certificate that is present in the SYSTEM_TRUSTED_KEYRING of your test
machine. Through openssl, the signing can be done via:
openssl smime -sign -in "$MY_POLICY" -signer "$MY_CERTIFICATE" \
-inkey "$MY_PRIVATE_KEY" -binary -outform der -noattr -nodetach \
-out "$MY_POLICY.p7s"
Then, simply cat the file into the IPE's "new_policy" securityfs node:
cat "$MY_POLICY.p7s" > /sys/kernel/security/ipe/new_policy
The policy should now be present under the policies/ subdirectory, under
its "policy_name" attribute.
The policy is now present in the kernel and can be marked as active,
via the sysctl "ipe.active_policy":
echo -n 1 > "/sys/kernel/security/ipe/$MY_POLICY_NAME/active"
This will now mark the policy as active and the system will be enforcing
$MY_POLICY_NAME. At any point the policy can be updated on the provision
that the policy version to be deployed is greater than or equal to the
running version (to prevent roll-back attacks). This update can be done
by redirecting the file into the policy's "raw" node, under the policies
subdirectory:
cat "$MY_UPDATED_POLICY.p7s" > \
"/sys/kernel/security/ipe/policies/$MY_POLICY_NAME/raw"
Additionally, policies can be deleted via the "del_policy" securityfs
node. Simply write the name of the policy to be deleted to that node:
echo -n 1 >
"/sys/kernel/security/ipe/policies/$MY_POLICY_NAME/delete"
There are two requirements to delete policies:
1. The policy being deleted must not be the active policy.
2. The policy being deleted must not be the boot policy.
It's important to know above that the "echo" command will add a newline
to the end of the input, and this will be considered as part of the
filename. You can remove the newline via the -n parameter.
NOTE: If a MAC LSM is enabled, the securityfs commands will require
CAP_MAC_ADMIN. This is due to sysfs supporting fine-grained MAC
attributes, while securityfs at the current moment does not.
Properties:
------------------------------------
This initial patchset introducing IPE adds three properties:
'boot_verified', 'dmverity_signature' and 'dmverity_roothash'.
boot_verified (CONFIG_IPE_BOOT_PROP):
This property can be utilized for authorization of the first
super-block that is mounted on the system, where IPE attempts
to evaluate a file. Typically this is used for systems with
an initramfs or other initial disk, where this is unmounted before
the system becomes available, and is not covered by any other property.
The format of this property is:
boot_verified=(TRUE|FALSE)
WARNING: This property will trust any disk where the first IPE
evaluation occurs. If you do not have a startup disk that is
unpacked and unmounted (like initramfs), then it will automatically
trust the root filesystem and potentially overauthorize the entire
disk.
dmverity_roothash (CONFIG_IPE_DM_VERITY_ROOTHASH):
This property can be utilized for authorization or revocation of
specific dmverity volumes, identified via root hash. It has a
dependency on the DM_VERITY module. The format of this property is:
dmverity_roothash=<HashHexDigest>
dmverity_signature (CONFIG_IPE_DM_VERITY_SIGNATURE):
This property can be utilized for authorization of all dm-verity
volumes that have a signed roothash that chains to the system
trusted keyring. It has a dependency on the
DM_VERITY_VERIFY_ROOTHASH_SIG config. The format of this property is:
dmverity_signature=(TRUE|FALSE)
Testing:
------------------------------------
A test suite is available (Appendix B) for ease of use. For manual
instructions:
Enable IPE through the following Kconfigs:
CONFIG_SECURITY_IPE=y
CONFIG_SECURITY_IPE_BOOT_POLICY="../AllowAllInitialSB.pol"
CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH=y
CONFIG_IPE_BOOT_PROP=y
CONFIG_IPE_DM_VERITY_ROOTHASH=y
CONFIG_IPE_DM_VERITY_SIGNATURE=y
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
CONFIG_SYSTEM_TRUSTED_KEYRING=y
CONFIG_SYSTEM_TRUSTED_KEYS="/path/to/my/cert/list.pem"
Start a test system, that boots directly from the filesystem, without
an initrd. I recommend testing in permissive mode until all tests
pass, then switch to enforce to ensure behavior remains identical.
boot_verified:
If booted correctly, the filesystem mounted on / should be marked as
boot_verified. Verify by turning on success auditing (sysctl
ipe.success_audit=1), and run a binary. In the audit output,
`prop_boot_verified` should be `TRUE`.
To test denials, mount a temporary filesystem (mount -t tmpfs -o
size=4M tmp tmp), and copy a binary (e.g. ls) to this new
filesystem. Disable success auditing and attempt to run the file.
The file should have an audit event, but be allowed to execute in
permissive mode, and prop_boot_verified should be FALSE.
dmverity_roothash:
First, you must create a dm-verity volume. This can be done through
squashfs-tools and veritysetup (provided by cryptsetup).
Creating a squashfs volume:
mksquashfs /path/to/directory/with/executable /path/to/output.squashfs
Format the volume for use with dm-verity & save the root hash:
output_rh=$(veritysetup format output.squashfs output.hashtree | \
tee verity_out.txt | awk "/Root hash/" | \
sed -E "s/Root hash:\s+//g")
echo -n $output_rh > output.roothash
Create a two policies, filling in the appropriate fields below:
Policy 1:
policy_name="roothash-denial" policy_version=0.0.0
DEFAULT action=ALLOW
op=EXECUTE dmverity_roothash=$output_rh action=DENY
Policy 2:
policy_name="roothash-allow" policy_version=0.0.0
DEFAULT action=ALLOW
DEFAULT op=EXECUTE action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_roothash=$output_rh action=ALLOW
Deploy each policy, then mark the first, "roothash-denial" as active,
per the "Deploying Policies" section of this cover letter. Mount the
dm-verity volume:
veritysetup open output.squashfs output.hashtree unverified \
`cat output.roothash`
mount /dev/mapper/unverified /my/mount/point
Attempt to execute a binary in the mount point, and it should emit an
audit event for a match against the rule:
op=EXECUTE dmverity_roothash=$output_rh action=DENY
To test the second policy, perform the same steps, but this time, enable
success auditing before running the executable. The success audit event
should be a match against this rule:
op=EXECUTE dmverity_roothash=$output_rh action=ALLOW
dmverity_signature:
Follow the setup steps for dmverity_roothash. Sign the roothash via:
openssl smime -sign -in "output.roothash" -signer "$MY_CERTIFICATE" \
-inkey "$MY_PRIVATE_KEY" -binary -outform der -noattr \
-out "output.p7s"
Create a policy:
policy_name="verified" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_verified=TRUE action=ALLOW
Deploy the policy, and mark as active, per the "Deploying Policies"
section of this cover letter. Mount the dm-verity volume with
verification:
veritysetup open output.squashfs output.hashtree unverified \
`cat output.roothash` --root-hash-signature=output.p7s
mount /dev/mapper/unverified /my/mount/point
NOTE: The --root-hash-signature option was introduced in veritysetup
2.3.0
Turn on success auditing and attempt to execute a binary in the mount
point, and it should emit an audit event for a match against the rule:
op=EXECUTE dmverity_verified=TRUE action=ALLOW
To test denials, mount the dm-verity volume the same way as the
"dmverity_roothash" section, and attempt to execute a binary. Failure
should occur.
Documentation:
------------------------------------
Full documentation is available on github in IPE's master repository
(Appendix A). This is intended to be an exhaustive source of documentation
around IPE.
Additionally, there is higher level documentation in the admin-guide.
Technical diagrams are available here:
http://microsoft.github.io/ipe/technical/diagrams/
Known Gaps:
------------------------------------
IPE has two known gaps:
1. IPE cannot verify the integrity of anonymous executable memory, such as
the trampolines created by gcc closures and libffi, or JIT'd code.
Unfortunately, as this is dynamically generated code, there is no way for
IPE to detect that this code has not been tampered with in transition
from where it was built, to where it is running. As a result, IPE is
incapable of tackling this problem for dynamically generated code.
However, there is a patch series being prepared that addresses this
problem for libffi and gcc closures by implemeting a safer kernel
trampoline API.
2. IPE cannot verify the integrity of interpreted languages' programs when
these scripts invoked via `<interpreter> <file>`. This is because the way
interpreters execute these files, the scripts themselves are not
evaluated as executable code through one of IPE's hooks. Interpreters
can be enlightened to the usage of IPE by trying to mmap a file into
executable memory (+X), after opening the file and responding to the
error code appropriately. This also applies to included files, or high
value files, such as configuration files of critical system components.
This specific gap is planned on being addressed within IPE. For more
information on how we plan to address this gap, please see the Future
Development section, below.
Future Development:
------------------------------------
Support for filtering signatures by specific certificates. In this case,
our "dmverity_signature" (or a separate property) can be set to a
specific certificate declared in IPE's policy, allowing for more
controlled use-cases determine by a user's PKI structure.
Support for integrity verification for general file reads. This addresses
the script interpreter issue indicated in the "Known Gaps" section, as
these script files are typically opened with O_RDONLY. We are evaluating
whether to do this by comparing the original userland filepath passed into
the open syscall, thereby allowing existing callers to take advantage
without any code changes; the alternate design is to extend the new
openat2(2) syscall, with an new flag, tentatively called "O_VERIFY". While
the second option requires a code change for all the interpreters,
frameworks and languages that wish to leverage it, it is a wholly cleaner
implementation in the kernel. For interpreters specifically, the O_MAYEXEC
patch series published by Mickaël Salaün[1] is a similar implementation
to the O_VERIFY idea described above.
Onboarding IPE's test suite to KernelCI. Currently we are developing a
test suite in the same vein as SELinux's test suite. Once development
of the test suite is complete, and provided IPE is accepted, we intend
to onboard this test suite onto KernelCI.
Hardened resistance against roll-back attacks. Currently there exists a
window of opportunity between user-mode setup and the user-policy being
deployed, where a prior user-policy can be loaded, that is potentially
insecure. However, with a kernel update, you can revise the boot policy's
version to be the same version as the latest policy, closing this window.
In the future, I would like to close this window of opportunity without
a kernel update, using some persistent storage mechanism.
Open Issues:
------------
For linux-audit/integrity folks:
1. Introduction of new audit definitions in the kernel integrity range - is
this preferred, as opposed to reusing definitions with existing IMA
definitions?
TODOs:
------
linux-audit changes to support the new audit events.
Appendix:
------------------------------------
A. IPE Github Repository: https://github.com/microsoft/ipe
Hosted Documentation: https://microsoft.github.io/ipe
B. IPE Users' Guide: Documentation/admin-guide/LSM/ipe.rst
C. IPE Test Suite: *TBA* (under development)
References:
------------------------------------
1. https://lore.kernel.org/linux-integrity/20200505153156.925111-1-mic@digikod.net/
Changelog:
------------------------------------
v1: Introduced
v2:
Split the second patch of the previous series into two.
Minor corrections in the cover-letter and documentation
comments regarding CAP_MAC_ADMIN checks in IPE.
v3:
Address various comments by Jann Horn. Highlights:
Switch various audit allocators to GFP_KERNEL.
Utilize rcu_access_pointer() in various locations.
Strip out the caching system for properties
Strip comments from headers
Move functions around in patches
Remove kernel command line parameters
Reconcile the race condition on the delete node for policy by
expanding the policy critical section.
Address a few comments by Jonathan Corbet around the documentation
pages for IPE.
Fix an issue with the initialization of IPE policy with a "-0"
version, caused by not initializing the hlist entries before
freeing.
v4:
Address a concern around IPE's behavior with unknown syntax.
Specifically, make any unknown syntax a fatal error instead of a
warning, as suggested by Mickaël Salaün.
Introduce a new securityfs node, $securityfs/ipe/property_config,
which provides a listing of what properties are enabled by the
kernel and their versions. This allows usermode to predict what
policies should be allowed.
Strip some comments from c files that I missed.
Clarify some documentation comments around 'boot_verified'.
While this currently does not functionally change the property
itself, the distinction is important when IPE can enforce verified
reads. Additionally, 'KERNEL_READ' was omitted from the documentation.
This has been corrected.
Change SecurityFS and SHA1 to a reverse dependency.
Update the cover-letter with the updated behavior of unknown syntax.
Remove all sysctls, making an equivalent function in securityfs.
Rework the active/delete mechanism to be a node under the policy in
$securityfs/ipe/policies.
The kernel command line parameters ipe.enforce and ipe.success_audit
have returned as this functionality is no longer exposed through
sysfs.
v5:
Correct some grammatical errors reported by Randy Dunlap.
Fix some warnings reported by kernel test bot.
Change convention around security_bdev_setsecurity. -ENOSYS
is now expected if an LSM does not implement a particular @name,
as suggested by Casey Schaufler.
Minor string corrections related to the move from sysfs to securityfs
Correct a spelling of an #ifdef for the permissive argument.
Add the kernel parameters re-added to the documentation.Integrity Policy Enforcement LSM (IPE)
Overview:
------------------------------------
IPE is a Linux Security Module which allows for a configurable
policy to enforce integrity requirements on the whole system. It
attempts to solve the issue of Code Integrity: that any code being
executed (or files being read), are identical to the version that
was built by a trusted source.
The type of system for which IPE is designed for use is an embedded device
with a specific purpose (e.g. network firewall device in a data center),
where all software and configuration is built and provisioned by the owner.
Specifically, a system which leverages IPE is not intended for general
purpose computing and does not utilize any software or configuration
built by a third party. An ideal system to leverage IPE has both mutable
and immutable components, however, all binary executable code is immutable.
The scope of IPE is constrained to the OS. It is assumed that platform
firmware verifies the the kernel and optionally the root filesystem (e.g.
via U-Boot verified boot). IPE then utilizes LSM hooks to enforce a
flexible, kernel-resident integrity verification policy.
IPE differs from other LSMs which provide integrity checking (for instance,
IMA), as it has no dependency on the filesystem metadata itself. The
attributes that IPE checks are deterministic properties that exist solely
in the kernel. Additionally, IPE provides no additional mechanisms of
verifying these files (e.g. IMA Signatures) - all of the attributes of
verifying files are existing features within the kernel, such as dm-verity
or fsverity.
IPE provides a policy that allows owners of the system to easily specify
integrity requirements and uses dm-verity signatures to simplify the
authentication of allowed objects like authorized code and data.
IPE supports two modes, permissive (similar to SELinux's permissive mode)
and enforce. Permissive mode performs the same checks, and logs policy
violations as enforce mode, but will not enforce the policy. This allows
users to test policies before enforcing them.
The default mode is enforce, and can be changed via the kernel commandline
parameter `ipe.enforce=(0|1)`, or the securityfs node
`/sys/kernel/security/ipe/enforce`. The ability to switch modes can be
compiled out of the LSM via setting the config
CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH to N.
IPE additionally supports success auditing. When enabled, all events
that pass IPE policy and are not blocked will emit an audit event. This
is disabled by default, and can be enabled via the kernel commandline
`ipe.success_audit=(0|1)` or the securityfs node
`/sys/kernel/security/ipe/success_audit`.
Policies can be staged at runtime through securityfs and activated through
sysfs. Please see the Deploying Policies section of this cover letter for
more information.
The IPE LSM is compiled under CONFIG_SECURITY_IPE.
Policy:
------------------------------------
IPE policy is designed to be both forward compatible and backwards
compatible. There is one required line, at the top of the policy,
indicating the policy name, and the policy version, for instance:
policy_name="Ex Policy" policy_version=0.0.0
The policy version indicates the current version of the policy (NOT the
policy syntax version). This is used to prevent roll-back of policy to
potentially insecure previous versions of the policy.
The next portion of IPE policy, are rules. Rules are formed by key=value
pairs, known as properties. IPE rules require two properties: "action",
which determines what IPE does when it encounters a match against the
policy, and "op", which determines when that rule should be evaluated.
Thus, a minimal rule is:
op=EXECUTE action=ALLOW
This example will allow any execution. Additional properties are used to
restrict attributes about the files being evaluated. These properties are
intended to be deterministic attributes that are resident in the kernel.
Available properties for IPE described in the properties section of this
cover-letter, the repository available in Appendix A, and the kernel
documentation page.
Order does not matter for the rule's properties - they can be listed in
any order, however it is encouraged to have the "op" property be first,
and the "action" property be last, for readability.
Additionally, rules are evaluated top-to-bottom. As a result, any
revocation rules, or denies should be placed early in the file to ensure
that these rules are evaluated before a rule with "action=ALLOW" is hit.
Any unknown syntax in IPE policy will result in a fatal error to parse
the policy. User mode can interrogate the kernel to understand what
properties and the associated versions through the securityfs node,
$securityfs/ipe/property_config, which will return a string of form:
key1=version1
key2=version2
.
.
.
keyN=versionN
User-mode should correlate these versions with the supported values
identified in the documentation to determine whether a policy should
be accepted by the system.
Additionally, a DEFAULT operation must be set for all understood
operations within IPE. For policies to remain completely forwards
compatible, it is recommended that users add a "DEFAULT action=ALLOW"
and override the defaults on a per-operation basis.
For more information about the policy syntax, please see Appendix A or
the kernel documentation page.
Early Usermode Protection:
--------------------------
IPE can be provided with a policy at startup to load and enforce.
This is intended to be a minimal policy to get the system to a state
where userland is setup and ready to receive commands, at which
point a policy can be deployed via securityfs. This "boot policy" can be
specified via the config, SECURITY_IPE_BOOT_POLICY, which accepts a path
to a plain-text version of the IPE policy to apply. This policy will be
compiled into the kernel. If not specified, IPE will be disabled until a
policy is deployed and activated through the method above.
Policy Examples:
------------------------------------
Allow all:
policy_name="Allow All" policy_version=0.0.0
DEFAULT action=ALLOW
Allow only initial superblock:
policy_name="Allow All Initial SB" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
Allow any signed dm-verity volume and the initial superblock:
policy_name="AllowSignedAndInitial" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_signature=TRUE action=ALLOW
Prohibit execution from a specific dm-verity volume:
policy_name="AllowSignedAndInitial" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_signature=TRUE action=ALLOW
Allow only a specific dm-verity volume:
policy_name="AllowSignedAndInitial" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=ALLOW
Deploying Policies:
-------------------
Deploying policies is simple. First sign a plain text policy, with a
certificate that is present in the SYSTEM_TRUSTED_KEYRING of your test
machine. Through openssl, the signing can be done via:
openssl smime -sign -in "$MY_POLICY" -signer "$MY_CERTIFICATE" \
-inkey "$MY_PRIVATE_KEY" -binary -outform der -noattr -nodetach \
-out "$MY_POLICY.p7s"
Then, simply cat the file into the IPE's "new_policy" securityfs node:
cat "$MY_POLICY.p7s" > /sys/kernel/security/ipe/new_policy
The policy should now be present under the policies/ subdirectory, under
its "policy_name" attribute.
The policy is now present in the kernel and can be marked as active,
via the sysctl "ipe.active_policy":
echo -n 1 > "/sys/kernel/security/ipe/$MY_POLICY_NAME/active"
This will now mark the policy as active and the system will be enforcing
$MY_POLICY_NAME. At any point the policy can be updated on the provision
that the policy version to be deployed is greater than or equal to the
running version (to prevent roll-back attacks). This update can be done
by redirecting the file into the policy's "raw" node, under the policies
subdirectory:
cat "$MY_UPDATED_POLICY.p7s" > \
"/sys/kernel/security/ipe/policies/$MY_POLICY_NAME/raw"
Additionally, policies can be deleted via the "del_policy" securityfs
node. Simply write the name of the policy to be deleted to that node:
echo -n 1 >
"/sys/kernel/security/ipe/policies/$MY_POLICY_NAME/delete"
There are two requirements to delete policies:
1. The policy being deleted must not be the active policy.
2. The policy being deleted must not be the boot policy.
It's important to know above that the "echo" command will add a newline
to the end of the input, and this will be considered as part of the
filename. You can remove the newline via the -n parameter.
NOTE: If a MAC LSM is enabled, the securityfs commands will require
CAP_MAC_ADMIN. This is due to sysfs supporting fine-grained MAC
attributes, while securityfs at the current moment does not.
Properties:
------------------------------------
This initial patchset introducing IPE adds three properties:
'boot_verified', 'dmverity_signature' and 'dmverity_roothash'.
boot_verified (CONFIG_IPE_BOOT_PROP):
This property can be utilized for authorization of the first
super-block that is mounted on the system, where IPE attempts
to evaluate a file. Typically this is used for systems with
an initramfs or other initial disk, where this is unmounted before
the system becomes available, and is not covered by any other property.
The format of this property is:
boot_verified=(TRUE|FALSE)
WARNING: This property will trust any disk where the first IPE
evaluation occurs. If you do not have a startup disk that is
unpacked and unmounted (like initramfs), then it will automatically
trust the root filesystem and potentially overauthorize the entire
disk.
dmverity_roothash (CONFIG_IPE_DM_VERITY_ROOTHASH):
This property can be utilized for authorization or revocation of
specific dmverity volumes, identified via root hash. It has a
dependency on the DM_VERITY module. The format of this property is:
dmverity_roothash=<HashHexDigest>
dmverity_signature (CONFIG_IPE_DM_VERITY_SIGNATURE):
This property can be utilized for authorization of all dm-verity
volumes that have a signed roothash that chains to the system
trusted keyring. It has a dependency on the
DM_VERITY_VERIFY_ROOTHASH_SIG config. The format of this property is:
dmverity_signature=(TRUE|FALSE)
Testing:
------------------------------------
A test suite is available (Appendix B) for ease of use. For manual
instructions:
Enable IPE through the following Kconfigs:
CONFIG_SECURITY_IPE=y
CONFIG_SECURITY_IPE_BOOT_POLICY="../AllowAllInitialSB.pol"
CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH=y
CONFIG_IPE_BOOT_PROP=y
CONFIG_IPE_DM_VERITY_ROOTHASH=y
CONFIG_IPE_DM_VERITY_SIGNATURE=y
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
CONFIG_SYSTEM_TRUSTED_KEYRING=y
CONFIG_SYSTEM_TRUSTED_KEYS="/path/to/my/cert/list.pem"
Start a test system, that boots directly from the filesystem, without
an initrd. I recommend testing in permissive mode until all tests
pass, then switch to enforce to ensure behavior remains identical.
boot_verified:
If booted correctly, the filesystem mounted on / should be marked as
boot_verified. Verify by turning on success auditing (sysctl
ipe.success_audit=1), and run a binary. In the audit output,
`prop_boot_verified` should be `TRUE`.
To test denials, mount a temporary filesystem (mount -t tmpfs -o
size=4M tmp tmp), and copy a binary (e.g. ls) to this new
filesystem. Disable success auditing and attempt to run the file.
The file should have an audit event, but be allowed to execute in
permissive mode, and prop_boot_verified should be FALSE.
dmverity_roothash:
First, you must create a dm-verity volume. This can be done through
squashfs-tools and veritysetup (provided by cryptsetup).
Creating a squashfs volume:
mksquashfs /path/to/directory/with/executable /path/to/output.squashfs
Format the volume for use with dm-verity & save the root hash:
output_rh=$(veritysetup format output.squashfs output.hashtree | \
tee verity_out.txt | awk "/Root hash/" | \
sed -E "s/Root hash:\s+//g")
echo -n $output_rh > output.roothash
Create a two policies, filling in the appropriate fields below:
Policy 1:
policy_name="roothash-denial" policy_version=0.0.0
DEFAULT action=ALLOW
op=EXECUTE dmverity_roothash=$output_rh action=DENY
Policy 2:
policy_name="roothash-allow" policy_version=0.0.0
DEFAULT action=ALLOW
DEFAULT op=EXECUTE action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_roothash=$output_rh action=ALLOW
Deploy each policy, then mark the first, "roothash-denial" as active,
per the "Deploying Policies" section of this cover letter. Mount the
dm-verity volume:
veritysetup open output.squashfs output.hashtree unverified \
`cat output.roothash`
mount /dev/mapper/unverified /my/mount/point
Attempt to execute a binary in the mount point, and it should emit an
audit event for a match against the rule:
op=EXECUTE dmverity_roothash=$output_rh action=DENY
To test the second policy, perform the same steps, but this time, enable
success auditing before running the executable. The success audit event
should be a match against this rule:
op=EXECUTE dmverity_roothash=$output_rh action=ALLOW
dmverity_signature:
Follow the setup steps for dmverity_roothash. Sign the roothash via:
openssl smime -sign -in "output.roothash" -signer "$MY_CERTIFICATE" \
-inkey "$MY_PRIVATE_KEY" -binary -outform der -noattr \
-out "output.p7s"
Create a policy:
policy_name="verified" policy_version=0.0.0
DEFAULT action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE dmverity_verified=TRUE action=ALLOW
Deploy the policy, and mark as active, per the "Deploying Policies"
section of this cover letter. Mount the dm-verity volume with
verification:
veritysetup open output.squashfs output.hashtree unverified \
`cat output.roothash` --root-hash-signature=output.p7s
mount /dev/mapper/unverified /my/mount/point
NOTE: The --root-hash-signature option was introduced in veritysetup
2.3.0
Turn on success auditing and attempt to execute a binary in the mount
point, and it should emit an audit event for a match against the rule:
op=EXECUTE dmverity_verified=TRUE action=ALLOW
To test denials, mount the dm-verity volume the same way as the
"dmverity_roothash" section, and attempt to execute a binary. Failure
should occur.
Documentation:
------------------------------------
Full documentation is available on github in IPE's master repository
(Appendix A). This is intended to be an exhaustive source of documentation
around IPE.
Additionally, there is higher level documentation in the admin-guide.
Technical diagrams are available here:
http://microsoft.github.io/ipe/technical/diagrams/
Known Gaps:
------------------------------------
IPE has two known gaps:
1. IPE cannot verify the integrity of anonymous executable memory, such as
the trampolines created by gcc closures and libffi, or JIT'd code.
Unfortunately, as this is dynamically generated code, there is no way for
IPE to detect that this code has not been tampered with in transition
from where it was built, to where it is running. As a result, IPE is
incapable of tackling this problem for dynamically generated code.
However, there is a patch series being prepared that addresses this
problem for libffi and gcc closures by implemeting a safer kernel
trampoline API.
2. IPE cannot verify the integrity of interpreted languages' programs when
these scripts invoked via `<interpreter> <file>`. This is because the way
interpreters execute these files, the scripts themselves are not
evaluated as executable code through one of IPE's hooks. Interpreters
can be enlightened to the usage of IPE by trying to mmap a file into
executable memory (+X), after opening the file and responding to the
error code appropriately. This also applies to included files, or high
value files, such as configuration files of critical system components.
This specific gap is planned on being addressed within IPE. For more
information on how we plan to address this gap, please see the Future
Development section, below.
Future Development:
------------------------------------
Support for filtering signatures by specific certificates. In this case,
our "dmverity_signature" (or a separate property) can be set to a
specific certificate declared in IPE's policy, allowing for more
controlled use-cases determine by a user's PKI structure.
Support for integrity verification for general file reads. This addresses
the script interpreter issue indicated in the "Known Gaps" section, as
these script files are typically opened with O_RDONLY. We are evaluating
whether to do this by comparing the original userland filepath passed into
the open syscall, thereby allowing existing callers to take advantage
without any code changes; the alternate design is to extend the new
openat2(2) syscall, with an new flag, tentatively called "O_VERIFY". While
the second option requires a code change for all the interpreters,
frameworks and languages that wish to leverage it, it is a wholly cleaner
implementation in the kernel. For interpreters specifically, the O_MAYEXEC
patch series published by Mickaël Salaün[1] is a similar implementation
to the O_VERIFY idea described above.
Onboarding IPE's test suite to KernelCI. Currently we are developing a
test suite in the same vein as SELinux's test suite. Once development
of the test suite is complete, and provided IPE is accepted, we intend
to onboard this test suite onto KernelCI.
Hardened resistance against roll-back attacks. Currently there exists a
window of opportunity between user-mode setup and the user-policy being
deployed, where a prior user-policy can be loaded, that is potentially
insecure. However, with a kernel update, you can revise the boot policy's
version to be the same version as the latest policy, closing this window.
In the future, I would like to close this window of opportunity without
a kernel update, using some persistent storage mechanism.
Open Issues:
------------
For linux-audit/integrity folks:
1. Introduction of new audit definitions in the kernel integrity range - is
this preferred, as opposed to reusing definitions with existing IMA
definitions?
TODOs:
------
linux-audit changes to support the new audit events.
Appendix:
------------------------------------
A. IPE Github Repository: https://github.com/microsoft/ipe
Hosted Documentation: https://microsoft.github.io/ipe
B. IPE Users' Guide: Documentation/admin-guide/LSM/ipe.rst
C. IPE Test Suite: *TBA* (under development)
References:
------------------------------------
1. https://lore.kernel.org/linux-integrity/20200505153156.925111-1-mic@digikod.net/
Changelog:
------------------------------------
v1: Introduced
v2:
Split the second patch of the previous series into two.
Minor corrections in the cover-letter and documentation
comments regarding CAP_MAC_ADMIN checks in IPE.
v3:
Address various comments by Jann Horn. Highlights:
Switch various audit allocators to GFP_KERNEL.
Utilize rcu_access_pointer() in various locations.
Strip out the caching system for properties
Strip comments from headers
Move functions around in patches
Remove kernel command line parameters
Reconcile the race condition on the delete node for policy by
expanding the policy critical section.
Address a few comments by Jonathan Corbet around the documentation
pages for IPE.
Fix an issue with the initialization of IPE policy with a "-0"
version, caused by not initializing the hlist entries before
freeing.
v4:
Address a concern around IPE's behavior with unknown syntax.
Specifically, make any unknown syntax a fatal error instead of a
warning, as suggested by Mickaël Salaün.
Introduce a new securityfs node, $securityfs/ipe/property_config,
which provides a listing of what properties are enabled by the
kernel and their versions. This allows usermode to predict what
policies should be allowed.
Strip some comments from c files that I missed.
Clarify some documentation comments around 'boot_verified'.
While this currently does not functionally change the property
itself, the distinction is important when IPE can enforce verified
reads. Additionally, 'KERNEL_READ' was omitted from the documentation.
This has been corrected.
Change SecurityFS and SHA1 to a reverse dependency.
Update the cover-letter with the updated behavior of unknown syntax.
Remove all sysctls, making an equivalent function in securityfs.
Rework the active/delete mechanism to be a node under the policy in
$securityfs/ipe/policies.
The kernel command line parameters ipe.enforce and ipe.success_audit
have returned as this functionality is no longer exposed through
sysfs.
v5:
Correct some grammatical errors reported by Randy Dunlap.
Fix some warnings reported by kernel test bot.
Change convention around security_bdev_setsecurity. -ENOSYS
is now expected if an LSM does not implement a particular @name,
as suggested by Casey Schaufler.
Minor string corrections related to the move from sysfs to securityfs
Correct a spelling of an #ifdef for the permissive argument.
Add the kernel parameters re-added to the documentation.
Fix a minor bug where the mode being audited on permissive switch
was the original mode, not the mode being swapped to.
Cleanup doc comments, fix some whitespace alignment issues.
Deven Bowers (11):
scripts: add ipe tooling to generate boot policy
security: add ipe lsm evaluation loop and audit system
security: add ipe lsm policy parser and policy loading
ipe: add property for trust of boot volume
fs: add security blob and hooks for block_device
dm-verity: move signature check after tree validation
dm-verity: add bdev_setsecurity hook for dm-verity signature
ipe: add property for signed dmverity volumes
dm-verity: add bdev_setsecurity hook for root-hash
documentation: add ipe documentation
cleanup: uapi/linux/audit.h
Documentation/admin-guide/LSM/index.rst | 1 +
Documentation/admin-guide/LSM/ipe.rst | 508 +++++++
.../admin-guide/kernel-parameters.txt | 12 +
MAINTAINERS | 8 +
drivers/md/dm-verity-target.c | 52 +-
drivers/md/dm-verity-verify-sig.c | 147 +-
drivers/md/dm-verity-verify-sig.h | 24 +-
drivers/md/dm-verity.h | 2 +-
fs/block_dev.c | 8 +
include/linux/device-mapper.h | 3 +
include/linux/fs.h | 1 +
include/linux/lsm_hook_defs.h | 5 +
include/linux/lsm_hooks.h | 12 +
include/linux/security.h | 22 +
include/uapi/linux/audit.h | 36 +-
scripts/Makefile | 1 +
scripts/ipe/Makefile | 2 +
scripts/ipe/polgen/.gitignore | 1 +
scripts/ipe/polgen/Makefile | 7 +
scripts/ipe/polgen/polgen.c | 136 ++
security/Kconfig | 12 +-
security/Makefile | 2 +
security/ipe/.gitignore | 2 +
security/ipe/Kconfig | 48 +
security/ipe/Makefile | 33 +
security/ipe/ipe-audit.c | 303 ++++
security/ipe/ipe-audit.h | 24 +
security/ipe/ipe-blobs.c | 95 ++
security/ipe/ipe-blobs.h | 18 +
security/ipe/ipe-engine.c | 213 +++
security/ipe/ipe-engine.h | 49 +
security/ipe/ipe-hooks.c | 169 +++
security/ipe/ipe-hooks.h | 70 +
security/ipe/ipe-parse.c | 889 +++++++++++
security/ipe/ipe-parse.h | 17 +
security/ipe/ipe-pin.c | 93 ++
security/ipe/ipe-pin.h | 36 +
security/ipe/ipe-policy.c | 149 ++
security/ipe/ipe-policy.h | 69 +
security/ipe/ipe-prop-internal.h | 49 +
security/ipe/ipe-property.c | 143 ++
security/ipe/ipe-property.h | 100 ++
security/ipe/ipe-secfs.c | 1309 +++++++++++++++++
security/ipe/ipe-secfs.h | 14 +
security/ipe/ipe.c | 115 ++
security/ipe/ipe.h | 22 +
security/ipe/properties/Kconfig | 36 +
security/ipe/properties/Makefile | 13 +
security/ipe/properties/boot-verified.c | 82 ++
security/ipe/properties/dmverity-roothash.c | 153 ++
security/ipe/properties/dmverity-signature.c | 82 ++
security/ipe/properties/prop-entry.h | 38 +
security/ipe/utility.h | 32 +
security/security.c | 74 +
54 files changed, 5443 insertions(+), 98 deletions(-)
create mode 100644 Documentation/admin-guide/LSM/ipe.rst
create mode 100644 scripts/ipe/Makefile
create mode 100644 scripts/ipe/polgen/.gitignore
create mode 100644 scripts/ipe/polgen/Makefile
create mode 100644 scripts/ipe/polgen/polgen.c
create mode 100644 security/ipe/.gitignore
create mode 100644 security/ipe/Kconfig
create mode 100644 security/ipe/Makefile
create mode 100644 security/ipe/ipe-audit.c
create mode 100644 security/ipe/ipe-audit.h
create mode 100644 security/ipe/ipe-blobs.c
create mode 100644 security/ipe/ipe-blobs.h
create mode 100644 security/ipe/ipe-engine.c
create mode 100644 security/ipe/ipe-engine.h
create mode 100644 security/ipe/ipe-hooks.c
create mode 100644 security/ipe/ipe-hooks.h
create mode 100644 security/ipe/ipe-parse.c
create mode 100644 security/ipe/ipe-parse.h
create mode 100644 security/ipe/ipe-pin.c
create mode 100644 security/ipe/ipe-pin.h
create mode 100644 security/ipe/ipe-policy.c
create mode 100644 security/ipe/ipe-policy.h
create mode 100644 security/ipe/ipe-prop-internal.h
create mode 100644 security/ipe/ipe-property.c
create mode 100644 security/ipe/ipe-property.h
create mode 100644 security/ipe/ipe-secfs.c
create mode 100644 security/ipe/ipe-secfs.h
create mode 100644 security/ipe/ipe.c
create mode 100644 security/ipe/ipe.h
create mode 100644 security/ipe/properties/Kconfig
create mode 100644 security/ipe/properties/Makefile
create mode 100644 security/ipe/properties/boot-verified.c
create mode 100644 security/ipe/properties/dmverity-roothash.c
create mode 100644 security/ipe/properties/dmverity-signature.c
create mode 100644 security/ipe/properties/prop-entry.h
create mode 100644 security/ipe/utility.h
--
2.27.0
^ permalink raw reply
* [RFC PATCH v5 10/11] documentation: add ipe documentation
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add IPE's documentation to the kernel tree.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
Acked-by: Jonathan Corbet <corbet@lwn.net>
---
Documentation/admin-guide/LSM/index.rst | 1 +
Documentation/admin-guide/LSM/ipe.rst | 508 ++++++++++++++++++
.../admin-guide/kernel-parameters.txt | 12 +
MAINTAINERS | 1 +
4 files changed, 522 insertions(+)
create mode 100644 Documentation/admin-guide/LSM/ipe.rst
diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst
index a6ba95fbaa9f..ce63be6d64ad 100644
--- a/Documentation/admin-guide/LSM/index.rst
+++ b/Documentation/admin-guide/LSM/index.rst
@@ -47,3 +47,4 @@ subdirectories.
tomoyo
Yama
SafeSetID
+ ipe
diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst
new file mode 100644
index 000000000000..2e6610c4a134
--- /dev/null
+++ b/Documentation/admin-guide/LSM/ipe.rst
@@ -0,0 +1,508 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Integrity Policy Enforcement (IPE)
+==================================
+
+Overview
+--------
+
+IPE is a Linux Security Module which allows for a configurable policy
+to enforce integrity requirements on the whole system. It attempts to
+solve the issue of Code Integrity: that any code being executed (or
+files being read), are identical to the version that was built by a
+trusted source.
+
+There are multiple implementations within the Linux kernel that solve
+some measure of integrity verification. For instance, device-mapper
+verity, which ensures integrity for a block device, and fs-verity which
+is a system that ensures integrity for a filesystem. What these
+implementations lack is a measure of run-time verification that binaries
+are sourced from these locations. IPE aims to address this gap.
+
+IPE is separated between two major components: A configurable policy,
+provided by the LSM ("IPE Core"), and deterministic attributes provided
+by the kernel to evaluate files against, ("IPE Properties").
+
+Use Cases
+---------
+
+IPE is designed for use is an embedded device with a specific purpose
+(e.g. network firewall device in a data center), where all software and
+configuration is built and provisioned by the owner.
+
+Ideally, a system which leverages IPE is not intended for general
+purpose computing and does not utilize any software or configuration
+built by a third party. An ideal system to leverage IPE has both mutable
+and immutable components, however, all binary executable code is
+immutable.
+
+For the highest level of security, platform firmware should verify the
+the kernel and optionally the root filesystem (e.g. via U-Boot verified
+boot). This allows the entire system to be integrity verified.
+
+Known Gaps
+----------
+
+IPE cannot verify the integrity of anonymous executable memory, such as
+the trampolines created by gcc closures and libffi, or JIT'd code.
+Unfortunately, as this is dynamically generated code, there is no way
+for IPE to detect that this code has not been tampered with in
+transition from where it was built, to where it is running. As a result,
+IPE is incapable of tackling this problem for dynamically generated
+code.
+
+IPE cannot verify the integrity of interpreted languages' programs when
+these scripts invoked via ``<interpreter> <file>``. This is because the
+way interpreters execute these files, the scripts themselves are not
+evaluated as executable code through one of IPE's hooks. Interpreters
+can be enlightened to the usage of IPE by trying to mmap a file into
+executable memory (+X), after opening the file and responding to the
+error code appropriately. This also applies to included files, or high
+value files, such as configuration files of critical system components.
+This specific gap is planned on being addressed within IPE.
+
+Threat Model
+------------
+
+The threat type addressed by IPE is tampering of executable user-land
+code beyond the initially booted kernel, and the initial verification of
+kernel modules that are loaded in userland through ``modprobe`` or
+``insmod``.
+
+Tampering violates the property of integrity. IPE's role in mitigating
+this threat is to verify the integrity (and authenticity) of all
+executable code and to deny their use if integrity verification fails.
+IPE generates audit logs which may be utilized to detect integrity
+verification failures.
+
+Tampering threat scenarios include modification or replacement of
+executable code by a range of actors including:
+
+- Insiders with physical access to the hardware
+- Insiders with local network access to the system
+- Insiders with access to the deployment system
+- Compromised internal systems under external control
+- Malicious end users of the system
+- Compromised end users of the system
+- Remote (external) compromise of the system
+
+IPE does not mitigate threats arising from malicious authorized
+developers, or compromised developer tools used by authorized
+developers. Additionally, IPE draws hard security boundary between user
+mode and kernel mode. As a result, IPE does not provide any protections
+against a kernel level exploit, and a kernel-level exploit can disable
+or tamper with IPE's protections.
+
+The root of trust for all of IPE's verifications is the
+``SYSTEM_TRUSTED_KEYRING``.
+
+IPE Core
+--------
+
+IPE Policy
+~~~~~~~~~~
+
+IPE policy is designed to be both forward compatible and backwards
+compatible. There is one required line, at the top of the policy,
+indicating the policy name, and the policy version, for instance::
+
+ policy_name="Ex Policy" policy_version=0.0.0
+
+The policy name is a unique key identifying this policy in a human
+readable name. This is used to create nodes under securityfs as well as
+uniquely identify policies to deploy new policies vs update existing
+policies.
+
+The policy version indicates the current version of the policy (NOT the
+policy syntax version). This is used to prevent roll-back of policy to
+potentially insecure previous versions of the policy.
+
+The next portion of IPE policy, are rules. Rules are formed by key=value
+pairs, known as properties. IPE rules require two properties: "action",
+which determines what IPE does when it encounters a match against the
+rule, and "op", which determines when that rule should be evaluated.
+Thus, a minimal rule is::
+
+ op=EXECUTE action=ALLOW
+
+This example will allow any execution. Additional properties are used to
+restrict attributes about the files being evaluated. These properties
+are intended to be deterministic attributes that are resident in the
+kernel.
+
+Order does not matter for the rule's properties - they can be listed in
+any order, however it is encouraged to have the "op" property be first,
+and the "action" property be last for readability. Rules are evaluated
+top-to-bottom. As a result, any revocation rules, or denies should be
+placed early in the file to ensure that these rules are evaluated before
+as rule with "action=ALLOW" is hit.
+
+IPE policy is designed to be forward compatible and backwards
+compatible, thus any failure to parse a rule will result in the line
+being ignored, and a warning being emitted. If backwards compatibility
+is not required, the kernel command line parameter and sysctl,
+``ipe.strict_parse`` can be enabled, which will cause these warnings to
+be fatal.
+
+IPE policy supports comments. The character '#' will function as a
+comment, ignoring all characters to the right of '#' until the newline.
+
+The default behavior of IPE evaluations can also be expressed in policy,
+through the ``DEFAULT`` statement. This can be done at a global level,
+or a per-operation level::
+
+ # Global
+ DEFAULT action=ALLOW
+
+ # Operation Specific
+ DEFAULT op=EXECUTE action=ALLOW
+
+A default must be set for all known operations in IPE. If you want to
+preserve older policies being compatible with newer kernels that can introduce
+new operations, please set a global default of 'ALLOW', and override the
+defaults on a per-operation basis.
+
+With configurable policy-based LSMs, there's several issues with
+enforcing the configurable policies at startup, around reading and
+parsing the policy:
+
+1. The kernel *should* not read files from userland, so directly reading
+ the policy file is prohibited.
+2. The kernel command line has a character limit, and one kernel module
+ should not reserve the entire character limit for its own
+ configuration.
+3. There are various boot loaders in the kernel ecosystem, so handing
+ off a memory block would be costly to maintain.
+
+As a result, IPE has addressed this problem through a concept of a "boot
+policy". A boot policy is a minimal policy, compiled into the kernel.
+This policy is intended to get the system to a state where userland is
+setup and ready to receive commands, at which point a more complex
+policy ("user policies") can be deployed via securityfs. The boot policy
+can be specified via the Kconfig, ``SECURITY_IPE_BOOT_POLICY``, which
+accepts a path to a plain-text version of the IPE policy to apply. This
+policy will be compiled into the kernel. If not specified, IPE will be
+disabled until a policy is deployed through securityfs, and activated
+through sysfs.
+
+Deploying Policies
+^^^^^^^^^^^^^^^^^^
+
+User policies as explained above, are policies that are deployed from
+userland, through securityfs. These policies are signed to enforce some
+level of authorization of the policies (prohibiting an attacker from
+gaining root, and deploying an "allow all" policy), through the PKCS#7
+enveloped data format. These policies must be signed by a certificate
+that chains to the ``SYSTEM_TRUSTED_KEYRING``. Through openssl, the
+signing can be done via::
+
+ openssl smime -sign -in "$MY_POLICY" -signer "$MY_CERTIFICATE" \
+ -inkey "$MY_PRIVATE_KEY" -binary -outform der -noattr -nodetach \
+ -out "$MY_POLICY.p7s"
+
+Deploying the policies is done through securityfs, through the
+``new_policy`` node. To deploy a policy, simply cat the file into the
+securityfs node::
+
+ cat "$MY_POLICY.p7s" > /sys/kernel/security/ipe/new_policy
+
+Upon success, this will create one subdirectory under
+``/sys/kernel/security/ipe/policies/``. The subdirectory will be the
+``policy_name`` field of the policy deployed, so for the example above,
+the directory will be ``/sys/kernel/security/ipe/policies/Ex\ Policy``.
+Within this directory, there will be four files: ``raw``, ``content``,
+``active``, and ``delete``.
+
+The ``raw`` file is rw, reading will provide the raw PKCS#7 data that
+was provided to the kernel, representing the policy. Writing, will
+deploy an in-place policy update - if this policy is the currently
+running policy, the new updated policy will replace it immediately upon
+success.
+
+The ``content`` file is read only. Reading will provide the PKCS#7 inner
+content of the policy, which will be the plain text policy.
+
+The ``active`` file is used to set a policy as the currently active policy.
+This file is rw, and accepts a value of ``"1"`` to set the policy as active.
+Since only a single policy can be active at one time, all other policies
+will be marked inactive.
+
+Similarly, the ``cat`` command above will result in an error upon
+syntactically invalid or untrusted policies. It will also error if a
+policy already exists with the same ``policy_name``. The write to the
+``raw`` node will error upon syntactically invalid, untrusted policies,
+or if the payload fails the version check. The write will also fail if
+the ``policy_name`` in the payload does not match the existing policy.
+
+Deploying these policies will *not* cause IPE to start enforcing this
+policy. Once deployment is successful, a policy can be marked as active,
+via ``/sys/kernel/security/ipe/$policy_name/active``. IPE will enforce
+whatever policy is marked as active. For our example, we can activate
+the ``Ex Policy`` via::
+
+ echo -n 1 > "/sys/kernel/security/ipe/Ex Policy/active"
+
+At which point, ``Ex Policy`` will now be the enforced policy on the
+system.
+
+.. NOTE::
+
+ The -n parameter is important, as it strips an additional newline.
+
+IPE also provides a way to delete policies. This can be done via the
+``delete`` securityfs node, ``/sys/kernel/security/ipe/$policy_name/delete``.
+Writing ``1`` to that file will delete that node::
+
+ echo -n 1 > "/sys/kernel/security/ipe/$policy_name/delete"
+
+There are two requirements to delete policies:
+
+1. The policy being deleted must not be the active policy.
+2. The policy being deleted must not be the boot policy.
+
+.. NOTE::
+
+ If a MAC system is enabled, all writes to ipe's securityfs nodes require
+ ``CAP_MAC_ADMIN``.
+
+Modes
+~~~~~
+
+IPE supports two modes of operation: permissive (similar to SELinux's
+permissive mode) and enforce. Permissive mode performs the same checks
+as enforce mode, and logs policy violations, but will not enforce the
+policy. This allows users to test policies before enforcing them.
+
+The default mode is enforce, and can be changed via the kernel command
+line parameter ``ipe.enforce=(0|1)``, or the securityfs node
+``/sys/kernel/security/ipe/enforce``. The ability to switch modes can
+be compiled out of the LSM via setting the Kconfig
+``CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH`` to N.
+
+.. NOTE::
+
+ If a MAC system is enabled, all writes to ipe's securityfs nodes require
+ ``CAP_MAC_ADMIN``.
+
+Audit Events
+~~~~~~~~~~~~
+
+Success Auditing
+^^^^^^^^^^^^^^^^
+
+IPE supports success auditing. When enabled, all events that pass IPE
+policy and are not blocked will emit an audit event. This is disabled by
+default, and can be enabled via the kernel command line
+``ipe.success_audit=(0|1)`` or the securityfs node,
+``/sys/kernel/security/ipe/success_audit``.
+
+This is very noisy, as IPE will check every user-mode binary on the
+system, but is useful for debugging policies.
+
+.. NOTE::
+
+ If a MAC system is enabled, all writes to ipe's securityfs nodes require
+ ``CAP_MAC_ADMIN``.
+
+IPE Properties
+--------------
+
+As explained above, IPE properties are ``key=value`` pairs expressed in
+IPE policy. Two properties are built-into the policy parser: 'op' and
+'action'. The other properties are determinstic attributes to express
+across files. Currently those properties are: 'boot_verified',
+'dmverity_signature', 'dmverity_roothash'. A description of all
+properties supported by IPE are listed below:
+
+op
+~~
+
+Indicates the operation for a rule to apply to. Must be in every rule.
+IPE supports the following operations:
+
+Version 1
+^^^^^^^^^
+
+``EXECUTE``
+
+ Pertains to any file attempting to be executed, or loaded as an
+ executable.
+
+``FIRMWARE``:
+
+ Pertains to firmware being loaded via the firmware_class interface.
+ This covers both the preallocated buffer and the firmware file
+ itself.
+
+``KMODULE``:
+
+ Pertains to loading kernel modules via ``modprobe`` or ``insmod``.
+
+``KEXEC_IMAGE``:
+
+ Pertains to kernel images loading via ``kexec``.
+
+``KEXEC_INITRAMFS``
+
+ Pertains to initrd images loading via ``kexec --initrd``.
+
+``POLICY``:
+
+ Controls loading IMA policies through the
+ ``/sys/kernel/security/ima/policy`` securityfs entry.
+
+``X509_CERT``:
+
+ Controls loading IMA certificates through the Kconfigs,
+ ``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``.
+
+``KERNEL_READ``:
+
+ Short hand for all of the following: ``FIRMWARE``, ``KMODULE``,
+ ``KEXEC_IMAGE``, ``KEXEC_INITRAMFS``, ``POLICY``, and ``X509_CERT``.
+
+action
+~~~~~~
+
+Version 1
+^^^^^^^^^
+
+Determines what IPE should do when a rule matches. Must be in every
+rule. Can be one of:
+
+``ALLOW``:
+
+ If the rule matches, explicitly allow the call to proceed without
+ executing any more rules.
+
+``DENY``:
+
+ If the rule matches, explicitly prohibit the call from proceeding
+ without executing any more rules.
+
+boot_verified
+~~~~~~~~~~~~~
+
+Version 1
+^^^^^^^^^
+
+This property can be utilized for authorization of the first super-block
+that executes a file. This is almost always init. Typically this is used
+for systems with an initramfs or other initial disk, where this is unmounted
+before the system becomes available, and is not covered by any other property.
+This property is controlled by the Kconfig, ``CONFIG_IPE_BOOT_PROP``. The
+format of this property is::
+
+ boot_verified=(TRUE|FALSE)
+
+
+.. WARNING::
+
+ This property will trust any disk where the first execution occurs
+ evaluation occurs. If you do not have a startup disk that is
+ unpacked and unmounted (like initramfs), then it will automatically
+ trust the root filesystem and potentially overauthorize the entire
+ disk.
+
+dmverity_roothash
+~~~~~~~~~~~~~~~~~
+
+Version 1
+^^^^^^^^^
+
+This property can be utilized for authorization or revocation of
+specific dm-verity volumes, identified via root hash. It has a
+dependency on the DM_VERITY module. This property is controlled by the
+property: ``CONFIG_IPE_DM_VERITY_ROOTHASH``. The format of this property
+is::
+
+ dmverity_roothash=HashHexDigest
+
+dmverity_signature
+~~~~~~~~~~~~~~~~~~
+
+Version 1
+^^^^^^^^^
+
+This property can be utilized for authorization of all dm-verity volumes
+that have a signed roothash that chains to the system trusted keyring.
+It has a dependency on the ``DM_VERITY_VERIFY_ROOTHASH_SIG`` Kconfig.
+This property is controlled by the Kconfig:
+``CONFIG_IPE_DM_VERITY_SIGNATURE``. The format of this property is::
+
+ dmverity_signature=(TRUE|FALSE)
+
+Policy Examples
+---------------
+
+Allow all
+~~~~~~~~~
+
+::
+
+ policy_name="Allow All" policy_version=0.0.0
+ DEFAULT action=ALLOW
+
+Allow only initial superblock
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="Allow All Initial SB" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE boot_verified=TRUE action=ALLOW
+
+Allow any signed dm-verity volume and the initial superblock
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="AllowSignedAndInitial" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE boot_verified=TRUE action=ALLOW
+ op=EXECUTE dmverity_signature=TRUE action=ALLOW
+
+Prohibit execution from a specific dm-verity volume
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="AllowSignedAndInitial" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=DENY
+ op=EXECUTE boot_verified=TRUE action=ALLOW
+ op=EXECUTE dmverity_signature=TRUE action=ALLOW
+
+Allow only a specific dm-verity volume
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="AllowSignedAndInitial" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=ALLOW
+
+External Information
+--------------------
+
+Please see the github repository at: https://github.com/microsoft/ipe
+
+FAQ
+---
+
+Q: What's the difference between other LSMs which provide integrity
+verification (i.e. IMA)?
+
+A: IPE differs from other LSMs which provide integrity checking, as it
+has no dependency on the filesystem metadata itself. The attributes that
+IPE checks are deterministic properties that exist solely in the kernel.
+Additionally, IPE provides no additional mechanisms of verifying these
+files (e.g. IMA Signatures) - all of the attributes of verifying files
+are existing features within the kernel.
+
+Additionally, IPE is completely restricted to integrity. It offers no
+measurement or attestation features, which IMA addresses.
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index fb95fad81c79..5309813f25f7 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1953,6 +1953,18 @@
ipcmni_extend [KNL] Extend the maximum number of unique System V
IPC identifiers from 32,768 to 16,777,216.
+ ipe.enforce= [IPE]
+ Format: <bool>
+ Determine whether IPE starts in permissive (0) or
+ enforce (1) mode. The default is enforce.
+
+ ipe.success_audit=
+ [IPE]
+ Format: <bool>
+ Start IPE with success auditing enabled, emitting
+ an audit event when a binary is allowed. The default
+ is 0.
+
irqaffinity= [SMP] Set the default irq affinity mask
The argument is a cpu list, as described above.
diff --git a/MAINTAINERS b/MAINTAINERS
index bed30cc1cfd7..a5ab3ee733b6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8583,6 +8583,7 @@ INTEGRITY POLICY ENFORCEMENT (IPE)
M: Deven Bowers <deven.desai@linux.microsoft.com>
L: linux-integrity@vger.kernel.org
S: Supported
+F: Documentation/admin-guide/LSM/ipe.rst
F: scripts/ipe/
F: security/ipe/
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 08/11] ipe: add property for signed dmverity volumes
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Allow IPE to leverage the stacked security blob infrastructure,
and enlighten IPE to the block_device security blob.
This allows IPE to have a property to express rules around a device-mapper
verity volume whose root-hash has been signed, and the signature has been
verified against the system keyring. This is property is also added in
this patch.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
security/ipe/Kconfig | 2 +-
security/ipe/Makefile | 1 +
security/ipe/ipe-blobs.c | 84 ++++++++++++++++++++
security/ipe/ipe-blobs.h | 18 +++++
security/ipe/ipe-engine.c | 4 +
security/ipe/ipe-engine.h | 9 +++
security/ipe/ipe-hooks.c | 1 +
security/ipe/ipe-hooks.h | 7 ++
security/ipe/ipe.c | 18 +++++
security/ipe/ipe.h | 2 +
security/ipe/properties/Kconfig | 10 +++
security/ipe/properties/Makefile | 1 +
security/ipe/properties/dmverity-signature.c | 82 +++++++++++++++++++
security/ipe/properties/prop-entry.h | 9 +++
security/ipe/utility.h | 10 +++
15 files changed, 257 insertions(+), 1 deletion(-)
create mode 100644 security/ipe/ipe-blobs.c
create mode 100644 security/ipe/ipe-blobs.h
create mode 100644 security/ipe/properties/dmverity-signature.c
diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
index 469ef78c2f4f..11b50ef9abca 100644
--- a/security/ipe/Kconfig
+++ b/security/ipe/Kconfig
@@ -5,7 +5,7 @@
menuconfig SECURITY_IPE
bool "Integrity Policy Enforcement (IPE)"
- depends on SECURITY && AUDIT
+ depends on SECURITY && AUDIT && BLOCK
select SYSTEM_DATA_VERIFICATION
select SECURITYFS
select CRYPTO_SHA1
diff --git a/security/ipe/Makefile b/security/ipe/Makefile
index 7e98982c5035..98a2245b6956 100644
--- a/security/ipe/Makefile
+++ b/security/ipe/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_SECURITY_IPE) += \
ipe-property.o \
ipe-hooks.o \
ipe-secfs.o \
+ ipe-blobs.o \
clean-files := ipe-bp.c
diff --git a/security/ipe/ipe-blobs.c b/security/ipe/ipe-blobs.c
new file mode 100644
index 000000000000..041d7d47b723
--- /dev/null
+++ b/security/ipe/ipe-blobs.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-engine.h"
+#include "ipe-blobs.h"
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/device-mapper.h>
+
+/**
+ * ipe_bdev_alloc_security: Performs the initialization of IPE's security blob.
+ * @bdev: The block device to source the security blob from.
+ *
+ * The allocation is performed earlier by the LSM infrastructure,
+ * (on behalf of all LSMs) in lsm_alloc_bdev. At the moment, IPE uses
+ * this time to zero out the region of memory reserved for IPE.
+ *
+ * Return:
+ * 0 - OK
+ */
+int ipe_bdev_alloc_security(struct block_device *bdev)
+{
+ struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev);
+
+ memset(bdev_sec, 0x0, sizeof(*bdev_sec));
+
+ return 0;
+}
+
+/**
+ * ipe_bdev_free_security: Frees all fields of IPE's block dev security blob.
+ * @bdev: The block device to source the security blob from.
+ *
+ * The deallocation of the blob itself is performed later by the LSM
+ * infrastructure, (on behalf of all LSMs) in lsm_free_bdev.
+ *
+ * Pointers allocated by the bdev_setsecurity hook and alloc_security
+ * hook need to be deallocated here.
+ */
+void ipe_bdev_free_security(struct block_device *bdev)
+{
+ struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev);
+
+ kfree(bdev_sec->dmverity_rh_sig);
+
+ memset(bdev_sec, 0x0, sizeof(*bdev_sec));
+}
+
+/**
+ * ipe_bdev_setsecurity: Sets the a certain field of a block device security
+ * blob, based on @key.
+ * @bdev: The block device to source the security blob from.
+ * @key: The key representing the information to be stored.
+ * @value: The value to be stored.
+ * @len: The length of @value.
+ *
+ * As block-devices are a generic implementation across specific stacks,
+ * this allows information to be stored from various stacks.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - Error
+ */
+int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
+ const void *value, size_t len)
+{
+ struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev);
+
+ if (!strcmp(key, DM_VERITY_SIGNATURE_SEC_NAME)) {
+ bdev_sec->dmverity_rh_sig = kmemdup(value, len, GFP_KERNEL);
+ if (!bdev_sec->dmverity_rh_sig)
+ return -ENOMEM;
+
+ bdev_sec->dmv_rh_sig_len = len;
+
+ return 0;
+ }
+
+ return -ENOSYS;
+}
diff --git a/security/ipe/ipe-blobs.h b/security/ipe/ipe-blobs.h
new file mode 100644
index 000000000000..7561f4cef558
--- /dev/null
+++ b/security/ipe/ipe-blobs.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+#include <linux/types.h>
+#include <linux/fs.h>
+
+#include "ipe.h"
+
+#ifndef IPE_BLOB_H
+#define IPE_BLOB_H
+
+static inline struct ipe_bdev_blob *ipe_bdev(struct block_device *bdev)
+{
+ return bdev->security + ipe_blobs.lbs_bdev;
+}
+
+#endif /* IPE_BLOB_H */
diff --git a/security/ipe/ipe-engine.c b/security/ipe/ipe-engine.c
index 0291ced99d64..043faf64ceef 100644
--- a/security/ipe/ipe-engine.c
+++ b/security/ipe/ipe-engine.c
@@ -10,6 +10,7 @@
#include "ipe-engine.h"
#include "ipe-audit.h"
#include "ipe-pin.h"
+#include "ipe-blobs.h"
#include "utility.h"
#include <linux/types.h>
@@ -112,6 +113,9 @@ static struct ipe_engine_ctx *build_ctx(const struct file *file,
local->op = op;
local->hook = hook;
+ if (has_bdev(file))
+ local->sec_bdev = ipe_bdev(bdev(file));
+
return local;
}
diff --git a/security/ipe/ipe-engine.h b/security/ipe/ipe-engine.h
index d9a95674e70d..038c39a8973e 100644
--- a/security/ipe/ipe-engine.h
+++ b/security/ipe/ipe-engine.h
@@ -3,20 +3,29 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*/
+#include "ipe.h"
#include "ipe-hooks.h"
#include <linux/types.h>
#include <linux/rbtree.h>
#include <linux/fs.h>
+#include <crypto/pkcs7.h>
+
#ifndef IPE_ENGINE_H
#define IPE_ENGINE_H
+struct ipe_bdev_blob {
+ u8 *dmverity_rh_sig;
+ size_t dmv_rh_sig_len;
+};
+
struct ipe_engine_ctx {
enum ipe_op op;
enum ipe_hook hook;
const struct file *file;
const char *audit_pathname;
+ const struct ipe_bdev_blob *sec_bdev;
};
struct ipe_prop_cache {
diff --git a/security/ipe/ipe-hooks.c b/security/ipe/ipe-hooks.c
index 45efe022be04..18ab2dcd74d1 100644
--- a/security/ipe/ipe-hooks.c
+++ b/security/ipe/ipe-hooks.c
@@ -15,6 +15,7 @@
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/security.h>
+#include <linux/device-mapper.h>
/**
* ipe_on_exec: LSM hook called on the exec family of system calls.
diff --git a/security/ipe/ipe-hooks.h b/security/ipe/ipe-hooks.h
index 5e46726f2562..b2417831cfc1 100644
--- a/security/ipe/ipe-hooks.h
+++ b/security/ipe/ipe-hooks.h
@@ -60,4 +60,11 @@ int ipe_on_kernel_load_data(enum kernel_load_data_id id);
void ipe_sb_free_security(struct super_block *mnt_sb);
+int ipe_bdev_alloc_security(struct block_device *bdev);
+
+void ipe_bdev_free_security(struct block_device *bdev);
+
+int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
+ const void *value, size_t len);
+
#endif /* IPE_HOOK_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 706ff38083c6..8a612eb62879 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -23,6 +23,9 @@ static struct security_hook_list ipe_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(kernel_load_data, ipe_on_kernel_load_data),
LSM_HOOK_INIT(file_mprotect, ipe_on_mprotect),
LSM_HOOK_INIT(sb_free_security, ipe_sb_free_security),
+ LSM_HOOK_INIT(bdev_alloc_security, ipe_bdev_alloc_security),
+ LSM_HOOK_INIT(bdev_free_security, ipe_bdev_free_security),
+ LSM_HOOK_INIT(bdev_setsecurity, ipe_bdev_setsecurity),
};
/**
@@ -40,6 +43,10 @@ static int __init ipe_load_properties(void)
if (rc != 0)
return rc;
+ rc = ipe_init_dm_verity_signature();
+ if (rc != 0)
+ return rc;
+
return rc;
}
@@ -72,9 +79,20 @@ static int __init ipe_init(void)
return rc;
}
+struct lsm_blob_sizes ipe_blobs = {
+ .lbs_cred = 0,
+ .lbs_file = 0,
+ .lbs_inode = 0,
+ .lbs_ipc = 0,
+ .lbs_msg_msg = 0,
+ .lbs_task = 0,
+ .lbs_bdev = sizeof(struct ipe_bdev_blob),
+};
+
DEFINE_LSM(ipe) = {
.name = "ipe",
.init = ipe_init,
+ .blobs = &ipe_blobs,
};
bool ipe_enforce = true;
diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h
index af72bb574f73..a59cae2deec6 100644
--- a/security/ipe/ipe.h
+++ b/security/ipe/ipe.h
@@ -10,11 +10,13 @@
#include <linux/types.h>
#include <linux/fs.h>
+#include <linux/lsm_hooks.h>
#define IPE_MODE_ENFORCE "enforce"
#define IPE_MODE_PERMISSIVE "permissive"
extern bool ipe_enforce;
extern bool ipe_success_audit;
+extern struct lsm_blob_sizes ipe_blobs;
#endif /* IPE_H */
diff --git a/security/ipe/properties/Kconfig b/security/ipe/properties/Kconfig
index 75c6c6ff6cd8..4046f7e5eaef 100644
--- a/security/ipe/properties/Kconfig
+++ b/security/ipe/properties/Kconfig
@@ -13,3 +13,13 @@ config IPE_BOOT_PROP
superblock.
if unsure, answer N.
+
+config IPE_DM_VERITY_SIGNATURE
+ bool "Enable property for signature verified dm-verity volumes"
+ depends on DM_VERITY_VERIFY_ROOTHASH_SIG
+ help
+ This option enables IPE's integration with Device-Mapper Verity's
+ signed root-hashes. This enables the usage of the property,
+ "dmverity_signature" in IPE's policy.
+
+ if unsure, answer Y.
diff --git a/security/ipe/properties/Makefile b/security/ipe/properties/Makefile
index e3e7fe17cf58..6b67cbe36e31 100644
--- a/security/ipe/properties/Makefile
+++ b/security/ipe/properties/Makefile
@@ -9,3 +9,4 @@
obj-$(CONFIG_SECURITY_IPE) += properties.o
properties-$(CONFIG_IPE_BOOT_PROP) += boot-verified.o
+properties-$(CONFIG_IPE_DM_VERITY_SIGNATURE) += dmverity-signature.o
diff --git a/security/ipe/properties/dmverity-signature.c b/security/ipe/properties/dmverity-signature.c
new file mode 100644
index 000000000000..819222f226b7
--- /dev/null
+++ b/security/ipe/properties/dmverity-signature.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "../ipe.h"
+#include "../ipe-pin.h"
+#include "../ipe-property.h"
+#include "../utility.h"
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/audit.h>
+#include <linux/mount.h>
+
+#define PROPERTY_NAME "dmverity_signature"
+
+static void audit(struct audit_buffer *ab, bool value)
+{
+ audit_log_format(ab, "%s", (value) ? "TRUE" : "FALSE");
+}
+
+static inline void audit_rule_value(struct audit_buffer *ab,
+ const void *value)
+{
+ audit(ab, (bool)value);
+}
+
+static inline void audit_ctx(struct audit_buffer *ab,
+ const struct ipe_engine_ctx *ctx)
+{
+ bool b = has_bdev(ctx->file) && ctx->sec_bdev->dmverity_rh_sig;
+
+ audit(ab, b);
+}
+
+static bool evaluate(const struct ipe_engine_ctx *ctx,
+ const void *value)
+{
+ bool expect = (bool)value;
+
+ if (!has_bdev(ctx->file))
+ return false;
+
+ return ((bool)ctx->sec_bdev->dmverity_rh_sig) == expect;
+}
+
+static int parse(const char *val_str, void **value)
+{
+ if (strcmp("TRUE", val_str) == 0)
+ *value = (void *)true;
+ else if (strcmp("FALSE", val_str) == 0)
+ *value = (void *)false;
+ else
+ return -EBADMSG;
+
+ return 0;
+}
+
+static inline int duplicate(const void *src, void **dest)
+{
+ *dest = (void *)(bool)src;
+
+ return 0;
+}
+
+static const struct ipe_property dmv_signature = {
+ .property_name = PROPERTY_NAME,
+ .version = 1,
+ .eval = evaluate,
+ .parse = parse,
+ .rule_audit = audit_rule_value,
+ .ctx_audit = audit_ctx,
+ .dup = duplicate,
+ .free_val = NULL,
+};
+
+int ipe_init_dm_verity_signature(void)
+{
+ return ipe_register_property(&dmv_signature);
+}
diff --git a/security/ipe/properties/prop-entry.h b/security/ipe/properties/prop-entry.h
index f598dd9608b9..85366366ff0d 100644
--- a/security/ipe/properties/prop-entry.h
+++ b/security/ipe/properties/prop-entry.h
@@ -17,4 +17,13 @@ static inline int __init ipe_init_bootv(void)
int __init ipe_init_bootv(void);
#endif /* CONFIG_IPE_BOOT_PROP */
+#ifndef CONFIG_IPE_DM_VERITY_SIGNATURE
+static inline int __init ipe_init_dm_verity_signature(void)
+{
+ return 0;
+}
+#else
+int __init ipe_init_dm_verity_signature(void);
+#endif /* CONFIG_IPE_DM_VERITY_SIGNATURE */
+
#endif /* IPE_PROP_ENTRY_H */
diff --git a/security/ipe/utility.h b/security/ipe/utility.h
index a13089bb0d8f..7580f17274a3 100644
--- a/security/ipe/utility.h
+++ b/security/ipe/utility.h
@@ -19,4 +19,14 @@ static inline bool has_sb(const struct file *file)
return has_mount(file) && file->f_path.mnt->mnt_sb;
}
+static inline bool has_bdev(const struct file *file)
+{
+ return has_sb(file) && file->f_path.mnt->mnt_sb->s_bdev;
+}
+
+static inline struct block_device *bdev(const struct file *file)
+{
+ return file->f_path.mnt->mnt_sb->s_bdev;
+}
+
#endif /* IPE_UTILITY_H */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 10/12] ipe: add property for dmverity roothash
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add a property to allow IPE policy to express rules around a specific
root-hash of a dm-verity volume.
This can be used for revocation, (when combined with the previous dm-verity
property) or the authorization of a single dm-verity volume.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
security/ipe/ipe-blobs.c | 11 ++
security/ipe/ipe-engine.h | 3 +
security/ipe/ipe.c | 4 +
security/ipe/properties/Kconfig | 13 +-
security/ipe/properties/Makefile | 1 +
security/ipe/properties/dmverity-roothash.c | 153 ++++++++++++++++++++
security/ipe/properties/prop-entry.h | 9 ++
7 files changed, 193 insertions(+), 1 deletion(-)
create mode 100644 security/ipe/properties/dmverity-roothash.c
diff --git a/security/ipe/ipe-blobs.c b/security/ipe/ipe-blobs.c
index 9d67b63497fc..18d5bf689c54 100644
--- a/security/ipe/ipe-blobs.c
+++ b/security/ipe/ipe-blobs.c
@@ -46,6 +46,7 @@ void ipe_bdev_free_security(struct block_device *bdev)
struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev);
kfree(bdev_sec->dmverity_rh_sig);
+ kfree(bdev_sec->dmverity_rh);
memset(bdev_sec, 0x0, sizeof(*bdev_sec));
}
@@ -80,5 +81,15 @@ int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
return 0;
}
+ if (!strcmp(key, DM_VERITY_ROOTHASH_SEC_NAME)) {
+ bdev_sec->dmverity_rh = kmemdup(value, len, GFP_KERNEL);
+ if (!bdev_sec->dmverity_rh)
+ return -ENOMEM;
+
+ bdev_sec->rh_size = len;
+
+ return 0;
+ }
+
return -EOPNOTSUPP;
}
diff --git a/security/ipe/ipe-engine.h b/security/ipe/ipe-engine.h
index 038c39a8973e..696baaa423ff 100644
--- a/security/ipe/ipe-engine.h
+++ b/security/ipe/ipe-engine.h
@@ -18,6 +18,9 @@
struct ipe_bdev_blob {
u8 *dmverity_rh_sig;
size_t dmv_rh_sig_len;
+
+ u8 *dmverity_rh;
+ size_t rh_size;
};
struct ipe_engine_ctx {
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index ad25ac3f2a4f..5d2535503c20 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -47,6 +47,10 @@ static int __init ipe_load_properties(void)
if (rc != 0)
return rc;
+ rc = ipe_init_dm_verity_rh();
+ if (rc != 0)
+ return rc;
+
return rc;
}
diff --git a/security/ipe/properties/Kconfig b/security/ipe/properties/Kconfig
index 4046f7e5eaef..4f09092522d9 100644
--- a/security/ipe/properties/Kconfig
+++ b/security/ipe/properties/Kconfig
@@ -14,8 +14,19 @@ config IPE_BOOT_PROP
if unsure, answer N.
+config IPE_DM_VERITY_ROOTHASH
+ bool "Enable property for authorizing dm-verity volumes via root-hash"
+ depends on DM_VERITY
+ help
+ This option enables IPE's integration with Device-Mapper Verity.
+ This enables the usage of the property "dmverity_roothash" in IPE's
+ policy. This property allows authorization or revocation via a
+ a hex-string representing the roothash of a dmverity volume.
+
+ if unsure, answer Y.
+
config IPE_DM_VERITY_SIGNATURE
- bool "Enable property for signature verified dm-verity volumes"
+ bool "Enable property for verified dm-verity volumes"
depends on DM_VERITY_VERIFY_ROOTHASH_SIG
help
This option enables IPE's integration with Device-Mapper Verity's
diff --git a/security/ipe/properties/Makefile b/security/ipe/properties/Makefile
index 6b67cbe36e31..d9a3807797f4 100644
--- a/security/ipe/properties/Makefile
+++ b/security/ipe/properties/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_SECURITY_IPE) += properties.o
properties-$(CONFIG_IPE_BOOT_PROP) += boot-verified.o
properties-$(CONFIG_IPE_DM_VERITY_SIGNATURE) += dmverity-signature.o
+properties-$(CONFIG_IPE_DM_VERITY_ROOTHASH) += dmverity-roothash.o
diff --git a/security/ipe/properties/dmverity-roothash.c b/security/ipe/properties/dmverity-roothash.c
new file mode 100644
index 000000000000..09112e1af753
--- /dev/null
+++ b/security/ipe/properties/dmverity-roothash.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "../ipe.h"
+#include "../ipe-pin.h"
+#include "../ipe-property.h"
+#include "../utility.h"
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/audit.h>
+#include <linux/kernel.h>
+
+#define PROPERTY_NAME "dmverity_roothash"
+
+struct counted_array {
+ u8 *arr;
+ size_t len;
+};
+
+static void audit(struct audit_buffer *ab, const void *value)
+{
+ const struct counted_array *a = (const struct counted_array *)value;
+
+ if (!a || a->len == 0)
+ audit_log_format(ab, "NULL");
+ else
+ audit_log_n_hex(ab, a->arr, a->len);
+}
+
+static inline void audit_rule_value(struct audit_buffer *ab,
+ const void *value)
+{
+ audit(ab, value);
+}
+
+static inline void audit_ctx(struct audit_buffer *ab,
+ const struct ipe_engine_ctx *ctx)
+{
+ struct counted_array a;
+
+ if (!has_bdev(ctx->file))
+ return audit(ab, NULL);
+
+ a.arr = ctx->sec_bdev->dmverity_rh;
+ a.len = ctx->sec_bdev->rh_size;
+
+ return audit(ab, &a);
+}
+
+static bool evaluate(const struct ipe_engine_ctx *ctx,
+ const void *value)
+{
+ const struct counted_array *a = (const struct counted_array *)value;
+
+ if (!has_bdev(ctx->file))
+ return false;
+
+ if (a->len != ctx->sec_bdev->rh_size)
+ return false;
+
+ return memcmp(a->arr, ctx->sec_bdev->dmverity_rh, a->len) == 0;
+}
+
+static int parse(const char *val_str, void **value)
+{
+ struct counted_array *arr = NULL;
+ int rv = 0;
+
+ arr = kzalloc(sizeof(*arr), GFP_KERNEL);
+ if (!arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ arr->len = strlen(val_str) / 2;
+
+ arr->arr = kzalloc(arr->len, GFP_KERNEL);
+ if (!arr->arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ rv = hex2bin(arr->arr, val_str, arr->len);
+ if (rv != 0)
+ goto err;
+
+ *value = arr;
+ return rv;
+err:
+ if (arr)
+ kfree(arr->arr);
+ kfree(arr);
+ return rv;
+}
+
+static int duplicate(const void *src, void **dest)
+{
+ struct counted_array *arr = NULL;
+ const struct counted_array *src_arr = src;
+ int rv = 0;
+
+ arr = kmemdup(src_arr, sizeof(*arr), GFP_KERNEL);
+ if (!arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ arr->arr = kmemdup(src_arr->arr, src_arr->len, GFP_KERNEL);
+ if (!arr->arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ *dest = arr;
+ return rv;
+err:
+ if (arr)
+ kfree(arr->arr);
+ kfree(arr);
+
+ return rv;
+}
+
+static void free_val(void **value)
+{
+ struct counted_array *a = (struct counted_array *)*value;
+
+ if (a)
+ kfree(a->arr);
+ kfree(a);
+ *value = NULL;
+}
+
+static const struct ipe_property dmv_roothash = {
+ .property_name = PROPERTY_NAME,
+ .version = 1,
+ .eval = evaluate,
+ .parse = parse,
+ .rule_audit = audit_rule_value,
+ .ctx_audit = audit_ctx,
+ .dup = duplicate,
+ .free_val = free_val,
+};
+
+int ipe_init_dm_verity_rh(void)
+{
+ return ipe_register_property(&dmv_roothash);
+}
diff --git a/security/ipe/properties/prop-entry.h b/security/ipe/properties/prop-entry.h
index 85366366ff0d..86a360570f3b 100644
--- a/security/ipe/properties/prop-entry.h
+++ b/security/ipe/properties/prop-entry.h
@@ -26,4 +26,13 @@ static inline int __init ipe_init_dm_verity_signature(void)
int __init ipe_init_dm_verity_signature(void);
#endif /* CONFIG_IPE_DM_VERITY_SIGNATURE */
+#ifndef CONFIG_IPE_DM_VERITY_ROOTHASH
+static inline int __init ipe_init_dm_verity_rh(void)
+{
+ return 0;
+}
+#else
+int __init ipe_init_dm_verity_rh(void);
+#endif /* CONFIG_IPE_DM_VERITY_ROOTHASH */
+
#endif /* IPE_PROP_ENTRY_H */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 03/11] security: add ipe lsm policy parser and policy loading
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Adds the policy parser and the policy loading to IPE, along with the
related securityfs entries and audit events.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
security/ipe/Kconfig | 2 +
security/ipe/Makefile | 3 +
security/ipe/ipe-audit.c | 74 +-
security/ipe/ipe-audit.h | 6 +
security/ipe/ipe-parse.c | 889 ++++++++++++++++++++
security/ipe/ipe-parse.h | 17 +
security/ipe/ipe-policy.c | 149 ++++
security/ipe/ipe-policy.h | 13 +-
security/ipe/ipe-prop-internal.h | 18 +-
security/ipe/ipe-property.c | 9 +-
security/ipe/ipe-property.h | 1 +
security/ipe/ipe-secfs.c | 1309 ++++++++++++++++++++++++++++++
security/ipe/ipe-secfs.h | 14 +
13 files changed, 2493 insertions(+), 11 deletions(-)
create mode 100644 security/ipe/ipe-parse.c
create mode 100644 security/ipe/ipe-parse.h
create mode 100644 security/ipe/ipe-policy.c
create mode 100644 security/ipe/ipe-secfs.c
create mode 100644 security/ipe/ipe-secfs.h
diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
index 7615109a19ca..665524fc3ca4 100644
--- a/security/ipe/Kconfig
+++ b/security/ipe/Kconfig
@@ -7,6 +7,8 @@ menuconfig SECURITY_IPE
bool "Integrity Policy Enforcement (IPE)"
depends on SECURITY && AUDIT
select SYSTEM_DATA_VERIFICATION
+ select SECURITYFS
+ select CRYPTO_SHA1
help
This option enables the Integrity Policy Enforcement subsystem
allowing systems to enforce integrity having no dependencies
diff --git a/security/ipe/Makefile b/security/ipe/Makefile
index 40565b73fac2..7d6da33dd0c4 100644
--- a/security/ipe/Makefile
+++ b/security/ipe/Makefile
@@ -19,7 +19,10 @@ obj-$(CONFIG_SECURITY_IPE) += \
ipe-audit.o \
ipe-bp.o \
ipe-engine.o \
+ ipe-parse.o \
+ ipe-policy.o \
ipe-property.o \
ipe-hooks.o \
+ ipe-secfs.o \
clean-files := ipe-bp.c
diff --git a/security/ipe/ipe-audit.c b/security/ipe/ipe-audit.c
index 2c754851bd40..0e731025f2f5 100644
--- a/security/ipe/ipe-audit.c
+++ b/security/ipe/ipe-audit.c
@@ -17,7 +17,8 @@
#include <crypto/sha1_base.h>
#define ACTION_STR(a) ((a) == ipe_action_allow ? "ALLOW" : "DENY")
-
+#define POLICY_LOAD_FSTR "IPE policy_name=\"%s\" policy_version=%hu.%hu.%hu sha1="
+#define POLICY_ACTIVATE_STR "IPE policy_name=\"%s\" policy_version=%hu.%hu.%hu"
#define IPE_UNKNOWN "UNKNOWN"
/* Keep in sync with ipe_op in ipe-hooks.h */
@@ -229,3 +230,74 @@ void ipe_audit_match(const struct ipe_engine_ctx *ctx,
audit_log_end(ab);
}
+
+/**
+ * ipe_audit_policy_load: Emit an audit event that an IPE policy has been
+ * loaded, with the name of the policy, the policy
+ * version triple, and a flat hash of the content.
+ * @pol: The parsed policy to derive the policy_name and policy_version
+ * triple.
+ * @raw: The raw content that was passed to the ipe.policy sysctl to derive
+ * the sha1 hash.
+ * @raw_size: the length of @raw.
+ * @tfm: shash structure allocated by the caller, used to fingerprint the
+ * policy being deployed
+ */
+void ipe_audit_policy_load(const struct ipe_policy *pol, const uint8_t *raw,
+ size_t raw_size, struct crypto_shash *tfm)
+{
+ int rc = 0;
+ struct audit_buffer *ab;
+ u8 digest[SHA1_DIGEST_SIZE];
+ SHASH_DESC_ON_STACK(desc, tfm);
+
+ ab = audit_log_start(audit_context(), GFP_KERNEL,
+ AUDIT_INTEGRITY_POLICY_LOAD);
+ if (!ab)
+ return;
+
+ audit_log_format(ab, POLICY_LOAD_FSTR, pol->policy_name,
+ pol->policy_version.major, pol->policy_version.minor,
+ pol->policy_version.rev);
+
+ desc->tfm = tfm;
+
+ if (crypto_shash_init(desc) != 0)
+ goto err;
+
+ if (crypto_shash_update(desc, raw, raw_size) != 0)
+ goto err;
+
+ if (crypto_shash_final(desc, digest) != 0)
+ goto err;
+
+ audit_log_n_hex(ab, digest, crypto_shash_digestsize(tfm));
+
+err:
+ if (rc != 0)
+ audit_log_format(ab, "ERR(%d)", rc);
+
+ audit_log_end(ab);
+}
+
+/**
+ * ipe_audit_policy_activation: Emit an audit event that a specific policy
+ * was activated as the active policy.
+ * @pol: policy that is being activated
+ */
+void ipe_audit_policy_activation(const struct ipe_policy *pol)
+{
+ struct audit_buffer *ab;
+
+ ab = audit_log_start(audit_context(), GFP_KERNEL,
+ AUDIT_INTEGRITY_POLICY_ACTIVATE);
+
+ if (!ab)
+ return;
+
+ audit_log_format(ab, POLICY_ACTIVATE_STR, pol->policy_name,
+ pol->policy_version.major, pol->policy_version.minor,
+ pol->policy_version.rev);
+
+ audit_log_end(ab);
+}
diff --git a/security/ipe/ipe-audit.h b/security/ipe/ipe-audit.h
index e00f415bed2c..350818d5c47f 100644
--- a/security/ipe/ipe-audit.h
+++ b/security/ipe/ipe-audit.h
@@ -3,6 +3,7 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*/
+#include "ipe-prop-internal.h"
#include "ipe-engine.h"
#include "ipe-policy.h"
@@ -15,4 +16,9 @@ void ipe_audit_match(const struct ipe_engine_ctx *ctx,
enum ipe_match match_type, enum ipe_action action,
const struct ipe_rule *rule);
+void ipe_audit_policy_load(const struct ipe_policy *pol, const uint8_t *raw,
+ size_t raw_size, struct crypto_shash *tfm);
+
+void ipe_audit_policy_activation(const struct ipe_policy *pol);
+
#endif /* IPE_AUDIT_H */
diff --git a/security/ipe/ipe-parse.c b/security/ipe/ipe-parse.c
new file mode 100644
index 000000000000..edc3f52617c5
--- /dev/null
+++ b/security/ipe/ipe-parse.c
@@ -0,0 +1,889 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-prop-internal.h"
+#include "ipe-hooks.h"
+#include "ipe-parse.h"
+#include "ipe-property.h"
+#include "ipe-audit.h"
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/list_sort.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/parser.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+
+#define ALLOW_ACTION "ALLOW"
+#define DENY_ACTION "DENY"
+#define COMMENT_CHAR '#'
+#define VER_FSTR "%hu.%hu.%hu"
+
+/* Internal Type Definitions */
+enum property_priority {
+ other = 0,
+ action = 1,
+ op = 2,
+ default_action = 3,
+ policy_ver = 4,
+ policy_name = 5,
+};
+
+struct token {
+ struct list_head next_tok;
+ const char *key;
+ enum property_priority key_priority;
+ const char *val;
+};
+
+/* Utility Functions */
+static inline bool is_quote(char c)
+{
+ return c == '"' || c == '\'';
+}
+
+static inline bool valid_token(char *s)
+{
+ return !s || !strpbrk(s, "\"\'");
+}
+
+static inline bool is_default(const struct token *t)
+{
+ return !t->val && t->key_priority == default_action;
+}
+
+static inline bool is_operation(const struct token *t)
+{
+ return t->val && t->key_priority == op;
+}
+
+static inline bool is_action(const struct token *t)
+{
+ return t->val && t->key_priority == action;
+}
+
+static inline bool is_name(const struct token *t)
+{
+ return t->val && t->key_priority == policy_name;
+}
+
+static inline bool is_ver(const struct token *t)
+{
+ return t->val && t->key_priority == policy_ver;
+}
+
+static int cmp_pri(void *priv, struct list_head *a, struct list_head *b)
+{
+ struct token *t_a = container_of(a, struct token, next_tok);
+ struct token *t_b = container_of(b, struct token, next_tok);
+
+ return t_b->key_priority - t_a->key_priority;
+}
+
+static char *trim_quotes(char *str)
+{
+ char s;
+ size_t len;
+
+ if (!str)
+ return str;
+
+ s = *str;
+
+ if (is_quote(s)) {
+ len = strlen(str) - 1;
+
+ if (str[len] != s)
+ return NULL;
+
+ str[len] = '\0';
+ ++str;
+ }
+
+ return str;
+}
+
+/**
+ * ipe_set_action: Set an action with error checking.
+ * @src: Valid pointer to the source location to set wih the result
+ * @set: Value to apply to @src, if valid
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - Attempting to set something that is already set
+ */
+static int ipe_set_action(enum ipe_action *src, enum ipe_action set)
+{
+ if (*src != ipe_action_unset)
+ return -EBADMSG;
+
+ *src = set;
+
+ return 0;
+}
+
+/**
+ * ipe_insert_token: Allocate and append the key=value pair indicated by @val,
+ * to the list represented by @head.
+ * @val: Token to parse, of form "key=val".
+ * @head: Head of the list to insert the token structure into.
+ *
+ * If "=val" is omitted, this function will succeed, and the value set will be
+ * NULL.
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - Invalid policy syntax
+ * -ENOMEM - No Memory
+ */
+static int ipe_insert_token(char *val, struct list_head *head)
+{
+ char *key;
+ substring_t match[MAX_OPT_ARGS];
+ struct token *tok;
+ const match_table_t prop_priorities = {
+ { policy_name, IPE_HEADER_POLICY_NAME },
+ { policy_ver, IPE_HEADER_POLICY_VERSION},
+ { op, IPE_PROPERTY_OPERATION },
+ { default_action, IPE_PROPERTY_DEFAULT },
+ { action, IPE_PROPERTY_ACTION },
+ { other, NULL },
+ };
+
+ key = strsep(&val, "=");
+ if (!key)
+ return -EBADMSG;
+
+ tok = kzalloc(sizeof(*tok), GFP_KERNEL);
+ if (!tok)
+ return -ENOMEM;
+
+ tok->key = key;
+ tok->val = trim_quotes(val);
+
+ /* remap empty string */
+ if (tok->val && !strlen(tok->val))
+ tok->val = NULL;
+
+ tok->key_priority = match_token(key, prop_priorities, match);
+ INIT_LIST_HEAD(&tok->next_tok);
+
+ list_add_tail(&tok->next_tok, head);
+
+ return 0;
+}
+
+/**
+ * ipe_tokenize_line: Parse a line of text into a list of token structures.
+ * @line: Line to parse.
+ * @list: Head of the list to insert the token structure into.
+ *
+ * The final result will be sorted in the priority order definted by
+ * enum property_priorities to enforce policy structure.
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - Invalid policy syntax
+ * -ENOMEM - No Memory
+ * -ENOENT - No tokens were parsed
+ */
+static int ipe_tokenize_line(char *line, struct list_head *list)
+{
+ int rc = 0;
+ size_t i = 0;
+ size_t len = 0;
+ char *tok = NULL;
+ char quote = '\0';
+
+ len = strlen(line);
+
+ for (i = 0; i < len; ++i) {
+ if (quote == '\0' && is_quote(line[i])) {
+ quote = line[i];
+ continue;
+ }
+
+ if (quote != '\0' && line[i] == quote) {
+ quote = '\0';
+ continue;
+ }
+
+ if (quote == '\0' && line[i] == COMMENT_CHAR) {
+ tok = NULL;
+ break;
+ }
+
+ if (isgraph(line[i]) && !tok)
+ tok = &line[i];
+
+ if (quote == '\0' && isspace(line[i])) {
+ line[i] = '\0';
+
+ if (!tok)
+ continue;
+
+ rc = ipe_insert_token(tok, list);
+ if (rc != 0)
+ return rc;
+
+ tok = NULL;
+ }
+ }
+
+ if (quote != '\0')
+ return -EBADMSG;
+
+ if (tok)
+ ipe_insert_token(tok, list);
+
+ if (list_empty(list))
+ return -ENOENT;
+
+ list_sort(NULL, list, cmp_pri);
+
+ return 0;
+}
+
+static inline int ipe_parse_version(const char *val, struct ipe_pol_ver *ver)
+{
+ if (sscanf(val, VER_FSTR, &ver->major, &ver->minor, &ver->rev) != 3)
+ return -EBADMSG;
+
+ return 0;
+}
+
+/**
+ * ipe_parse_action: Given a token, parse the value as if it were an 'action'
+ * token.
+ * @action: Token to parse to determine the action.
+ *
+ * Action tokens are of the form: action=(ALLOW|DENY) for more information
+ * about IPE policy, please see the documentation.
+ *
+ * Return:
+ * ipe_action_allow - OK
+ * ipe_action_deny - OK
+ * ipe_action_unset - ERR
+ */
+static enum ipe_action ipe_parse_action(struct token *action)
+{
+ if (!action->val)
+ return ipe_action_unset;
+ else if (!strcmp(action->val, ALLOW_ACTION))
+ return ipe_action_allow;
+ else if (!strcmp(action->val, DENY_ACTION))
+ return ipe_action_deny;
+
+ return ipe_action_unset;
+}
+
+/**
+ * ipe_parse_op: Given a token, parse the value as if it were an 'op' token.
+ * @op: Token to parse to determine the operation.
+ *
+ * "op" tokens are of the form: op=(EXECUTE|FIRMWARE|KEXEC_IMAGE|...)
+ * for more information about IPE policy, please see the documentation.
+ *
+ * Return:
+ * ipe_op_max - ERR
+ * otherwise - OK
+ */
+static enum ipe_op ipe_parse_op(struct token *op)
+{
+ substring_t match[MAX_OPT_ARGS];
+ const match_table_t ops = {
+ { ipe_op_execute, IPE_OP_EXECUTE },
+ { ipe_op_firmware, IPE_OP_FIRMWARE },
+ { ipe_op_kexec_image, IPE_OP_KEXEC_IMAGE },
+ { ipe_op_kexec_initramfs, IPE_OP_KEXEC_INITRAMFS },
+ { ipe_op_x509, IPE_OP_X509_CERTIFICATE },
+ { ipe_op_policy, IPE_OP_POLICY },
+ { ipe_op_kmodule, IPE_OP_KMODULE },
+ { ipe_op_kernel_read, IPE_OP_KERNEL_READ },
+ { ipe_op_max, NULL },
+ };
+
+ return match_token((char *)op->val, ops, match);
+}
+
+/**
+ * ipe_set_default: Set the default of the policy, at various scope levels
+ * depending on the value of op.
+ * @op: Operation that was parsed.
+ * @pol: Policy to modify with the newly-parsed default action.
+ * @a: Action token (see parse_action) to parse to determine
+ * the default.
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - Invalid policy format
+ */
+static int ipe_set_default(enum ipe_op op, struct ipe_policy *pol,
+ struct token *a)
+{
+ int rc = 0;
+ size_t i = 0;
+ enum ipe_action act = ipe_parse_action(a);
+
+ if (act == ipe_action_unset)
+ return -EBADMSG;
+
+ if (op == ipe_op_max)
+ return ipe_set_action(&pol->def, act);
+
+ if (op == ipe_op_kernel_read) {
+ for (i = ipe_op_firmware; i <= ipe_op_kmodule; ++i) {
+ rc = ipe_set_action(&pol->ops[i].def, act);
+ if (rc != 0)
+ return rc;
+ }
+ return 0;
+ }
+
+ return ipe_set_action(&pol->ops[op].def, act);
+}
+
+/**
+ * ipe_parse_default: Parse a default statement of an IPE policy modify @pol
+ * with the proper changes
+ * @tokens: List of tokens parsed from the line
+ * @pol: Policy to modify with the newly-parsed default action
+ *
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - Invalid policy format
+ * -ENOENT - Unknown policy structure
+ */
+static int ipe_parse_default(struct list_head *tokens,
+ struct ipe_policy *pol)
+{
+ struct token *f = NULL;
+ struct token *s = NULL;
+ struct token *t = NULL;
+ enum ipe_op i = ipe_op_max;
+
+ f = list_first_entry(tokens, struct token, next_tok);
+ s = list_next_entry(f, next_tok);
+ if (is_action(s))
+ return ipe_set_default(ipe_op_max, pol, s);
+
+ i = ipe_parse_op(s);
+ if (i == ipe_op_max)
+ return -ENOENT;
+
+ t = list_next_entry(s, next_tok);
+ if (is_action(t)) {
+ t = list_next_entry(s, next_tok);
+ return ipe_set_default(i, pol, t);
+ }
+
+ return -ENOENT;
+}
+
+/**
+ * ipe_free_token_list - Free a list of tokens, and then reinitialize @list
+ * dropping all tokens.
+ * @list: List to be freed.
+ */
+static void ipe_free_token_list(struct list_head *list)
+{
+ struct token *ptr, *next;
+
+ list_for_each_entry_safe(ptr, next, list, next_tok)
+ kfree(ptr);
+
+ INIT_LIST_HEAD(list);
+}
+
+/**
+ * ipe_free_prop - Deallocator for an ipe_prop_container structure.
+ * @cont: Object to free.
+ */
+static void ipe_free_prop(struct ipe_prop_container *cont)
+{
+ if (IS_ERR_OR_NULL(cont))
+ return;
+
+ if (cont->prop->free_val)
+ cont->prop->free_val(&cont->value);
+ kfree(cont);
+}
+
+/**
+ * ipe_alloc_prop: Allocator for a ipe_prop_container structure.
+ * @tok: Token structure representing the "key=value" pair of the property.
+ *
+ * Return:
+ * Pointer to ipe_rule - OK
+ * ERR_PTR(-ENOMEM) - Allocation failed
+ */
+static struct ipe_prop_container *ipe_alloc_prop(const struct token *tok)
+{
+ int rc = 0;
+ const struct ipe_property *prop = NULL;
+ struct ipe_prop_container *cont = NULL;
+
+ prop = ipe_lookup_prop(tok->key);
+ if (!prop) {
+ rc = -ENOENT;
+ goto err;
+ }
+
+ cont = kzalloc(sizeof(*cont), GFP_KERNEL);
+ if (!cont) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ INIT_LIST_HEAD(&cont->next);
+
+ rc = prop->parse(tok->val, &cont->value);
+ if (rc != 0)
+ goto err;
+
+ cont->prop = prop;
+
+ return cont;
+err:
+ ipe_free_prop(cont);
+ return ERR_PTR(rc);
+}
+
+/**
+ * ipe_free_rule: Deallocator for an ipe_rule structure.
+ * @rule: Object to free.
+ */
+static void ipe_free_rule(struct ipe_rule *rule)
+{
+ struct ipe_prop_container *ptr;
+ struct list_head *l_ptr, *l_next;
+
+ if (IS_ERR_OR_NULL(rule))
+ return;
+
+ list_for_each_safe(l_ptr, l_next, &rule->props) {
+ ptr = container_of(l_ptr, struct ipe_prop_container, next);
+ list_del(l_ptr);
+ ipe_free_prop(ptr);
+ }
+
+ kfree(rule);
+}
+
+/**
+ * ipe_alloc_rule: Allocate a ipe_rule structure, for operation @op, parsed
+ * from the first token in list @head.
+ * @op: Operation parsed from the first token in @head.
+ * @t: The first token in @head that was parsed.
+ * @head: List of remaining tokens to parse.
+ *
+ * Return:
+ * Valid ipe_rule pointer - OK
+ * ERR_PTR(-EBADMSG) - Invalid syntax
+ * ERR_PTR(-ENOMEM) - Out of memory
+ */
+static struct ipe_rule *ipe_alloc_rule(enum ipe_op op, struct token *t,
+ struct list_head *head)
+{
+ int rc = 0;
+ struct token *ptr;
+ enum ipe_action act;
+ struct ipe_rule *rule = NULL;
+ struct ipe_prop_container *prop = NULL;
+
+ ptr = list_next_entry(t, next_tok);
+ if (!is_action(ptr)) {
+ rc = -EBADMSG;
+ goto err;
+ }
+
+ act = ipe_parse_action(ptr);
+ if (act == ipe_action_unset) {
+ rc = -EBADMSG;
+ goto err;
+ }
+
+ rule = kzalloc(sizeof(*rule), GFP_KERNEL);
+ if (!rule) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ INIT_LIST_HEAD(&rule->props);
+ INIT_LIST_HEAD(&rule->next);
+ rule->action = act;
+ rule->op = op;
+
+ list_for_each_entry_continue(ptr, head, next_tok) {
+ prop = ipe_alloc_prop(ptr);
+
+ if (IS_ERR(prop)) {
+ rc = PTR_ERR(prop);
+ goto err;
+ }
+
+ list_add_tail(&prop->next, &rule->props);
+ }
+
+ return rule;
+err:
+ ipe_free_prop(prop);
+ ipe_free_rule(rule);
+ return ERR_PTR(rc);
+}
+
+/**
+ * ipe_dup_prop: Duplicate an ipe_prop_container structure
+ * @p: Container to duplicate.
+ *
+ * This function is used to duplicate individual properties within a rule.
+ * It should only be called in operations that actually map to one or more
+ * operations.
+ *
+ * Return:
+ * Valid ipe_prop_container - OK
+ * ERR_PTR(-ENOMEM) - Out of memory
+ * Other Errors - see various property duplicator functions
+ */
+static
+struct ipe_prop_container *ipe_dup_prop(const struct ipe_prop_container *p)
+{
+ int rc = 0;
+ struct ipe_prop_container *dup;
+
+ dup = kzalloc(sizeof(*dup), GFP_KERNEL);
+ if (!dup) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ dup->prop = p->prop;
+ INIT_LIST_HEAD(&dup->next);
+
+ rc = p->prop->dup(p->value, &dup->value);
+ if (rc != 0)
+ goto err;
+
+ return dup;
+err:
+ ipe_free_prop(dup);
+ return ERR_PTR(rc);
+}
+
+/**
+ * ipe_dup_rule: Duplicate a policy rule, used for pseudo hooks like
+ * KERNEL_READ to map a policy rule across all hooks.
+ * @r: Rule to duplicate.
+ *
+ * Return:
+ * valid ipe_rule - OK
+ * ERR_PTR(-ENOMEM) - Out of memory
+ * Other Errors - See ipe_dup_prop
+ */
+static struct ipe_rule *ipe_dup_rule(const struct ipe_rule *r)
+{
+ int rc = 0;
+ struct ipe_rule *dup;
+ struct ipe_prop_container *ptr;
+
+ dup = kzalloc(sizeof(*dup), GFP_KERNEL);
+ if (!dup) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ dup->op = r->op;
+ dup->action = r->action;
+ INIT_LIST_HEAD(&dup->props);
+ INIT_LIST_HEAD(&dup->next);
+
+ list_for_each_entry(ptr, &r->props, next) {
+ struct ipe_prop_container *prop2;
+
+ prop2 = ipe_dup_prop(ptr);
+ if (IS_ERR(prop2)) {
+ rc = PTR_ERR(prop2);
+ goto err;
+ }
+
+ list_add_tail(&prop2->next, &dup->props);
+ }
+
+ return dup;
+err:
+ ipe_free_rule(dup);
+ return ERR_PTR(rc);
+}
+
+/**
+ * ipe_free_policy: Deallocate an ipe_policy structure.
+ * @pol: Policy to free.
+ */
+void ipe_free_policy(struct ipe_policy *pol)
+{
+ size_t i;
+ struct ipe_rule *ptr;
+ struct ipe_rule_table *op;
+ struct list_head *l_ptr, *l_next;
+
+ if (IS_ERR_OR_NULL(pol))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(pol->ops); ++i) {
+ op = &pol->ops[i];
+
+ list_for_each_safe(l_ptr, l_next, &op->rules) {
+ ptr = list_entry(l_ptr, struct ipe_rule, next);
+ list_del(l_ptr);
+ ipe_free_rule(ptr);
+ }
+ }
+
+ kfree(pol->policy_name);
+ kfree(pol);
+}
+
+/**
+ * ipe_alloc_policy: Give a list of tokens representing the first line of the
+ * token, attempt to parse it as an IPE policy header, and
+ * allocate a policy structure based on those values.
+ * @tokens: List of tokens parsed from the first line of the policy
+ *
+ * Return:
+ * Valid ipe_policy pointer - OK
+ * ERR_PTR(-ENOMEM) - Out of memory
+ * ERR_PTR(-EBADMSG) - Invalid policy syntax
+ */
+static struct ipe_policy *ipe_alloc_policy(struct list_head *tokens)
+{
+ size_t i;
+ int rc = 0;
+ struct token *name = NULL;
+ struct token *ver = NULL;
+ struct ipe_policy *lp = NULL;
+
+ name = list_first_entry(tokens, struct token, next_tok);
+ if (!is_name(name)) {
+ rc = -EBADMSG;
+ goto err;
+ }
+
+ if (list_is_singular(tokens)) {
+ rc = -EBADMSG;
+ goto err;
+ }
+
+ ver = list_next_entry(name, next_tok);
+ if (!is_ver(ver)) {
+ rc = -EBADMSG;
+ goto err;
+ }
+
+ lp = kzalloc(sizeof(*lp), GFP_KERNEL);
+ if (!lp) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(lp->ops); ++i) {
+ lp->ops[i].def = ipe_action_unset;
+ INIT_LIST_HEAD(&lp->ops[i].rules);
+ }
+
+ lp->policy_name = kstrdup(name->val, GFP_KERNEL);
+ if (!lp->policy_name) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ rc = ipe_parse_version(ver->val, &lp->policy_version);
+ if (rc != 0)
+ goto err;
+
+ lp->def = ipe_action_unset;
+
+ return lp;
+err:
+ ipe_free_policy(lp);
+ return ERR_PTR(rc);
+}
+
+/**
+ * ipe_add_rule_for_range: Given a ipe_rule @r, duplicate @r and add the rule
+ * to @pol for the operation range @start to @end.
+ * @start: The starting point of the range to add the rule to.
+ * @end: The ending point of the range to add the rule to.
+ * @r: The rule to copy.
+ * @pol: Policy structure to modify with the result.
+ *
+ * This is @start to @end, inclusive. @r is still valid after this function,
+ * and should be freed if appropriate.
+ *
+ * Return:
+ * 0 - OK
+ * Other Errors - See ipe_dup_prop
+ */
+static int ipe_add_rule_for_range(enum ipe_op start, enum ipe_op end,
+ struct ipe_rule *r, struct ipe_policy *pol)
+{
+ enum ipe_op i;
+ struct ipe_rule *cpy = NULL;
+
+ for (i = start; i <= end; ++i) {
+ cpy = ipe_dup_rule(r);
+ if (IS_ERR(cpy))
+ return PTR_ERR(cpy);
+
+ list_add_tail(&cpy->next, &pol->ops[i].rules);
+ }
+
+ return 0;
+}
+
+/**
+ * ipe_parse_line: Given a list of tokens, attempt to parse it into a rule
+ * structure, and add it to the passed-in ipe_policy structure.
+ * @tokens: List of tokens that were parsed.
+ * @pol: Policy structure to modify with the result.
+ *
+ * Return:
+ * 0 - OK
+ * -ENOENT - Unrecognized property
+ * -ENOMEM - Out of memory
+ * Other Errors - See ipe_dup_prop
+ */
+static int ipe_parse_line(struct list_head *tokens,
+ struct ipe_policy *pol)
+{
+ int rc = 0;
+ struct token *f;
+ enum ipe_op i = ipe_op_max;
+ struct ipe_rule *rule = NULL;
+
+ f = list_first_entry(tokens, struct token, next_tok);
+
+ switch (f->key_priority) {
+ case default_action:
+ rc = ipe_parse_default(tokens, pol);
+ break;
+ case op:
+ i = ipe_parse_op(f);
+ if (i == ipe_op_max)
+ return -ENOENT;
+
+ if (list_is_singular(tokens))
+ return -EBADMSG;
+
+ rule = ipe_alloc_rule(i, f, tokens);
+ if (IS_ERR(rule)) {
+ rc = PTR_ERR(rule);
+ goto cleanup;
+ }
+
+ if (i == ipe_op_kernel_read) {
+ rc = ipe_add_rule_for_range(ipe_op_firmware,
+ ipe_op_kmodule, rule, pol);
+ if (rc != 0)
+ goto cleanup;
+ } else {
+ list_add_tail(&rule->next, &pol->ops[i].rules);
+ rule = NULL;
+ }
+ break;
+ default:
+ return -ENOENT;
+ }
+
+cleanup:
+ ipe_free_rule(rule);
+ return rc;
+}
+
+/**
+ * ipe_check_policy_defaults: Ensure all defaults in policy are set
+ * for every operation known to IPE.
+ *
+ * @p: Policy to check the defaults.
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - A default was left unset.
+ */
+static int ipe_check_policy_defaults(const struct ipe_policy *p)
+{
+ size_t i;
+
+ if (p->def == ipe_action_unset) {
+ for (i = 0; i < ARRAY_SIZE(p->ops); ++i) {
+ if (p->ops[i].def == ipe_action_unset)
+ return -EBADMSG;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ipe_parse_policy: Given a string, parse the string into an IPE policy
+ * structure.
+ * @policy: NULL terminated string to parse.
+ *
+ * This function will modify @policy, callers should pass a copy if this
+ * value is needed later.
+ *
+ * Return:
+ * Valid ipe_policy structure - OK
+ * ERR_PTR(-EBADMSG) - Invalid Policy Syntax (Unrecoverable)
+ * ERR_PTR(-ENOMEM) - Out of Memory
+ */
+struct ipe_policy *ipe_parse_policy(char *policy)
+{
+ int rc = 0;
+ size_t i = 1;
+ char *p = NULL;
+ LIST_HEAD(t_list);
+ struct ipe_policy *local_p = NULL;
+
+ while ((p = strsep(&policy, "\n\0")) != NULL) {
+ rc = ipe_tokenize_line(p, &t_list);
+ if (rc == -ENOENT) {
+ ++i;
+ continue;
+ }
+ if (rc != 0)
+ goto err;
+
+ if (!local_p) {
+ local_p = ipe_alloc_policy(&t_list);
+ if (IS_ERR(local_p)) {
+ rc = PTR_ERR(local_p);
+ goto err;
+ }
+ } else {
+ rc = ipe_parse_line(&t_list, local_p);
+ if (rc) {
+ pr_warn("failed to parse line %zu", i);
+ goto err;
+ }
+ }
+
+ ipe_free_token_list(&t_list);
+ ++i;
+ }
+
+ rc = ipe_check_policy_defaults(local_p);
+ if (rc != 0)
+ goto err;
+
+ return local_p;
+err:
+ ipe_free_token_list(&t_list);
+ ipe_free_policy(local_p);
+ return ERR_PTR(rc);
+}
diff --git a/security/ipe/ipe-parse.h b/security/ipe/ipe-parse.h
new file mode 100644
index 000000000000..b1a9bbd97534
--- /dev/null
+++ b/security/ipe/ipe-parse.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe-policy.h"
+
+#include <linux/types.h>
+
+#ifndef IPE_PARSE_H
+#define IPE_PARSE_H
+
+struct ipe_policy *ipe_parse_policy(char *policy);
+
+void ipe_free_policy(struct ipe_policy *pol);
+
+#endif /* IPE_AUDIT_H */
diff --git a/security/ipe/ipe-policy.c b/security/ipe/ipe-policy.c
new file mode 100644
index 000000000000..74535fb03666
--- /dev/null
+++ b/security/ipe/ipe-policy.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-secfs.h"
+#include "ipe-policy.h"
+#include "ipe-parse.h"
+#include "ipe-audit.h"
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/lockdep.h>
+#include <linux/fs.h>
+#include <linux/security.h>
+#include <linux/rcupdate.h>
+
+#define VER_TO_UINT64(_major, _minor, _rev) \
+ ((((((u64)(_major)) << 16) | ((u64)(_minor))) << 16) | ((u64)(_rev)))
+
+/**
+ * ipe_is_version_allowed: Determine if @new has a greater or equal
+ * policy version than @old.
+ * @old: The policy to compare against.
+ * @new: The policy staged to replace @old.
+ *
+ * Return:
+ * true - @new has a policy version >= than @old
+ * false - @new does not have a policy version >= than @old
+ */
+static bool ipe_is_version_allowed(const struct ipe_pol_ver *old,
+ const struct ipe_pol_ver *new)
+{
+ u64 old_ver = VER_TO_UINT64(old->major, old->minor, old->rev);
+ u64 new_ver = VER_TO_UINT64(new->major, new->minor, new->rev);
+
+ return new_ver >= old_ver;
+}
+
+/**
+ * ipe_is_valid_policy: determine if @old is allowed to replace @new.
+ * @old: policy that the @new is supposed to replace. Can be NULL.
+ * @new: the policy that is supposed to replace @new.
+ *
+ * Return:
+ * true - @new can replace @old
+ * false - @new cannot replace @old
+ */
+bool ipe_is_valid_policy(const struct ipe_policy *old,
+ const struct ipe_policy *new)
+{
+ if (old)
+ return ipe_is_version_allowed(&old->policy_version,
+ &new->policy_version);
+ return true;
+}
+
+/**
+ * ipe_is_active_policy: Determine if @policy is the currently active policy.
+ * @policy: Policy to check if it's the active policy.
+ *
+ * Return:
+ * true - @policy is the active policy
+ * false - @policy is not the active policy
+ */
+bool ipe_is_active_policy(const struct ipe_policy *policy)
+{
+ return rcu_access_pointer(ipe_active_policy) == policy;
+}
+
+/**
+ * ipe_update_active_policy: Determine if @old is the active policy, and update
+ * the active policy if necessary.
+ * @old: The previous policy that the update is trying to replace.
+ * @new: The new policy attempting to replace @old.
+ *
+ * If @old is not the active policy, nothing will be done.
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - Invalid Policy
+ */
+int ipe_update_active_policy(const struct ipe_policy *old,
+ const struct ipe_policy *new)
+{
+ const struct ipe_policy *curr = NULL;
+
+ lockdep_assert_held(&ipe_policy_lock);
+
+ /* no active policy, safe to ignore */
+ if (!rcu_access_pointer(ipe_active_policy))
+ return 0;
+
+ curr = rcu_dereference_protected(ipe_active_policy,
+ lockdep_is_held(&ipe_policy_lock));
+
+ if (curr == old) {
+ if (!ipe_is_valid_policy(curr, new))
+ return -EINVAL;
+
+ ipe_audit_policy_activation(new);
+
+ (void)rcu_replace_pointer(ipe_active_policy, new,
+ lockdep_is_held(&ipe_policy_lock));
+ }
+
+ return 0;
+}
+
+/**
+ * ipe_activate_policy: Set a specific policy as the active policy.
+ * @pol: The policy to set as the active policy.
+ *
+ * This is only called by the securityfs entry,
+ * "$securityfs/ipe/policies/$policy_name/active".
+ *
+ * Return:
+ * 0 - OK
+ * -EINVAL - Policy that is being activated is lower in version than
+ * currently running policy.
+ */
+int ipe_activate_policy(const struct ipe_policy *pol)
+{
+ const struct ipe_policy *curr = NULL;
+
+ lockdep_assert_held(&ipe_policy_lock);
+
+ curr = rcu_dereference_protected(ipe_active_policy,
+ lockdep_is_held(&ipe_policy_lock));
+
+ /*
+ * User-set policies must be >= to current running policy.
+ */
+ if (!ipe_is_valid_policy(curr, pol))
+ return -EINVAL;
+
+ ipe_audit_policy_activation(pol);
+
+ /* cleanup of this pointer is handled by the secfs removal */
+ (void)rcu_replace_pointer(ipe_active_policy, pol,
+ lockdep_is_held(&ipe_policy_lock));
+
+ return 0;
+}
diff --git a/security/ipe/ipe-policy.h b/security/ipe/ipe-policy.h
index c0c9f2962c92..b3ef3d3d2e7f 100644
--- a/security/ipe/ipe-policy.h
+++ b/security/ipe/ipe-policy.h
@@ -14,9 +14,6 @@
#ifndef IPE_POLICY_H
#define IPE_POLICY_H
-#define IPE_HEADER_POLICY_NAME "policy_name"
-#define IPE_HEADER_POLICY_VERSION "policy_version"
-
extern const char *const ipe_boot_policy;
extern const struct ipe_policy *ipe_active_policy;
@@ -59,4 +56,14 @@ struct ipe_policy {
struct ipe_rule_table ops[ipe_op_max - 1];
};
+bool ipe_is_valid_policy(const struct ipe_policy *old,
+ const struct ipe_policy *new);
+
+bool ipe_is_active_policy(const struct ipe_policy *policy);
+
+int ipe_update_active_policy(const struct ipe_policy *old,
+ const struct ipe_policy *new);
+
+int ipe_activate_policy(const struct ipe_policy *policy);
+
#endif /* IPE_POLICY_H */
diff --git a/security/ipe/ipe-prop-internal.h b/security/ipe/ipe-prop-internal.h
index 95a2081e77ee..7c14b204be13 100644
--- a/security/ipe/ipe-prop-internal.h
+++ b/security/ipe/ipe-prop-internal.h
@@ -10,9 +10,19 @@
#ifndef IPE_PROPERTY_INTERNAL_H
#define IPE_PROPERTY_INTERNAL_H
-#define IPE_PROPERTY_OPERATION "op"
-#define IPE_PROPERTY_DEFAULT "DEFAULT"
-#define IPE_PROPERTY_ACTION "action"
+/* built-in tokens */
+#define IPE_HEADER_POLICY_NAME "policy_name"
+#define IPE_HEADER_POLICY_VERSION "policy_version"
+#define IPE_PROPERTY_OPERATION "op"
+#define IPE_PROPERTY_DEFAULT "DEFAULT"
+#define IPE_PROPERTY_ACTION "action"
+
+/* Version strings for built-in tokens */
+#define IPE_PROPERTY_OPERATION_VER IPE_PROPERTY_OPERATION "=1"
+#define IPE_PROPERTY_ACTION_VER IPE_PROPERTY_ACTION "=1"
+#define IPE_PROPERTY_DEFAULT_VER IPE_PROPERTY_DEFAULT "=1"
+#define IPE_HEADER_POLICY_NAME_VER IPE_HEADER_POLICY_NAME "=1"
+#define IPE_HEADER_POLICY_VERSION_VER IPE_HEADER_POLICY_VERSION "=1"
#define IPE_OP_EXECUTE "EXECUTE"
#define IPE_OP_FIRMWARE "FIRMWARE"
@@ -23,6 +33,8 @@
#define IPE_OP_KMODULE "KMODULE"
#define IPE_OP_KERNEL_READ "KERNEL_READ"
+#define IPE_UNKNOWN "UNKNOWN"
+
struct ipe_prop_reg {
struct rb_node node;
const struct ipe_property *prop;
diff --git a/security/ipe/ipe-property.c b/security/ipe/ipe-property.c
index d4b0283f86bd..262da9f622d6 100644
--- a/security/ipe/ipe-property.c
+++ b/security/ipe/ipe-property.c
@@ -12,7 +12,7 @@
#include <linux/slab.h>
/* global root containing all registered properties */
-struct rb_root ipe_registry_root = RB_ROOT;
+static struct rb_root ipe_registry_root = RB_ROOT;
/**
* reg_lookup: Attempt to find a `prop_reg` structure with property_name @key.
@@ -70,7 +70,8 @@ const struct ipe_property *ipe_lookup_prop(const char *key)
* the system, after calling ipe_register_property.
*
* All necessary properties need to be loaded via this method before
- * loading a policy, otherwise the properties will be ignored as unknown.
+ * loading a policy, otherwise the marked as unknown, and cause parsing to
+ * fail.
*
* Return:
* 0 - OK
@@ -113,9 +114,9 @@ int ipe_register_property(const struct ipe_property *prop)
/**
* ipe_for_each_prop: Iterate over all currently-registered properties
- * calling @fn on the values, and providing @view @ctx.
+ * calling @fn on the values, and providing @view @ctx.
* @view: The function to call for each property. This is given the property
- * structure as the first argument, and @ctx as the second.
+ * structure as the first argument, and @ctx as the second.
* @ctx: caller-specified context that is passed to the function. Can be NULL.
*
* Return:
diff --git a/security/ipe/ipe-property.h b/security/ipe/ipe-property.h
index cf570d52d0d2..8bb2e2c1619c 100644
--- a/security/ipe/ipe-property.h
+++ b/security/ipe/ipe-property.h
@@ -86,6 +86,7 @@ typedef void (*ipe_free_value)(void **value);
struct ipe_property {
const char *const property_name;
+ u16 version;
ipe_property_evaluator eval;
ipe_property_audit rule_audit;
ipe_ctx_audit ctx_audit;
diff --git a/security/ipe/ipe-secfs.c b/security/ipe/ipe-secfs.c
new file mode 100644
index 000000000000..006619598d57
--- /dev/null
+++ b/security/ipe/ipe-secfs.c
@@ -0,0 +1,1309 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-parse.h"
+#include "ipe-secfs.h"
+#include "ipe-policy.h"
+#include "ipe-audit.h"
+
+#include <linux/types.h>
+#include <linux/security.h>
+#include <linux/fs.h>
+#include <linux/rcupdate.h>
+#include <linux/mutex.h>
+#include <linux/init.h>
+#include <linux/dcache.h>
+#include <linux/namei.h>
+#include <linux/verification.h>
+#include <linux/capability.h>
+
+#define IPE_ROOT "ipe"
+#define IPE_POLICIES "policies"
+#define NEW_POLICY "new_policy"
+#define IPE_PROPERTY_CFG "property_config"
+#define IPE_SUCCESS_AUDIT "success_audit"
+#define IPE_ENFORCE "enforce"
+
+#define IPE_FULL_CONTENT "raw"
+#define IPE_INNER_CONTENT "content"
+#define IPE_ACTIVE_POLICY "active"
+#define IPE_DELETE_POLICY "delete"
+
+struct ipe_policy_node {
+ u8 *data;
+ size_t data_len;
+ const u8 *content;
+ size_t content_size;
+
+ struct ipe_policy *parsed;
+};
+
+/* root directory */
+static struct dentry *securityfs_root __ro_after_init;
+
+/* subdirectory containing policies */
+static struct dentry *policies_root __ro_after_init;
+
+/* boot policy */
+static struct dentry *boot_policy_node __ro_after_init;
+
+/* top-level IPE commands */
+static struct dentry *new_policy_node __ro_after_init;
+static struct dentry *property_cfg_node __ro_after_init;
+static struct dentry *enforce_node __ro_after_init;
+static struct dentry *success_audit_node __ro_after_init;
+
+/* lock for synchronizing writers across ipe policy */
+DEFINE_MUTEX(ipe_policy_lock);
+
+/**
+ * get_int_user - retrieve a single integer from a string located in userspace.
+ * @data: usespace address to parse for an integer
+ * @len: length of @data
+ * @offset: offset into @data. Unused.
+ * @value: pointer to a value to propagate with the result
+ *
+ * Return:
+ * 0 - OK
+ * -ENOMEM - allocation failed
+ * -EINVAL - more than 1 integer was present
+ * Other - see strnpy_from_user
+ */
+static int get_int_user(const char __user *data, size_t len, loff_t *offset,
+ int *value)
+{
+ int rc = 0;
+ char *buffer = NULL;
+
+ buffer = kzalloc(len + 1, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ rc = strncpy_from_user(buffer, data, len + 1);
+ if (rc < 0)
+ goto out;
+
+ rc = kstrtoint(buffer, 10, value);
+out:
+ kfree(buffer);
+ return rc;
+}
+
+/**
+ * ipe_get_audit_mode - retrieve the current value of the success_audit flag
+ * as a string representation.
+ * @f: The file structure representing the securityfs entry. Unused.
+ * @data: userspace buffer to place the result
+ * @len: length of @data
+ * @offset: offset into @data
+ *
+ * This is the handler for the 'read' syscall on the securityfs node,
+ * ipe/success_audit
+ *
+ * Return:
+ * > 0 - OK
+ * < 0 - Error, see simple_read_from_buffer
+ */
+static ssize_t ipe_get_audit_mode(struct file *f, char __user *data, size_t len,
+ loff_t *offset)
+{
+ char tmp[3] = { 0 };
+
+ snprintf(tmp, ARRAY_SIZE(tmp), "%c\n", (ipe_success_audit) ? '1' : '0');
+
+ return simple_read_from_buffer(data, len, offset, tmp,
+ ARRAY_SIZE(tmp));
+}
+
+/**
+ * ipe_set_audit_mode - change the value of the ipe_success_audit flag.
+ * @f: The file structure representing the securityfs entry
+ * @data: userspace buffer containing value to be set. Should be "1" or "0".
+ * @len: length of @data
+ * @offset: offset into @data
+ *
+ * Return:
+ * > 0 - OK
+ * -EPERM - if MAC system available, missing CAP_MAC_ADMIN.
+ * -EINVAL - value written was not "1" or "0".
+ */
+static ssize_t ipe_set_audit_mode(struct file *f, const char __user *data, size_t len,
+ loff_t *offset)
+{
+ int v = 0;
+ int rc = 0;
+
+ if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
+ return -EPERM;
+
+ rc = get_int_user(data, len, offset, &v);
+ if (rc)
+ return rc;
+
+ if (v != 0 && v != 1)
+ return -EINVAL;
+
+ ipe_success_audit = v == 1;
+
+ return len;
+}
+
+static const struct file_operations audit_ops = {
+ .read = ipe_get_audit_mode,
+ .write = ipe_set_audit_mode
+};
+
+#ifdef CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH
+
+/**
+ * ipe_get_enforce - retrieve the current value of the ipe_enforce flag
+ * as a string representation.
+ * @f: The file structure representing the securityfs entry. Unused.
+ * @data: userspace buffer to place the result
+ * @len: length of @data
+ * @offset: offset into @data
+ *
+ * This is the handler for the 'read' syscall on the securityfs node,
+ * ipe/enforce
+ *
+ * Return:
+ * > 0 - OK
+ * < 0 - Error, see simple_read_from_buffer
+ */
+static ssize_t ipe_get_enforce(struct file *f, char __user *data, size_t len,
+ loff_t *offset)
+{
+ char tmp[3] = { 0 };
+
+ snprintf(tmp, ARRAY_SIZE(tmp), "%c\n", (ipe_enforce) ? '1' : '0');
+
+ return simple_read_from_buffer(data, len, offset, tmp,
+ ARRAY_SIZE(tmp));
+}
+
+/**
+ * ipe_set_enforce - change the value of the ipe_enforce flag.
+ * @f: The file structure representing the securityfs entry
+ * @data: userspace buffer containing value to be set. Should be "1" or "0".
+ * @len: length of @data
+ * @offset: offset into @data
+ *
+ * Return:
+ * > 0 - OK
+ * -EPERM - if MAC system available, missing CAP_MAC_ADMIN.
+ * -EINVAL - value written was not "1" or "0".
+ */
+static ssize_t ipe_set_enforce(struct file *f, const char __user *data, size_t len,
+ loff_t *offset)
+{
+ int v = 0;
+ int rc = 0;
+ bool ret = 0;
+
+ if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
+ return -EPERM;
+
+ rc = get_int_user(data, len, offset, &v);
+ if (rc)
+ return rc;
+
+ if (v != 0 && v != 1)
+ return -EINVAL;
+
+ ret = v == 1;
+
+ if (ret != ipe_enforce)
+ ipe_audit_mode(ret);
+
+ ipe_enforce = ret;
+
+ return len;
+}
+
+static const struct file_operations enforce_ops = {
+ .read = ipe_get_enforce,
+ .write = ipe_set_enforce
+};
+
+/**
+ * ipe_init_enforce_node - Wrapper around securityfs_create_file for the
+ * ipe/enforce securityfs node.
+ * @root: securityfs node that is the parent of the new node to be created
+ *
+ * This allows this function to be no-op'd when the permissive switch is
+ * disabled.
+ *
+ * Return:
+ * See securityfs_create_file.
+ */
+static inline struct dentry *ipe_init_enforce_node(struct dentry *root)
+{
+ return securityfs_create_file(IPE_ENFORCE, 0644, root, NULL,
+ &enforce_ops);
+}
+
+#else
+
+/**
+ * ipe_init_enforce_node - Wrapper around securityfs_create_file for the
+ * ipe/enforce securityfs node.
+ * @root: Unused
+ *
+ * This allows this function to be no-op'd when the permissive switch is
+ * disabled.
+ *
+ * Return:
+ * NULL.
+ */
+static inline struct dentry *ipe_init_enforce_node(struct dentry *root)
+{
+ return NULL;
+}
+
+#endif /* CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH */
+
+/**
+ * retrieve_backed_dentry: Retrieve a dentry with a backing inode, identified
+ * by @name, under @parent.
+ * @name: Name of the dentry under @parent.
+ * @parent: The parent dentry to search under for @name.
+ * @size: Length of @name.
+ *
+ * This takes a reference to the returned dentry. Caller needs to call dput
+ * to drop the reference.
+ *
+ * Return:
+ * valid dentry - OK
+ * ERR_PTR - Error, see lookup_one_len_unlocked
+ * NULL - No backing inode was found
+ */
+static struct dentry *retrieve_backed_dentry(const char *name,
+ struct dentry *parent,
+ size_t size)
+{
+ struct dentry *tmp = NULL;
+
+ tmp = lookup_one_len_unlocked(name, parent, size);
+ if (IS_ERR(tmp))
+ return tmp;
+
+ if (!d_really_is_positive(tmp))
+ return NULL;
+
+ return tmp;
+}
+
+/**
+ * alloc_size_cb: Callback for determining the allocation size of the grammar
+ * buffer
+ * @prop: ipe_property structure to determine allocation size
+ * @ctx: void* representing a size_t* to add the allocation size to.
+ *
+ * Return:
+ * 0 - Always
+ */
+static int alloc_size_cb(const struct ipe_property *prop, void *ctx)
+{
+ size_t *ref = ctx;
+ char tmp[6] = { 0 };
+
+ snprintf(tmp, ARRAY_SIZE(tmp), "%d", prop->version);
+
+ /* property_name=u16\n */
+ *ref += strlen(prop->property_name) + strlen(tmp) + 2;
+
+ return 0;
+}
+
+/**
+ * build_cfg_str: Callback to populate the previously-allocated string
+ * buffer for ipe's grammar version with the content.
+ * @prop: ipe_property structure to determine allocation size
+ * @ctx: void* representing a char* to append the population to.
+ *
+ * Return:
+ * 0 - Always
+ */
+static int build_cfg_str(const struct ipe_property *prop, void *ctx)
+{
+ char *ref = (char *)ctx;
+ char tmp[6] = { 0 };
+
+ snprintf(tmp, ARRAY_SIZE(tmp), "%d", prop->version);
+ strcat(ref, prop->property_name);
+ strcat(ref, "=");
+ strcat(ref, tmp);
+ strcat(ref, "\n");
+
+ return 0;
+}
+
+/**
+ * create_new_prop_cfg: create a new property configuration string for consumers
+ * of IPE policy.
+ *
+ * This function will iterate over all currently registered properties, and
+ * return a string of form:
+ *
+ * property1=version1\n
+ * property2=version2\n
+ * ...
+ * propertyN=versionN
+ *
+ * Where propertyX is the property_name and versionX is the version associated.
+ *
+ * Return:
+ * !ERR_PTR - Success
+ * ERR_PTR(-ENOMEM) - Allocation Failed
+ */
+static char *create_new_prop_cfg(void)
+{
+ size_t i;
+ ssize_t rc = 0;
+ size_t alloc = 0;
+ char *ret = NULL;
+ const char *const built_ins[] = {
+ IPE_PROPERTY_OPERATION_VER,
+ IPE_PROPERTY_ACTION_VER,
+ IPE_PROPERTY_DEFAULT_VER,
+ IPE_HEADER_POLICY_NAME_VER,
+ IPE_HEADER_POLICY_VERSION_VER
+ };
+
+ for (i = 0; i < ARRAY_SIZE(built_ins); ++i)
+ alloc += strlen(built_ins[i]) + 1; /* \n */
+
+ (void)ipe_for_each_prop(alloc_size_cb, (void *)&alloc);
+ ++alloc; /* null for strcat */
+
+ ret = kzalloc(alloc, GFP_KERNEL);
+ if (!ret)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < ARRAY_SIZE(built_ins); ++i) {
+ strcat(ret, built_ins[i]);
+ strcat(ret, "\n");
+ }
+
+ rc = ipe_for_each_prop(build_cfg_str, (void *)ret);
+ if (rc)
+ goto err;
+
+ return ret;
+err:
+ kfree(ret);
+ return ERR_PTR(rc);
+}
+
+/**
+ * ipe_get_prop_cfg: Get (or allocate if one does not exist) the property
+ * configuration string for IPE.
+ *
+ * @f: File representing the securityfs entry.
+ * @data: User mode buffer to place the configuration string.
+ * @len: Length of @data.
+ * @offset: Offset into @data.
+ *
+ * As this string can only change on a new kernel build, this string
+ * is cached in the i_private field of @f's inode for subsequent calls.
+ *
+ * Return:
+ * < 0 - Error
+ * > 0 - Success, bytes written to @data
+ */
+static ssize_t ipe_get_prop_cfg(struct file *f, char __user *data, size_t size,
+ loff_t *offset)
+{
+ ssize_t rc = 0;
+ const char *cfg = NULL;
+ struct inode *grammar = d_inode(property_cfg_node);
+
+ inode_lock(grammar);
+
+ /*
+ * This can only change with a new kernel build,
+ * so cache the result in i->private
+ */
+ if (IS_ERR_OR_NULL(grammar->i_private)) {
+ grammar->i_private = create_new_prop_cfg();
+ if (IS_ERR(grammar->i_private)) {
+ rc = PTR_ERR(grammar->i_private);
+ goto out;
+ }
+ }
+ cfg = (const char *)grammar->i_private;
+
+ rc = simple_read_from_buffer(data, size, offset, cfg, strlen(cfg));
+
+out:
+ inode_unlock(grammar);
+ return rc;
+}
+
+static const struct file_operations prop_cfg_ops = {
+ .read = ipe_get_prop_cfg
+};
+
+/**
+ * ipe_free_policy_node: Free an ipe_policy_node structure allocated by
+ * ipe_alloc_policy_node.
+ * @n: ipe_policy_node to free
+ */
+static void ipe_free_policy_node(struct ipe_policy_node *n)
+{
+ if (IS_ERR_OR_NULL(n))
+ return;
+
+ ipe_free_policy(n->parsed);
+ kfree(n->data);
+
+ kfree(n);
+}
+
+/**
+ * alloc_callback: Callback given to verify_pkcs7_signature function to set
+ * the inner content reference and parse the policy.
+ * @ctx: "ipe_policy_node" to set inner content, size and parsed policy of.
+ * @data: Start of PKCS#7 inner content.
+ * @len: Length of @data.
+ * @asn1hdrlen: Unused.
+ *
+ * Return:
+ * 0 - OK
+ * ERR_PTR(-EBADMSG) - Invalid policy syntax
+ * ERR_PTR(-ENOMEM) - Out of memory
+ */
+static int alloc_callback(void *ctx, const void *data, size_t len,
+ size_t asn1hdrlen)
+{
+ char *cpy = NULL;
+ struct ipe_policy *pol = NULL;
+ struct ipe_policy_node *n = (struct ipe_policy_node *)ctx;
+
+ n->content = (const u8 *)data;
+ n->content_size = len;
+
+ if (len == 0)
+ return -EBADMSG;
+
+ cpy = kzalloc(len + 1, GFP_KERNEL);
+ if (!cpy)
+ return -ENOMEM;
+
+ (void)memcpy(cpy, data, len);
+
+ pol = ipe_parse_policy(cpy);
+ if (IS_ERR(pol)) {
+ kfree(cpy);
+ return PTR_ERR(pol);
+ }
+
+ n->parsed = pol;
+ kfree(cpy);
+ return 0;
+}
+
+/**
+ * ipe_delete_policy_tree - delete the policy subtree under
+ * $securityfs/ipe/policies.
+ * @policy_root: the policy root directory, i.e.
+ * $securityfs/ipe/policies/$policy_name
+ *
+ * Return:
+ * 0 - OK
+ * -EPERM - Tree being deleted is the active policy
+ * -ENOENT - A subnode is missing under the tree.
+ * Other - see lookup_one_len_unlocked.
+ */
+static int ipe_delete_policy_tree(struct dentry *policy_root)
+{
+ int rc = 0;
+ struct dentry *raw = NULL;
+ struct dentry *active = NULL;
+ struct dentry *content = NULL;
+ struct dentry *delete = NULL;
+ const struct ipe_policy_node *target = NULL;
+
+ /* ensure the active policy cannot be changed */
+ lockdep_assert_held(&ipe_policy_lock);
+
+ /* fail if it's the active policy */
+ target = (const struct ipe_policy_node *)d_inode(policy_root)->i_private;
+ if (ipe_is_active_policy(target->parsed)) {
+ rc = -EPERM;
+ goto out;
+ }
+
+ raw = retrieve_backed_dentry(IPE_FULL_CONTENT, policy_root,
+ strlen(IPE_FULL_CONTENT));
+ if (IS_ERR_OR_NULL(raw)) {
+ rc = IS_ERR(raw) ? PTR_ERR(raw) : -ENOENT;
+ goto out;
+ }
+
+ content = retrieve_backed_dentry(IPE_INNER_CONTENT, policy_root,
+ strlen(IPE_INNER_CONTENT));
+ if (IS_ERR_OR_NULL(content)) {
+ rc = IS_ERR(content) ? PTR_ERR(content) : -ENOENT;
+ goto out_free_raw;
+ }
+
+ active = retrieve_backed_dentry(IPE_ACTIVE_POLICY, policy_root,
+ strlen(IPE_ACTIVE_POLICY));
+ if (IS_ERR_OR_NULL(active)) {
+ rc = IS_ERR(active) ? PTR_ERR(active) : -ENOENT;
+ goto out_free_content;
+ }
+
+ delete = retrieve_backed_dentry(IPE_DELETE_POLICY, policy_root,
+ strlen(IPE_DELETE_POLICY));
+ if (IS_ERR_OR_NULL(active)) {
+ rc = IS_ERR(active) ? PTR_ERR(active) : -ENOENT;
+ goto out_free_active;
+ }
+
+ inode_lock(d_inode(policy_root));
+ ipe_free_policy_node(d_inode(policy_root)->i_private);
+ d_inode(policy_root)->i_private = NULL;
+ inode_unlock(d_inode(policy_root));
+
+ /* drop references from acquired in this function */
+ dput(raw);
+ dput(content);
+ dput(policy_root);
+ dput(active);
+ dput(delete);
+
+ /* drop securityfs' references */
+ securityfs_remove(raw);
+ securityfs_remove(content);
+ securityfs_remove(policy_root);
+ securityfs_remove(active);
+ securityfs_remove(delete);
+
+ return rc;
+
+out_free_active:
+ dput(active);
+out_free_content:
+ dput(content);
+out_free_raw:
+ dput(raw);
+out:
+ return rc;
+}
+
+/**
+ * ipe_delete_policy: Delete a policy, which is stored in this file's parent
+ * dentry's inode.
+ * @f: File representing the securityfs entry.
+ * @data: Buffer containing the value 1.
+ * @len: sizeof(u8).
+ * @offset: Offset into @data.
+ *
+ * Return:
+ * > 0 - OK
+ * -ENOMEM - Out of memory
+ * -EINVAL - Incorrect parameter
+ * -EPERM - Policy is active
+ * -ENOENT - A policy subnode does not exist
+ * -EPERM - if a MAC subsystem is enabled, missing CAP_MAC_ADMIN
+ * Other - See retrieve_backed_dentry
+ */
+static ssize_t ipe_delete_policy(struct file *f, const char __user *data,
+ size_t len, loff_t *offset)
+{
+ int v = 0;
+ ssize_t rc = 0;
+ struct inode *policy_i = NULL;
+ struct dentry *policy_root = NULL;
+
+ if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
+ return -EPERM;
+
+ rc = get_int_user(data, len, offset, &v);
+ if (rc)
+ return rc;
+
+ if (v != 1)
+ return -EINVAL;
+
+ policy_root = f->f_path.dentry->d_parent;
+ policy_i = d_inode(policy_root);
+
+ if (!policy_i->i_private)
+ return -ENOENT;
+
+ /* guarantee active policy cannot change */
+ mutex_lock(&ipe_policy_lock);
+
+ rc = ipe_delete_policy_tree(policy_root);
+ if (rc)
+ goto out_unlock;
+
+ mutex_unlock(&ipe_policy_lock);
+ synchronize_rcu();
+
+ return len;
+
+out_unlock:
+ mutex_unlock(&ipe_policy_lock);
+ return rc;
+}
+
+static const struct file_operations policy_delete_ops = {
+ .write = ipe_delete_policy
+};
+
+/**
+ * ipe_alloc_policy_node: Allocate a new ipe_policy_node structure.
+ * @data: Raw enveloped PKCS#7 data that represents the policy.
+ * @len: Length of @data.
+ *
+ * Return:
+ * valid ipe_policy_node - OK
+ * ERR_PTR(-EBADMSG) - Invalid policy syntax
+ * ERR_PTR(-ENOMEM) - Out of memory
+ */
+static struct ipe_policy_node *ipe_alloc_policy_node(const u8 *data,
+ size_t len)
+{
+ int rc = 0;
+ struct ipe_policy_node *node = NULL;
+
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (!node) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ node->data_len = len;
+ node->data = kmemdup(data, len, GFP_KERNEL);
+ if (!node->data) {
+ rc = -ENOMEM;
+ goto out2;
+ }
+
+ rc = verify_pkcs7_signature(node->content, node->content_size,
+ node->data, node->data_len, NULL,
+ VERIFYING_UNSPECIFIED_SIGNATURE,
+ alloc_callback, node);
+ if (rc != 0)
+ goto out2;
+
+ return node;
+out2:
+ ipe_free_policy_node(node);
+out:
+ return ERR_PTR(rc);
+}
+
+/**
+ * ipe_read_policy: Read the raw content (full enveloped PKCS7) data of
+ * the policy stored within the file's parent inode.
+ * @f: File representing the securityfs entry.
+ * @data: User mode buffer to place the raw pkcs7.
+ * @len: Length of @data.
+ * @offset: Offset into @data.
+ *
+ * Return:
+ * > 0 - OK
+ * -ENOMEM - Out of memory
+ */
+static ssize_t ipe_read_policy(struct file *f, char __user *data,
+ size_t size, loff_t *offset)
+{
+ ssize_t rc = 0;
+ size_t avail = 0;
+ struct inode *root = NULL;
+ const struct ipe_policy_node *node = NULL;
+
+ root = d_inode(f->f_path.dentry->d_parent);
+
+ inode_lock_shared(root);
+ node = (const struct ipe_policy_node *)root->i_private;
+
+ avail = node->data_len;
+ rc = simple_read_from_buffer(data, size, offset, node->data, avail);
+
+ inode_unlock_shared(root);
+ return rc;
+}
+
+/**
+ * ipe_update_policy: Update a policy in place with a new PKCS7 policy.
+ * @f: File representing the securityfs entry.
+ * @data: Buffer user mode to place the raw pkcs7.
+ * @len: Length of @data.
+ * @offset: Offset into @data.
+ *
+ * Return:
+ * 0 - OK
+ * -EBADMSG - Invalid policy format
+ * -ENOMEM - Out of memory
+ * -EPERM - if a MAC subsystem is enabled, missing CAP_MAC_ADMIN
+ * -EINVAL - Incorrect policy name for this node, or version is < current
+ */
+static ssize_t ipe_update_policy(struct file *f, const char __user *data,
+ size_t len, loff_t *offset)
+{
+ ssize_t rc = 0;
+ u8 *cpy = NULL;
+ struct inode *root = NULL;
+ struct crypto_shash *tfm = NULL;
+ struct ipe_policy_node *new = NULL;
+ struct ipe_policy_node *old = NULL;
+
+ if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
+ return -EPERM;
+
+ cpy = memdup_user(data, len);
+ if (IS_ERR(cpy))
+ return PTR_ERR(cpy);
+
+ new = ipe_alloc_policy_node(cpy, len);
+ if (IS_ERR(new)) {
+ rc = PTR_ERR(new);
+ goto out_free_cpy;
+ }
+
+ tfm = crypto_alloc_shash("sha1", 0, 0);
+ if (IS_ERR(tfm))
+ goto out_free_cpy;
+
+ root = d_inode(f->f_path.dentry->d_parent);
+ inode_lock(root);
+ mutex_lock(&ipe_policy_lock);
+
+ old = (struct ipe_policy_node *)root->i_private;
+
+ if (strcmp(old->parsed->policy_name, new->parsed->policy_name)) {
+ rc = -EINVAL;
+ goto out_unlock_inode;
+ }
+
+ if (!ipe_is_valid_policy(old->parsed, new->parsed)) {
+ rc = -EINVAL;
+ goto out_unlock_inode;
+ }
+
+ rc = ipe_update_active_policy(old->parsed, new->parsed);
+ if (rc != 0)
+ goto out_unlock_inode;
+
+ ipe_audit_policy_load(new->parsed, new->data, new->data_len, tfm);
+ swap(root->i_private, new);
+
+ mutex_unlock(&ipe_policy_lock);
+ synchronize_rcu();
+
+ inode_unlock(root);
+ kfree(cpy);
+ ipe_free_policy_node(new);
+ crypto_free_shash(tfm);
+
+ return len;
+
+out_unlock_inode:
+ mutex_unlock(&ipe_policy_lock);
+ inode_unlock(root);
+ ipe_free_policy_node(new);
+ crypto_free_shash(tfm);
+out_free_cpy:
+ kfree(cpy);
+ return rc;
+}
+
+static const struct file_operations policy_raw_ops = {
+ .read = ipe_read_policy,
+ .write = ipe_update_policy
+};
+
+/**
+ * ipe_read_content: Read the inner content of the enveloped PKCS7 data,
+ * representing the IPE policy.
+ * @f: File representing the securityfs entry.
+ * @data: User mode buffer to place the inner content of the pkcs7 data.
+ * @len: Length of @data.
+ * @offset: Offset into @data.
+ *
+ * Return:
+ * > 0 - OK
+ * -ENOMEM - Out of memory
+ */
+static ssize_t ipe_read_content(struct file *f, char __user *data,
+ size_t size, loff_t *offset)
+{
+ ssize_t rc = 0;
+ size_t avail = 0;
+ struct inode *root = NULL;
+ const struct ipe_policy_node *node = NULL;
+
+ root = d_inode(f->f_path.dentry->d_parent);
+
+ inode_lock_shared(root);
+ node = (const struct ipe_policy_node *)root->i_private;
+
+ avail = node->content_size;
+ rc = simple_read_from_buffer(data, size, offset, node->content, avail);
+
+ inode_unlock_shared(root);
+ return rc;
+}
+
+static const struct file_operations policy_content_ops = {
+ .read = ipe_read_content
+};
+
+/**
+ * ipe_get_active - return a string representation of whether a policy
+ * is active.
+ * @f: File struct representing the securityfs node. Unused.
+ * @data: buffer to place the result.
+ * @len: length of @data.
+ * @offset: offset into @data.
+ *
+ * This is the 'read' syscall handler for
+ * $securityfs/ipe/policies/$policy_name/active
+ *
+ * Return:
+ * > 0 - OK
+ * < 0 - see simple_read_from_buffer.
+ */
+static ssize_t ipe_get_active(struct file *f, char __user *data, size_t len,
+ loff_t *offset)
+{
+ ssize_t rc = 0;
+ char tmp[3] = { 0 };
+ struct inode *root = NULL;
+ const struct ipe_policy_node *node = NULL;
+
+ root = d_inode(f->f_path.dentry->d_parent);
+ inode_lock_shared(root);
+
+ node = (const struct ipe_policy_node *)root->i_private;
+
+ snprintf(tmp, ARRAY_SIZE(tmp), "%c\n",
+ ipe_is_active_policy(node->parsed) ? '1' : '0');
+
+ rc = simple_read_from_buffer(data, len, offset, tmp,
+ ARRAY_SIZE(tmp));
+
+ inode_unlock_shared(root);
+
+ return rc;
+}
+
+/**
+ * ipe_set_active - mark a policy as active, causing IPE to start enforcing
+ * this policy.
+ * @f: File struct representing the securityfs node.
+ * @data: buffer containing data written to the securityfs node..
+ * @len: length of @data.
+ * @offset: offset into @data.
+ *
+ * This is the 'write' syscall handler for
+ * $securityfs/ipe/policies/$policy_name/active
+ *
+ * Return:
+ * > 0 - OK
+ * -EINVAL - Value written is not "1".
+ * -EPERM - if MAC system is enabled, missing CAP_MAC_ADMIN.
+ * Other - see ipe_activate_policy, get_int_user
+ */
+static ssize_t ipe_set_active(struct file *f, const char __user *data, size_t len,
+ loff_t *offset)
+{
+ int v = 0;
+ ssize_t rc = 0;
+ struct inode *root = NULL;
+ const struct ipe_policy_node *node = NULL;
+
+ if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
+ return -EPERM;
+
+ rc = get_int_user(data, len, offset, &v);
+ if (rc)
+ return rc;
+
+ if (v != 1)
+ return -EINVAL;
+
+ root = d_inode(f->f_path.dentry->d_parent);
+ mutex_lock(&ipe_policy_lock);
+ inode_lock_shared(root);
+
+ node = (const struct ipe_policy_node *)root->i_private;
+ rc = ipe_activate_policy(node->parsed);
+
+ inode_unlock_shared(root);
+ mutex_unlock(&ipe_policy_lock);
+ synchronize_rcu();
+
+ return len;
+}
+
+static const struct file_operations policy_active_ops = {
+ .read = ipe_get_active,
+ .write = ipe_set_active
+};
+
+/**
+ * ipe_alloc_policy_tree - allocate the proper subnodes for a policy under
+ * securityfs.
+ * @parent: The parent directory that these securityfs files should be created
+ * under.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - See securityfs_create_file
+ */
+static int ipe_alloc_policy_tree(struct dentry *parent)
+{
+ int rc = 0;
+ struct dentry *raw = NULL;
+ struct dentry *delete = NULL;
+ struct dentry *active = NULL;
+ struct dentry *content = NULL;
+
+ raw = securityfs_create_file(IPE_FULL_CONTENT, 0644, parent, NULL,
+ &policy_raw_ops);
+ if (IS_ERR(raw))
+ return PTR_ERR(raw);
+
+ content = securityfs_create_file(IPE_INNER_CONTENT, 0444, parent,
+ NULL, &policy_content_ops);
+ if (IS_ERR(raw)) {
+ rc = PTR_ERR(raw);
+ goto free_raw;
+ }
+
+ active = securityfs_create_file(IPE_ACTIVE_POLICY, 0644, parent, NULL,
+ &policy_active_ops);
+ if (IS_ERR(active)) {
+ rc = PTR_ERR(active);
+ goto free_content;
+ }
+
+ delete = securityfs_create_file(IPE_DELETE_POLICY, 0644, parent, NULL,
+ &policy_delete_ops);
+ if (IS_ERR(delete)) {
+ rc = PTR_ERR(delete);
+ goto free_active;
+ }
+
+ return rc;
+
+free_active:
+ securityfs_remove(active);
+free_content:
+ securityfs_remove(content);
+free_raw:
+ securityfs_remove(raw);
+
+ return rc;
+}
+
+/**
+ * ipe_build_policy_node: Build a new securityfs node for IPE policies.
+ * @data: Raw enveloped PKCS#7 data that represents the policy.
+ * @len: Length of @data.
+ *
+ * Return:
+ * 0 - OK
+ * -EEXIST - Policy already exists
+ * -EBADMSG - Invalid policy syntax
+ * -ENOMEM - Out of memory
+ */
+static int ipe_build_policy_node(const u8 *data, size_t len)
+{
+ int rc = 0;
+ struct dentry *root = NULL;
+ struct inode *root_i = NULL;
+ struct crypto_shash *tfm = NULL;
+ struct ipe_policy_node *node = NULL;
+
+ tfm = crypto_alloc_shash("sha1", 0, 0);
+ if (IS_ERR(tfm)) {
+ rc = PTR_ERR(tfm);
+ goto out;
+ }
+
+ node = ipe_alloc_policy_node(data, len);
+ if (IS_ERR(node)) {
+ rc = PTR_ERR(node);
+ goto free_hash;
+ }
+
+ root = securityfs_create_dir(node->parsed->policy_name,
+ policies_root);
+ if (IS_ERR(root)) {
+ rc = PTR_ERR(root);
+ goto free_private;
+ }
+
+ root_i = d_inode(root);
+
+ inode_lock(root_i);
+ root_i->i_private = node;
+ ipe_audit_policy_load(node->parsed, node->data, node->data_len, tfm);
+ inode_unlock(root_i);
+
+ rc = ipe_alloc_policy_tree(root);
+ if (rc)
+ goto free_secfs;
+
+ crypto_free_shash(tfm);
+ return rc;
+
+free_secfs:
+ securityfs_remove(root);
+free_private:
+ ipe_free_policy_node(node);
+free_hash:
+ crypto_free_shash(tfm);
+out:
+ return rc;
+}
+
+/**
+ * ipe_new_policy: Entry point of the securityfs node, "ipe/new_policy".
+ * @f: File representing the securityfs entry.
+ * @data: Raw enveloped PKCS#7 data that represents the policy.
+ * @len: Length of @data.
+ * @offset: Offset for @data.
+ *
+ * Return:
+ * > 0 - OK
+ * -EEXIST - Policy already exists
+ * -EBADMSG - Invalid policy syntax
+ * -ENOMEM - Out of memory
+ * -EPERM - if a MAC subsystem is enabled, missing CAP_MAC_ADMIN
+ */
+static ssize_t ipe_new_policy(struct file *f, const char __user *data,
+ size_t len, loff_t *offset)
+{
+ ssize_t rc = 0;
+ u8 *cpy = NULL;
+
+ if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN))
+ return -EPERM;
+
+ cpy = memdup_user(data, len);
+ if (IS_ERR(cpy))
+ return PTR_ERR(cpy);
+
+ rc = ipe_build_policy_node(cpy, len);
+
+ kfree(cpy);
+ return rc < 0 ? rc : len;
+}
+
+static const struct file_operations new_policy_ops = {
+ .write = ipe_new_policy
+};
+
+/**
+ * ipe_build_secfs_root: Build the root of securityfs for IPE.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - See securityfs_create_dir and securityfs_create_file
+ */
+static int __init ipe_build_secfs_root(void)
+{
+ int rc = 0;
+ struct dentry *new = NULL;
+ struct dentry *cfg = NULL;
+ struct dentry *root = NULL;
+ struct dentry *audit = NULL;
+ struct dentry *enforce = NULL;
+ struct dentry *policies = NULL;
+
+ root = securityfs_create_dir(IPE_ROOT, NULL);
+ if (IS_ERR(root)) {
+ rc = PTR_ERR(root);
+ goto out;
+ }
+
+ new = securityfs_create_file(NEW_POLICY, 0644, root, NULL,
+ &new_policy_ops);
+ if (IS_ERR(new)) {
+ rc = PTR_ERR(new);
+ goto out_free_root;
+ }
+
+ policies = securityfs_create_dir(IPE_POLICIES, root);
+ if (IS_ERR(policies)) {
+ rc = PTR_ERR(policies);
+ goto out_free_new;
+ }
+
+ cfg = securityfs_create_file(IPE_PROPERTY_CFG, 0444, root, NULL,
+ &prop_cfg_ops);
+ if (IS_ERR(cfg)) {
+ rc = PTR_ERR(cfg);
+ goto out_free_policies;
+ }
+
+ audit = securityfs_create_file(IPE_SUCCESS_AUDIT, 0644, root, NULL,
+ &audit_ops);
+ if (IS_ERR(cfg)) {
+ rc = PTR_ERR(audit);
+ goto out_free_cfg;
+ }
+
+ enforce = ipe_init_enforce_node(root);
+ if (IS_ERR(enforce)) {
+ rc = PTR_ERR(audit);
+ goto out_free_audit;
+ }
+
+ securityfs_root = root;
+ new_policy_node = new;
+ policies_root = policies;
+ property_cfg_node = cfg;
+ success_audit_node = audit;
+ enforce_node = enforce;
+
+ return rc;
+
+out_free_audit:
+ securityfs_remove(audit);
+out_free_cfg:
+ securityfs_remove(cfg);
+out_free_policies:
+ securityfs_remove(policies);
+out_free_new:
+ securityfs_remove(new);
+out_free_root:
+ securityfs_remove(root);
+out:
+ return rc;
+}
+
+/**
+ * ipe_build_boot_node: Build a policy node for IPE's boot policy.
+ *
+ * This differs from the normal policy nodes, as the IPE boot policy is
+ * read only, and only has the 'content' and 'active' nodes (as it is
+ * unsigned).
+ *
+ * Return:
+ * 0 - OK
+ * !0 - See securityfs_create_dir and securityfs_create_file
+ */
+static int __init ipe_build_boot_node(void)
+{
+ int rc = 0;
+ char *cpy = NULL;
+ struct inode *root_i = NULL;
+ struct dentry *root = NULL;
+ struct dentry *active = NULL;
+ struct dentry *content = NULL;
+ struct ipe_policy *parsed = NULL;
+ struct ipe_policy_node *node = NULL;
+
+ if (!ipe_boot_policy)
+ return 0;
+
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (!node) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ cpy = kstrdup(ipe_boot_policy, GFP_KERNEL);
+ if (!cpy) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ parsed = ipe_parse_policy(cpy);
+ if (IS_ERR(parsed)) {
+ rc = PTR_ERR(parsed);
+ goto out_free_policy;
+ }
+
+ node->content = ipe_boot_policy;
+ node->content_size = strlen(ipe_boot_policy);
+ node->parsed = parsed;
+
+ root = securityfs_create_dir(node->parsed->policy_name,
+ policies_root);
+ if (IS_ERR(root)) {
+ rc = PTR_ERR(root);
+ goto out_free_policy;
+ }
+
+ content = securityfs_create_file(IPE_INNER_CONTENT, 0444, root, NULL,
+ &policy_content_ops);
+ if (IS_ERR(content)) {
+ rc = PTR_ERR(content);
+ goto out_free_root;
+ }
+
+ active = securityfs_create_file(IPE_ACTIVE_POLICY, 0644, root, NULL,
+ &policy_active_ops);
+ if (IS_ERR(active)) {
+ rc = PTR_ERR(active);
+ goto out_free_content;
+ }
+
+ root_i = d_inode(root);
+
+ inode_lock(root_i);
+ root_i->i_private = node;
+ inode_unlock(root_i);
+
+ boot_policy_node = root;
+ mutex_lock(&ipe_policy_lock);
+ rc = ipe_activate_policy(node->parsed);
+ mutex_unlock(&ipe_policy_lock);
+ synchronize_rcu();
+
+ return rc;
+
+out_free_content:
+ securityfs_remove(active);
+out_free_root:
+ securityfs_remove(root);
+out_free_policy:
+ ipe_free_policy(parsed);
+out:
+ kfree(cpy);
+ kfree(node);
+ return rc;
+}
+
+/**
+ * ipe_securityfs_init: Initialize IPE's securityfs entries.
+ *
+ * This is called after the lsm initialization.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - Error
+ */
+static int __init ipe_securityfs_init(void)
+{
+ int rc = 0;
+
+ rc = ipe_build_secfs_root();
+ if (rc != 0)
+ goto err;
+
+ rc = ipe_build_boot_node();
+ if (rc != 0)
+ panic("IPE failed to initialize the boot policy: %d", rc);
+
+ return rc;
+err:
+ pr_err("failed to initialize secfs: %d", -rc);
+ return rc;
+}
+
+core_initcall(ipe_securityfs_init);
diff --git a/security/ipe/ipe-secfs.h b/security/ipe/ipe-secfs.h
new file mode 100644
index 000000000000..7732a8dcf11f
--- /dev/null
+++ b/security/ipe/ipe-secfs.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+#include <linux/types.h>
+
+#include "ipe-policy.h"
+
+#ifndef IPE_SECFS_H
+#define IPE_SECFS_H
+
+extern struct mutex ipe_policy_lock;
+
+#endif /* IPE_SECFS_H */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 12/12] cleanup: uapi/linux/audit.h
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Remove trailing whitespaces and align the integrity #defines in
linux/uapi/audit.h
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
include/uapi/linux/audit.h | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 5a634cca1d42..609b4a5e8a80 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -48,7 +48,7 @@
* 2500 - 2999 future user space (maybe integrity labels and related events)
*
* Messages from 1000-1199 are bi-directional. 1200-1299 & 2100 - 2999 are
- * exclusively user space. 1300-2099 is kernel --> user space
+ * exclusively user space. 1300-2099 is kernel --> user space
* communication.
*/
#define AUDIT_GET 1000 /* Get status */
@@ -78,7 +78,7 @@
#define AUDIT_LAST_USER_MSG 1199
#define AUDIT_FIRST_USER_MSG2 2100 /* More user space messages */
#define AUDIT_LAST_USER_MSG2 2999
-
+
#define AUDIT_DAEMON_START 1200 /* Daemon startup record */
#define AUDIT_DAEMON_END 1201 /* Daemon normal stop record */
#define AUDIT_DAEMON_ABORT 1202 /* Daemon error stop record */
@@ -140,20 +140,20 @@
#define AUDIT_MAC_CALIPSO_ADD 1418 /* NetLabel: add CALIPSO DOI entry */
#define AUDIT_MAC_CALIPSO_DEL 1419 /* NetLabel: del CALIPSO DOI entry */
-#define AUDIT_FIRST_KERN_ANOM_MSG 1700
-#define AUDIT_LAST_KERN_ANOM_MSG 1799
-#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */
-#define AUDIT_ANOM_ABEND 1701 /* Process ended abnormally */
-#define AUDIT_ANOM_LINK 1702 /* Suspicious use of file links */
-#define AUDIT_ANOM_CREAT 1703 /* Suspicious file creation */
-#define AUDIT_INTEGRITY_DATA 1800 /* Data integrity verification */
-#define AUDIT_INTEGRITY_METADATA 1801 /* Metadata integrity verification */
-#define AUDIT_INTEGRITY_STATUS 1802 /* Integrity enable status */
-#define AUDIT_INTEGRITY_HASH 1803 /* Integrity HASH type */
-#define AUDIT_INTEGRITY_PCR 1804 /* PCR invalidation msgs */
-#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */
-#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */
-#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */
+#define AUDIT_FIRST_KERN_ANOM_MSG 1700
+#define AUDIT_LAST_KERN_ANOM_MSG 1799
+#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */
+#define AUDIT_ANOM_ABEND 1701 /* Process ended abnormally */
+#define AUDIT_ANOM_LINK 1702 /* Suspicious use of file links */
+#define AUDIT_ANOM_CREAT 1703 /* Suspicious file creation */
+#define AUDIT_INTEGRITY_DATA 1800 /* Data integrity verification */
+#define AUDIT_INTEGRITY_METADATA 1801 /* Metadata integrity verification */
+#define AUDIT_INTEGRITY_STATUS 1802 /* Integrity enable status */
+#define AUDIT_INTEGRITY_HASH 1803 /* Integrity HASH type */
+#define AUDIT_INTEGRITY_PCR 1804 /* PCR invalidation msgs */
+#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */
+#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */
+#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */
#define AUDIT_INTEGRITY_POLICY_LOAD 1808 /* IPE Policy Load */
#define AUDIT_INTEGRITY_POLICY_ACTIVATE 1809 /* IPE Policy Activation */
#define AUDIT_INTEGRITY_EVENT 1810 /* IPE Evaluation Event */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 11/12] documentation: add ipe documentation
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add IPE's documentation to the kernel tree.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
Acked-by: Jonathan Corbet <corbet@lwn.net>
---
Documentation/admin-guide/LSM/index.rst | 1 +
Documentation/admin-guide/LSM/ipe.rst | 508 ++++++++++++++++++++++++
MAINTAINERS | 1 +
3 files changed, 510 insertions(+)
create mode 100644 Documentation/admin-guide/LSM/ipe.rst
diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst
index a6ba95fbaa9f..ce63be6d64ad 100644
--- a/Documentation/admin-guide/LSM/index.rst
+++ b/Documentation/admin-guide/LSM/index.rst
@@ -47,3 +47,4 @@ subdirectories.
tomoyo
Yama
SafeSetID
+ ipe
diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst
new file mode 100644
index 000000000000..2e6610c4a134
--- /dev/null
+++ b/Documentation/admin-guide/LSM/ipe.rst
@@ -0,0 +1,508 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Integrity Policy Enforcement (IPE)
+==================================
+
+Overview
+--------
+
+IPE is a Linux Security Module which allows for a configurable policy
+to enforce integrity requirements on the whole system. It attempts to
+solve the issue of Code Integrity: that any code being executed (or
+files being read), are identical to the version that was built by a
+trusted source.
+
+There are multiple implementations within the Linux kernel that solve
+some measure of integrity verification. For instance, device-mapper
+verity, which ensures integrity for a block device, and fs-verity which
+is a system that ensures integrity for a filesystem. What these
+implementations lack is a measure of run-time verification that binaries
+are sourced from these locations. IPE aims to address this gap.
+
+IPE is separated between two major components: A configurable policy,
+provided by the LSM ("IPE Core"), and deterministic attributes provided
+by the kernel to evaluate files against, ("IPE Properties").
+
+Use Cases
+---------
+
+IPE is designed for use is an embedded device with a specific purpose
+(e.g. network firewall device in a data center), where all software and
+configuration is built and provisioned by the owner.
+
+Ideally, a system which leverages IPE is not intended for general
+purpose computing and does not utilize any software or configuration
+built by a third party. An ideal system to leverage IPE has both mutable
+and immutable components, however, all binary executable code is
+immutable.
+
+For the highest level of security, platform firmware should verify the
+the kernel and optionally the root filesystem (e.g. via U-Boot verified
+boot). This allows the entire system to be integrity verified.
+
+Known Gaps
+----------
+
+IPE cannot verify the integrity of anonymous executable memory, such as
+the trampolines created by gcc closures and libffi, or JIT'd code.
+Unfortunately, as this is dynamically generated code, there is no way
+for IPE to detect that this code has not been tampered with in
+transition from where it was built, to where it is running. As a result,
+IPE is incapable of tackling this problem for dynamically generated
+code.
+
+IPE cannot verify the integrity of interpreted languages' programs when
+these scripts invoked via ``<interpreter> <file>``. This is because the
+way interpreters execute these files, the scripts themselves are not
+evaluated as executable code through one of IPE's hooks. Interpreters
+can be enlightened to the usage of IPE by trying to mmap a file into
+executable memory (+X), after opening the file and responding to the
+error code appropriately. This also applies to included files, or high
+value files, such as configuration files of critical system components.
+This specific gap is planned on being addressed within IPE.
+
+Threat Model
+------------
+
+The threat type addressed by IPE is tampering of executable user-land
+code beyond the initially booted kernel, and the initial verification of
+kernel modules that are loaded in userland through ``modprobe`` or
+``insmod``.
+
+Tampering violates the property of integrity. IPE's role in mitigating
+this threat is to verify the integrity (and authenticity) of all
+executable code and to deny their use if integrity verification fails.
+IPE generates audit logs which may be utilized to detect integrity
+verification failures.
+
+Tampering threat scenarios include modification or replacement of
+executable code by a range of actors including:
+
+- Insiders with physical access to the hardware
+- Insiders with local network access to the system
+- Insiders with access to the deployment system
+- Compromised internal systems under external control
+- Malicious end users of the system
+- Compromised end users of the system
+- Remote (external) compromise of the system
+
+IPE does not mitigate threats arising from malicious authorized
+developers, or compromised developer tools used by authorized
+developers. Additionally, IPE draws hard security boundary between user
+mode and kernel mode. As a result, IPE does not provide any protections
+against a kernel level exploit, and a kernel-level exploit can disable
+or tamper with IPE's protections.
+
+The root of trust for all of IPE's verifications is the
+``SYSTEM_TRUSTED_KEYRING``.
+
+IPE Core
+--------
+
+IPE Policy
+~~~~~~~~~~
+
+IPE policy is designed to be both forward compatible and backwards
+compatible. There is one required line, at the top of the policy,
+indicating the policy name, and the policy version, for instance::
+
+ policy_name="Ex Policy" policy_version=0.0.0
+
+The policy name is a unique key identifying this policy in a human
+readable name. This is used to create nodes under securityfs as well as
+uniquely identify policies to deploy new policies vs update existing
+policies.
+
+The policy version indicates the current version of the policy (NOT the
+policy syntax version). This is used to prevent roll-back of policy to
+potentially insecure previous versions of the policy.
+
+The next portion of IPE policy, are rules. Rules are formed by key=value
+pairs, known as properties. IPE rules require two properties: "action",
+which determines what IPE does when it encounters a match against the
+rule, and "op", which determines when that rule should be evaluated.
+Thus, a minimal rule is::
+
+ op=EXECUTE action=ALLOW
+
+This example will allow any execution. Additional properties are used to
+restrict attributes about the files being evaluated. These properties
+are intended to be deterministic attributes that are resident in the
+kernel.
+
+Order does not matter for the rule's properties - they can be listed in
+any order, however it is encouraged to have the "op" property be first,
+and the "action" property be last for readability. Rules are evaluated
+top-to-bottom. As a result, any revocation rules, or denies should be
+placed early in the file to ensure that these rules are evaluated before
+as rule with "action=ALLOW" is hit.
+
+IPE policy is designed to be forward compatible and backwards
+compatible, thus any failure to parse a rule will result in the line
+being ignored, and a warning being emitted. If backwards compatibility
+is not required, the kernel command line parameter and sysctl,
+``ipe.strict_parse`` can be enabled, which will cause these warnings to
+be fatal.
+
+IPE policy supports comments. The character '#' will function as a
+comment, ignoring all characters to the right of '#' until the newline.
+
+The default behavior of IPE evaluations can also be expressed in policy,
+through the ``DEFAULT`` statement. This can be done at a global level,
+or a per-operation level::
+
+ # Global
+ DEFAULT action=ALLOW
+
+ # Operation Specific
+ DEFAULT op=EXECUTE action=ALLOW
+
+A default must be set for all known operations in IPE. If you want to
+preserve older policies being compatible with newer kernels that can introduce
+new operations, please set a global default of 'ALLOW', and override the
+defaults on a per-operation basis.
+
+With configurable policy-based LSMs, there's several issues with
+enforcing the configurable policies at startup, around reading and
+parsing the policy:
+
+1. The kernel *should* not read files from userland, so directly reading
+ the policy file is prohibited.
+2. The kernel command line has a character limit, and one kernel module
+ should not reserve the entire character limit for its own
+ configuration.
+3. There are various boot loaders in the kernel ecosystem, so handing
+ off a memory block would be costly to maintain.
+
+As a result, IPE has addressed this problem through a concept of a "boot
+policy". A boot policy is a minimal policy, compiled into the kernel.
+This policy is intended to get the system to a state where userland is
+setup and ready to receive commands, at which point a more complex
+policy ("user policies") can be deployed via securityfs. The boot policy
+can be specified via the Kconfig, ``SECURITY_IPE_BOOT_POLICY``, which
+accepts a path to a plain-text version of the IPE policy to apply. This
+policy will be compiled into the kernel. If not specified, IPE will be
+disabled until a policy is deployed through securityfs, and activated
+through sysfs.
+
+Deploying Policies
+^^^^^^^^^^^^^^^^^^
+
+User policies as explained above, are policies that are deployed from
+userland, through securityfs. These policies are signed to enforce some
+level of authorization of the policies (prohibiting an attacker from
+gaining root, and deploying an "allow all" policy), through the PKCS#7
+enveloped data format. These policies must be signed by a certificate
+that chains to the ``SYSTEM_TRUSTED_KEYRING``. Through openssl, the
+signing can be done via::
+
+ openssl smime -sign -in "$MY_POLICY" -signer "$MY_CERTIFICATE" \
+ -inkey "$MY_PRIVATE_KEY" -binary -outform der -noattr -nodetach \
+ -out "$MY_POLICY.p7s"
+
+Deploying the policies is done through securityfs, through the
+``new_policy`` node. To deploy a policy, simply cat the file into the
+securityfs node::
+
+ cat "$MY_POLICY.p7s" > /sys/kernel/security/ipe/new_policy
+
+Upon success, this will create one subdirectory under
+``/sys/kernel/security/ipe/policies/``. The subdirectory will be the
+``policy_name`` field of the policy deployed, so for the example above,
+the directory will be ``/sys/kernel/security/ipe/policies/Ex\ Policy``.
+Within this directory, there will be four files: ``raw``, ``content``,
+``active``, and ``delete``.
+
+The ``raw`` file is rw, reading will provide the raw PKCS#7 data that
+was provided to the kernel, representing the policy. Writing, will
+deploy an in-place policy update - if this policy is the currently
+running policy, the new updated policy will replace it immediately upon
+success.
+
+The ``content`` file is read only. Reading will provide the PKCS#7 inner
+content of the policy, which will be the plain text policy.
+
+The ``active`` file is used to set a policy as the currently active policy.
+This file is rw, and accepts a value of ``"1"`` to set the policy as active.
+Since only a single policy can be active at one time, all other policies
+will be marked inactive.
+
+Similarly, the ``cat`` command above will result in an error upon
+syntactically invalid or untrusted policies. It will also error if a
+policy already exists with the same ``policy_name``. The write to the
+``raw`` node will error upon syntactically invalid, untrusted policies,
+or if the payload fails the version check. The write will also fail if
+the ``policy_name`` in the payload does not match the existing policy.
+
+Deploying these policies will *not* cause IPE to start enforcing this
+policy. Once deployment is successful, a policy can be marked as active,
+via ``/sys/kernel/security/ipe/$policy_name/active``. IPE will enforce
+whatever policy is marked as active. For our example, we can activate
+the ``Ex Policy`` via::
+
+ echo -n 1 > "/sys/kernel/security/ipe/Ex Policy/active"
+
+At which point, ``Ex Policy`` will now be the enforced policy on the
+system.
+
+.. NOTE::
+
+ The -n parameter is important, as it strips an additional newline.
+
+IPE also provides a way to delete policies. This can be done via the
+``delete`` securityfs node, ``/sys/kernel/security/ipe/$policy_name/delete``.
+Writing ``1`` to that file will delete that node::
+
+ echo -n 1 > "/sys/kernel/security/ipe/$policy_name/delete"
+
+There are two requirements to delete policies:
+
+1. The policy being deleted must not be the active policy.
+2. The policy being deleted must not be the boot policy.
+
+.. NOTE::
+
+ If a MAC system is enabled, all writes to ipe's securityfs nodes require
+ ``CAP_MAC_ADMIN``.
+
+Modes
+~~~~~
+
+IPE supports two modes of operation: permissive (similar to SELinux's
+permissive mode) and enforce. Permissive mode performs the same checks
+as enforce mode, and logs policy violations, but will not enforce the
+policy. This allows users to test policies before enforcing them.
+
+The default mode is enforce, and can be changed via the kernel command
+line parameter ``ipe.enforce=(0|1)``, or the securityfs node
+``/sys/kernel/security/ipe/enforce``. The ability to switch modes can
+be compiled out of the LSM via setting the Kconfig
+``CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH`` to N.
+
+.. NOTE::
+
+ If a MAC system is enabled, all writes to ipe's securityfs nodes require
+ ``CAP_MAC_ADMIN``.
+
+Audit Events
+~~~~~~~~~~~~
+
+Success Auditing
+^^^^^^^^^^^^^^^^
+
+IPE supports success auditing. When enabled, all events that pass IPE
+policy and are not blocked will emit an audit event. This is disabled by
+default, and can be enabled via the kernel command line
+``ipe.success_audit=(0|1)`` or the securityfs node,
+``/sys/kernel/security/ipe/success_audit``.
+
+This is very noisy, as IPE will check every user-mode binary on the
+system, but is useful for debugging policies.
+
+.. NOTE::
+
+ If a MAC system is enabled, all writes to ipe's securityfs nodes require
+ ``CAP_MAC_ADMIN``.
+
+IPE Properties
+--------------
+
+As explained above, IPE properties are ``key=value`` pairs expressed in
+IPE policy. Two properties are built-into the policy parser: 'op' and
+'action'. The other properties are determinstic attributes to express
+across files. Currently those properties are: 'boot_verified',
+'dmverity_signature', 'dmverity_roothash'. A description of all
+properties supported by IPE are listed below:
+
+op
+~~
+
+Indicates the operation for a rule to apply to. Must be in every rule.
+IPE supports the following operations:
+
+Version 1
+^^^^^^^^^
+
+``EXECUTE``
+
+ Pertains to any file attempting to be executed, or loaded as an
+ executable.
+
+``FIRMWARE``:
+
+ Pertains to firmware being loaded via the firmware_class interface.
+ This covers both the preallocated buffer and the firmware file
+ itself.
+
+``KMODULE``:
+
+ Pertains to loading kernel modules via ``modprobe`` or ``insmod``.
+
+``KEXEC_IMAGE``:
+
+ Pertains to kernel images loading via ``kexec``.
+
+``KEXEC_INITRAMFS``
+
+ Pertains to initrd images loading via ``kexec --initrd``.
+
+``POLICY``:
+
+ Controls loading IMA policies through the
+ ``/sys/kernel/security/ima/policy`` securityfs entry.
+
+``X509_CERT``:
+
+ Controls loading IMA certificates through the Kconfigs,
+ ``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``.
+
+``KERNEL_READ``:
+
+ Short hand for all of the following: ``FIRMWARE``, ``KMODULE``,
+ ``KEXEC_IMAGE``, ``KEXEC_INITRAMFS``, ``POLICY``, and ``X509_CERT``.
+
+action
+~~~~~~
+
+Version 1
+^^^^^^^^^
+
+Determines what IPE should do when a rule matches. Must be in every
+rule. Can be one of:
+
+``ALLOW``:
+
+ If the rule matches, explicitly allow the call to proceed without
+ executing any more rules.
+
+``DENY``:
+
+ If the rule matches, explicitly prohibit the call from proceeding
+ without executing any more rules.
+
+boot_verified
+~~~~~~~~~~~~~
+
+Version 1
+^^^^^^^^^
+
+This property can be utilized for authorization of the first super-block
+that executes a file. This is almost always init. Typically this is used
+for systems with an initramfs or other initial disk, where this is unmounted
+before the system becomes available, and is not covered by any other property.
+This property is controlled by the Kconfig, ``CONFIG_IPE_BOOT_PROP``. The
+format of this property is::
+
+ boot_verified=(TRUE|FALSE)
+
+
+.. WARNING::
+
+ This property will trust any disk where the first execution occurs
+ evaluation occurs. If you do not have a startup disk that is
+ unpacked and unmounted (like initramfs), then it will automatically
+ trust the root filesystem and potentially overauthorize the entire
+ disk.
+
+dmverity_roothash
+~~~~~~~~~~~~~~~~~
+
+Version 1
+^^^^^^^^^
+
+This property can be utilized for authorization or revocation of
+specific dm-verity volumes, identified via root hash. It has a
+dependency on the DM_VERITY module. This property is controlled by the
+property: ``CONFIG_IPE_DM_VERITY_ROOTHASH``. The format of this property
+is::
+
+ dmverity_roothash=HashHexDigest
+
+dmverity_signature
+~~~~~~~~~~~~~~~~~~
+
+Version 1
+^^^^^^^^^
+
+This property can be utilized for authorization of all dm-verity volumes
+that have a signed roothash that chains to the system trusted keyring.
+It has a dependency on the ``DM_VERITY_VERIFY_ROOTHASH_SIG`` Kconfig.
+This property is controlled by the Kconfig:
+``CONFIG_IPE_DM_VERITY_SIGNATURE``. The format of this property is::
+
+ dmverity_signature=(TRUE|FALSE)
+
+Policy Examples
+---------------
+
+Allow all
+~~~~~~~~~
+
+::
+
+ policy_name="Allow All" policy_version=0.0.0
+ DEFAULT action=ALLOW
+
+Allow only initial superblock
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="Allow All Initial SB" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE boot_verified=TRUE action=ALLOW
+
+Allow any signed dm-verity volume and the initial superblock
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="AllowSignedAndInitial" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE boot_verified=TRUE action=ALLOW
+ op=EXECUTE dmverity_signature=TRUE action=ALLOW
+
+Prohibit execution from a specific dm-verity volume
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="AllowSignedAndInitial" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=DENY
+ op=EXECUTE boot_verified=TRUE action=ALLOW
+ op=EXECUTE dmverity_signature=TRUE action=ALLOW
+
+Allow only a specific dm-verity volume
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ policy_name="AllowSignedAndInitial" policy_version=0.0.0
+ DEFAULT action=DENY
+
+ op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=ALLOW
+
+External Information
+--------------------
+
+Please see the github repository at: https://github.com/microsoft/ipe
+
+FAQ
+---
+
+Q: What's the difference between other LSMs which provide integrity
+verification (i.e. IMA)?
+
+A: IPE differs from other LSMs which provide integrity checking, as it
+has no dependency on the filesystem metadata itself. The attributes that
+IPE checks are deterministic properties that exist solely in the kernel.
+Additionally, IPE provides no additional mechanisms of verifying these
+files (e.g. IMA Signatures) - all of the attributes of verifying files
+are existing features within the kernel.
+
+Additionally, IPE is completely restricted to integrity. It offers no
+measurement or attestation features, which IMA addresses.
diff --git a/MAINTAINERS b/MAINTAINERS
index 2876c69435d5..492de7bda6b9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8584,6 +8584,7 @@ INTEGRITY POLICY ENFORCEMENT (IPE)
M: Deven Bowers <deven.desai@linux.microsoft.com>
L: linux-integrity@vger.kernel.org
S: Supported
+F: Documentation/admin-guide/LSM/ipe.rst
F: scripts/ipe/
F: security/ipe/
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 09/11] dm-verity: add bdev_setsecurity hook for root-hash
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add a security hook call to set a security property of a block_device
in dm-verity with the root-hash that was verified to match the merkel-tree.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
drivers/md/dm-verity-target.c | 8 +
include/linux/device-mapper.h | 1 +
security/ipe/ipe-blobs.c | 11 ++
security/ipe/ipe-engine.h | 3 +
security/ipe/ipe.c | 4 +
security/ipe/properties/Kconfig | 13 +-
security/ipe/properties/Makefile | 1 +
security/ipe/properties/dmverity-roothash.c | 153 ++++++++++++++++++++
security/ipe/properties/prop-entry.h | 9 ++
9 files changed, 202 insertions(+), 1 deletion(-)
create mode 100644 security/ipe/properties/dmverity-roothash.c
diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c
index fabc173aa7b3..7f609906af16 100644
--- a/drivers/md/dm-verity-target.c
+++ b/drivers/md/dm-verity-target.c
@@ -16,8 +16,10 @@
#include "dm-verity.h"
#include "dm-verity-fec.h"
#include "dm-verity-verify-sig.h"
+#include "dm-core.h"
#include <linux/module.h>
#include <linux/reboot.h>
+#include <linux/security.h>
#define DM_MSG_PREFIX "verity"
@@ -530,6 +532,12 @@ static int verity_verify_io(struct dm_verity_io *io)
return -EIO;
}
+ r = security_bdev_setsecurity(dm_table_get_md(v->ti->table)->bdev,
+ DM_VERITY_ROOTHASH_SEC_NAME,
+ v->root_digest, v->digest_size);
+ if (unlikely(r < 0))
+ return r;
+
/*
* At this point, the merkel tree has finished validating.
* if signature was specified, validate the signature here.
diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h
index 02be0be21d38..b82e8223d52a 100644
--- a/include/linux/device-mapper.h
+++ b/include/linux/device-mapper.h
@@ -625,5 +625,6 @@ static inline unsigned long to_bytes(sector_t n)
}
#define DM_VERITY_SIGNATURE_SEC_NAME DM_NAME ".verity-sig"
+#define DM_VERITY_ROOTHASH_SEC_NAME DM_NAME ".verity-rh"
#endif /* _LINUX_DEVICE_MAPPER_H */
diff --git a/security/ipe/ipe-blobs.c b/security/ipe/ipe-blobs.c
index 041d7d47b723..6a09d5c6dea8 100644
--- a/security/ipe/ipe-blobs.c
+++ b/security/ipe/ipe-blobs.c
@@ -46,6 +46,7 @@ void ipe_bdev_free_security(struct block_device *bdev)
struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev);
kfree(bdev_sec->dmverity_rh_sig);
+ kfree(bdev_sec->dmverity_rh);
memset(bdev_sec, 0x0, sizeof(*bdev_sec));
}
@@ -80,5 +81,15 @@ int ipe_bdev_setsecurity(struct block_device *bdev, const char *key,
return 0;
}
+ if (!strcmp(key, DM_VERITY_ROOTHASH_SEC_NAME)) {
+ bdev_sec->dmverity_rh = kmemdup(value, len, GFP_KERNEL);
+ if (!bdev_sec->dmverity_rh)
+ return -ENOMEM;
+
+ bdev_sec->rh_size = len;
+
+ return 0;
+ }
+
return -ENOSYS;
}
diff --git a/security/ipe/ipe-engine.h b/security/ipe/ipe-engine.h
index 038c39a8973e..696baaa423ff 100644
--- a/security/ipe/ipe-engine.h
+++ b/security/ipe/ipe-engine.h
@@ -18,6 +18,9 @@
struct ipe_bdev_blob {
u8 *dmverity_rh_sig;
size_t dmv_rh_sig_len;
+
+ u8 *dmverity_rh;
+ size_t rh_size;
};
struct ipe_engine_ctx {
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
index 8a612eb62879..8f4dfb8c547f 100644
--- a/security/ipe/ipe.c
+++ b/security/ipe/ipe.c
@@ -47,6 +47,10 @@ static int __init ipe_load_properties(void)
if (rc != 0)
return rc;
+ rc = ipe_init_dm_verity_rh();
+ if (rc != 0)
+ return rc;
+
return rc;
}
diff --git a/security/ipe/properties/Kconfig b/security/ipe/properties/Kconfig
index 4046f7e5eaef..4f09092522d9 100644
--- a/security/ipe/properties/Kconfig
+++ b/security/ipe/properties/Kconfig
@@ -14,8 +14,19 @@ config IPE_BOOT_PROP
if unsure, answer N.
+config IPE_DM_VERITY_ROOTHASH
+ bool "Enable property for authorizing dm-verity volumes via root-hash"
+ depends on DM_VERITY
+ help
+ This option enables IPE's integration with Device-Mapper Verity.
+ This enables the usage of the property "dmverity_roothash" in IPE's
+ policy. This property allows authorization or revocation via a
+ a hex-string representing the roothash of a dmverity volume.
+
+ if unsure, answer Y.
+
config IPE_DM_VERITY_SIGNATURE
- bool "Enable property for signature verified dm-verity volumes"
+ bool "Enable property for verified dm-verity volumes"
depends on DM_VERITY_VERIFY_ROOTHASH_SIG
help
This option enables IPE's integration with Device-Mapper Verity's
diff --git a/security/ipe/properties/Makefile b/security/ipe/properties/Makefile
index 6b67cbe36e31..d9a3807797f4 100644
--- a/security/ipe/properties/Makefile
+++ b/security/ipe/properties/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_SECURITY_IPE) += properties.o
properties-$(CONFIG_IPE_BOOT_PROP) += boot-verified.o
properties-$(CONFIG_IPE_DM_VERITY_SIGNATURE) += dmverity-signature.o
+properties-$(CONFIG_IPE_DM_VERITY_ROOTHASH) += dmverity-roothash.o
diff --git a/security/ipe/properties/dmverity-roothash.c b/security/ipe/properties/dmverity-roothash.c
new file mode 100644
index 000000000000..09112e1af753
--- /dev/null
+++ b/security/ipe/properties/dmverity-roothash.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "../ipe.h"
+#include "../ipe-pin.h"
+#include "../ipe-property.h"
+#include "../utility.h"
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/audit.h>
+#include <linux/kernel.h>
+
+#define PROPERTY_NAME "dmverity_roothash"
+
+struct counted_array {
+ u8 *arr;
+ size_t len;
+};
+
+static void audit(struct audit_buffer *ab, const void *value)
+{
+ const struct counted_array *a = (const struct counted_array *)value;
+
+ if (!a || a->len == 0)
+ audit_log_format(ab, "NULL");
+ else
+ audit_log_n_hex(ab, a->arr, a->len);
+}
+
+static inline void audit_rule_value(struct audit_buffer *ab,
+ const void *value)
+{
+ audit(ab, value);
+}
+
+static inline void audit_ctx(struct audit_buffer *ab,
+ const struct ipe_engine_ctx *ctx)
+{
+ struct counted_array a;
+
+ if (!has_bdev(ctx->file))
+ return audit(ab, NULL);
+
+ a.arr = ctx->sec_bdev->dmverity_rh;
+ a.len = ctx->sec_bdev->rh_size;
+
+ return audit(ab, &a);
+}
+
+static bool evaluate(const struct ipe_engine_ctx *ctx,
+ const void *value)
+{
+ const struct counted_array *a = (const struct counted_array *)value;
+
+ if (!has_bdev(ctx->file))
+ return false;
+
+ if (a->len != ctx->sec_bdev->rh_size)
+ return false;
+
+ return memcmp(a->arr, ctx->sec_bdev->dmverity_rh, a->len) == 0;
+}
+
+static int parse(const char *val_str, void **value)
+{
+ struct counted_array *arr = NULL;
+ int rv = 0;
+
+ arr = kzalloc(sizeof(*arr), GFP_KERNEL);
+ if (!arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ arr->len = strlen(val_str) / 2;
+
+ arr->arr = kzalloc(arr->len, GFP_KERNEL);
+ if (!arr->arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ rv = hex2bin(arr->arr, val_str, arr->len);
+ if (rv != 0)
+ goto err;
+
+ *value = arr;
+ return rv;
+err:
+ if (arr)
+ kfree(arr->arr);
+ kfree(arr);
+ return rv;
+}
+
+static int duplicate(const void *src, void **dest)
+{
+ struct counted_array *arr = NULL;
+ const struct counted_array *src_arr = src;
+ int rv = 0;
+
+ arr = kmemdup(src_arr, sizeof(*arr), GFP_KERNEL);
+ if (!arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ arr->arr = kmemdup(src_arr->arr, src_arr->len, GFP_KERNEL);
+ if (!arr->arr) {
+ rv = -ENOMEM;
+ goto err;
+ }
+
+ *dest = arr;
+ return rv;
+err:
+ if (arr)
+ kfree(arr->arr);
+ kfree(arr);
+
+ return rv;
+}
+
+static void free_val(void **value)
+{
+ struct counted_array *a = (struct counted_array *)*value;
+
+ if (a)
+ kfree(a->arr);
+ kfree(a);
+ *value = NULL;
+}
+
+static const struct ipe_property dmv_roothash = {
+ .property_name = PROPERTY_NAME,
+ .version = 1,
+ .eval = evaluate,
+ .parse = parse,
+ .rule_audit = audit_rule_value,
+ .ctx_audit = audit_ctx,
+ .dup = duplicate,
+ .free_val = free_val,
+};
+
+int ipe_init_dm_verity_rh(void)
+{
+ return ipe_register_property(&dmv_roothash);
+}
diff --git a/security/ipe/properties/prop-entry.h b/security/ipe/properties/prop-entry.h
index 85366366ff0d..86a360570f3b 100644
--- a/security/ipe/properties/prop-entry.h
+++ b/security/ipe/properties/prop-entry.h
@@ -26,4 +26,13 @@ static inline int __init ipe_init_dm_verity_signature(void)
int __init ipe_init_dm_verity_signature(void);
#endif /* CONFIG_IPE_DM_VERITY_SIGNATURE */
+#ifndef CONFIG_IPE_DM_VERITY_ROOTHASH
+static inline int __init ipe_init_dm_verity_rh(void)
+{
+ return 0;
+}
+#else
+int __init ipe_init_dm_verity_rh(void);
+#endif /* CONFIG_IPE_DM_VERITY_ROOTHASH */
+
#endif /* IPE_PROP_ENTRY_H */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 02/11] security: add ipe lsm evaluation loop and audit system
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
Add the core logic of the IPE LSM, the evaluation loop (engine),
a portion of the audit system, and the skeleton of the policy
structure.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
MAINTAINERS | 1 +
include/uapi/linux/audit.h | 4 +
security/Kconfig | 12 +-
security/Makefile | 2 +
security/ipe/.gitignore | 2 +
security/ipe/Kconfig | 44 ++++++
security/ipe/Makefile | 25 ++++
security/ipe/ipe-audit.c | 231 +++++++++++++++++++++++++++++++
security/ipe/ipe-audit.h | 18 +++
security/ipe/ipe-engine.c | 205 +++++++++++++++++++++++++++
security/ipe/ipe-engine.h | 37 +++++
security/ipe/ipe-hooks.c | 149 ++++++++++++++++++++
security/ipe/ipe-hooks.h | 61 ++++++++
security/ipe/ipe-policy.h | 62 +++++++++
security/ipe/ipe-prop-internal.h | 37 +++++
security/ipe/ipe-property.c | 142 +++++++++++++++++++
security/ipe/ipe-property.h | 99 +++++++++++++
security/ipe/ipe.c | 67 +++++++++
security/ipe/ipe.h | 20 +++
19 files changed, 1212 insertions(+), 6 deletions(-)
create mode 100644 security/ipe/.gitignore
create mode 100644 security/ipe/Kconfig
create mode 100644 security/ipe/Makefile
create mode 100644 security/ipe/ipe-audit.c
create mode 100644 security/ipe/ipe-audit.h
create mode 100644 security/ipe/ipe-engine.c
create mode 100644 security/ipe/ipe-engine.h
create mode 100644 security/ipe/ipe-hooks.c
create mode 100644 security/ipe/ipe-hooks.h
create mode 100644 security/ipe/ipe-policy.h
create mode 100644 security/ipe/ipe-prop-internal.h
create mode 100644 security/ipe/ipe-property.c
create mode 100644 security/ipe/ipe-property.h
create mode 100644 security/ipe/ipe.c
create mode 100644 security/ipe/ipe.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 86450be5437b..bed30cc1cfd7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8584,6 +8584,7 @@ M: Deven Bowers <deven.desai@linux.microsoft.com>
L: linux-integrity@vger.kernel.org
S: Supported
F: scripts/ipe/
+F: security/ipe/
INTEL 810/815 FRAMEBUFFER DRIVER
M: Antonino Daplas <adaplas@gmail.com>
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 9b6a973f4cc3..5a634cca1d42 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -154,6 +154,10 @@
#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */
#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */
#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */
+#define AUDIT_INTEGRITY_POLICY_LOAD 1808 /* IPE Policy Load */
+#define AUDIT_INTEGRITY_POLICY_ACTIVATE 1809 /* IPE Policy Activation */
+#define AUDIT_INTEGRITY_EVENT 1810 /* IPE Evaluation Event */
+#define AUDIT_INTEGRITY_MODE 1811 /* IPE Mode Switch */
#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
diff --git a/security/Kconfig b/security/Kconfig
index cd3cc7da3a55..94924556b637 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -238,6 +238,7 @@ source "security/loadpin/Kconfig"
source "security/yama/Kconfig"
source "security/safesetid/Kconfig"
source "security/lockdown/Kconfig"
+source "security/ipe/Kconfig"
source "security/integrity/Kconfig"
@@ -277,11 +278,11 @@ endchoice
config LSM
string "Ordered list of enabled LSMs"
- default "lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
- default "lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
- default "lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
- default "lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
- default "lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf"
+ default "lockdown,yama,loadpin,ipe,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK
+ default "lockdown,yama,loadpin,ipe,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
+ default "lockdown,yama,loadpin,ipe,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
+ default "lockdown,yama,loadpin,ipe,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
+ default "lockdown,yama,loadpin,ipe,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf"
help
A comma-separated list of LSMs, in initialization order.
Any LSMs left off this list will be ignored. This can be
@@ -292,4 +293,3 @@ config LSM
source "security/Kconfig.hardening"
endmenu
-
diff --git a/security/Makefile b/security/Makefile
index 3baf435de541..48bd063d66e1 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -13,6 +13,7 @@ subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin
subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid
subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown
subdir-$(CONFIG_BPF_LSM) += bpf
+subdir-$(CONFIG_SECURITY_IPE) += ipe
# always enable default capabilities
obj-y += commoncap.o
@@ -32,6 +33,7 @@ obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/
obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/
obj-$(CONFIG_CGROUPS) += device_cgroup.o
obj-$(CONFIG_BPF_LSM) += bpf/
+obj-$(CONFIG_SECURITY_IPE) += ipe/
# Object integrity file lists
subdir-$(CONFIG_INTEGRITY) += integrity
diff --git a/security/ipe/.gitignore b/security/ipe/.gitignore
new file mode 100644
index 000000000000..bbf824e665d7
--- /dev/null
+++ b/security/ipe/.gitignore
@@ -0,0 +1,2 @@
+# Generated Boot Policy
+ipe-bp.c
diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig
new file mode 100644
index 000000000000..7615109a19ca
--- /dev/null
+++ b/security/ipe/Kconfig
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Integrity Policy Enforcement (IPE) configuration
+#
+
+menuconfig SECURITY_IPE
+ bool "Integrity Policy Enforcement (IPE)"
+ depends on SECURITY && AUDIT
+ select SYSTEM_DATA_VERIFICATION
+ help
+ This option enables the Integrity Policy Enforcement subsystem
+ allowing systems to enforce integrity having no dependencies
+ on filesystem metadata, making its decisions based off of kernel-
+ resident features and data structures. A key feature of IPE is a
+ customizable policy to allow admins to reconfigure integrity
+ requirements on the fly.
+
+ If unsure, answer N.
+
+if SECURITY_IPE
+
+config SECURITY_IPE_BOOT_POLICY
+ string "Integrity policy to apply on system startup"
+ help
+ This option specifies a filepath to a IPE policy that is compiled
+ into the kernel. This policy will be enforced until a policy update
+ is deployed via the $securityfs/ipe/policies/$policy_name/active
+ interface.
+
+ If unsure, leave blank.
+
+config SECURITY_IPE_PERMISSIVE_SWITCH
+ bool "Enable the ability to switch IPE to permissive mode"
+ default y
+ help
+ This option enables two ways of switching IPE to permissive mode,
+ a securityfs node, `$securityfs/ipe/enforce`, or a kernel command line
+ parameter, `ipe.enforce`. If either of these is set to 0, files
+ will be subject to IPE's policy, audit messages will be logged, but
+ the policy will not be enforced.
+
+ If unsure, answer Y.
+
+endif
diff --git a/security/ipe/Makefile b/security/ipe/Makefile
new file mode 100644
index 000000000000..40565b73fac2
--- /dev/null
+++ b/security/ipe/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) Microsoft Corporation. All rights reserved.
+#
+# Makefile for building the IPE module as part of the kernel tree.
+#
+
+quiet_cmd_polgen = IPE_POL $(patsubst "%",%,$(2))
+ cmd_polgen = scripts/ipe/polgen/polgen security/ipe/ipe-bp.c $(2)
+
+$(eval $(call config_filename,SECURITY_IPE_BOOT_POLICY))
+
+targets += ipe-bp.c
+$(obj)/ipe-bp.c: scripts/ipe/polgen/polgen $(SECURITY_IPE_BOOT_POLICY_FILENAME) FORCE
+ $(call if_changed,polgen,$(SECURITY_IPE_BOOT_POLICY_FILENAME))
+
+obj-$(CONFIG_SECURITY_IPE) += \
+ ipe.o \
+ ipe-audit.o \
+ ipe-bp.o \
+ ipe-engine.o \
+ ipe-property.o \
+ ipe-hooks.o \
+
+clean-files := ipe-bp.c
diff --git a/security/ipe/ipe-audit.c b/security/ipe/ipe-audit.c
new file mode 100644
index 000000000000..2c754851bd40
--- /dev/null
+++ b/security/ipe/ipe-audit.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-audit.h"
+#include "ipe-engine.h"
+#include "ipe-prop-internal.h"
+
+#include <linux/types.h>
+#include <linux/audit.h>
+#include <linux/rcupdate.h>
+#include <linux/lsm_audit.h>
+#include <linux/rbtree.h>
+#include <crypto/hash.h>
+#include <crypto/sha1_base.h>
+
+#define ACTION_STR(a) ((a) == ipe_action_allow ? "ALLOW" : "DENY")
+
+#define IPE_UNKNOWN "UNKNOWN"
+
+/* Keep in sync with ipe_op in ipe-hooks.h */
+const char *audit_op_names[] = {
+ IPE_OP_EXECUTE,
+ IPE_OP_FIRMWARE,
+ IPE_OP_KEXEC_IMAGE,
+ IPE_OP_KEXEC_INITRAMFS,
+ IPE_OP_X509_CERTIFICATE,
+ IPE_OP_POLICY,
+ IPE_OP_KMODULE,
+ IPE_OP_KERNEL_READ,
+ IPE_UNKNOWN,
+};
+
+/* Keep in sync with ipe_hook in ipe-hooks.h */
+const char *audit_hook_names[] = {
+ IPE_HOOK_EXEC,
+ IPE_HOOK_MMAP,
+ IPE_HOOK_MPROTECT,
+ IPE_HOOK_KERNEL_READ,
+ IPE_HOOK_KERNEL_LOAD,
+ IPE_UNKNOWN,
+};
+
+/**
+ * ipe_audit_mode: Emit an audit event indicating what mode IPE is currently
+ * in.
+ *
+ * This event is of form "IPE mode=(enforce|audit)"
+ */
+void ipe_audit_mode(bool enforcing)
+{
+ struct audit_buffer *ab;
+ const char *mode_str = (enforcing) ? IPE_MODE_ENFORCE :
+ IPE_MODE_PERMISSIVE;
+
+ ab = audit_log_start(audit_context(), GFP_KERNEL,
+ AUDIT_INTEGRITY_MODE);
+ if (!ab)
+ return;
+
+ audit_log_format(ab, "IPE mode=%s", mode_str);
+
+ audit_log_end(ab);
+}
+
+/**
+ * audit_engine_ctx: Add the string representation of ipe_engine_ctx to the
+ * end of an audit buffer.
+ * @ab: the audit buffer to append the string representation of @ctx
+ * @ctx: the ipe_engine_ctx structure to transform into a string
+ * representation
+ *
+ * This string representation is of form:
+ * "ctx_pid=%d ctx_op=%s ctx_hook=%s ctx_comm=%s ctx_audit_pathname=%s ctx_ino=%ld ctx_dev=%s"
+ *
+ * Certain fields may be omitted or replaced with ERR(%d).
+ *
+ */
+static void audit_engine_ctx(struct audit_buffer *ab,
+ const struct ipe_engine_ctx *ctx)
+{
+ audit_log_format(ab, "ctx_pid=%d ctx_op=%s ctx_hook=%s ctx_comm=",
+ task_tgid_nr(current),
+ audit_op_names[ctx->op],
+ audit_hook_names[ctx->hook]);
+
+ audit_log_untrustedstring(ab, current->comm);
+
+ if (ctx->file) {
+ if (IS_ERR(ctx->audit_pathname)) {
+ audit_log_format(ab, " ctx_audit_pathname=ERR(%ld) ",
+ PTR_ERR(ctx->audit_pathname));
+ } else {
+ audit_log_format(ab, " ctx_audit_pathname=\"%s\" ",
+ ctx->audit_pathname);
+ }
+
+ audit_log_format(ab, "ctx_ino=%ld ctx_dev=%s",
+ ctx->file->f_inode->i_ino,
+ ctx->file->f_inode->i_sb->s_id);
+ }
+}
+
+struct prop_audit_ctx {
+ struct audit_buffer *ab;
+ const struct ipe_engine_ctx *ctx;
+};
+
+/**
+ * audit_property: callback to print a property, used with ipe_for_each_prop.
+ * @prop: property to print an audit record for.
+ * @ctx: context passed to ipe_for_each_prop. In this case, it is of type
+ * prop_audit_ctx, containing the audit buffer and engine ctx.
+ *
+ * Return:
+ * 0 - Always
+ */
+static int audit_property(const struct ipe_property *prop, void *ctx)
+{
+ const struct prop_audit_ctx *aud_ctx = (struct prop_audit_ctx *)ctx;
+
+ audit_log_format(aud_ctx->ab, "prop_%s=", prop->property_name);
+ prop->ctx_audit(aud_ctx->ab, aud_ctx->ctx);
+ audit_log_format(aud_ctx->ab, " ");
+
+ return 0;
+}
+
+/**
+ * audit_eval_properties: Append the string representation of evaluated
+ * properties to an audit buffer.
+ * @ab: the audit buffer to append the string representation of the evaluated
+ * properties.
+ * @ctx: the ipe_engine_ctx structure to pass to property audit function.
+ *
+ * This string representation is of form:
+ * "prop_key1=value1 prop_key2=value2 ... "
+ *
+ * Certain values may be replaced with ERR(%d). Prop may also be empty,
+ * and thus omitted entirely.
+ *
+ */
+static inline void audit_eval_properties(struct audit_buffer *ab,
+ const struct ipe_engine_ctx *ctx)
+{
+ const struct prop_audit_ctx aud_ctx = {
+ .ab = ab,
+ .ctx = ctx
+ };
+
+ (void)ipe_for_each_prop(audit_property, (void *)&aud_ctx);
+}
+
+/**
+ * audit_rule: Add the string representation of a non-default IPE rule to the
+ * end of an audit buffer.
+ * @ab: the audit buffer to append the string representation of a rule.
+ * @rule: the ipe_rule structure to transform into a string representation.
+ *
+ * This string representation is of form:
+ * 'rule="op=%s key1=value1 key2=value2 ... action=%s"'
+ *
+ * Certain values may be replaced with ERR(%d).
+ *
+ */
+static void audit_rule(struct audit_buffer *ab,
+ const struct ipe_rule *rule)
+{
+ struct ipe_prop_container *ptr;
+
+ audit_log_format(ab, "rule=\"op=%s ", audit_op_names[rule->op]);
+
+ list_for_each_entry(ptr, &rule->props, next) {
+ audit_log_format(ab, "%s=", ptr->prop->property_name);
+
+ ptr->prop->rule_audit(ab, ptr->value);
+
+ audit_log_format(ab, " ");
+ }
+
+ audit_log_format(ab, "action=%s\"", ACTION_STR(rule->action));
+}
+
+/**
+ * ipe_audit_match: Emit an audit event indicating that the IPE engine has
+ * determined a match to a rule in IPE policy.
+ * @ctx: the engine context structure to audit
+ * @rule: The rule that was matched. If NULL, then assumed to be a default
+ * either operation specific, indicated by table, or global.
+ * @table: the operation-specific rule table. If NULL, then it assumed
+ * that the global default is matched.
+ * @match_type: The type of match that the engine used during evaluation
+ * @action: The action that the engine decided to take
+ * @rule: The rule that was matched. Must be set if @match_type is
+ * ipe_match_rule and NULL otherwise.
+ */
+void ipe_audit_match(const struct ipe_engine_ctx *ctx,
+ enum ipe_match match_type, enum ipe_action action,
+ const struct ipe_rule *rule)
+{
+ struct audit_buffer *ab;
+
+ if (!ipe_success_audit && action == ipe_action_allow)
+ return;
+
+ ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
+ AUDIT_INTEGRITY_EVENT);
+ if (!ab)
+ return;
+
+ audit_log_format(ab, "IPE ");
+
+ audit_engine_ctx(ab, ctx);
+
+ audit_log_format(ab, " ");
+
+ audit_eval_properties(ab, ctx);
+
+ if (match_type == ipe_match_rule)
+ audit_rule(ab, rule);
+ else if (match_type == ipe_match_table)
+ audit_log_format(ab, "rule=\"DEFAULT op=%s action=%s\"",
+ audit_op_names[ctx->op], ACTION_STR(action));
+ else if (match_type == ipe_match_global)
+ audit_log_format(ab, "rule=\"DEFAULT action=%s\"",
+ ACTION_STR(action));
+
+ audit_log_end(ab);
+}
diff --git a/security/ipe/ipe-audit.h b/security/ipe/ipe-audit.h
new file mode 100644
index 000000000000..e00f415bed2c
--- /dev/null
+++ b/security/ipe/ipe-audit.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe-engine.h"
+#include "ipe-policy.h"
+
+#ifndef IPE_AUDIT_H
+#define IPE_AUDIT_H
+
+void ipe_audit_mode(bool enforcing);
+
+void ipe_audit_match(const struct ipe_engine_ctx *ctx,
+ enum ipe_match match_type, enum ipe_action action,
+ const struct ipe_rule *rule);
+
+#endif /* IPE_AUDIT_H */
diff --git a/security/ipe/ipe-engine.c b/security/ipe/ipe-engine.c
new file mode 100644
index 000000000000..ac526d4ea5e6
--- /dev/null
+++ b/security/ipe/ipe-engine.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-property.h"
+#include "ipe-prop-internal.h"
+#include "ipe-policy.h"
+#include "ipe-engine.h"
+#include "ipe-audit.h"
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/rcupdate.h>
+#include <linux/security.h>
+
+const struct ipe_policy *ipe_active_policy;
+
+/**
+ * get_audit_pathname: Return the absolute path of the file struct passed in
+ * @file: file to derive an absolute path from.
+ *
+ * This function walks past chroots and mount points.
+ *
+ * Return:
+ * !NULL - OK
+ * ERR_PTR(-ENOENT) - No File
+ * ERR_PTR(-ENOMEM) - No Memory
+ * ERR_PTR(-ENAMETOOLONG) - Path Exceeds PATH_MAX
+ */
+static char *get_audit_pathname(const struct file *file)
+{
+ int rc = 0;
+ char *pos = NULL;
+ char *pathbuf = NULL;
+ char *temp_path = NULL;
+
+ /* No File to get Path From */
+ if (!file)
+ return ERR_PTR(-ENOENT);
+
+ pathbuf = __getname();
+ if (!pathbuf)
+ return ERR_PTR(-ENOMEM);
+
+ pos = d_absolute_path(&file->f_path, pathbuf, PATH_MAX);
+ if (IS_ERR(pos)) {
+ rc = PTR_ERR(pos);
+ goto err;
+ }
+
+ temp_path = __getname();
+ if (!temp_path) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ strlcpy(temp_path, pos, PATH_MAX);
+
+ __putname(pathbuf);
+
+ return temp_path;
+
+err:
+ __putname(pathbuf);
+ return ERR_PTR(rc);
+}
+
+/**
+ * free_ctx: free a previously allocated ipe_engine_ctx struct
+ * @ctx: structure to deallocate.
+ *
+ */
+static void free_ctx(struct ipe_engine_ctx *ctx)
+{
+ if (IS_ERR_OR_NULL(ctx))
+ return;
+
+ if (!IS_ERR_OR_NULL(ctx->audit_pathname))
+ __putname(ctx->audit_pathname);
+
+ kfree(ctx);
+}
+
+/**
+ * build_ctx: allocate a new ipe_engine_ctx structure
+ * @file: File that is being evaluated against IPE policy.
+ * @op: Operation that the file is being evaluated against.
+ * @hook: Specific hook that the file is being evaluated through.
+ *
+ * Return:
+ * !NULL - OK
+ * ERR_PTR(-ENOMEM) - no memory
+ */
+static struct ipe_engine_ctx *build_ctx(const struct file *file,
+ enum ipe_op op, enum ipe_hook hook)
+{
+ struct ipe_engine_ctx *local;
+
+ local = kzalloc(sizeof(*local), GFP_KERNEL);
+ if (!local)
+ return ERR_PTR(-ENOMEM);
+
+ /* if there's an error here, it's O.K. */
+ local->audit_pathname = get_audit_pathname(file);
+ local->file = file;
+ local->op = op;
+ local->hook = hook;
+
+ return local;
+}
+
+/**
+ * evaluate: Process an @ctx against IPE's current active policy.
+ * @ctx: the engine ctx to perform an evaluation on.
+ *
+ * Return:
+ * -EACCES - A match occurred against a "action=DENY" rule
+ * -ENOMEM - Out of memory
+ */
+static int evaluate(struct ipe_engine_ctx *ctx)
+{
+ int rc = 0;
+ bool match = false;
+ enum ipe_action action;
+ enum ipe_match match_type;
+ const struct ipe_rule *rule;
+ const struct ipe_policy *pol;
+ const struct ipe_rule_table *rules;
+ const struct ipe_prop_container *prop;
+
+ if (!rcu_access_pointer(ipe_active_policy))
+ return rc;
+
+ rcu_read_lock();
+
+ pol = rcu_dereference(ipe_active_policy);
+
+ rules = &pol->ops[ctx->op];
+
+ list_for_each_entry(rule, &rules->rules, next) {
+ match = true;
+
+ list_for_each_entry(prop, &rule->props, next)
+ match = match && prop->prop->eval(ctx, prop->value);
+
+ if (match)
+ break;
+ }
+
+ if (match) {
+ match_type = ipe_match_rule;
+ action = rule->action;
+ } else if (rules->def != ipe_action_unset) {
+ match_type = ipe_match_table;
+ action = rules->def;
+ rule = NULL;
+ } else {
+ match_type = ipe_match_global;
+ action = pol->def;
+ rule = NULL;
+ }
+
+ ipe_audit_match(ctx, match_type, action, rule);
+
+ if (action == ipe_action_deny)
+ rc = -EACCES;
+
+ if (ipe_enforce == 0)
+ rc = 0;
+
+ rcu_read_unlock();
+ return rc;
+}
+
+/**
+ * ipe_process_event: Perform an evaluation of @file, @op, and @hook against
+ * IPE's current active policy.
+ * @file: File that is being evaluated against IPE policy.
+ * @op: Operation that the file is being evaluated against.
+ * @hook: Specific hook that the file is being evaluated through.
+ *
+ * Return:
+ * -ENOMEM: (No Memory)
+ * -EACCES: (A match occurred against a "action=DENY" rule)
+ */
+int ipe_process_event(const struct file *file, enum ipe_op op,
+ enum ipe_hook hook)
+{
+ int rc = 0;
+ struct ipe_engine_ctx *ctx;
+
+ ctx = build_ctx(file, op, hook);
+ if (IS_ERR(ctx))
+ goto cleanup;
+
+ rc = evaluate(ctx);
+
+cleanup:
+ free_ctx(ctx);
+ return rc;
+}
diff --git a/security/ipe/ipe-engine.h b/security/ipe/ipe-engine.h
new file mode 100644
index 000000000000..d9a95674e70d
--- /dev/null
+++ b/security/ipe/ipe-engine.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe-hooks.h"
+
+#include <linux/types.h>
+#include <linux/rbtree.h>
+#include <linux/fs.h>
+
+#ifndef IPE_ENGINE_H
+#define IPE_ENGINE_H
+
+struct ipe_engine_ctx {
+ enum ipe_op op;
+ enum ipe_hook hook;
+ const struct file *file;
+ const char *audit_pathname;
+};
+
+struct ipe_prop_cache {
+ struct rb_node node;
+ void *storage;
+ const struct ipe_property *prop;
+};
+
+enum ipe_match {
+ ipe_match_rule = 0,
+ ipe_match_table,
+ ipe_match_global
+};
+
+int ipe_process_event(const struct file *file, enum ipe_op op,
+ enum ipe_hook hook);
+
+#endif /* IPE_ENGINE_H */
diff --git a/security/ipe/ipe-hooks.c b/security/ipe/ipe-hooks.c
new file mode 100644
index 000000000000..071c4af23a3d
--- /dev/null
+++ b/security/ipe/ipe-hooks.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-hooks.h"
+#include "ipe-engine.h"
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/binfmts.h>
+#include <linux/mount.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/security.h>
+
+/**
+ * ipe_on_exec: LSM hook called on the exec family of system calls.
+ * @bprm: A structure to hold arguments that are used when loading binaries,
+ * used to extract the file being executed.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - see ipe_process_event
+ */
+int ipe_on_exec(struct linux_binprm *bprm)
+{
+ return ipe_process_event(bprm->file, ipe_op_execute, ipe_hook_exec);
+}
+
+/**
+ * ipe_on_mmap: LSM hook called on the mmap system call.
+ * @file: File being mapped into memory.
+ * @reqprot: Unused.
+ * @prot: A protection mapping of the memory region, calculated based on
+ * @reqprot, and the system configuration.
+ * @flags: Unused.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - see ipe_process_event
+ */
+int ipe_on_mmap(struct file *file, unsigned long reqprot, unsigned long prot,
+ unsigned long flags)
+{
+ if (prot & PROT_EXEC)
+ return ipe_process_event(file, ipe_op_execute, ipe_hook_mmap);
+
+ return 0;
+}
+
+/**
+ * ipe_on_mprotect: LSM hook called on the mprotect system call
+ * @vma: A structure representing the existing memory region.
+ * @reqprot: Unused.
+ * @prot: A protection mapping of the memory region, calculated based on
+ * @reqprot, and the system configuration.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - see ipe_process_event
+ */
+int ipe_on_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
+ unsigned long prot)
+{
+ if ((prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC))
+ return ipe_process_event(vma->vm_file, ipe_op_execute,
+ ipe_hook_mprotect);
+
+ return 0;
+}
+
+/**
+ * ipe_on_kernel_read: LSM hook called on kernel_read_file.
+ * @file: File being read by the hook kernel_read_file.
+ * @id: Enumeration indicating the type of file being read.
+ *
+ * For more information, see the LSM hook, kernel_read_file.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - see ipe_process_event
+ */
+int ipe_on_kernel_read(struct file *file, enum kernel_read_file_id id)
+{
+ switch (id) {
+ case READING_FIRMWARE:
+ case READING_FIRMWARE_PREALLOC_BUFFER:
+ return ipe_process_event(file, ipe_op_firmware,
+ ipe_hook_kernel_read);
+ case READING_MODULE:
+ return ipe_process_event(file, ipe_op_kmodule,
+ ipe_hook_kernel_read);
+ case READING_KEXEC_INITRAMFS:
+ return ipe_process_event(file, ipe_op_kexec_initramfs,
+ ipe_hook_kernel_read);
+ case READING_KEXEC_IMAGE:
+ return ipe_process_event(file, ipe_op_kexec_image,
+ ipe_hook_kernel_read);
+ case READING_POLICY:
+ return ipe_process_event(file, ipe_op_policy,
+ ipe_hook_kernel_read);
+ case READING_X509_CERTIFICATE:
+ return ipe_process_event(file, ipe_op_x509,
+ ipe_hook_kernel_read);
+ default:
+ return ipe_process_event(file, ipe_op_kernel_read,
+ ipe_hook_kernel_read);
+ }
+}
+
+/**
+ * ipe_on_kernel_load_data: LSM hook called on kernel_load_data.
+ * @id: Enumeration indicating what type of data is being loaded.
+ *
+ * For more information, see the LSM hook, kernel_load_data.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - see ipe_process_event
+ */
+int ipe_on_kernel_load_data(enum kernel_load_data_id id)
+{
+ switch (id) {
+ case LOADING_FIRMWARE:
+ case LOADING_FIRMWARE_PREALLOC_BUFFER:
+ return ipe_process_event(NULL, ipe_op_firmware,
+ ipe_hook_kernel_load);
+ case LOADING_MODULE:
+ return ipe_process_event(NULL, ipe_op_kmodule,
+ ipe_hook_kernel_load);
+ case LOADING_KEXEC_INITRAMFS:
+ return ipe_process_event(NULL, ipe_op_kexec_initramfs,
+ ipe_hook_kernel_load);
+ case LOADING_KEXEC_IMAGE:
+ return ipe_process_event(NULL, ipe_op_kexec_image,
+ ipe_hook_kernel_load);
+ case LOADING_POLICY:
+ return ipe_process_event(NULL, ipe_op_policy,
+ ipe_hook_kernel_load);
+ case LOADING_X509_CERTIFICATE:
+ return ipe_process_event(NULL, ipe_op_x509,
+ ipe_hook_kernel_load);
+ default:
+ return ipe_process_event(NULL, ipe_op_kernel_read,
+ ipe_hook_kernel_load);
+ }
+}
diff --git a/security/ipe/ipe-hooks.h b/security/ipe/ipe-hooks.h
new file mode 100644
index 000000000000..806659b7cdbe
--- /dev/null
+++ b/security/ipe/ipe-hooks.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/binfmts.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/security.h>
+
+#ifndef IPE_HOOK_H
+#define IPE_HOOK_H
+
+#define IPE_HOOK_EXEC "EXEC"
+#define IPE_HOOK_MMAP "MMAP"
+#define IPE_HOOK_MPROTECT "MPROTECT"
+#define IPE_HOOK_KERNEL_READ "KERNEL_READ"
+#define IPE_HOOK_KERNEL_LOAD "KERNEL_LOAD"
+
+enum ipe_hook {
+ ipe_hook_exec = 0,
+ ipe_hook_mmap,
+ ipe_hook_mprotect,
+ ipe_hook_kernel_read,
+ ipe_hook_kernel_load,
+ ipe_hook_max
+};
+
+/*
+ * The sequence between ipe_op_firmware and ipe_op_kmodule
+ * must remain the same for ipe_op_kernel read to function
+ * appropriately.
+ */
+enum ipe_op {
+ ipe_op_execute = 0,
+ ipe_op_firmware,
+ ipe_op_kexec_image,
+ ipe_op_kexec_initramfs,
+ ipe_op_x509,
+ ipe_op_policy,
+ ipe_op_kmodule,
+ ipe_op_kernel_read,
+ ipe_op_max
+};
+
+int ipe_on_exec(struct linux_binprm *bprm);
+
+int ipe_on_mmap(struct file *file, unsigned long reqprot, unsigned long prot,
+ unsigned long flags);
+
+int ipe_on_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
+ unsigned long prot);
+
+int ipe_on_kernel_read(struct file *file, enum kernel_read_file_id id);
+
+int ipe_on_kernel_load_data(enum kernel_load_data_id id);
+
+#endif /* IPE_HOOK_H */
diff --git a/security/ipe/ipe-policy.h b/security/ipe/ipe-policy.h
new file mode 100644
index 000000000000..c0c9f2962c92
--- /dev/null
+++ b/security/ipe/ipe-policy.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe-hooks.h"
+#include "ipe-property.h"
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/rcupdate.h>
+
+#ifndef IPE_POLICY_H
+#define IPE_POLICY_H
+
+#define IPE_HEADER_POLICY_NAME "policy_name"
+#define IPE_HEADER_POLICY_VERSION "policy_version"
+
+extern const char *const ipe_boot_policy;
+extern const struct ipe_policy *ipe_active_policy;
+
+enum ipe_action {
+ ipe_action_unset = 0,
+ ipe_action_allow,
+ ipe_action_deny
+};
+
+struct ipe_prop_container {
+ struct list_head next;
+ void *value;
+ const struct ipe_property *prop;
+};
+
+struct ipe_rule {
+ struct list_head props;
+ struct list_head next;
+ enum ipe_action action;
+ enum ipe_op op;
+};
+
+struct ipe_rule_table {
+ struct list_head rules;
+ enum ipe_action def;
+};
+
+struct ipe_pol_ver {
+ u16 major;
+ u16 minor;
+ u16 rev;
+};
+
+struct ipe_policy {
+ char *policy_name;
+ struct ipe_pol_ver policy_version;
+ enum ipe_action def;
+
+ /* KERNEL_READ stores no data itself */
+ struct ipe_rule_table ops[ipe_op_max - 1];
+};
+
+#endif /* IPE_POLICY_H */
diff --git a/security/ipe/ipe-prop-internal.h b/security/ipe/ipe-prop-internal.h
new file mode 100644
index 000000000000..95a2081e77ee
--- /dev/null
+++ b/security/ipe/ipe-prop-internal.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe-property.h"
+
+#include <linux/types.h>
+
+#ifndef IPE_PROPERTY_INTERNAL_H
+#define IPE_PROPERTY_INTERNAL_H
+
+#define IPE_PROPERTY_OPERATION "op"
+#define IPE_PROPERTY_DEFAULT "DEFAULT"
+#define IPE_PROPERTY_ACTION "action"
+
+#define IPE_OP_EXECUTE "EXECUTE"
+#define IPE_OP_FIRMWARE "FIRMWARE"
+#define IPE_OP_KEXEC_IMAGE "KEXEC_IMAGE"
+#define IPE_OP_KEXEC_INITRAMFS "KEXEC_INITRAMFS"
+#define IPE_OP_X509_CERTIFICATE "X509_CERT"
+#define IPE_OP_POLICY "POLICY"
+#define IPE_OP_KMODULE "KMODULE"
+#define IPE_OP_KERNEL_READ "KERNEL_READ"
+
+struct ipe_prop_reg {
+ struct rb_node node;
+ const struct ipe_property *prop;
+};
+
+int ipe_for_each_prop(int (*view)(const struct ipe_property *prop,
+ void *ctx),
+ void *ctx);
+
+const struct ipe_property *ipe_lookup_prop(const char *key);
+
+#endif /* IPE_PROPERTY_INTERNAL_H */
diff --git a/security/ipe/ipe-property.c b/security/ipe/ipe-property.c
new file mode 100644
index 000000000000..d4b0283f86bd
--- /dev/null
+++ b/security/ipe/ipe-property.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-prop-internal.h"
+#include "ipe-property.h"
+
+#include <linux/types.h>
+#include <linux/rbtree.h>
+#include <linux/slab.h>
+
+/* global root containing all registered properties */
+struct rb_root ipe_registry_root = RB_ROOT;
+
+/**
+ * reg_lookup: Attempt to find a `prop_reg` structure with property_name @key.
+ * @key: The property_name to look for in the tree.
+ *
+ * Return:
+ * ipe_prop_reg structure - OK
+ * NULL - No such property exists
+ */
+static struct ipe_prop_reg *reg_lookup(const char *key)
+{
+ struct rb_node *n = ipe_registry_root.rb_node;
+
+ while (n) {
+ int r;
+ struct ipe_prop_reg *reg =
+ container_of(n, struct ipe_prop_reg, node);
+
+ r = strcmp(reg->prop->property_name, key);
+ if (r == 0)
+ return reg;
+ else if (r > 0)
+ n = n->rb_right;
+ else
+ n = n->rb_left;
+ }
+
+ return NULL;
+}
+
+/**
+ * ipe_lookup_prop: Attempt to find a ipe_property structure by name @key.
+ * @key: The property_name to look for in the tree.
+ *
+ * Return:
+ * ipe_property structure - OK
+ * NULL - No property exists under @key
+ */
+const struct ipe_property *ipe_lookup_prop(const char *key)
+{
+ struct ipe_prop_reg *reg = reg_lookup(key);
+
+ if (!reg)
+ return NULL;
+
+ return reg->prop;
+}
+
+/**
+ * ipe_register_property: Insert a property into the registration system.
+ * @prop: Read-only property structure containing the property_name, as well
+ * as the necessary function pointers for a property.
+ *
+ * The caller needs to maintain the lifetime of @prop throughout the life of
+ * the system, after calling ipe_register_property.
+ *
+ * All necessary properties need to be loaded via this method before
+ * loading a policy, otherwise the properties will be ignored as unknown.
+ *
+ * Return:
+ * 0 - OK
+ * -EEXIST - A key exists with the name @prop->property_name
+ * -ENOMEM - Out of Memory
+ */
+int ipe_register_property(const struct ipe_property *prop)
+{
+ struct rb_node *parent = NULL;
+ struct ipe_prop_reg *new_data = NULL;
+ struct rb_node **new = &ipe_registry_root.rb_node;
+
+ while (*new) {
+ int r;
+ struct ipe_prop_reg *reg =
+ container_of(*new, struct ipe_prop_reg, node);
+
+ parent = *new;
+
+ r = strcmp(reg->prop->property_name, prop->property_name);
+ if (r == 0)
+ return -EEXIST;
+ else if (r > 0)
+ new = &((*new)->rb_right);
+ else
+ new = &((*new)->rb_left);
+ }
+
+ new_data = kzalloc(sizeof(*new_data), GFP_KERNEL);
+ if (!new_data)
+ return -ENOMEM;
+
+ new_data->prop = prop;
+
+ rb_link_node(&new_data->node, parent, new);
+ rb_insert_color(&new_data->node, &ipe_registry_root);
+
+ return 0;
+}
+
+/**
+ * ipe_for_each_prop: Iterate over all currently-registered properties
+ * calling @fn on the values, and providing @view @ctx.
+ * @view: The function to call for each property. This is given the property
+ * structure as the first argument, and @ctx as the second.
+ * @ctx: caller-specified context that is passed to the function. Can be NULL.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - Proper errno as returned by @view.
+ */
+int ipe_for_each_prop(int (*view)(const struct ipe_property *prop,
+ void *ctx),
+ void *ctx)
+{
+ struct rb_node *node;
+ struct ipe_prop_reg *val;
+ int rc = 0;
+
+ for (node = rb_first(&ipe_registry_root); node; node = rb_next(node)) {
+ val = container_of(node, struct ipe_prop_reg, node);
+
+ rc = view(val->prop, ctx);
+ if (rc)
+ return rc;
+ }
+
+ return rc;
+}
diff --git a/security/ipe/ipe-property.h b/security/ipe/ipe-property.h
new file mode 100644
index 000000000000..cf570d52d0d2
--- /dev/null
+++ b/security/ipe/ipe-property.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe-engine.h"
+
+#include <linux/types.h>
+#include <linux/lsm_audit.h>
+
+#ifndef IPE_PROPERTY_H
+#define IPE_PROPERTY_H
+
+/**
+ * ipe_property_evaluator: Determines whether a file subject matches the
+ * property.
+ * @value: Value to compare against for a match
+ *
+ * NOTE: This is done in an rcu read critical section - sleeping
+ * allocations are prohibited.
+ *
+ * Return:
+ * true - The property matches evaluation
+ * false - The property does not match evaluation
+ */
+typedef bool (*ipe_property_evaluator)(const struct ipe_engine_ctx *ctx,
+ const void *value);
+
+/**
+ * ipe_property_audit: Transform a rule value into a string representation.
+ * @ab: Audit buffer to add the string representation of @value to.
+ * @value: Value to transform into a string representation.
+ *
+ * NOTE: This is done in an rcu read critical section - sleeping
+ * allocations are prohibited.
+ */
+typedef void (*ipe_property_audit)(struct audit_buffer *ab, const void *value);
+
+/**
+ * ipe_ctx_audit: Called by the auditing to provide the values
+ * that were evaluated about the subject, @ctx->file, to determine how
+ * a value was evaluated.
+ *
+ * NOTE: This is done in an rcu read critical section - sleeping
+ * allocations are prohibited.
+ *
+ * @ab: Audit buffer to add the string representation of @value to.
+ * @value: Value to transform into a string representation.
+ *
+ */
+typedef void (*ipe_ctx_audit)(struct audit_buffer *ab,
+ const struct ipe_engine_ctx *ctx);
+
+/**
+ * ipe_parse_value: Transform a string representation of a rule into an
+ * internal ipe data-structure, opaque to the engine.
+ * @val_str: String-value parsed by the policy parser.
+ * @value: Valid-pointer indicating address to store parsed value.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - ERR, use Standard Return Codes
+ */
+typedef int(*ipe_parse_value)(const char *val_str, void **value);
+
+/**
+ * ipe_dup_val: Called by the policy parser to make duplicate properties for
+ * pseudo-properties like "KERNEL_READ".
+ * @src: Value to copy.
+ * @dest: Pointer to the destination where the value should be copied.
+ *
+ * Return:
+ * 0 - OK
+ * !0 - ERR, use Standard Return Codes
+ */
+typedef int (*ipe_dup_val)(const void *src, void **dest);
+
+/**
+ * ipe_free_value: Free a policy value, created by ipe_parse_value.
+ * @value: Valid-pointer to the value to be interpreted and
+ * freed by the property.
+ *
+ * Optional, can be NULL - in which case, this will not be called.
+ */
+typedef void (*ipe_free_value)(void **value);
+
+struct ipe_property {
+ const char *const property_name;
+ ipe_property_evaluator eval;
+ ipe_property_audit rule_audit;
+ ipe_ctx_audit ctx_audit;
+ ipe_parse_value parse;
+ ipe_dup_val dup;
+ ipe_free_value free_val;
+};
+
+int ipe_register_property(const struct ipe_property *prop);
+
+#endif /* IPE_PROPERTY_H */
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c
new file mode 100644
index 000000000000..6e3b9a10813c
--- /dev/null
+++ b/security/ipe/ipe.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#include "ipe.h"
+#include "ipe-policy.h"
+#include "ipe-hooks.h"
+
+#include <linux/module.h>
+#include <linux/lsm_hooks.h>
+#include <linux/sysctl.h>
+#include <linux/rcupdate.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/security.h>
+
+static struct security_hook_list ipe_hooks[] __lsm_ro_after_init = {
+ LSM_HOOK_INIT(bprm_check_security, ipe_on_exec),
+ LSM_HOOK_INIT(mmap_file, ipe_on_mmap),
+ LSM_HOOK_INIT(kernel_read_file, ipe_on_kernel_read),
+ LSM_HOOK_INIT(kernel_load_data, ipe_on_kernel_load_data),
+ LSM_HOOK_INIT(file_mprotect, ipe_on_mprotect),
+};
+
+/**
+ * ipe_init: Entry point of IPE.
+ *
+ * This is called at LSM init, which happens occurs early during kernel
+ * start up. During this phase, IPE loads the
+ * properties compiled into the kernel, and register's IPE's hooks.
+ * The boot policy is loaded later, during securityfs init, at which point
+ * IPE will start enforcing its policy.
+ *
+ * Return:
+ * 0 - OK
+ * -ENOMEM - sysctl registration failed.
+ */
+static int __init ipe_init(void)
+{
+ pr_info("mode=%s", (ipe_enforce == 1) ? IPE_MODE_ENFORCE :
+ IPE_MODE_PERMISSIVE);
+
+ security_add_hooks(ipe_hooks, ARRAY_SIZE(ipe_hooks), "IPE");
+
+ return 0;
+}
+
+DEFINE_LSM(ipe) = {
+ .name = "ipe",
+ .init = ipe_init,
+};
+
+bool ipe_enforce = true;
+bool ipe_success_audit;
+
+#ifdef CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH
+
+/* Module Parameter for Default Behavior on Boot */
+module_param_named(enforce, ipe_enforce, bool, 0644);
+MODULE_PARM_DESC(enforce, "IPE Permissive Switch");
+
+#endif /* CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH */
+
+/* Module Parameter for Success Audit on Boot */
+module_param_named(success_audit, ipe_success_audit, bool, 0644);
+MODULE_PARM_DESC(success_audit, "IPE Audit on Success");
diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h
new file mode 100644
index 000000000000..af72bb574f73
--- /dev/null
+++ b/security/ipe/ipe.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ */
+
+#ifndef IPE_H
+#define IPE_H
+
+#define pr_fmt(fmt) "IPE " fmt "\n"
+
+#include <linux/types.h>
+#include <linux/fs.h>
+
+#define IPE_MODE_ENFORCE "enforce"
+#define IPE_MODE_PERMISSIVE "permissive"
+
+extern bool ipe_enforce;
+extern bool ipe_success_audit;
+
+#endif /* IPE_H */
--
2.27.0
^ permalink raw reply related
* [RFC PATCH v5 06/11] dm-verity: move signature check after tree validation
From: Deven Bowers @ 2020-07-28 21:36 UTC (permalink / raw)
To: agk, axboe, snitzer, jmorris, serge, zohar, viro, paul, eparis,
jannh, dm-devel, linux-integrity, linux-security-module,
linux-fsdevel, linux-block, linux-audit
Cc: tyhicks, linux-kernel, corbet, sashal, jaskarankhurana, mdsakib,
nramas, pasha.tatashin
In-Reply-To: <20200728213614.586312-1-deven.desai@linux.microsoft.com>
The CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG introduced by Jaskaran was
intended to be used to allow an LSM to enforce verifications for all
dm-verity volumes.
However, with it's current implementation, this signature verification
occurs after the merkel-tree is validated, as a result the signature can
pass initial verification by passing a matching root-hash and signature.
This results in an unreadable block_device, but that has passed signature
validation (and subsequently, would be marked as verified).
This change moves the signature verification to after the merkel-tree has
finished validation.
Signed-off-by: Deven Bowers <deven.desai@linux.microsoft.com>
---
drivers/md/dm-verity-target.c | 44 ++++------
drivers/md/dm-verity-verify-sig.c | 140 ++++++++++++++++++++++--------
drivers/md/dm-verity-verify-sig.h | 24 +++--
drivers/md/dm-verity.h | 2 +-
4 files changed, 134 insertions(+), 76 deletions(-)
diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c
index eec9f252e935..fabc173aa7b3 100644
--- a/drivers/md/dm-verity-target.c
+++ b/drivers/md/dm-verity-target.c
@@ -471,9 +471,9 @@ static int verity_verify_io(struct dm_verity_io *io)
struct bvec_iter start;
unsigned b;
struct crypto_wait wait;
+ int r;
for (b = 0; b < io->n_blocks; b++) {
- int r;
sector_t cur_block = io->block + b;
struct ahash_request *req = verity_io_hash_req(v, io);
@@ -530,6 +530,16 @@ static int verity_verify_io(struct dm_verity_io *io)
return -EIO;
}
+ /*
+ * At this point, the merkel tree has finished validating.
+ * if signature was specified, validate the signature here.
+ */
+ r = verity_verify_root_hash(v);
+ if (r < 0) {
+ DMERR_LIMIT("signature mismatch");
+ return r;
+ }
+
return 0;
}
@@ -728,7 +738,7 @@ static void verity_status(struct dm_target *ti, status_type_t type,
args++;
if (v->validated_blocks)
args++;
- if (v->signature_key_desc)
+ if (v->sig)
args += DM_VERITY_ROOT_HASH_VERIFICATION_OPTS;
if (!args)
return;
@@ -751,9 +761,9 @@ static void verity_status(struct dm_target *ti, status_type_t type,
if (v->validated_blocks)
DMEMIT(" " DM_VERITY_OPT_AT_MOST_ONCE);
sz = verity_fec_status_table(v, sz, result, maxlen);
- if (v->signature_key_desc)
+ if (v->sig)
DMEMIT(" " DM_VERITY_ROOT_HASH_VERIFICATION_OPT_SIG_KEY
- " %s", v->signature_key_desc);
+ " %s", v->sig->signature_key_desc);
break;
}
}
@@ -819,7 +829,7 @@ static void verity_dtr(struct dm_target *ti)
verity_fec_dtr(v);
- kfree(v->signature_key_desc);
+ verity_verify_dtr(v);
kfree(v);
}
@@ -876,8 +886,7 @@ static int verity_alloc_zero_digest(struct dm_verity *v)
return r;
}
-static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v,
- struct dm_verity_sig_opts *verify_args)
+static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v)
{
int r;
unsigned argc;
@@ -927,9 +936,7 @@ static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v,
return r;
continue;
} else if (verity_verify_is_sig_opt_arg(arg_name)) {
- r = verity_verify_sig_parse_opt_args(as, v,
- verify_args,
- &argc, arg_name);
+ r = verity_verify_sig_parse_opt_args(as, v, &argc);
if (r)
return r;
continue;
@@ -960,7 +967,6 @@ static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v,
static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
{
struct dm_verity *v;
- struct dm_verity_sig_opts verify_args = {0};
struct dm_arg_set as;
unsigned int num;
unsigned long long num_ll;
@@ -968,7 +974,6 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
int i;
sector_t hash_position;
char dummy;
- char *root_hash_digest_to_validate;
v = kzalloc(sizeof(struct dm_verity), GFP_KERNEL);
if (!v) {
@@ -1102,7 +1107,6 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
r = -EINVAL;
goto bad;
}
- root_hash_digest_to_validate = argv[8];
if (strcmp(argv[9], "-")) {
v->salt_size = strlen(argv[9]) / 2;
@@ -1128,20 +1132,11 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
as.argc = argc;
as.argv = argv;
- r = verity_parse_opt_args(&as, v, &verify_args);
+ r = verity_parse_opt_args(&as, v);
if (r < 0)
goto bad;
}
- /* Root hash signature is a optional parameter*/
- r = verity_verify_root_hash(root_hash_digest_to_validate,
- strlen(root_hash_digest_to_validate),
- verify_args.sig,
- verify_args.sig_size);
- if (r < 0) {
- ti->error = "Root hash verification failed";
- goto bad;
- }
v->hash_per_block_bits =
__fls((1 << v->hash_dev_block_bits) / v->digest_size);
@@ -1207,13 +1202,10 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
ti->per_io_data_size = roundup(ti->per_io_data_size,
__alignof__(struct dm_verity_io));
- verity_verify_sig_opts_cleanup(&verify_args);
-
return 0;
bad:
- verity_verify_sig_opts_cleanup(&verify_args);
verity_dtr(ti);
return r;
diff --git a/drivers/md/dm-verity-verify-sig.c b/drivers/md/dm-verity-verify-sig.c
index 614e43db93aa..27dac8aa2e5a 100644
--- a/drivers/md/dm-verity-verify-sig.c
+++ b/drivers/md/dm-verity-verify-sig.c
@@ -22,6 +22,16 @@ MODULE_PARM_DESC(require_signatures,
#define DM_VERITY_IS_SIG_FORCE_ENABLED() \
(require_signatures != false)
+static void destroy_verity_sig(struct dm_verity_sig *sig_info)
+{
+ if (!sig_info)
+ return;
+
+ kfree(sig_info->sig);
+ kfree(sig_info->signature_key_desc);
+ kfree(sig_info);
+}
+
bool verity_verify_is_sig_opt_arg(const char *arg_name)
{
return (!strcasecmp(arg_name,
@@ -29,7 +39,7 @@ bool verity_verify_is_sig_opt_arg(const char *arg_name)
}
static int verity_verify_get_sig_from_key(const char *key_desc,
- struct dm_verity_sig_opts *sig_opts)
+ struct dm_verity_sig *sig_info)
{
struct key *key;
const struct user_key_payload *ukp;
@@ -48,14 +58,14 @@ static int verity_verify_get_sig_from_key(const char *key_desc,
goto end;
}
- sig_opts->sig = kmalloc(ukp->datalen, GFP_KERNEL);
- if (!sig_opts->sig) {
+ sig_info->sig = kmalloc(ukp->datalen, GFP_KERNEL);
+ if (!sig_info->sig) {
ret = -ENOMEM;
goto end;
}
- sig_opts->sig_size = ukp->datalen;
+ sig_info->sig_size = ukp->datalen;
- memcpy(sig_opts->sig, ukp->data, sig_opts->sig_size);
+ memcpy(sig_info->sig, ukp->data, sig_info->sig_size);
end:
up_read(&key->sem);
@@ -64,70 +74,128 @@ static int verity_verify_get_sig_from_key(const char *key_desc,
return ret;
}
+/**
+ * Parse any signature verification arguments.
+ * This function will populate v->sig, it is the caller's
+ * responsibility to free this structure via verity_verify_dtr
+ *
+ * @as: argument set passed in to parse
+ * @v: verity context structure. Should have a NULL v->sig member.
+ * @argc: current argument number
+ */
int verity_verify_sig_parse_opt_args(struct dm_arg_set *as,
struct dm_verity *v,
- struct dm_verity_sig_opts *sig_opts,
- unsigned int *argc,
- const char *arg_name)
+ unsigned int *argc)
{
struct dm_target *ti = v->ti;
+ struct dm_verity_sig *sig_info = NULL;
int ret = 0;
const char *sig_key = NULL;
if (!*argc) {
ti->error = DM_VERITY_VERIFY_ERR("Signature key not specified");
- return -EINVAL;
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
+ sig_info = kzalloc(sizeof(*sig_info), GFP_KERNEL);
+ if (!sig_info) {
+ ret = -ENOMEM;
+ goto cleanup;
}
sig_key = dm_shift_arg(as);
(*argc)--;
- ret = verity_verify_get_sig_from_key(sig_key, sig_opts);
- if (ret < 0)
+ ret = verity_verify_get_sig_from_key(sig_key, sig_info);
+ if (ret < 0) {
ti->error = DM_VERITY_VERIFY_ERR("Invalid key specified");
+ goto cleanup;
+ }
- v->signature_key_desc = kstrdup(sig_key, GFP_KERNEL);
- if (!v->signature_key_desc)
- return -ENOMEM;
+ sig_info->signature_key_desc = kstrdup(sig_key, GFP_KERNEL);
+ if (!sig_info->signature_key_desc) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ v->sig = sig_info;
+ sig_info = NULL;
+cleanup:
+ if (sig_info)
+ destroy_verity_sig(sig_info);
return ret;
}
-/*
+/**
* verify_verify_roothash - Verify the root hash of the verity hash device
* using builtin trusted keys.
*
- * @root_hash: For verity, the roothash/data to be verified.
- * @root_hash_len: Size of the roothash/data to be verified.
- * @sig_data: The trusted signature that verifies the roothash/data.
- * @sig_len: Size of the signature.
+ * @v: dm_verity structure containing all context for the dm_verity
+ * operation.
*
*/
-int verity_verify_root_hash(const void *root_hash, size_t root_hash_len,
- const void *sig_data, size_t sig_len)
+int verity_verify_root_hash(const struct dm_verity *v)
{
- int ret;
+ int ret = 0;
+ char *root_hash = NULL;
+ size_t root_hash_size = 0;
+ struct dm_verity_sig *sig_target = NULL;
+
+ if (!v || !v->ti || !v->root_digest || v->digest_size == 0) {
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
+ sig_target = v->sig;
+
+ if (!sig_target || !sig_target->sig || sig_target->sig_size == 0) {
+ if (DM_VERITY_IS_SIG_FORCE_ENABLED()) {
+ ret = -ENOKEY;
+ goto cleanup;
+ } else {
+ goto cleanup;
+ }
+ }
- if (!root_hash || root_hash_len == 0)
- return -EINVAL;
+ /*
+ * If signature has passed validation once, assume
+ * that future signatures will pass.
+ */
+ if (sig_target->passed)
+ goto cleanup;
- if (!sig_data || sig_len == 0) {
- if (DM_VERITY_IS_SIG_FORCE_ENABLED())
- return -ENOKEY;
- else
- return 0;
+ root_hash_size = v->digest_size * 2;
+ root_hash = kzalloc(root_hash_size, GFP_KERNEL);
+ if (!root_hash) {
+ ret = -ENOMEM;
+ goto cleanup;
}
- ret = verify_pkcs7_signature(root_hash, root_hash_len, sig_data,
- sig_len, NULL, VERIFYING_UNSPECIFIED_SIGNATURE,
- NULL, NULL);
+ bin2hex(root_hash, v->root_digest, v->digest_size);
+
+ ret = verify_pkcs7_signature(root_hash, root_hash_size, v->sig->sig,
+ v->sig->sig_size, NULL,
+ VERIFYING_UNSPECIFIED_SIGNATURE, NULL,
+ NULL);
+ if (ret != 0)
+ goto cleanup;
+ sig_target->passed = true;
+cleanup:
+ kfree(root_hash);
return ret;
}
-void verity_verify_sig_opts_cleanup(struct dm_verity_sig_opts *sig_opts)
+/**
+ * Performs destruction / cleanup of a valid dm_verity_sig struct
+ *
+ * @v: dm_verity structure containing the dm_verity_sig struct to
+ * be freed.
+ */
+
+void verity_verify_dtr(struct dm_verity *v)
{
- kfree(sig_opts->sig);
- sig_opts->sig = NULL;
- sig_opts->sig_size = 0;
+ destroy_verity_sig(v->sig);
+ v->sig = NULL;
}
diff --git a/drivers/md/dm-verity-verify-sig.h b/drivers/md/dm-verity-verify-sig.h
index 19b1547aa741..7de8409fa9fa 100644
--- a/drivers/md/dm-verity-verify-sig.h
+++ b/drivers/md/dm-verity-verify-sig.h
@@ -11,48 +11,46 @@
#define DM_VERITY_ROOT_HASH_VERIFICATION "DM Verity Sig Verification"
#define DM_VERITY_ROOT_HASH_VERIFICATION_OPT_SIG_KEY "root_hash_sig_key_desc"
-struct dm_verity_sig_opts {
+struct dm_verity_sig {
+ char *signature_key_desc;
unsigned int sig_size;
u8 *sig;
+ bool passed;
};
#ifdef CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG
#define DM_VERITY_ROOT_HASH_VERIFICATION_OPTS 2
-int verity_verify_root_hash(const void *data, size_t data_len,
- const void *sig_data, size_t sig_len);
+int verity_verify_root_hash(const struct dm_verity *v);
bool verity_verify_is_sig_opt_arg(const char *arg_name);
int verity_verify_sig_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v,
- struct dm_verity_sig_opts *sig_opts,
- unsigned int *argc, const char *arg_name);
+ unsigned int *argc);
-void verity_verify_sig_opts_cleanup(struct dm_verity_sig_opts *sig_opts);
+void verity_verify_dtr(struct dm_verity *v);
#else
#define DM_VERITY_ROOT_HASH_VERIFICATION_OPTS 0
-int verity_verify_root_hash(const void *data, size_t data_len,
- const void *sig_data, size_t sig_len)
+inline int verity_verify_root_hash(const struct dm_verity *v)
{
return 0;
}
-bool verity_verify_is_sig_opt_arg(const char *arg_name)
+inline bool verity_verify_is_sig_opt_arg(const char *arg_name)
{
return false;
}
-int verity_verify_sig_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v,
- struct dm_verity_sig_opts *sig_opts,
- unsigned int *argc, const char *arg_name)
+inline int verity_verify_sig_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v,
+ unsigned int *argc)
{
return -EINVAL;
}
-void verity_verify_sig_opts_cleanup(struct dm_verity_sig_opts *sig_opts)
+inline void verity_verify_dtr(struct dm_verity *v)
{
}
diff --git a/drivers/md/dm-verity.h b/drivers/md/dm-verity.h
index 641b9e3a399b..995c495decad 100644
--- a/drivers/md/dm-verity.h
+++ b/drivers/md/dm-verity.h
@@ -64,7 +64,7 @@ struct dm_verity {
struct dm_verity_fec *fec; /* forward error correction */
unsigned long *validated_blocks; /* bitset blocks validated */
- char *signature_key_desc; /* signature keyring reference */
+ struct dm_verity_sig *sig; /* signature verification */
};
struct dm_verity_io {
--
2.27.0
^ permalink raw reply related
* Re: [PATCH v19 16/23] LSM: Use lsmcontext in security_inode_getsecctx
From: John Johansen @ 2020-07-28 20:28 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: linux-audit, keescook, penguin-kernel, paul, sds
In-Reply-To: <20200724203226.16374-17-casey@schaufler-ca.com>
On 7/24/20 1:32 PM, Casey Schaufler wrote:
> Change the security_inode_getsecctx() interface to fill
> a lsmcontext structure instead of data and length pointers.
> This provides the information about which LSM created the
> context so that security_release_secctx() can use the
> correct hook.
>
Reviewed-by: John Johansen <john.johansen@canonical.com>
> Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
> ---
> fs/nfsd/nfs4xdr.c | 23 +++++++++--------------
> include/linux/security.h | 5 +++--
> security/security.c | 13 +++++++++++--
> 3 files changed, 23 insertions(+), 18 deletions(-)
>
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 61d6b8a0e8f0..6673221d5606 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -2379,11 +2379,11 @@ nfsd4_encode_layout_types(struct xdr_stream *xdr, u32 layout_types)
> #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
> static inline __be32
> nfsd4_encode_security_label(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> - void *context, int len)
> + struct lsmcontext *context)
> {
> __be32 *p;
>
> - p = xdr_reserve_space(xdr, len + 4 + 4 + 4);
> + p = xdr_reserve_space(xdr, context->len + 4 + 4 + 4);
> if (!p)
> return nfserr_resource;
>
> @@ -2393,13 +2393,13 @@ nfsd4_encode_security_label(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> */
> *p++ = cpu_to_be32(0); /* lfs */
> *p++ = cpu_to_be32(0); /* pi */
> - p = xdr_encode_opaque(p, context, len);
> + p = xdr_encode_opaque(p, context->context, context->len);
> return 0;
> }
> #else
> static inline __be32
> nfsd4_encode_security_label(struct xdr_stream *xdr, struct svc_rqst *rqstp,
> - void *context, int len)
> + struct lsmcontext *context)
> { return 0; }
> #endif
>
> @@ -2496,9 +2496,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
> int err;
> struct nfs4_acl *acl = NULL;
> #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
> - struct lsmcontext scaff; /* scaffolding */
> - void *context = NULL;
> - int contextlen;
> + struct lsmcontext context = { };
> #endif
> bool contextsupport = false;
> struct nfsd4_compoundres *resp = rqstp->rq_resp;
> @@ -2556,7 +2554,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
> bmval0 & FATTR4_WORD0_SUPPORTED_ATTRS) {
> if (exp->ex_flags & NFSEXP_SECURITY_LABEL)
> err = security_inode_getsecctx(d_inode(dentry),
> - &context, &contextlen);
> + &context);
> else
> err = -EOPNOTSUPP;
> contextsupport = (err == 0);
> @@ -2986,8 +2984,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>
> #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
> if (bmval2 & FATTR4_WORD2_SECURITY_LABEL) {
> - status = nfsd4_encode_security_label(xdr, rqstp, context,
> - contextlen);
> + status = nfsd4_encode_security_label(xdr, rqstp, &context);
> if (status)
> goto out;
> }
> @@ -2999,10 +2996,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
>
> out:
> #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
> - if (context) {
> - lsmcontext_init(&scaff, context, contextlen, 0); /*scaffolding*/
> - security_release_secctx(&scaff);
> - }
> + if (context.context)
> + security_release_secctx(&context);
> #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */
> kfree(acl);
> if (tempfh) {
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 43f8a2660d37..02dc3b5ef57b 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -560,7 +560,7 @@ void security_release_secctx(struct lsmcontext *cp);
> void security_inode_invalidate_secctx(struct inode *inode);
> int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
> int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
> -int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
> +int security_inode_getsecctx(struct inode *inode, struct lsmcontext *cp);
> int security_locked_down(enum lockdown_reason what);
> #else /* CONFIG_SECURITY */
>
> @@ -1399,7 +1399,8 @@ static inline int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32
> {
> return -EOPNOTSUPP;
> }
> -static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
> +static inline int security_inode_getsecctx(struct inode *inode,
> + struct lsmcontext *cp)
> {
> return -EOPNOTSUPP;
> }
> diff --git a/security/security.c b/security/security.c
> index 862f0bc2f114..ddbaf2073b02 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2263,9 +2263,18 @@ int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen)
> }
> EXPORT_SYMBOL(security_inode_setsecctx);
>
> -int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
> +int security_inode_getsecctx(struct inode *inode, struct lsmcontext *cp)
> {
> - return call_int_hook(inode_getsecctx, -EOPNOTSUPP, inode, ctx, ctxlen);
> + struct security_hook_list *hp;
> +
> + memset(cp, 0, sizeof(*cp));
> +
> + hlist_for_each_entry(hp, &security_hook_heads.inode_getsecctx, list) {
> + cp->slot = hp->lsmid->slot;
> + return hp->hook.inode_getsecctx(inode, (void **)&cp->context,
> + &cp->len);
> + }
> + return -EOPNOTSUPP;
> }
> EXPORT_SYMBOL(security_inode_getsecctx);
>
>
^ permalink raw reply
* Re: [PATCH v19 02/23] LSM: Create and manage the lsmblob data structure.
From: John Johansen @ 2020-07-28 19:50 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: linux-audit, keescook, penguin-kernel, paul, sds
In-Reply-To: <20200724203226.16374-3-casey@schaufler-ca.com>
On 7/24/20 1:32 PM, Casey Schaufler wrote:
> When more than one security module is exporting data to
> audit and networking sub-systems a single 32 bit integer
> is no longer sufficient to represent the data. Add a
> structure to be used instead.
>
> The lsmblob structure is currently an array of
> u32 "secids". There is an entry for each of the
> security modules built into the system that would
> use secids if active. The system assigns the module
> a "slot" when it registers hooks. If modules are
> compiled in but not registered there will be unused
> slots.
>
> A new lsm_id structure, which contains the name
> of the LSM and its slot number, is created. There
> is an instance for each LSM, which assigns the name
> and passes it to the infrastructure to set the slot.
>
> The audit rules data is expanded to use an array of
> security module data rather than a single instance.
> Because IMA uses the audit rule functions it is
> affected as well.
>
assuming the config issue Stephan found is fixed
you can have my
Acked-by: John Johansen <john.johansen@canonical.com>
> Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
> Acked-by: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
> ---
> include/linux/audit.h | 4 +-
> include/linux/lsm_hooks.h | 12 ++++-
> include/linux/security.h | 66 ++++++++++++++++++++++++--
> kernel/auditfilter.c | 24 +++++-----
> kernel/auditsc.c | 12 ++---
> security/apparmor/lsm.c | 7 ++-
> security/commoncap.c | 7 ++-
> security/integrity/ima/ima_policy.c | 40 +++++++++++-----
> security/loadpin/loadpin.c | 8 +++-
> security/lockdown/lockdown.c | 7 ++-
> security/safesetid/lsm.c | 8 +++-
> security/security.c | 72 ++++++++++++++++++++++++-----
> security/selinux/hooks.c | 8 +++-
> security/smack/smack_lsm.c | 7 ++-
> security/tomoyo/tomoyo.c | 8 +++-
> security/yama/yama_lsm.c | 7 ++-
> 16 files changed, 242 insertions(+), 55 deletions(-)
>
> diff --git a/include/linux/audit.h b/include/linux/audit.h
> index 3fcd9ee49734..aabbbe6d9296 100644
> --- a/include/linux/audit.h
> +++ b/include/linux/audit.h
> @@ -11,6 +11,7 @@
>
> #include <linux/sched.h>
> #include <linux/ptrace.h>
> +#include <linux/security.h>
> #include <uapi/linux/audit.h>
>
> #define AUDIT_INO_UNSET ((unsigned long)-1)
> @@ -64,8 +65,9 @@ struct audit_field {
> kuid_t uid;
> kgid_t gid;
> struct {
> + bool lsm_isset;
> char *lsm_str;
> - void *lsm_rule;
> + void *lsm_rules[LSMBLOB_ENTRIES];
> };
> };
> u32 op;
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index b4bcafc79e0b..c9f792066d86 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -1532,6 +1532,14 @@ struct security_hook_heads {
> #undef LSM_HOOK
> } __randomize_layout;
>
> +/*
> + * Information that identifies a security module.
> + */
> +struct lsm_id {
> + const char *lsm; /* Name of the LSM */
> + int slot; /* Slot in lsmblob if one is allocated */
> +};
> +
> /*
> * Security module hook list structure.
> * For use with generic list macros for common operations.
> @@ -1540,7 +1548,7 @@ struct security_hook_list {
> struct hlist_node list;
> struct hlist_head *head;
> union security_list_options hook;
> - char *lsm;
> + struct lsm_id *lsmid;
> } __randomize_layout;
>
> /*
> @@ -1575,7 +1583,7 @@ extern struct security_hook_heads security_hook_heads;
> extern char *lsm_names;
>
> extern void security_add_hooks(struct security_hook_list *hooks, int count,
> - char *lsm);
> + struct lsm_id *lsmid);
>
> #define LSM_FLAG_LEGACY_MAJOR BIT(0)
> #define LSM_FLAG_EXCLUSIVE BIT(1)
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 0a0a03b36a3b..591dae299c6f 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -131,6 +131,64 @@ enum lockdown_reason {
>
> extern const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1];
>
> +/*
> + * Data exported by the security modules
> + *
> + * Any LSM that provides secid or secctx based hooks must be included.
> + */
> +#define LSMBLOB_ENTRIES ( \
> + (IS_ENABLED(CONFIG_SECURITY_SELINUX) ? 1 : 0) + \
> + (IS_ENABLED(CONFIG_SECURITY_SMACK) ? 1 : 0) + \
> + (IS_ENABLED(CONFIG_SECURITY_APPARMOR) ? 1 : 0))
> +
> +struct lsmblob {
> + u32 secid[LSMBLOB_ENTRIES];
> +};
> +
> +#define LSMBLOB_INVALID -1 /* Not a valid LSM slot number */
> +#define LSMBLOB_NEEDED -2 /* Slot requested on initialization */
> +#define LSMBLOB_NOT_NEEDED -3 /* Slot not requested */
> +
> +/**
> + * lsmblob_init - initialize an lsmblob structure.
> + * @blob: Pointer to the data to initialize
> + * @secid: The initial secid value
> + *
> + * Set all secid for all modules to the specified value.
> + */
> +static inline void lsmblob_init(struct lsmblob *blob, u32 secid)
> +{
> + int i;
> +
> + for (i = 0; i < LSMBLOB_ENTRIES; i++)
> + blob->secid[i] = secid;
> +}
> +
> +/**
> + * lsmblob_is_set - report if there is an value in the lsmblob
> + * @blob: Pointer to the exported LSM data
> + *
> + * Returns true if there is a secid set, false otherwise
> + */
> +static inline bool lsmblob_is_set(struct lsmblob *blob)
> +{
> + struct lsmblob empty = {};
> +
> + return !!memcmp(blob, &empty, sizeof(*blob));
> +}
> +
> +/**
> + * lsmblob_equal - report if the two lsmblob's are equal
> + * @bloba: Pointer to one LSM data
> + * @blobb: Pointer to the other LSM data
> + *
> + * Returns true if all entries in the two are equal, false otherwise
> + */
> +static inline bool lsmblob_equal(struct lsmblob *bloba, struct lsmblob *blobb)
> +{
> + return !memcmp(bloba, blobb, sizeof(*bloba));
> +}
> +
> /* These functions are in security/commoncap.c */
> extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
> int cap, unsigned int opts);
> @@ -1820,8 +1878,8 @@ static inline int security_key_getsecurity(struct key *key, char **_buffer)
> #ifdef CONFIG_SECURITY
> int security_audit_rule_init(u32 field, u32 op, char *rulestr, void **lsmrule);
> int security_audit_rule_known(struct audit_krule *krule);
> -int security_audit_rule_match(u32 secid, u32 field, u32 op, void *lsmrule);
> -void security_audit_rule_free(void *lsmrule);
> +int security_audit_rule_match(u32 secid, u32 field, u32 op, void **lsmrule);
> +void security_audit_rule_free(void **lsmrule);
>
> #else
>
> @@ -1837,12 +1895,12 @@ static inline int security_audit_rule_known(struct audit_krule *krule)
> }
>
> static inline int security_audit_rule_match(u32 secid, u32 field, u32 op,
> - void *lsmrule)
> + void **lsmrule)
> {
> return 0;
> }
>
> -static inline void security_audit_rule_free(void *lsmrule)
> +static inline void security_audit_rule_free(void **lsmrule)
> { }
>
> #endif /* CONFIG_SECURITY */
> diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
> index a10e2997aa6c..f9a632ae7be1 100644
> --- a/kernel/auditfilter.c
> +++ b/kernel/auditfilter.c
> @@ -74,7 +74,7 @@ static void audit_free_lsm_field(struct audit_field *f)
> case AUDIT_OBJ_LEV_LOW:
> case AUDIT_OBJ_LEV_HIGH:
> kfree(f->lsm_str);
> - security_audit_rule_free(f->lsm_rule);
> + security_audit_rule_free(f->lsm_rules);
> }
> }
>
> @@ -519,9 +519,10 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
> goto exit_free;
> }
> entry->rule.buflen += f_val;
> + f->lsm_isset = true;
> f->lsm_str = str;
> err = security_audit_rule_init(f->type, f->op, str,
> - (void **)&f->lsm_rule);
> + f->lsm_rules);
> /* Keep currently invalid fields around in case they
> * become valid after a policy reload. */
> if (err == -EINVAL) {
> @@ -774,7 +775,7 @@ static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b)
> return 0;
> }
>
> -/* Duplicate LSM field information. The lsm_rule is opaque, so must be
> +/* Duplicate LSM field information. The lsm_rules is opaque, so must be
> * re-initialized. */
> static inline int audit_dupe_lsm_field(struct audit_field *df,
> struct audit_field *sf)
> @@ -788,9 +789,9 @@ static inline int audit_dupe_lsm_field(struct audit_field *df,
> return -ENOMEM;
> df->lsm_str = lsm_str;
>
> - /* our own (refreshed) copy of lsm_rule */
> + /* our own (refreshed) copy of lsm_rules */
> ret = security_audit_rule_init(df->type, df->op, df->lsm_str,
> - (void **)&df->lsm_rule);
> + df->lsm_rules);
> /* Keep currently invalid fields around in case they
> * become valid after a policy reload. */
> if (ret == -EINVAL) {
> @@ -842,7 +843,7 @@ struct audit_entry *audit_dupe_rule(struct audit_krule *old)
> new->tree = old->tree;
> memcpy(new->fields, old->fields, sizeof(struct audit_field) * fcount);
>
> - /* deep copy this information, updating the lsm_rule fields, because
> + /* deep copy this information, updating the lsm_rules fields, because
> * the originals will all be freed when the old rule is freed. */
> for (i = 0; i < fcount; i++) {
> switch (new->fields[i].type) {
> @@ -1358,10 +1359,11 @@ int audit_filter(int msgtype, unsigned int listtype)
> case AUDIT_SUBJ_TYPE:
> case AUDIT_SUBJ_SEN:
> case AUDIT_SUBJ_CLR:
> - if (f->lsm_rule) {
> + if (f->lsm_isset) {
> security_task_getsecid(current, &sid);
> result = security_audit_rule_match(sid,
> - f->type, f->op, f->lsm_rule);
> + f->type, f->op,
> + f->lsm_rules);
> }
> break;
> case AUDIT_EXE:
> @@ -1388,7 +1390,7 @@ int audit_filter(int msgtype, unsigned int listtype)
> return ret;
> }
>
> -static int update_lsm_rule(struct audit_krule *r)
> +static int update_lsm_rules(struct audit_krule *r)
> {
> struct audit_entry *entry = container_of(r, struct audit_entry, rule);
> struct audit_entry *nentry;
> @@ -1420,7 +1422,7 @@ static int update_lsm_rule(struct audit_krule *r)
> return err;
> }
>
> -/* This function will re-initialize the lsm_rule field of all applicable rules.
> +/* This function will re-initialize the lsm_rules field of all applicable rules.
> * It will traverse the filter lists serarching for rules that contain LSM
> * specific filter fields. When such a rule is found, it is copied, the
> * LSM field is re-initialized, and the old rule is replaced with the
> @@ -1435,7 +1437,7 @@ int audit_update_lsm_rules(void)
>
> for (i = 0; i < AUDIT_NR_FILTERS; i++) {
> list_for_each_entry_safe(r, n, &audit_rules_list[i], list) {
> - int res = update_lsm_rule(r);
> + int res = update_lsm_rules(r);
> if (!err)
> err = res;
> }
> diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> index 468a23390457..bafa03a5c866 100644
> --- a/kernel/auditsc.c
> +++ b/kernel/auditsc.c
> @@ -649,14 +649,14 @@ static int audit_filter_rules(struct task_struct *tsk,
> match for now to avoid losing information that
> may be wanted. An error message will also be
> logged upon error */
> - if (f->lsm_rule) {
> + if (f->lsm_isset) {
> if (need_sid) {
> security_task_getsecid(tsk, &sid);
> need_sid = 0;
> }
> result = security_audit_rule_match(sid, f->type,
> f->op,
> - f->lsm_rule);
> + f->lsm_rules);
> }
> break;
> case AUDIT_OBJ_USER:
> @@ -666,21 +666,21 @@ static int audit_filter_rules(struct task_struct *tsk,
> case AUDIT_OBJ_LEV_HIGH:
> /* The above note for AUDIT_SUBJ_USER...AUDIT_SUBJ_CLR
> also applies here */
> - if (f->lsm_rule) {
> + if (f->lsm_isset) {
> /* Find files that match */
> if (name) {
> result = security_audit_rule_match(
> name->osid,
> f->type,
> f->op,
> - f->lsm_rule);
> + f->lsm_rules);
> } else if (ctx) {
> list_for_each_entry(n, &ctx->names_list, list) {
> if (security_audit_rule_match(
> n->osid,
> f->type,
> f->op,
> - f->lsm_rule)) {
> + f->lsm_rules)) {
> ++result;
> break;
> }
> @@ -691,7 +691,7 @@ static int audit_filter_rules(struct task_struct *tsk,
> break;
> if (security_audit_rule_match(ctx->ipc.osid,
> f->type, f->op,
> - f->lsm_rule))
> + f->lsm_rules))
> ++result;
> }
> break;
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index f1c365905d5e..432915c1d427 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -1152,6 +1152,11 @@ struct lsm_blob_sizes apparmor_blob_sizes __lsm_ro_after_init = {
> .lbs_sock = sizeof(struct aa_sk_ctx),
> };
>
> +static struct lsm_id apparmor_lsmid __lsm_ro_after_init = {
> + .lsm = "apparmor",
> + .slot = LSMBLOB_NEEDED
> +};
> +
> static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
> LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check),
> LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme),
> @@ -1852,7 +1857,7 @@ static int __init apparmor_init(void)
> goto buffers_out;
> }
> security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks),
> - "apparmor");
> + &apparmor_lsmid);
>
> /* Report that AppArmor successfully initialized */
> apparmor_initialized = 1;
> diff --git a/security/commoncap.c b/security/commoncap.c
> index 59bf3c1674c8..959a9f96b7f1 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -1341,6 +1341,11 @@ int cap_mmap_file(struct file *file, unsigned long reqprot,
>
> #ifdef CONFIG_SECURITY
>
> +static struct lsm_id capability_lsmid __lsm_ro_after_init = {
> + .lsm = "capability",
> + .slot = LSMBLOB_NOT_NEEDED
> +};
> +
> static struct security_hook_list capability_hooks[] __lsm_ro_after_init = {
> LSM_HOOK_INIT(capable, cap_capable),
> LSM_HOOK_INIT(settime, cap_settime),
> @@ -1365,7 +1370,7 @@ static struct security_hook_list capability_hooks[] __lsm_ro_after_init = {
> static int __init capability_init(void)
> {
> security_add_hooks(capability_hooks, ARRAY_SIZE(capability_hooks),
> - "capability");
> + &capability_lsmid);
> return 0;
> }
>
> diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
> index e493063a3c34..a442b8940e93 100644
> --- a/security/integrity/ima/ima_policy.c
> +++ b/security/integrity/ima/ima_policy.c
> @@ -73,7 +73,7 @@ struct ima_rule_entry {
> bool (*fowner_op)(kuid_t, kuid_t); /* uid_eq(), uid_gt(), uid_lt() */
> int pcr;
> struct {
> - void *rule; /* LSM file metadata specific */
> + void *rules[LSMBLOB_ENTRIES];
> void *args_p; /* audit value */
> int type; /* audit type */
> } lsm[MAX_LSM_RULES];
> @@ -82,6 +82,22 @@ struct ima_rule_entry {
> struct ima_template_desc *template;
> };
>
> +/**
> + * ima_lsm_isset - Is a rule set for any of the active security modules
> + * @rules: The set of IMA rules to check.
> + *
> + * If a rule is set for any LSM return true, otherwise return false.
> + */
> +static inline bool ima_lsm_isset(void *rules[])
> +{
> + int i;
> +
> + for (i = 0; i < LSMBLOB_ENTRIES; i++)
> + if (rules[i])
> + return true;
> + return false;
> +}
> +
> /*
> * Without LSM specific knowledge, the default policy can only be
> * written in terms of .action, .func, .mask, .fsmagic, .uid, and .fowner
> @@ -256,9 +272,11 @@ __setup("ima_appraise_tcb", default_appraise_policy_setup);
> static void ima_lsm_free_rule(struct ima_rule_entry *entry)
> {
> int i;
> + int r;
>
> for (i = 0; i < MAX_LSM_RULES; i++) {
> - kfree(entry->lsm[i].rule);
> + for (r = 0; r < LSMBLOB_ENTRIES; r++)
> + kfree(entry->lsm[i].rules[r]);
> kfree(entry->lsm[i].args_p);
> }
> kfree(entry);
> @@ -293,8 +311,8 @@ static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_rule_entry *entry)
> security_filter_rule_init(nentry->lsm[i].type,
> Audit_equal,
> nentry->lsm[i].args_p,
> - &nentry->lsm[i].rule);
> - if (!nentry->lsm[i].rule)
> + nentry->lsm[i].rules);
> + if (!ima_lsm_isset(nentry->lsm[i].rules))
> pr_warn("rule for LSM \'%s\' is undefined\n",
> (char *)entry->lsm[i].args_p);
> }
> @@ -463,7 +481,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode,
> int rc = 0;
> u32 osid;
>
> - if (!rule->lsm[i].rule) {
> + if (!ima_lsm_isset(rule->lsm[i].rules)) {
> if (!rule->lsm[i].args_p)
> continue;
> else
> @@ -477,7 +495,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode,
> rc = security_filter_rule_match(osid,
> rule->lsm[i].type,
> Audit_equal,
> - rule->lsm[i].rule);
> + rule->lsm[i].rules);
> break;
> case LSM_SUBJ_USER:
> case LSM_SUBJ_ROLE:
> @@ -485,7 +503,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode,
> rc = security_filter_rule_match(secid,
> rule->lsm[i].type,
> Audit_equal,
> - rule->lsm[i].rule);
> + rule->lsm[i].rules);
> default:
> break;
> }
> @@ -872,7 +890,7 @@ static int ima_lsm_rule_init(struct ima_rule_entry *entry,
> {
> int result;
>
> - if (entry->lsm[lsm_rule].rule)
> + if (ima_lsm_isset(entry->lsm[lsm_rule].rules))
> return -EINVAL;
>
> entry->lsm[lsm_rule].args_p = match_strdup(args);
> @@ -883,8 +901,8 @@ static int ima_lsm_rule_init(struct ima_rule_entry *entry,
> result = security_filter_rule_init(entry->lsm[lsm_rule].type,
> Audit_equal,
> entry->lsm[lsm_rule].args_p,
> - &entry->lsm[lsm_rule].rule);
> - if (!entry->lsm[lsm_rule].rule) {
> + entry->lsm[lsm_rule].rules);
> + if (!ima_lsm_isset(entry->lsm[lsm_rule].rules)) {
> pr_warn("rule for LSM \'%s\' is undefined\n",
> (char *)entry->lsm[lsm_rule].args_p);
>
> @@ -1585,7 +1603,7 @@ int ima_policy_show(struct seq_file *m, void *v)
> }
>
> for (i = 0; i < MAX_LSM_RULES; i++) {
> - if (entry->lsm[i].rule) {
> + if (ima_lsm_isset(entry->lsm[i].rules)) {
> switch (i) {
> case LSM_OBJ_USER:
> seq_printf(m, pt(Opt_obj_user),
> diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c
> index ee5cb944f4ad..86317e78899f 100644
> --- a/security/loadpin/loadpin.c
> +++ b/security/loadpin/loadpin.c
> @@ -180,6 +180,11 @@ static int loadpin_load_data(enum kernel_load_data_id id)
> return loadpin_read_file(NULL, (enum kernel_read_file_id) id);
> }
>
> +static struct lsm_id loadpin_lsmid __lsm_ro_after_init = {
> + .lsm = "loadpin",
> + .slot = LSMBLOB_NOT_NEEDED
> +};
> +
> static struct security_hook_list loadpin_hooks[] __lsm_ro_after_init = {
> LSM_HOOK_INIT(sb_free_security, loadpin_sb_free_security),
> LSM_HOOK_INIT(kernel_read_file, loadpin_read_file),
> @@ -227,7 +232,8 @@ static int __init loadpin_init(void)
> pr_info("ready to pin (currently %senforcing)\n",
> enforce ? "" : "not ");
> parse_exclude();
> - security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks), "loadpin");
> + security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks),
> + &loadpin_lsmid);
> return 0;
> }
>
> diff --git a/security/lockdown/lockdown.c b/security/lockdown/lockdown.c
> index 87cbdc64d272..4e24ea3f7b7e 100644
> --- a/security/lockdown/lockdown.c
> +++ b/security/lockdown/lockdown.c
> @@ -75,6 +75,11 @@ static struct security_hook_list lockdown_hooks[] __lsm_ro_after_init = {
> LSM_HOOK_INIT(locked_down, lockdown_is_locked_down),
> };
>
> +static struct lsm_id lockdown_lsmid __lsm_ro_after_init = {
> + .lsm = "lockdown",
> + .slot = LSMBLOB_NOT_NEEDED
> +};
> +
> static int __init lockdown_lsm_init(void)
> {
> #if defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY)
> @@ -83,7 +88,7 @@ static int __init lockdown_lsm_init(void)
> lock_kernel_down("Kernel configuration", LOCKDOWN_CONFIDENTIALITY_MAX);
> #endif
> security_add_hooks(lockdown_hooks, ARRAY_SIZE(lockdown_hooks),
> - "lockdown");
> + &lockdown_lsmid);
> return 0;
> }
>
> diff --git a/security/safesetid/lsm.c b/security/safesetid/lsm.c
> index 7760019ad35d..950dfb7f931e 100644
> --- a/security/safesetid/lsm.c
> +++ b/security/safesetid/lsm.c
> @@ -149,6 +149,11 @@ static int safesetid_task_fix_setuid(struct cred *new,
> return -EACCES;
> }
>
> +static struct lsm_id safesetid_lsmid __lsm_ro_after_init = {
> + .lsm = "safesetid",
> + .slot = LSMBLOB_NOT_NEEDED
> +};
> +
> static struct security_hook_list safesetid_security_hooks[] = {
> LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
> LSM_HOOK_INIT(capable, safesetid_security_capable)
> @@ -157,7 +162,8 @@ static struct security_hook_list safesetid_security_hooks[] = {
> static int __init safesetid_security_init(void)
> {
> security_add_hooks(safesetid_security_hooks,
> - ARRAY_SIZE(safesetid_security_hooks), "safesetid");
> + ARRAY_SIZE(safesetid_security_hooks),
> + &safesetid_lsmid);
>
> /* Report that SafeSetID successfully initialized */
> safesetid_initialized = 1;
> diff --git a/security/security.c b/security/security.c
> index d2366c694cd8..17d701cd7f69 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -340,6 +340,7 @@ static void __init ordered_lsm_init(void)
> init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
> init_debug("sock blob size = %d\n", blob_sizes.lbs_sock);
> init_debug("task blob size = %d\n", blob_sizes.lbs_task);
> + init_debug("lsmblob size = %zu\n", sizeof(struct lsmblob));
>
> /*
> * Create any kmem_caches needed for blobs
> @@ -467,21 +468,36 @@ static int lsm_append(const char *new, char **result)
> return 0;
> }
>
> +/*
> + * Current index to use while initializing the lsmblob secid list.
> + */
> +static int lsm_slot __lsm_ro_after_init;
> +
> /**
> * security_add_hooks - Add a modules hooks to the hook lists.
> * @hooks: the hooks to add
> * @count: the number of hooks to add
> - * @lsm: the name of the security module
> + * @lsmid: the the identification information for the security module
> *
> * Each LSM has to register its hooks with the infrastructure.
> + * If the LSM is using hooks that export secids allocate a slot
> + * for it in the lsmblob.
> */
> void __init security_add_hooks(struct security_hook_list *hooks, int count,
> - char *lsm)
> + struct lsm_id *lsmid)
> {
> int i;
>
> + if (lsmid->slot == LSMBLOB_NEEDED) {
> + if (lsm_slot >= LSMBLOB_ENTRIES)
> + panic("%s Too many LSMs registered.\n", __func__);
> + lsmid->slot = lsm_slot++;
> + init_debug("%s assigned lsmblob slot %d\n", lsmid->lsm,
> + lsmid->slot);
> + }
> +
> for (i = 0; i < count; i++) {
> - hooks[i].lsm = lsm;
> + hooks[i].lsmid = lsmid;
> hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
> }
>
> @@ -490,7 +506,7 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
> * and fix this up afterwards.
> */
> if (slab_is_available()) {
> - if (lsm_append(lsm, &lsm_names) < 0)
> + if (lsm_append(lsmid->lsm, &lsm_names) < 0)
> panic("%s - Cannot get early memory.\n", __func__);
> }
> }
> @@ -1989,7 +2005,7 @@ int security_getprocattr(struct task_struct *p, const char *lsm, char *name,
> struct security_hook_list *hp;
>
> hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
> - if (lsm != NULL && strcmp(lsm, hp->lsm))
> + if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
> continue;
> return hp->hook.getprocattr(p, name, value);
> }
> @@ -2002,7 +2018,7 @@ int security_setprocattr(const char *lsm, const char *name, void *value,
> struct security_hook_list *hp;
>
> hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
> - if (lsm != NULL && strcmp(lsm, hp->lsm))
> + if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
> continue;
> return hp->hook.setprocattr(name, value, size);
> }
> @@ -2494,7 +2510,24 @@ int security_key_getsecurity(struct key *key, char **_buffer)
>
> int security_audit_rule_init(u32 field, u32 op, char *rulestr, void **lsmrule)
> {
> - return call_int_hook(audit_rule_init, 0, field, op, rulestr, lsmrule);
> + struct security_hook_list *hp;
> + bool one_is_good = false;
> + int rc = 0;
> + int trc;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.audit_rule_init, list) {
> + if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> + continue;
> + trc = hp->hook.audit_rule_init(field, op, rulestr,
> + &lsmrule[hp->lsmid->slot]);
> + if (trc == 0)
> + one_is_good = true;
> + else
> + rc = trc;
> + }
> + if (one_is_good)
> + return 0;
> + return rc;
> }
>
> int security_audit_rule_known(struct audit_krule *krule)
> @@ -2502,14 +2535,31 @@ int security_audit_rule_known(struct audit_krule *krule)
> return call_int_hook(audit_rule_known, 0, krule);
> }
>
> -void security_audit_rule_free(void *lsmrule)
> +void security_audit_rule_free(void **lsmrule)
> {
> - call_void_hook(audit_rule_free, lsmrule);
> + struct security_hook_list *hp;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.audit_rule_free, list) {
> + if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> + continue;
> + hp->hook.audit_rule_free(lsmrule[hp->lsmid->slot]);
> + }
> }
>
> -int security_audit_rule_match(u32 secid, u32 field, u32 op, void *lsmrule)
> +int security_audit_rule_match(u32 secid, u32 field, u32 op, void **lsmrule)
> {
> - return call_int_hook(audit_rule_match, 0, secid, field, op, lsmrule);
> + struct security_hook_list *hp;
> + int rc;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.audit_rule_match, list) {
> + if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> + continue;
> + rc = hp->hook.audit_rule_match(secid, field, op,
> + &lsmrule[hp->lsmid->slot]);
> + if (rc)
> + return rc;
> + }
> + return 0;
> }
> #endif /* CONFIG_AUDIT */
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 7f312714d96a..1c821bec7472 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6929,6 +6929,11 @@ static int selinux_perf_event_write(struct perf_event *event)
> }
> #endif
>
> +static struct lsm_id selinux_lsmid __lsm_ro_after_init = {
> + .lsm = "selinux",
> + .slot = LSMBLOB_NEEDED
> +};
> +
> /*
> * IMPORTANT NOTE: When adding new hooks, please be careful to keep this order:
> * 1. any hooks that don't belong to (2.) or (3.) below,
> @@ -7240,7 +7245,8 @@ static __init int selinux_init(void)
>
> hashtab_cache_init();
>
> - security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), "selinux");
> + security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks),
> + &selinux_lsmid);
>
> if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))
> panic("SELinux: Unable to register AVC netcache callback\n");
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index 4f9023f7f219..d4655dec2d70 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -4649,6 +4649,11 @@ struct lsm_blob_sizes smack_blob_sizes __lsm_ro_after_init = {
> .lbs_sock = sizeof(struct socket_smack),
> };
>
> +static struct lsm_id smack_lsmid __lsm_ro_after_init = {
> + .lsm = "smack",
> + .slot = LSMBLOB_NEEDED
> +};
> +
> static struct security_hook_list smack_hooks[] __lsm_ro_after_init = {
> LSM_HOOK_INIT(ptrace_access_check, smack_ptrace_access_check),
> LSM_HOOK_INIT(ptrace_traceme, smack_ptrace_traceme),
> @@ -4848,7 +4853,7 @@ static __init int smack_init(void)
> /*
> * Register with LSM
> */
> - security_add_hooks(smack_hooks, ARRAY_SIZE(smack_hooks), "smack");
> + security_add_hooks(smack_hooks, ARRAY_SIZE(smack_hooks), &smack_lsmid);
> smack_enabled = 1;
>
> pr_info("Smack: Initializing.\n");
> diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
> index f9adddc42ac8..e5848069af34 100644
> --- a/security/tomoyo/tomoyo.c
> +++ b/security/tomoyo/tomoyo.c
> @@ -523,6 +523,11 @@ static void tomoyo_task_free(struct task_struct *task)
> }
> }
>
> +static struct lsm_id tomoyo_lsmid __lsm_ro_after_init = {
> + .lsm = "tomoyo",
> + .slot = LSMBLOB_NOT_NEEDED
> +};
> +
> /*
> * tomoyo_security_ops is a "struct security_operations" which is used for
> * registering TOMOYO.
> @@ -575,7 +580,8 @@ static int __init tomoyo_init(void)
> struct tomoyo_task *s = tomoyo_task(current);
>
> /* register ourselves with the security framework */
> - security_add_hooks(tomoyo_hooks, ARRAY_SIZE(tomoyo_hooks), "tomoyo");
> + security_add_hooks(tomoyo_hooks, ARRAY_SIZE(tomoyo_hooks),
> + &tomoyo_lsmid);
> pr_info("TOMOYO Linux initialized\n");
> s->domain_info = &tomoyo_kernel_domain;
> atomic_inc(&tomoyo_kernel_domain.users);
> diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c
> index 536c99646f6a..c582757669f6 100644
> --- a/security/yama/yama_lsm.c
> +++ b/security/yama/yama_lsm.c
> @@ -421,6 +421,11 @@ static int yama_ptrace_traceme(struct task_struct *parent)
> return rc;
> }
>
> +static struct lsm_id yama_lsmid __lsm_ro_after_init = {
> + .lsm = "yama",
> + .slot = LSMBLOB_NOT_NEEDED
> +};
> +
> static struct security_hook_list yama_hooks[] __lsm_ro_after_init = {
> LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check),
> LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme),
> @@ -477,7 +482,7 @@ static inline void yama_init_sysctl(void) { }
> static int __init yama_init(void)
> {
> pr_info("Yama: becoming mindful.\n");
> - security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks), "yama");
> + security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks), &yama_lsmid);
> yama_init_sysctl();
> return 0;
> }
>
^ permalink raw reply
* Re: [PATCH v19 15/23] LSM: Use lsmcontext in security_secid_to_secctx
From: John Johansen @ 2020-07-28 20:13 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: linux-audit, keescook, penguin-kernel, paul, sds, netdev
In-Reply-To: <20200724203226.16374-16-casey@schaufler-ca.com>
On 7/24/20 1:32 PM, Casey Schaufler wrote:
> Replace the (secctx,seclen) pointer pair with a single
> lsmcontext pointer to allow return of the LSM identifier
> along with the context and context length. This allows
> security_release_secctx() to know how to release the
> context. Callers have been modified to use or save the
> returned data from the new structure.
>
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
> Acked-by: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
> Cc: netdev@vger.kernel.org
> ---
> drivers/android/binder.c | 26 +++++++---------
> include/linux/security.h | 4 +--
> include/net/scm.h | 10 ++-----
> kernel/audit.c | 35 ++++++++--------------
> kernel/auditsc.c | 31 +++++++------------
> net/ipv4/ip_sockglue.c | 7 ++---
> net/netfilter/nf_conntrack_netlink.c | 18 +++++------
> net/netfilter/nf_conntrack_standalone.c | 7 ++---
> net/netfilter/nfnetlink_queue.c | 5 +++-
> net/netlabel/netlabel_unlabeled.c | 40 ++++++++-----------------
> net/netlabel/netlabel_user.c | 7 ++---
> security/security.c | 10 +++++--
> 12 files changed, 76 insertions(+), 124 deletions(-)
>
> diff --git a/drivers/android/binder.c b/drivers/android/binder.c
> index b7ab206f8bb3..ceb5987c7d76 100644
> --- a/drivers/android/binder.c
> +++ b/drivers/android/binder.c
> @@ -2861,9 +2861,7 @@ static void binder_transaction(struct binder_proc *proc,
> binder_size_t last_fixup_min_off = 0;
> struct binder_context *context = proc->context;
> int t_debug_id = atomic_inc_return(&binder_last_id);
> - char *secctx = NULL;
> - u32 secctx_sz = 0;
> - struct lsmcontext scaff; /* scaffolding */
> + struct lsmcontext lsmctx = { };
>
> e = binder_transaction_log_add(&binder_transaction_log);
> e->debug_id = t_debug_id;
> @@ -3111,14 +3109,14 @@ static void binder_transaction(struct binder_proc *proc,
> size_t added_size;
>
> security_task_getsecid(proc->tsk, &blob);
> - ret = security_secid_to_secctx(&blob, &secctx, &secctx_sz);
> + ret = security_secid_to_secctx(&blob, &lsmctx);
> if (ret) {
> return_error = BR_FAILED_REPLY;
> return_error_param = ret;
> return_error_line = __LINE__;
> goto err_get_secctx_failed;
> }
> - added_size = ALIGN(secctx_sz, sizeof(u64));
> + added_size = ALIGN(lsmctx.len, sizeof(u64));
> extra_buffers_size += added_size;
> if (extra_buffers_size < added_size) {
> /* integer overflow of extra_buffers_size */
> @@ -3145,24 +3143,22 @@ static void binder_transaction(struct binder_proc *proc,
> t->buffer = NULL;
> goto err_binder_alloc_buf_failed;
> }
> - if (secctx) {
> + if (lsmctx.context) {
> int err;
> size_t buf_offset = ALIGN(tr->data_size, sizeof(void *)) +
> ALIGN(tr->offsets_size, sizeof(void *)) +
> ALIGN(extra_buffers_size, sizeof(void *)) -
> - ALIGN(secctx_sz, sizeof(u64));
> + ALIGN(lsmctx.len, sizeof(u64));
>
> t->security_ctx = (uintptr_t)t->buffer->user_data + buf_offset;
> err = binder_alloc_copy_to_buffer(&target_proc->alloc,
> t->buffer, buf_offset,
> - secctx, secctx_sz);
> + lsmctx.context, lsmctx.len);
> if (err) {
> t->security_ctx = 0;
> WARN_ON(1);
> }
> - lsmcontext_init(&scaff, secctx, secctx_sz, 0);
> - security_release_secctx(&scaff);
> - secctx = NULL;
> + security_release_secctx(&lsmctx);
> }
> t->buffer->debug_id = t->debug_id;
> t->buffer->transaction = t;
> @@ -3218,7 +3214,7 @@ static void binder_transaction(struct binder_proc *proc,
> off_end_offset = off_start_offset + tr->offsets_size;
> sg_buf_offset = ALIGN(off_end_offset, sizeof(void *));
> sg_buf_end_offset = sg_buf_offset + extra_buffers_size -
> - ALIGN(secctx_sz, sizeof(u64));
> + ALIGN(lsmctx.len, sizeof(u64));
> off_min = 0;
> for (buffer_offset = off_start_offset; buffer_offset < off_end_offset;
> buffer_offset += sizeof(binder_size_t)) {
> @@ -3494,10 +3490,8 @@ static void binder_transaction(struct binder_proc *proc,
> binder_alloc_free_buf(&target_proc->alloc, t->buffer);
> err_binder_alloc_buf_failed:
> err_bad_extra_size:
> - if (secctx) {
> - lsmcontext_init(&scaff, secctx, secctx_sz, 0);
> - security_release_secctx(&scaff);
> - }
> + if (lsmctx.context)
> + security_release_secctx(&lsmctx);
> err_get_secctx_failed:
> kfree(tcomplete);
> binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
> diff --git a/include/linux/security.h b/include/linux/security.h
> index f67e4084b893..43f8a2660d37 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -553,7 +553,7 @@ int security_setprocattr(const char *lsm, const char *name, void *value,
> size_t size);
> int security_netlink_send(struct sock *sk, struct sk_buff *skb);
> int security_ismaclabel(const char *name);
> -int security_secid_to_secctx(struct lsmblob *blob, char **secdata, u32 *seclen);
> +int security_secid_to_secctx(struct lsmblob *blob, struct lsmcontext *cp);
> int security_secctx_to_secid(const char *secdata, u32 seclen,
> struct lsmblob *blob);
> void security_release_secctx(struct lsmcontext *cp);
> @@ -1371,7 +1371,7 @@ static inline int security_ismaclabel(const char *name)
> }
>
> static inline int security_secid_to_secctx(struct lsmblob *blob,
> - char **secdata, u32 *seclen)
> + struct lsmcontext *cp)
> {
> return -EOPNOTSUPP;
> }
> diff --git a/include/net/scm.h b/include/net/scm.h
> index 30ba801c91bd..4a6ad8caf423 100644
> --- a/include/net/scm.h
> +++ b/include/net/scm.h
> @@ -93,18 +93,14 @@ static __inline__ int scm_send(struct socket *sock, struct msghdr *msg,
> static inline void scm_passec(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm)
> {
> struct lsmcontext context;
> - char *secdata;
> - u32 seclen;
> int err;
>
> if (test_bit(SOCK_PASSSEC, &sock->flags)) {
> - err = security_secid_to_secctx(&scm->lsmblob, &secdata,
> - &seclen);
> + err = security_secid_to_secctx(&scm->lsmblob, &context);
>
> if (!err) {
> - put_cmsg(msg, SOL_SOCKET, SCM_SECURITY, seclen, secdata);
> - /*scaffolding*/
> - lsmcontext_init(&context, secdata, seclen, 0);
> + put_cmsg(msg, SOL_SOCKET, SCM_SECURITY,
> + context.len, context.context);
> security_release_secctx(&context);
> }
> }
> diff --git a/kernel/audit.c b/kernel/audit.c
> index 3378c773b1c1..d300e41ca443 100644
> --- a/kernel/audit.c
> +++ b/kernel/audit.c
> @@ -1186,9 +1186,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
> struct audit_buffer *ab;
> u16 msg_type = nlh->nlmsg_type;
> struct audit_sig_info *sig_data;
> - char *ctx = NULL;
> - u32 len;
> - struct lsmcontext scaff; /* scaffolding */
> + struct lsmcontext context = { };
>
> err = audit_netlink_ok(skb, msg_type);
> if (err)
> @@ -1430,30 +1428,26 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
> break;
> }
> case AUDIT_SIGNAL_INFO:
> - len = 0;
> if (lsmblob_is_set(&audit_sig_lsm)) {
> - err = security_secid_to_secctx(&audit_sig_lsm, &ctx,
> - &len);
> + err = security_secid_to_secctx(&audit_sig_lsm,
> + &context);
> if (err)
> return err;
> }
> - sig_data = kmalloc(sizeof(*sig_data) + len, GFP_KERNEL);
> + sig_data = kmalloc(sizeof(*sig_data) + context.len, GFP_KERNEL);
> if (!sig_data) {
> - if (lsmblob_is_set(&audit_sig_lsm)) {
> - lsmcontext_init(&scaff, ctx, len, 0);
> - security_release_secctx(&scaff);
> - }
> + if (lsmblob_is_set(&audit_sig_lsm))
> + security_release_secctx(&context);
> return -ENOMEM;
> }
> sig_data->uid = from_kuid(&init_user_ns, audit_sig_uid);
> sig_data->pid = audit_sig_pid;
> if (lsmblob_is_set(&audit_sig_lsm)) {
> - memcpy(sig_data->ctx, ctx, len);
> - lsmcontext_init(&scaff, ctx, len, 0);
> - security_release_secctx(&scaff);
> + memcpy(sig_data->ctx, context.context, context.len);
> + security_release_secctx(&context);
> }
> audit_send_reply(skb, seq, AUDIT_SIGNAL_INFO, 0, 0,
> - sig_data, sizeof(*sig_data) + len);
> + sig_data, sizeof(*sig_data) + context.len);
use after "free", move the security_release_secctx(&context) to after here
> kfree(sig_data);
> break;
> case AUDIT_TTY_GET: {
> @@ -2116,26 +2110,23 @@ void audit_log_key(struct audit_buffer *ab, char *key)
>
> int audit_log_task_context(struct audit_buffer *ab)
> {
> - char *ctx = NULL;
> - unsigned len;
> int error;
> struct lsmblob blob;
> - struct lsmcontext scaff; /* scaffolding */
> + struct lsmcontext context;
>
> security_task_getsecid(current, &blob);
> if (!lsmblob_is_set(&blob))
> return 0;
>
> - error = security_secid_to_secctx(&blob, &ctx, &len);
> + error = security_secid_to_secctx(&blob, &context);
> if (error) {
> if (error != -EINVAL)
> goto error_path;
> return 0;
> }
>
> - audit_log_format(ab, " subj=%s", ctx);
> - lsmcontext_init(&scaff, ctx, len, 0);
> - security_release_secctx(&scaff);
> + audit_log_format(ab, " subj=%s", context.context);
> + security_release_secctx(&context);
> return 0;
>
> error_path:
> diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> index ac6836c1f2d3..1f7bd6b34ec7 100644
> --- a/kernel/auditsc.c
> +++ b/kernel/auditsc.c
> @@ -980,9 +980,7 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid,
> struct lsmblob *blob, char *comm)
> {
> struct audit_buffer *ab;
> - struct lsmcontext lsmcxt;
> - char *ctx = NULL;
> - u32 len;
> + struct lsmcontext lsmctx;
> int rc = 0;
>
> ab = audit_log_start(context, GFP_KERNEL, AUDIT_OBJ_PID);
> @@ -993,13 +991,12 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid,
> from_kuid(&init_user_ns, auid),
> from_kuid(&init_user_ns, uid), sessionid);
> if (lsmblob_is_set(blob)) {
> - if (security_secid_to_secctx(blob, &ctx, &len)) {
> + if (security_secid_to_secctx(blob, &lsmctx)) {
> audit_log_format(ab, " obj=(none)");
> rc = 1;
> } else {
> - audit_log_format(ab, " obj=%s", ctx);
> - lsmcontext_init(&lsmcxt, ctx, len, 0); /*scaffolding*/
> - security_release_secctx(&lsmcxt);
> + audit_log_format(ab, " obj=%s", lsmctx.context);
> + security_release_secctx(&lsmctx);
> }
> }
> audit_log_format(ab, " ocomm=");
> @@ -1212,7 +1209,6 @@ static void audit_log_fcaps(struct audit_buffer *ab, struct audit_names *name)
>
> static void show_special(struct audit_context *context, int *call_panic)
> {
> - struct lsmcontext lsmcxt;
> struct audit_buffer *ab;
> int i;
>
> @@ -1236,17 +1232,15 @@ static void show_special(struct audit_context *context, int *call_panic)
> from_kgid(&init_user_ns, context->ipc.gid),
> context->ipc.mode);
> if (osid) {
> - char *ctx = NULL;
> - u32 len;
> + struct lsmcontext lsmcxt;
> struct lsmblob blob;
>
> lsmblob_init(&blob, osid);
> - if (security_secid_to_secctx(&blob, &ctx, &len)) {
> + if (security_secid_to_secctx(&blob, &lsmcxt)) {
> audit_log_format(ab, " osid=%u", osid);
> *call_panic = 1;
> } else {
> - audit_log_format(ab, " obj=%s", ctx);
> - lsmcontext_init(&lsmcxt, ctx, len, 0);
> + audit_log_format(ab, " obj=%s", lsmcxt.context);
> security_release_secctx(&lsmcxt);
> }
> }
> @@ -1390,20 +1384,17 @@ static void audit_log_name(struct audit_context *context, struct audit_names *n,
> MAJOR(n->rdev),
> MINOR(n->rdev));
> if (n->osid != 0) {
> - char *ctx = NULL;
> - u32 len;
> struct lsmblob blob;
> - struct lsmcontext lsmcxt;
> + struct lsmcontext lsmctx;
>
> lsmblob_init(&blob, n->osid);
> - if (security_secid_to_secctx(&blob, &ctx, &len)) {
> + if (security_secid_to_secctx(&blob, &lsmctx)) {
> audit_log_format(ab, " osid=%u", n->osid);
> if (call_panic)
> *call_panic = 2;
> } else {
> - audit_log_format(ab, " obj=%s", ctx);
> - lsmcontext_init(&lsmcxt, ctx, len, 0); /* scaffolding */
> - security_release_secctx(&lsmcxt);
> + audit_log_format(ab, " obj=%s", lsmctx.context);
> + security_release_secctx(&lsmctx);
> }
> }
>
> diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
> index 6391a570f9ad..176ac9ce6069 100644
> --- a/net/ipv4/ip_sockglue.c
> +++ b/net/ipv4/ip_sockglue.c
> @@ -132,20 +132,17 @@ static void ip_cmsg_recv_security(struct msghdr *msg, struct sk_buff *skb)
> {
> struct lsmcontext context;
> struct lsmblob lb;
> - char *secdata;
> - u32 seclen;
> int err;
>
> err = security_socket_getpeersec_dgram(NULL, skb, &lb);
> if (err)
> return;
>
> - err = security_secid_to_secctx(&lb, &secdata, &seclen);
> + err = security_secid_to_secctx(&lb, &context);
> if (err)
> return;
>
> - put_cmsg(msg, SOL_IP, SCM_SECURITY, seclen, secdata);
> - lsmcontext_init(&context, secdata, seclen, 0); /* scaffolding */
> + put_cmsg(msg, SOL_IP, SCM_SECURITY, context.len, context.context);
> security_release_secctx(&context);
> }
>
> diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
> index 1c45ca8c3c21..e38b5182e301 100644
> --- a/net/netfilter/nf_conntrack_netlink.c
> +++ b/net/netfilter/nf_conntrack_netlink.c
> @@ -331,8 +331,7 @@ static int ctnetlink_dump_mark(struct sk_buff *skb, const struct nf_conn *ct)
> static int ctnetlink_dump_secctx(struct sk_buff *skb, const struct nf_conn *ct)
> {
> struct nlattr *nest_secctx;
> - int len, ret;
> - char *secctx;
> + int ret;
> struct lsmblob blob;
> struct lsmcontext context;
>
> @@ -340,7 +339,7 @@ static int ctnetlink_dump_secctx(struct sk_buff *skb, const struct nf_conn *ct)
> * security_secid_to_secctx() will know which security module
> * to use to create the secctx. */
> lsmblob_init(&blob, ct->secmark);
> - ret = security_secid_to_secctx(&blob, &secctx, &len);
> + ret = security_secid_to_secctx(&blob, &context);
> if (ret)
> return 0;
>
> @@ -349,13 +348,12 @@ static int ctnetlink_dump_secctx(struct sk_buff *skb, const struct nf_conn *ct)
> if (!nest_secctx)
> goto nla_put_failure;
>
> - if (nla_put_string(skb, CTA_SECCTX_NAME, secctx))
> + if (nla_put_string(skb, CTA_SECCTX_NAME, context.context))
> goto nla_put_failure;
> nla_nest_end(skb, nest_secctx);
>
> ret = 0;
> nla_put_failure:
> - lsmcontext_init(&context, secctx, len, 0); /* scaffolding */
> security_release_secctx(&context);
> return ret;
> }
> @@ -655,15 +653,15 @@ static inline int ctnetlink_secctx_size(const struct nf_conn *ct)
> #ifdef CONFIG_NF_CONNTRACK_SECMARK
> int len, ret;
> struct lsmblob blob;
> + struct lsmcontext context;
>
> - /* lsmblob_init() puts ct->secmark into all of the secids in blob.
> - * security_secid_to_secctx() will know which security module
> - * to use to create the secctx. */
> - lsmblob_init(&blob, ct->secmark);
> - ret = security_secid_to_secctx(&blob, NULL, &len);
> + ret = security_secid_to_secctx(&blob, &context);
> if (ret)
> return 0;
>
> + len = context.len;
> + security_release_secctx(&context);
> +
> return nla_total_size(0) /* CTA_SECCTX */
> + nla_total_size(sizeof(char) * len); /* CTA_SECCTX_NAME */
> #else
> diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c
> index fd01d778c295..0ecd1040f4f1 100644
> --- a/net/netfilter/nf_conntrack_standalone.c
> +++ b/net/netfilter/nf_conntrack_standalone.c
> @@ -173,19 +173,16 @@ static void ct_seq_stop(struct seq_file *s, void *v)
> static void ct_show_secctx(struct seq_file *s, const struct nf_conn *ct)
> {
> int ret;
> - u32 len;
> - char *secctx;
> struct lsmblob blob;
> struct lsmcontext context;
>
> lsmblob_init(&blob, ct->secmark);
> - ret = security_secid_to_secctx(&blob, &secctx, &len);
> + ret = security_secid_to_secctx(&blob, &context);
> if (ret)
> return;
>
> - seq_printf(s, "secctx=%s ", secctx);
> + seq_printf(s, "secctx=%s ", context.context);
>
> - lsmcontext_init(&context, secctx, len, 0); /* scaffolding */
> security_release_secctx(&context);
> }
> #else
> diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
> index c89bd87d0dae..fe19ae7216db 100644
> --- a/net/netfilter/nfnetlink_queue.c
> +++ b/net/netfilter/nfnetlink_queue.c
> @@ -306,6 +306,7 @@ static u32 nfqnl_get_sk_secctx(struct sk_buff *skb, char **secdata)
> u32 seclen = 0;
> #if IS_ENABLED(CONFIG_NETWORK_SECMARK)
> struct lsmblob blob;
> + struct lsmcontext context = { };
>
> if (!skb || !sk_fullsock(skb->sk))
> return 0;
> @@ -317,10 +318,12 @@ static u32 nfqnl_get_sk_secctx(struct sk_buff *skb, char **secdata)
> * blob. security_secid_to_secctx() will know which security
> * module to use to create the secctx. */
> lsmblob_init(&blob, skb->secmark);
> - security_secid_to_secctx(&blob, secdata, &seclen);
> + security_secid_to_secctx(&blob, &context);
> + *secdata = context.context;
> }
>
> read_unlock_bh(&skb->sk->sk_callback_lock);
> + seclen = context.len;
> #endif
> return seclen;
> }
> diff --git a/net/netlabel/netlabel_unlabeled.c b/net/netlabel/netlabel_unlabeled.c
> index 5785e6dcf54b..cf4c56beb3ec 100644
> --- a/net/netlabel/netlabel_unlabeled.c
> +++ b/net/netlabel/netlabel_unlabeled.c
> @@ -375,8 +375,6 @@ int netlbl_unlhsh_add(struct net *net,
> struct netlbl_unlhsh_iface *iface;
> struct audit_buffer *audit_buf = NULL;
> struct lsmcontext context;
> - char *secctx = NULL;
> - u32 secctx_len;
> struct lsmblob blob;
>
> if (addr_len != sizeof(struct in_addr) &&
> @@ -444,12 +442,9 @@ int netlbl_unlhsh_add(struct net *net,
> * security_secid_to_secctx() will know which security module
> * to use to create the secctx. */
> lsmblob_init(&blob, secid);
> - if (security_secid_to_secctx(&blob,
> - &secctx,
> - &secctx_len) == 0) {
> - audit_log_format(audit_buf, " sec_obj=%s", secctx);
> - /* scaffolding */
> - lsmcontext_init(&context, secctx, secctx_len, 0);
> + if (security_secid_to_secctx(&blob, &context) == 0) {
> + audit_log_format(audit_buf, " sec_obj=%s",
> + context.context);
> security_release_secctx(&context);
> }
> audit_log_format(audit_buf, " res=%u", ret_val == 0 ? 1 : 0);
> @@ -482,8 +477,6 @@ static int netlbl_unlhsh_remove_addr4(struct net *net,
> struct audit_buffer *audit_buf;
> struct net_device *dev;
> struct lsmcontext context;
> - char *secctx;
> - u32 secctx_len;
> struct lsmblob blob;
>
> spin_lock(&netlbl_unlhsh_lock);
> @@ -510,11 +503,9 @@ static int netlbl_unlhsh_remove_addr4(struct net *net,
> if (entry != NULL)
> lsmblob_init(&blob, entry->secid);
> if (entry != NULL &&
> - security_secid_to_secctx(&blob,
> - &secctx, &secctx_len) == 0) {
> - audit_log_format(audit_buf, " sec_obj=%s", secctx);
> - /* scaffolding */
> - lsmcontext_init(&context, secctx, secctx_len, 0);
> + security_secid_to_secctx(&blob, &context) == 0) {
> + audit_log_format(audit_buf, " sec_obj=%s",
> + context.context);
> security_release_secctx(&context);
> }
> audit_log_format(audit_buf, " res=%u", entry != NULL ? 1 : 0);
> @@ -553,8 +544,6 @@ static int netlbl_unlhsh_remove_addr6(struct net *net,
> struct audit_buffer *audit_buf;
> struct net_device *dev;
> struct lsmcontext context;
> - char *secctx;
> - u32 secctx_len;
> struct lsmblob blob;
>
> spin_lock(&netlbl_unlhsh_lock);
> @@ -580,10 +569,9 @@ static int netlbl_unlhsh_remove_addr6(struct net *net,
> if (entry != NULL)
> lsmblob_init(&blob, entry->secid);
> if (entry != NULL &&
> - security_secid_to_secctx(&blob,
> - &secctx, &secctx_len) == 0) {
> - audit_log_format(audit_buf, " sec_obj=%s", secctx);
> - lsmcontext_init(&context, secctx, secctx_len, 0);
> + security_secid_to_secctx(&blob, &context) == 0) {
> + audit_log_format(audit_buf, " sec_obj=%s",
> + context.context);
> security_release_secctx(&context);
> }
> audit_log_format(audit_buf, " res=%u", entry != NULL ? 1 : 0);
> @@ -1106,8 +1094,6 @@ static int netlbl_unlabel_staticlist_gen(u32 cmd,
> struct lsmcontext context;
> void *data;
> u32 secid;
> - char *secctx;
> - u32 secctx_len;
> struct lsmblob blob;
>
> data = genlmsg_put(cb_arg->skb, NETLINK_CB(cb_arg->nl_cb->skb).portid,
> @@ -1167,15 +1153,13 @@ static int netlbl_unlabel_staticlist_gen(u32 cmd,
> * security_secid_to_secctx() will know which security module
> * to use to create the secctx. */
> lsmblob_init(&blob, secid);
> - ret_val = security_secid_to_secctx(&blob, &secctx, &secctx_len);
> + ret_val = security_secid_to_secctx(&blob, &context);
> if (ret_val != 0)
> goto list_cb_failure;
> ret_val = nla_put(cb_arg->skb,
> NLBL_UNLABEL_A_SECCTX,
> - secctx_len,
> - secctx);
> - /* scaffolding */
> - lsmcontext_init(&context, secctx, secctx_len, 0);
> + context.len,
> + context.context);
> security_release_secctx(&context);
> if (ret_val != 0)
> goto list_cb_failure;
> diff --git a/net/netlabel/netlabel_user.c b/net/netlabel/netlabel_user.c
> index ef139d8ae7cd..951ba0639d20 100644
> --- a/net/netlabel/netlabel_user.c
> +++ b/net/netlabel/netlabel_user.c
> @@ -85,8 +85,6 @@ struct audit_buffer *netlbl_audit_start_common(int type,
> {
> struct audit_buffer *audit_buf;
> struct lsmcontext context;
> - char *secctx;
> - u32 secctx_len;
> struct lsmblob blob;
>
> if (audit_enabled == AUDIT_OFF)
> @@ -102,9 +100,8 @@ struct audit_buffer *netlbl_audit_start_common(int type,
>
> lsmblob_init(&blob, audit_info->secid);
> if (audit_info->secid != 0 &&
> - security_secid_to_secctx(&blob, &secctx, &secctx_len) == 0) {
> - audit_log_format(audit_buf, " subj=%s", secctx);
> - lsmcontext_init(&context, secctx, secctx_len, 0);/*scaffolding*/
> + security_secid_to_secctx(&blob, &context) == 0) {
> + audit_log_format(audit_buf, " subj=%s", context.context);
> security_release_secctx(&context);
> }
>
> diff --git a/security/security.c b/security/security.c
> index a6d0b6851a66..862f0bc2f114 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2191,18 +2191,22 @@ int security_ismaclabel(const char *name)
> }
> EXPORT_SYMBOL(security_ismaclabel);
>
> -int security_secid_to_secctx(struct lsmblob *blob, char **secdata, u32 *seclen)
> +int security_secid_to_secctx(struct lsmblob *blob, struct lsmcontext *cp)
> {
> struct security_hook_list *hp;
> int display = lsm_task_display(current);
>
> + memset(cp, 0, sizeof(*cp));
> +
> hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx, list) {
> if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> continue;
> - if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
> + if (display == LSMBLOB_INVALID || display == hp->lsmid->slot) {
> + cp->slot = hp->lsmid->slot;
> return hp->hook.secid_to_secctx(
> blob->secid[hp->lsmid->slot],
> - secdata, seclen);
> + &cp->context, &cp->len);
> + }
> }
>
> return LSM_RET_DEFAULT(secid_to_secctx);
>
^ permalink raw reply
* Re: [PATCH v19 13/23] LSM: Specify which LSM to display
From: John Johansen @ 2020-07-28 18:29 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: linux-audit, keescook, penguin-kernel, paul, sds
In-Reply-To: <20200724203226.16374-14-casey@schaufler-ca.com>
On 7/24/20 1:32 PM, Casey Schaufler wrote:
> Create a new entry "display" in the procfs attr directory for
> controlling which LSM security information is displayed for a
> process. A process can only read or write its own display value.
>
> The name of an active LSM that supplies hooks for
> human readable data may be written to "display" to set the
> value. The name of the LSM currently in use can be read from
> "display". At this point there can only be one LSM capable
> of display active. A helper function lsm_task_display() is
> provided to get the display slot for a task_struct.
>
> Setting the "display" requires that all security modules using
> setprocattr hooks allow the action. Each security module is
> responsible for defining its policy.
>
> AppArmor hook provided by John Johansen <john.johansen@canonical.com>
> SELinux hook provided by Stephen Smalley <sds@tycho.nsa.gov>
>
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
> Acked-by: Paul Moore <paul@paul-moore.com>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
> ---
> fs/proc/base.c | 1 +
> include/linux/lsm_hooks.h | 17 +++
> security/apparmor/include/apparmor.h | 3 +-
> security/apparmor/lsm.c | 32 +++++
> security/security.c | 167 ++++++++++++++++++++++++---
> security/selinux/hooks.c | 11 ++
> security/selinux/include/classmap.h | 2 +-
> security/smack/smack_lsm.c | 7 ++
> 8 files changed, 221 insertions(+), 19 deletions(-)
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index d86c0afc8a85..40471a12ced2 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -2794,6 +2794,7 @@ static const struct pid_entry attr_dir_stuff[] = {
> ATTR(NULL, "fscreate", 0666),
> ATTR(NULL, "keycreate", 0666),
> ATTR(NULL, "sockcreate", 0666),
> + ATTR(NULL, "display", 0666),
> #ifdef CONFIG_SECURITY_SMACK
> DIR("smack", 0555,
> proc_smack_attr_dir_inode_ops, proc_smack_attr_dir_ops),
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index c9f792066d86..6908fa03cf31 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -1647,4 +1647,21 @@ static inline void security_delete_hooks(struct security_hook_list *hooks,
>
> extern int lsm_inode_alloc(struct inode *inode);
>
> +/**
> + * lsm_task_display - the "display" LSM for this task
> + * @task: The task to report on
> + *
> + * Returns the task's display LSM slot.
> + */
> +static inline int lsm_task_display(struct task_struct *task)
> +{
> +#ifdef CONFIG_SECURITY
> + int *display = task->security;
> +
> + if (display)
> + return *display;
> +#endif
> + return LSMBLOB_INVALID;
> +}
> +
> #endif /* ! __LINUX_LSM_HOOKS_H */
> diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
> index 1fbabdb565a8..b1622fcb4394 100644
> --- a/security/apparmor/include/apparmor.h
> +++ b/security/apparmor/include/apparmor.h
> @@ -28,8 +28,9 @@
> #define AA_CLASS_SIGNAL 10
> #define AA_CLASS_NET 14
> #define AA_CLASS_LABEL 16
> +#define AA_CLASS_DISPLAY_LSM 17
>
> -#define AA_CLASS_LAST AA_CLASS_LABEL
> +#define AA_CLASS_LAST AA_CLASS_DISPLAY_LSM
>
> /* Control parameters settable through module/boot flags */
> extern enum audit_mode aa_g_audit;
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index 432915c1d427..31a6f11890f1 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -612,6 +612,25 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,
> return error;
> }
>
> +
> +static int profile_display_lsm(struct aa_profile *profile,
> + struct common_audit_data *sa)
> +{
> + struct aa_perms perms = { };
> + unsigned int state;
> +
> + state = PROFILE_MEDIATES(profile, AA_CLASS_DISPLAY_LSM);
> + if (state) {
> + aa_compute_perms(profile->policy.dfa, state, &perms);
> + aa_apply_modes_to_perms(profile, &perms);
> + aad(sa)->label = &profile->label;
> +
> + return aa_check_perms(profile, &perms, AA_MAY_WRITE, sa, NULL);
> + }
> +
> + return 0;
> +}
> +
> static int apparmor_setprocattr(const char *name, void *value,
> size_t size)
> {
> @@ -623,6 +642,19 @@ static int apparmor_setprocattr(const char *name, void *value,
> if (size == 0)
> return -EINVAL;
>
> + /* LSM infrastructure does actual setting of display if allowed */
> + if (!strcmp(name, "display")) {
> + struct aa_profile *profile;
> + struct aa_label *label;
> +
> + aad(&sa)->info = "set display lsm";
> + label = begin_current_label_crit_section();
> + error = fn_for_each_confined(label, profile,
> + profile_display_lsm(profile, &sa));
> + end_current_label_crit_section(label);
> + return error;
> + }
> +
> /* AppArmor requires that the buffer must be null terminated atm */
> if (args[size - 1] != '\0') {
> /* null terminate */
> diff --git a/security/security.c b/security/security.c
> index c3bac45bbb79..e1c9f87db64b 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -75,7 +75,14 @@ static struct kmem_cache *lsm_file_cache;
> static struct kmem_cache *lsm_inode_cache;
>
> char *lsm_names;
> -static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init;
> +
> +/*
> + * The task blob includes the "display" slot used for
> + * chosing which module presents contexts.
> + */
> +static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init = {
> + .lbs_task = sizeof(int),
> +};
since we are stuffing structures in here with arbitrary items might
need alignment this should get changed to sizeof(long). The display
index itself can stay an int. This will just result in padding if
sizeof(int) < sizeof(long)
>
> /* Boot-time LSM user choice */
> static __initdata const char *chosen_lsm_order;
> @@ -470,8 +477,10 @@ static int lsm_append(const char *new, char **result)
>
> /*
> * Current index to use while initializing the lsmblob secid list.
> + * Pointers to the LSM id structures for local use.
> */
> static int lsm_slot __lsm_ro_after_init;
> +static struct lsm_id *lsm_slotlist[LSMBLOB_ENTRIES];
>
> /**
> * security_add_hooks - Add a modules hooks to the hook lists.
> @@ -491,6 +500,7 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
> if (lsmid->slot == LSMBLOB_NEEDED) {
> if (lsm_slot >= LSMBLOB_ENTRIES)
> panic("%s Too many LSMs registered.\n", __func__);
> + lsm_slotlist[lsm_slot] = lsmid;
> lsmid->slot = lsm_slot++;
> init_debug("%s assigned lsmblob slot %d\n", lsmid->lsm,
> lsmid->slot);
> @@ -620,6 +630,8 @@ int lsm_inode_alloc(struct inode *inode)
> */
> static int lsm_task_alloc(struct task_struct *task)
> {
> + int *display;
> +
> if (blob_sizes.lbs_task == 0) {
lbs_task will always be > 0 now, but if you want to keep the guard I am not
opposed
> task->security = NULL;
> return 0;
> @@ -628,6 +640,15 @@ static int lsm_task_alloc(struct task_struct *task)
> task->security = kzalloc(blob_sizes.lbs_task, GFP_KERNEL);
> if (task->security == NULL)
> return -ENOMEM;
> +
> + /*
> + * The start of the task blob contains the "display" LSM slot number.
> + * Start with it set to the invalid slot number, indicating that the
> + * default first registered LSM be displayed.
> + */
> + display = task->security;
> + *display = LSMBLOB_INVALID;
> +
> return 0;
> }
>
> @@ -1628,14 +1649,26 @@ int security_file_open(struct file *file)
>
> int security_task_alloc(struct task_struct *task, unsigned long clone_flags)
> {
> + int *odisplay = current->security;
> + int *ndisplay;
> int rc = lsm_task_alloc(task);
>
> - if (rc)
> + if (unlikely(rc))
> return rc;
> +
> rc = call_int_hook(task_alloc, 0, task, clone_flags);
> - if (unlikely(rc))
> + if (unlikely(rc)) {
> security_task_free(task);
> - return rc;
> + return rc;
> + }
> +
> + if (odisplay) {
odisplay should always be valid because lbs_task is > 0, again if you want
to keep the guard that is fine
> + ndisplay = task->security;
> + if (ndisplay)
> + *ndisplay = *odisplay;
> + }
> +
> + return 0;
> }
>
> void security_task_free(struct task_struct *task)
> @@ -2038,23 +2071,110 @@ int security_getprocattr(struct task_struct *p, const char *lsm, char *name,
> char **value)
> {
> struct security_hook_list *hp;
> + int display = lsm_task_display(current);
> + int slot = 0;
> +
> + if (!strcmp(name, "display")) {
> + /*
> + * lsm_slot will be 0 if there are no displaying modules.
> + */
> + if (lsm_slot == 0)
> + return -EINVAL;
> +
> + /*
> + * Only allow getting the current process' display.
> + * There are too few reasons to get another process'
> + * display and too many LSM policy issues.
> + */
It turns out to be fairly important when trying to help people figure
out what is going on. I'm not going to ask for it to be changed here
but this is something we should revisit in the future.
> + if (current != p)
> + return -EINVAL;
> +
> + display = lsm_task_display(p);
> + if (display != LSMBLOB_INVALID)
> + slot = display;
> + *value = kstrdup(lsm_slotlist[slot]->lsm, GFP_KERNEL);
> + if (*value)
> + return strlen(*value);
> + return -ENOMEM;
> + }
>
> hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
> if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
> continue;
> + if (lsm == NULL && display != LSMBLOB_INVALID &&
> + display != hp->lsmid->slot)
> + continue;
> return hp->hook.getprocattr(p, name, value);
> }
> return LSM_RET_DEFAULT(getprocattr);
> }
>
> +/**
> + * security_setprocattr - Set process attributes via /proc
> + * @lsm: name of module involved, or NULL
> + * @name: name of the attribute
> + * @value: value to set the attribute to
> + * @size: size of the value
> + *
> + * Set the process attribute for the specified security module
> + * to the specified value. Note that this can only be used to set
> + * the process attributes for the current, or "self" process.
> + * The /proc code has already done this check.
> + *
> + * Returns 0 on success, an appropriate code otherwise.
> + */
> int security_setprocattr(const char *lsm, const char *name, void *value,
> size_t size)
> {
> struct security_hook_list *hp;
> + char *termed;
> + char *copy;
> + int *display = current->security;
> + int rc = -EINVAL;
> + int slot = 0;
> +
> + if (!strcmp(name, "display")) {
> + /*
> + * Change the "display" value only if all the security
> + * modules that support setting a procattr allow it.
> + * It is assumed that all such security modules will be
> + * cooperative.
> + */
> + if (size == 0)
> + return -EINVAL;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.setprocattr,
> + list) {
> + rc = hp->hook.setprocattr(name, value, size);
> + if (rc < 0)
> + return rc;
> + }
> +
> + rc = -EINVAL;
> +
> + copy = kmemdup_nul(value, size, GFP_KERNEL);
> + if (copy == NULL)
> + return -ENOMEM;
> +
> + termed = strsep(©, " \n");
> +
> + for (slot = 0; slot < lsm_slot; slot++)
> + if (!strcmp(termed, lsm_slotlist[slot]->lsm)) {
> + *display = lsm_slotlist[slot]->slot;
> + rc = size;
> + break;
> + }
> +
> + kfree(termed);
> + return rc;
> + }
>
> hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
> if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
> continue;
> + if (lsm == NULL && *display != LSMBLOB_INVALID &&
> + *display != hp->lsmid->slot)
> + continue;
> return hp->hook.setprocattr(name, value, size);
> }
> return LSM_RET_DEFAULT(setprocattr);
> @@ -2074,15 +2194,15 @@ EXPORT_SYMBOL(security_ismaclabel);
> int security_secid_to_secctx(struct lsmblob *blob, char **secdata, u32 *seclen)
> {
> struct security_hook_list *hp;
> - int rc;
> + int display = lsm_task_display(current);
>
> hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx, list) {
> if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> continue;
> - rc = hp->hook.secid_to_secctx(blob->secid[hp->lsmid->slot],
> - secdata, seclen);
> - if (rc != LSM_RET_DEFAULT(secid_to_secctx))
> - return rc;
> + if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
> + return hp->hook.secid_to_secctx(
> + blob->secid[hp->lsmid->slot],
> + secdata, seclen);
> }
>
> return LSM_RET_DEFAULT(secid_to_secctx);
> @@ -2093,16 +2213,15 @@ int security_secctx_to_secid(const char *secdata, u32 seclen,
> struct lsmblob *blob)
> {
> struct security_hook_list *hp;
> - int rc;
> + int display = lsm_task_display(current);
>
> lsmblob_init(blob, 0);
> hlist_for_each_entry(hp, &security_hook_heads.secctx_to_secid, list) {
> if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> continue;
> - rc = hp->hook.secctx_to_secid(secdata, seclen,
> - &blob->secid[hp->lsmid->slot]);
> - if (rc != 0)
> - return rc;
> + if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
> + return hp->hook.secctx_to_secid(secdata, seclen,
> + &blob->secid[hp->lsmid->slot]);
> }
> return 0;
> }
> @@ -2110,7 +2229,14 @@ EXPORT_SYMBOL(security_secctx_to_secid);
>
> void security_release_secctx(char *secdata, u32 seclen)
> {
> - call_void_hook(release_secctx, secdata, seclen);
> + struct security_hook_list *hp;
> + int display = lsm_task_display(current);
> +
> + hlist_for_each_entry(hp, &security_hook_heads.release_secctx, list)
> + if (display == LSMBLOB_INVALID || display == hp->lsmid->slot) {
> + hp->hook.release_secctx(secdata, seclen);
> + return;
> + }
> }
> EXPORT_SYMBOL(security_release_secctx);
>
> @@ -2251,8 +2377,15 @@ EXPORT_SYMBOL(security_sock_rcv_skb);
> int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
> int __user *optlen, unsigned len)
> {
> - return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
> - optval, optlen, len);
> + int display = lsm_task_display(current);
> + struct security_hook_list *hp;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_stream,
> + list)
> + if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
> + return hp->hook.socket_getpeersec_stream(sock, optval,
> + optlen, len);
> + return -ENOPROTOOPT;
> }
>
> int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb,
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 1c821bec7472..bedcf737ff26 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6330,6 +6330,17 @@ static int selinux_setprocattr(const char *name, void *value, size_t size)
> /*
> * Basic control over ability to set these attributes at all.
> */
> +
> + /*
> + * For setting display, we only perform a permission check;
> + * the actual update to the display value is handled by the
> + * LSM framework.
> + */
> + if (!strcmp(name, "display"))
> + return avc_has_perm(&selinux_state,
> + mysid, mysid, SECCLASS_PROCESS2,
> + PROCESS2__SETDISPLAY, NULL);
> +
> if (!strcmp(name, "exec"))
> error = avc_has_perm(&selinux_state,
> mysid, mysid, SECCLASS_PROCESS,
> diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
> index 98e1513b608a..905aa184b3cd 100644
> --- a/security/selinux/include/classmap.h
> +++ b/security/selinux/include/classmap.h
> @@ -52,7 +52,7 @@ struct security_class_mapping secclass_map[] = {
> "execmem", "execstack", "execheap", "setkeycreate",
> "setsockcreate", "getrlimit", NULL } },
> { "process2",
> - { "nnp_transition", "nosuid_transition", NULL } },
> + { "nnp_transition", "nosuid_transition", "setdisplay", NULL } },
> { "system",
> { "ipc_info", "syslog_read", "syslog_mod",
> "syslog_console", "module_request", "module_load", NULL } },
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index d4655dec2d70..8b708cca921a 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -3494,6 +3494,13 @@ static int smack_setprocattr(const char *name, void *value, size_t size)
> struct smack_known_list_elem *sklep;
> int rc;
>
> + /*
> + * Allow the /proc/.../attr/current and SO_PEERSEC "display"
> + * to be reset at will.
> + */
> + if (strcmp(name, "display") == 0)
> + return 0;
> +
> if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel))
> return -EPERM;
>
>
^ permalink raw reply
* Re: [PATCH v19 06/23] LSM: Use lsmblob in security_secctx_to_secid
From: John Johansen @ 2020-07-28 11:11 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: linux-audit, keescook, penguin-kernel, paul, sds, netdev
In-Reply-To: <20200724203226.16374-7-casey@schaufler-ca.com>
On 7/24/20 1:32 PM, Casey Schaufler wrote:
> Change security_secctx_to_secid() to fill in a lsmblob instead
> of a u32 secid. Multiple LSMs may be able to interpret the
> string, and this allows for setting whichever secid is
> appropriate. Change security_secmark_relabel_packet() to use a
> lsmblob instead of a u32 secid. In some other cases there is
> scaffolding where interfaces have yet to be converted.
>
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
> Cc: netdev@vger.kernel.org
one comment below, but its a nice to have so
Reviewed-by: John Johansen <john.johansen@canonical.com>
> ---
> include/linux/security.h | 30 +++++++++++++++++++++++----
> include/net/scm.h | 7 +++++--
> kernel/cred.c | 4 +---
> net/ipv4/ip_sockglue.c | 6 ++++--
> net/netfilter/nft_meta.c | 18 +++++++++-------
> net/netfilter/xt_SECMARK.c | 9 ++++++--
> net/netlabel/netlabel_unlabeled.c | 23 +++++++++++++--------
> security/security.c | 34 ++++++++++++++++++++++++++-----
> 8 files changed, 98 insertions(+), 33 deletions(-)
>
> diff --git a/include/linux/security.h b/include/linux/security.h
> index d81e8886d799..98176faaaba5 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -189,6 +189,27 @@ static inline bool lsmblob_equal(struct lsmblob *bloba, struct lsmblob *blobb)
> return !memcmp(bloba, blobb, sizeof(*bloba));
> }
>
> +/**
> + * lsmblob_value - find the first non-zero value in an lsmblob structure.
> + * @blob: Pointer to the data
> + *
> + * This needs to be used with extreme caution, as the cases where
> + * it is appropriate are rare.
> + *
> + * Return the first secid value set in the lsmblob.
> + * There should only be one.
It would be really nice if we could have an LSM debug config, that would
do things like checking there is indeed only one value when this fn
is called.
> + */
> +static inline u32 lsmblob_value(const struct lsmblob *blob)
> +{
> + int i;
> +
> + for (i = 0; i < LSMBLOB_ENTRIES; i++)
> + if (blob->secid[i])
> + return blob->secid[i];
> +
> + return 0;
> +}
> +
> /* These functions are in security/commoncap.c */
> extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
> int cap, unsigned int opts);
> @@ -502,7 +523,8 @@ int security_setprocattr(const char *lsm, const char *name, void *value,
> int security_netlink_send(struct sock *sk, struct sk_buff *skb);
> int security_ismaclabel(const char *name);
> int security_secid_to_secctx(u32 secid, char **secdata, u32 *seclen);
> -int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid);
> +int security_secctx_to_secid(const char *secdata, u32 seclen,
> + struct lsmblob *blob);
> void security_release_secctx(char *secdata, u32 seclen);
> void security_inode_invalidate_secctx(struct inode *inode);
> int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
> @@ -1321,7 +1343,7 @@ static inline int security_secid_to_secctx(u32 secid, char **secdata, u32 *secle
>
> static inline int security_secctx_to_secid(const char *secdata,
> u32 seclen,
> - u32 *secid)
> + struct lsmblob *blob)
> {
> return -EOPNOTSUPP;
> }
> @@ -1411,7 +1433,7 @@ void security_inet_csk_clone(struct sock *newsk,
> const struct request_sock *req);
> void security_inet_conn_established(struct sock *sk,
> struct sk_buff *skb);
> -int security_secmark_relabel_packet(u32 secid);
> +int security_secmark_relabel_packet(struct lsmblob *blob);
> void security_secmark_refcount_inc(void);
> void security_secmark_refcount_dec(void);
> int security_tun_dev_alloc_security(void **security);
> @@ -1584,7 +1606,7 @@ static inline void security_inet_conn_established(struct sock *sk,
> {
> }
>
> -static inline int security_secmark_relabel_packet(u32 secid)
> +static inline int security_secmark_relabel_packet(struct lsmblob *blob)
> {
> return 0;
> }
> diff --git a/include/net/scm.h b/include/net/scm.h
> index e2e71c4bf9d0..c09f2dfeec88 100644
> --- a/include/net/scm.h
> +++ b/include/net/scm.h
> @@ -97,8 +97,11 @@ static inline void scm_passec(struct socket *sock, struct msghdr *msg, struct sc
> int err;
>
> if (test_bit(SOCK_PASSSEC, &sock->flags)) {
> - /* Scaffolding - it has to be element 0 for now */
> - err = security_secid_to_secctx(scm->lsmblob.secid[0],
> + /* There can currently be only one value in the lsmblob,
> + * so getting it from lsmblob_value is appropriate until
> + * security_secid_to_secctx() is converted to taking a
> + * lsmblob directly. */
> + err = security_secid_to_secctx(lsmblob_value(&scm->lsmblob),
> &secdata, &seclen);
>
> if (!err) {
> diff --git a/kernel/cred.c b/kernel/cred.c
> index 22e0e7cbefde..848306c7d823 100644
> --- a/kernel/cred.c
> +++ b/kernel/cred.c
> @@ -757,14 +757,12 @@ EXPORT_SYMBOL(set_security_override);
> int set_security_override_from_ctx(struct cred *new, const char *secctx)
> {
> struct lsmblob blob;
> - u32 secid;
> int ret;
>
> - ret = security_secctx_to_secid(secctx, strlen(secctx), &secid);
> + ret = security_secctx_to_secid(secctx, strlen(secctx), &blob);
> if (ret < 0)
> return ret;
>
> - lsmblob_init(&blob, secid);
> return set_security_override(new, &blob);
> }
> EXPORT_SYMBOL(set_security_override_from_ctx);
> diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
> index 3ea1103b4c29..6bdac5f87a1e 100644
> --- a/net/ipv4/ip_sockglue.c
> +++ b/net/ipv4/ip_sockglue.c
> @@ -139,8 +139,10 @@ static void ip_cmsg_recv_security(struct msghdr *msg, struct sk_buff *skb)
> if (err)
> return;
>
> - /* Scaffolding - it has to be element 0 */
> - err = security_secid_to_secctx(lb.secid[0], &secdata, &seclen);
> + /* There can only be one secid in the lsmblob at this point,
> + * so getting it using lsmblob_value() is sufficient until
> + * security_secid_to_secctx() is changed to use a lsmblob */
> + err = security_secid_to_secctx(lsmblob_value(&lb), &secdata, &seclen);
> if (err)
> return;
>
> diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c
> index 951b6e87ed5d..5875222aeac5 100644
> --- a/net/netfilter/nft_meta.c
> +++ b/net/netfilter/nft_meta.c
> @@ -801,7 +801,7 @@ struct nft_expr_type nft_meta_type __read_mostly = {
>
> #ifdef CONFIG_NETWORK_SECMARK
> struct nft_secmark {
> - u32 secid;
> + struct lsmblob lsmdata;
> char *ctx;
> };
>
> @@ -811,21 +811,21 @@ static const struct nla_policy nft_secmark_policy[NFTA_SECMARK_MAX + 1] = {
>
> static int nft_secmark_compute_secid(struct nft_secmark *priv)
> {
> - u32 tmp_secid = 0;
> + struct lsmblob blob;
> int err;
>
> - err = security_secctx_to_secid(priv->ctx, strlen(priv->ctx), &tmp_secid);
> + err = security_secctx_to_secid(priv->ctx, strlen(priv->ctx), &blob);
> if (err)
> return err;
>
> - if (!tmp_secid)
> + if (!lsmblob_is_set(&blob))
> return -ENOENT;
>
> - err = security_secmark_relabel_packet(tmp_secid);
> + err = security_secmark_relabel_packet(&blob);
> if (err)
> return err;
>
> - priv->secid = tmp_secid;
> + priv->lsmdata = blob;
> return 0;
> }
>
> @@ -835,7 +835,11 @@ static void nft_secmark_obj_eval(struct nft_object *obj, struct nft_regs *regs,
> const struct nft_secmark *priv = nft_obj_data(obj);
> struct sk_buff *skb = pkt->skb;
>
> - skb->secmark = priv->secid;
> + /* It is not possible for more than one secid to be set in
> + * the lsmblob structure because it is set using
> + * security_secctx_to_secid(). Any secid that is set must therefore
> + * be the one that should go in the secmark. */
> + skb->secmark = lsmblob_value(&priv->lsmdata);
> }
>
> static int nft_secmark_obj_init(const struct nft_ctx *ctx,
> diff --git a/net/netfilter/xt_SECMARK.c b/net/netfilter/xt_SECMARK.c
> index 75625d13e976..5a268707eeda 100644
> --- a/net/netfilter/xt_SECMARK.c
> +++ b/net/netfilter/xt_SECMARK.c
> @@ -43,13 +43,14 @@ secmark_tg(struct sk_buff *skb, const struct xt_action_param *par)
>
> static int checkentry_lsm(struct xt_secmark_target_info *info)
> {
> + struct lsmblob blob;
> int err;
>
> info->secctx[SECMARK_SECCTX_MAX - 1] = '\0';
> info->secid = 0;
>
> err = security_secctx_to_secid(info->secctx, strlen(info->secctx),
> - &info->secid);
> + &blob);
> if (err) {
> if (err == -EINVAL)
> pr_info_ratelimited("invalid security context \'%s\'\n",
> @@ -57,13 +58,17 @@ static int checkentry_lsm(struct xt_secmark_target_info *info)
> return err;
> }
>
> + /* xt_secmark_target_info can't be changed to use lsmblobs because
> + * it is exposed as an API. Use lsmblob_value() to get the one
> + * value that got set by security_secctx_to_secid(). */
> + info->secid = lsmblob_value(&blob);
> if (!info->secid) {
> pr_info_ratelimited("unable to map security context \'%s\'\n",
> info->secctx);
> return -ENOENT;
> }
>
> - err = security_secmark_relabel_packet(info->secid);
> + err = security_secmark_relabel_packet(&blob);
> if (err) {
> pr_info_ratelimited("unable to obtain relabeling permission\n");
> return err;
> diff --git a/net/netlabel/netlabel_unlabeled.c b/net/netlabel/netlabel_unlabeled.c
> index 77bb1bb22c3b..8948557eaebb 100644
> --- a/net/netlabel/netlabel_unlabeled.c
> +++ b/net/netlabel/netlabel_unlabeled.c
> @@ -882,7 +882,7 @@ static int netlbl_unlabel_staticadd(struct sk_buff *skb,
> void *addr;
> void *mask;
> u32 addr_len;
> - u32 secid;
> + struct lsmblob blob;
> struct netlbl_audit audit_info;
>
> /* Don't allow users to add both IPv4 and IPv6 addresses for a
> @@ -906,13 +906,18 @@ static int netlbl_unlabel_staticadd(struct sk_buff *skb,
> ret_val = security_secctx_to_secid(
> nla_data(info->attrs[NLBL_UNLABEL_A_SECCTX]),
> nla_len(info->attrs[NLBL_UNLABEL_A_SECCTX]),
> - &secid);
> + &blob);
> if (ret_val != 0)
> return ret_val;
>
> + /* netlbl_unlhsh_add will be changed to pass a struct lsmblob *
> + * instead of a u32 later in this patch set. security_secctx_to_secid()
> + * will only be setting one entry in the lsmblob struct, so it is
> + * safe to use lsmblob_value() to get that one value. */
> +
> return netlbl_unlhsh_add(&init_net,
> - dev_name, addr, mask, addr_len, secid,
> - &audit_info);
> + dev_name, addr, mask, addr_len,
> + lsmblob_value(&blob), &audit_info);
> }
>
> /**
> @@ -933,7 +938,7 @@ static int netlbl_unlabel_staticadddef(struct sk_buff *skb,
> void *addr;
> void *mask;
> u32 addr_len;
> - u32 secid;
> + struct lsmblob blob;
> struct netlbl_audit audit_info;
>
> /* Don't allow users to add both IPv4 and IPv6 addresses for a
> @@ -955,13 +960,15 @@ static int netlbl_unlabel_staticadddef(struct sk_buff *skb,
> ret_val = security_secctx_to_secid(
> nla_data(info->attrs[NLBL_UNLABEL_A_SECCTX]),
> nla_len(info->attrs[NLBL_UNLABEL_A_SECCTX]),
> - &secid);
> + &blob);
> if (ret_val != 0)
> return ret_val;
>
> + /* security_secctx_to_secid() will only put one secid into the lsmblob
> + * so it's safe to use lsmblob_value() to get the secid. */
> return netlbl_unlhsh_add(&init_net,
> - NULL, addr, mask, addr_len, secid,
> - &audit_info);
> + NULL, addr, mask, addr_len,
> + lsmblob_value(&blob), &audit_info);
> }
>
> /**
> diff --git a/security/security.c b/security/security.c
> index c42873876954..5c2ed1db0658 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2065,10 +2065,22 @@ int security_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
> }
> EXPORT_SYMBOL(security_secid_to_secctx);
>
> -int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid)
> +int security_secctx_to_secid(const char *secdata, u32 seclen,
> + struct lsmblob *blob)
> {
> - *secid = 0;
> - return call_int_hook(secctx_to_secid, 0, secdata, seclen, secid);
> + struct security_hook_list *hp;
> + int rc;
> +
> + lsmblob_init(blob, 0);
> + hlist_for_each_entry(hp, &security_hook_heads.secctx_to_secid, list) {
> + if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> + continue;
> + rc = hp->hook.secctx_to_secid(secdata, seclen,
> + &blob->secid[hp->lsmid->slot]);
> + if (rc != 0)
> + return rc;
> + }
> + return 0;
> }
> EXPORT_SYMBOL(security_secctx_to_secid);
>
> @@ -2301,9 +2313,21 @@ void security_inet_conn_established(struct sock *sk,
> }
> EXPORT_SYMBOL(security_inet_conn_established);
>
> -int security_secmark_relabel_packet(u32 secid)
> +int security_secmark_relabel_packet(struct lsmblob *blob)
> {
> - return call_int_hook(secmark_relabel_packet, 0, secid);
> + struct security_hook_list *hp;
> + int rc = 0;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.secmark_relabel_packet,
> + list) {
> + if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> + continue;
> + rc = hp->hook.secmark_relabel_packet(
> + blob->secid[hp->lsmid->slot]);
> + if (rc != 0)
> + break;
> + }
> + return rc;
> }
> EXPORT_SYMBOL(security_secmark_relabel_packet);
>
>
^ permalink raw reply
* Re: [PATCH v19 05/23] net: Prepare UDS for security module stacking
From: John Johansen @ 2020-07-28 10:57 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: linux-audit, keescook, penguin-kernel, paul, sds
In-Reply-To: <20200724203226.16374-6-casey@schaufler-ca.com>
On 7/24/20 1:32 PM, Casey Schaufler wrote:
> Change the data used in UDS SO_PEERSEC processing from a
> secid to a more general struct lsmblob. Update the
> security_socket_getpeersec_dgram() interface to use the
> lsmblob. There is a small amount of scaffolding code
> that will come out when the security_secid_to_secctx()
> code is brought in line with the lsmblob.
>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: John Johansen <john.johansen@canonical.com>
> ---
> include/linux/security.h | 7 +++++--
> include/net/af_unix.h | 2 +-
> include/net/scm.h | 8 +++++---
> net/ipv4/ip_sockglue.c | 8 +++++---
> net/unix/af_unix.c | 6 +++---
> security/security.c | 18 +++++++++++++++---
> 6 files changed, 34 insertions(+), 15 deletions(-)
>
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 6d403a522918..d81e8886d799 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -1397,7 +1397,8 @@ int security_socket_shutdown(struct socket *sock, int how);
> int security_sock_rcv_skb(struct sock *sk, struct sk_buff *skb);
> int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
> int __user *optlen, unsigned len);
> -int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid);
> +int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb,
> + struct lsmblob *blob);
> int security_sk_alloc(struct sock *sk, int family, gfp_t priority);
> void security_sk_free(struct sock *sk);
> void security_sk_clone(const struct sock *sk, struct sock *newsk);
> @@ -1535,7 +1536,9 @@ static inline int security_socket_getpeersec_stream(struct socket *sock, char __
> return -ENOPROTOOPT;
> }
>
> -static inline int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)
> +static inline int security_socket_getpeersec_dgram(struct socket *sock,
> + struct sk_buff *skb,
> + struct lsmblob *blob)
> {
> return -ENOPROTOOPT;
> }
> diff --git a/include/net/af_unix.h b/include/net/af_unix.h
> index f42fdddecd41..a86da0cb5ec1 100644
> --- a/include/net/af_unix.h
> +++ b/include/net/af_unix.h
> @@ -36,7 +36,7 @@ struct unix_skb_parms {
> kgid_t gid;
> struct scm_fp_list *fp; /* Passed files */
> #ifdef CONFIG_SECURITY_NETWORK
> - u32 secid; /* Security ID */
> + struct lsmblob lsmblob; /* Security LSM data */
> #endif
> u32 consumed;
> } __randomize_layout;
> diff --git a/include/net/scm.h b/include/net/scm.h
> index 1ce365f4c256..e2e71c4bf9d0 100644
> --- a/include/net/scm.h
> +++ b/include/net/scm.h
> @@ -33,7 +33,7 @@ struct scm_cookie {
> struct scm_fp_list *fp; /* Passed files */
> struct scm_creds creds; /* Skb credentials */
> #ifdef CONFIG_SECURITY_NETWORK
> - u32 secid; /* Passed security ID */
> + struct lsmblob lsmblob; /* Passed LSM data */
> #endif
> };
>
> @@ -46,7 +46,7 @@ struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl);
> #ifdef CONFIG_SECURITY_NETWORK
> static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_cookie *scm)
> {
> - security_socket_getpeersec_dgram(sock, NULL, &scm->secid);
> + security_socket_getpeersec_dgram(sock, NULL, &scm->lsmblob);
> }
> #else
> static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_cookie *scm)
> @@ -97,7 +97,9 @@ static inline void scm_passec(struct socket *sock, struct msghdr *msg, struct sc
> int err;
>
> if (test_bit(SOCK_PASSSEC, &sock->flags)) {
> - err = security_secid_to_secctx(scm->secid, &secdata, &seclen);
> + /* Scaffolding - it has to be element 0 for now */
> + err = security_secid_to_secctx(scm->lsmblob.secid[0],
> + &secdata, &seclen);
>
> if (!err) {
> put_cmsg(msg, SOL_SOCKET, SCM_SECURITY, seclen, secdata);
> diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
> index 84ec3703c909..3ea1103b4c29 100644
> --- a/net/ipv4/ip_sockglue.c
> +++ b/net/ipv4/ip_sockglue.c
> @@ -130,15 +130,17 @@ static void ip_cmsg_recv_checksum(struct msghdr *msg, struct sk_buff *skb,
>
> static void ip_cmsg_recv_security(struct msghdr *msg, struct sk_buff *skb)
> {
> + struct lsmblob lb;
> char *secdata;
> - u32 seclen, secid;
> + u32 seclen;
> int err;
>
> - err = security_socket_getpeersec_dgram(NULL, skb, &secid);
> + err = security_socket_getpeersec_dgram(NULL, skb, &lb);
> if (err)
> return;
>
> - err = security_secid_to_secctx(secid, &secdata, &seclen);
> + /* Scaffolding - it has to be element 0 */
> + err = security_secid_to_secctx(lb.secid[0], &secdata, &seclen);
> if (err)
> return;
>
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 3385a7a0b231..a676dc264464 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -138,17 +138,17 @@ static struct hlist_head *unix_sockets_unbound(void *addr)
> #ifdef CONFIG_SECURITY_NETWORK
> static void unix_get_secdata(struct scm_cookie *scm, struct sk_buff *skb)
> {
> - UNIXCB(skb).secid = scm->secid;
> + UNIXCB(skb).lsmblob = scm->lsmblob;
> }
>
> static inline void unix_set_secdata(struct scm_cookie *scm, struct sk_buff *skb)
> {
> - scm->secid = UNIXCB(skb).secid;
> + scm->lsmblob = UNIXCB(skb).lsmblob;
> }
>
> static inline bool unix_secdata_eq(struct scm_cookie *scm, struct sk_buff *skb)
> {
> - return (scm->secid == UNIXCB(skb).secid);
> + return lsmblob_equal(&scm->lsmblob, &(UNIXCB(skb).lsmblob));
> }
> #else
> static inline void unix_get_secdata(struct scm_cookie *scm, struct sk_buff *skb)
> diff --git a/security/security.c b/security/security.c
> index d6d882b1f7d5..c42873876954 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -2219,10 +2219,22 @@ int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
> optval, optlen, len);
> }
>
> -int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)
> +int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb,
> + struct lsmblob *blob)
> {
> - return call_int_hook(socket_getpeersec_dgram, -ENOPROTOOPT, sock,
> - skb, secid);
> + struct security_hook_list *hp;
> + int rc = -ENOPROTOOPT;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_dgram,
> + list) {
> + if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> + continue;
> + rc = hp->hook.socket_getpeersec_dgram(sock, skb,
> + &blob->secid[hp->lsmid->slot]);
> + if (rc != 0)
> + break;
> + }
> + return rc;
> }
> EXPORT_SYMBOL(security_socket_getpeersec_dgram);
>
>
^ permalink raw reply
* Re: [PATCH v19 04/23] LSM: Use lsmblob in security_kernel_act_as
From: John Johansen @ 2020-07-28 10:34 UTC (permalink / raw)
To: Casey Schaufler, casey.schaufler, jmorris, linux-security-module,
selinux
Cc: linux-audit, keescook, penguin-kernel, paul, sds
In-Reply-To: <20200724203226.16374-5-casey@schaufler-ca.com>
On 7/24/20 1:32 PM, Casey Schaufler wrote:
> Change the security_kernel_act_as interface to use a lsmblob
> structure in place of the single u32 secid in support of
> module stacking. Change its only caller, set_security_override,
> to do the same. Change that one's only caller,
> set_security_override_from_ctx, to call it with the new
> parameter type.
>
> The security module hook is unchanged, still taking a secid.
> The infrastructure passes the correct entry from the lsmblob.
> lsmblob_init() is used to fill the lsmblob structure, however
> this will be removed later in the series when security_secctx_to_secid()
> is undated to provide a lsmblob instead of a secid.
>
fix ^ "undated" to updated
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Reviewed-by: John Johansen <john.johansen@canonical.com>
> Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
> Signed-off-by: Casey Schaufler <casey@schaufler-ca.com>
> ---
> include/linux/cred.h | 3 ++-
> include/linux/security.h | 5 +++--
> kernel/cred.c | 10 ++++++----
> security/security.c | 14 ++++++++++++--
> 4 files changed, 23 insertions(+), 9 deletions(-)
>
> diff --git a/include/linux/cred.h b/include/linux/cred.h
> index 18639c069263..03ae0182cba6 100644
> --- a/include/linux/cred.h
> +++ b/include/linux/cred.h
> @@ -18,6 +18,7 @@
>
> struct cred;
> struct inode;
> +struct lsmblob;
>
> /*
> * COW Supplementary groups list
> @@ -165,7 +166,7 @@ extern const struct cred *override_creds(const struct cred *);
> extern void revert_creds(const struct cred *);
> extern struct cred *prepare_kernel_cred(struct task_struct *);
> extern int change_create_files_as(struct cred *, struct inode *);
> -extern int set_security_override(struct cred *, u32);
> +extern int set_security_override(struct cred *, struct lsmblob *);
> extern int set_security_override_from_ctx(struct cred *, const char *);
> extern int set_create_files_as(struct cred *, struct inode *);
> extern int cred_fscmp(const struct cred *, const struct cred *);
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 294410533b51..6d403a522918 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -441,7 +441,7 @@ void security_cred_free(struct cred *cred);
> int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp);
> void security_transfer_creds(struct cred *new, const struct cred *old);
> void security_cred_getsecid(const struct cred *c, u32 *secid);
> -int security_kernel_act_as(struct cred *new, u32 secid);
> +int security_kernel_act_as(struct cred *new, struct lsmblob *blob);
> int security_kernel_create_files_as(struct cred *new, struct inode *inode);
> int security_kernel_module_request(char *kmod_name);
> int security_kernel_load_data(enum kernel_load_data_id id);
> @@ -1055,7 +1055,8 @@ static inline void security_transfer_creds(struct cred *new,
> {
> }
>
> -static inline int security_kernel_act_as(struct cred *cred, u32 secid)
> +static inline int security_kernel_act_as(struct cred *cred,
> + struct lsmblob *blob)
> {
> return 0;
> }
> diff --git a/kernel/cred.c b/kernel/cred.c
> index 421b1149c651..22e0e7cbefde 100644
> --- a/kernel/cred.c
> +++ b/kernel/cred.c
> @@ -733,14 +733,14 @@ EXPORT_SYMBOL(prepare_kernel_cred);
> /**
> * set_security_override - Set the security ID in a set of credentials
> * @new: The credentials to alter
> - * @secid: The LSM security ID to set
> + * @blob: The LSM security information to set
> *
> * Set the LSM security ID in a set of credentials so that the subjective
> * security is overridden when an alternative set of credentials is used.
> */
> -int set_security_override(struct cred *new, u32 secid)
> +int set_security_override(struct cred *new, struct lsmblob *blob)
> {
> - return security_kernel_act_as(new, secid);
> + return security_kernel_act_as(new, blob);
> }
> EXPORT_SYMBOL(set_security_override);
>
> @@ -756,6 +756,7 @@ EXPORT_SYMBOL(set_security_override);
> */
> int set_security_override_from_ctx(struct cred *new, const char *secctx)
> {
> + struct lsmblob blob;
> u32 secid;
> int ret;
>
> @@ -763,7 +764,8 @@ int set_security_override_from_ctx(struct cred *new, const char *secctx)
> if (ret < 0)
> return ret;
>
> - return set_security_override(new, secid);
> + lsmblob_init(&blob, secid);
> + return set_security_override(new, &blob);
> }
> EXPORT_SYMBOL(set_security_override_from_ctx);
>
> diff --git a/security/security.c b/security/security.c
> index f9a249a93215..d6d882b1f7d5 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -1692,9 +1692,19 @@ void security_cred_getsecid(const struct cred *c, u32 *secid)
> }
> EXPORT_SYMBOL(security_cred_getsecid);
>
> -int security_kernel_act_as(struct cred *new, u32 secid)
> +int security_kernel_act_as(struct cred *new, struct lsmblob *blob)
> {
> - return call_int_hook(kernel_act_as, 0, new, secid);
> + struct security_hook_list *hp;
> + int rc;
> +
> + hlist_for_each_entry(hp, &security_hook_heads.kernel_act_as, list) {
> + if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
> + continue;
> + rc = hp->hook.kernel_act_as(new, blob->secid[hp->lsmid->slot]);
> + if (rc != 0)
> + return rc;
> + }
> + return 0;
> }
>
> int security_kernel_create_files_as(struct cred *new, struct inode *inode)
>
^ permalink raw reply
* Re: [PATCH v3 15/19] IMA: Add support for file reads without contents
From: Kees Cook @ 2020-07-28 20:12 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Mimi Zohar, Scott Branden, Luis Chamberlain, Jessica Yu,
SeongJae Park, KP Singh, linux-efi, linux-security-module,
linux-integrity, selinux, linux-kselftest, linux-kernel
In-Reply-To: <20200728195640.GA342741@kroah.com>
On Tue, Jul 28, 2020 at 09:56:40PM +0200, Greg Kroah-Hartman wrote:
> On Tue, Jul 28, 2020 at 12:44:50PM -0700, Kees Cook wrote:
> > On Mon, Jul 27, 2020 at 09:23:34AM -0400, Mimi Zohar wrote:
> > > On Fri, 2020-07-24 at 14:36 -0700, Kees Cook wrote:
> > > > From: Scott Branden <scott.branden@broadcom.com>
> > > >
> > > > When the kernel_read_file LSM hook is called with contents=false, IMA
> > > > can appraise the file directly, without requiring a filled buffer. When
> > > > such a buffer is available, though, IMA can continue to use it instead
> > > > of forcing a double read here.
> > > >
> > > > Signed-off-by: Scott Branden <scott.branden@broadcom.com>
> > > > Link: https://lore.kernel.org/lkml/20200706232309.12010-10-scott.branden@broadcom.com/
> > > > Signed-off-by: Kees Cook <keescook@chromium.org>
> > >
> > > After adjusting the comment below.
> > >
> > > Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
> >
> > Sure!
> >
> > Greg, shall I send a v4 with added Reviews and the comment change or is
> > that minor enough that you're able to do it?
>
> v4 is needed, as this series is a mess of reviewes and you will have to
> redo at least one patch and drop some others, right?
Well, I wasn't sure what your desire was, given the weirdness of taking
some and reverting others. I will do a v4 based on driver-core-next.
Thanks!
--
Kees Cook
^ permalink raw reply
* Re: [PATCH v3 15/19] IMA: Add support for file reads without contents
From: Greg Kroah-Hartman @ 2020-07-28 19:56 UTC (permalink / raw)
To: Kees Cook
Cc: Mimi Zohar, Scott Branden, Luis Chamberlain, Jessica Yu,
SeongJae Park, KP Singh, linux-efi, linux-security-module,
linux-integrity, selinux, linux-kselftest, linux-kernel
In-Reply-To: <202007281244.2F2681AE9@keescook>
On Tue, Jul 28, 2020 at 12:44:50PM -0700, Kees Cook wrote:
> On Mon, Jul 27, 2020 at 09:23:34AM -0400, Mimi Zohar wrote:
> > On Fri, 2020-07-24 at 14:36 -0700, Kees Cook wrote:
> > > From: Scott Branden <scott.branden@broadcom.com>
> > >
> > > When the kernel_read_file LSM hook is called with contents=false, IMA
> > > can appraise the file directly, without requiring a filled buffer. When
> > > such a buffer is available, though, IMA can continue to use it instead
> > > of forcing a double read here.
> > >
> > > Signed-off-by: Scott Branden <scott.branden@broadcom.com>
> > > Link: https://lore.kernel.org/lkml/20200706232309.12010-10-scott.branden@broadcom.com/
> > > Signed-off-by: Kees Cook <keescook@chromium.org>
> >
> > After adjusting the comment below.
> >
> > Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
>
> Sure!
>
> Greg, shall I send a v4 with added Reviews and the comment change or is
> that minor enough that you're able to do it?
v4 is needed, as this series is a mess of reviewes and you will have to
redo at least one patch and drop some others, right?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v3 00/19] Introduce partial kernel_read_file() support
From: Scott Branden @ 2020-07-28 19:56 UTC (permalink / raw)
To: Mimi Zohar, Kees Cook, Greg Kroah-Hartman
Cc: Luis Chamberlain, Jessica Yu, SeongJae Park, KP Singh, linux-efi,
linux-security-module, linux-integrity, selinux, linux-kselftest,
linux-kernel
In-Reply-To: <fa96a33641070b1580f21de86fedd5f8da5eff21.camel@linux.ibm.com>
Hi Mimi,
On 2020-07-28 11:48 a.m., Mimi Zohar wrote:
> On Mon, 2020-07-27 at 12:18 -0700, Scott Branden wrote:
>> Hi Mimi/Kees,
>>
>> On 2020-07-27 4:16 a.m., Mimi Zohar wrote:
>>> On Fri, 2020-07-24 at 14:36 -0700, Kees Cook wrote:
>>>> v3:
>>>> - add reviews/acks
>>>> - add "IMA: Add support for file reads without contents" patch
>>>> - trim CC list, in case that's why vger ignored v2
>>>> v2: [missing from lkml archives! (CC list too long?) repeating changes
>> here]
>>>> - fix issues in firmware test suite
>>>> - add firmware partial read patches
>>>> - various bug fixes/cleanups
>>>> v1:
>> https://lore.kernel.org/lkml/20200717174309.1164575-1-keescook@chromium.org/
>>>> Hi,
>>>>
>>>> Here's my tree for adding partial read support in kernel_read_file(),
>>>> which fixes a number of issues along the way. It's got Scott's firmware
>>>> and IMA patches ported and everything tests cleanly for me (even with
>>>> CONFIG_IMA_APPRAISE=y).
>>> Thanks, Kees. Other than my comments on the new
>>> security_kernel_post_load_data() hook, the patch set is really nice.
>>>
>>> In addition to compiling with CONFIG_IMA_APPRAISE enabled, have you
>>> booted the kernel with the ima_policy=tcb? The tcb policy will add
>>> measurements to the IMA measurement list and extend the TPM with the
>>> file or buffer data digest. Are you seeing the firmware measurements,
>>> in particular the partial read measurement?
>> I booted the kernel with ima_policy=tcb.
>>
>> Unfortunately after enabling the following, fw_run_tests.sh does not run.
>>
>> mkdir /sys/kernel/security
>> mount -t securityfs securityfs /sys/kernel/security
>> echo "measure func=FIRMWARE_CHECK" > /sys/kernel/security/ima/policy
>> echo "appraise func=FIRMWARE_CHECK appraise_type=imasig" >
>> /sys/kernel/security/ima/policy
>> ./fw_run_tests.sh
>>
>> [ 1296.258052] test_firmware: loading 'test-firmware.bin'
>> [ 1296.263903] misc test_firmware: loading /lib/firmware/test-firmware.bin
>> failed with error -13
>> [ 1296.263905] audit: type=1800 audit(1595905754.266:9): pid=5696 uid=0
>> auid=4294967295 ses=4294967295 subj=kernel op=appraise_data cause=IMA-
>> signature-required comm="fw_namespace" name="/lib/firmware/test-firmware.bin"
>> dev="tmpfs" ino=4592 res=0
>> [ 1296.297085] misc test_firmware: Direct firmware load for test-firmware.bin
>> failed with error -13
>> [ 1296.305947] test_firmware: load of 'test-firmware.bin' failed: -13
> The "appraise" rule verifies the IMA signature. Unless you signed the firmware
> (evmctl) and load the public key on the IMA keyring, that's to be expected. I
> assume you are seeing firmware measurements in the IMA measuremenet log.
Yes, I see the firmware measurements in the IMA measurement log.
I have not signed the firmware nor loaded a public key on the IMA keyring.
Therefore everything is working as expected.
>
> Mimi
>
Thanks,
Scott
^ permalink raw reply
* Re: [PATCH v3 15/19] IMA: Add support for file reads without contents
From: Kees Cook @ 2020-07-28 19:44 UTC (permalink / raw)
To: Mimi Zohar
Cc: Greg Kroah-Hartman, Scott Branden, Luis Chamberlain, Jessica Yu,
SeongJae Park, KP Singh, linux-efi, linux-security-module,
linux-integrity, selinux, linux-kselftest, linux-kernel
In-Reply-To: <1595856214.4841.86.camel@kernel.org>
On Mon, Jul 27, 2020 at 09:23:34AM -0400, Mimi Zohar wrote:
> On Fri, 2020-07-24 at 14:36 -0700, Kees Cook wrote:
> > From: Scott Branden <scott.branden@broadcom.com>
> >
> > When the kernel_read_file LSM hook is called with contents=false, IMA
> > can appraise the file directly, without requiring a filled buffer. When
> > such a buffer is available, though, IMA can continue to use it instead
> > of forcing a double read here.
> >
> > Signed-off-by: Scott Branden <scott.branden@broadcom.com>
> > Link: https://lore.kernel.org/lkml/20200706232309.12010-10-scott.branden@broadcom.com/
> > Signed-off-by: Kees Cook <keescook@chromium.org>
>
> After adjusting the comment below.
>
> Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
Sure!
Greg, shall I send a v4 with added Reviews and the comment change or is
that minor enough that you're able to do it?
Thanks for the reviews Mimi!
-Kees
>
> > ---
> > security/integrity/ima/ima_main.c | 22 ++++++++++++++++------
> > 1 file changed, 16 insertions(+), 6 deletions(-)
> >
> > diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
> > index dc4f90660aa6..459e50526a12 100644
> > --- a/security/integrity/ima/ima_main.c
> > +++ b/security/integrity/ima/ima_main.c
> > @@ -613,11 +613,8 @@ void ima_post_path_mknod(struct dentry *dentry)
> > int ima_read_file(struct file *file, enum kernel_read_file_id read_id,
> > bool contents)
> > {
> > - /* Reject all partial reads during appraisal. */
> > - if (!contents) {
> > - if (ima_appraise & IMA_APPRAISE_ENFORCE)
> > - return -EACCES;
> > - }
> > + enum ima_hooks func;
> > + u32 secid;
> >
> > /*
> > * Do devices using pre-allocated memory run the risk of the
> > @@ -626,7 +623,20 @@ int ima_read_file(struct file *file, enum kernel_read_file_id read_id,
> > * buffers? It may be desirable to include the buffer address
> > * in this API and walk all the dma_map_single() mappings to check.
> > */
> > - return 0;
> > +
> > + /*
> > + * There will be a call made to ima_post_read_file() with
> > + * a filled buffer, so we don't need to perform an extra
> > + * read early here.
> > + */
> > + if (contents)
> > + return 0;
> > +
> > + /* Read entire file for all partial reads during appraisal. */
>
> In addition to verifying the file signature, the file might be
> included in the IMA measurement list or the file hash may be used to
> augment the audit record. Please remove "during appraisal" from the
> comment.
>
> > + func = read_idmap[read_id] ?: FILE_CHECK;
> > + security_task_getsecid(current, &secid);
> > + return process_measurement(file, current_cred(), secid, NULL,
> > + 0, MAY_READ, func);
> > }
> >
> > const int read_idmap[READING_MAX_ID] = {
>
--
Kees Cook
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox