Update Linux to v5.4.2

Change-Id: Idf6911045d9d382da2cfe01b1edff026404ac8fd
diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c
new file mode 100644
index 0000000..74a13d4
--- /dev/null
+++ b/security/safesetid/securityfs.c
@@ -0,0 +1,261 @@
+// 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.
+ *
+ */
+
+#define pr_fmt(fmt) "SafeSetID: " fmt
+
+#include <linux/security.h>
+#include <linux/cred.h>
+
+#include "lsm.h"
+
+static DEFINE_MUTEX(policy_update_lock);
+
+/*
+ * 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.
+ * Contents of @buf may be modified.
+ */
+static int parse_policy_line(struct file *file, char *buf,
+	struct setuid_rule *rule)
+{
+	char *child_str;
+	int ret;
+	u32 parsed_parent, parsed_child;
+
+	/* Format of |buf| string should be <UID>:<UID>. */
+	child_str = strchr(buf, ':');
+	if (child_str == NULL)
+		return -EINVAL;
+	*child_str = '\0';
+	child_str++;
+
+	ret = kstrtou32(buf, 0, &parsed_parent);
+	if (ret)
+		return ret;
+
+	ret = kstrtou32(child_str, 0, &parsed_child);
+	if (ret)
+		return ret;
+
+	rule->src_uid = make_kuid(file->f_cred->user_ns, parsed_parent);
+	rule->dst_uid = make_kuid(file->f_cred->user_ns, parsed_child);
+	if (!uid_valid(rule->src_uid) || !uid_valid(rule->dst_uid))
+		return -EINVAL;
+
+	return 0;
+}
+
+static void __release_ruleset(struct rcu_head *rcu)
+{
+	struct setuid_ruleset *pol =
+		container_of(rcu, struct setuid_ruleset, rcu);
+	int bucket;
+	struct setuid_rule *rule;
+	struct hlist_node *tmp;
+
+	hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
+		kfree(rule);
+	kfree(pol->policy_str);
+	kfree(pol);
+}
+
+static void release_ruleset(struct setuid_ruleset *pol)
+{
+	call_rcu(&pol->rcu, __release_ruleset);
+}
+
+static void insert_rule(struct setuid_ruleset *pol, struct setuid_rule *rule)
+{
+	hash_add(pol->rules, &rule->next, __kuid_val(rule->src_uid));
+}
+
+static int verify_ruleset(struct setuid_ruleset *pol)
+{
+	int bucket;
+	struct setuid_rule *rule, *nrule;
+	int res = 0;
+
+	hash_for_each(pol->rules, bucket, rule, next) {
+		if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) ==
+		    SIDPOL_DEFAULT) {
+			pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
+				__kuid_val(rule->src_uid),
+				__kuid_val(rule->dst_uid));
+			res = -EINVAL;
+
+			/* fix it up */
+			nrule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL);
+			if (!nrule)
+				return -ENOMEM;
+			nrule->src_uid = rule->dst_uid;
+			nrule->dst_uid = rule->dst_uid;
+			insert_rule(pol, nrule);
+		}
+	}
+	return res;
+}
+
+static ssize_t handle_policy_update(struct file *file,
+				    const char __user *ubuf, size_t len)
+{
+	struct setuid_ruleset *pol;
+	char *buf, *p, *end;
+	int err;
+
+	pol = kmalloc(sizeof(struct setuid_ruleset), GFP_KERNEL);
+	if (!pol)
+		return -ENOMEM;
+	pol->policy_str = NULL;
+	hash_init(pol->rules);
+
+	p = buf = memdup_user_nul(ubuf, len);
+	if (IS_ERR(buf)) {
+		err = PTR_ERR(buf);
+		goto out_free_pol;
+	}
+	pol->policy_str = kstrdup(buf, GFP_KERNEL);
+	if (pol->policy_str == NULL) {
+		err = -ENOMEM;
+		goto out_free_buf;
+	}
+
+	/* policy lines, including the last one, end with \n */
+	while (*p != '\0') {
+		struct setuid_rule *rule;
+
+		end = strchr(p, '\n');
+		if (end == NULL) {
+			err = -EINVAL;
+			goto out_free_buf;
+		}
+		*end = '\0';
+
+		rule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL);
+		if (!rule) {
+			err = -ENOMEM;
+			goto out_free_buf;
+		}
+
+		err = parse_policy_line(file, p, rule);
+		if (err)
+			goto out_free_rule;
+
+		if (_setuid_policy_lookup(pol, rule->src_uid, rule->dst_uid) ==
+		    SIDPOL_ALLOWED) {
+			pr_warn("bad policy: duplicate entry\n");
+			err = -EEXIST;
+			goto out_free_rule;
+		}
+
+		insert_rule(pol, rule);
+		p = end + 1;
+		continue;
+
+out_free_rule:
+		kfree(rule);
+		goto out_free_buf;
+	}
+
+	err = verify_ruleset(pol);
+	/* bogus policy falls through after fixing it up */
+	if (err && err != -EINVAL)
+		goto out_free_buf;
+
+	/*
+	 * Everything looks good, apply the policy and release the old one.
+	 * What we really want here is an xchg() wrapper for RCU, but since that
+	 * doesn't currently exist, just use a spinlock for now.
+	 */
+	mutex_lock(&policy_update_lock);
+	rcu_swap_protected(safesetid_setuid_rules, pol,
+			   lockdep_is_held(&policy_update_lock));
+	mutex_unlock(&policy_update_lock);
+	err = len;
+
+out_free_buf:
+	kfree(buf);
+out_free_pol:
+	if (pol)
+                release_ruleset(pol);
+	return err;
+}
+
+static ssize_t safesetid_file_write(struct file *file,
+				    const char __user *buf,
+				    size_t len,
+				    loff_t *ppos)
+{
+	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
+		return -EPERM;
+
+	if (*ppos != 0)
+		return -EINVAL;
+
+	return handle_policy_update(file, buf, len);
+}
+
+static ssize_t safesetid_file_read(struct file *file, char __user *buf,
+				   size_t len, loff_t *ppos)
+{
+	ssize_t res = 0;
+	struct setuid_ruleset *pol;
+	const char *kbuf;
+
+	mutex_lock(&policy_update_lock);
+	pol = rcu_dereference_protected(safesetid_setuid_rules,
+					lockdep_is_held(&policy_update_lock));
+	if (pol) {
+		kbuf = pol->policy_str;
+		res = simple_read_from_buffer(buf, len, ppos,
+					      kbuf, strlen(kbuf));
+	}
+	mutex_unlock(&policy_update_lock);
+	return res;
+}
+
+static const struct file_operations safesetid_file_fops = {
+	.read = safesetid_file_read,
+	.write = safesetid_file_write,
+};
+
+static int __init safesetid_init_securityfs(void)
+{
+	int ret;
+	struct dentry *policy_dir;
+	struct dentry *policy_file;
+
+	if (!safesetid_initialized)
+		return 0;
+
+	policy_dir = securityfs_create_dir("safesetid", NULL);
+	if (IS_ERR(policy_dir)) {
+		ret = PTR_ERR(policy_dir);
+		goto error;
+	}
+
+	policy_file = securityfs_create_file("whitelist_policy", 0600,
+			policy_dir, NULL, &safesetid_file_fops);
+	if (IS_ERR(policy_file)) {
+		ret = PTR_ERR(policy_file);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	securityfs_remove(policy_dir);
+	return ret;
+}
+fs_initcall(safesetid_init_securityfs);