All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kentaro Takeda <takedakn@nttdata.co.jp>
To: Stephen Smalley <sds@tycho.nsa.gov>,
	James Morris <jmorris@namei.org>,
	Chris Wright <chrisw@sous-sol.org>
Cc: "Serge E. Hallyn" <serue@us.ibm.com>,
	David Howells <dhowells@redhat.com>,
	linux-security-module@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	Toshiharu Harada <haradats@nttdata.co.jp>,
	Andrew Morton <akpm@linux-foundation.org>,
	Kentaro Takeda <takedakn@nttdata.co.jp>,
	Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Subject: [TOMOYO #10 (linux-next) 7/8] File operation restriction part.
Date: Thu, 09 Oct 2008 13:28:21 +0900	[thread overview]
Message-ID: <20081009042922.939610141@nttdata.co.jp> (raw)
In-Reply-To: 20081009042814.398846861@nttdata.co.jp

This file controls file related operations.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp>
---
 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 = "<unknown>";
+	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;
+}

--


  parent reply	other threads:[~2008-10-09  4:32 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-10-09  4:28 [TOMOYO #10 (linux-next) 0/8] TOMOYO Linux Kentaro Takeda
2008-10-09  4:28 ` [TOMOYO #10 (linux-next) 1/8] Introduce new LSM hooks where vfsmount is available Kentaro Takeda
2008-10-09  4:28 ` [TOMOYO #10 (linux-next) 2/8] Add in_execve flag into task_struct Kentaro Takeda
2008-10-09  4:28 ` [TOMOYO #10 (linux-next) 3/8] LSM adapter functions Kentaro Takeda
2008-10-09  6:10   ` KAMEZAWA Hiroyuki
2008-10-09  6:57     ` Kentaro Takeda
2008-10-09  4:28 ` [TOMOYO #10 (linux-next) 4/8] Memory and pathname management functions Kentaro Takeda
2008-10-09  6:18   ` KAMEZAWA Hiroyuki
2008-10-09  7:17     ` Kentaro Takeda
2008-10-09  4:28 ` [TOMOYO #10 (linux-next) 5/8] Common functions for TOMOYO Linux Kentaro Takeda
2008-10-09  4:28 ` [TOMOYO #10 (linux-next) 6/8] Domain transition handler Kentaro Takeda
2008-10-09  4:28 ` Kentaro Takeda [this message]
2008-10-09 16:48   ` [TOMOYO #10 (linux-next) 7/8] File operation restriction part Serge E. Hallyn
2008-10-12  0:09     ` Tetsuo Handa
2008-10-15  1:29       ` Paul E. McKenney
2008-10-16  4:05         ` Kentaro Takeda
2008-10-16 15:10           ` Paul E. McKenney
2008-10-17  8:32             ` Kentaro Takeda
2008-10-17 14:56               ` Paul E. McKenney
2008-10-18 14:04                 ` Tetsuo Handa
2008-10-18 15:18                   ` Paul E. McKenney
2008-10-19 13:10                     ` Tetsuo Handa
2008-10-20  4:17                       ` Paul E. McKenney
2008-10-15 15:24       ` Serge E. Hallyn
2008-10-09  4:28 ` [TOMOYO #10 (linux-next) 8/8] Kconfig and Makefile Kentaro Takeda

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20081009042922.939610141@nttdata.co.jp \
    --to=takedakn@nttdata.co.jp \
    --cc=akpm@linux-foundation.org \
    --cc=chrisw@sous-sol.org \
    --cc=dhowells@redhat.com \
    --cc=haradats@nttdata.co.jp \
    --cc=jmorris@namei.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=penguin-kernel@I-love.SAKURA.ne.jp \
    --cc=sds@tycho.nsa.gov \
    --cc=serue@us.ibm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.