This patch contains code for handling domain transition. tomoyo_read_lock()/tomoyo_read_unlock() protects the data against the garbage collector. I call tomoyo_read_lock() when an execve() operation starts and call tomoyo_read_unlock() when an execve() operation finishes rather than calling tomoyo_read_lock()/tomoyo_read_unlock() upon individual list traversal. In this way, the pointer saved in "struct tomoyo_execve_entry" is guaranteed to be valid. Please ignore warning: context imbalance in 'tomoyo_start_execve' - wrong count at exit warning: context imbalance in 'tomoyo_finish_execve' - unexpected unlock messages when built with "C=1" option. Signed-off-by: Tetsuo Handa --- security/tomoyo/new-domain.c | 1354 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1354 insertions(+) --- /dev/null +++ security-testing-2.6/security/tomoyo/new-domain.c @@ -0,0 +1,1354 @@ +/* + * security/tomoyo/domain.c + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + */ +#include "internal.h" +#include +#include +#include +#include + +/* Variables definitions.*/ + +/* The initial domain. */ +struct tomoyo_domain_info tomoyo_kernel_domain; + +/* + * tomoyo_domain_list is used for holding list of domains. + * The ->acl_info_list of "struct tomoyo_domain_info" is used for holding + * permissions (e.g. "allow_read /lib/libc-2.5.so") given to each domain. + * + * An entry is added by + * + * # ( echo ""; echo "allow_execute /sbin/init" ) > \ + * /sys/kernel/security/tomoyo/domain_policy + * + * and is deleted by + * + * # ( echo ""; echo "delete allow_execute /sbin/init" ) > \ + * /sys/kernel/security/tomoyo/domain_policy + * + * and all entries are retrieved by + * + * # cat /sys/kernel/security/tomoyo/domain_policy + * + * A domain is added by + * + * # echo "" > /sys/kernel/security/tomoyo/domain_policy + * + * and is deleted by + * + * # echo "delete " > /sys/kernel/security/tomoyo/domain_policy + * + * and all domains are retrieved by + * + * # grep '^' /sys/kernel/security/tomoyo/domain_policy + * + * Normally, a domainname is monotonically getting longer because a domainname + * which the process will belong to if an execve() operation succeeds is + * defined as a concatenation of "current domainname" + "pathname passed to + * execve()". + * See tomoyo_domain_initializer_list and tomoyo_domain_keeper_list for + * exceptions. + */ +LIST_HEAD(tomoyo_domain_list); + +/** + * tomoyo_audit_execute_handler_log - Audit execute_handler log. + * + * @ee: Pointer to "struct tomoyo_execve_entry". + * @is_default: True if it is "execute_handler" log. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_execute_handler_log(struct tomoyo_execve_entry *ee, + const bool is_default) +{ + struct tomoyo_request_info *r = &ee->r; + const char *handler = ee->handler->name; + r->mode = tomoyo_get_mode(r->profile, TOMOYO_MAC_FILE_EXECUTE); + return tomoyo_write_audit_log(true, r, "%s %s\n", + is_default ? + TOMOYO_KEYWORD_EXECUTE_HANDLER : + TOMOYO_KEYWORD_DENIED_EXECUTE_HANDLER, + handler); +} + +/** + * tomoyo_audit_domain_creation_log - Audit domain creation log. + * + * @domain: Pointer to "struct tomoyo_domain_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_domain_creation_log(struct tomoyo_domain_info *domain) +{ + int error; + struct tomoyo_request_info r; + tomoyo_init_request_info(&r, domain, TOMOYO_MAC_FILE_EXECUTE); + error = tomoyo_write_audit_log(false, &r, "use_profile %u\n", + r.profile); + return error; +} + +/* + * tomoyo_domain_initializer_list is used for holding list of programs which + * triggers reinitialization of domainname. Normally, a domainname is + * monotonically getting longer. But sometimes, we restart daemon programs. + * It would be convenient for us that "a daemon started upon system boot" and + * "the daemon restarted from console" belong to the same domain. Thus, TOMOYO + * provides a way to shorten domainnames. + * + * An entry is added by + * + * # echo 'initialize_domain /usr/sbin/httpd' > \ + * /sys/kernel/security/tomoyo/exception_policy + * + * and is deleted by + * + * # echo 'delete initialize_domain /usr/sbin/httpd' > \ + * /sys/kernel/security/tomoyo/exception_policy + * + * and all entries are retrieved by + * + * # grep ^initialize_domain /sys/kernel/security/tomoyo/exception_policy + * + * In the example above, /usr/sbin/httpd will belong to + * " /usr/sbin/httpd" domain. + * + * You may specify a domainname using "from" keyword. + * "initialize_domain /usr/sbin/httpd from /etc/rc.d/init.d/httpd" + * will cause "/usr/sbin/httpd" executed from " /etc/rc.d/init.d/httpd" + * domain to belong to " /usr/sbin/httpd" domain. + * + * You may add "no_" prefix to "initialize_domain". + * "initialize_domain /usr/sbin/httpd" and + * "no_initialize_domain /usr/sbin/httpd from /etc/rc.d/init.d/httpd" + * will cause "/usr/sbin/httpd" to belong to " /usr/sbin/httpd" domain + * unless executed from " /etc/rc.d/init.d/httpd" domain. + */ +LIST_HEAD(tomoyo_domain_initializer_list); + +/** + * tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list. + * + * @domainname: The name of domain. May be NULL. + * @program: The name of program. + * @is_not: True if it is "no_initialize_domain" entry. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_domain_initializer_entry(const char *domainname, + const char *program, + const bool is_not, + const bool is_delete) +{ + struct tomoyo_domain_initializer_entry *entry = NULL; + struct tomoyo_domain_initializer_entry *ptr; + struct tomoyo_domain_initializer_entry e = { .is_not = is_not }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (!tomoyo_is_correct_path(program, 1, -1, -1)) + return -EINVAL; /* No patterns allowed. */ + if (domainname) { + if (!tomoyo_is_domain_def(domainname) && + tomoyo_is_correct_path(domainname, 1, -1, -1)) + e.is_last_name = true; + else if (!tomoyo_is_correct_domain(domainname)) + return -EINVAL; + e.domainname = tomoyo_get_name(domainname); + if (!e.domainname) + goto out; + } + e.program = tomoyo_get_name(program); + if (!e.program) + goto out; + if (!is_delete) + entry = kmalloc(sizeof(e), GFP_KERNEL); + mutex_lock(&tomoyo_policy_lock); + list_for_each_entry_rcu(ptr, &tomoyo_domain_initializer_list, list) { + if (tomoyo_memcmp(ptr, &e, offsetof(typeof(e), is_not), + sizeof(e))) + continue; + ptr->is_deleted = is_delete; + error = 0; + break; + } + if (!is_delete && error && tomoyo_commit_ok(entry, &e, sizeof(e))) { + list_add_tail_rcu(&entry->list, + &tomoyo_domain_initializer_list); + entry = NULL; + error = 0; + } + mutex_unlock(&tomoyo_policy_lock); + out: + tomoyo_put_name(e.domainname); + tomoyo_put_name(e.program); + kfree(entry); + return error; +} + +/** + * tomoyo_read_domain_initializer_policy - Read "struct tomoyo_domain_initializer_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + list_for_each_cookie(pos, head->read_var2, + &tomoyo_domain_initializer_list) { + const char *no; + const char *from = ""; + const char *domain = ""; + struct tomoyo_domain_initializer_entry *ptr; + ptr = list_entry(pos, struct tomoyo_domain_initializer_entry, + list); + if (ptr->is_deleted) + continue; + no = ptr->is_not ? "no_" : ""; + if (ptr->domainname) { + from = " from "; + domain = ptr->domainname->name; + } + done = tomoyo_io_printf(head, "%s" + TOMOYO_KEYWORD_INITIALIZE_DOMAIN + "%s%s%s\n", no, ptr->program->name, + from, domain); + if (!done) + break; + } + return done; +} + +/** + * tomoyo_write_domain_initializer_policy - Write "struct tomoyo_domain_initializer_entry" list. + * + * @data: String to parse. + * @is_not: True if it is "no_initialize_domain" entry. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_domain_initializer_policy(char *data, const bool is_not, + const bool is_delete) +{ + char *cp = strstr(data, " from "); + if (cp) { + *cp = '\0'; + return tomoyo_update_domain_initializer_entry(cp + 6, data, + is_not, + is_delete); + } + return tomoyo_update_domain_initializer_entry(NULL, data, is_not, + is_delete); +} + +/** + * tomoyo_is_domain_initializer - Check whether the given program causes domainname reinitialization. + * + * @domainname: The name of domain. + * @program: The name of program. + * @last_name: The last component of @domainname. + * + * Returns true if executing @program reinitializes domain transition, + * false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_is_domain_initializer +(const struct tomoyo_path_info *domainname, + const struct tomoyo_path_info *program, + const struct tomoyo_path_info *last_name) +{ + struct tomoyo_domain_initializer_entry *ptr; + bool flag = false; + list_for_each_entry_rcu(ptr, &tomoyo_domain_initializer_list, list) { + if (ptr->is_deleted) + continue; + if (ptr->domainname) { + if (!ptr->is_last_name) { + if (ptr->domainname != domainname) + continue; + } else { + if (tomoyo_pathcmp(ptr->domainname, last_name)) + continue; + } + } + if (tomoyo_pathcmp(ptr->program, program)) + continue; + if (ptr->is_not) { + flag = false; + break; + } + flag = true; + } + return flag; +} + +/* + * tomoyo_domain_keeper_list is used for holding list of domainnames which + * suppresses domain transition. Normally, a domainname is monotonically + * getting longer. But sometimes, we want to suppress domain transition. + * It would be convenient for us that programs executed from a login session + * belong to the same domain. Thus, TOMOYO provides a way to suppress domain + * transition. + * + * An entry is added by + * + * # echo 'keep_domain /usr/sbin/sshd /bin/bash' > \ + * /sys/kernel/security/tomoyo/exception_policy + * + * and is deleted by + * + * # echo 'delete keep_domain /usr/sbin/sshd /bin/bash' > \ + * /sys/kernel/security/tomoyo/exception_policy + * + * and all entries are retrieved by + * + * # grep ^keep_domain /sys/kernel/security/tomoyo/exception_policy + * + * In the example above, any process which belongs to + * " /usr/sbin/sshd /bin/bash" domain will remain in that domain, + * unless explicitly specified by "initialize_domain" or "no_keep_domain". + * + * You may specify a program using "from" keyword. + * "keep_domain /bin/pwd from /usr/sbin/sshd /bin/bash" + * will cause "/bin/pwd" executed from " /usr/sbin/sshd /bin/bash" + * domain to remain in " /usr/sbin/sshd /bin/bash" domain. + * + * You may add "no_" prefix to "keep_domain". + * "keep_domain /usr/sbin/sshd /bin/bash" and + * "no_keep_domain /usr/bin/passwd from /usr/sbin/sshd /bin/bash" will + * cause "/usr/bin/passwd" to belong to + * " /usr/sbin/sshd /bin/bash /usr/bin/passwd" domain, unless + * explicitly specified by "initialize_domain". + */ +LIST_HEAD(tomoyo_domain_keeper_list); + +/** + * tomoyo_update_domain_keeper_entry - Update "struct tomoyo_domain_keeper_entry" list. + * + * @domainname: The name of domain. + * @program: The name of program. May be NULL. + * @is_not: True if it is "no_keep_domain" entry. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_domain_keeper_entry(const char *domainname, + const char *program, + const bool is_not, + const bool is_delete) +{ + struct tomoyo_domain_keeper_entry *entry = NULL; + struct tomoyo_domain_keeper_entry *ptr; + struct tomoyo_domain_keeper_entry e = { .is_not = is_not }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (!tomoyo_is_domain_def(domainname) && + tomoyo_is_correct_path(domainname, 1, -1, -1)) + e.is_last_name = true; + else if (!tomoyo_is_correct_domain(domainname)) + return -EINVAL; + if (program) { + if (!tomoyo_is_correct_path(program, 1, -1, -1)) + return -EINVAL; + e.program = tomoyo_get_name(program); + if (!e.program) + goto out; + } + e.domainname = tomoyo_get_name(domainname); + if (!e.domainname) + goto out; + if (!is_delete) + entry = kmalloc(sizeof(e), GFP_KERNEL); + mutex_lock(&tomoyo_policy_lock); + list_for_each_entry_rcu(ptr, &tomoyo_domain_keeper_list, list) { + if (tomoyo_memcmp(ptr, &e, offsetof(typeof(e), is_not), + sizeof(e))) + continue; + ptr->is_deleted = is_delete; + error = 0; + break; + } + if (!is_delete && error && tomoyo_commit_ok(entry, &e, sizeof(e))) { + list_add_tail_rcu(&entry->list, &tomoyo_domain_keeper_list); + entry = NULL; + error = 0; + } + mutex_unlock(&tomoyo_policy_lock); + out: + tomoyo_put_name(e.domainname); + tomoyo_put_name(e.program); + kfree(entry); + return error; +} + +/** + * tomoyo_write_domain_keeper_policy - Write "struct tomoyo_domain_keeper_entry" list. + * + * @data: String to parse. + * @is_not: True if it is "no_keep_domain" entry. + * @is_delete: True if it is a delete request. + * + */ +int tomoyo_write_domain_keeper_policy(char *data, const bool is_not, + const bool is_delete) +{ + char *cp = strstr(data, " from "); + if (cp) { + *cp = '\0'; + return tomoyo_update_domain_keeper_entry(cp + 6, data, + is_not, is_delete); + } + return tomoyo_update_domain_keeper_entry(data, NULL, is_not, + is_delete); +} + +/** + * tomoyo_read_domain_keeper_policy - Read "struct tomoyo_domain_keeper_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + list_for_each_cookie(pos, head->read_var2, + &tomoyo_domain_keeper_list) { + struct tomoyo_domain_keeper_entry *ptr; + const char *no; + const char *from = ""; + const char *program = ""; + ptr = list_entry(pos, struct tomoyo_domain_keeper_entry, list); + if (ptr->is_deleted) + continue; + no = ptr->is_not ? "no_" : ""; + if (ptr->program) { + from = " from "; + program = ptr->program->name; + } + done = tomoyo_io_printf(head, "%s" TOMOYO_KEYWORD_KEEP_DOMAIN + "%s%s%s\n", no, program, from, + ptr->domainname->name); + if (!done) + break; + } + return done; +} + +/** + * tomoyo_is_domain_keeper - Check whether the given program causes domain transition suppression. + * + * @domainname: The name of domain. + * @program: The name of program. + * @last_name: The last component of @domainname. + * + * Returns true if executing @program supresses domain transition, + * false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_is_domain_keeper(const struct tomoyo_path_info *domainname, + const struct tomoyo_path_info *program, + const struct tomoyo_path_info *last_name) +{ + struct tomoyo_domain_keeper_entry *ptr; + bool flag = false; + list_for_each_entry_rcu(ptr, &tomoyo_domain_keeper_list, list) { + if (ptr->is_deleted) + continue; + if (!ptr->is_last_name) { + if (ptr->domainname != domainname) + continue; + } else { + if (tomoyo_pathcmp(ptr->domainname, last_name)) + continue; + } + if (ptr->program && tomoyo_pathcmp(ptr->program, program)) + continue; + if (ptr->is_not) { + flag = false; + break; + } + flag = true; + } + return flag; +} + +/* The list for "struct tomoyo_aggregator_entry". */ +LIST_HEAD(tomoyo_aggregator_list); + +/** + * tomoyo_update_aggregator_entry - Update "struct tomoyo_aggregator_entry" list. + * + * @original_name: The original program's name. + * @aggregated_name: The aggregated program's name. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_aggregator_entry(const char *original_name, + const char *aggregated_name, + const bool is_delete) +{ + struct tomoyo_aggregator_entry *entry = NULL; + struct tomoyo_aggregator_entry *ptr; + struct tomoyo_aggregator_entry e = { }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (!tomoyo_is_correct_path(original_name, 1, 0, -1) || + !tomoyo_is_correct_path(aggregated_name, 1, -1, -1)) + return -EINVAL; + e.original_name = tomoyo_get_name(original_name); + e.aggregated_name = tomoyo_get_name(aggregated_name); + if (!e.original_name || !e.aggregated_name) + goto out; + if (!is_delete) + entry = kmalloc(sizeof(e), GFP_KERNEL); + mutex_lock(&tomoyo_policy_lock); + list_for_each_entry_rcu(ptr, &tomoyo_aggregator_list, list) { + if (tomoyo_memcmp(ptr, &e, offsetof(typeof(e), original_name), + sizeof(e))) + continue; + ptr->is_deleted = is_delete; + error = 0; + break; + } + if (!is_delete && error && tomoyo_commit_ok(entry, &e, sizeof(e))) { + list_add_tail_rcu(&entry->list, &tomoyo_aggregator_list); + entry = NULL; + error = 0; + } + mutex_unlock(&tomoyo_policy_lock); + out: + tomoyo_put_name(e.original_name); + tomoyo_put_name(e.aggregated_name); + kfree(entry); + return error; +} + +/** + * tomoyo_read_aggregator_policy - Read "struct tomoyo_aggregator_entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +bool tomoyo_read_aggregator_policy(struct tomoyo_io_buffer *head) +{ + struct list_head *pos; + bool done = true; + list_for_each_cookie(pos, head->read_var2, &tomoyo_aggregator_list) { + struct tomoyo_aggregator_entry *ptr; + ptr = list_entry(pos, struct tomoyo_aggregator_entry, list); + if (ptr->is_deleted) + continue; + done = tomoyo_io_printf(head, TOMOYO_KEYWORD_AGGREGATOR + "%s %s\n", ptr->original_name->name, + ptr->aggregated_name->name); + if (!done) + break; + } + return done; +} + +/** + * tomoyo_write_aggregator_policy - Write "struct tomoyo_aggregator_entry" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_aggregator_policy(char *data, const bool is_delete) +{ + char *w[2]; + if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[1][0]) + return -EINVAL; + return tomoyo_update_aggregator_entry(w[0], w[1], is_delete); +} + +/* Domain create/delete handler. */ + +/** + * tomoyo_delete_domain - Delete a domain. + * + * @domainname: The name of domain. + * + * Returns 0. + */ +int tomoyo_delete_domain(char *domainname) +{ + struct tomoyo_domain_info *domain; + struct tomoyo_path_info name; + name.name = domainname; + tomoyo_fill_path_info(&name); + mutex_lock(&tomoyo_policy_lock); + /* Is there an active domain? */ + list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { + /* Never delete tomoyo_kernel_domain */ + if (domain == &tomoyo_kernel_domain) + continue; + if (domain->is_deleted || + tomoyo_pathcmp(domain->domainname, &name)) + continue; + domain->is_deleted = true; + break; + } + mutex_unlock(&tomoyo_policy_lock); + return 0; +} + +/** + * tomoyo_find_or_assign_new_domain - Create a domain. + * + * @domainname: The name of domain. + * @profile: Profile number to assign if the domain was newly created. + * + * Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise. + */ +struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain +(const char *domainname, const u8 profile) +{ + struct tomoyo_domain_info *entry; + struct tomoyo_domain_info *domain; + const struct tomoyo_path_info *saved_domainname; + bool found = false; + + if (!tomoyo_is_correct_domain(domainname)) + return NULL; + saved_domainname = tomoyo_get_name(domainname); + if (!saved_domainname) + return NULL; + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + mutex_lock(&tomoyo_policy_lock); + list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { + if (domain->is_deleted || + tomoyo_pathcmp(saved_domainname, domain->domainname)) + continue; + found = true; + break; + } + if (!found && tomoyo_memory_ok(entry, sizeof(*entry))) { + INIT_LIST_HEAD(&entry->acl_info_list); + entry->domainname = saved_domainname; + saved_domainname = NULL; + entry->profile = profile; + list_add_tail_rcu(&entry->list, &tomoyo_domain_list); + domain = entry; + entry = NULL; + found = true; + } + mutex_unlock(&tomoyo_policy_lock); + tomoyo_put_name(saved_domainname); + kfree(entry); + return found ? domain : NULL; +} + +/** + * tomoyo_find_next_domain - Find a domain. + * + * @ee: Pointer to "struct tomoyo_execve_entry". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_find_next_domain(struct tomoyo_execve_entry *ee) +{ + struct tomoyo_request_info *r = &ee->r; + const struct tomoyo_path_info *handler = ee->handler; + struct tomoyo_domain_info *domain = NULL; + const char *old_domain_name = r->domain->domainname->name; + struct linux_binprm *bprm = ee->bprm; + const u32 tomoyo_flags = current->tomoyo_flags; + struct tomoyo_path_info rn = { }; /* real name */ + struct tomoyo_path_info ln; /* last name */ + int retval; + bool need_kfree = false; + ln.name = tomoyo_last_word(old_domain_name); + tomoyo_fill_path_info(&ln); + retry: + current->tomoyo_flags = tomoyo_flags; + r->cond = NULL; + if (need_kfree) { + kfree(rn.name); + need_kfree = false; + } + + /* Get symlink's pathname of program. */ + retval = tomoyo_symlink_path(bprm->filename, &rn); + if (retval < 0) + goto out; + need_kfree = true; + + if (handler) { + if (tomoyo_pathcmp(&rn, handler)) { + /* Failed to verify execute handler. */ + static u8 counter = 20; + if (counter) { + counter--; + printk(KERN_WARNING "Failed to verify: %s\n", + handler->name); + } + goto out; + } + } else { + struct tomoyo_aggregator_entry *ptr; + /* Check 'aggregator' directive. */ + list_for_each_entry_rcu(ptr, &tomoyo_aggregator_list, list) { + if (ptr->is_deleted || + !tomoyo_path_matches_pattern(&rn, + ptr->original_name)) + continue; + kfree(rn.name); + need_kfree = false; + /* This is OK because it is read only. */ + rn = *ptr->aggregated_name; + break; + } + + /* Check execute permission. */ + retval = tomoyo_exec_perm(r, &rn); + if (retval == 1) + goto retry; + if (retval < 0) + goto out; + } + + /* Calculate domain to transit to. */ + if (tomoyo_is_domain_initializer(r->domain->domainname, &rn, &ln)) { + /* Transit to the child of tomoyo_kernel_domain domain. */ + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, ROOT_NAME " " "%s", + rn.name); + } else if (r->domain == &tomoyo_kernel_domain && + !tomoyo_policy_loaded) { + /* + * Needn't to transit from kernel domain before starting + * /sbin/init. But transit from kernel domain if executing + * initializers because they might start before /sbin/init. + */ + domain = r->domain; + } else if (tomoyo_is_domain_keeper(r->domain->domainname, &rn, &ln)) { + /* Keep current domain. */ + domain = r->domain; + } else { + /* Normal domain transition. */ + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", + old_domain_name, rn.name); + } + if (domain || strlen(ee->tmp) >= TOMOYO_EXEC_TMPSIZE - 10) + goto done; + domain = tomoyo_find_domain(ee->tmp); + if (domain) + goto done; + if (r->mode == TOMOYO_CONFIG_ENFORCING) { + int error = tomoyo_supervisor(r, "# wants to create domain\n" + "%s\n", ee->tmp); + if (error == 1) + goto retry; + if (error < 0) + goto done; + } + domain = tomoyo_find_or_assign_new_domain(ee->tmp, r->profile); + if (domain) + tomoyo_audit_domain_creation_log(r->domain); + done: + if (!domain) { + printk(KERN_WARNING "ERROR: Domain '%s' not defined.\n", + ee->tmp); + if (r->mode == TOMOYO_CONFIG_ENFORCING) + retval = -EPERM; + else { + retval = 0; + r->domain->domain_transition_failed = true; + } + } else { + retval = 0; + } + out: + if (domain) + r->domain = domain; + if (need_kfree) + kfree(rn.name); + return retval; +} + +/** + * tomoyo_environ - Check permission for environment variable names. + * + * @ee: Pointer to "struct tomoyo_execve_entry". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_environ(struct tomoyo_execve_entry *ee) +{ + struct tomoyo_request_info *r = &ee->r; + struct linux_binprm *bprm = ee->bprm; + char *arg_ptr = ee->tmp; + int arg_len = 0; + unsigned long pos = bprm->p; + int offset = pos % PAGE_SIZE; + int argv_count = bprm->argc; + int envp_count = bprm->envc; + /* printk(KERN_DEBUG "start %d %d\n", argv_count, envp_count); */ + int error = -ENOMEM; + if (!r->mode || !envp_count) + return 0; + while (error == -ENOMEM) { + if (!tomoyo_dump_page(bprm, pos, &ee->dump)) + goto out; + pos += PAGE_SIZE - offset; + /* Read. */ + while (argv_count && offset < PAGE_SIZE) { + const char *kaddr = ee->dump.data; + if (!kaddr[offset++]) + argv_count--; + } + if (argv_count) { + offset = 0; + continue; + } + while (offset < PAGE_SIZE) { + const char *kaddr = ee->dump.data; + const unsigned char c = kaddr[offset++]; + if (c && arg_len < TOMOYO_EXEC_TMPSIZE - 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 (tomoyo_env_perm(r, arg_ptr)) { + error = -EPERM; + break; + } + if (!--envp_count) { + error = 0; + break; + } + arg_len = 0; + } + offset = 0; + } + out: + if (r->mode != 3) + error = 0; + return error; +} + +/** + * tomoyo_unescape - Unescape escaped string. + * + * @dest: String to unescape. + * + * Returns nothing. + */ +static void tomoyo_unescape(unsigned char *dest) +{ + unsigned char *src = dest; + unsigned char c; + unsigned char d; + unsigned char e; + while (1) { + c = *src++; + if (!c) + break; + if (c != '\\') { + *dest++ = c; + continue; + } + c = *src++; + if (c == '\\') { + *dest++ = c; + continue; + } + if (c < '0' || c > '3') + break; + d = *src++; + if (d < '0' || d > '7') + break; + e = *src++; + if (e < '0' || e > '7') + break; + *dest++ = ((c - '0') << 6) + ((d - '0') << 3) + (e - '0'); + } + *dest = '\0'; +} + +/** + * tomoyo_root_depth - Get number of directories to strip. + * + * @dentry: Pointer to "struct dentry". + * @vfsmnt: Pointer to "struct vfsmount". + * + * Returns number of directories to strip. + */ +static inline int tomoyo_root_depth(struct dentry *dentry, + struct vfsmount *vfsmnt) +{ + int depth = 0; + spin_lock(&dcache_lock); + spin_lock(&vfsmount_lock); + for (;;) { + if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { + /* Global root? */ + if (vfsmnt->mnt_parent == vfsmnt) + break; + dentry = vfsmnt->mnt_mountpoint; + vfsmnt = vfsmnt->mnt_parent; + continue; + } + dentry = dentry->d_parent; + depth++; + } + spin_unlock(&vfsmount_lock); + spin_unlock(&dcache_lock); + return depth; +} + +/** + * tomoyo_get_root_depth - return the depth of root directory. + * + * Returns number of directories to strip. + */ +static int tomoyo_get_root_depth(void) +{ + int depth; + struct dentry *dentry; + struct vfsmount *vfsmnt; + struct path root; + read_lock(¤t->fs->lock); + root = current->fs->root; + path_get(¤t->fs->root); + dentry = root.dentry; + vfsmnt = root.mnt; + read_unlock(¤t->fs->lock); + depth = tomoyo_root_depth(dentry, vfsmnt); + path_put(&root); + return depth; +} + +/** + * tomoyo_try_alt_exec - Try to start execute handler. + * + * @ee: Pointer to "struct tomoyo_execve_entry". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_try_alt_exec(struct tomoyo_execve_entry *ee) +{ + /* + * Contents of modified bprm. + * The envp[] in original bprm is moved to argv[] so that + * the alternatively executed program won't be affected by + * some dangerous environment variables like LD_PRELOAD. + * + * modified bprm->argc + * = original bprm->argc + original bprm->envc + 7 + * modified bprm->envc + * = 0 + * + * modified bprm->argv[0] + * = the program's name specified by execute_handler + * modified bprm->argv[1] + * = tomoyo_current_domain()->domainname->name + * modified bprm->argv[2] + * = the current process's name + * modified bprm->argv[3] + * = the current process's information (e.g. uid/gid). + * modified bprm->argv[4] + * = original bprm->filename + * modified bprm->argv[5] + * = original bprm->argc in string expression + * modified bprm->argv[6] + * = original bprm->envc in string expression + * modified bprm->argv[7] + * = original bprm->argv[0] + * ... + * modified bprm->argv[bprm->argc + 6] + * = original bprm->argv[bprm->argc - 1] + * modified bprm->argv[bprm->argc + 7] + * = original bprm->envp[0] + * ... + * modified bprm->argv[bprm->envc + bprm->argc + 6] + * = original bprm->envp[bprm->envc - 1] + */ + struct linux_binprm *bprm = ee->bprm; + struct file *filp; + int retval; + const int original_argc = bprm->argc; + const int original_envc = bprm->envc; + struct task_struct *task = current; + + /* Close the requested program's dentry. */ + ee->obj.path1.dentry = NULL; + ee->obj.path1.mnt = NULL; + ee->obj.validate_done = false; + allow_write_access(bprm->file); + fput(bprm->file); + bprm->file = NULL; + + /* Invalidate page dump cache. */ + ee->dump.page = NULL; + + /* Move envp[] to argv[] */ + bprm->argc += bprm->envc; + bprm->envc = 0; + + /* Set argv[6] */ + { + char *cp = ee->tmp; + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%d", + original_envc); + retval = copy_strings_kernel(1, &cp, bprm); + if (retval < 0) + goto out; + bprm->argc++; + } + + /* Set argv[5] */ + { + char *cp = ee->tmp; + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%d", + original_argc); + retval = copy_strings_kernel(1, &cp, bprm); + if (retval < 0) + goto out; + bprm->argc++; + } + + /* Set argv[4] */ + { + retval = copy_strings_kernel(1, &bprm->filename, bprm); + if (retval < 0) + goto out; + bprm->argc++; + } + + /* Set argv[3] */ + { + char *cp = ee->tmp; + const u32 tomoyo_flags = task->tomoyo_flags; + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, + "pid=%d uid=%d gid=%d euid=%d egid=%d suid=%d " + "sgid=%d fsuid=%d fsgid=%d state[0]=%u " + "state[1]=%u state[2]=%u", + (pid_t) sys_getpid(), current_uid(), current_gid(), + current_euid(), current_egid(), current_suid(), + current_sgid(), current_fsuid(), current_fsgid(), + (u8) (tomoyo_flags >> 24), (u8) (tomoyo_flags >> 16), + (u8) (tomoyo_flags >> 8)); + retval = copy_strings_kernel(1, &cp, bprm); + if (retval < 0) + goto out; + bprm->argc++; + } + + /* Set argv[2] */ + { + char *exe = (char *) tomoyo_get_exe(); + if (exe) { + retval = copy_strings_kernel(1, &exe, bprm); + kfree(exe); + } else { + exe = ee->tmp; + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, + ""); + retval = copy_strings_kernel(1, &exe, bprm); + } + if (retval < 0) + goto out; + bprm->argc++; + } + + /* Set argv[1] */ + { + char *cp = ee->tmp; + snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s", + tomoyo_current_domain()->domainname->name); + retval = copy_strings_kernel(1, &cp, bprm); + if (retval < 0) + goto out; + bprm->argc++; + } + + /* Set argv[0] */ + { + int depth = tomoyo_get_root_depth(); + int len = strlen(ee->handler->name) + 1; + char *cp = kmalloc(len, GFP_KERNEL); + if (!cp) { + retval = -ENOMEM; + goto out; + } + ee->handler_path = cp; + memmove(cp, ee->handler->name, len); + tomoyo_unescape(cp); + retval = -ENOENT; + if (!*cp || *cp != '/') + goto out; + /* Adjust root directory for open_exec(). */ + while (depth) { + cp = strchr(cp + 1, '/'); + if (!cp) + goto out; + depth--; + } + memmove(ee->handler_path, cp, strlen(cp) + 1); + cp = ee->handler_path; + retval = copy_strings_kernel(1, &cp, bprm); + if (retval < 0) + goto out; + bprm->argc++; + } + + /* + * OK, now restart the process with execute handler program's dentry. + */ + filp = open_exec(ee->handler_path); + if (IS_ERR(filp)) { + retval = PTR_ERR(filp); + goto out; + } + ee->obj.path1.dentry = filp->f_dentry; + ee->obj.path1.mnt = filp->f_vfsmnt; + bprm->file = filp; + bprm->filename = ee->handler_path; + bprm->interp = bprm->filename; + retval = prepare_binprm(bprm); + if (retval < 0) + goto out; + task->tomoyo_flags |= TOMOYO_DONT_SLEEP_ON_ENFORCE_ERROR; + retval = tomoyo_find_next_domain(ee); + task->tomoyo_flags &= ~TOMOYO_DONT_SLEEP_ON_ENFORCE_ERROR; + out: + return retval; +} + +/** + * tomoyo_find_execute_handler - Find an execute handler. + * + * @ee: Pointer to "struct tomoyo_execve_entry". + * @type: Type of execute handler. + * + * Returns true if found, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_find_execute_handler(struct tomoyo_execve_entry *ee, + const u8 type) +{ + struct task_struct *task = current; + const struct tomoyo_domain_info *domain = tomoyo_current_domain(); + struct tomoyo_acl_info *ptr; + bool found = false; + /* + * Don't use execute handler if the current process is + * marked as execute handler to avoid infinite execute handler loop. + */ + if (task->tomoyo_flags & TOMOYO_TASK_IS_EXECUTE_HANDLER) + return false; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + struct tomoyo_execute_handler_record *acl; + if (ptr->type != type) + continue; + acl = container_of(ptr, struct tomoyo_execute_handler_record, + head); + ee->handler = acl->handler; + found = true; + break; + } + return found; +} + +/** + * tomoyo_dump_page - Dump a page to buffer. + * + * @bprm: Pointer to "struct linux_binprm". + * @pos: Location to dump. + * @dump: Poiner to "struct tomoyo_page_dump". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos, + struct tomoyo_page_dump *dump) +{ + struct page *page; + /* dump->data is released by tomoyo_finish_execve(). */ + if (!dump->data) { + dump->data = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!dump->data) + return false; + } + /* Same with get_arg_page(bprm, pos, 0) in fs/exec.c */ +#if defined(CONFIG_MMU) + if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page, NULL) <= 0) + return false; +#else + page = bprm->page[pos / PAGE_SIZE]; +#endif + if (page != dump->page) { + const unsigned int offset = pos % PAGE_SIZE; + /* + * Maybe kmap()/kunmap() should be used here. + * But remove_arg_zero() uses kmap_atomic()/kunmap_atomic(). + * So do I. + */ + char *kaddr = kmap_atomic(page, KM_USER0); + dump->page = page; + memcpy(dump->data + offset, kaddr + offset, + PAGE_SIZE - offset); + kunmap_atomic(kaddr, KM_USER0); + } + /* Same with put_arg_page(page) in fs/exec.c */ +#if defined(CONFIG_MMU) + put_page(page); +#endif + return true; +} + +/** + * tomoyo_start_execve - Prepare for execve() operation. + * + * @bprm: Pointer to "struct linux_binprm". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_start_execve(struct linux_binprm *bprm) +{ + int retval; + struct task_struct *task = current; + struct tomoyo_execve_entry *ee; + if (!tomoyo_policy_loaded) + tomoyo_load_policy(bprm->filename); + ee = kzalloc(sizeof(*ee), GFP_KERNEL); + if (!ee) + return -ENOMEM; + ee->tmp = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_KERNEL); + if (!ee->tmp) { + kfree(ee); + return -ENOMEM; + } + /* This lock is released at tomoyo_finish_execve(). */ + ee->reader_idx = tomoyo_read_lock(); + /* ee->dump->data is allocated by tomoyo_dump_page(). */ + ee->previous_domain = task->tomoyo_domain_info; + /* Clear manager flag. */ + task->tomoyo_flags &= ~TOMOYO_TASK_IS_POLICY_MANAGER; + /* Tell GC that I started execve(). */ + task->tomoyo_flags |= TOMOYO_TASK_IS_IN_EXECVE; + /* + * Make task->tomoyo_flags visible to GC before changing + * task->tomoyo_domain_info . + */ + smp_mb(); + bprm->cred->security = ee; + atomic_set(&ee->cred_users, 1); + tomoyo_init_request_info(&ee->r, NULL, TOMOYO_MAC_FILE_EXECUTE); + ee->r.ee = ee; + ee->bprm = bprm; + ee->r.obj = &ee->obj; + ee->obj.path1.dentry = bprm->file->f_dentry; + ee->obj.path1.mnt = bprm->file->f_vfsmnt; + if (tomoyo_find_execute_handler(ee, TOMOYO_TYPE_EXECUTE_HANDLER)) { + retval = tomoyo_try_alt_exec(ee); + if (!retval) + tomoyo_audit_execute_handler_log(ee, true); + goto ok; + } + retval = tomoyo_find_next_domain(ee); + if (retval != -EPERM) + goto ok; + if (tomoyo_find_execute_handler(ee, + TOMOYO_TYPE_DENIED_EXECUTE_HANDLER)) { + retval = tomoyo_try_alt_exec(ee); + if (!retval) + tomoyo_audit_execute_handler_log(ee, false); + } + ok: + if (retval < 0) + goto out; + /* + * Proceed to the next domain in order to allow reaching via PID. + * It will be reverted if execve() failed. Reverting is not good. + * But it is better than being unable to reach via PID in interactive + * enforcing mode. + */ + task->tomoyo_domain_info = ee->r.domain; + ee->r.mode = tomoyo_get_mode(ee->r.domain->profile, + TOMOYO_MAC_ENVIRON); + retval = tomoyo_environ(ee); + if (retval < 0) + goto out; + retval = 0; + out: + if (retval) { + tomoyo_finish_execve(ee, true); + bprm->cred->security = NULL; + } + return retval; +} + +/** + * tomoyo_finish_execve - Clean up execve() operation. + * + * @ee: Pointer to "struct tomoyo_execve_entry". + * @rollback: True if need to rollback. + * + * Caller holds tomoyo_read_lock(). + */ +void tomoyo_finish_execve(struct tomoyo_execve_entry *ee, const bool rollback) +{ + struct task_struct *task = current; + if (!ee) + return; + if (rollback) { + task->tomoyo_domain_info = ee->previous_domain; + /* + * Make task->tomoyo_domain_info visible to GC before changing + * task->tomoyo_flags . + */ + smp_mb(); + } else { + /* Mark the current process as execute handler. */ + if (ee->handler) + task->tomoyo_flags |= TOMOYO_TASK_IS_EXECUTE_HANDLER; + /* Mark the current process as normal process. */ + else + task->tomoyo_flags &= ~TOMOYO_TASK_IS_EXECUTE_HANDLER; + } + /* Tell GC that I finished execve(). */ + task->tomoyo_flags &= ~TOMOYO_TASK_IS_IN_EXECVE; + /* This lock is acquired at tomoyo_start_execve(). */ + tomoyo_read_unlock(ee->reader_idx); + kfree(ee->handler_path); + kfree(ee->tmp); + kfree(ee->dump.data); + kfree(ee); +} -- -- 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/