Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932310AbXHXMvQ (ORCPT ); Fri, 24 Aug 2007 08:51:16 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1761433AbXHXMu4 (ORCPT ); Fri, 24 Aug 2007 08:50:56 -0400 Received: from rv-out-0910.google.com ([209.85.198.186]:4326 "EHLO rv-out-0910.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1761226AbXHXMux (ORCPT ); Fri, 24 Aug 2007 08:50:53 -0400 DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=beta; h=received:message-id:date:from:user-agent:mime-version:to:subject:references:in-reply-to:content-type:content-transfer-encoding; b=OPOJnR6Vj/Tofy87QNgg5I1wsXjzdwsAMjzji78HWIOGh7PO05rALDouH4ldZWS+ZjN73uP6r4hpGJPHf5cUt5xeWgLM/ERCWQV6oz1jswfNuHd9l3PZV53Lz7ScrTKUsmW9Swda/d9V6wQ0SfWhhjXveNSP5L9qxbcID7eh56M= Message-ID: <46CED427.8080105@gmail.com> Date: Fri, 24 Aug 2007 21:50:47 +0900 From: Kentaro Takeda User-Agent: Thunderbird 2.0.0.6 (Windows/20070728) MIME-Version: 1.0 To: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, chrisw@sous-sol.org Subject: [TOMOYO 06/15] Domain transition handler functions. References: <46CED214.6050505@gmail.com> In-Reply-To: <46CED214.6050505@gmail.com> Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 32942 Lines: 1313 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 ' /sbin/init /etc/init.d/rc'. Domain information is stored in task_struct->security. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa --- security/tomoyo/domain.c | 1291 +++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 1291 insertions(+) --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6/security/tomoyo/domain.c 2007-08-24 15:51:36.000000000 +0900 @@ -0,0 +1,1291 @@ +/* + * security/tomoyo/domain.c + * + * Domain transition functions for TOMOYO Linux. + */ + +#include "tomoyo.h" +#include "realpath.h" +#include +#include + +/************************* 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; + int 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; + int 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; +} + +/** + * tmy_too_many_acl - check quota for a domain. + * @domain: pointer to "struct domain_info" to examine. + * + * Returns nonzero if quota exceeded. + * Returns zero otherwise. + * + * This is a safeguard for "learning mode", for appending entries + * without limit dulls the system response and consumes much memory. + */ +int tmy_too_many_acl(struct domain_info * const domain) +{ + unsigned int count = 0; + struct acl_info *ptr; + + for (ptr = domain->first_acl_ptr; ptr; ptr = ptr->next) { + if (!ptr->is_deleted) + count++; + } + /* If there are so many entries, don't append if accept mode. */ + if (count < tmy_flags(TMY_MAX_ACCEPT_ENTRY)) + return 0; + + if (!domain->quota_warned) { + printk(KERN_INFO + "TOMOYO-WARNING: Domain '%s' has so many ACLs " + "to hold. Stopped learning mode.\n", + domain->domainname->name); + domain->quota_warned = 1; + } + + return 1; +} + + +/************************ 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 int is_not, + const int 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; + int 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 int is_not, + const int 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 "" 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 int is_not, + const int 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; + int 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 int is_not, + const int 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 int 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 int 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 int 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 int 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 int 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; +} - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/