// SPDX-License-Identifier: GPL-2.0 /* * SafeSetID Linux Security Module * * Author: Micah Morton * * 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 #include #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 :. * 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);