summaryrefslogtreecommitdiffstats
path: root/security/safesetid/securityfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/safesetid/securityfs.c')
-rw-r--r--security/safesetid/securityfs.c193
1 files changed, 193 insertions, 0 deletions
diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c
new file mode 100644
index 000000000000..2c6c829be044
--- /dev/null
+++ b/security/safesetid/securityfs.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SafeSetID Linux Security Module
+ *
+ * Author: Micah Morton <mortonm@chromium.org>
+ *
+ * Copyright (C) 2018 The Chromium OS Authors.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/security.h>
+#include <linux/cred.h>
+
+#include "lsm.h"
+
+static struct dentry *safesetid_policy_dir;
+
+struct safesetid_file_entry {
+ const char *name;
+ enum safesetid_whitelist_file_write_type type;
+ struct dentry *dentry;
+};
+
+static struct safesetid_file_entry safesetid_files[] = {
+ {.name = "add_whitelist_policy",
+ .type = SAFESETID_WHITELIST_ADD},
+ {.name = "flush_whitelist_policies",
+ .type = SAFESETID_WHITELIST_FLUSH},
+};
+
+/*
+ * In the case the input buffer contains one or more invalid UIDs, the kuid_t
+ * variables pointed to by 'parent' and 'child' will get updated but this
+ * function will return an error.
+ */
+static int parse_safesetid_whitelist_policy(const char __user *buf,
+ size_t len,
+ kuid_t *parent,
+ kuid_t *child)
+{
+ char *kern_buf;
+ char *parent_buf;
+ char *child_buf;
+ const char separator[] = ":";
+ int ret;
+ size_t first_substring_length;
+ long parsed_parent;
+ long parsed_child;
+
+ /* Duplicate string from user memory and NULL-terminate */
+ kern_buf = memdup_user_nul(buf, len);
+ if (IS_ERR(kern_buf))
+ return PTR_ERR(kern_buf);
+
+ /*
+ * Format of |buf| string should be <UID>:<UID>.
+ * Find location of ":" in kern_buf (copied from |buf|).
+ */
+ first_substring_length = strcspn(kern_buf, separator);
+ if (first_substring_length == 0 || first_substring_length == len) {
+ ret = -EINVAL;
+ goto free_kern;
+ }
+
+ parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL);
+ if (!parent_buf) {
+ ret = -ENOMEM;
+ goto free_kern;
+ }
+
+ ret = kstrtol(parent_buf, 0, &parsed_parent);
+ if (ret)
+ goto free_both;
+
+ child_buf = kern_buf + first_substring_length + 1;
+ ret = kstrtol(child_buf, 0, &parsed_child);
+ if (ret)
+ goto free_both;
+
+ *parent = make_kuid(current_user_ns(), parsed_parent);
+ if (!uid_valid(*parent)) {
+ ret = -EINVAL;
+ goto free_both;
+ }
+
+ *child = make_kuid(current_user_ns(), parsed_child);
+ if (!uid_valid(*child)) {
+ ret = -EINVAL;
+ goto free_both;
+ }
+
+free_both:
+ kfree(parent_buf);
+free_kern:
+ kfree(kern_buf);
+ return ret;
+}
+
+static ssize_t safesetid_file_write(struct file *file,
+ const char __user *buf,
+ size_t len,
+ loff_t *ppos)
+{
+ struct safesetid_file_entry *file_entry =
+ file->f_inode->i_private;
+ kuid_t parent;
+ kuid_t child;
+ int ret;
+
+ if (!ns_capable(current_user_ns(), CAP_MAC_ADMIN))
+ return -EPERM;
+
+ if (*ppos != 0)
+ return -EINVAL;
+
+ switch (file_entry->type) {
+ case SAFESETID_WHITELIST_FLUSH:
+ flush_safesetid_whitelist_entries();
+ break;
+ case SAFESETID_WHITELIST_ADD:
+ ret = parse_safesetid_whitelist_policy(buf, len, &parent,
+ &child);
+ if (ret)
+ return ret;
+
+ ret = add_safesetid_whitelist_entry(parent, child);
+ if (ret)
+ return ret;
+ break;
+ default:
+ pr_warn("Unknown securityfs file %d\n", file_entry->type);
+ break;
+ }
+
+ /* Return len on success so caller won't keep trying to write */
+ return len;
+}
+
+static const struct file_operations safesetid_file_fops = {
+ .write = safesetid_file_write,
+};
+
+static void safesetid_shutdown_securityfs(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
+ struct safesetid_file_entry *entry =
+ &safesetid_files[i];
+ securityfs_remove(entry->dentry);
+ entry->dentry = NULL;
+ }
+
+ securityfs_remove(safesetid_policy_dir);
+ safesetid_policy_dir = NULL;
+}
+
+static int __init safesetid_init_securityfs(void)
+{
+ int i;
+ int ret;
+
+ if (!safesetid_initialized)
+ return 0;
+
+ safesetid_policy_dir = securityfs_create_dir("safesetid", NULL);
+ if (IS_ERR(safesetid_policy_dir)) {
+ ret = PTR_ERR(safesetid_policy_dir);
+ goto error;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
+ struct safesetid_file_entry *entry =
+ &safesetid_files[i];
+ entry->dentry = securityfs_create_file(
+ entry->name, 0200, safesetid_policy_dir,
+ entry, &safesetid_file_fops);
+ if (IS_ERR(entry->dentry)) {
+ ret = PTR_ERR(entry->dentry);
+ goto error;
+ }
+ }
+
+ return 0;
+
+error:
+ safesetid_shutdown_securityfs();
+ return ret;
+}
+fs_initcall(safesetid_init_securityfs);