diff options
Diffstat (limited to 'security/tomoyo/domain.c')
-rw-r--r-- | security/tomoyo/domain.c | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c new file mode 100644 index 00000000..35388408 --- /dev/null +++ b/security/tomoyo/domain.c @@ -0,0 +1,542 @@ +/* + * security/tomoyo/domain.c + * + * Domain transition functions for TOMOYO. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + */ + +#include "common.h" +#include <linux/binfmts.h> +#include <linux/slab.h> + +/* Variables definitions.*/ + +/* The initial domain. */ +struct tomoyo_domain_info tomoyo_kernel_domain; + +/** + * tomoyo_update_policy - Update an entry for exception policy. + * + * @new_entry: Pointer to "struct tomoyo_acl_info". + * @size: Size of @new_entry in bytes. + * @is_delete: True if it is a delete request. + * @list: Pointer to "struct list_head". + * @check_duplicate: Callback function to find duplicated entry. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size, + bool is_delete, struct list_head *list, + bool (*check_duplicate) (const struct tomoyo_acl_head + *, + const struct tomoyo_acl_head + *)) +{ + int error = is_delete ? -ENOENT : -ENOMEM; + struct tomoyo_acl_head *entry; + + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + return -ENOMEM; + list_for_each_entry_rcu(entry, list, list) { + if (!check_duplicate(entry, new_entry)) + continue; + entry->is_deleted = is_delete; + error = 0; + break; + } + if (error && !is_delete) { + entry = tomoyo_commit_ok(new_entry, size); + if (entry) { + list_add_tail_rcu(&entry->list, list); + error = 0; + } + } + mutex_unlock(&tomoyo_policy_lock); + return error; +} + +/** + * tomoyo_update_domain - Update an entry for domain policy. + * + * @new_entry: Pointer to "struct tomoyo_acl_info". + * @size: Size of @new_entry in bytes. + * @is_delete: True if it is a delete request. + * @domain: Pointer to "struct tomoyo_domain_info". + * @check_duplicate: Callback function to find duplicated entry. + * @merge_duplicate: Callback function to merge duplicated entry. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size, + bool is_delete, struct tomoyo_domain_info *domain, + bool (*check_duplicate) (const struct tomoyo_acl_info + *, + const struct tomoyo_acl_info + *), + bool (*merge_duplicate) (struct tomoyo_acl_info *, + struct tomoyo_acl_info *, + const bool)) +{ + int error = is_delete ? -ENOENT : -ENOMEM; + struct tomoyo_acl_info *entry; + + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + return error; + list_for_each_entry_rcu(entry, &domain->acl_info_list, list) { + if (!check_duplicate(entry, new_entry)) + continue; + if (merge_duplicate) + entry->is_deleted = merge_duplicate(entry, new_entry, + is_delete); + else + entry->is_deleted = is_delete; + error = 0; + break; + } + if (error && !is_delete) { + entry = tomoyo_commit_ok(new_entry, size); + if (entry) { + list_add_tail_rcu(&entry->list, &domain->acl_info_list); + error = 0; + } + } + mutex_unlock(&tomoyo_policy_lock); + return error; +} + +void tomoyo_check_acl(struct tomoyo_request_info *r, + bool (*check_entry) (struct tomoyo_request_info *, + const struct tomoyo_acl_info *)) +{ + const struct tomoyo_domain_info *domain = r->domain; + struct tomoyo_acl_info *ptr; + + list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) { + if (ptr->is_deleted || ptr->type != r->param_type) + continue; + if (check_entry(r, ptr)) { + r->granted = true; + return; + } + } + r->granted = false; +} + +/* The list for "struct tomoyo_domain_info". */ +LIST_HEAD(tomoyo_domain_list); + +struct list_head tomoyo_policy_list[TOMOYO_MAX_POLICY]; +struct list_head tomoyo_group_list[TOMOYO_MAX_GROUP]; + +/** + * tomoyo_last_word - Get last component of a domainname. + * + * @domainname: Domainname to check. + * + * Returns the last word of @domainname. + */ +static const char *tomoyo_last_word(const char *name) +{ + const char *cp = strrchr(name, ' '); + if (cp) + return cp + 1; + return name; +} + +static bool tomoyo_same_transition_control(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + const struct tomoyo_transition_control *p1 = container_of(a, + typeof(*p1), + head); + const struct tomoyo_transition_control *p2 = container_of(b, + typeof(*p2), + head); + return p1->type == p2->type && p1->is_last_name == p2->is_last_name + && p1->domainname == p2->domainname + && p1->program == p2->program; +} + +/** + * tomoyo_update_transition_control_entry - Update "struct tomoyo_transition_control" list. + * + * @domainname: The name of domain. Maybe NULL. + * @program: The name of program. Maybe NULL. + * @type: Type of transition. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_update_transition_control_entry(const char *domainname, + const char *program, + const u8 type, + const bool is_delete) +{ + struct tomoyo_transition_control e = { .type = type }; + int error = is_delete ? -ENOENT : -ENOMEM; + if (program) { + if (!tomoyo_correct_path(program)) + return -EINVAL; + e.program = tomoyo_get_name(program); + if (!e.program) + goto out; + } + if (domainname) { + if (!tomoyo_correct_domain(domainname)) { + if (!tomoyo_correct_path(domainname)) + goto out; + e.is_last_name = true; + } + e.domainname = tomoyo_get_name(domainname); + if (!e.domainname) + goto out; + } + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list + [TOMOYO_ID_TRANSITION_CONTROL], + tomoyo_same_transition_control); + out: + tomoyo_put_name(e.domainname); + tomoyo_put_name(e.program); + return error; +} + +/** + * tomoyo_write_transition_control - Write "struct tomoyo_transition_control" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * @type: Type of this entry. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_transition_control(char *data, const bool is_delete, + const u8 type) +{ + char *domainname = strstr(data, " from "); + if (domainname) { + *domainname = '\0'; + domainname += 6; + } else if (type == TOMOYO_TRANSITION_CONTROL_NO_KEEP || + type == TOMOYO_TRANSITION_CONTROL_KEEP) { + domainname = data; + data = NULL; + } + return tomoyo_update_transition_control_entry(domainname, data, type, + is_delete); +} + +/** + * tomoyo_transition_type - Get domain transition type. + * + * @domainname: The name of domain. + * @program: The name of program. + * + * Returns TOMOYO_TRANSITION_CONTROL_INITIALIZE if executing @program + * reinitializes domain transition, TOMOYO_TRANSITION_CONTROL_KEEP if executing + * @program suppresses domain transition, others otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static u8 tomoyo_transition_type(const struct tomoyo_path_info *domainname, + const struct tomoyo_path_info *program) +{ + const struct tomoyo_transition_control *ptr; + const char *last_name = tomoyo_last_word(domainname->name); + u8 type; + for (type = 0; type < TOMOYO_MAX_TRANSITION_TYPE; type++) { + next: + list_for_each_entry_rcu(ptr, &tomoyo_policy_list + [TOMOYO_ID_TRANSITION_CONTROL], + head.list) { + if (ptr->head.is_deleted || ptr->type != type) + continue; + if (ptr->domainname) { + if (!ptr->is_last_name) { + if (ptr->domainname != domainname) + continue; + } else { + /* + * Use direct strcmp() since this is + * unlikely used. + */ + if (strcmp(ptr->domainname->name, + last_name)) + continue; + } + } + if (ptr->program && + tomoyo_pathcmp(ptr->program, program)) + continue; + if (type == TOMOYO_TRANSITION_CONTROL_NO_INITIALIZE) { + /* + * Do not check for initialize_domain if + * no_initialize_domain matched. + */ + type = TOMOYO_TRANSITION_CONTROL_NO_KEEP; + goto next; + } + goto done; + } + } + done: + return type; +} + +static bool tomoyo_same_aggregator(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + const struct tomoyo_aggregator *p1 = container_of(a, typeof(*p1), head); + const struct tomoyo_aggregator *p2 = container_of(b, typeof(*p2), head); + return p1->original_name == p2->original_name && + p1->aggregated_name == p2->aggregated_name; +} + +/** + * tomoyo_update_aggregator_entry - Update "struct tomoyo_aggregator" list. + * + * @original_name: The original program's name. + * @aggregated_name: The program name to use. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_update_aggregator_entry(const char *original_name, + const char *aggregated_name, + const bool is_delete) +{ + struct tomoyo_aggregator e = { }; + int error = is_delete ? -ENOENT : -ENOMEM; + + if (!tomoyo_correct_path(original_name) || + !tomoyo_correct_path(aggregated_name)) + 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 || + e.aggregated_name->is_patterned) /* No patterns allowed. */ + goto out; + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list[TOMOYO_ID_AGGREGATOR], + tomoyo_same_aggregator); + out: + tomoyo_put_name(e.original_name); + tomoyo_put_name(e.aggregated_name); + return error; +} + +/** + * tomoyo_write_aggregator - Write "struct tomoyo_aggregator" list. + * + * @data: String to parse. + * @is_delete: True if it is a delete request. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_aggregator(char *data, const bool is_delete) +{ + char *cp = strchr(data, ' '); + + if (!cp) + return -EINVAL; + *cp++ = '\0'; + return tomoyo_update_aggregator_entry(data, cp, is_delete); +} + +/** + * tomoyo_assign_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. + * + * Caller holds tomoyo_read_lock(). + */ +struct tomoyo_domain_info *tomoyo_assign_domain(const char *domainname, + const u8 profile) +{ + struct tomoyo_domain_info *entry; + struct tomoyo_domain_info *domain = NULL; + const struct tomoyo_path_info *saved_domainname; + bool found = false; + + if (!tomoyo_correct_domain(domainname)) + return NULL; + saved_domainname = tomoyo_get_name(domainname); + if (!saved_domainname) + return NULL; + entry = kzalloc(sizeof(*entry), GFP_NOFS); + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + goto out; + 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)) { + 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); + out: + tomoyo_put_name(saved_domainname); + kfree(entry); + return found ? domain : NULL; +} + +/** + * tomoyo_find_next_domain - Find a domain. + * + * @bprm: Pointer to "struct linux_binprm". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_find_next_domain(struct linux_binprm *bprm) +{ + struct tomoyo_request_info r; + char *tmp = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS); + struct tomoyo_domain_info *old_domain = tomoyo_domain(); + struct tomoyo_domain_info *domain = NULL; + const char *original_name = bprm->filename; + u8 mode; + bool is_enforce; + int retval = -ENOMEM; + bool need_kfree = false; + struct tomoyo_path_info rn = { }; /* real name */ + + mode = tomoyo_init_request_info(&r, NULL, TOMOYO_MAC_FILE_EXECUTE); + is_enforce = (mode == TOMOYO_CONFIG_ENFORCING); + if (!tmp) + goto out; + + retry: + if (need_kfree) { + kfree(rn.name); + need_kfree = false; + } + /* Get symlink's pathname of program. */ + retval = -ENOENT; + rn.name = tomoyo_realpath_nofollow(original_name); + if (!rn.name) + goto out; + tomoyo_fill_path_info(&rn); + need_kfree = true; + + /* Check 'aggregator' directive. */ + { + struct tomoyo_aggregator *ptr; + list_for_each_entry_rcu(ptr, &tomoyo_policy_list + [TOMOYO_ID_AGGREGATOR], head.list) { + if (ptr->head.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_path_permission(&r, TOMOYO_TYPE_EXECUTE, &rn); + if (retval == TOMOYO_RETRY_REQUEST) + goto retry; + if (retval < 0) + goto out; + /* + * To be able to specify domainnames with wildcards, use the + * pathname specified in the policy (which may contain + * wildcard) rather than the pathname passed to execve() + * (which never contains wildcard). + */ + if (r.param.path.matched_path) { + if (need_kfree) + kfree(rn.name); + need_kfree = false; + /* This is OK because it is read only. */ + rn = *r.param.path.matched_path; + } + + /* Calculate domain to transit to. */ + switch (tomoyo_transition_type(old_domain->domainname, &rn)) { + case TOMOYO_TRANSITION_CONTROL_INITIALIZE: + /* Transit to the child of tomoyo_kernel_domain domain. */ + snprintf(tmp, TOMOYO_EXEC_TMPSIZE - 1, TOMOYO_ROOT_NAME " " + "%s", rn.name); + break; + case TOMOYO_TRANSITION_CONTROL_KEEP: + /* Keep current domain. */ + domain = old_domain; + break; + default: + if (old_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 = old_domain; + } else { + /* Normal domain transition. */ + snprintf(tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", + old_domain->domainname->name, rn.name); + } + break; + } + if (domain || strlen(tmp) >= TOMOYO_EXEC_TMPSIZE - 10) + goto done; + domain = tomoyo_find_domain(tmp); + if (domain) + goto done; + if (is_enforce) { + int error = tomoyo_supervisor(&r, "# wants to create domain\n" + "%s\n", tmp); + if (error == TOMOYO_RETRY_REQUEST) + goto retry; + if (error < 0) + goto done; + } + domain = tomoyo_assign_domain(tmp, old_domain->profile); + done: + if (domain) + goto out; + printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n", tmp); + if (is_enforce) + retval = -EPERM; + else + old_domain->transition_failed = true; + out: + if (!domain) + domain = old_domain; + /* Update reference count on "struct tomoyo_domain_info". */ + atomic_inc(&domain->users); + bprm->cred->security = domain; + if (need_kfree) + kfree(rn.name); + kfree(tmp); + return retval; +} |