aboutsummaryrefslogtreecommitdiff
path: root/security/trustees/security.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/trustees/security.c')
-rwxr-xr-xsecurity/trustees/security.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/security/trustees/security.c b/security/trustees/security.c
new file mode 100755
index 00000000000..bbc98886243
--- /dev/null
+++ b/security/trustees/security.c
@@ -0,0 +1,417 @@
+/*
+ * Trustees ACL Project
+ *
+ * Copyright (c) 1999-2000 Vyacheslav Zavadsky
+ * Copyright (c) 2004 Andrew Ruder (aeruder@ksu.edu)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ * The security module (LSM API) component of the trustees system
+ *
+ * One quick note: generally security modules with the LSM are supposed
+ * to be solely restrictive modules. Unless the trustees module were to
+ * require that people set all files rwx by all, it could not function
+ * as it is meant to function as a solely restrictive module.
+ *
+ * To compensate, every process is given the capability CAP_DAC_OVERRIDE.
+ * In other words, every process is first given full rights to the filesystem.
+ * This is the only non-restricting portion of this module, since it -does-
+ * in fact give additional permissions. However, in the inode_permission hook,
+ * any rights the user should not have are taken away.
+ *
+ * Side effects: Posix ACLs or other filesystem-specific permissions are not
+ * honored. Trustees ACLs can (and do) take into account the standard unix
+ * permissions, but any permissions further than that are difficult, to say
+ * the least, to take into account. I, personally, do not find this to
+ * be a problem since if you are using Trustees ACLs, why also require the use
+ * of another ACL system?
+ */
+
+#include <linux/security.h>
+#include <linux/capability.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/nsproxy.h>
+#include <linux/cred.h>
+#include <linux/mnt_namespace.h>
+
+#include "internal.h"
+
+static int trustees_capable(struct task_struct *tsk, const struct cred *cred,
+ int cap,
+ int audit);
+static int trustees_inode_permission(struct inode *inode, int mask);
+
+/* Checks if user has access to the inode due to root status
+ */
+static inline int has_root_perm(struct inode *inode, int mask)
+{
+ umode_t mode = inode->i_mode;
+
+ if (!(mask & MAY_EXEC) || (mode & S_IXUGO) || S_ISDIR(mode))
+ if (current_fsuid() == 0)
+ return 0;
+
+ return -EACCES;
+}
+
+/* The logic for this was mostly stolen from vfs_permission. The security API
+ * doesn't give a good way to use the actual vfs_permission for this since our
+ * CAP_DAC_OVERRIDE causes it to always return 0. But if we didn't return
+ * CAP_DAC_OVERRIDE, we'd never get to handle permissions! Since we don't need
+ * to handle capabilities and dealing with ACLs with trustees loaded isn't an
+ * issue for me, the function ends up being pretty simple.
+ */
+
+static inline int has_unix_perm(struct inode *inode, int mask)
+{
+ umode_t mode = inode->i_mode;
+ mask &= ~MAY_APPEND;
+
+ if (current_fsuid() == inode->i_uid)
+ mode >>= 6;
+ else if (in_group_p(inode->i_gid))
+ mode >>= 3;
+
+ if (((mode & mask & (MAY_READ | MAY_WRITE | MAY_EXEC)) == mask))
+ return 0;
+
+ return -EACCES;
+}
+
+/* Find a vfsmount given an inode */
+static inline struct vfsmount *find_inode_mnt(struct inode *inode)
+{
+ struct mnt_namespace *ns = NULL;
+ struct vfsmount *mnt = NULL;
+
+ if (!inode->i_sb) {
+ TS_ERR_MSG("Inode without a i_sb entry?");
+ return NULL;
+ }
+
+ /* Okay, we need to find the vfsmount by looking
+ * at the namespace now.
+ */
+ task_lock(current);
+ if (current->nsproxy) {
+ ns = current->nsproxy->mnt_ns;
+ if (ns)
+ get_mnt_ns(ns);
+ }
+ task_unlock(current);
+
+ if (!ns)
+ return NULL;
+
+ list_for_each_entry(mnt, &ns->list, mnt_list) {
+ if (mnt->mnt_sb == inode->i_sb && mnt->mnt_devname) {
+ mntget(mnt);
+ goto out;
+ }
+ }
+
+ out:
+ put_mnt_ns(ns);
+
+ return mnt;
+}
+
+/* Find a dentry given an inode */
+static inline struct dentry *find_inode_dentry(struct inode *inode)
+{
+ struct dentry *dentry;
+
+ dentry = d_find_alias(inode);
+
+ return dentry;
+}
+
+/* Find a path (dentry/vfsmount pair) given an inode
+ */
+static inline int find_inode_path(struct inode *inode, struct path *path)
+{
+ int ret = 0;
+
+ path->dentry = NULL;
+ path->mnt = NULL;
+
+ path->mnt = find_inode_mnt(inode);
+ if (unlikely(!path->mnt)) {
+ TS_ERR_MSG("inode does not have a mnt!\n");
+ goto error_out;
+ }
+
+ path->dentry = find_inode_dentry(inode);
+ if (unlikely(!path->dentry)) {
+ /* Most of the time when this happens, it is the /
+ * If it is not, we need to dump as much information
+ * as possible on it and dump it to logs, because
+ * I'm really not sure how it happens.
+ */
+ if (path->mnt->mnt_root && inode == path->mnt->mnt_root->d_inode) {
+ path->dentry = dget(path->mnt->mnt_root);
+ } else {
+ /* I have seen this happen once but I did not have any
+ * way to see what caused it. I am gonna dump_stack
+ * until I have that happen again to see if the cause
+ * is something that I need to worry about.
+ */
+ dump_stack(); /* DEBUG FIXME */
+ TS_ERR_MSG("Inode number: %ld\n", inode->i_ino);
+ TS_ERR_MSG("dentry does not exist!\n");
+ goto error_mnt_out;
+ }
+ }
+
+ goto out;
+
+error_mnt_out:
+ mntput(path->mnt);
+ path->mnt = NULL;
+
+error_out:
+ ret = 1;
+
+out:
+ return ret;
+}
+
+/*
+ * Return 1 if they are under the same set of trustees
+ * otherwise return 0. In the case that we are handling
+ * a directory, we also check to see if there are subdirectories
+ * with trustees.
+ */
+static inline int have_same_trustees_rename(struct path *path1, struct path *path2)
+{
+ char *filename1, *filename2;
+ int depth1, depth2;
+ struct trustee_hash_element *deep1, *deep2;
+ int is_dir;
+ int ret = 0;
+
+ filename1 = trustees_filename_for_dentry(path1->dentry, &depth1, 1);
+ if (!filename1) {
+ TS_ERR_MSG("Couldn't allocate filename\n");
+ goto out;
+ }
+
+ filename2 = trustees_filename_for_dentry(path2->dentry, &depth2, 1);
+ if (!filename2) {
+ TS_ERR_MSG("Couldn't allocate filename\n");
+ goto out_file_name;
+ }
+
+ is_dir = S_ISDIR(path1->dentry->d_inode->i_mode);
+
+ read_lock(&trustee_hash_lock);
+ trustee_perm(path1, filename1, ret, depth1, is_dir, &deep1);
+ trustee_perm(path2, filename2, ret, depth2, is_dir, &deep2);
+ if (deep1 == deep2) {
+ ret = 1;
+ if (is_dir) {
+ if (trustee_has_child(path1->mnt, filename1) ||
+ trustee_has_child(path2->mnt, filename2)) ret = 0;
+ }
+ }
+ read_unlock(&trustee_hash_lock);
+
+ kfree(filename2);
+ out_file_name:
+ kfree(filename1);
+ out:
+
+ return ret;
+}
+
+static int trustees_inode_rename(struct inode *old_dir,
+ struct dentry *old_dentry,
+ struct inode *new_dir,
+ struct dentry *new_dentry);
+static int trustees_inode_link(struct dentry *old_dentry,
+ struct inode *dir,
+ struct dentry *new_dentry);
+
+/* Structure where we fill in the various hooks we are implementing in this module
+ */
+struct security_operations trustees_security_ops = {
+ .capable = trustees_capable,
+ .inode_permission = trustees_inode_permission,
+ .inode_link = trustees_inode_link,
+ .inode_rename = trustees_inode_rename
+};
+
+#define ALL_MAYS (MAY_WRITE | MAY_EXEC | MAY_READ)
+/* Converts a trustee_mask to a normal unix mask
+ */
+static int inline trustee_mask_to_normal_mask(int mask, int isdir)
+{
+ int r = 0;
+ if ((mask & TRUSTEE_READ_MASK) && !isdir)
+ r |= MAY_READ;
+ if ((mask & TRUSTEE_READ_DIR_MASK) && isdir)
+ r |= MAY_READ;
+ if (mask & TRUSTEE_WRITE_MASK)
+ r |= MAY_WRITE;
+ if ((mask & TRUSTEE_BROWSE_MASK) && isdir)
+ r |= MAY_EXEC;
+ if ((mask & TRUSTEE_EXECUTE_MASK) && !isdir)
+ r |= MAY_EXEC;
+ return r;
+}
+
+/* This is the meat of the permissions checking. First it checks for root,
+ * otherwise it first checks for any errors finding the dentry/vfsmount for
+ * the inode, and then it looks up the dentry in the trustees hash.
+ */
+static int trustees_inode_permission(struct inode *inode, int mask)
+{
+ struct path path;
+ char *file_name;
+ int is_dir;
+ int ret;
+ int depth;
+ int amask;
+ int dmask;
+ umode_t mode = inode->i_mode;
+
+ if (has_root_perm(inode, mask) == 0)
+ return 0;
+
+ ret = has_unix_perm(inode, mask);
+
+ if (find_inode_path(inode, &path)) {
+ return -EACCES;
+ }
+
+ file_name = trustees_filename_for_dentry(path.dentry, &depth, 1);
+ if (!file_name) {
+ TS_ERR_MSG("Couldn't allocate filename\n");
+ ret = -EACCES;
+ goto out_path;
+ }
+
+ is_dir = S_ISDIR(inode->i_mode);
+
+ read_lock(&trustee_hash_lock);
+ amask = trustee_perm(&path, file_name, ret, depth, is_dir,
+ (struct trustee_hash_element **)NULL);
+ read_unlock(&trustee_hash_lock);
+ dmask = amask >> TRUSTEE_NUM_ACL_BITS;
+
+ /* no permission if denied */
+ if (trustee_mask_to_normal_mask(dmask, is_dir) & mask & ALL_MAYS) {
+ ret = -EACCES;
+ goto out;
+ }
+ /* use unix perms */
+ if (!(dmask & TRUSTEE_USE_UNIX_MASK) &&
+ (amask & TRUSTEE_USE_UNIX_MASK) && (!ret))
+ goto out;
+
+ /* if the file isn't executable, then the trustees shouldn't
+ * make it executable
+ */
+ if ((mask & MAY_EXEC) && !(mode & S_IXOTH) &&
+ !((mode >> 3) & S_IXOTH) & !((mode >> 6) & S_IXOTH) &&
+ (!is_dir)) {
+ ret = -EACCES;
+ goto out;
+ }
+ /* Check trustees for permission
+ */
+ if ((trustee_mask_to_normal_mask(amask, is_dir) & mask & ALL_MAYS)
+ == mask) {
+ ret = 0;
+ goto out;
+ } else
+ ret = -EACCES;
+
+out:
+ kfree(file_name);
+out_path:
+ path_put(&path);
+
+ return ret;
+}
+
+/* We should only allow hard links under one of two conditions:
+ * 1. Its in the same trustee
+ * - if the two dentries are covered by the same trustee, there shouldn't
+ * be much of a problem with allowing the hardlink to occur.
+ * 2. fsuid = 0
+ */
+static int trustees_inode_link(struct dentry *old_dentry,
+ struct inode *dir,
+ struct dentry *new_dentry)
+{
+ int ret = -EXDEV;
+ struct path path1;
+ struct path path2;
+
+ if (current_fsuid() == 0)
+ return 0;
+
+ path1.dentry = dget(old_dentry);
+ path1.mnt = find_inode_mnt(old_dentry->d_inode);
+ path2.dentry = dget(new_dentry);
+ path2.mnt = mntget(path1.mnt);
+
+ if (have_same_trustees_rename(&path1, &path2))
+ ret = 0;
+
+ path_put(&path1);
+ path_put(&path2);
+
+ return ret;
+}
+
+/* We have a few renames to protect against:
+ * 1. Any file or directory that is affected by different trustees at its
+ * old location than at its new location.
+ * 2. In the case of a directory, we should protect against moving a directory
+ * that has trustees set inside of it.
+ *
+ * In any case above, we return -EXDEV which signifies to the calling program that
+ * the files are on different devices, and assuming the program is written correctly
+ * it should then handle the situation by copying the files and removing the originals
+ * ( which will then use the trustees permissions as they are meant to be used )
+ */
+static int trustees_inode_rename(struct inode *old_dir,
+ struct dentry *old_dentry,
+ struct inode *new_dir,
+ struct dentry *new_dentry)
+{
+ return trustees_inode_link(old_dentry, new_dir, new_dentry);
+}
+
+/* Return CAP_DAC_OVERRIDE on everything. We want to handle our own
+ * permissions (overriding those normally allowed by unix permissions)
+ */
+static int trustees_capable(struct task_struct *tsk, const struct cred *cred,
+ int cap,
+ int audit)
+{
+ if (cap == CAP_DAC_OVERRIDE)
+ return 0;
+
+ return cap_capable(tsk, cred, cap, audit);
+}
+
+/* Register the security module
+ */
+int trustees_init_security(void)
+{
+ if (register_security(&trustees_security_ops)) {
+ TS_ERR_MSG("Could not register security component\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}