From ace9429bb58fd418f0c81d4c2835699bddf6bde6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:27:49 +0200 Subject: Adding upstream version 6.6.15. Signed-off-by: Daniel Baumann --- security/apparmor/apparmorfs.c | 2698 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2698 insertions(+) create mode 100644 security/apparmor/apparmorfs.c (limited to 'security/apparmor/apparmorfs.c') diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c new file mode 100644 index 0000000000..63ddefb6dd --- /dev/null +++ b/security/apparmor/apparmorfs.c @@ -0,0 +1,2698 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/security/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/apparmorfs.h" +#include "include/audit.h" +#include "include/cred.h" +#include "include/crypto.h" +#include "include/ipc.h" +#include "include/label.h" +#include "include/policy.h" +#include "include/policy_ns.h" +#include "include/resource.h" +#include "include/policy_unpack.h" +#include "include/task.h" + +/* + * The apparmor filesystem interface used for policy load and introspection + * The interface is split into two main components based on their function + * a securityfs component: + * used for static files that are always available, and which allows + * userspace to specificy the location of the security filesystem. + * + * fns and data are prefixed with + * aa_sfs_ + * + * an apparmorfs component: + * used loaded policy content and introspection. It is not part of a + * regular mounted filesystem and is available only through the magic + * policy symlink in the root of the securityfs apparmor/ directory. + * Tasks queries will be magically redirected to the correct portion + * of the policy tree based on their confinement. + * + * fns and data are prefixed with + * aafs_ + * + * The aa_fs_ prefix is used to indicate the fn is used by both the + * securityfs and apparmorfs filesystems. + */ + + +/* + * support fns + */ + +struct rawdata_f_data { + struct aa_loaddata *loaddata; +}; + +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY +#define RAWDATA_F_DATA_BUF(p) (char *)(p + 1) + +static void rawdata_f_data_free(struct rawdata_f_data *private) +{ + if (!private) + return; + + aa_put_loaddata(private->loaddata); + kvfree(private); +} + +static struct rawdata_f_data *rawdata_f_data_alloc(size_t size) +{ + struct rawdata_f_data *ret; + + if (size > SIZE_MAX - sizeof(*ret)) + return ERR_PTR(-EINVAL); + + ret = kvzalloc(sizeof(*ret) + size, GFP_KERNEL); + if (!ret) + return ERR_PTR(-ENOMEM); + + return ret; +} +#endif + +/** + * mangle_name - mangle a profile name to std profile layout form + * @name: profile name to mangle (NOT NULL) + * @target: buffer to store mangled name, same length as @name (MAYBE NULL) + * + * Returns: length of mangled name + */ +static int mangle_name(const char *name, char *target) +{ + char *t = target; + + while (*name == '/' || *name == '.') + name++; + + if (target) { + for (; *name; name++) { + if (*name == '/') + *(t)++ = '.'; + else if (isspace(*name)) + *(t)++ = '_'; + else if (isalnum(*name) || strchr("._-", *name)) + *(t)++ = *name; + } + + *t = 0; + } else { + int len = 0; + for (; *name; name++) { + if (isalnum(*name) || isspace(*name) || + strchr("/._-", *name)) + len++; + } + + return len; + } + + return t - target; +} + + +/* + * aafs - core fns and data for the policy tree + */ + +#define AAFS_NAME "apparmorfs" +static struct vfsmount *aafs_mnt; +static int aafs_count; + + +static int aafs_show_path(struct seq_file *seq, struct dentry *dentry) +{ + seq_printf(seq, "%s:[%lu]", AAFS_NAME, d_inode(dentry)->i_ino); + return 0; +} + +static void aafs_free_inode(struct inode *inode) +{ + if (S_ISLNK(inode->i_mode)) + kfree(inode->i_link); + free_inode_nonrcu(inode); +} + +static const struct super_operations aafs_super_ops = { + .statfs = simple_statfs, + .free_inode = aafs_free_inode, + .show_path = aafs_show_path, +}; + +static int apparmorfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + static struct tree_descr files[] = { {""} }; + int error; + + error = simple_fill_super(sb, AAFS_MAGIC, files); + if (error) + return error; + sb->s_op = &aafs_super_ops; + + return 0; +} + +static int apparmorfs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, apparmorfs_fill_super); +} + +static const struct fs_context_operations apparmorfs_context_ops = { + .get_tree = apparmorfs_get_tree, +}; + +static int apparmorfs_init_fs_context(struct fs_context *fc) +{ + fc->ops = &apparmorfs_context_ops; + return 0; +} + +static struct file_system_type aafs_ops = { + .owner = THIS_MODULE, + .name = AAFS_NAME, + .init_fs_context = apparmorfs_init_fs_context, + .kill_sb = kill_anon_super, +}; + +/** + * __aafs_setup_d_inode - basic inode setup for apparmorfs + * @dir: parent directory for the dentry + * @dentry: dentry we are seting the inode up for + * @mode: permissions the file should have + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used + * @iops: struct of inode_operations that should be used + */ +static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, + umode_t mode, void *data, char *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct inode *inode = new_inode(dir->i_sb); + + AA_BUG(!dir); + AA_BUG(!dentry); + + if (!inode) + return -ENOMEM; + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = inode_set_ctime_current(inode); + inode->i_private = data; + if (S_ISDIR(mode)) { + inode->i_op = iops ? iops : &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inc_nlink(inode); + inc_nlink(dir); + } else if (S_ISLNK(mode)) { + inode->i_op = iops ? iops : &simple_symlink_inode_operations; + inode->i_link = link; + } else { + inode->i_fop = fops; + } + d_instantiate(dentry, inode); + dget(dentry); + + return 0; +} + +/** + * aafs_create - create a dentry in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used for + * @iops: struct of inode_operations that should be used + * + * This is the basic "create a xxx" function for apparmorfs. + * + * Returns a pointer to a dentry if it succeeds, that must be free with + * aafs_remove(). Will return ERR_PTR on failure. + */ +static struct dentry *aafs_create(const char *name, umode_t mode, + struct dentry *parent, void *data, void *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct dentry *dentry; + struct inode *dir; + int error; + + AA_BUG(!name); + AA_BUG(!parent); + + if (!(mode & S_IFMT)) + mode = (mode & S_IALLUGO) | S_IFREG; + + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + if (error) + return ERR_PTR(error); + + dir = d_inode(parent); + + inode_lock(dir); + dentry = lookup_one_len(name, parent, strlen(name)); + if (IS_ERR(dentry)) { + error = PTR_ERR(dentry); + goto fail_lock; + } + + if (d_really_is_positive(dentry)) { + error = -EEXIST; + goto fail_dentry; + } + + error = __aafs_setup_d_inode(dir, dentry, mode, data, link, fops, iops); + if (error) + goto fail_dentry; + inode_unlock(dir); + + return dentry; + +fail_dentry: + dput(dentry); + +fail_lock: + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); + + return ERR_PTR(error); +} + +/** + * aafs_create_file - create a file in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @fops: struct file_operations that should be used for + * + * see aafs_create + */ +static struct dentry *aafs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + return aafs_create(name, mode, parent, data, NULL, fops, NULL); +} + +/** + * aafs_create_dir - create a directory in the apparmorfs filesystem + * + * @name: name of dentry to create + * @parent: parent directory for this dentry + * + * see aafs_create + */ +static struct dentry *aafs_create_dir(const char *name, struct dentry *parent) +{ + return aafs_create(name, S_IFDIR | 0755, parent, NULL, NULL, NULL, + NULL); +} + +/** + * aafs_remove - removes a file or directory from the apparmorfs filesystem + * + * @dentry: dentry of the file/directory/symlink to removed. + */ +static void aafs_remove(struct dentry *dentry) +{ + struct inode *dir; + + if (!dentry || IS_ERR(dentry)) + return; + + dir = d_inode(dentry->d_parent); + inode_lock(dir); + if (simple_positive(dentry)) { + if (d_is_dir(dentry)) + simple_rmdir(dir, dentry); + else + simple_unlink(dir, dentry); + d_delete(dentry); + dput(dentry); + } + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); +} + + +/* + * aa_fs - policy load/replace/remove + */ + +/** + * aa_simple_write_to_buffer - common routine for getting policy from user + * @userbuf: user buffer to copy data from (NOT NULL) + * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) + * @copy_size: size of data to copy from user buffer + * @pos: position write is at in the file (NOT NULL) + * + * Returns: kernel buffer containing copy of user buffer data or an + * ERR_PTR on failure. + */ +static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, + size_t alloc_size, + size_t copy_size, + loff_t *pos) +{ + struct aa_loaddata *data; + + AA_BUG(copy_size > alloc_size); + + if (*pos != 0) + /* only writes from pos 0, that is complete writes */ + return ERR_PTR(-ESPIPE); + + /* freed by caller to simple_write_to_buffer */ + data = aa_loaddata_alloc(alloc_size); + if (IS_ERR(data)) + return data; + + data->size = copy_size; + if (copy_from_user(data->data, userbuf, copy_size)) { + aa_put_loaddata(data); + return ERR_PTR(-EFAULT); + } + + return data; +} + +static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, + loff_t *pos, struct aa_ns *ns) +{ + struct aa_loaddata *data; + struct aa_label *label; + ssize_t error; + + label = begin_current_label_crit_section(); + + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(current_cred(), label, ns, mask); + if (error) + goto end_section; + + data = aa_simple_write_to_buffer(buf, size, size, pos); + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_replace_profiles(ns, label, mask, data); + aa_put_loaddata(data); + } +end_section: + end_current_label_crit_section(label); + + return error; +} + +/* .load file hook fn to load policy */ +static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, + loff_t *pos) +{ + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns); + + aa_put_ns(ns); + + return error; +} + +static const struct file_operations aa_fs_profile_load = { + .write = profile_load, + .llseek = default_llseek, +}; + +/* .replace file hook fn to load and/or replace policy */ +static ssize_t profile_replace(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, + buf, size, pos, ns); + aa_put_ns(ns); + + return error; +} + +static const struct file_operations aa_fs_profile_replace = { + .write = profile_replace, + .llseek = default_llseek, +}; + +/* .remove file hook fn to remove loaded policy */ +static ssize_t profile_remove(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + struct aa_loaddata *data; + struct aa_label *label; + ssize_t error; + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + + label = begin_current_label_crit_section(); + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(current_cred(), label, ns, + AA_MAY_REMOVE_POLICY); + if (error) + goto out; + + /* + * aa_remove_profile needs a null terminated string so 1 extra + * byte is allocated and the copied data is null terminated. + */ + data = aa_simple_write_to_buffer(buf, size + 1, size, pos); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + data->data[size] = 0; + error = aa_remove_profiles(ns, label, data->data, size); + aa_put_loaddata(data); + } + out: + end_current_label_crit_section(label); + aa_put_ns(ns); + return error; +} + +static const struct file_operations aa_fs_profile_remove = { + .write = profile_remove, + .llseek = default_llseek, +}; + +struct aa_revision { + struct aa_ns *ns; + long last_read; +}; + +/* revision file hook fn for policy loads */ +static int ns_revision_release(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = file->private_data; + + if (rev) { + aa_put_ns(rev->ns); + kfree(rev); + } + + return 0; +} + +static ssize_t ns_revision_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + struct aa_revision *rev = file->private_data; + char buffer[32]; + long last_read; + int avail; + + mutex_lock_nested(&rev->ns->lock, rev->ns->level); + last_read = rev->last_read; + if (last_read == rev->ns->revision) { + mutex_unlock(&rev->ns->lock); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(rev->ns->wait, + last_read != + READ_ONCE(rev->ns->revision))) + return -ERESTARTSYS; + mutex_lock_nested(&rev->ns->lock, rev->ns->level); + } + + avail = sprintf(buffer, "%ld\n", rev->ns->revision); + if (*ppos + size > avail) { + rev->last_read = rev->ns->revision; + *ppos = 0; + } + mutex_unlock(&rev->ns->lock); + + return simple_read_from_buffer(buf, size, ppos, buffer, avail); +} + +static int ns_revision_open(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = kzalloc(sizeof(*rev), GFP_KERNEL); + + if (!rev) + return -ENOMEM; + + rev->ns = aa_get_ns(inode->i_private); + if (!rev->ns) + rev->ns = aa_get_current_ns(); + file->private_data = rev; + + return 0; +} + +static __poll_t ns_revision_poll(struct file *file, poll_table *pt) +{ + struct aa_revision *rev = file->private_data; + __poll_t mask = 0; + + if (rev) { + mutex_lock_nested(&rev->ns->lock, rev->ns->level); + poll_wait(file, &rev->ns->wait, pt); + if (rev->last_read < rev->ns->revision) + mask |= EPOLLIN | EPOLLRDNORM; + mutex_unlock(&rev->ns->lock); + } + + return mask; +} + +void __aa_bump_ns_revision(struct aa_ns *ns) +{ + WRITE_ONCE(ns->revision, READ_ONCE(ns->revision) + 1); + wake_up_interruptible(&ns->wait); +} + +static const struct file_operations aa_fs_ns_revision_fops = { + .owner = THIS_MODULE, + .open = ns_revision_open, + .poll = ns_revision_poll, + .read = ns_revision_read, + .llseek = generic_file_llseek, + .release = ns_revision_release, +}; + +static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, + const char *match_str, size_t match_len) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + struct aa_perms tmp = { }; + aa_state_t state = DFA_NOMATCH; + + if (profile_unconfined(profile)) + return; + if (rules->file.dfa && *match_str == AA_CLASS_FILE) { + state = aa_dfa_match_len(rules->file.dfa, + rules->file.start[AA_CLASS_FILE], + match_str + 1, match_len - 1); + if (state) { + struct path_cond cond = { }; + + tmp = *(aa_lookup_fperms(&(rules->file), state, &cond)); + } + } else if (rules->policy.dfa) { + if (!RULE_MEDIATES(rules, *match_str)) + return; /* no change to current perms */ + state = aa_dfa_match_len(rules->policy.dfa, + rules->policy.start[0], + match_str, match_len); + if (state) + tmp = *aa_lookup_perms(&rules->policy, state); + } + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum_raw(perms, &tmp); +} + + +/** + * query_data - queries a policy and writes its data to buf + * @buf: the resulting data is stored here (NOT NULL) + * @buf_len: size of buf + * @query: query string used to retrieve data + * @query_len: size of query including second NUL byte + * + * The buffers pointed to by buf and query may overlap. The query buffer is + * parsed before buf is written to. + * + * The query should look like "