123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * SafeSetID Linux Security Module
- *
- * Author: Micah Morton <[email protected]>
- *
- * 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(uid_policy_update_lock);
- static DEFINE_MUTEX(gid_policy_update_lock);
- /*
- * In the case the input buffer contains one or more invalid IDs, the kid_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 setid_rule *rule)
- {
- char *child_str;
- int ret;
- u32 parsed_parent, parsed_child;
- /* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */
- 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;
- if (rule->type == UID){
- rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent);
- rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child);
- if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid))
- return -EINVAL;
- } else if (rule->type == GID){
- rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent);
- rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child);
- if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid))
- return -EINVAL;
- } else {
- /* Error, rule->type is an invalid type */
- return -EINVAL;
- }
- return 0;
- }
- static void __release_ruleset(struct rcu_head *rcu)
- {
- struct setid_ruleset *pol =
- container_of(rcu, struct setid_ruleset, rcu);
- int bucket;
- struct setid_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 setid_ruleset *pol){
- call_rcu(&pol->rcu, __release_ruleset);
- }
- static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule)
- {
- if (pol->type == UID)
- hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid));
- else if (pol->type == GID)
- hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid));
- else /* Error, pol->type is neither UID or GID */
- return;
- }
- static int verify_ruleset(struct setid_ruleset *pol)
- {
- int bucket;
- struct setid_rule *rule, *nrule;
- int res = 0;
- hash_for_each(pol->rules, bucket, rule, next) {
- if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) {
- if (pol->type == UID) {
- pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
- __kuid_val(rule->src_id.uid),
- __kuid_val(rule->dst_id.uid));
- } else if (pol->type == GID) {
- pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n",
- __kgid_val(rule->src_id.gid),
- __kgid_val(rule->dst_id.gid));
- } else { /* pol->type is an invalid type */
- res = -EINVAL;
- return res;
- }
- res = -EINVAL;
- /* fix it up */
- nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
- if (!nrule)
- return -ENOMEM;
- if (pol->type == UID){
- nrule->src_id.uid = rule->dst_id.uid;
- nrule->dst_id.uid = rule->dst_id.uid;
- nrule->type = UID;
- } else { /* pol->type must be GID if we've made it to here */
- nrule->src_id.gid = rule->dst_id.gid;
- nrule->dst_id.gid = rule->dst_id.gid;
- nrule->type = GID;
- }
- insert_rule(pol, nrule);
- }
- }
- return res;
- }
- static ssize_t handle_policy_update(struct file *file,
- const char __user *ubuf, size_t len, enum setid_type policy_type)
- {
- struct setid_ruleset *pol;
- char *buf, *p, *end;
- int err;
- pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
- if (!pol)
- return -ENOMEM;
- pol->policy_str = NULL;
- pol->type = policy_type;
- 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 setid_rule *rule;
- end = strchr(p, '\n');
- if (end == NULL) {
- err = -EINVAL;
- goto out_free_buf;
- }
- *end = '\0';
- rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
- if (!rule) {
- err = -ENOMEM;
- goto out_free_buf;
- }
- rule->type = policy_type;
- err = parse_policy_line(file, p, rule);
- if (err)
- goto out_free_rule;
- if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == 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.
- */
- if (policy_type == UID) {
- mutex_lock(&uid_policy_update_lock);
- pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
- lockdep_is_held(&uid_policy_update_lock));
- mutex_unlock(&uid_policy_update_lock);
- } else if (policy_type == GID) {
- mutex_lock(&gid_policy_update_lock);
- pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
- lockdep_is_held(&gid_policy_update_lock));
- mutex_unlock(&gid_policy_update_lock);
- } else {
- /* Error, policy type is neither UID or GID */
- pr_warn("error: bad policy type");
- }
- err = len;
- out_free_buf:
- kfree(buf);
- out_free_pol:
- if (pol)
- release_ruleset(pol);
- return err;
- }
- static ssize_t safesetid_uid_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, UID);
- }
- static ssize_t safesetid_gid_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, GID);
- }
- static ssize_t safesetid_file_read(struct file *file, char __user *buf,
- size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
- {
- ssize_t res = 0;
- struct setid_ruleset *pol;
- const char *kbuf;
- mutex_lock(policy_update_lock);
- pol = rcu_dereference_protected(ruleset, 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 ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
- size_t len, loff_t *ppos)
- {
- return safesetid_file_read(file, buf, len, ppos,
- &uid_policy_update_lock, safesetid_setuid_rules);
- }
- static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
- size_t len, loff_t *ppos)
- {
- return safesetid_file_read(file, buf, len, ppos,
- &gid_policy_update_lock, safesetid_setgid_rules);
- }
- static const struct file_operations safesetid_uid_file_fops = {
- .read = safesetid_uid_file_read,
- .write = safesetid_uid_file_write,
- };
- static const struct file_operations safesetid_gid_file_fops = {
- .read = safesetid_gid_file_read,
- .write = safesetid_gid_file_write,
- };
- static int __init safesetid_init_securityfs(void)
- {
- int ret;
- struct dentry *policy_dir;
- struct dentry *uid_policy_file;
- struct dentry *gid_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;
- }
- uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
- policy_dir, NULL, &safesetid_uid_file_fops);
- if (IS_ERR(uid_policy_file)) {
- ret = PTR_ERR(uid_policy_file);
- goto error;
- }
- gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
- policy_dir, NULL, &safesetid_gid_file_fops);
- if (IS_ERR(gid_policy_file)) {
- ret = PTR_ERR(gid_policy_file);
- goto error;
- }
- return 0;
- error:
- securityfs_remove(policy_dir);
- return ret;
- }
- fs_initcall(safesetid_init_securityfs);
|