All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kentaro Takeda <takedakn@nttdata.co.jp>
To: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org
Cc: chrisw@sous-sol.org
Subject: [TOMOYO 05/15](repost) Domain transition handler functions.
Date: Tue, 02 Oct 2007 16:32:41 +0900	[thread overview]
Message-ID: <4701F419.8080103@nttdata.co.jp> (raw)
In-Reply-To: <4701F285.5000206@nttdata.co.jp>

Domain transition functions for TOMOYO Linux.
Every process belongs to a domain in TOMOYO Linux.
Domain transition occurs when execve(2) is called
and the domain is expressed as 'process invocation history',
such as '<kernel> /sbin/init /etc/init.d/rc'.
Domain information is stored in task_struct->security.

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

--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6/security/tomoyo/domain.c	2007-10-02 11:26:21.000000000 +0900
@@ -0,0 +1,1256 @@
+/*
+ * security/tomoyo/domain.c
+ *
+ * Domain transition functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+#include <linux/highmem.h>
+#include <linux/binfmts.h>
+
+/*************************  VARIABLES  *************************/
+
+/* Lock for appending domain's ACL. */
+DECLARE_MUTEX(domain_acl_lock);
+
+/* Domain creation lock. */
+static DECLARE_MUTEX(new_domain_assign_lock);
+
+/***** The structure for program files to force domain reconstruction. *****/
+
+struct domain_initializer_entry {
+	struct domain_initializer_entry *next;
+	const struct path_info *domainname;    /* This may be NULL */
+	const struct path_info *program;
+	u8 is_deleted;
+	u8 is_not;
+	u8 is_last_name;
+};
+
+/***** The structure for domains to not to transit domains. *****/
+
+struct domain_keeper_entry {
+	struct domain_keeper_entry *next;
+	const struct path_info *domainname;
+	const struct path_info *program;       /* This may be NULL */
+	u8 is_deleted;
+	u8 is_not;
+	u8 is_last_name;
+};
+
+/***** The structure for program files that should be aggregated. *****/
+
+struct aggregator_entry {
+	struct aggregator_entry *next;
+	const struct path_info *original_name;
+	const struct path_info *aggregated_name;
+	u8 is_deleted;
+};
+
+/***** The structure for program files that should be aliased. *****/
+
+struct alias_entry {
+	struct alias_entry *next;
+	const struct path_info *original_name;
+	const struct path_info *aliased_name;
+	u8 is_deleted;
+};
+
+/*************************  UTILITY FUNCTIONS  *************************/
+
+/**
+ * tmy_is_domain_def - check if the line is likely a domain definition.
+ * @buffer: the line to check.
+ *
+ * Returns nonzero if @buffer is likely a domain definition.
+ * Returns zero otherwise.
+ *
+ * For complete validation check, use tmy_is_correct_domain().
+ */
+int tmy_is_domain_def(const unsigned char *buffer)
+{
+	return strncmp(buffer, TMY_ROOT_NAME, TMY_ROOT_NAME_LEN) == 0;
+}
+
+/**
+ * tmy_add_acl - add an entry to a domain.
+ * @ptr: pointer to the last "struct acl_info" of @domain. May be NULL.
+ * @domain: pointer to "struct domain_info".
+ * @new_ptr: pointer to "struct acl_info" to add.
+ *
+ * Returns zero.
+ *
+ * To be honest, I can calculate @ptr from @domain.
+ * But since the caller knows @ptr, I use it.
+ */
+int tmy_add_acl(struct acl_info *ptr,
+		struct domain_info *domain,
+		struct acl_info *new_ptr)
+{
+	mb(); /* Instead of using spinlock. */
+
+	if (!ptr)
+		domain->first_acl_ptr = (struct acl_info *) new_ptr;
+	else
+		ptr->next = (struct acl_info *) new_ptr;
+
+	tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+	return 0;
+}
+
+/**
+ * tmy_del_acl - remove an entry from a domain
+ * @ptr: pointer to "struct acl_info" to remove.
+ *
+ * Returns zero.
+ *
+ * TOMOYO Linux doesn't free memory used by policy because policies are not
+ * so frequently changed after entring into enforcing mode.
+ * This makes the code free of read-lock.
+ * The caller uses "down(&domain_acl_lock);" as write-lock.
+ */
+int tmy_del_acl(struct acl_info *ptr)
+{
+	ptr->is_deleted = 1;
+	tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+	return 0;
+}
+
+/************************  DOMAIN INITIALIZER HANDLER  ************************/
+
+static struct domain_initializer_entry *domain_initializer_list;
+
+/* Update domain initializer list. */
+static int tmy_add_domain_initializer_entry(const char *domainname,
+					    const char *program,
+					    const u8 is_not,
+					    const u8 is_delete)
+{
+	struct domain_initializer_entry *new_entry;
+	struct domain_initializer_entry *ptr;
+	static DECLARE_MUTEX(lock);
+	const struct path_info *saved_program;
+	const struct path_info *saved_domainname = NULL;
+	int error = -ENOMEM;
+	u8 is_last_name = 0;
+
+	if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+		return -EINVAL; /* No patterns allowed. */
+
+	if (domainname) {
+		if (!tmy_is_domain_def(domainname) &&
+		    tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+			is_last_name = 1;
+
+		else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+			return -EINVAL;
+
+		saved_domainname = tmy_save_name(domainname);
+		if (!saved_domainname)
+			return -ENOMEM;
+	}
+
+	saved_program = tmy_save_name(program);
+	if (!saved_program)
+		return -ENOMEM;
+
+	down(&lock);
+
+	for (ptr = domain_initializer_list; ptr; ptr = ptr->next) {
+		if (ptr->is_not == is_not &&
+		    ptr->domainname == saved_domainname &&
+		    ptr->program == saved_program) {
+			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->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+
+	mb(); /* Instead of using spinlock. */
+	ptr = domain_initializer_list;
+	if (ptr) {
+		while (ptr->next)
+			ptr = ptr->next;
+		ptr->next = new_entry;
+	} else
+		domain_initializer_list = new_entry;
+
+	error = 0;
+out: ;
+
+	up(&lock);
+	return error;
+}
+
+/**
+ * tmy_read_domain_initializer_policy - read domain initializer policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_initializer_policy(struct io_buffer *head)
+{
+	struct domain_initializer_entry *ptr = head->read_var2;
+
+	if (!ptr)
+		ptr = domain_initializer_list;
+
+	while (ptr) {
+		head->read_var2 = ptr;
+		if (!ptr->is_deleted) {
+			const char *s1 = ptr->is_not ? "no_" : "";
+			const char *s2 = TMY_INITIALIZE_DOMAIN;
+			const char *s3 = ptr->program->name;
+
+			if (ptr->domainname) {
+				if (tmy_io_printf(head, "%s%s%s from %s\n",
+						  s1, s2, s3,
+						  ptr->domainname->name))
+					break;
+			} else {
+				if (tmy_io_printf(head, "%s%s%s\n",
+						  s1, s2, s3))
+					break;
+			}
+		}
+		ptr = ptr->next;
+	}
+
+	return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_domain_initializer_policy - add domain initializer policy
+ * @data:      a line to parse.
+ * @is_not:    is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain initializer entry.
+ */
+int tmy_add_domain_initializer_policy(char *data,
+				      const u8 is_not,
+				      const u8 is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return tmy_add_domain_initializer_entry(cp + 6,	data, is_not,
+							is_delete);
+	}
+
+	return tmy_add_domain_initializer_entry(NULL, data, is_not, is_delete);
+}
+
+/* Should I transit to a domain under "<kernel>" domain? */
+static int tmy_is_domain_initializer(const struct path_info *domainname,
+				     const struct path_info *program,
+				     const struct path_info *last_name)
+{
+	struct domain_initializer_entry *ptr;
+	int flag = 0;
+
+	for (ptr = domain_initializer_list; ptr; ptr = ptr->next) {
+		if (ptr->is_deleted)
+			continue;
+
+		if (ptr->domainname) {
+			if (!ptr->is_last_name) {
+				if (ptr->domainname != domainname)
+					continue;
+			} else {
+				if (tmy_pathcmp(ptr->domainname, last_name))
+					continue;
+			}
+		}
+
+		if (tmy_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return 0;
+
+		flag = 1;
+	}
+
+	return flag;
+}
+
+/*************************  DOMAIN KEEPER HANDLER  *************************/
+
+static struct domain_keeper_entry *domain_keeper_list;
+
+/* Update domain keeper list. */
+static int tmy_add_domain_keeper_entry(const char *domainname,
+				       const char *program,
+				       const u8 is_not,
+				       const u8 is_delete)
+{
+	struct domain_keeper_entry *new_entry;
+	struct domain_keeper_entry *ptr;
+	const struct path_info *saved_domainname;
+	const struct path_info *saved_program = NULL;
+	static DECLARE_MUTEX(lock);
+	int error = -ENOMEM;
+	u8 is_last_name = 0;
+
+	if (!tmy_is_domain_def(domainname) &&
+	    tmy_correct_path(domainname, 1, -1, -1, __FUNCTION__))
+		is_last_name = 1;
+
+	else if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+		return -EINVAL;
+
+	if (program) {
+		if (!tmy_correct_path(program, 1, -1, -1, __FUNCTION__))
+			return -EINVAL;
+
+		saved_program = tmy_save_name(program);
+		if (!saved_program)
+			return -ENOMEM;
+	}
+
+	saved_domainname = tmy_save_name(domainname);
+	if (!saved_domainname)
+		return -ENOMEM;
+
+	down(&lock);
+
+	for (ptr = domain_keeper_list; ptr; ptr = ptr->next) {
+		if (ptr->is_not == is_not &&
+		    ptr->domainname == saved_domainname &&
+		    ptr->program == saved_program) {
+			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->domainname = saved_domainname;
+	new_entry->program = saved_program;
+	new_entry->is_not = is_not;
+	new_entry->is_last_name = is_last_name;
+
+	mb(); /* Instead of using spinlock. */
+	ptr = domain_keeper_list;
+	if (ptr) {
+		while (ptr->next)
+			ptr = ptr->next;
+		ptr->next = new_entry;
+	} else
+		domain_keeper_list = new_entry;
+	error = 0;
+
+out: ;
+
+	up(&lock);
+	return error;
+}
+
+/**
+ * tmy_add_domain_keeper_policy - add domain keeper policy.
+ * @data:      a line to parse.
+ * @is_not:    is this overriding?
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes a domain keeper entry.
+ *
+ */
+int tmy_add_domain_keeper_policy(char *data,
+				 const u8 is_not,
+				 const u8 is_delete)
+{
+	char *cp = strstr(data, " from ");
+
+	if (cp) {
+		*cp = '\0';
+		return tmy_add_domain_keeper_entry(cp + 6, data,
+						   is_not, is_delete);
+	}
+
+	return tmy_add_domain_keeper_entry(data, NULL, is_not, is_delete);
+}
+
+/**
+ * tmy_read_domain_keeper_policy - read domain keeper policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_domain_keeper_policy(struct io_buffer *head)
+{
+	struct domain_keeper_entry *ptr = head->read_var2;
+
+	if (!ptr)
+		ptr = domain_keeper_list;
+
+	for (; ptr; ptr = ptr->next) {
+		head->read_var2 = ptr;
+		if (ptr->is_deleted)
+			continue;
+
+		if (ptr->program) {
+			if (tmy_io_printf(head,
+					  "%s" TMY_KEEP_DOMAIN
+					  "%s from %s\n",
+					  ptr->is_not ? "no_" : "",
+					  ptr->program->name,
+					  ptr->domainname->name))
+				break;
+
+		} else {
+			if (tmy_io_printf(head,
+					  "%s" TMY_KEEP_DOMAIN
+					  "%s\n",
+					  ptr->is_not ? "no_" : "",
+					  ptr->domainname->name))
+				break;
+
+		}
+	}
+
+	return ptr ? -ENOMEM : 0;
+}
+
+/* Should I remain in current domain? */
+static int tmy_is_domain_keeper(const struct path_info *domainname,
+				const struct path_info *program,
+				const struct path_info *last_name)
+{
+	struct domain_keeper_entry *ptr;
+	int flag = 0;
+
+	for (ptr = domain_keeper_list; ptr; ptr = ptr->next) {
+		if (ptr->is_deleted)
+			continue;
+
+		if (!ptr->is_last_name) {
+			if (ptr->domainname != domainname)
+				continue;
+		} else {
+			if (tmy_pathcmp(ptr->domainname, last_name))
+				continue;
+		}
+
+		if (ptr->program && tmy_pathcmp(ptr->program, program))
+			continue;
+		if (ptr->is_not)
+			return 0;
+		flag = 1;
+	}
+
+	return flag;
+}
+
+/*********************  SYMBOLIC LINKED PROGRAM HANDLER  *********************/
+
+static struct alias_entry *alias_list;
+
+/* Update alias list. */
+static int tmy_add_alias_entry(const char *original_name,
+			       const char *aliased_name,
+			       const u8 is_delete)
+{
+	struct alias_entry *new_entry;
+	struct alias_entry *ptr;
+	static DECLARE_MUTEX(lock);
+	const struct path_info *saved_original_name;
+	const struct path_info *saved_aliased_name;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(original_name, 1, -1, -1, __FUNCTION__) ||
+	    !tmy_correct_path(aliased_name, 1, -1, -1, __FUNCTION__))
+		return -EINVAL; /* No patterns allowed. */
+
+	saved_original_name = tmy_save_name(original_name);
+	saved_aliased_name = tmy_save_name(aliased_name);
+	if (!saved_original_name || !saved_aliased_name)
+		return -ENOMEM;
+
+	down(&lock);
+
+	for (ptr = alias_list; ptr; ptr = ptr->next) {
+		if (ptr->original_name == saved_original_name &&
+		    ptr->aliased_name == saved_aliased_name) {
+			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->original_name = saved_original_name;
+	new_entry->aliased_name = saved_aliased_name;
+
+	mb(); /* Instead of using spinlock. */
+	ptr = alias_list;
+	if (ptr) {
+		while (ptr->next)
+			ptr = ptr->next;
+		ptr->next = new_entry;
+	} else
+		alias_list = new_entry;
+
+	error = 0;
+out: ;
+
+	up(&lock);
+
+	return error;
+}
+
+/**
+ * tmy_read_alias_policy - read alias policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_alias_policy(struct io_buffer *head)
+{
+	struct alias_entry *ptr = head->read_var2;
+
+	if (!ptr)
+		ptr = alias_list;
+
+	for (; ptr; ptr = ptr->next) {
+		head->read_var2 = ptr;
+		if (!ptr->is_deleted &&
+		    tmy_io_printf(head,
+				  TMY_ALIAS "%s %s\n",
+				  ptr->original_name->name,
+				  ptr->aliased_name->name))
+			break;
+	}
+
+	return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_alias_policy - add alias policy.
+ * @data:      a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an alias entry.
+ */
+int tmy_add_alias_policy(char *data, const u8 is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+
+	return tmy_add_alias_entry(data, cp, is_delete);
+}
+
+/************************  DOMAIN AGGREGATOR HANDLER  ************************/
+
+static struct aggregator_entry *aggregator_list;
+
+/* Update aggregator list. */
+static int tmy_add_aggregator_entry(const char *original_name,
+				    const char *aggregated_name,
+				    const u8 is_delete)
+{
+	struct aggregator_entry *new_entry;
+	struct aggregator_entry *ptr;
+	static DECLARE_MUTEX(lock);
+	const struct path_info *saved_original_name;
+	const struct path_info *saved_aggregated_name;
+	int error = -ENOMEM;
+
+	if (!tmy_correct_path(original_name, 1, 0, -1, __FUNCTION__) ||
+	    !tmy_correct_path(aggregated_name, 1, -1, -1, __FUNCTION__))
+		return -EINVAL;
+
+	saved_original_name = tmy_save_name(original_name);
+	saved_aggregated_name = tmy_save_name(aggregated_name);
+	if (!saved_original_name || saved_aggregated_name)
+		return -ENOMEM;
+
+	down(&lock);
+
+	for (ptr = aggregator_list; ptr; ptr = ptr->next) {
+		if (ptr->original_name == saved_original_name &&
+		    ptr->aggregated_name == saved_aggregated_name) {
+			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->original_name = saved_original_name;
+	new_entry->aggregated_name = saved_aggregated_name;
+
+	mb(); /* Instead of using spinlock. */
+	ptr = aggregator_list;
+	if (ptr) {
+		while (ptr->next)
+			ptr = ptr->next;
+		ptr->next = new_entry;
+	} else
+		aggregator_list = new_entry;
+
+	error = 0;
+out: ;
+
+	up(&lock);
+
+	return error;
+}
+
+/**
+ * tmy_read_aggregator_policy - read aggregator policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_aggregator_policy(struct io_buffer *head)
+{
+	struct aggregator_entry *ptr = head->read_var2;
+
+	if (!ptr)
+		ptr = aggregator_list;
+
+	for (; ptr; ptr = ptr->next) {
+		head->read_var2 = ptr;
+		if (!ptr->is_deleted &&
+		    tmy_io_printf(head,
+				  TMY_AGGREGATOR "%s %s\n",
+				  ptr->original_name->name,
+				  ptr->aggregated_name->name))
+			break;
+	}
+
+	return ptr ? -ENOMEM : 0;
+}
+
+/**
+ * tmy_add_aggregator_policy - add aggregator policy.
+ * @data:      a line to parse.
+ * @is_delete: is this remove request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ *
+ * This function adds or removes an aggregator entry.
+ */
+int tmy_add_aggregator_policy(char *data, const u8 is_delete)
+{
+	char *cp = strchr(data, ' ');
+
+	if (!cp)
+		return -EINVAL;
+	*cp++ = '\0';
+
+	return tmy_add_aggregator_entry(data, cp, is_delete);
+}
+
+/*************************  DOMAIN DELETION HANDLER  *************************/
+
+/**
+ * tmy_delete_domain - delete a domain.
+ * @domainname0: domainname to delete.
+ *
+ * Returns zero.
+ *
+ * This function deletes domains.
+ * The behavior of deleting domain is like deleting files on Linux's
+ * filesystem. A process transits to different domain upon do_execve(),
+ * and the process can refer the deleted domains after the domain is deleted,
+ * like a process opens a file and the process can read()/write() the deleted
+ * file after the file is deleted.
+ * This avoids processes from crashing due to referring non-existent domains.
+ * Administrator manually terminates processes thet are referring deleted
+ * domains after deleting domains.
+ * Also, undeleting domains is supported. See tmy_undelete_domain().
+ */
+int tmy_delete_domain(char *domainname0)
+{
+	struct domain_info *domain;
+	struct path_info domainname;
+
+	domainname.name = domainname0;
+	tmy_fill_path_info(&domainname);
+
+	down(&new_domain_assign_lock);
+
+	/* Is there an active domain? */ /* Never delete KERNEL_DOMAIN */
+	for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+		if (domain->is_deleted ||
+		    tmy_pathcmp(domain->domainname, &domainname))
+			continue;
+		break;
+	}
+	if (domain) {
+		struct domain_info *domain2;
+
+		/* Mark already deleted domains as non undeletable. */
+		for (domain2 = KERNEL_DOMAIN.next;
+		     domain2;
+		     domain2 = domain2->next) {
+			if (!domain2->is_deleted ||
+			    tmy_pathcmp(domain2->domainname, &domainname))
+				continue;
+			domain2->is_deleted = 255;
+		}
+
+		/* Delete and mark active domain as undeletable. */
+		domain->is_deleted = 1;
+	}
+
+	up(&new_domain_assign_lock);
+
+	return 0;
+}
+
+/**
+ * tmy_undelete_domain - undelete a domain.
+ * @domainname0: domainname to undelete.
+ *
+ * Returns pointer to undeleted "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function undeletes domains.
+ * Not only the domain previously deleted by tmy_delete_domain()
+ * but also all domains deleted by tmy_delete_domain() are undeletable.
+ * If there is no deleted domain named @domainname0 or
+ * a not-yet-deleted domain named @domainname0 exists, undelete fails.
+ * Otherwise, previously deleted domain named @domainname0 is undeleted.
+ */
+struct domain_info *tmy_undelete_domain(const char *domainname0)
+{
+	struct domain_info *domain;
+	struct domain_info *candidate_domain = NULL;
+	struct path_info domainname;
+
+	domainname.name = domainname0;
+	tmy_fill_path_info(&domainname);
+
+	down(&new_domain_assign_lock);
+
+	for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+		if (tmy_pathcmp(&domainname, domain->domainname))
+			continue;
+
+		if (!domain->is_deleted) {
+			/* This domain is active. I can't undelete. */
+			candidate_domain = NULL;
+			break;
+		}
+
+		/* Is this domain undeletable? */
+		if (domain->is_deleted == 1)
+			candidate_domain = domain;
+	}
+	if (candidate_domain)
+		candidate_domain->is_deleted = 0;
+
+	up(&new_domain_assign_lock);
+
+	return candidate_domain;
+}
+
+/************************  DOMAIN TRANSITION HANDLER  ************************/
+
+/**
+ * tmy_find_domain - find a domain with given domainname.
+ * @domainname0: the domainname to find.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function does not create a new domain
+ * if a domain named @domainname0 does not exist.
+ */
+struct domain_info *tmy_find_domain(const char *domainname0)
+{
+	struct domain_info *domain;
+	static int first = 1;
+	struct path_info domainname;
+
+	domainname.name = domainname0;
+	tmy_fill_path_info(&domainname);
+
+	if (first) {
+		KERNEL_DOMAIN.domainname = tmy_save_name(TMY_ROOT_NAME);
+		first = 0;
+	}
+
+	for (domain = &KERNEL_DOMAIN; domain; domain = domain->next) {
+		if (!domain->is_deleted &&
+		    !tmy_pathcmp(&domainname, domain->domainname))
+			return domain;
+	}
+
+	return NULL;
+}
+
+/**
+ * tmy_new_domain - find or assign a domain with given domainname.
+ * @domainname: the domainname to find.
+ * @profile:    profile number to assign if newly created.
+ *
+ * Returns pointer to "struct domain_info" on success.
+ * Returns NULL on failure.
+ *
+ * This function creates a new domain if a domain named @domainname0
+ * does not exist.
+ */
+struct domain_info *tmy_new_domain(const char *domainname,
+				   const u8 profile)
+{
+	struct domain_info *domain = NULL;
+	const struct path_info *saved_domainname;
+
+	down(&new_domain_assign_lock);
+
+	domain = tmy_find_domain(domainname);
+	if (domain)
+		goto out;
+
+	if (!tmy_is_correct_domain(domainname, __FUNCTION__))
+		goto out;
+
+	saved_domainname = tmy_save_name(domainname);
+	if (!saved_domainname)
+		goto out;
+
+	/* Can I reuse memory of deleted domain? */
+	for (domain = KERNEL_DOMAIN.next; domain; domain = domain->next) {
+		struct task_struct *p;
+		struct acl_info *ptr;
+		int flag;
+
+		if (!domain->is_deleted ||
+		    domain->domainname != saved_domainname)
+			continue;
+		flag = 0;
+
+		/***** CRITICAL SECTION START *****/
+		read_lock(&tasklist_lock);
+		for_each_process(p) {
+			/* "struct task_struct"->security is not NULL. */
+			if (((struct tmy_security *) p->security)->domain
+			    == domain) {
+				flag = 1;
+				break;
+			}
+		}
+		read_unlock(&tasklist_lock);
+		/***** CRITICAL SECTION END *****/
+
+		/* Somebody is still referring this deleted domain. */
+		if (flag)
+			continue;
+
+		/* OK. Let's reuse memory for this deleted domain. */
+
+		/* Delete all entries in this deleted domain. */
+		for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next)
+			ptr->is_deleted = 1;
+
+		domain->profile = profile;
+		domain->quota_warned = 0;
+
+		mb(); /* Instead of using spinlock. */
+		/* Undelete this deleted domain. */
+		domain->is_deleted = 0;
+		goto out;
+	}
+
+	/* No memory reusable. Create using new memory. */
+	domain = tmy_alloc_element(sizeof(*domain));
+	if (domain) {
+		struct domain_info *ptr = &KERNEL_DOMAIN;
+
+		domain->domainname = saved_domainname;
+		domain->profile = profile;
+
+		mb(); /* Instead of using spinlock. */
+		while (ptr->next)
+			ptr = ptr->next;
+		ptr->next = domain;
+	}
+out: ;
+	up(&new_domain_assign_lock);
+
+	return domain;
+}
+
+/* Convert non ASCII printable characters to ASCII printable characters. */
+static int tmy_escape(char *dest, const char *src, int dest_len)
+{
+	while (*src) {
+		const unsigned char c = *(const unsigned char *) src;
+
+		if (c == '\\') {
+			dest_len -= 2;
+			if (dest_len <= 0)
+				goto out;
+			*dest++ = '\\';
+			*dest++ = '\\';
+		} else if (c > ' ' && c < 127) {
+			if (--dest_len <= 0)
+				goto out;
+			*dest++ = c;
+		} else {
+			dest_len -= 4;
+			if (dest_len <= 0)
+				goto out;
+			*dest++ = '\\';
+			*dest++ = (c >> 6) + '0';
+			*dest++ = ((c >> 3) & 7) + '0';
+			*dest++ = (c & 7) + '0';
+		}
+		src++;
+	}
+
+	if (--dest_len <= 0)
+		goto out;
+	*dest = '\0';
+
+	return 0;
+out: ;
+	return -ENOMEM;
+}
+
+/* Get argv[0] of "struct linux_binprm". */
+static char *tmy_get_argv0(struct linux_binprm *bprm)
+{
+	char *arg_ptr;
+	int arg_len = 0;
+	unsigned long pos = bprm->p;
+	int i = pos / PAGE_SIZE;
+	int offset = pos % PAGE_SIZE;
+
+	if (bprm->argc <= 0)
+		return NULL;
+
+	arg_ptr = tmy_alloc(PAGE_SIZE);
+
+	if (!arg_ptr)
+		goto out;
+
+	while (1) {
+		struct page *page;
+		const char *kaddr;
+		char *tmp_arg;
+
+#ifdef CONFIG_MMU
+		if (get_user_pages(current, bprm->mm, pos,
+				   1, 0, 1, &page, NULL) <= 0)
+			goto out;
+#else
+		page = bprm->page[i];
+#endif
+		kaddr = kmap(page);
+		if (!kaddr) { /* Mapping failed. */
+#ifdef CONFIG_MMU
+			put_page(page);
+#endif
+			goto out;
+		}
+
+		memmove(arg_ptr + arg_len, kaddr + offset, PAGE_SIZE - offset);
+		kunmap(page);
+
+#ifdef CONFIG_MMU
+		put_page(page);
+		pos += PAGE_SIZE - offset;
+#endif
+
+		arg_len += PAGE_SIZE - offset;
+
+		if (memchr(arg_ptr, '\0', arg_len))
+			break;
+
+		tmp_arg = tmy_alloc(arg_len + PAGE_SIZE);
+		if (!tmp_arg)
+			goto out;
+
+		memmove(tmp_arg, arg_ptr, arg_len);
+		tmy_free(arg_ptr);
+		arg_ptr = tmp_arg;
+		i++;
+		offset = 0;
+	}
+	return arg_ptr;
+out: ;
+	tmy_free(arg_ptr);
+
+	return NULL;
+}
+
+/**
+ * tmy_find_next_domain - find a domain to transit to if do_execve() succeeds.
+ * @bprm:        pointer to "struct linux_binprm".
+ * @next_domain: pointer to pointer to "struct domain_info".
+ *
+ * Returns zero if success. @next_domain receives new domain to transit to.
+ * Returns nonzero on failure.
+ *
+ * This function handles TOMOYO Linux's domain transition.
+ * New domains are automatically created unless the domain the caller process
+ * belongs to is assigned a profile for enforcing mode.
+ */
+int tmy_find_next_domain(struct linux_binprm *bprm,
+			 struct domain_info **next_domain)
+{
+	/* This function assumes that the size of buffer returned */
+	/* by tmy_realpath() = TMY_MAX_PATHNAME_LEN. */
+	struct domain_info *old_domain = TMY_SECURITY->domain;
+	struct domain_info *domain = NULL;
+	const char *old_domain_name = old_domain->domainname->name;
+	const char *original_name = bprm->filename;
+	struct file *filp = bprm->file;
+	char *new_domain_name = NULL;
+	char *real_program_name = NULL;
+	char *symlink_program_name = NULL;
+	const u8 is_enforce = tmy_enforce(TMY_MAC_FOR_FILE);
+	int retval;
+	struct path_info r;
+	struct path_info s;
+	struct path_info l;
+
+	/*
+	 * Built-in initializers.
+	 * This is needed because policies are not loaded
+	 * until starting /sbin/init .
+	 */
+	static int first = 1;
+	if (first) {
+		tmy_add_domain_initializer_entry(NULL, "/sbin/hotplug", 0, 0);
+		tmy_add_domain_initializer_entry(NULL, "/sbin/modprobe", 0, 0);
+		tmy_add_domain_initializer_entry(NULL, "/sbin/udevd", 0, 0);
+		first = 0;
+	}
+
+	/* Get realpath of program. */
+	/* I hope tmy_realpath() won't fail with -ENOMEM. */
+	retval = -ENOENT;
+	real_program_name = tmy_realpath(original_name);
+
+	if (!real_program_name)
+		goto out;
+
+	/* Get realpath of symbolic link. */
+	symlink_program_name = tmy_realpath_nofollow(original_name);
+	if (!symlink_program_name)
+		goto out;
+
+	r.name = real_program_name;
+	tmy_fill_path_info(&r);
+	s.name = symlink_program_name;
+	tmy_fill_path_info(&s);
+	l.name = strrchr(old_domain_name, ' ');
+
+	if (l.name)
+		l.name++;
+	else
+		l.name = old_domain_name;
+	tmy_fill_path_info(&l);
+
+	/* Check 'alias' directive. */
+	if (tmy_pathcmp(&r, &s)) {
+		struct alias_entry *ptr;
+
+		/* Is this program allowed to be called via symbolic links? */
+		for (ptr = alias_list; ptr; ptr = ptr->next) {
+			if (ptr->is_deleted ||
+			    tmy_pathcmp(&r, ptr->original_name) ||
+			    tmy_pathcmp(&s, ptr->aliased_name))
+				continue;
+			memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+			strncpy(real_program_name,
+				ptr->aliased_name->name,
+				TMY_MAX_PATHNAME_LEN - 1);
+			tmy_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Compare basename of real_program_name and argv[0] */
+	if (bprm->argc > 0 && tmy_flags(TMY_MAC_FOR_ARGV0)) {
+
+		char *org_argv0 = tmy_get_argv0(bprm);
+
+		retval = -ENOMEM;
+		if (org_argv0) {
+
+			const int len = strlen(org_argv0);
+			char *printable_argv0 = tmy_alloc(len * 4 + 8);
+
+			if (printable_argv0 &&
+			    !tmy_escape(printable_argv0, org_argv0,
+					len * 4 + 8)) {
+				const char *base_argv0;
+				const char *base_filename;
+
+				base_argv0 = strrchr(printable_argv0, '/');
+				if (!base_argv0)
+					base_argv0 = printable_argv0;
+				else
+					base_argv0++;
+
+				base_filename = strrchr(real_program_name, '/');
+				if (!base_filename)
+					base_filename = real_program_name;
+				else
+					base_filename++;
+
+				if (strcmp(base_argv0, base_filename))
+					retval = tmy_argv0_perm(&r, base_argv0);
+				else
+					retval = 0;
+			}
+
+			tmy_free(printable_argv0);
+			tmy_free(org_argv0);
+		}
+
+		if (retval)
+			goto out;
+
+	}
+
+
+	/* Check 'aggregator' directive. */
+	{
+		struct aggregator_entry *ptr;
+
+		/* Is this program allowed to be aggregated? */
+		for (ptr = aggregator_list; ptr; ptr = ptr->next) {
+			if (ptr->is_deleted ||
+			    !tmy_path_match(&r, ptr->original_name))
+				continue;
+			memset(real_program_name, 0, TMY_MAX_PATHNAME_LEN);
+			strncpy(real_program_name,
+				ptr->aggregated_name->name,
+				TMY_MAX_PATHNAME_LEN - 1);
+			tmy_fill_path_info(&r);
+			break;
+		}
+	}
+
+	/* Check execute permission. */
+	retval = tmy_exec_perm(&r, filp);
+	if (retval < 0)
+		goto out;
+
+	/* Allocate memory for calcurating domain name. */
+	retval = -ENOMEM;
+	new_domain_name = tmy_alloc(TMY_MAX_PATHNAME_LEN + 16);
+	if (!new_domain_name)
+		goto out;
+
+	if (tmy_is_domain_initializer(old_domain->domainname, &r, &l))
+		/* Transit to the child of KERNEL_DOMAIN domain. */
+		snprintf(new_domain_name, TMY_MAX_PATHNAME_LEN + 1,
+			 TMY_ROOT_NAME " " "%s", real_program_name);
+	else if (old_domain == &KERNEL_DOMAIN && !sbin_init_started)
+		/*
+		 * Needn't to transit from kernel domain
+		 * before starting /sbin/init .
+		 * But transit from kernel domain if executing initializers,
+		 * for they might start before /sbin/init .
+		 */
+		domain = old_domain;
+	else if (tmy_is_domain_keeper(old_domain->domainname, &r, &l))
+		/* Keep current domain. */
+		domain = old_domain;
+	else
+		/* Normal domain transition. */
+		snprintf(new_domain_name,
+			 TMY_MAX_PATHNAME_LEN + 1,
+			 "%s %s",
+			 old_domain_name,
+			 real_program_name);
+
+	if (domain || strlen(new_domain_name) >= TMY_MAX_PATHNAME_LEN)
+		goto ok;
+
+	if (is_enforce) {
+		domain = tmy_find_domain(new_domain_name);
+		if (!domain &&
+		    tmy_supervisor("#Need to create domain\n%s\n",
+				   new_domain_name) == 0) {
+			const u8 profile = TMY_SECURITY->domain->profile;
+			domain = tmy_new_domain(new_domain_name, profile);
+		}
+	} else {
+		const u8 profile = TMY_SECURITY->domain->profile;
+		domain = tmy_new_domain(new_domain_name, profile);
+	}
+
+ok: ;
+
+	if (!domain) {
+		printk(KERN_INFO "TOMOYO-ERROR: Domain '%s' not defined.\n",
+		       new_domain_name);
+		if (is_enforce)
+			retval = -EPERM;
+	} else
+		retval = 0;
+out: ;
+
+	tmy_free(new_domain_name);
+	tmy_free(real_program_name);
+	tmy_free(symlink_program_name);
+	*next_domain = domain ? domain : old_domain;
+
+	return retval;
+}


  parent reply	other threads:[~2007-10-02  7:33 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-10-02  7:25 [TOMOYO 00/15](repost) TOMOYO Linux - MAC based on process invocation history Kentaro Takeda
2007-10-02  7:28 ` [TOMOYO 01/15](repost) Allow use of namespace_sem from LSM module Kentaro Takeda
2007-10-02  7:29 ` [TOMOYO 02/15](repost) Data structures and prototypes definition Kentaro Takeda
2007-10-02  7:30 ` [TOMOYO 03/15](repost) Memory and pathname management functions Kentaro Takeda
2007-10-03  7:39   ` James Morris
2007-10-03 11:12     ` Tetsuo Handa
2007-10-02  7:31 ` [TOMOYO 04/15](repost) Utility functions and securityfs interface for policy manipulation Kentaro Takeda
2007-10-02  8:05   ` Paul Mundt
2007-10-02 14:15     ` Greg KH
2007-10-02  7:32 ` Kentaro Takeda [this message]
2007-10-02 11:15   ` [TOMOYO 05/15](repost) Domain transition handler functions James Morris
2007-10-02 12:44     ` Tetsuo Handa
2007-10-02 13:00       ` YOSHIFUJI Hideaki / 吉藤英明
2007-10-02 13:07       ` James Morris
2007-10-02 14:50         ` Andi Kleen
2007-10-03 11:24         ` Tetsuo Handa
2007-10-03 11:43           ` YOSHIFUJI Hideaki / 吉藤英明
2007-10-03 12:37             ` James Morris
2007-10-03 13:04               ` Tetsuo Handa
2007-10-03 13:11                 ` YOSHIFUJI Hideaki / 吉藤英明
2007-10-03 13:14                 ` KaiGai Kohei
2007-10-03 13:59                   ` Tetsuo Handa
2007-10-03 14:07                     ` Peter Zijlstra
2007-10-03 14:26                       ` Tetsuo Handa
2007-10-03 14:26                         ` Peter Zijlstra
2007-10-03 14:32                         ` YOSHIFUJI Hideaki / 吉藤英明
2007-10-03 14:39                           ` James Morris
2007-10-03 14:56                           ` Tetsuo Handa
2007-10-04 12:57                             ` Tetsuo Handa
2007-10-03 14:37                         ` Jiri Kosina
2007-10-07 10:38                       ` Sleeping in RCU list traversal Tetsuo Handa
2007-10-03 13:24                 ` [TOMOYO 05/15](repost) Domain transition handler functions Peter Zijlstra
2007-10-03 14:19                   ` Tetsuo Handa
2007-10-03 14:28                     ` Peter Zijlstra
2007-10-15 11:46                       ` Tetsuo Handa
2007-10-03 14:35                     ` David P. Quigley
2007-10-15 12:09               ` Tetsuo Handa
2007-10-02  7:33 ` [TOMOYO 06/15](repost) Auditing interface Kentaro Takeda
2007-10-02  7:34 ` [TOMOYO 07/15](repost) File access control functions Kentaro Takeda
2007-10-02  7:35 ` [TOMOYO 08/15](repost) Argv[0] " Kentaro Takeda
2007-10-02  7:36 ` [TOMOYO 09/15](repost) Networking " Kentaro Takeda
2007-10-02  7:37 ` [TOMOYO 10/15](repost) Namespace manipulation " Kentaro Takeda
2007-10-02  7:37 ` [TOMOYO 11/15](repost) Signal transmission " Kentaro Takeda
2007-10-02  7:38 ` [TOMOYO 12/15](repost) LSM adapter for TOMOYO Kentaro Takeda
2007-10-02  7:39 ` [TOMOYO 13/15](repost) Conditional permission support Kentaro Takeda
2007-10-02  7:39 ` [TOMOYO 14/15](repost) LSM expansion for TOMOYO Linux Kentaro Takeda
2007-10-02 12:48   ` James Morris
2007-10-02 13:33     ` Tetsuo Handa
2007-10-02 14:36   ` James Morris
2007-10-02 21:49     ` Tetsuo Handa
2007-10-02  7:40 ` [TOMOYO 15/15](repost) Kconfig and Makefile " Kentaro Takeda
2007-10-02  7:42 ` [TOMOYO 00/15](repost) TOMOYO Linux - MAC based on process invocation history Kentaro Takeda
2007-10-02 10:37   ` James Morris
2007-10-02 10:58     ` 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=4701F419.8080103@nttdata.co.jp \
    --to=takedakn@nttdata.co.jp \
    --cc=chrisw@sous-sol.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    /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.