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 Signed-off-by: Tetsuo Handa --- security/tomoyo/file.c | 1468 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1468 insertions(+) --- /dev/null +++ linux-2.6-mm/security/tomoyo/file.c @@ -0,0 +1,1468 @@ +/* + * 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; + + tmy_sncatprintf(buf, len - 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; + + tmy_sncatprintf(buf, len - 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 = container_of(ptr, struct file_acl, head); + 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, &acl->head); + goto ok; +remove: ; + error = -ENOENT; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + acl = container_of(ptr, struct file_acl, head); + 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; + acl = container_of(ptr, struct file_acl, head); + + 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 = container_of(ptr, struct single_acl, head); + 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, &acl->head); + goto ok; +remove: ; + error = -ENOENT; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + acl = container_of(ptr, struct single_acl, head); + + 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 = container_of(ptr, struct double_acl, head); + 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, &acl->head); + goto ok; +remove: ; + error = -ENOENT; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + acl = container_of(ptr, struct double_acl, head); + 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; + acl = container_of(ptr, struct single_acl, head); + + 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; + acl = container_of(ptr, struct double_acl, head); + + 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; +} --