LKML Archive on 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>,
	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 #11 (linux-next) 07/11] File operation restriction part.
Date: Mon, 20 Oct 2008 16:34:30 +0900	[thread overview]
Message-ID: <20081020073655.338744544@nttdata.co.jp> (raw)
In-Reply-To: <20081020073423.024299308@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 | 1232 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1232 insertions(+)

--- /dev/null
+++ linux-next/security/tomoyo/file.c
@@ -0,0 +1,1232 @@
+/*
+ * 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/10
+ *
+ */
+
+#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.
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns pointer to "struct path_info" on success, NULL otherwise.
+ */
+static struct path_info *tmy_get_path(struct path *path)
+{
+	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_path2(path, 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);
+
+/**
+ * tmy_add_domain_acl - Add the given ACL to the given domain.
+ *
+ * @domain: Pointer to "struct domain_info". May be NULL.
+ * @acl:    Pointer to "struct acl_info".
+ *
+ * Returns 0.
+ */
+static int tmy_add_domain_acl(struct domain_info *domain, struct acl_info *acl)
+{
+	if (domain) {
+		/*
+		 * We need to serialize because this function is called by
+		 * update_single_path_acl() and update_double_path_acl().
+		 */
+		static DEFINE_SPINLOCK(lock);
+		/***** CRITICAL SECTION START *****/
+		spin_lock(&lock);
+		list1_add_tail(&acl->list, &domain->acl_info_list);
+		spin_unlock(&lock);
+		/***** CRITICAL SECTION END *****/
+	} else {
+		acl->type &= ~ACL_DELETED;
+	}
+	tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/**
+ * tmy_del_domain_acl - Delete the given ACL from the domain.
+ *
+ * @acl: Pointer to "struct acl_info". May be NULL.
+ *
+ * Returns 0.
+ */
+static int tmy_del_domain_acl(struct acl_info *acl)
+{
+	if (acl)
+		acl->type |= ACL_DELETED;
+	tmy_update_counter(TMY_UPDATES_COUNTER_DOMAIN_POLICY);
+	return 0;
+}
+
+/*
+ * The list for "struct globally_readable_file_entry".
+ *
+ * This list is updated only inside update_globally_readable_entry(), thus
+ * no global mutex exists.
+ */
+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;
+	/***** EXCLUSIVE SECTION START *****/
+	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(&new_entry->list, &globally_readable_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	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".
+ *
+ * This list is updated only inside update_file_pattern_entry(), thus
+ * no global mutex exists.
+ */
+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;
+	/***** EXCLUSIVE SECTION START *****/
+	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(&new_entry->list, &pattern_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	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".
+ *
+ * This list is updated only inside update_no_rewrite_entry(), thus
+ * no global mutex exists.
+ */
+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;
+	/***** EXCLUSIVE SECTION START *****/
+	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(&new_entry->list, &no_rewrite_list);
+	error = 0;
+out:
+	mutex_unlock(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	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 DEFINE_MUTEX(lock);
+	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;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&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(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	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)
+{
+	static DEFINE_MUTEX(lock);
+	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;
+	/***** EXCLUSIVE SECTION START *****/
+	mutex_lock(&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(&lock);
+	/***** EXCLUSIVE SECTION END *****/
+	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").
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_file_perm(struct domain_info *domain, const char *filename,
+			const u8 perm)
+{
+	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, "sysctl", 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".
+ * @path:   Pointer to "struct path".
+ * @flag:   Flags for open().
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_open_permission(struct domain_info *domain,
+			      struct path *path, 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 || !path->mnt)
+		return 0;
+	if (acc_mode == 0)
+		return 0;
+	if (path->dentry->d_inode && S_ISDIR(path->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(path);
+	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.
+ * @path:      Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_1path_perm(struct domain_info *domain, const u8 operation,
+			 struct path *path)
+{
+	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 || !path->mnt)
+		return 0;
+	buf = tmy_get_path(path);
+	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);
+	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.
+ * @path1:      Pointer to "struct path".
+ * @path2:      Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int tmy_check_2path_perm(struct domain_info * const domain, const u8 operation,
+			 struct path *path1, struct path *path2)
+{
+	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 || !path1->mnt || !path2->mnt)
+		return 0;
+	buf1 = tmy_get_path(path1);
+	buf2 = tmy_get_path(path2);
+	if (!buf1 || !buf2)
+		goto out;
+	{
+		struct dentry *dentry = path1->dentry;
+		if (dentry->d_inode && S_ISDIR(dentry->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-20  7:39 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-10-20  7:34 [TOMOYO #11 (linux-next) 00/11] TOMOYO Linux Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 01/11] Introduce new LSM hooks where vfsmount is available Kentaro Takeda
2008-10-20 12:27   ` Shaya Potter
2008-10-20 19:34     ` crispin
2008-10-20 21:23       ` Shaya Potter
2008-10-23 17:57         ` Shaya Potter
2008-10-20 16:44   ` Miklos Szeredi
2008-10-21  5:09     ` Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 02/11] Add in_execve flag into task_struct Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 03/11] Singly linked list implementation Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 04/11] Introduce d_realpath() Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 05/11] Memory and pathname management functions Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 06/11] Common functions for TOMOYO Linux Kentaro Takeda
2008-10-20  7:34 ` Kentaro Takeda [this message]
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 08/11] Domain transition handler Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 09/11] LSM adapter functions Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 10/11] Kconfig and Makefile Kentaro Takeda
2008-10-20  7:34 ` [TOMOYO #11 (linux-next) 11/11] MAINTAINERS info Kentaro Takeda
2008-10-27  2:18 ` [TOMOYO #11 (linux-next) 00/11] TOMOYO Linux Kentaro Takeda
2008-10-29 19:18   ` Serge E. Hallyn
2008-10-30  5:27     ` Toshiharu Harada

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=20081020073655.338744544@nttdata.co.jp \
    --to=takedakn@nttdata.co.jp \
    --cc=akpm@linux-foundation.org \
    --cc=chrisw@sous-sol.org \
    --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 \
    --subject='Re: [TOMOYO #11 (linux-next) 07/11] File operation restriction part.' \
    /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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).