On Tue, Apr 7, 2020 at 12:14 AM<deven.desai@xxxxxxxxxxxxxxxxxxx> wrote:Sure, no problem. I'll add a bit more detail to the Kconfig in v3, something
Add the core logic of the IPE LSM, the evaluation loop (engine),Here's a first review pass for this patch without really understanding
the audit system, and the skeleton of the policy structure.
your data structures yet:
[...]
diff --git a/security/ipe/Kconfig b/security/ipe/KconfigThis text is very generic and doesn't really make it clear how IPE is
new file mode 100644
index 000000000000..0c67cd049d0c
--- /dev/null
+++ b/security/ipe/Kconfig
@@ -0,0 +1,41 @@
+# 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 requirements on various
+ aspects of user-mode applications. These requirements are
+ controlled by a policy.
different from other LSMs; could you perhaps add some more text here
on the parts of IPE that distinguish it from other LSMs?
In the cover letter, you have this stuff at the top:
"""
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.
"""
Perhaps you could add a summary of that here?
[...]
diff --git a/security/ipe/ipe-audit.c b/security/ipe/ipe-audit.c[...]
+void ipe_audit_mode(void)Why is this GFP_ATOMIC? ipe_audit_mode() is used from
+{
+ struct audit_buffer *ab;
+
+ ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
+ AUDIT_INTEGRITY_MODE);
ipe_switch_mode(), which is allowed to sleep, right?
Fair point. I wanted to collapse as much of the *common* user-facing strings+ if (!ab)[...]
+ return;
+
+ audit_log_format(ab, "IPE mode=%s", (enforce) ? IPE_MODE_ENFORCE :
+ IPE_MODE_PERMISSIVE);
+
+ audit_log_end(ab);
+}
+/**It seems a bit silly to have an extra method just for this?
+ * ipe_audit_ignore_line: Emit a warning that the line was not understood by
+ * IPE's parser and the line will be ignored and not
+ * parsed.
+ * @line_num: line number that is being ignored.
+ */
+void ipe_audit_ignore_line(size_t i)
+{
+ pr_warn("failed to parse line number %zu, ignoring", i);
+}
Yep. I'll fix it in v3. Same with ipe_audit_policy_activation, below. The only one+/**Again, this runs in sleepable context and GFP_ATOMIC is unnecessary, right?
+ * 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_ATOMIC | __GFP_NOWARN,
+ AUDIT_INTEGRITY_POLICY_ACTIVATE);
I'll remove it.+ 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-engine.c b/security/ipe/ipe-engine.c[...]
+/**[...]
+ * 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.
+ */[...]
+static char *get_audit_pathname(const struct file *file)
+{
+ sb = file->f_path.dentry->d_sb;Just as an FYI, no change required: d_absolute_path() will also
+
+ pathbuf = __getname();
+ if (!pathbuf) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ pos = d_absolute_path(&file->f_path, pathbuf, PATH_MAX);
succeed for files that are not contained within the filesystem root of
the current process; in that case, you'll get stuff like paths rooted
in a different mount namespace.
+ if (IS_ERR(pos)) {This check seems superfluous.
+ rc = PTR_ERR(pos);
+ goto err;
+ }
+
+ temp_path = __getname();
+ if (!temp_path) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ strlcpy(temp_path, pos, PATH_MAX);
+
+ if (pathbuf)
Thanks!+ __putname(pathbuf);[...]
+
+ return temp_path;
+err:
+ if (pathbuf)
+ __putname(pathbuf);
+ if (temp_path)
+ __putname(temp_path);
+
+ return ERR_PTR(rc);
+}
+/**s/maybe/may be/
+ * prealloc_cache: preallocate the cache tree for all ipe properties, so
+ * that this data maybe used later in the read side critical
Thanks. I'm still new to rcu - any comments or corrections regarding it+ * section.Please use rcu_access_pointer() here.
+ * @ctx: Ipe engine context structure passed to the property prealloc function.
+ * @cache: Root of the cache tree to insert nodes under.
+ *
+ * Return:
+ * 0 - OK
+ * -ENOMEM - Out of memory
+ * Other - See individual property preallocator functions.
+ */
+static int prealloc_cache(struct ipe_engine_ctx *ctx,
+ struct rb_root *cache)
+{
+ int rc = 0;
+ struct rb_node *node;
+ struct ipe_prop_reg *reg;
+ struct ipe_prop_cache *storage;
+
+ for (node = rb_first(&ipe_registry_root); node; node = rb_next(node)) {
+ reg = container_of(node, struct ipe_prop_reg, node);
+
+ storage = insert_or_find_cache(cache, reg->prop);
+ if (IS_ERR(storage))
+ return PTR_ERR(storage);
+
+ if (reg->prop->prealloc) {
+ rc = reg->prop->prealloc(ctx, &storage->storage);
+ if (rc != 0)
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * evaluate: Process an @ctx against IPE's current active policy.
+ * @ctx: the engine ctx to perform an evaluation on.
+ * @cache: the red-black tree root that is used for cache storage.
+ *
+ * This uses a preallocated @cache as storage for the properties to avoid
+ * re-evaulation.
+ *
+ * Return:
+ * -EACCES - A match occurred against a "action=DENY" rule
+ * -ENOMEM - Out of memory
+ */
+static int evaluate(struct ipe_engine_ctx *ctx, struct rb_root *cache)
+{
+ int rc = 0;
+ bool match = false;
+ enum ipe_action action;
+ struct ipe_prop_cache *c;
+ 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 (!ipe_active_policy)
+ return rc;What's going on with the `cache` pointer here? We give
+
+ 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) {
+ void *cache = NULL;
+
+ if (prop->prop->prealloc) {
+ c = insert_or_find_cache(cache, prop->prop);
insert_or_find_cache() a NULL cache, and then in
insert_or_find_cache() `new` will be a near-NULL pointer, and it'll
crash immediately at `while (*new)`? Am I missing something?
Also, I think the intent here is that the preceding call to
prealloc_cache() should have allocated memory for us. If so, can you
please add a short comment here, something like "/* won't sleep
because of preceding prealloc_cache() */"?
Thanks, I'll correct it.+ if (IS_ERR(c))[...]
+ return PTR_ERR(c);
+
+ cache = c->storage;
+ }
+
+ match = match && prop->prop->eval(ctx, prop->value,
+ &cache);
+ }
+
+ 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, cache, match_type, action, rule);
+
+ if (action == ipe_action_deny)
+ rc = -EACCES;
+
+ if (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;
+ struct rb_root cache = RB_ROOT;
+
+ ctx = build_ctx(file, op, hook);
+ if (IS_ERR(ctx))
+ goto cleanup;
+
+ rc = prealloc_cache(ctx, &cache);
+ if (rc != 0)
+ goto cleanup;
+
+ rc = evaluate(ctx, &cache);
+
+cleanup:
+ free_ctx(ctx);
+ destroy_cache(&cache);
+ return rc;
+}
diff --git a/security/ipe/ipe-hooks.c b/security/ipe/ipe-hooks.c[..]
+#define HAS_EXEC(_p, _rp) (((_rp) & PROT_EXEC) || ((_p) & PROT_EXEC))This should be unnecessary; reqprot are the protections requested by
userspace, prot are the possibly expanded protections the kernel is
applying. I think you just want to use prot and ignore reqprot.
[...]This variable is set by an auto-generated file, invoked by the tool created
diff --git a/security/ipe/ipe-policy.h b/security/ipe/ipe-policy.h[...]
+extern const char *const ipe_boot_policy;I don't see anything in the entire patch series that actually sets
this variable. Am I missing something?
I'm not sure why locking is necessary on this structure? This structure is+extern const struct ipe_policy *ipe_active_policy;[...]
diff --git a/security/ipe/ipe-property.c b/security/ipe/ipe-property.c[...]
+/* global root containing all registered properties */[...]
+struct rb_root ipe_registry_root = RB_ROOT;
+static struct ipe_prop_reg *reg_lookup(const char *key)Where is the locking for ipe_registry_root? I've looked through the
+{
+ 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;
+}
callers and can't find it. Also, please add a lockdep assertion
(`lockdep_assert_held(...)`) here if possible to ensure that when the
kernel is buildt with appropriate debugging options turned on
(CONFIG_LOCKDEP), it will warn about calling this method with
inappropriate locking.
[...]Thanks, I'll strip the header comments.
+/**Normal Linux kernel style is to have comments on the definitions of
+ * 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);
methods (in the .c files), not in the headers. It looks like you
duplicated the same comment between the header and the .c file -
please don't do that. Same thing in a bunch of other places.
I'll remove it.+#endif /* IPE_PROPERTY_H */[...]
diff --git a/security/ipe/ipe-sysfs.c b/security/ipe/ipe-sysfs.c
+#else /* !CONFIG_SYSCTL */"inline" doesn't make sense to me if the caller is in a different
+
+/**
+ * ipe_sysctl_init: Initialize IPE's sysfs entries.
+ *
+ * Return:
+ * 0 - OK
+ * -ENOMEM - Sysctl registration failed
+ */
+inline int __init ipe_sysctl_init(void)
compilation unit
[...]I'll move it forward.
diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c[...]
+/**this belongs in patch 4 ("ipe: add property for trust of boot volume")
+ * 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)
+{
+ return 0;
+}
[...]I'll fix it.
+static int __init ipe_init(void)useless initialization
+{
+ int rc = 0;
Easily fixed by adding a \n to pr_fmt. Will be fixed in next iteration.+ rc = ipe_sysctl_init();pr_err() needs to have an explicit \n at the end of the message unless
+ if (rc != 0)
+ pr_err("failed to configure sysctl: %d", -rc);
you're planning to continue printing more text on the same line via
pr_cont(). Same issue in many other places.
This is my inexperience with rcu. I recognize statically allocated variables+Why? Statically allocated variables are zero-initialized by default in C.
+ pr_info("mode=%s", (enforce == 1) ? IPE_MODE_ENFORCE :
+ IPE_MODE_PERMISSIVE);
+
+ RCU_INIT_POINTER(ipe_active_policy, NULL);
Ah, this is great! It's not a problem with the sysctls being set too late - the+ security_add_hooks(ipe_hooks, ARRAY_SIZE(ipe_hooks), "IPE");[...]
+
+ return rc;
+}
+/**[...]
+ * enforce: Kernel command line parameter to set the permissive mode for IPE
+ * at system startup. By default, this will always be in enforce mode.
+ *
+ * This is also controlled by the sysctl, "ipe.enforce".
+ */
+module_param(enforce, int, 0644);
+MODULE_PARM_DESC(enforce, "enforce/permissive mode switch");
+/**There is a pending patch series that will allow setting arbitrary
+ * success_audit: Kernel command line parameter to enable success auditing
+ * (emit an audit event when a file is allowed) at system
+ * startup. By default, this will be off.
+ *
+ * This is also controlled by the sysctl, "ipe.success_audit".
+ */
+int success_audit;
+module_param(success_audit, int, 0644);
+MODULE_PARM_DESC(success_audit, "audit message on allow");
sysctls from the kernel command line
(https://lore.kernel.org/lkml/20200330115535.3215-1-vbabka@xxxxxxx/);
if that also works for your usecase, you should probably avoid
explicitly adding module parameters here, unless that is necessary
because the pending series sets the sysctls too late.
I'll add the prefixes. Thanks.diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h[...]
+extern int enforce;You probably shouldn't be defining global symbols with such broad
+extern int success_audit;
names to avoid colliding with global symbols defined elsewhere in the
kernel. Consider adding "ipe_" prefixes to the variable names, or
something like that.