Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1759089AbYJIEcJ (ORCPT ); Thu, 9 Oct 2008 00:32:09 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1758333AbYJIE3n (ORCPT ); Thu, 9 Oct 2008 00:29:43 -0400 Received: from ms1.nttdata.co.jp ([163.135.193.232]:52265 "EHLO ms1.nttdata.co.jp" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757926AbYJIE3b (ORCPT ); Thu, 9 Oct 2008 00:29:31 -0400 Message-Id: <20081009042922.939610141@nttdata.co.jp> References: <20081009042814.398846861@nttdata.co.jp> User-Agent: quilt/0.45-1 Date: Thu, 09 Oct 2008 13:28:21 +0900 From: Kentaro Takeda To: Stephen Smalley , James Morris , Chris Wright Cc: "Serge E. Hallyn" , David Howells , linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Toshiharu Harada , Andrew Morton , Kentaro Takeda , Tetsuo Handa Subject: [TOMOYO #10 (linux-next) 7/8] File operation restriction part. X-OriginalArrivalTime: 09 Oct 2008 04:29:27.0929 (UTC) FILETIME=[9DCA6E90:01C929C7] Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 33882 Lines: 1169 This file controls file related operations. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa Signed-off-by: Toshiharu Harada --- security/tomoyo/file.c | 1149 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1149 insertions(+) --- /dev/null +++ linux-next/security/tomoyo/file.c @@ -0,0 +1,1149 @@ +/* + * security/tomoyo/file.c + * + * Implementation of the Domain-Based Mandatory Access Control. + * + * Copyright (C) 2005-2008 NTT DATA CORPORATION + * + * Version: 2.2.0-pre 2008/10/01 + * + */ + +#include "common.h" +#include "tomoyo.h" +#include "realpath.h" +#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE]) + +/* Structure for "allow_read" keyword. */ +struct globally_readable_file_entry { + struct list1_head list; + const struct path_info *filename; + bool is_deleted; +}; + +/* Structure for "file_pattern" keyword. */ +struct pattern_entry { + struct list1_head list; + const struct path_info *pattern; + bool is_deleted; +}; + +/* Structure for "deny_rewrite" keyword. */ +struct no_rewrite_entry { + struct list1_head list; + const struct path_info *pattern; + bool is_deleted; +}; + +/* Keyword array for single path operations. */ +static const char *sp_keyword[MAX_SINGLE_PATH_OPERATION] = { + [TMY_TYPE_READ_WRITE_ACL] = "read/write", + [TMY_TYPE_EXECUTE_ACL] = "execute", + [TMY_TYPE_READ_ACL] = "read", + [TMY_TYPE_WRITE_ACL] = "write", + [TMY_TYPE_CREATE_ACL] = "create", + [TMY_TYPE_UNLINK_ACL] = "unlink", + [TMY_TYPE_MKDIR_ACL] = "mkdir", + [TMY_TYPE_RMDIR_ACL] = "rmdir", + [TMY_TYPE_MKFIFO_ACL] = "mkfifo", + [TMY_TYPE_MKSOCK_ACL] = "mksock", + [TMY_TYPE_MKBLOCK_ACL] = "mkblock", + [TMY_TYPE_MKCHAR_ACL] = "mkchar", + [TMY_TYPE_TRUNCATE_ACL] = "truncate", + [TMY_TYPE_SYMLINK_ACL] = "symlink", + [TMY_TYPE_REWRITE_ACL] = "rewrite", +}; + +/* Keyword array for double path operations. */ +static const char *dp_keyword[MAX_DOUBLE_PATH_OPERATION] = { + [TMY_TYPE_LINK_ACL] = "link", + [TMY_TYPE_RENAME_ACL] = "rename", +}; + +/** + * tmy_sp2keyword - Get the name of single path operation. + * + * @operation: Type of operation. + * + * Returns the name of single path operation. + */ +const char *tmy_sp2keyword(const u8 operation) +{ + return (operation < MAX_SINGLE_PATH_OPERATION) + ? sp_keyword[operation] : NULL; +} + +/** + * tmy_dp2keyword - Get the name of double path operation. + * + * @operation: Type of operation. + * + * Returns the name of double path operation. + */ +const char *tmy_dp2keyword(const u8 operation) +{ + return (operation < MAX_DOUBLE_PATH_OPERATION) + ? dp_keyword[operation] : NULL; +} + +/** + * strendswith - Check whether the token ends with the given token. + * + * @name: The token to check. + * @tail: The token to find. + * + * Returns true if @name ends with @tail, false otherwise. + */ +static bool strendswith(const char *name, const char *tail) +{ + int len; + if (!name || !tail) + return false; + len = strlen(name) - strlen(tail); + return len >= 0 && !strcmp(name + len, tail); +} + +/** + * tmy_get_path - Get realpath. + * + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". + * + * Returns pointer to "struct path_info" on success, NULL otherwise. + */ +static struct path_info *tmy_get_path(struct dentry *dentry, + struct vfsmount *mnt) +{ + int error; + struct path_info_with_data *buf = tmy_alloc(sizeof(*buf)); + if (!buf) + return NULL; + /* Preserve one byte for appending "/". */ + error = tmy_realpath_from_dentry2(dentry, mnt, buf->body, + sizeof(buf->body) - 2); + if (!error) { + buf->head.name = buf->body; + tmy_fill_path_info(&buf->head); + return &buf->head; + } + tmy_free(buf); + return NULL; +} + +static int update_double_path_acl(const u8 type, const char *filename1, + const char *filename2, + struct domain_info * const domain, + const bool is_delete); +static int update_single_path_acl(const u8 type, const char *filename, + struct domain_info * const domain, + const bool is_delete); + +/* The list for "struct globally_readable_file_entry". */ +static LIST1_HEAD(globally_readable_list); + +/** + * update_globally_readable_entry - Update "struct globally_readable_file_entry" list. + * + * @filename: Filename unconditionally permitted to open() for reading. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int update_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(lock); + const struct path_info *saved_filename; + int error = -ENOMEM; + if (!tmy_is_correct_path(filename, 1, 0, -1, __func__)) + return -EINVAL; + saved_filename = tmy_save_name(filename); + if (!saved_filename) + return -ENOMEM; + mutex_lock(&lock); + list1_for_each_entry(ptr, &globally_readable_list, list) { + if (ptr->filename != saved_filename) + continue; + 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_filename; + list1_add_tail_mb(&new_entry->list, &globally_readable_list); + error = 0; +out: + mutex_unlock(&lock); + tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY); + return error; +} + +/** + * is_globally_readable_file - Check if the file is unconditionnaly permitted to be open()ed for reading. + * + * @filename: The filename to check. + * + * Returns true if any domain can open @filename for reading, false otherwise. + */ +static bool is_globally_readable_file(const struct path_info *filename) +{ + struct globally_readable_file_entry *ptr; + list1_for_each_entry(ptr, &globally_readable_list, list) { + if (!ptr->is_deleted && + tmy_path_matches_pattern(filename, ptr->filename)) + return true; + } + return false; +} + +/** + * tmy_write_globally_readable_policy - Write "struct globally_readable_file_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_write_globally_readable_policy(char *data, const bool is_delete) +{ + return update_globally_readable_entry(data, is_delete); +} + +/** + * tmy_read_globally_readable_policy - Read "struct globally_readable_file_entry" list. + * + * @head: Pointer to "struct tmy_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tmy_read_globally_readable_policy(struct tmy_io_buffer *head) +{ + struct list1_head *pos; + list1_for_each_cookie(pos, head->read_var2, &globally_readable_list) { + struct globally_readable_file_entry *ptr; + ptr = list1_entry(pos, struct globally_readable_file_entry, + list); + if (ptr->is_deleted) + continue; + if (!tmy_io_printf(head, KEYWORD_ALLOW_READ "%s\n", + ptr->filename->name)) + goto out; + } + return true; +out: + return false; +} + +/* The list for "struct pattern_entry". */ +static LIST1_HEAD(pattern_list); + +/** + * update_file_pattern_entry - Update "struct pattern_entry" list. + * + * @pattern: Pathname pattern. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int update_file_pattern_entry(const char *pattern, const bool is_delete) +{ + struct pattern_entry *new_entry; + struct pattern_entry *ptr; + static DEFINE_MUTEX(lock); + const struct path_info *saved_pattern; + int error = -ENOMEM; + if (!tmy_is_correct_path(pattern, 0, 1, 0, __func__)) + return -EINVAL; + saved_pattern = tmy_save_name(pattern); + if (!saved_pattern) + return -ENOMEM; + mutex_lock(&lock); + list1_for_each_entry(ptr, &pattern_list, list) { + if (saved_pattern != ptr->pattern) + continue; + 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_pattern; + list1_add_tail_mb(&new_entry->list, &pattern_list); + error = 0; +out: + mutex_unlock(&lock); + tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY); + return error; +} + +/** + * get_file_pattern - Get patterned pathname. + * + * @filename: The filename to find patterned pathname. + * + * Returns pointer to pathname pattern if matched, @filename otherwise. + */ +static const struct path_info * +get_file_pattern(const struct path_info *filename) +{ + struct pattern_entry *ptr; + const struct path_info *pattern = NULL; + list1_for_each_entry(ptr, &pattern_list, list) { + if (ptr->is_deleted) + continue; + if (!tmy_path_matches_pattern(filename, ptr->pattern)) + continue; + pattern = ptr->pattern; + if (strendswith(pattern->name, "/\\*")) { + /* Do nothing. Try to find the better match. */ + } else { + /* This would be the better match. Use this. */ + break; + } + } + if (pattern) + filename = pattern; + return filename; +} + +/** + * tmy_write_pattern_policy - Write "struct pattern_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_write_pattern_policy(char *data, const bool is_delete) +{ + return update_file_pattern_entry(data, is_delete); +} + +/** + * tmy_read_file_pattern - Read "struct pattern_entry" list. + * + * @head: Pointer to "struct tmy_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tmy_read_file_pattern(struct tmy_io_buffer *head) +{ + struct list1_head *pos; + list1_for_each_cookie(pos, head->read_var2, &pattern_list) { + struct pattern_entry *ptr; + ptr = list1_entry(pos, struct pattern_entry, list); + if (ptr->is_deleted) + continue; + if (!tmy_io_printf(head, KEYWORD_FILE_PATTERN "%s\n", + ptr->pattern->name)) + goto out; + } + return true; +out: + return false; +} + +/* The list for "struct no_rewrite_entry". */ +static LIST1_HEAD(no_rewrite_list); + +/** + * update_no_rewrite_entry - Update "struct no_rewrite_entry" list. + * + * @pattern: Pathname pattern that are not rewritable by default. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int update_no_rewrite_entry(const char *pattern, const bool is_delete) +{ + struct no_rewrite_entry *new_entry, *ptr; + static DEFINE_MUTEX(lock); + const struct path_info *saved_pattern; + int error = -ENOMEM; + if (!tmy_is_correct_path(pattern, 0, 0, 0, __func__)) + return -EINVAL; + saved_pattern = tmy_save_name(pattern); + if (!saved_pattern) + return -ENOMEM; + mutex_lock(&lock); + list1_for_each_entry(ptr, &no_rewrite_list, list) { + if (ptr->pattern != saved_pattern) + continue; + 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_pattern; + list1_add_tail_mb(&new_entry->list, &no_rewrite_list); + error = 0; +out: + mutex_unlock(&lock); + tmy_update_counter(TMY_UPDATES_COUNTER_EXCEPTION_POLICY); + return error; +} + +/** + * is_no_rewrite_file - Check if the given pathname is not permitted to be rewrited. + * + * @filename: Filename to check. + * + * Returns true if @filename is specified by "deny_rewrite" directive, + * false otherwise. + */ +static bool is_no_rewrite_file(const struct path_info *filename) +{ + struct no_rewrite_entry *ptr; + list1_for_each_entry(ptr, &no_rewrite_list, list) { + if (ptr->is_deleted) + continue; + if (!tmy_path_matches_pattern(filename, ptr->pattern)) + continue; + return true; + } + return false; +} + +/** + * tmy_write_no_rewrite_policy - Write "struct no_rewrite_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_write_no_rewrite_policy(char *data, const bool is_delete) +{ + return update_no_rewrite_entry(data, is_delete); +} + +/** + * tmy_read_no_rewrite_policy - Read "struct no_rewrite_entry" list. + * + * @head: Pointer to "struct tmy_io_buffer". + * + * Returns true on success, false otherwise. + */ +bool tmy_read_no_rewrite_policy(struct tmy_io_buffer *head) +{ + struct list1_head *pos; + list1_for_each_cookie(pos, head->read_var2, &no_rewrite_list) { + struct no_rewrite_entry *ptr; + ptr = list1_entry(pos, struct no_rewrite_entry, list); + if (ptr->is_deleted) + continue; + if (!tmy_io_printf(head, KEYWORD_DENY_REWRITE "%s\n", + ptr->pattern->name)) + goto out; + } + return true; +out: + return false; +} + +/** + * update_file_acl - Update file's read/write/execute ACL. + * + * @filename: Filename. + * @perm: Permission (between 1 to 7). + * @domain: Pointer to "struct domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * This is legacy support interface for older policy syntax. + * Current policy syntax uses "allow_read/write" instead of "6", + * "allow_read" instead of "4", "allow_write" instead of "2", + * "allow_execute" instead of "1". + */ +static int update_file_acl(const char *filename, u8 perm, + struct domain_info * const domain, + const bool is_delete) +{ + if (perm > 7 || !perm) { + printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n", + __func__, perm, filename); + return -EINVAL; + } + if (filename[0] != '@' && strendswith(filename, "/")) + /* + * Only 'allow_mkdir' and 'allow_rmdir' are valid for + * directory permissions. + */ + return 0; + if (perm & 4) + update_single_path_acl(TMY_TYPE_READ_ACL, filename, domain, + is_delete); + if (perm & 2) + update_single_path_acl(TMY_TYPE_WRITE_ACL, filename, domain, + is_delete); + if (perm & 1) + update_single_path_acl(TMY_TYPE_EXECUTE_ACL, filename, domain, + is_delete); + return 0; +} + +/** + * check_single_path_acl2 - Check permission for single path operation. + * + * @domain: Pointer to "struct domain_info". + * @filename: Filename to check. + * @perm: Permission. + * @may_use_pattern: True if patterned ACL is permitted. + * + * Returns 0 on success, -EPERM otherwise. + */ +static int check_single_path_acl2(const struct domain_info *domain, + const struct path_info *filename, + const u16 perm, const bool may_use_pattern) +{ + struct acl_info *ptr; + list1_for_each_entry(ptr, &domain->acl_info_list, list) { + struct single_path_acl_record *acl; + if (tmy_acl_type2(ptr) != TYPE_SINGLE_PATH_ACL) + continue; + acl = container_of(ptr, struct single_path_acl_record, head); + if (!(acl->perm & perm)) + continue; + if (may_use_pattern || !acl->filename->is_patterned) { + if (!tmy_path_matches_pattern(filename, + acl->filename)) + continue; + } else { + continue; + } + return 0; + } + return -EPERM; +} + +/** + * check_file_acl - Check permission for opening files. + * + * @domain: Pointer to "struct domain_info". + * @filename: Filename to check. + * @operation: Mode ("read" or "write" or "read/write" or "execute"). + * + * Returns 0 on success, -EPERM otherwise. + */ +static int check_file_acl(const struct domain_info *domain, + const struct path_info *filename, const u8 operation) +{ + u16 perm = 0; + if (!tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE)) + return 0; + if (operation == 6) + perm = 1 << TMY_TYPE_READ_WRITE_ACL; + else if (operation == 4) + perm = 1 << TMY_TYPE_READ_ACL; + else if (operation == 2) + perm = 1 << TMY_TYPE_WRITE_ACL; + else if (operation == 1) + perm = 1 << TMY_TYPE_EXECUTE_ACL; + else + BUG(); + return check_single_path_acl2(domain, filename, perm, operation != 1); +} + +/** + * check_file_perm2 - Check permission for opening files. + * + * @domain: Pointer to "struct domain_info". + * @filename: Filename to check. + * @perm: Mode ("read" or "write" or "read/write" or "execute"). + * @operation: Operation name passed used for verbose mode. + * @mode: Access control mode. + * + * Returns 0 on success, negative value otherwise. + */ +static int check_file_perm2(struct domain_info * const domain, + const struct path_info *filename, const u8 perm, + const char *operation, const u8 mode) +{ + const bool is_enforce = (mode == 3); + const char *msg = ""; + int error = 0; + if (!filename) + return 0; + error = check_file_acl(domain, filename, perm); + if (error && perm == 4 && + (domain->flags & DOMAIN_FLAGS_IGNORE_GLOBAL_ALLOW_READ) == 0 && + is_globally_readable_file(filename)) + error = 0; + if (perm == 6) + msg = tmy_sp2keyword(TMY_TYPE_READ_WRITE_ACL); + else if (perm == 4) + msg = tmy_sp2keyword(TMY_TYPE_READ_ACL); + else if (perm == 2) + msg = tmy_sp2keyword(TMY_TYPE_WRITE_ACL); + else if (perm == 1) + msg = tmy_sp2keyword(TMY_TYPE_EXECUTE_ACL); + else + BUG(); + if (!error) + return 0; + if (tmy_verbose_mode(domain)) + printk(KERN_WARNING "TOMOYO-%s: Access '%s(%s) %s' denied " + "for %s\n", tmy_get_msg(is_enforce), msg, operation, + filename->name, tmy_get_last_name(domain)); + if (is_enforce) + return error; + if (mode == 1 && tmy_check_domain_quota(domain)) { + /* Don't use patterns for execute permission. */ + const struct path_info *patterned_file = (perm != 1) ? + get_file_pattern(filename) : filename; + update_file_acl(patterned_file->name, perm, + domain, false); + } + return 0; +} + +/** + * tmy_write_file_policy - Update file related list. + * + * @data: String to parse. + * @domain: Pointer to "struct domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_write_file_policy(char *data, struct domain_info *domain, + const bool is_delete) +{ + char *filename = strchr(data, ' '); + char *filename2; + unsigned int perm; + u8 type; + if (!filename) + return -EINVAL; + *filename++ = '\0'; + if (sscanf(data, "%u", &perm) == 1) + return update_file_acl(filename, (u8) perm, domain, is_delete); + if (strncmp(data, "allow_", 6)) + goto out; + data += 6; + for (type = 0; type < MAX_SINGLE_PATH_OPERATION; type++) { + if (strcmp(data, sp_keyword[type])) + continue; + return update_single_path_acl(type, filename, + domain, is_delete); + } + filename2 = strchr(filename, ' '); + if (!filename2) + goto out; + *filename2++ = '\0'; + for (type = 0; type < MAX_DOUBLE_PATH_OPERATION; type++) { + if (strcmp(data, dp_keyword[type])) + continue; + return update_double_path_acl(type, filename, filename2, domain, + is_delete); + } +out: + return -EINVAL; +} + +/** + * update_single_path_acl - Update "struct single_path_acl_record" list. + * + * @type: Type of operation. + * @filename: Filename. + * @domain: Pointer to "struct domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int update_single_path_acl(const u8 type, const char *filename, + struct domain_info * const domain, + const bool is_delete) +{ + static const u16 rw_mask = + (1 << TMY_TYPE_READ_ACL) | (1 << TMY_TYPE_WRITE_ACL); + const struct path_info *saved_filename; + struct acl_info *ptr; + struct single_path_acl_record *acl; + int error = -ENOMEM; + const u16 perm = 1 << type; + if (!domain) + return -EINVAL; + if (!tmy_is_correct_path(filename, 0, 0, 0, __func__)) + return -EINVAL; + saved_filename = tmy_save_name(filename); + if (!saved_filename) + return -ENOMEM; + mutex_lock(&domain_acl_lock); + if (is_delete) + goto delete; + list1_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tmy_acl_type1(ptr) != TYPE_SINGLE_PATH_ACL) + continue; + acl = container_of(ptr, struct single_path_acl_record, head); + if (acl->filename != saved_filename) + continue; + /* Special case. Clear all bits if marked as deleted. */ + if (ptr->type & ACL_DELETED) + acl->perm = 0; + acl->perm |= perm; + if ((acl->perm & rw_mask) == rw_mask) + acl->perm |= 1 << TMY_TYPE_READ_WRITE_ACL; + else if (acl->perm & (1 << TMY_TYPE_READ_WRITE_ACL)) + acl->perm |= rw_mask; + error = tmy_add_domain_acl(NULL, ptr); + goto out; + } + /* Not found. Append it to the tail. */ + acl = tmy_alloc_acl_element(TYPE_SINGLE_PATH_ACL); + if (!acl) + goto out; + acl->perm = perm; + acl->filename = saved_filename; + error = tmy_add_domain_acl(domain, &acl->head); + goto out; +delete: + error = -ENOENT; + list1_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tmy_acl_type2(ptr) != TYPE_SINGLE_PATH_ACL) + continue; + acl = container_of(ptr, struct single_path_acl_record, head); + if (acl->filename != saved_filename) + continue; + acl->perm &= ~perm; + if ((acl->perm & rw_mask) != rw_mask) + acl->perm &= ~(1 << TMY_TYPE_READ_WRITE_ACL); + else if (!(acl->perm & (1 << TMY_TYPE_READ_WRITE_ACL))) + acl->perm &= ~rw_mask; + error = tmy_del_domain_acl(acl->perm ? NULL : ptr); + break; + } +out: + mutex_unlock(&domain_acl_lock); + return error; +} + +/** + * update_double_path_acl - Update "struct double_path_acl_record" list. + * + * @type: Type of operation. + * @filename1: First filename. + * @filename2: Second filename. + * @domain: Pointer to "struct domain_info". + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int update_double_path_acl(const u8 type, const char *filename1, + const char *filename2, + struct domain_info * const domain, + const bool is_delete) +{ + const struct path_info *saved_filename1; + const struct path_info *saved_filename2; + struct acl_info *ptr; + struct double_path_acl_record *acl; + int error = -ENOMEM; + const u8 perm = 1 << type; + if (!domain) + return -EINVAL; + if (!tmy_is_correct_path(filename1, 0, 0, 0, __func__) || + !tmy_is_correct_path(filename2, 0, 0, 0, __func__)) + return -EINVAL; + saved_filename1 = tmy_save_name(filename1); + saved_filename2 = tmy_save_name(filename2); + if (!saved_filename1 || !saved_filename2) + return -ENOMEM; + mutex_lock(&domain_acl_lock); + if (is_delete) + goto delete; + list1_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tmy_acl_type1(ptr) != TYPE_DOUBLE_PATH_ACL) + continue; + acl = container_of(ptr, struct double_path_acl_record, head); + if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) + continue; + /* Special case. Clear all bits if marked as deleted. */ + if (ptr->type & ACL_DELETED) + acl->perm = 0; + acl->perm |= perm; + error = tmy_add_domain_acl(NULL, ptr); + goto out; + } + /* Not found. Append it to the tail. */ + acl = tmy_alloc_acl_element(TYPE_DOUBLE_PATH_ACL); + if (!acl) + goto out; + acl->perm = perm; + acl->filename1 = saved_filename1; + acl->filename2 = saved_filename2; + error = tmy_add_domain_acl(domain, &acl->head); + goto out; +delete: + error = -ENOENT; + list1_for_each_entry(ptr, &domain->acl_info_list, list) { + if (tmy_acl_type2(ptr) != TYPE_DOUBLE_PATH_ACL) + continue; + acl = container_of(ptr, struct double_path_acl_record, head); + if (acl->filename1 != saved_filename1 || + acl->filename2 != saved_filename2) + continue; + acl->perm &= ~perm; + error = tmy_del_domain_acl(acl->perm ? NULL : ptr); + break; + } +out: + mutex_unlock(&domain_acl_lock); + return error; +} + +/** + * check_single_path_acl - Check permission for single path operation. + * + * @domain: Pointer to "struct domain_info". + * @type: Type of operation. + * @filename: Filename to check. + * + * Returns 0 on success, negative value otherwise. + */ +static int check_single_path_acl(struct domain_info *domain, const u8 type, + const struct path_info *filename) +{ + if (!tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE)) + return 0; + return check_single_path_acl2(domain, filename, 1 << type, 1); +} + +/** + * check_double_path_acl - Check permission for double path operation. + * + * @domain: Pointer to "struct domain_info". + * @type: Type of operation. + * @filename1: First filename to check. + * @filename2: Second filename to check. + * + * Returns 0 on success, -EPERM otherwise. + */ +static int check_double_path_acl(const struct domain_info *domain, + const u8 type, + const struct path_info *filename1, + const struct path_info *filename2) +{ + struct acl_info *ptr; + const u8 perm = 1 << type; + if (!tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE)) + return 0; + list1_for_each_entry(ptr, &domain->acl_info_list, list) { + struct double_path_acl_record *acl; + if (tmy_acl_type2(ptr) != TYPE_DOUBLE_PATH_ACL) + continue; + acl = container_of(ptr, struct double_path_acl_record, head); + if (!(acl->perm & perm)) + continue; + if (!tmy_path_matches_pattern(filename1, + acl->filename1)) + continue; + if (!tmy_path_matches_pattern(filename2, + acl->filename2)) + continue; + return 0; + } + return -EPERM; +} + +/** + * check_single_path_permission2 - Check permission for single path operation. + * + * @domain: Pointer to "struct domain_info". + * @operation: Type of operation. + * @filename: Filename to check. + * @mode: Access control mode. + * + * Returns 0 on success, negative value otherwise. + */ +static int check_single_path_permission2(struct domain_info * const domain, + u8 operation, + const struct path_info *filename, + const u8 mode) +{ + const char *msg; + int error; + const bool is_enforce = (mode == 3); + if (!mode) + return 0; +next: + error = check_single_path_acl(domain, operation, filename); + msg = tmy_sp2keyword(operation); + if (!error) + goto ok; + if (tmy_verbose_mode(domain)) + printk(KERN_WARNING "TOMOYO-%s: Access '%s %s' denied for %s\n", + tmy_get_msg(is_enforce), msg, filename->name, + tmy_get_last_name(domain)); + if (mode == 1 && tmy_check_domain_quota(domain)) + update_single_path_acl(operation, + get_file_pattern(filename)->name, + domain, false); + if (!is_enforce) + error = 0; +ok: + /* + * Since "allow_truncate" doesn't imply "allow_rewrite" permission, + * we need to check "allow_rewrite" permission if the filename is + * specified by "deny_rewrite" keyword. + */ + if (!error && operation == TMY_TYPE_TRUNCATE_ACL && + is_no_rewrite_file(filename)) { + operation = TMY_TYPE_REWRITE_ACL; + goto next; + } + return error; +} + +/** + * tmy_check_file_perm - Check permission for sysctl()'s "read" and "write". + * + * @domain: Pointer to "struct domain_info". + * @filename: Filename to check. + * @perm: Mode ("read" or "write" or "read/write"). + * @operation: Always "sysctl". + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_check_file_perm(struct domain_info *domain, const char *filename, + const u8 perm, const char *operation) +{ + struct path_info name; + const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE); + if (!mode) + return 0; + name.name = filename; + tmy_fill_path_info(&name); + return check_file_perm2(domain, &name, perm, operation, mode); +} + +/** + * tmy_check_exec_perm - Check permission for "execute". + * + * @domain: Pointer to "struct domain_info". + * @filename: Check permission for "execute". + * @tmp: Buffer for temporal use. + * + * Returns 0 on success, negativevalue otherwise. + */ +int tmy_check_exec_perm(struct domain_info *domain, + const struct path_info *filename, + struct tmy_page_buffer *tmp) +{ + const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE); + if (!mode) + return 0; + return check_file_perm2(domain, filename, 1, "do_execve", mode); +} + +/** + * tmy_check_open_permission - Check permission for "read" and "write". + * + * @domain: Pointer to "struct domain_info". + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". + * @flag: Flags for open(). + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_check_open_permission(struct domain_info *domain, + struct dentry *dentry, struct vfsmount *mnt, + const int flag) +{ + const u8 acc_mode = ACC_MODE(flag); + int error = -ENOMEM; + struct path_info *buf; + const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + if (!mode || !mnt) + 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; + error = 0; + /* + * If the filename is specified by "deny_rewrite" keyword, + * we need to check "allow_rewrite" permission when the filename is not + * opened for append mode or the filename is truncated at open time. + */ + if ((acc_mode & MAY_WRITE) && + ((flag & O_TRUNC) || !(flag & O_APPEND)) && + (is_no_rewrite_file(buf))) { + error = check_single_path_permission2(domain, + TMY_TYPE_REWRITE_ACL, + buf, mode); + } + if (!error) + error = check_file_perm2(domain, buf, acc_mode, "open", mode); + if (!error && (flag & O_TRUNC)) + error = check_single_path_permission2(domain, + TMY_TYPE_TRUNCATE_ACL, + buf, mode); +out: + tmy_free(buf); + if (!is_enforce) + error = 0; + return error; +} + +/** + * tmy_check_1path_perm - Check permission for "create", "unlink", "mkdir", "rmdir", "mkfifo", "mksock", "mkblock", "mkchar", "truncate" and "symlink". + * + * @domain: Pointer to "struct domain_info". + * @operation: Type of operation. + * @dentry: Pointer to "struct dentry". + * @mnt: Pointer to "struct vfsmount". + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_check_1path_perm(struct domain_info *domain, const u8 operation, + struct dentry *dentry, struct vfsmount *mnt) +{ + int error = -ENOMEM; + struct path_info *buf; + const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + if (!mode || !mnt) + return 0; + buf = tmy_get_path(dentry, mnt); + if (!buf) + goto out; + switch (operation) { + case TMY_TYPE_MKDIR_ACL: + case TMY_TYPE_RMDIR_ACL: + if (!buf->is_dir) { + /* tmy_get_path() preserves space for appending "/." */ + strcat((char *) buf->name, "/"); + tmy_fill_path_info(buf); + } + } + error = check_single_path_permission2(domain, operation, buf, mode); +out: + tmy_free(buf); + if (!is_enforce) + error = 0; + return error; +} + +/** + * tmy_check_rewrite_permission - Check permission for "rewrite". + * + * @domain: Pointer to "struct domain_info". + * @filp: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_check_rewrite_permission(struct domain_info *domain, struct file *filp) +{ + int error = -ENOMEM; + const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + struct path_info *buf; + if (!mode || !filp->f_path.mnt) + return 0; + buf = tmy_get_path(filp->f_path.dentry, filp->f_path.mnt); + if (!buf) + goto out; + if (!is_no_rewrite_file(buf)) { + error = 0; + goto out; + } + error = check_single_path_permission2(domain, TMY_TYPE_REWRITE_ACL, + buf, mode); +out: + tmy_free(buf); + if (!is_enforce) + error = 0; + return error; +} + +/** + * tmy_check_2path_perm - Check permission for "rename" and "link". + * + * @domain: Pointer to "struct domain_info". + * @operation: Type of operation. + * @dentry1: Pointer to "struct dentry". + * @mnt1: Pointer to "struct vfsmount". + * @dentry2: Pointer to "struct dentry". + * @mnt2: Pointer to "struct vfsmount". + * + * Returns 0 on success, negative value otherwise. + */ +int tmy_check_2path_perm(struct domain_info * const domain, const u8 operation, + struct dentry *dentry1, struct vfsmount *mnt1, + struct dentry *dentry2, struct vfsmount *mnt2) +{ + int error = -ENOMEM; + struct path_info *buf1, *buf2; + const u8 mode = tmy_check_flags(domain, TMY_TOMOYO_MAC_FOR_FILE); + const bool is_enforce = (mode == 3); + const char *msg; + if (!mode || !mnt1 || !mnt2) + return 0; + buf1 = tmy_get_path(dentry1, mnt1); + buf2 = tmy_get_path(dentry2, mnt2); + if (!buf1 || !buf2) + goto out; + if (operation == TMY_TYPE_RENAME_ACL) { + /* TYPE_LINK_ACL can't reach here for directory. */ + if (dentry1->d_inode && S_ISDIR(dentry1->d_inode->i_mode)) { + /* tmy_get_path() preserves space for appending "/." */ + 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 = check_double_path_acl(domain, operation, buf1, buf2); + msg = tmy_dp2keyword(operation); + if (!error) + goto out; + if (tmy_verbose_mode(domain)) + printk(KERN_WARNING "TOMOYO-%s: Access '%s %s %s' " + "denied for %s\n", tmy_get_msg(is_enforce), + msg, buf1->name, buf2->name, tmy_get_last_name(domain)); + if (mode == 1 && tmy_check_domain_quota(domain)) + update_double_path_acl(operation, + get_file_pattern(buf1)->name, + get_file_pattern(buf2)->name, + domain, false); +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/