lkml.org 
[lkml]   [2007]   [Nov]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[TOMOYO #5 09/18] File access control functions.
    TOMOYO Linux checks permission in
    open/creat/unlink/truncate/ftruncate/mknod/mkdir/
    rmdir/symlink/link/rename/uselib/sysctl .

    Each permission can be automatically accumulated into
    the policy of each domain using 'learning mode'.

    Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
    Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
    security/tomoyo/file.c | 1471 +++++++++++++++++++++++++++++++++++++++++++++++++
    1 file changed, 1471 insertions(+)

    --- /dev/null 1970-01-01 00:00:00.000000000 +0000
    +++ linux-2.6-mm/security/tomoyo/file.c 2007-11-14 15:15:44.000000000 +0900
    @@ -0,0 +1,1471 @@
    +/*
    + * security/tomoyo/file.c
    + *
    + * File access control functions for TOMOYO Linux.
    + */
    +
    +#include "tomoyo.h"
    +#include "realpath.h"
    +
    +#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
    +
    +/************************* VARIABLES *************************/
    +
    +/***** The structure for globally readable files. *****/
    +
    +struct globally_readable_file_entry {
    + struct list_head list;
    + const struct path_info *filename;
    + bool is_deleted;
    +};
    +
    +/***** The structure for filename patterns. *****/
    +
    +struct pattern_entry {
    + struct list_head list;
    + const struct path_info *pattern;
    + bool is_deleted;
    +};
    +
    +/***** The structure for non-rewritable-by-default file patterns. *****/
    +
    +struct no_rewrite_entry {
    + struct list_head list;
    + const struct path_info *pattern;
    + bool is_deleted;
    +};
    +
    +/***** The structure for detailed write operations. *****/
    +
    +static struct {
    + const char *keyword;
    + const int paths;
    +} acl_type_array[] = {
    + { "create", 1 }, /* TMY_TYPE_CREATE_ACL */
    + { "unlink", 1 }, /* TMY_TYPE_UNLINK_ACL */
    + { "mkdir", 1 }, /* TMY_TYPE_MKDIR_ACL */
    + { "rmdir", 1 }, /* TMY_TYPE_RMDIR_ACL */
    + { "mkfifo", 1 }, /* TMY_TYPE_MKFIFO_ACL */
    + { "mksock", 1 }, /* TMY_TYPE_MKSOCK_ACL */
    + { "mkblock", 1 }, /* TMY_TYPE_MKBLOCK_ACL */
    + { "mkchar", 1 }, /* TMY_TYPE_MKCHAR_ACL */
    + { "truncate", 1 }, /* TMY_TYPE_TRUNCATE_ACL */
    + { "symlink", 1 }, /* TMY_TYPE_SYMLINK_ACL */
    + { "link", 2 }, /* TMY_TYPE_LINK_ACL */
    + { "rename", 2 }, /* TMY_TYPE_RENAME_ACL */
    + { "rewrite", 1 }, /* TMY_TYPE_REWRITE_ACL */
    + { NULL, 0 }
    +};
    +
    +/************************* UTILITY FUNCTIONS *************************/
    +
    +/**
    + * tmy_acltype2keyword - get keyword from access control index.
    + * @acl_type: index number.
    + *
    + * Returns keyword that corresponds with @acl_type .
    + */
    +const char *tmy_acltype2keyword(const unsigned int acl_type)
    +{
    + return (acl_type < ARRAY_SIZE(acl_type_array))
    + ? acl_type_array[acl_type].keyword : NULL;
    +}
    +
    +/**
    + * tmy_acltype2paths - get number of arguments from access control index.
    + * @acl_type: index number.
    + *
    + * Returns number of arguments that corresponds with @acl_type .
    + */
    +int tmy_acltype2paths(const unsigned int acl_type)
    +{
    + return (acl_type < ARRAY_SIZE(acl_type_array))
    + ? acl_type_array[acl_type].paths : 0;
    +}
    +
    +static int tmy_strendswith(const char *name, const char *tail)
    +{
    + int len;
    +
    + if (!name || !tail)
    + return 0;
    +
    + len = strlen(name) - strlen(tail);
    + return len >= 0 && strcmp(name + len, tail) == 0;
    +}
    +
    +static struct path_info *tmy_get_path(struct dentry *dentry,
    + struct vfsmount *mnt)
    +{
    + /* sizeof(struct path_info_with_data) <= PAGE_SIZE */
    + struct path_info_with_data {
    + /* Keep this first, this pointer is passed to tmy_free(). */
    + struct path_info head;
    + char bariier1[16];
    + char body[TMY_MAX_PATHNAME_LEN];
    + char barrier2[16];
    + } *buf = tmy_alloc(sizeof(*buf));
    +
    + if (buf) {
    + int error = tmy_realpath_dentry2(dentry,
    + mnt,
    + buf->body,
    + sizeof(buf->body) - 1);
    +
    + if (error == 0) {
    + buf->head.name = buf->body;
    + tmy_fill_path_info(&buf->head);
    + return &buf->head;
    + }
    +
    + tmy_free(buf);
    + buf = NULL;
    + printk(KERN_INFO "tmy_realpath_dentry = %d\n", error);
    + }
    +
    + return NULL;
    +}
    +
    +/************************* PROTOTYPES *************************/
    +
    +static int tmy_add_double_write_acl(const u8 type,
    + const char *filename1,
    + const char *filename2,
    + struct domain_info * const domain,
    + const struct condition_list *cond,
    + const bool is_delete);
    +static int tmy_add_single_write_acl(const u8 type,
    + const char *filename,
    + struct domain_info * const domain,
    + const struct condition_list *cond,
    + const bool is_delete);
    +
    +/************************* AUDIT FUNCTIONS *************************/
    +
    +static int tmy_audit_file_log(const struct path_info *filename,
    + const u8 perm,
    + const bool is_granted,
    + const u8 profile,
    + const unsigned int mode)
    +{
    + char *buf;
    + int len;
    +
    + if (is_granted) {
    + if (!tmy_audit_grant())
    + return 0;
    + } else {
    + if (!tmy_audit_reject())
    + return 0;
    + }
    +
    + len = filename->total_len + 8;
    + buf = tmy_init_audit_log(&len, profile, mode);
    +
    + if (!buf)
    + return -ENOMEM;
    +
    + snprintf(buf + strlen(buf),
    + len - strlen(buf) - 1,
    + "%d %s\n",
    + perm,
    + filename->name);
    +
    + return tmy_write_audit_log(buf, is_granted);
    +}
    +
    +static int tmy_audit_write_log(const char *operation,
    + const struct path_info *filename1,
    + const struct path_info *filename2,
    + const bool is_granted,
    + const u8 profile,
    + const unsigned int mode)
    +{
    + char *buf;
    + int len;
    +
    + if (is_granted) {
    + if (!tmy_audit_grant())
    + return 0;
    + } else {
    + if (!tmy_audit_reject())
    + return 0;
    + }
    +
    + len = strlen(operation) +
    + filename1->total_len +
    + (filename2 ? filename2->total_len : 0)
    + + 16;
    +
    + buf = tmy_init_audit_log(&len, profile, mode);
    + if (!buf)
    + return -ENOMEM;
    +
    + snprintf(buf + strlen(buf), len - strlen(buf) - 1,
    + "allow_%s %s %s\n",
    + operation, filename1->name, filename2 ? filename2->name : "");
    +
    + return tmy_write_audit_log(buf, is_granted);
    +}
    +
    +/********************** GLOBALLY READABLE FILE HANDLER **********************/
    +
    +static LIST_HEAD(globally_readable_list);
    +
    +static int tmy_add_globally_readable_entry(const char *filename,
    + const bool is_delete)
    +{
    + struct globally_readable_file_entry *new_entry;
    + struct globally_readable_file_entry *ptr;
    + static DEFINE_MUTEX(mutex);
    + const struct path_info *saved;
    + int error = -ENOMEM;
    +
    + if (!tmy_correct_path(filename, 1, -1, -1, __FUNCTION__))
    + return -EINVAL; /* No patterns allowed. */
    + saved = tmy_save_name(filename);
    + if (!saved)
    + return -ENOMEM;
    +
    + mutex_lock(&mutex);
    +
    + list_for_each_entry(ptr, &globally_readable_list, list) {
    + if (ptr->filename == saved) {
    + ptr->is_deleted = is_delete;
    + error = 0;
    + goto out;
    + }
    + }
    + if (is_delete) {
    + error = -ENOENT;
    + goto out;
    + }
    +
    + new_entry = tmy_alloc_element(sizeof(*new_entry));
    + if (!new_entry)
    + goto out;
    +
    + new_entry->filename = saved;
    + list_add_tail_mb(&new_entry->list, &globally_readable_list);
    + error = 0;
    +
    +out: ;
    + mutex_unlock(&mutex);
    +
    + return error;
    +}
    +
    +static int tmy_globally_readable(const struct path_info *filename)
    +{
    + struct globally_readable_file_entry *ptr;
    +
    + list_for_each_entry(ptr, &globally_readable_list, list) {
    + if (!ptr->is_deleted &&
    + !tmy_pathcmp(filename, ptr->filename))
    + return 1;
    + }
    +
    + return 0;
    +}
    +
    +/**
    + * tmy_add_globally_readable_policy - add or delete globally readable policy.
    + * @filename: pointer to filename to add ore remove.
    + * @is_delete: is this delete request?
    + *
    + * Returns zero on success.
    + * Returns nonzero on failure.
    + */
    +int tmy_add_globally_readable_policy(char *filename, const bool is_delete)
    +{
    + return tmy_add_globally_readable_entry(filename, is_delete);
    +}
    +
    +/**
    + * tmy_read_globally_readable_policy - read globally readable policy.
    + * @head: pointer to "struct io_buffer".
    + *
    + * Returns nonzero if reading incomplete.
    + * Returns zero otherwise.
    + */
    +int tmy_read_globally_readable_policy(struct io_buffer *head)
    +{
    + struct list_head *pos;
    + list_for_each_cookie(pos, head->read_var2, &globally_readable_list) {
    + struct globally_readable_file_entry *ptr;
    + ptr = list_entry(pos, struct globally_readable_file_entry,
    + list);
    + if (ptr->is_deleted)
    + continue;
    + if (tmy_io_printf(head, TMY_ALLOW_READ "%s\n",
    + ptr->filename->name))
    + return -ENOMEM;
    + }
    + return 0;
    +}
    +
    +/************************* FILE GROUP HANDLER *************************/
    +
    +static LIST_HEAD(path_group_list);
    +
    +static int tmy_add_group_entry(const char *group_name,
    + const char *member_name,
    + const bool is_delete)
    +{
    + static DEFINE_MUTEX(mutex);
    + struct path_group_entry *new_group;
    + struct path_group_entry *group;
    + struct path_group_member *new_member;
    + struct path_group_member *member;
    + const struct path_info *saved_group;
    + const struct path_info *saved_member;
    + int error = -ENOMEM;
    + bool found = 0;
    +
    + if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
    + !group_name[0] ||
    + !tmy_correct_path(member_name, 0, 0, 0, __FUNCTION__) ||
    + !member_name[0])
    + return -EINVAL;
    +
    + saved_group = tmy_save_name(group_name);
    + saved_member = tmy_save_name(member_name);
    +
    + if (!saved_group || !saved_member)
    + return -ENOMEM;
    +
    + mutex_lock(&mutex);
    + list_for_each_entry(group, &path_group_list, list) {
    + if (saved_group != group->group_name)
    + continue;
    + list_for_each_entry(member, &group->path_group_member_list,
    + list) {
    + if (member->member_name == saved_member) {
    + member->is_deleted = is_delete;
    + error = 0;
    + goto out;
    + }
    + }
    + found = 1;
    + break;
    + }
    +
    + if (is_delete) {
    + error = -ENOENT;
    + goto out;
    + }
    +
    + if (!found) {
    + new_group = tmy_alloc_element(sizeof(*new_group));
    + if (!new_group)
    + goto out;
    + INIT_LIST_HEAD(&new_group->path_group_member_list);
    + new_group->group_name = saved_group;
    + list_add_tail_mb(&new_group->list, &path_group_list);
    + group = new_group;
    + }
    +
    + new_member = tmy_alloc_element(sizeof(*new_member));
    + if (!new_member)
    + goto out;
    + new_member->member_name = saved_member;
    + list_add_tail_mb(&new_member->list, &group->path_group_member_list);
    + error = 0;
    +out: ;
    + mutex_unlock(&mutex);
    +
    + return error;
    +}
    +
    +/**
    + * tmy_add_group_policy - add or delete path group policy.
    + * @data: a line to parse.
    + * @is_delete: is this delete request?
    + *
    + * Returns zero on success.
    + * Returns nonzero on failure.
    + */
    +int tmy_add_group_policy(char *data, const bool is_delete)
    +{
    + char *cp = strchr(data, ' ');
    +
    + if (!cp)
    + return -EINVAL;
    + *cp++ = '\0';
    + return tmy_add_group_entry(data, cp, is_delete);
    +}
    +
    +static struct path_group_entry *tmy_new_path_group(const char *group_name)
    +{
    + int i;
    + struct path_group_entry *group;
    +
    + for (i = 0; i <= 1; i++) {
    + list_for_each_entry(group, &path_group_list, list) {
    + if (strcmp(group_name, group->group_name->name) == 0)
    + return group;
    + }
    +
    + if (i == 0) {
    + /*
    + * Add a dummy entry to create new path group
    + * and delete that entry.
    + */
    + tmy_add_group_entry(group_name, "/", 0);
    + tmy_add_group_entry(group_name, "/", 1);
    + }
    + }
    +
    + return NULL;
    +}
    +
    +static int tmy_path_match_group(const struct path_info *pathname,
    + const struct path_group_entry *group,
    + const int may_use_pattern)
    +{
    + struct path_group_member *member;
    + list_for_each_entry(member, &group->path_group_member_list, list) {
    + if (member->is_deleted)
    + continue;
    + if (!member->member_name->is_patterned) {
    + if (!tmy_pathcmp(pathname, member->member_name))
    + return 1;
    + } else if (may_use_pattern) {
    + if (tmy_path_match(pathname, member->member_name))
    + return 1;
    + }
    + }
    +
    + return 0;
    +}
    +
    +/**
    + * tmy_read_path_group_policy - read path group policy.
    + * @head: pointer to "struct io_buffer".
    + *
    + * Returns nonzero if reading incomplete.
    + * Returns zero otherwise.
    + */
    +int tmy_read_path_group_policy(struct io_buffer *head)
    +{
    + struct list_head *gpos;
    + struct list_head *mpos;
    + list_for_each_cookie(gpos, head->read_var1, &path_group_list) {
    + struct path_group_entry *group;
    + group = list_entry(gpos, struct path_group_entry, list);
    + list_for_each_cookie(mpos, head->read_var2,
    + &group->path_group_member_list) {
    + struct path_group_member *member;
    + member = list_entry(mpos, struct path_group_member,
    + list);
    + if (member->is_deleted)
    + continue;
    + if (tmy_io_printf(head,
    + TMY_PATH_GROUP "%s %s\n",
    + group->group_name->name,
    + member->member_name->name))
    + return -ENOMEM;
    + }
    + }
    + return 0;
    +}
    +
    +/************************* FILE PATTERN HANDLER *************************/
    +
    +static LIST_HEAD(pattern_list);
    +
    +static int tmy_add_pattern_entry(const char *pattern, const bool is_delete)
    +{
    + struct pattern_entry *new_entry;
    + struct pattern_entry *ptr;
    + static DEFINE_MUTEX(mutex);
    + const struct path_info *saved;
    + int error = -ENOMEM;
    +
    + if (!tmy_correct_path(pattern, 0, 1, 0, __FUNCTION__))
    + return -EINVAL;
    +
    + saved = tmy_save_name(pattern);
    + if (!saved)
    + return -ENOMEM;
    +
    + mutex_lock(&mutex);
    +
    + list_for_each_entry(ptr, &pattern_list, list) {
    + if (saved == ptr->pattern) {
    + ptr->is_deleted = is_delete;
    + error = 0;
    + goto out;
    + }
    + }
    +
    + if (is_delete) {
    + error = -ENOENT;
    + goto out;
    + }
    +
    + new_entry = tmy_alloc_element(sizeof(*new_entry));
    +
    + if (!new_entry)
    + goto out;
    + new_entry->pattern = saved;
    + list_add_tail_mb(&new_entry->list, &pattern_list);
    + error = 0;
    +out: ;
    + mutex_unlock(&mutex);
    + return error;
    +}
    +
    +static const struct path_info *tmy_get_pattern(const struct path_info *filename)
    +{
    + struct pattern_entry *ptr;
    + const struct path_info *pattern = NULL;
    +
    + list_for_each_entry(ptr, &pattern_list, list) {
    + if (ptr->is_deleted)
    + continue;
    + if (!tmy_path_match(filename, ptr->pattern))
    + continue;
    + pattern = ptr->pattern;
    + if (!tmy_strendswith(pattern->name, "/\\*"))
    + break;
    + }
    +
    + if (pattern)
    + filename = pattern;
    +
    + return filename;
    +}
    +
    +/**
    + * tmy_add_pattern_policy - add or delete file pattern policy.
    + * @pattern: pointer to file pattern entry.
    + * @is_delete: is this delete request?
    + *
    + * Returns zero on success.
    + * Returns nonzero on failure.
    + */
    +int tmy_add_pattern_policy(char *pattern, const bool is_delete)
    +{
    + return tmy_add_pattern_entry(pattern, is_delete);
    +}
    +
    +/**
    + * tmy_read_pattern_policy - read file pattern policy.
    + * @head: pointer to "struct io_buffer".
    + *
    + * Returns nonzero if reading incomplete.
    + * Returns zero otherwise.
    + */
    +int tmy_read_pattern_policy(struct io_buffer *head)
    +{
    + struct list_head *pos;
    + list_for_each_cookie(pos, head->read_var2, &pattern_list) {
    + struct pattern_entry *ptr;
    + ptr = list_entry(pos, struct pattern_entry, list);
    + if (ptr->is_deleted)
    + continue;
    + if (tmy_io_printf(head, TMY_FILE_PATTERN "%s\n",
    + ptr->pattern->name))
    + return -ENOMEM;
    + }
    + return 0;
    +}
    +
    +/*********************** NON REWRITABLE FILE HANDLER ***********************/
    +
    +static LIST_HEAD(no_rewrite_list);
    +
    +static int tmy_add_no_rewrite_entry(const char *pattern, const bool is_delete)
    +{
    + struct no_rewrite_entry *new_entry;
    + struct no_rewrite_entry *ptr;
    + static DEFINE_MUTEX(mutex);
    + const struct path_info *saved;
    + int error = -ENOMEM;
    +
    + if (!tmy_correct_path(pattern, 0, 0, 0, __FUNCTION__))
    + return -EINVAL;
    + saved = tmy_save_name(pattern);
    + if (!saved)
    + return -ENOMEM;
    +
    + mutex_lock(&mutex);
    + list_for_each_entry(ptr, &no_rewrite_list, list) {
    + if (ptr->pattern == saved) {
    + ptr->is_deleted = is_delete;
    + error = 0;
    + goto out;
    + }
    + }
    +
    + if (is_delete) {
    + error = -ENOENT;
    + goto out;
    + }
    +
    + new_entry = tmy_alloc_element(sizeof(*new_entry));
    + if (!new_entry)
    + goto out;
    +
    + new_entry->pattern = saved;
    + list_add_tail_mb(&new_entry->list, &no_rewrite_list);
    + error = 0;
    +out: ;
    + mutex_unlock(&mutex);
    +
    + return error;
    +}
    +
    +static int tmy_is_no_rewrite_file(const struct path_info *filename)
    +{
    + struct no_rewrite_entry *ptr;
    +
    + list_for_each_entry(ptr, &no_rewrite_list, list) {
    + if (ptr->is_deleted)
    + continue;
    + if (!tmy_path_match(filename, ptr->pattern))
    + continue;
    + return 1;
    + }
    +
    + return 0;
    +}
    +
    +/**
    + * tmy_add_no_rewrite_policy - add or delete no-rewrite policy.
    + * @pattern: pointer to no-rewrite entry.
    + * @is_delete: is this delete request?
    + *
    + * Returns zero on success.
    + * Returns nonzero on failure.
    + */
    +int tmy_add_no_rewrite_policy(char *pattern, const bool is_delete)
    +{
    + return tmy_add_no_rewrite_entry(pattern, is_delete);
    +}
    +
    +/**
    + * tmy_read_no_rewrite_policy - read no-rewrite policy.
    + * @head: pointer to "struct io_buffer".
    + *
    + * Returns nonzero if reading incomplete.
    + * Returns zero otherwise.
    + */
    +int tmy_read_no_rewrite_policy(struct io_buffer *head)
    +{
    + struct list_head *pos;
    + list_for_each_cookie(pos, head->read_var2, &no_rewrite_list) {
    + struct no_rewrite_entry *ptr;
    + ptr = list_entry(pos, struct no_rewrite_entry, list);
    + if (ptr->is_deleted)
    + continue;
    + if (tmy_io_printf(head, TMY_DENY_REWRITE "%s\n",
    + ptr->pattern->name))
    + return -ENOMEM;
    + }
    + return 0;
    +}
    +
    +/************************* FILE ACL HANDLER *************************/
    +
    +static int tmy_add_file_acl(const char *filename,
    + u8 perm,
    + struct domain_info * const domain,
    + const struct condition_list *cond,
    + const bool is_delete)
    +{
    + const struct path_info *saved;
    + struct acl_info *ptr;
    + struct file_acl *acl;
    + int error = -ENOMEM;
    + bool is_group = 0;
    +
    + if (!domain)
    + return -EINVAL;
    + if (perm > 7 || !perm) {
    + printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
    + __FUNCTION__, perm, filename);
    + return -EINVAL;
    + }
    + if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
    + return -EINVAL;
    +
    + if (filename[0] == '@') {
    + /* This cast is OK because I don't dereference. */
    + saved = (struct path_info *) tmy_new_path_group(filename + 1);
    + if (!saved)
    + return -ENOMEM;
    + is_group = 1;
    + } else {
    +
    + if (tmy_strendswith(filename, "/"))
    + /*
    + * Valid permissions for directory are
    + * only 'allow_mkdir' and 'allow_rmdir'.
    + */
    + return 0;
    +
    + saved = tmy_save_name(filename);
    + if (!saved)
    + return -ENOMEM;
    +
    + if (!is_delete && perm == 4 &&
    + tmy_globally_readable(saved))
    + return 0;
    +
    + }
    +
    + mutex_lock(&domain_acl_lock);
    +
    + if (is_delete)
    + goto remove;
    +
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + acl = (struct file_acl *) ptr;
    + if ((ptr->type == TMY_TYPE_FILE_ACL) &&
    + ptr->cond == cond &&
    + (acl->u.filename == saved)) {
    + if (ptr->is_deleted) {
    + acl->perm = 0;
    + mb(); /* Avoid out-of-order execution. */
    + ptr->is_deleted = 0;
    + }
    + /* Found. Just 'OR' the permission bits. */
    + acl->perm |= perm;
    + error = 0;
    + tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
    + goto ok;
    + }
    + }
    + /* Not found. Append it to the tail. */
    + acl = tmy_alloc_element(sizeof(*acl));
    + if (!acl)
    + goto ok;
    +
    + acl->head.type = TMY_TYPE_FILE_ACL;
    + acl->head.cond = cond;
    + acl->perm = perm;
    + acl->u_is_group = is_group;
    + acl->u.filename = saved;
    + error = tmy_add_acl(domain, (struct acl_info *) acl);
    + goto ok;
    +remove: ;
    + error = -ENOENT;
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + acl = (struct file_acl *) ptr;
    + if (ptr->type != TMY_TYPE_FILE_ACL ||
    + ptr->cond != cond ||
    + ptr->is_deleted ||
    + acl->perm != perm ||
    + acl->u.filename != saved)
    + continue;
    + error = tmy_del_acl(ptr);
    + break;
    + }
    +ok: ;
    + mutex_unlock(&domain_acl_lock);
    + return error;
    +}
    +
    +static int tmy_file_acl(const struct path_info *filename, const u8 perm,
    + struct obj_info *obj)
    +{
    + const struct domain_info *domain = TMY_SECURITY->domain;
    + struct acl_info *ptr;
    + const int may_use_pat = ((perm & 1) == 0);
    +
    + if (!tmy_flags(TMY_MAC_FOR_FILE))
    + return 0;
    + if (!filename->is_dir) {
    + if (perm == 4 && tmy_globally_readable(filename))
    + return 0;
    + }
    +
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + struct file_acl *acl = (struct file_acl *) ptr;
    +
    + if (ptr->type != TMY_TYPE_FILE_ACL ||
    + ptr->is_deleted ||
    + (acl->perm & perm) != perm ||
    + tmy_check_condition(ptr->cond, obj))
    + continue;
    +
    + if (acl->u_is_group) {
    + if (tmy_path_match_group(filename,
    + acl->u.group,
    + may_use_pat))
    + return 0;
    + } else {
    + if ((may_use_pat || !acl->u.filename->is_patterned) &&
    + tmy_path_match(filename, acl->u.filename))
    + return 0;
    + }
    + }
    +
    + return -EPERM;
    +}
    +
    +static int tmy_file_perm2(const struct path_info *filename,
    + const u8 perm,
    + struct obj_info *obj,
    + const char *operation)
    +{
    + int error = 0;
    + struct domain_info * const domain = TMY_SECURITY->domain;
    + const u8 profile = domain->profile;
    + const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
    + const bool is_enforce = (mode == 3);
    +
    + if (!filename)
    + return 0;
    +
    + error = tmy_file_acl(filename, perm, obj);
    +
    + tmy_audit_file_log(filename, perm, !error, profile, mode);
    +
    + if (!error)
    + return error;
    +
    + if (tmy_flags(TMY_VERBOSE))
    + tmy_audit("TOMOYO-%s: Access %d(%s) to %s denied for %s\n",
    + tmy_getmsg(is_enforce), perm, operation,
    + filename->name, tmy_lastname(domain));
    +
    + if (is_enforce)
    + error = tmy_supervisor("%s\n%d %s\n",
    + domain->domainname->name,
    + perm, filename->name);
    +
    + else if (mode == 1 && tmy_quota()) {
    + /* Don't use patterns if execution bit is on. */
    + const struct path_info *patterned =
    + ((perm & 1) == 0) ?
    + tmy_get_pattern(filename) : filename;
    + tmy_add_file_acl(patterned->name, perm, domain, NULL, 0);
    + }
    +
    + if (!is_enforce)
    + error = 0;
    +
    + return error;
    +}
    +
    +/**
    + * tmy_file_perm - check permission for sysctl(2) operation.
    + * @filename0: pointer to filename returned by sysctlpath_from_table().
    + * @perm: mode (read = 4, write = 2, read-write = 6).
    + * @operation: pointer to error message.
    + *
    + * Returns zero if permission granted.
    + * Returns nonzero if permission denied.
    + */
    +int tmy_file_perm(const char *filename0, const u8 perm, const char *operation)
    +{
    + struct path_info filename;
    +
    + if (!tmy_flags(TMY_MAC_FOR_FILE))
    + return 0;
    +
    + filename.name = filename0;
    + tmy_fill_path_info(&filename);
    +
    + return tmy_file_perm2(&filename, perm, NULL, operation);
    +}
    +
    +/**
    + * tmy_add_file_policy - add or delete file policy.
    + * @data: a line to parse.
    + * @domain: pointer to "struct domain_info".
    + * @cond: pointer to "struct condition_list". May be NULL.
    + * @is_delete: is this delete request?
    + *
    + * Returns zero on success.
    + * Returns nonzero on failure.
    + */
    +int tmy_add_file_policy(char *data,
    + struct domain_info *domain,
    + const struct condition_list *cond,
    + const bool is_delete)
    +{
    + char *filename = strchr(data, ' ');
    + unsigned int perm;
    + u8 type;
    +
    + if (!filename)
    + return -EINVAL;
    + *filename++ = '\0';
    +
    + if (sscanf(data, "%u", &perm) == 1)
    + return tmy_add_file_acl(filename, (u8) perm, domain, cond,
    + is_delete);
    +
    + if (strncmp(data, "allow_", 6))
    + goto out;
    +
    + data += 6;
    +
    + for (type = 0; acl_type_array[type].keyword; type++) {
    + if (strcmp(data, acl_type_array[type].keyword))
    + continue;
    +
    + if (acl_type_array[type].paths == 2) {
    + char *filename2 = strchr(filename, ' ');
    +
    + if (!filename2)
    + break;
    + *filename2++ = '\0';
    + return tmy_add_double_write_acl(type, filename,
    + filename2,
    + domain, cond,
    + is_delete);
    + } else
    + return tmy_add_single_write_acl(type, filename,
    + domain, cond,
    + is_delete);
    +
    + break;
    + }
    +out: ;
    + return -EINVAL;
    +}
    +
    +static int tmy_add_single_write_acl(const u8 type,
    + const char *filename,
    + struct domain_info * const domain,
    + const struct condition_list *cond,
    + const bool is_delete)
    +{
    + const struct path_info *saved;
    + struct acl_info *ptr;
    + struct single_acl *acl;
    + int error = -ENOMEM;
    + bool is_group = 0;
    +
    + if (!domain)
    + return -EINVAL;
    + if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
    + return -EINVAL;
    +
    + if (filename[0] == '@') {
    + /* This cast is OK because I don't dereference. */
    + saved = (struct path_info *) tmy_new_path_group(filename + 1);
    + if (!saved)
    + return -ENOMEM;
    + is_group = 1;
    + } else {
    + saved = tmy_save_name(filename);
    + if (!saved)
    + return -ENOMEM;
    + }
    +
    + mutex_lock(&domain_acl_lock);
    + if (is_delete)
    + goto remove;
    +
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + acl = (struct single_acl *) ptr;
    + if (ptr->type == type && ptr->cond == cond) {
    + if (acl->u.filename == saved) {
    + ptr->is_deleted = 0;
    + /* Found. Nothing to do. */
    + error = 0;
    + goto ok;
    + }
    + }
    + }
    + /* Not found. Append it to the tail. */
    + acl = tmy_alloc_element(sizeof(*acl));
    + if (!acl)
    + goto ok;
    +
    + acl->head.type = type;
    + acl->head.cond = cond;
    + acl->u_is_group = is_group;
    + acl->u.filename = saved;
    + error = tmy_add_acl(domain, (struct acl_info *) acl);
    + goto ok;
    +remove: ;
    + error = -ENOENT;
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + acl = (struct single_acl *) ptr;
    +
    + if (ptr->type != type || ptr->is_deleted ||
    + ptr->cond != cond || acl->u.filename != saved)
    + continue;
    +
    + error = tmy_del_acl(ptr);
    + break;
    + }
    +ok: ;
    + mutex_unlock(&domain_acl_lock);
    +
    + return error;
    +}
    +
    +static int tmy_add_double_write_acl(const u8 type,
    + const char *filename1,
    + const char *filename2,
    + struct domain_info * const domain,
    + const struct condition_list *cond,
    + const bool is_delete)
    +{
    + const struct path_info *saved1;
    + const struct path_info *saved2;
    + struct acl_info *ptr;
    + struct double_acl *acl;
    + int error = -ENOMEM;
    + bool is_group1 = 0;
    + bool is_group2 = 0;
    +
    + if (!domain)
    + return -EINVAL;
    + if (!tmy_correct_path(filename1, 0, 0, 0, __FUNCTION__) ||
    + !tmy_correct_path(filename2, 0, 0, 0, __FUNCTION__))
    + return -EINVAL;
    +
    + if (filename1[0] == '@') {
    + /* This cast is OK because I don't dereference. */
    + saved1 = (struct path_info *) tmy_new_path_group(filename1 + 1);
    + if (!saved1)
    + return -ENOMEM;
    + is_group1 = 1;
    + } else {
    + saved1 = tmy_save_name(filename1);
    + if (!saved1)
    + return -ENOMEM;
    + }
    +
    + if (filename2[0] == '@') {
    + /* This cast is OK because I don't dereference. */
    + saved2 = (struct path_info *) tmy_new_path_group(filename2 + 1);
    + if (!saved2)
    + return -ENOMEM;
    + is_group2 = 1;
    + } else {
    + saved2 = tmy_save_name(filename2);
    + if (!saved2)
    + return -ENOMEM;
    + }
    +
    + mutex_lock(&domain_acl_lock);
    +
    + if (is_delete)
    + goto remove;
    +
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + acl = (struct double_acl *) ptr;
    + if (ptr->type == type && ptr->cond == cond) {
    + if (acl->u1.filename1 == saved1 &&
    + acl->u2.filename2 == saved2) {
    + ptr->is_deleted = 0;
    + /* Found. Nothing to do. */
    + error = 0;
    + goto ok;
    + }
    + }
    + }
    + /* Not found. Append it to the tail. */
    + acl = tmy_alloc_element(sizeof(*acl));
    + if (!acl)
    + goto ok;
    +
    + acl->head.type = type;
    + acl->head.cond = cond;
    + acl->u1_is_group = is_group1;
    + acl->u2_is_group = is_group2;
    + acl->u1.filename1 = saved1;
    + acl->u2.filename2 = saved2;
    + error = tmy_add_acl(domain,
    + (struct acl_info *) acl);
    + goto ok;
    +remove: ;
    + error = -ENOENT;
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + acl = (struct double_acl *) ptr;
    + if (ptr->type != type || ptr->is_deleted ||
    + ptr->cond != cond ||
    + acl->u1.filename1 != saved1 ||
    + acl->u2.filename2 != saved2)
    + continue;
    + error = tmy_del_acl(ptr);
    + break;
    + }
    + ok: ;
    + mutex_unlock(&domain_acl_lock);
    + return error;
    +}
    +
    +static int tmy_single_write_acl(const u8 type,
    + const struct path_info *filename,
    + struct obj_info *obj)
    +{
    + const struct domain_info *domain = TMY_SECURITY->domain;
    + struct acl_info *ptr;
    +
    + if (!tmy_flags(TMY_MAC_FOR_FILE))
    + return 0;
    +
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + struct single_acl *acl = (struct single_acl *) ptr;
    +
    + if (ptr->type != type || ptr->is_deleted ||
    + tmy_check_condition(ptr->cond, obj))
    + continue;
    +
    + if (acl->u_is_group) {
    + if (!tmy_path_match_group(filename, acl->u.group, 1))
    + continue;
    + } else {
    + if (!tmy_path_match(filename, acl->u.filename))
    + continue;
    + }
    + return 0;
    + }
    +
    + return -EPERM;
    +}
    +
    +static int tmy_double_write_acl(const u8 type,
    + const struct path_info *filename1,
    + const struct path_info *filename2,
    + struct obj_info *obj)
    +{
    + const struct domain_info *domain = TMY_SECURITY->domain;
    + struct acl_info *ptr;
    +
    + if (!tmy_flags(TMY_MAC_FOR_FILE))
    + return 0;
    +
    + list_for_each_entry(ptr, &domain->acl_info_list, list) {
    + struct double_acl *acl = (struct double_acl *) ptr;
    +
    + if (ptr->type != type || ptr->is_deleted ||
    + tmy_check_condition(ptr->cond, obj))
    + continue;
    +
    + if (acl->u1_is_group) {
    + if (!tmy_path_match_group(filename1,
    + acl->u1.group1, 1))
    + continue;
    + } else {
    + if (!tmy_path_match(filename1, acl->u1.filename1))
    + continue;
    + }
    +
    + if (acl->u2_is_group) {
    + if (!tmy_path_match_group(filename2,
    + acl->u2.group2, 1))
    + continue;
    + } else {
    + if (!tmy_path_match(filename2, acl->u2.filename2))
    + continue;
    + }
    +
    + return 0;
    + }
    +
    + return -EPERM;
    +}
    +
    +static int tmy_single_write_perm2(const unsigned int operation,
    + const struct path_info *filename,
    + struct obj_info *obj)
    +{
    + int error;
    + struct domain_info * const domain = TMY_SECURITY->domain;
    + const u8 profile = domain->profile;
    + const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
    + const bool is_enforce = (mode == 3);
    +
    + if (!mode)
    + return 0;
    +
    + error = tmy_single_write_acl(operation, filename, obj);
    +
    + tmy_audit_write_log(tmy_acltype2keyword(operation),
    + filename, NULL, !error, profile, mode);
    +
    + if (!error)
    + goto ok;
    +
    + if (tmy_flags(TMY_VERBOSE))
    + tmy_audit("TOMOYO-%s: Access '%s %s' denied for %s\n",
    + tmy_getmsg(is_enforce),
    + tmy_acltype2keyword(operation), filename->name,
    + tmy_lastname(domain));
    +
    + if (is_enforce)
    + error = tmy_supervisor("%s\nallow_%s %s\n",
    + domain->domainname->name,
    + tmy_acltype2keyword(operation),
    + filename->name);
    +
    + else if (mode == 1 && tmy_quota())
    + tmy_add_single_write_acl(operation,
    + tmy_get_pattern(filename)->name,
    + domain, NULL, 0);
    +
    + if (!is_enforce)
    + error = 0;
    +
    +ok: ;
    + if (!error && operation == TMY_TYPE_TRUNCATE_ACL &&
    + tmy_is_no_rewrite_file(filename))
    + error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
    + filename, obj);
    +
    + return error;
    +}
    +
    +/**
    + * tmy_exec_perm - check permission for execve(2) operation.
    + * @filename: pointer to filename to execute.
    + * @filp: pointer to "struct file".
    + *
    + * Returns zero if permission granted.
    + * Returns nonzero if permission denied.
    + */
    +int tmy_exec_perm(const struct path_info *filename, struct file *filp)
    +{
    + struct obj_info obj;
    + if (!tmy_flags(TMY_MAC_FOR_FILE))
    + return 0;
    + memset(&obj, 0, sizeof(obj));
    + obj.path1_dentry = filp->f_dentry;
    + obj.path1_vfsmnt = filp->f_vfsmnt;
    + return tmy_file_perm2(filename, 1, &obj, "do_execve");
    +}
    +
    +/**
    + * tmy_open_perm - check permission for open(2) operation.
    + * @dentry: pointer to "struct dentry".
    + * @mnt: pointer to "struct vfsmount".
    + * @flag: open flags.
    + *
    + * Returns zero if permission granted.
    + * Returns nonzero if permission denied.
    + */
    +int tmy_open_perm(struct dentry *dentry,
    + struct vfsmount *mnt,
    + const int flag)
    +{
    + struct obj_info obj;
    + const int acc_mode = ACC_MODE(flag);
    + int error = -ENOMEM;
    + struct path_info *buf;
    + const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
    + const bool is_enforce = (mode == 3);
    +
    + if (!mode)
    + return 0;
    + if (acc_mode == 0)
    + return 0;
    + if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
    + /* I don't check directories here */
    + /* because mkdir() and rmdir() don't call me. */
    + return 0;
    +
    + buf = tmy_get_path(dentry, mnt);
    +
    + if (!buf)
    + goto out;
    +
    + memset(&obj, 0, sizeof(obj));
    + obj.path1_dentry = dentry;
    + obj.path1_vfsmnt = mnt;
    +
    + error = 0;
    + if ((acc_mode & MAY_WRITE) &&
    + ((flag & O_TRUNC) || !(flag & O_APPEND)) &&
    + tmy_is_no_rewrite_file(buf))
    + error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
    + buf, &obj);
    +
    + if (error == 0)
    + error = tmy_file_perm2(buf, acc_mode, &obj, "open");
    +
    + if (error == 0 && (flag & O_TRUNC))
    + error = tmy_single_write_perm2(TMY_TYPE_TRUNCATE_ACL,
    + buf, &obj);
    +
    + tmy_free(buf);
    +
    +out: ;
    + if (!is_enforce)
    + error = 0;
    + return error;
    +}
    +
    +/**
    + * tmy_single_write_perm - check permission for create(2) etc. operation.
    + * @operation: operation index number.
    + * @dentry: pointer to "struct dentry".
    + * @mnt: pointer to "struct vfsmount".
    + *
    + * Returns zero if permission granted.
    + * Returns nonzero if permission denied.
    + */
    +int tmy_single_write_perm(const unsigned int operation,
    + struct dentry *dentry,
    + struct vfsmount *mnt)
    +{
    + struct obj_info obj;
    + int error = -ENOMEM;
    + struct path_info *buf;
    + const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
    + const bool is_enforce = (mode == 3);
    +
    + if (!mode)
    + return 0;
    +
    + buf = tmy_get_path(dentry, mnt);
    +
    + if (!buf)
    + goto out;
    +
    + memset(&obj, 0, sizeof(obj));
    + obj.path1_dentry = dentry;
    + obj.path1_vfsmnt = mnt;
    +
    + switch (operation) {
    + case TMY_TYPE_MKDIR_ACL:
    + case TMY_TYPE_RMDIR_ACL:
    + if (!buf->is_dir) {
    + strcat((char *) buf->name, "/");
    + tmy_fill_path_info(buf);
    + }
    + }
    + error = tmy_single_write_perm2(operation, buf, &obj);
    + tmy_free(buf);
    +
    +out: ;
    + if (!is_enforce)
    + error = 0;
    +
    + return error;
    +}
    +
    +/**
    + * tmy_rewrite_perm - check permission for truncate/overwrite operation.
    + * @filp: pointer to "struct file".
    + *
    + * Returns zero if permission granted.
    + * Returns nonzero if permission denied.
    + */
    +int tmy_rewrite_perm(struct file *filp)
    +{
    + int error = -ENOMEM;
    + const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
    + const bool is_enforce = (mode == 3);
    + struct path_info *buf;
    +
    + if (!mode)
    + return 0;
    +
    + buf = tmy_get_path(filp->f_dentry, filp->f_vfsmnt);
    + if (!buf)
    + goto out;
    +
    + if (tmy_is_no_rewrite_file(buf)) {
    + struct obj_info obj;
    + memset(&obj, 0, sizeof(obj));
    + obj.path1_dentry = filp->f_dentry;
    + obj.path1_vfsmnt = filp->f_vfsmnt;
    + error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
    + buf, &obj);
    + } else
    + error = 0;
    +
    + tmy_free(buf);
    +
    +out: ;
    + if (!is_enforce)
    + error = 0;
    + return error;
    +}
    +
    +/**
    + * tmy_double_write_perm - check permission for link(2)/rename(2) operation.
    + * @operation: operation index number.
    + * @dentry1: pointer to "struct dentry".
    + * @mnt1: pointer to "struct vfsmount".
    + * @dentry2: pointer to "struct dentry".
    + * @mnt2: pointer to "struct vfsmount".
    + *
    + * Returns zero if permission granted.
    + * Returns nonzero if permission denied.
    + */
    +int tmy_double_write_perm(const unsigned int operation,
    + struct dentry *dentry1,
    + struct vfsmount *mnt1,
    + struct dentry *dentry2,
    + struct vfsmount *mnt2)
    +{
    + struct obj_info obj;
    + int error = -ENOMEM;
    + struct path_info *buf1;
    + struct path_info *buf2;
    + struct domain_info * const domain = TMY_SECURITY->domain;
    + const u8 profile = domain->profile;
    + const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
    + const bool is_enforce = (mode == 3);
    +
    + if (!mode)
    + return 0;
    + buf1 = tmy_get_path(dentry1, mnt1);
    + buf2 = tmy_get_path(dentry2, mnt2);
    +
    + if (!buf1 || !buf2)
    + goto out;
    +
    + memset(&obj, 0, sizeof(obj));
    + obj.path1_dentry = dentry1;
    + obj.path1_vfsmnt = mnt1;
    + obj.path2_dentry = dentry2;
    + obj.path2_vfsmnt = mnt2;
    + if (operation == TMY_TYPE_RENAME_ACL) {
    + /* TMY_TYPE_LINK_ACL can't reach here for directory. */
    + if (dentry1->d_inode && S_ISDIR(dentry1->d_inode->i_mode)) {
    + if (!buf1->is_dir) {
    + strcat((char *) buf1->name, "/");
    + tmy_fill_path_info(buf1);
    + }
    + if (!buf2->is_dir) {
    + strcat((char *) buf2->name, "/");
    + tmy_fill_path_info(buf2);
    + }
    + }
    + }
    + error = tmy_double_write_acl(operation, buf1, buf2, &obj);
    +
    + tmy_audit_write_log(tmy_acltype2keyword(operation),
    + buf1, buf2, !error, profile, mode);
    +
    + if (!error)
    + goto out;
    +
    + if (tmy_flags(TMY_VERBOSE))
    + tmy_audit("TOMOYO-%s: Access '%s %s %s' denied for %s\n",
    + tmy_getmsg(is_enforce),
    + tmy_acltype2keyword(operation),
    + buf1->name, buf2->name, tmy_lastname(domain));
    +
    + if (is_enforce)
    + error = tmy_supervisor("%s\nallow_%s %s %s\n",
    + domain->domainname->name,
    + tmy_acltype2keyword(operation),
    + buf1->name, buf2->name);
    + else if (mode == 1 && tmy_quota())
    + tmy_add_double_write_acl(operation,
    + tmy_get_pattern(buf1)->name,
    + tmy_get_pattern(buf2)->name,
    + domain, NULL, 0);
    +
    +out: ;
    + tmy_free(buf1);
    + tmy_free(buf2);
    + if (!is_enforce)
    + error = 0;
    + return error;
    +}
    --
    -
    To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
    the body of a message to majordomo@vger.kernel.org
    More majordomo info at http://vger.kernel.org/majordomo-info.html
    Please read the FAQ at http://www.tux.org/lkml/

    \
     
     \ /
      Last update: 2007-11-16 18:45    [W:0.092 / U:63.388 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site