diff options
Diffstat (limited to 'security/trustees')
-rwxr-xr-x | security/trustees/Makefile | 7 | ||||
-rwxr-xr-x | security/trustees/fs.c | 273 | ||||
-rwxr-xr-x | security/trustees/funcs.c | 812 | ||||
-rwxr-xr-x | security/trustees/init.c | 57 | ||||
-rwxr-xr-x | security/trustees/internal.h | 101 | ||||
-rwxr-xr-x | security/trustees/security.c | 417 |
6 files changed, 1667 insertions, 0 deletions
diff --git a/security/trustees/Makefile b/security/trustees/Makefile new file mode 100755 index 00000000000..0613ea613c9 --- /dev/null +++ b/security/trustees/Makefile @@ -0,0 +1,7 @@ +ifeq ($(CONFIG_SECURITY_TRUSTEES_DEBUG),y) + EXTRA_CFLAGS += -DTRUSTEES_DEBUG +endif + +obj-$(CONFIG_SECURITY_TRUSTEES) := trustees.o + +trustees-y := security.o fs.o init.o funcs.o diff --git a/security/trustees/fs.c b/security/trustees/fs.c new file mode 100755 index 00000000000..6463c64631a --- /dev/null +++ b/security/trustees/fs.c @@ -0,0 +1,273 @@ +/* + * 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. + * + * This code handles the virtual filesystem for trustees. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/vmalloc.h> +#include <linux/security.h> +#include <asm/atomic.h> +#include <asm/uaccess.h> + +#include "internal.h" + + +/* initialization code for the trustees filesystem */ + +/* File operations + * + * this is all the code for handling the file operations done on the few files + * in the trustees filesystem + */ +static int trustees_open(struct inode *inode, struct file *filp); +static ssize_t trustees_read_bogus(struct file *filp, char __user * buf, + size_t count, loff_t * offset); +static ssize_t trustees_write_bogus(struct file *filp, + const char __user * buf, size_t count, + loff_t * offset); +static ssize_t trustees_read_status(struct file *filp, char __user * buf, + size_t count, loff_t * offset); +static ssize_t trustees_read_apiversion(struct file *filp, char __user * buf, + size_t count, loff_t * offset); +static ssize_t trustees_write_trustees(struct file *filp, + const char __user * buf, + size_t count, loff_t * offset); +static int trustees_open_trustees(struct inode *inode, struct file *file); +static int trustees_release_trustees(struct inode *inode, struct file *file); + +/* Various structs + */ + +static struct file_operations trustees_ops_apiversion = { + .open = trustees_open, + .read = trustees_read_apiversion, + .write = trustees_write_bogus, +}; + +static struct file_operations trustees_ops_status = { + .open = trustees_open, + .read = trustees_read_status, + .write = trustees_write_bogus +}; + +static struct file_operations trustees_ops_trustees = { + .open = trustees_open_trustees, + .read = trustees_read_bogus, + .write = trustees_write_trustees, + .release = trustees_release_trustees +}; + +static struct trustees_file_info { + const char *name; + struct file_operations *fops; + int mode; + struct dentry *dentry; +} trustees_files[] = { + {.name = "device", + .fops = &trustees_ops_trustees, + .mode = S_IWUSR, + .dentry = 0 + }, + {.name = "status", + .fops = &trustees_ops_status, + .mode = S_IRUSR, + .dentry = 0 + }, + {.name = "apiversion", + .fops = &trustees_ops_apiversion, + .mode = S_IRUSR | S_IRGRP | S_IROTH, + .dentry = 0 + }, + {"", NULL, 0, 0} +}; + +struct trustee_command_reader { + struct trustee_command command; + unsigned curarg; + void *arg[TRUSTEE_MAX_ARGS]; + size_t argsize[TRUSTEE_MAX_ARGS]; +}; + + +static struct dentry *toplevel = NULL; + +int trustees_init_fs(void) +{ + struct trustees_file_info *iter; + toplevel = securityfs_create_dir("trustees", NULL); + if (!toplevel) trustees_deinit_fs(); + for (iter = trustees_files; iter->fops && toplevel; iter++) { + iter->dentry = securityfs_create_file( + iter->name, iter->mode, toplevel, NULL, iter->fops); + if (!iter->dentry) trustees_deinit_fs(); + } + return !toplevel; +} + +void trustees_deinit_fs(void) +{ + struct trustees_file_info *iter; + for (iter = trustees_files; iter->fops; iter++) { + securityfs_remove(iter->dentry); + iter->dentry = NULL; + } + securityfs_remove(toplevel); + toplevel = NULL; +} + +/* + * They're opening the file... + */ + +static int trustees_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +static int trustees_open_trustees(struct inode *inode, struct file *file) +{ + file->private_data = vmalloc(sizeof(struct trustee_command_reader)); + if (!file->private_data) + return -ENOMEM; + + memset(file->private_data, 0, sizeof(struct trustee_command_reader)); + + return 0; +} + +static int trustees_release_trustees(struct inode *inode, struct file *file) +{ + vfree(file->private_data); + return 0; +} + +/* Do a read on a bogus file. Just return nothing :) */ +static ssize_t trustees_read_bogus(struct file *filp, char __user * buf, + size_t count, loff_t * offset) +{ + return 0; +} + +/* Similar way to handle writes. Just say we wrote the data and return */ +static ssize_t trustees_write_bogus(struct file *filp, + const char __user * buf, size_t count, + loff_t * offset) +{ + return count; +} + +/* Function for handling reading of the status. */ +static ssize_t trustees_read_status(struct file *filp, char __user * buf, + size_t count, loff_t * offset) +{ + static const char msg[] = "Damnit, it works, OK?!\n"; + unsigned long nocopy; + + if (*offset >= (sizeof(msg) - 1)) { + return 0; + } + + if (count > (sizeof(msg) - 1 - *offset)) { + count = sizeof(msg) - 1 - *offset; + } + nocopy = copy_to_user(buf, msg, count); + (*offset) += count; + (*offset) -= nocopy; + + return count; +} + +/* Function for handling reading of the apiversion. */ +static ssize_t trustees_read_apiversion(struct file *filp, char __user * buf, + size_t count, loff_t * offset) +{ + static const char msg[] = TRUSTEES_APIVERSION_STR "\n"; + unsigned long nocopy; + + if (*offset >= (sizeof(msg) - 1)) { + return 0; + } + + if (count > (sizeof(msg) - 1 - *offset)) { + count = sizeof(msg) - 1 - *offset; + } + nocopy = copy_to_user(buf, msg, count); + (*offset) += count; + (*offset) -= nocopy; + + return count; +} + +/* Cleanup our reader (deallocate all the allocated memory) */ +static void cleanup_reader(struct trustee_command_reader *reader) { + int z; + if (!reader) { + TS_ERR_MSG("How does reader disappear on us?\n"); + return; + } + + for (z = reader->curarg - 1; z >= 0; z--) { + vfree(reader->arg[z]); + reader->argsize[z] = 0; + } + reader->command.command = 0; + reader->curarg = 0; +} + +static ssize_t trustees_write_trustees(struct file *filp, + const char __user * buf, + size_t count, loff_t * offset) +{ + struct trustee_command_reader *reader = filp->private_data; + + if (reader->command.command == 0) { + reader->curarg = 0; + if (count != sizeof(struct trustee_command)) { + return -EIO; + } + if (copy_from_user(&reader->command, buf, count)) { + reader->command.command = 0; + TS_ERR_MSG("copy_from_user failed on command\n"); + return -EIO; + } + if (reader->command.numargs > TRUSTEE_MAX_ARGS) { + TS_ERR_MSG("Too many arguments specified for command %d\n", + reader->command.command); + return -EIO; + } + } else { + unsigned curarg = reader->curarg; + if (!(reader->arg[curarg] = vmalloc(count+1))) { + cleanup_reader(reader); + return -EIO; + } + reader->argsize[curarg] = count; + ((char *)reader->arg[curarg])[count] = '\0'; + reader->curarg++; + if (copy_from_user(reader->arg[curarg], buf, count)) { + cleanup_reader(reader); + TS_ERR_MSG("copy_from_user failed on arg\n"); + return -EIO; + } + } + + if (reader->command.command && reader->curarg == reader->command.numargs) { + int ret = trustees_process_command(reader->command, reader->arg, + reader->argsize); + cleanup_reader(reader); + if (ret) return -EIO; + } + + return count; +} diff --git a/security/trustees/funcs.c b/security/trustees/funcs.c new file mode 100755 index 00000000000..fe152d75df2 --- /dev/null +++ b/security/trustees/funcs.c @@ -0,0 +1,812 @@ +/* + * 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. + * + * This code contains the functions for handling the actual trustees data + * and returning the permissions for a given file, etc. + * + * + */ + +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/dcache.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/limits.h> +#include <linux/list.h> +#include <linux/vmalloc.h> +#include <linux/ctype.h> +#include <linux/cred.h> + +#include "internal.h" + +/* + * This is a hash of all the trustee_names currently added. These values + * are hashed on a combination of device/filename. Before reading/writing + * be sure to take care of the locking of trustee_hash_lock. + */ +rwlock_t trustee_hash_lock; +static struct hlist_head *trustee_hash = NULL; + +/* + * This is the deepest level trustee. When calculating filenames, we can + * skip several of the levels in many case since we know it won't be any + * deeper than this. + * + * Kept up to date by calculate_deepest_level + * + * / => 0 + * /test => 1 + * /test/blah => 2 + */ +static int deepest_level = 0; + +/* + * A list of filesystems that need to have their case + * ignored. This is protected by trustee_hash_lock. + */ +static LIST_HEAD(trustee_ic_list); + + +/* The calling method needs to free the buffer created by this function + * This method returns the filename for a dentry. This is, of course, + * relative to the device. The filename can be truncated to be as deep as + * the deepest trustee. The depth returned in d will always be the true + * depth, however. + * + * Args: + * dentry: The dentry we are interested in. + * d: a pointer to the place where the depth can be stored. + * trunc: ok to truncate the name to the longest that needs to be figured out. + */ + +#define FN_CHUNK_SIZE 64 +char *trustees_filename_for_dentry(struct dentry *dentry, int *d, int trunc) +{ + char *buffer = NULL, *tmpbuf = NULL; + int bufsize = FN_CHUNK_SIZE; + char c; + int i, j, k; + int depth = 0; + struct dentry *temp_dentry; + + if (dentry->d_parent == NULL) { + TS_ERR_MSG("d_parent is null\n"); + return NULL; + } + + if (dentry->d_name.name == NULL) { + TS_ERR_MSG("name is null\n"); + return NULL; + } + + buffer = kmalloc(FN_CHUNK_SIZE, GFP_KERNEL); + if (!buffer) { + TS_ERR_MSG("could not allocate filename buffer\n"); + return NULL; + } + + buffer[0] = '/'; + buffer[i = 1] = '\0'; + for (temp_dentry = dentry; !IS_ROOT(temp_dentry); temp_dentry = temp_dentry->d_parent) + depth++; + if (d) *d = depth; + if (deepest_level <= 0) return buffer; + + for (;;) { + if (IS_ROOT(dentry)) + break; + if (depth-- > deepest_level) continue; + + j = i + strlen(dentry->d_name.name); + if ((j + 2) > bufsize) { /* reallocate - won't fit */ + bufsize = (((j + 2) / FN_CHUNK_SIZE) + 1) * FN_CHUNK_SIZE; + tmpbuf = kmalloc(bufsize, GFP_KERNEL); + if (!tmpbuf) { + kfree(buffer); + TS_ERR_MSG + ("Out of memory allocating tmpbuf\n"); + return NULL; + } + memcpy(tmpbuf, buffer, i); + kfree(buffer); + buffer = tmpbuf; + } + /* Throw the name in there backward */ + for (k = 0; dentry->d_name.name[k]; k++) { + buffer[j - 1 - k] = dentry->d_name.name[k]; + } + i = j; + buffer[i++] = '/'; + dentry = dentry->d_parent; + } + buffer[i] = 0; + + /* buffer is backwards, reverse it */ + for (j = 0; j < (i / 2); ++j) { + c = buffer[j]; + buffer[j] = buffer[i - j - 1]; + buffer[i - j - 1] = c; + } + + return buffer; +} + +/** + * Allocate memory using vmalloc and return a duplicate of the passed in string. + * Returns NULL if a problem occurs + */ +static char *vmalloc_strdup(const char *str, size_t len) +{ + char *r; + + if (!str) return NULL; + len = strlen(str); + r = vmalloc(len + 1); + if (!r) return NULL; + memcpy(r, str, len + 1); + + return r; +} + +/* + * Add a filesystem as a ignored-case dev. + */ +static inline void add_ic_dev(u32 dev, char *devname) +{ + char *devname2; + struct trustee_ic *ic; + size_t dev_len; + + dev_len = strlen(devname); + + if (dev_len > PATH_MAX) { + TS_ERR_MSG("devname bad, add_ic_dev ignored.\n"); + return; + } + + if (!dev_len) { + TS_ERR_MSG("No devname specified in add_ic_dev.\n"); + return; + } + + devname2 = vmalloc_strdup(devname, dev_len); + if (!devname2) { + TS_ERR_MSG + ("Seems that we have ran out of memory adding ic dev!\n"); + return; + } + + ic = vmalloc(sizeof(struct trustee_ic)); + if (!ic) { + TS_ERR_MSG + ("Seems that we ran out of memory allocating ic!\n"); + return; + } + + ic->dev = new_decode_dev(dev); + ic->devname = devname2; + + write_lock(&trustee_hash_lock); + list_add(&ic->ic_list, &trustee_ic_list); + write_unlock(&trustee_hash_lock); +} + +/* + * Remove all ignored-case filesystems. + */ +static inline void remove_ic_devs(void) +{ + struct trustee_ic *ic, *temp_ic; + struct list_head temp_ic_list; + + INIT_LIST_HEAD(&temp_ic_list); + list_splice_init(&trustee_ic_list, &temp_ic_list); + + list_for_each_entry_safe(ic, temp_ic, &temp_ic_list, ic_list) { + vfree(ic->devname); + vfree(ic); + } +} + +/* + * This frees all the capsules in a trustee element. + */ +static inline void free_hash_element_list(struct trustee_hash_element *e) +{ + struct trustee_permission_capsule *capsule, *temp; + + list_for_each_entry_safe(capsule, temp, &e->perm_list, perm_list) { + list_del(&capsule->perm_list); + vfree(capsule); + } +} + +/* + * Free a trustee name. This frees the devname and the filename + */ +static inline void free_trustee_name(struct trustee_name *name) +{ + vfree(name->filename); + vfree(name->devname); +} + +/* + * Frees the capsules, and the filenames for a trustee hash element. + * Also marks it as unused in the hash. + */ +static inline void free_hash_element(struct trustee_hash_element *e) +{ + free_hash_element_list(e); + free_trustee_name(&e->name); + vfree(e); +} + +/** + * Copies from src to dest (duplicating the strings in the + * trustee_name structure. Returns zero for unsuccesful. + */ +static int copy_trustee_name(struct trustee_name *dst, struct trustee_name *src) +{ + *dst = *src; + if (dst->filename) { + dst->filename = vmalloc_strdup(src->filename, strlen(src->filename)); + if (!dst->filename) { + TS_ERR_MSG("Ran out of memory duplicating src->filename\n"); + return 0; + } + } + + if (dst->devname) { + dst->devname = vmalloc_strdup(src->devname, strlen(src->devname)); + if (!dst->devname) { + TS_ERR_MSG("Ran out of memory duplicating src->devname\n"); + vfree(dst->filename); + return 0; + } + } + + return 1; +} + + +/* + * hashing function researched by Karl Nelson <kenelson @ ece ucdavis edu> + * and is used in glib. + */ +static inline unsigned int hash_string(const char *s) +{ + unsigned int v = 0; + + while (*s) { + v = (v << 5) - v + tolower(*s); + s++; + } + + return v; +} + +/* + * Return the hash for a device. + */ +static inline unsigned int hash_device(const char *name, dev_t device) +{ + if (MAJOR(device) == 0) { + return hash_string(name); + } + + return new_encode_dev(device); +} + +/* + * Return the hash for a file. This is a combination of the + * hash of the filename and the hash for the device. + */ +static inline unsigned int hash(const struct trustee_name *name) +{ + return hash_string(name->filename) ^ + hash_device(name->devname, name->dev); +} + +/* + * Return the slot in the trustees_hash where a trustee is located + */ +static inline unsigned int hash_slot(const struct trustee_name *name) +{ + return hash(name) % trustee_hash_size; +} + +/* + * Compare two devices. Return 1 if they are equal otherwise return 0 + */ +static inline int trustee_dev_cmp(dev_t dev1, dev_t dev2, const char *devname1, + const char *devname2) +{ + if ((MAJOR(dev1) == 0) && (MAJOR(dev2) == 0)) + return (strcmp(devname1, devname2) == 0); + else if ((MAJOR(dev1) != 0) && (MAJOR(dev2) != 0)) + return (dev1 == dev2); + return 0; +} + +/* + * Compare two trustee_name's. Returns 1 if they are are equal + * otherwise return 0 + */ +static inline int trustee_name_cmp(const struct trustee_name *n1, + const struct trustee_name *n2, + unsigned ignore_case) +{ + if (trustee_dev_cmp(n1->dev, n2->dev, n1->devname, n2->devname)) + return ignore_case ? + (strnicmp(n1->filename, n2->filename, PATH_MAX) == 0) : + (strcmp(n1->filename, n2->filename) == 0); + return 0; +} + +/* + * Calculate the deepest level. + */ +static inline void calculate_deepest_level(const struct trustee_name *name) +{ + const char *fn = name->filename; + const char *x; + int level = 0; + + for (x = fn; *x; ++x) { + if (*x == '/') + ++level; + } + + /* If it is the root, it should have + * a level of 0. + */ + if (x == (fn + 1)) level = 0; + + if (level > deepest_level) deepest_level = level; +} + +/* + * Return the trustee element for a name. + * This should be called with a lock on the trustee_hash (which should + * not be released until you are done with the returned hash_element)! + */ +static struct trustee_hash_element *get_trustee_for_name(const struct trustee_name *name, + unsigned ignore_case) +{ + struct trustee_hash_element *item = NULL; + struct hlist_node *iter = NULL; + + hlist_for_each_entry(item, iter, &trustee_hash[hash_slot(name)], hash_list) { + if (trustee_name_cmp(&item->name, name, ignore_case)) + return item; + } + + return NULL; +} + +/** + * Add a new blank trustee to the hash. + * + * If this returns zero, then the adding failed and name should be freed + * (assuming must_copy is 0), otherwise assume we used its memory. + */ +static unsigned add_trustee(struct trustee_name *name, int must_copy) { + struct trustee_name newname; + struct trustee_name rootname; + unsigned is_root = 1; + unsigned r = 0; + struct trustee_hash_element *new; + struct trustee_hash_element *root; + + if (!name->filename || !name->filename[0]) goto err0; + + if (!copy_trustee_name(&rootname, name)) goto err0; + rootname.filename[1] = '\0'; + + if (strlen(name->filename) > 1 && strcmp(name->filename, "/")) { + add_trustee(&rootname, 1); + is_root = 0; + } + + if (must_copy) { + if (!copy_trustee_name(&newname, name)) goto err1; + } else { + newname = *name; + } + + new = vmalloc(sizeof(struct trustee_hash_element)); + if (!new) goto err2; + new->name = newname; + INIT_HLIST_NODE(&new->hash_list); + INIT_LIST_HEAD(&new->perm_list); + INIT_LIST_HEAD(&new->device_list); + + write_lock(&trustee_hash_lock); + if (get_trustee_for_name(&newname, 0)) goto err3; + + if (is_root) { + root = NULL; + } else if (!(root = get_trustee_for_name(&rootname, 0))) { + TS_ERR_MSG("Root trustee disappeared on us!\n"); + goto err3; + } + hlist_add_head(&new->hash_list, &trustee_hash[hash_slot(name)]); + if (!is_root) { + list_add_tail(&new->device_list, &root->device_list); + } + calculate_deepest_level(&newname); + TS_DEBUG_MSG("Created '%s' trustee\n", newname.filename); + r = 1; +err3: + write_unlock(&trustee_hash_lock); + if (!r) vfree(new); +err2: + if (must_copy && !r) free_trustee_name(&newname); +err1: + free_trustee_name(&rootname); +err0: + return r; +} + +/** + * Add a permissions module to the trustee specified by name. + */ +static unsigned add_trustee_perm + (struct trustee_name *name, struct trustee_permission acl) +{ + struct trustee_hash_element *r = NULL; + struct trustee_permission_capsule *capsule; + + capsule = vmalloc(sizeof(struct trustee_permission_capsule)); + if (!capsule) { + TS_ERR_MSG + ("Can not allocate memory for trustee capsule\n"); + return 0; + } + capsule->permission = acl; + + write_lock(&trustee_hash_lock); + r = get_trustee_for_name(name, 0); + + if (r) { + list_add_tail(&capsule->perm_list, &r->perm_list); + write_unlock(&trustee_hash_lock); + TS_DEBUG_MSG("Added permission capsule to '%s' trustee\n", name->filename); + return 1; + } + write_unlock(&trustee_hash_lock); + TS_ERR_MSG("trustee disappeared under us while trying to add perms\n"); + vfree(capsule); + + return 0; +} + +/* + * Get the mask for a trustee name. + * This should be called with a lock on the trustee_hash (which should + * not be released until you are done with the returned hash_element)! + */ +static int get_trustee_mask_for_name(struct trustee_name *name, + int oldmask, int height, + struct trustee_hash_element **element, + unsigned ignore_case) +{ + struct trustee_hash_element *e; + int m; + struct trustee_permission_capsule *l; + int appl; + e = get_trustee_for_name(name, ignore_case); + if (!e) { + return oldmask; + } + list_for_each_entry(l, &e->perm_list, perm_list) { + if ((height < 0) + && (l->permission.mask & TRUSTEE_ONE_LEVEL_MASK)) + continue; + if (element) { + *element = e; + element = NULL; + } + appl = ((!(l->permission.mask & TRUSTEE_IS_GROUP_MASK)) + && (current_fsuid() == l->permission.u.uid)) + || (((l->permission.mask & TRUSTEE_IS_GROUP_MASK)) + && (in_group_p(l->permission.u.gid))) + || (l->permission.mask & TRUSTEE_ALL_MASK); + if (l->permission.mask & TRUSTEE_NOT_MASK) + appl = !appl; + + if (!appl) + continue; + + m = l->permission.mask & TRUSTEE_ACL_MASK; + + if (l->permission.mask & TRUSTEE_ALLOW_DENY_MASK) + m <<= TRUSTEE_NUM_ACL_BITS; + + oldmask = + l->permission. + mask & TRUSTEE_CLEAR_SET_MASK ? (oldmask & (~m)) + : (oldmask | m); + } + + return oldmask; +} + +/* + * Return non-zero if a trustee exists in a subpath. + * + * WARNING! + * This function requires that you lock/unlock the trustees_hash_lock + */ +int trustee_has_child(struct vfsmount *mnt, char *file_name) +{ + struct trustee_name trustee_name; + char tempchar; + unsigned ignore_case = 0; + struct trustee_hash_element *root; + size_t len; + struct trustee_ic *iter; + struct trustee_hash_element *r; + + if (!file_name || !*file_name) return 0; + + trustee_name.dev = mnt->mnt_sb->s_dev; + trustee_name.devname = mnt->mnt_devname; + trustee_name.filename = file_name; + + list_for_each_entry(iter, &trustee_ic_list, ic_list) { + if (trustee_dev_cmp + (iter->dev, trustee_name.dev, iter->devname, + trustee_name.devname)) { + ignore_case = 1; + break; + } + } + + tempchar = file_name[1]; + file_name[1] = '\0'; + + root = get_trustee_for_name(&trustee_name, ignore_case); + if (!root) return 0; + + file_name[1] = tempchar; + + len = strlen(file_name); + + list_for_each_entry(r, &root->device_list, device_list) { + size_t this_len = strlen(r->name.filename); + if (this_len <= len) continue; + if (!strncmp(file_name, r->name.filename, len) && + r->name.filename[len] != '\0') + return 1; + } + + return 0; +} + +/* + * Return the mask for a file. + * + * WARNING! + * This function requires that you lock/unlock the trustees_hash_lock + */ +int trustee_perm(struct path *path, + char *file_name, int unix_ret, int depth, int is_dir, + struct trustee_hash_element **deepest) +{ + static char dbl_nul_slash[3] = { '/', '\0', '\0' }; + int oldmask = trustee_default_acl; + int height = 0; + char *filecount; + char c; + struct trustee_name trustee_name; + struct trustee_ic *iter; + unsigned ignore_case = 0; + + trustee_name.dev = path->mnt->mnt_sb->s_dev; + trustee_name.devname = path->mnt->mnt_devname; + trustee_name.filename = file_name; + + list_for_each_entry(iter, &trustee_ic_list, ic_list) { + if (trustee_dev_cmp + (iter->dev, trustee_name.dev, iter->devname, + trustee_name.devname)) { + ignore_case = 1; + break; + } + } + + if (deepest) *deepest = NULL; + + filecount = file_name + 1; + /* Try to handle the unlikely case where the string will be '/' + * out here to simplify the logic inside the loop. We do this + * by giving it a string with two nul byte terminators so that it + * will gracefully (and safely) make it through the loop below. + */ + if (*filecount == '\0') { + file_name = dbl_nul_slash; + filecount = file_name + 1; + } + do { + c = *filecount; + *filecount = 0; + oldmask = + get_trustee_mask_for_name(&trustee_name, oldmask, + height - depth + !is_dir, + deepest, ignore_case); + height++; + *filecount = c; + ++filecount; + while ((*filecount) && (*filecount != '/')) filecount++; + + } while(*filecount); + + return oldmask; +} + +/* Clear out the hash of trustees and release the hash itself. + * Also gets rid of the ignore-case list + */ +static void trustees_clear_all(void) +{ + struct trustee_hash_element *item = NULL; + struct hlist_node *iter, *temp = NULL; + unsigned i; + write_lock(&trustee_hash_lock); + + for (i = 0; i < trustee_hash_size; i++) { + hlist_for_each_entry_safe(item, iter, temp, &trustee_hash[i], hash_list) { + free_hash_element(item); + } + INIT_HLIST_HEAD(&trustee_hash[i]); + } + + deepest_level = 0; + + remove_ic_devs(); + + write_unlock(&trustee_hash_lock); +} + +/* + * Initialize globals + */ +int trustees_funcs_init_globals(void) +{ + unsigned int iter; + + if (trustee_hash_size <= 0) + return 1; + + rwlock_init(&trustee_hash_lock); + + trustee_hash = vmalloc(sizeof(*trustee_hash) * trustee_hash_size); + if (!trustee_hash) + return 1; + + for (iter = 0; iter < trustee_hash_size; iter++) + INIT_HLIST_HEAD(trustee_hash + iter); + + return 0; +} + +/* + * Clear globals + */ +int trustees_funcs_cleanup_globals(void) +{ + trustees_clear_all(); + + vfree(trustee_hash); + + return 0; +} + +/* + * Prepare a trustee name from a passed in trustee name. + */ +static int prepare_trustee_name(u32 device, char *devname, char *filename, struct trustee_name *name) +{ + size_t devl, filel; + char *devb = NULL, *fileb = NULL; + + if ((!name)) + return 0; + + filel = strlen(filename); + devl = strlen(devname); + + if (devl > PATH_MAX) { + TS_ERR_MSG("device name bad, command ignored.\n"); + return 0; + } + if (filel > PATH_MAX) { + TS_ERR_MSG("file name bad, command ignored.\n"); + return 0; + } + + devb = vmalloc_strdup(devname, devl); + if (!devb) { + TS_ERR_MSG("Couldn't allocate mem for devb.\n"); + return 0; + } + + fileb = vmalloc_strdup(filename, filel); + if (!fileb) { + TS_ERR_MSG("Couldn't allocate mem for fileb.\n"); + return 0; + } + + name->devname = devb; + name->filename = fileb; + + name->dev = new_decode_dev(device); + + return 1; +} + +/* + * Process a user command + */ +extern int trustees_process_command(struct trustee_command command, + void **arg, + size_t *argsize) +{ + int r = -ENOSYS; + int must_free = 0; + struct trustee_name name; + + if ((current_euid() != 0) && !capable(CAP_SYS_ADMIN)) { + r = -EACCES; + return r; + } + + switch (command.command) { + case TRUSTEE_COMMAND_MAKE_IC: + if (command.numargs != 2 || + argsize[1] != sizeof(u32)) goto unlk; + add_ic_dev(*(u32 *)arg[1], arg[0]); + r = 0; + break; + case TRUSTEE_COMMAND_REMOVE_ALL: + if (command.numargs != 0) goto unlk; + trustees_clear_all(); + r = 0; + break; + case TRUSTEE_COMMAND_ADD: + if (command.numargs != 4 || + argsize[3] != sizeof(u32) || + argsize[1] != sizeof(struct trustee_permission)) + goto unlk; + if (!prepare_trustee_name(*(u32 *)arg[3], arg[2], arg[0], &name)) { + r = -ENOMEM; + goto unlk; + } + if (!add_trustee(&name, 0)) { + must_free = 1; + } + if (!add_trustee_perm(&name, *(struct trustee_permission *)arg[1])) + r = -ENOMEM; + else + r = 0; + + if (must_free) free_trustee_name(&name); + break; + } + unlk: + + return r; +} diff --git a/security/trustees/init.c b/security/trustees/init.c new file mode 100755 index 00000000000..38eb964c382 --- /dev/null +++ b/security/trustees/init.c @@ -0,0 +1,57 @@ +/* + * 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. + * + * Module initialization and cleanup + * + * History: + * 2002-12-16 trustees 2.10 released by Vyacheslav Zavadsky + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/security.h> +#include <linux/capability.h> + +#include "internal.h" + +unsigned int trustee_hash_size = 256; + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Trustees ACL System"); +MODULE_AUTHOR("Vyacheslav Zavadsky and Andrew E. Ruder <aeruder@ksu.edu>"); +MODULE_VERSION("2.11"); + +MODULE_PARM_DESC(hash_size, "Trustees hash size"); +module_param_named(hash_size, trustee_hash_size, uint, 0444); + + +static int __init trustees_init(void) +{ + if (trustees_funcs_init_globals() != 0) { + return -EINVAL; + } + + if (trustees_init_fs() != 0) { + trustees_funcs_cleanup_globals(); + return -EINVAL; + } + + if (trustees_init_security() != 0) { + trustees_deinit_fs(); + trustees_funcs_cleanup_globals(); + return -EINVAL; + } + + return 0; +} + +fs_initcall(trustees_init); diff --git a/security/trustees/internal.h b/security/trustees/internal.h new file mode 100755 index 00000000000..f7203a05fc1 --- /dev/null +++ b/security/trustees/internal.h @@ -0,0 +1,101 @@ +/* + * 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. + * + * Private methods and definitions used only within the module. + * + */ + +#ifndef _LINUX_TRUSTEES_H +#define _LINUX_TRUSTEES_H +#include <linux/types.h> +#include <linux/dcache.h> +#include <linux/kdev_t.h> +#include <linux/list.h> +#include <linux/version.h> +#include <linux/trustees.h> +#include <linux/path.h> + +#define TRUSTEE_DEFAULT_MASK TRUSTEE_USE_UNIX_MASK + +struct trustee_ic { + dev_t dev; + char *devname; /* ONLY if MAJOR(dev)==0 */ + struct list_head ic_list; +}; + +struct trustee_name { + dev_t dev; + char *filename; + const char *devname; /* ONLY if MAJOR(dev)==0 */ +}; + +struct trustee_permission_capsule { + struct list_head perm_list; + struct trustee_permission permission; +}; + +/* For the usage field */ +#define TRUSTEE_HASH_ELEMENT_USED 2 +#define TRUSTEE_HASH_ELEMENT_DELETED 1 +#define TRUSTEE_HASH_ELEMENT_NOTUSED 0 + +struct trustee_hash_element { + struct trustee_name name; + struct list_head perm_list; + struct hlist_node hash_list; + struct list_head device_list; +}; + +extern char *trustees_filename_for_dentry(struct dentry *dentry, int *d, int trunc); + +extern int trustees_funcs_init_globals(void); +extern int trustees_funcs_cleanup_globals(void); + +int trustee_has_child(struct vfsmount *mnt, char *file_name); +int trustee_perm(struct path *path, + char *file_name, int unix_ret, int depth, int is_dir, + struct trustee_hash_element **deepest); + +extern int trustees_process_command(struct trustee_command command, + void **arg, size_t *argsize); + +extern unsigned int trustee_hash_size; +extern rwlock_t trustee_hash_lock; + +#define TRUSTEE_INITIAL_NAME_BUFFER 256 +#define TRUSTEE_HASDEVNAME(TNAME) (MAJOR((TNAME).dev)==0) + +#define TS_ERR_MSG(...) printk(KERN_ERR "Trustees: " __VA_ARGS__) + +#ifdef TRUSTEES_DEBUG +#define TS_DEBUG_MSG(...) printk(KERN_ERR "Trustees: " __VA_ARGS__) +#else +#define TS_DEBUG_MSG(...) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#define NAMESPACE_SEM(_ns) (namespace_sem) +#else +#define NAMESPACE_SEM(_ns) ((_ns)->sem) +#endif + +/* + * Magic number! + * + * FIXME: Do I just make this up or is there some system for coming + * up with magic numbers? + */ +#define TRUSTEES_MAGIC 0x32236975 + +int trustees_init_fs(void); +void trustees_deinit_fs(void); + +int trustees_init_security(void); +#endif /* _LINUX_TRUSTEES_H */ 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; +} |