From: Kentaro Takeda <takedakn@nttdata.co.jp>
To: akpm@linux-foundation.org
Cc: linux-kernel@vger.kernel.org,
linux-security-module@vger.kernel.org,
Kentaro Takeda <takedakn@nttdata.co.jp>,
Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Subject: [TOMOYO #6 retry 09/21] Domain transition functions.
Date: Wed, 09 Jan 2008 09:53:29 +0900 [thread overview]
Message-ID: <20080109005422.166190759@nttdata.co.jp> (raw)
In-Reply-To: 20080109005320.323184643@nttdata.co.jp
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 | 1261 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1261 insertions(+)
--- /dev/null
+++ linux-2.6-mm/security/tomoyo/domain.c
@@ -0,0 +1,1261 @@
+/*
+ * security/tomoyo/domain.c
+ *
+ * Domain transition functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/************************* VARIABLES *************************/
+
+/* The initial domain. */
+struct domain_info KERNEL_DOMAIN;
+
+/* Lock for appending domain's ACL. */
+DEFINE_MUTEX(domain_acl_lock);
+
+/* Domain creation lock. */
+static DEFINE_MUTEX(new_domain_assign_lock);
+
+/***** The structure for program files to force domain reconstruction. *****/
+
+struct domain_initializer_entry {
+ struct list_head list;
+ const struct path_info *domainname; /* This may be NULL */
+ const struct path_info *program;
+ bool is_deleted;
+ bool is_not;
+ bool is_last_name;
+};
+
+/***** The structure for domains to not to transit domains. *****/
+
+struct domain_keeper_entry {
+ struct list_head list;
+ const struct path_info *domainname;
+ const struct path_info *program; /* This may be NULL */
+ bool is_deleted;
+ bool is_not;
+ bool is_last_name;
+};
+
+/***** The structure for program files that should be aggregated. *****/
+
+struct aggregator_entry {
+ struct list_head list;
+ const struct path_info *original_name;
+ const struct path_info *aggregated_name;
+ bool is_deleted;
+};
+
+/***** The structure for program files that should be aliased. *****/
+
+struct alias_entry {
+ struct list_head list;
+ const struct path_info *original_name;
+ const struct path_info *aliased_name;
+ bool is_deleted;
+};
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/**
+ * tmy_is_domain_def - check if the line is likely a domain definition.
+ * @buffer: the line to check.
+ *
+ * Returns true if @buffer is likely a domain definition.
+ * Returns false otherwise.
+ *
+ * For complete validation check, use tmy_is_correct_domain().
+ */
+bool 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.
+ * @domain: pointer to "struct domain_info".
+ * @acl: pointer to "struct acl_info" to add.
+ *
+ * Returns zero.
+ */
+int tmy_add_acl(struct domain_info *domain,
+ struct acl_info *acl)
+{
+ list_add_tail_mb(&acl->list, &domain->acl_info_list);
+ 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 LIST_HEAD(domain_initializer_list);
+
+/* Update domain initializer list. */
+static int tmy_add_domain_initializer_entry(const char *domainname,
+ const char *program,
+ const bool is_not,
+ const bool is_delete)
+{
+ struct domain_initializer_entry *new_entry;
+ struct domain_initializer_entry *ptr;
+ static DEFINE_MUTEX(mutex);
+ const struct path_info *saved_program;
+ const struct path_info *saved_domainname = NULL;
+ int error = -ENOMEM;
+ bool 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;
+
+ mutex_lock(&mutex);
+
+ list_for_each_entry(ptr, &domain_initializer_list, list) {
+ 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;
+ list_add_tail_mb(&new_entry->list, &domain_initializer_list);
+ error = 0;
+out: ;
+
+ mutex_unlock(&mutex);
+ 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 list_head *pos;
+ list_for_each_cookie(pos, head->read_var2, &domain_initializer_list) {
+ struct domain_initializer_entry *ptr;
+ ptr = list_entry(pos, struct domain_initializer_entry, list);
+ if (ptr->is_deleted)
+ continue;
+ if (ptr->domainname) {
+ if (tmy_io_printf(head, "%s" TMY_INITIALIZE_DOMAIN
+ "%s from %s\n",
+ ptr->is_not ? "no_" : "",
+ ptr->program->name,
+ ptr->domainname->name))
+ return -ENOMEM;
+ } else {
+ if (tmy_io_printf(head, "%s" TMY_INITIALIZE_DOMAIN
+ "%s\n",
+ ptr->is_not ? "no_" : "",
+ ptr->program->name))
+ return -ENOMEM;
+ }
+ }
+ return 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 bool is_not,
+ const bool 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;
+ list_for_each_entry(ptr, &domain_initializer_list, list) {
+ 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 LIST_HEAD(domain_keeper_list);
+
+/* Update domain keeper list. */
+static int tmy_add_domain_keeper_entry(const char *domainname,
+ const char *program,
+ const bool is_not,
+ const bool 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 DEFINE_MUTEX(mutex);
+ int error = -ENOMEM;
+ bool 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;
+
+ mutex_lock(&mutex);
+ list_for_each_entry(ptr, &domain_keeper_list, list) {
+ 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;
+ list_add_tail_mb(&new_entry->list, &domain_keeper_list);
+ error = 0;
+
+out: ;
+
+ mutex_unlock(&mutex);
+ 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 bool is_not,
+ const bool 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 list_head *pos;
+ list_for_each_cookie(pos, head->read_var2, &domain_keeper_list) {
+ struct domain_keeper_entry *ptr;
+ ptr = list_entry(pos, struct domain_keeper_entry, list);
+ 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))
+ return -ENOMEM;
+ } else {
+ if (tmy_io_printf(head,
+ "%s" TMY_KEEP_DOMAIN
+ "%s\n",
+ ptr->is_not ? "no_" : "",
+ ptr->domainname->name))
+ return -ENOMEM;
+ }
+ }
+ return 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;
+ list_for_each_entry(ptr, &domain_keeper_list, list) {
+ 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 LIST_HEAD(alias_list);
+
+/* Update alias list. */
+static int tmy_add_alias_entry(const char *original_name,
+ const char *aliased_name,
+ const bool is_delete)
+{
+ struct alias_entry *new_entry;
+ struct alias_entry *ptr;
+ static DEFINE_MUTEX(mutex);
+ 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;
+
+ mutex_lock(&mutex);
+
+ list_for_each_entry(ptr, &alias_list, list) {
+ 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;
+ list_add_tail_mb(&new_entry->list, &alias_list);
+ error = 0;
+out: ;
+ mutex_unlock(&mutex);
+ 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 list_head *pos;
+ list_for_each_cookie(pos, head->read_var2, &alias_list) {
+ struct alias_entry *ptr;
+ ptr = list_entry(pos, struct alias_entry, list);
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_io_printf(head,
+ TMY_ALIAS "%s %s\n",
+ ptr->original_name->name,
+ ptr->aliased_name->name))
+ return -ENOMEM;
+ }
+ return 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 bool 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 LIST_HEAD(aggregator_list);
+
+/* Update aggregator list. */
+static int tmy_add_aggregator_entry(const char *original_name,
+ const char *aggregated_name,
+ const bool is_delete)
+{
+ struct aggregator_entry *new_entry;
+ struct aggregator_entry *ptr;
+ static DEFINE_MUTEX(mutex);
+ 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;
+
+ mutex_lock(&mutex);
+
+ list_for_each_entry(ptr, &aggregator_list, list) {
+ 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;
+ list_add_tail_mb(&new_entry->list, &aggregator_list);
+ error = 0;
+out: ;
+
+ mutex_unlock(&mutex);
+
+ 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 list_head *pos;
+ list_for_each_cookie(pos, head->read_var2, &aggregator_list) {
+ struct aggregator_entry *ptr;
+ ptr = list_entry(pos, struct aggregator_entry, list);
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_io_printf(head,
+ TMY_AGGREGATOR "%s %s\n",
+ ptr->original_name->name,
+ ptr->aggregated_name->name))
+ return -ENOMEM;
+ }
+ return 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 bool 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);
+
+ mutex_lock(&new_domain_assign_lock);
+ /* Is there an active domain? */ /* Never delete KERNEL_DOMAIN */
+ list_for_each_entry(domain, &domain_list, list) {
+ struct domain_info *domain2;
+ if (domain == &KERNEL_DOMAIN ||
+ domain->is_deleted ||
+ tmy_pathcmp(domain->domainname, &domainname))
+ continue;
+ /* Mark already deleted domains as non undeletable. */
+ list_for_each_entry(domain2, &domain_list, list) {
+ 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;
+ break;
+ }
+ mutex_unlock(&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);
+
+ mutex_lock(&new_domain_assign_lock);
+
+ list_for_each_entry(domain, &domain_list, list) {
+ 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;
+
+ mutex_unlock(&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 bool 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;
+ }
+
+ list_for_each_entry(domain, &domain_list, list) {
+ 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;
+
+ mutex_lock(&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? */
+ list_for_each_entry(domain, &domain_list, list) {
+ 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. */
+ list_for_each_entry(ptr, &domain->acl_info_list, list)
+ ptr->is_deleted = 1;
+
+ domain->profile = profile;
+ domain->quota_warned = 0;
+
+ mb(); /* Avoid out-of-order execution. */
+ /* 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) {
+ INIT_LIST_HEAD(&domain->acl_info_list);
+ domain->domainname = saved_domainname;
+ domain->profile = profile;
+ list_add_tail_mb(&domain->list, &domain_list);
+ }
+out: ;
+ mutex_unlock(&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 bool is_enforce = (tmy_flags(TMY_MAC_FOR_FILE) == 3);
+ 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 bool 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? */
+ list_for_each_entry(ptr, &alias_list, list) {
+ 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? */
+ list_for_each_entry(ptr, &aggregator_list, list) {
+ 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;
+}
+
+int tmy_check_environ(struct linux_binprm *bprm)
+{
+ const u8 profile = TMY_SECURITY->domain->profile;
+ const unsigned int mode = tmy_flags(TMY_MAC_FOR_ENV);
+ char *arg_ptr;
+ int arg_len = 0;
+ unsigned long pos = bprm->p;
+ int i = pos / PAGE_SIZE;
+ int offset = pos % PAGE_SIZE;
+ int argv_count = bprm->argc;
+ int envp_count = bprm->envc;
+ int error = -ENOMEM;
+ if (!mode || !envp_count)
+ return 0;
+ arg_ptr = tmy_alloc(TMY_MAX_PATHNAME_LEN);
+ if (!arg_ptr)
+ goto out;
+ while (error == -ENOMEM) {
+ struct page *page;
+ const char *kaddr;
+#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
+ /* Map */
+ kaddr = kmap(page);
+ if (!kaddr) {
+ /* Mapping failed. */
+#ifdef CONFIG_MMU
+ put_page(page);
+#endif
+ goto out;
+ }
+ /* Read. */
+ while (argv_count && offset < PAGE_SIZE) {
+ if (!kaddr[offset++])
+ argv_count--;
+ }
+ if (argv_count)
+ goto unmap_page;
+ while (offset < PAGE_SIZE) {
+ const unsigned char c = kaddr[offset++];
+ if (c && arg_len < TMY_MAX_PATHNAME_LEN - 10) {
+ if (c == '=') {
+ arg_ptr[arg_len++] = '\0';
+ } else if (c == '\\') {
+ arg_ptr[arg_len++] = '\\';
+ arg_ptr[arg_len++] = '\\';
+ } else if (c > ' ' && c < 127) {
+ arg_ptr[arg_len++] = c;
+ } else {
+ arg_ptr[arg_len++] = '\\';
+ arg_ptr[arg_len++] = (c >> 6) + '0';
+ arg_ptr[arg_len++] =
+ ((c >> 3) & 7) + '0';
+ arg_ptr[arg_len++] = (c & 7) + '0';
+ }
+ } else {
+ arg_ptr[arg_len] = '\0';
+ }
+ if (c)
+ continue;
+ if (tmy_env_perm(arg_ptr, profile, mode)) {
+ error = -EPERM;
+ break;
+ }
+ if (!--envp_count) {
+ error = 0;
+ break;
+ }
+ arg_len = 0;
+ }
+unmap_page:
+ /* Unmap. */
+ kunmap(page);
+#ifdef CONFIG_MMU
+ put_page(page);
+ pos += PAGE_SIZE - offset;
+#endif
+ i++;
+ offset = 0;
+ }
+out:
+ tmy_free(arg_ptr);
+ if (error && mode != 3)
+ error = 0;
+ return error;
+}
--
next prev parent reply other threads:[~2008-01-09 0:59 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-01-09 0:53 [TOMOYO #6 retry 00/21] TOMOYO Linux - MAC based on process invocation history Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 01/21] TOMOYO Linux documentation Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 02/21] Add struct vfsmount to struct task_struct Kentaro Takeda
2008-01-15 21:16 ` Serge E. Hallyn
2008-01-16 0:22 ` Kentaro Takeda
2008-01-16 14:39 ` Serge E. Hallyn
2008-01-17 4:55 ` Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 03/21] Add wrapper functions for VFS helper functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 04/21] Replace VFS with wrapper functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 05/21] Add packet filtering based on processs security context Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 06/21] Data structures and prototype defitions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 07/21] Memory and pathname management functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 08/21] Utility functions and policy manipulation interface Kentaro Takeda
2008-01-09 4:25 ` James Morris
2008-01-09 4:29 ` James Morris
2008-01-12 2:06 ` [TOMOYO #6 retry 08/21] Utility functions and policy manipulationinterface Tetsuo Handa
2008-01-12 3:06 ` James Morris
2008-01-12 4:45 ` Greg KH
2008-01-12 7:34 ` [TOMOYO #6 retry 08/21] Utility functions and policymanipulationinterface Tetsuo Handa
2008-01-09 4:31 ` [TOMOYO #6 retry 08/21] Utility functions and policy manipulation interface Kentaro Takeda
2008-01-09 0:53 ` Kentaro Takeda [this message]
2008-01-09 0:53 ` [TOMOYO #6 retry 10/21] Auditing interface Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 11/21] File access control functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 12/21] argv0 check functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 13/21] environment variable name " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 14/21] Network access control functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 15/21] Namespace manipulation " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 16/21] Signal " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 17/21] Capability access " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 18/21] LSM adapter functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 19/21] Conditional permission support Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 20/21] Kconfig and Makefile Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 21/21] Add signal hooks at sleepable location 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=20080109005422.166190759@nttdata.co.jp \
--to=takedakn@nttdata.co.jp \
--cc=akpm@linux-foundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=penguin-kernel@I-love.SAKURA.ne.jp \
/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.