/* * 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; }