aboutsummaryrefslogtreecommitdiff
path: root/security/tomoyo/domain.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/tomoyo/domain.c')
-rw-r--r--security/tomoyo/domain.c1449
1 files changed, 714 insertions, 735 deletions
diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c
index fcf52accce2..38651454ed0 100644
--- a/security/tomoyo/domain.c
+++ b/security/tomoyo/domain.c
@@ -1,786 +1,667 @@
/*
* security/tomoyo/domain.c
*
- * Implementation of the Domain-Based Mandatory Access Control.
- *
- * Copyright (C) 2005-2009 NTT DATA CORPORATION
- *
- * Version: 2.2.0 2009/04/01
- *
+ * Copyright (C) 2005-2011 NTT DATA CORPORATION
*/
#include "common.h"
-#include "tomoyo.h"
-#include "realpath.h"
#include <linux/binfmts.h>
+#include <linux/slab.h>
/* 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 "<kernel>"; echo "allow_execute /sbin/init" ) > \
- * /sys/kernel/security/tomoyo/domain_policy
- *
- * and is deleted by
- *
- * # ( echo "<kernel>"; 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 "<kernel>" > /sys/kernel/security/tomoyo/domain_policy
- *
- * and is deleted by
- *
- * # echo "delete <kernel>" > /sys/kernel/security/tomoyo/domain_policy
+/**
+ * tomoyo_update_policy - Update an entry for exception policy.
*
- * and all domains are retrieved by
+ * @new_entry: Pointer to "struct tomoyo_acl_info".
+ * @size: Size of @new_entry in bytes.
+ * @param: Pointer to "struct tomoyo_acl_param".
+ * @check_duplicate: Callback function to find duplicated entry.
*
- * # grep '^<kernel>' /sys/kernel/security/tomoyo/domain_policy
+ * Returns 0 on success, negative value otherwise.
*
- * 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);
-DECLARE_RWSEM(tomoyo_domain_list_lock);
-
-/*
- * tomoyo_domain_initializer_entry is a structure which is used for holding
- * "initialize_domain" and "no_initialize_domain" entries.
- * It has following fields.
- *
- * (1) "list" which is linked to tomoyo_domain_initializer_list .
- * (2) "domainname" which is "a domainname" or "the last component of a
- * domainname". This field is NULL if "from" clause is not specified.
- * (3) "program" which is a program's pathname.
- * (4) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
- * (5) "is_not" is a bool which is true if "no_initialize_domain", false
- * otherwise.
- * (6) "is_last_name" is a bool which is true if "domainname" is "the last
- * component of a domainname", false otherwise.
+ * Caller holds tomoyo_read_lock().
*/
-struct tomoyo_domain_initializer_entry {
- struct list_head list;
- const struct tomoyo_path_info *domainname; /* This may be NULL */
- const struct tomoyo_path_info *program;
- bool is_deleted;
- bool is_not; /* True if this entry is "no_initialize_domain". */
- /* True if the domainname is tomoyo_get_last_name(). */
- bool is_last_name;
-};
-
-/*
- * tomoyo_domain_keeper_entry is a structure which is used for holding
- * "keep_domain" and "no_keep_domain" entries.
- * It has following fields.
- *
- * (1) "list" which is linked to tomoyo_domain_keeper_list .
- * (2) "domainname" which is "a domainname" or "the last component of a
- * domainname".
- * (3) "program" which is a program's pathname.
- * This field is NULL if "from" clause is not specified.
- * (4) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
- * (5) "is_not" is a bool which is true if "no_initialize_domain", false
- * otherwise.
- * (6) "is_last_name" is a bool which is true if "domainname" is "the last
- * component of a domainname", false otherwise.
- */
-struct tomoyo_domain_keeper_entry {
- struct list_head list;
- const struct tomoyo_path_info *domainname;
- const struct tomoyo_path_info *program; /* This may be NULL */
- bool is_deleted;
- bool is_not; /* True if this entry is "no_keep_domain". */
- /* True if the domainname is tomoyo_get_last_name(). */
- bool is_last_name;
-};
+int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size,
+ struct tomoyo_acl_param *param,
+ bool (*check_duplicate) (const struct tomoyo_acl_head
+ *,
+ const struct tomoyo_acl_head
+ *))
+{
+ int error = param->is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_acl_head *entry;
+ struct list_head *list = param->list;
-/*
- * tomoyo_alias_entry is a structure which is used for holding "alias" entries.
- * It has following fields.
- *
- * (1) "list" which is linked to tomoyo_alias_list .
- * (2) "original_name" which is a dereferenced pathname.
- * (3) "aliased_name" which is a symlink's pathname.
- * (4) "is_deleted" is a bool which is true if marked as deleted, false
- * otherwise.
- */
-struct tomoyo_alias_entry {
- struct list_head list;
- const struct tomoyo_path_info *original_name;
- const struct tomoyo_path_info *aliased_name;
- bool is_deleted;
-};
+ if (mutex_lock_interruptible(&tomoyo_policy_lock))
+ return -ENOMEM;
+ list_for_each_entry_rcu(entry, list, list) {
+ if (entry->is_deleted == TOMOYO_GC_IN_PROGRESS)
+ continue;
+ if (!check_duplicate(entry, new_entry))
+ continue;
+ entry->is_deleted = param->is_delete;
+ error = 0;
+ break;
+ }
+ if (error && !param->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_set_domain_flag - Set or clear domain's attribute flags.
+ * tomoyo_same_acl_head - Check for duplicated "struct tomoyo_acl_info" entry.
*
- * @domain: Pointer to "struct tomoyo_domain_info".
- * @is_delete: True if it is a delete request.
- * @flags: Flags to set or clear.
+ * @a: Pointer to "struct tomoyo_acl_info".
+ * @b: Pointer to "struct tomoyo_acl_info".
*
- * Returns nothing.
+ * Returns true if @a == @b, false otherwise.
*/
-void tomoyo_set_domain_flag(struct tomoyo_domain_info *domain,
- const bool is_delete, const u8 flags)
+static inline bool tomoyo_same_acl_head(const struct tomoyo_acl_info *a,
+ const struct tomoyo_acl_info *b)
{
- /* We need to serialize because this is bitfield operation. */
- static DEFINE_SPINLOCK(lock);
- spin_lock(&lock);
- if (!is_delete)
- domain->flags |= flags;
- else
- domain->flags &= ~flags;
- spin_unlock(&lock);
+ return a->type == b->type && a->cond == b->cond;
}
/**
- * tomoyo_get_last_name - Get last component of a domainname.
+ * tomoyo_update_domain - Update an entry for domain policy.
+ *
+ * @new_entry: Pointer to "struct tomoyo_acl_info".
+ * @size: Size of @new_entry in bytes.
+ * @param: Pointer to "struct tomoyo_acl_param".
+ * @check_duplicate: Callback function to find duplicated entry.
+ * @merge_duplicate: Callback function to merge duplicated entry.
*
- * @domain: Pointer to "struct tomoyo_domain_info".
+ * Returns 0 on success, negative value otherwise.
*
- * Returns the last component of the domainname.
+ * Caller holds tomoyo_read_lock().
*/
-const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain)
+int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size,
+ struct tomoyo_acl_param *param,
+ 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))
{
- const char *cp0 = domain->domainname->name;
- const char *cp1 = strrchr(cp0, ' ');
-
- if (cp1)
- return cp1 + 1;
- return cp0;
+ const bool is_delete = param->is_delete;
+ int error = is_delete ? -ENOENT : -ENOMEM;
+ struct tomoyo_acl_info *entry;
+ struct list_head * const list = param->list;
+
+ if (param->data[0]) {
+ new_entry->cond = tomoyo_get_condition(param);
+ if (!new_entry->cond)
+ return -EINVAL;
+ /*
+ * Domain transition preference is allowed for only
+ * "file execute" entries.
+ */
+ if (new_entry->cond->transit &&
+ !(new_entry->type == TOMOYO_TYPE_PATH_ACL &&
+ container_of(new_entry, struct tomoyo_path_acl, head)
+ ->perm == 1 << TOMOYO_TYPE_EXECUTE))
+ goto out;
+ }
+ if (mutex_lock_interruptible(&tomoyo_policy_lock))
+ goto out;
+ list_for_each_entry_rcu(entry, list, list) {
+ if (entry->is_deleted == TOMOYO_GC_IN_PROGRESS)
+ continue;
+ if (!tomoyo_same_acl_head(entry, new_entry) ||
+ !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, list);
+ error = 0;
+ }
+ }
+ mutex_unlock(&tomoyo_policy_lock);
+out:
+ tomoyo_put_condition(new_entry->cond);
+ 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
+/**
+ * tomoyo_check_acl - Do permission check.
*
- * In the example above, /usr/sbin/httpd will belong to
- * "<kernel> /usr/sbin/httpd" domain.
+ * @r: Pointer to "struct tomoyo_request_info".
+ * @check_entry: Callback function to check type specific parameters.
*
- * You may specify a domainname using "from" keyword.
- * "initialize_domain /usr/sbin/httpd from <kernel> /etc/rc.d/init.d/httpd"
- * will cause "/usr/sbin/httpd" executed from "<kernel> /etc/rc.d/init.d/httpd"
- * domain to belong to "<kernel> /usr/sbin/httpd" domain.
+ * Returns 0 on success, negative value otherwise.
*
- * You may add "no_" prefix to "initialize_domain".
- * "initialize_domain /usr/sbin/httpd" and
- * "no_initialize_domain /usr/sbin/httpd from <kernel> /etc/rc.d/init.d/httpd"
- * will cause "/usr/sbin/httpd" to belong to "<kernel> /usr/sbin/httpd" domain
- * unless executed from "<kernel> /etc/rc.d/init.d/httpd" domain.
+ * Caller holds tomoyo_read_lock().
*/
-static LIST_HEAD(tomoyo_domain_initializer_list);
-static DECLARE_RWSEM(tomoyo_domain_initializer_list_lock);
+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;
+ bool retried = false;
+ const struct list_head *list = &domain->acl_info_list;
+
+retry:
+ list_for_each_entry_rcu(ptr, list, list) {
+ if (ptr->is_deleted || ptr->type != r->param_type)
+ continue;
+ if (!check_entry(r, ptr))
+ continue;
+ if (!tomoyo_condition(r, ptr->cond))
+ continue;
+ r->matched_acl = ptr;
+ r->granted = true;
+ return;
+ }
+ if (!retried) {
+ retried = true;
+ list = &domain->ns->acl_group[domain->group];
+ goto retry;
+ }
+ r->granted = false;
+}
+
+/* The list for "struct tomoyo_domain_info". */
+LIST_HEAD(tomoyo_domain_list);
/**
- * tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list.
+ * tomoyo_last_word - Get last component of a domainname.
*
- * @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.
+ * @name: Domainname to check.
*
- * Returns 0 on success, negative value otherwise.
+ * Returns the last word of @domainname.
*/
-static int tomoyo_update_domain_initializer_entry(const char *domainname,
- const char *program,
- const bool is_not,
- const bool is_delete)
+static const char *tomoyo_last_word(const char *name)
{
- struct tomoyo_domain_initializer_entry *new_entry;
- struct tomoyo_domain_initializer_entry *ptr;
- const struct tomoyo_path_info *saved_program;
- const struct tomoyo_path_info *saved_domainname = NULL;
- int error = -ENOMEM;
- bool is_last_name = false;
-
- if (!tomoyo_is_correct_path(program, 1, -1, -1, __func__))
- return -EINVAL; /* No patterns allowed. */
- if (domainname) {
- if (!tomoyo_is_domain_def(domainname) &&
- tomoyo_is_correct_path(domainname, 1, -1, -1, __func__))
- is_last_name = true;
- else if (!tomoyo_is_correct_domain(domainname, __func__))
- return -EINVAL;
- saved_domainname = tomoyo_save_name(domainname);
- if (!saved_domainname)
- return -ENOMEM;
- }
- saved_program = tomoyo_save_name(program);
- if (!saved_program)
- return -ENOMEM;
- down_write(&tomoyo_domain_initializer_list_lock);
- list_for_each_entry(ptr, &tomoyo_domain_initializer_list, list) {
- if (ptr->is_not != is_not ||
- ptr->domainname != saved_domainname ||
- ptr->program != saved_program)
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- goto out;
- }
- if (is_delete) {
- error = -ENOENT;
- goto out;
- }
- new_entry = tomoyo_alloc_element(sizeof(*new_entry));
- if (!new_entry)
- goto out;
- new_entry->domainname = saved_domainname;
- new_entry->program = saved_program;
- new_entry->is_not = is_not;
- new_entry->is_last_name = is_last_name;
- list_add_tail(&new_entry->list, &tomoyo_domain_initializer_list);
- error = 0;
- out:
- up_write(&tomoyo_domain_initializer_list_lock);
- return error;
+ const char *cp = strrchr(name, ' ');
+ if (cp)
+ return cp + 1;
+ return name;
}
/**
- * tomoyo_read_domain_initializer_policy - Read "struct tomoyo_domain_initializer_entry" list.
+ * tomoyo_same_transition_control - Check for duplicated "struct tomoyo_transition_control" entry.
*
- * @head: Pointer to "struct tomoyo_io_buffer".
+ * @a: Pointer to "struct tomoyo_acl_head".
+ * @b: Pointer to "struct tomoyo_acl_head".
*
- * Returns true on success, false otherwise.
+ * Returns true if @a == @b, false otherwise.
*/
-bool tomoyo_read_domain_initializer_policy(struct tomoyo_io_buffer *head)
+static bool tomoyo_same_transition_control(const struct tomoyo_acl_head *a,
+ const struct tomoyo_acl_head *b)
{
- struct list_head *pos;
- bool done = true;
-
- down_read(&tomoyo_domain_initializer_list_lock);
- 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;
- }
- up_read(&tomoyo_domain_initializer_list_lock);
- return done;
+ 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_write_domain_initializer_policy - Write "struct tomoyo_domain_initializer_entry" list.
+ * tomoyo_write_transition_control - Write "struct tomoyo_transition_control" list.
*
- * @data: String to parse.
- * @is_not: True if it is "no_initialize_domain" entry.
- * @is_delete: True if it is a delete request.
+ * @param: Pointer to "struct tomoyo_acl_param".
+ * @type: Type of this entry.
*
* Returns 0 on success, negative value otherwise.
*/
-int tomoyo_write_domain_initializer_policy(char *data, const bool is_not,
- const bool is_delete)
+int tomoyo_write_transition_control(struct tomoyo_acl_param *param,
+ const u8 type)
{
- char *cp = strstr(data, " from ");
-
- if (cp) {
- *cp = '\0';
- return tomoyo_update_domain_initializer_entry(cp + 6, data,
- is_not,
- is_delete);
+ struct tomoyo_transition_control e = { .type = type };
+ int error = param->is_delete ? -ENOENT : -ENOMEM;
+ char *program = param->data;
+ char *domainname = strstr(program, " from ");
+ if (domainname) {
+ *domainname = '\0';
+ domainname += 6;
+ } else if (type == TOMOYO_TRANSITION_CONTROL_NO_KEEP ||
+ type == TOMOYO_TRANSITION_CONTROL_KEEP) {
+ domainname = program;
+ program = NULL;
+ }
+ if (program && strcmp(program, "any")) {
+ if (!tomoyo_correct_path(program))
+ return -EINVAL;
+ e.program = tomoyo_get_name(program);
+ if (!e.program)
+ goto out;
}
- return tomoyo_update_domain_initializer_entry(NULL, data, is_not,
- is_delete);
+ if (domainname && strcmp(domainname, "any")) {
+ 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;
+ }
+ param->list = &param->ns->policy_list[TOMOYO_ID_TRANSITION_CONTROL];
+ error = tomoyo_update_policy(&e.head, sizeof(e), param,
+ tomoyo_same_transition_control);
+out:
+ tomoyo_put_name(e.domainname);
+ tomoyo_put_name(e.program);
+ return error;
}
/**
- * tomoyo_is_domain_initializer - Check whether the given program causes domainname reinitialization.
+ * tomoyo_scan_transition - Try to find specific domain transition type.
*
- * @domainname: The name of domain.
- * @program: The name of program.
+ * @list: Pointer to "struct list_head".
+ * @domainname: The name of current domain.
+ * @program: The name of requested program.
* @last_name: The last component of @domainname.
+ * @type: One of values in "enum tomoyo_transition_type".
*
- * Returns true if executing @program reinitializes domain transition,
- * false otherwise.
+ * Returns true if found one, 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)
+static inline bool tomoyo_scan_transition
+(const struct list_head *list, const struct tomoyo_path_info *domainname,
+ const struct tomoyo_path_info *program, const char *last_name,
+ const enum tomoyo_transition_type type)
{
- struct tomoyo_domain_initializer_entry *ptr;
- bool flag = false;
-
- down_read(&tomoyo_domain_initializer_list_lock);
- list_for_each_entry(ptr, &tomoyo_domain_initializer_list, list) {
- if (ptr->is_deleted)
+ const struct tomoyo_transition_control *ptr;
+ list_for_each_entry_rcu(ptr, list, 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 {
- if (tomoyo_pathcmp(ptr->domainname, last_name))
+ /*
+ * Use direct strcmp() since this is
+ * unlikely used.
+ */
+ if (strcmp(ptr->domainname->name, last_name))
continue;
}
}
- if (tomoyo_pathcmp(ptr->program, program))
+ if (ptr->program && tomoyo_pathcmp(ptr->program, program))
continue;
- if (ptr->is_not) {
- flag = false;
- break;
- }
- flag = true;
+ return true;
}
- up_read(&tomoyo_domain_initializer_list_lock);
- return flag;
+ return false;
}
-/*
- * 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 <kernel> /usr/sbin/sshd /bin/bash' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and is deleted by
- *
- * # echo 'delete keep_domain <kernel> /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
- * "<kernel> /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 <kernel> /usr/sbin/sshd /bin/bash"
- * will cause "/bin/pwd" executed from "<kernel> /usr/sbin/sshd /bin/bash"
- * domain to remain in "<kernel> /usr/sbin/sshd /bin/bash" domain.
- *
- * You may add "no_" prefix to "keep_domain".
- * "keep_domain <kernel> /usr/sbin/sshd /bin/bash" and
- * "no_keep_domain /usr/bin/passwd from <kernel> /usr/sbin/sshd /bin/bash" will
- * cause "/usr/bin/passwd" to belong to
- * "<kernel> /usr/sbin/sshd /bin/bash /usr/bin/passwd" domain, unless
- * explicitly specified by "initialize_domain".
- */
-static LIST_HEAD(tomoyo_domain_keeper_list);
-static DECLARE_RWSEM(tomoyo_domain_keeper_list_lock);
-
/**
- * tomoyo_update_domain_keeper_entry - Update "struct tomoyo_domain_keeper_entry" list.
+ * tomoyo_transition_type - Get domain transition type.
*
- * @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.
+ * @ns: Pointer to "struct tomoyo_policy_namespace".
+ * @domainname: The name of current domain.
+ * @program: The name of requested program.
*
- * Returns 0 on success, negative value otherwise.
+ * Returns TOMOYO_TRANSITION_CONTROL_TRANSIT if executing @program causes
+ * domain transition across namespaces, TOMOYO_TRANSITION_CONTROL_INITIALIZE if
+ * executing @program reinitializes domain transition within that namespace,
+ * TOMOYO_TRANSITION_CONTROL_KEEP if executing @program stays at @domainname ,
+ * others otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
*/
-static int tomoyo_update_domain_keeper_entry(const char *domainname,
- const char *program,
- const bool is_not,
- const bool is_delete)
+static enum tomoyo_transition_type tomoyo_transition_type
+(const struct tomoyo_policy_namespace *ns,
+ const struct tomoyo_path_info *domainname,
+ const struct tomoyo_path_info *program)
{
- struct tomoyo_domain_keeper_entry *new_entry;
- struct tomoyo_domain_keeper_entry *ptr;
- const struct tomoyo_path_info *saved_domainname;
- const struct tomoyo_path_info *saved_program = NULL;
- int error = -ENOMEM;
- bool is_last_name = false;
-
- if (!tomoyo_is_domain_def(domainname) &&
- tomoyo_is_correct_path(domainname, 1, -1, -1, __func__))
- is_last_name = true;
- else if (!tomoyo_is_correct_domain(domainname, __func__))
- return -EINVAL;
- if (program) {
- if (!tomoyo_is_correct_path(program, 1, -1, -1, __func__))
- return -EINVAL;
- saved_program = tomoyo_save_name(program);
- if (!saved_program)
- return -ENOMEM;
- }
- saved_domainname = tomoyo_save_name(domainname);
- if (!saved_domainname)
- return -ENOMEM;
- down_write(&tomoyo_domain_keeper_list_lock);
- list_for_each_entry(ptr, &tomoyo_domain_keeper_list, list) {
- if (ptr->is_not != is_not ||
- ptr->domainname != saved_domainname ||
- ptr->program != saved_program)
+ const char *last_name = tomoyo_last_word(domainname->name);
+ enum tomoyo_transition_type type = TOMOYO_TRANSITION_CONTROL_NO_RESET;
+ while (type < TOMOYO_MAX_TRANSITION_TYPE) {
+ const struct list_head * const list =
+ &ns->policy_list[TOMOYO_ID_TRANSITION_CONTROL];
+ if (!tomoyo_scan_transition(list, domainname, program,
+ last_name, type)) {
+ type++;
continue;
- ptr->is_deleted = is_delete;
- error = 0;
- goto out;
- }
- if (is_delete) {
- error = -ENOENT;
- goto out;
+ }
+ if (type != TOMOYO_TRANSITION_CONTROL_NO_RESET &&
+ type != TOMOYO_TRANSITION_CONTROL_NO_INITIALIZE)
+ break;
+ /*
+ * Do not check for reset_domain if no_reset_domain matched.
+ * Do not check for initialize_domain if no_initialize_domain
+ * matched.
+ */
+ type++;
+ type++;
}
- new_entry = tomoyo_alloc_element(sizeof(*new_entry));
- if (!new_entry)
- goto out;
- new_entry->domainname = saved_domainname;
- new_entry->program = saved_program;
- new_entry->is_not = is_not;
- new_entry->is_last_name = is_last_name;
- list_add_tail(&new_entry->list, &tomoyo_domain_keeper_list);
- error = 0;
- out:
- up_write(&tomoyo_domain_keeper_list_lock);
- return error;
+ return type;
}
/**
- * tomoyo_write_domain_keeper_policy - Write "struct tomoyo_domain_keeper_entry" list.
+ * tomoyo_same_aggregator - Check for duplicated "struct tomoyo_aggregator" entry.
*
- * @data: String to parse.
- * @is_not: True if it is "no_keep_domain" entry.
- * @is_delete: True if it is a delete request.
+ * @a: Pointer to "struct tomoyo_acl_head".
+ * @b: Pointer to "struct tomoyo_acl_head".
*
+ * Returns true if @a == @b, false otherwise.
*/
-int tomoyo_write_domain_keeper_policy(char *data, const bool is_not,
- const bool is_delete)
+static bool tomoyo_same_aggregator(const struct tomoyo_acl_head *a,
+ const struct tomoyo_acl_head *b)
{
- 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);
+ 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_read_domain_keeper_policy - Read "struct tomoyo_domain_keeper_entry" list.
+ * tomoyo_write_aggregator - Write "struct tomoyo_aggregator" list.
*
- * @head: Pointer to "struct tomoyo_io_buffer".
+ * @param: Pointer to "struct tomoyo_acl_param".
*
- * Returns true on success, false otherwise.
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
*/
-bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head)
+int tomoyo_write_aggregator(struct tomoyo_acl_param *param)
{
- struct list_head *pos;
- bool done = true;
-
- down_read(&tomoyo_domain_keeper_list_lock);
- 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;
- }
- up_read(&tomoyo_domain_keeper_list_lock);
- return done;
+ struct tomoyo_aggregator e = { };
+ int error = param->is_delete ? -ENOENT : -ENOMEM;
+ const char *original_name = tomoyo_read_token(param);
+ const char *aggregated_name = tomoyo_read_token(param);
+ if (!tomoyo_correct_word(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;
+ param->list = &param->ns->policy_list[TOMOYO_ID_AGGREGATOR];
+ error = tomoyo_update_policy(&e.head, sizeof(e), param,
+ tomoyo_same_aggregator);
+out:
+ tomoyo_put_name(e.original_name);
+ tomoyo_put_name(e.aggregated_name);
+ return error;
}
/**
- * tomoyo_is_domain_keeper - Check whether the given program causes domain transition suppression.
+ * tomoyo_find_namespace - Find specified namespace.
*
- * @domainname: The name of domain.
- * @program: The name of program.
- * @last_name: The last component of @domainname.
+ * @name: Name of namespace to find.
+ * @len: Length of @name.
+ *
+ * Returns pointer to "struct tomoyo_policy_namespace" if found,
+ * NULL otherwise.
*
- * 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)
+static struct tomoyo_policy_namespace *tomoyo_find_namespace
+(const char *name, const unsigned int len)
{
- struct tomoyo_domain_keeper_entry *ptr;
- bool flag = false;
-
- down_read(&tomoyo_domain_keeper_list_lock);
- list_for_each_entry(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))
+ struct tomoyo_policy_namespace *ns;
+ list_for_each_entry(ns, &tomoyo_namespace_list, namespace_list) {
+ if (strncmp(name, ns->name, len) ||
+ (name[len] && name[len] != ' '))
continue;
- if (ptr->is_not) {
- flag = false;
- break;
- }
- flag = true;
+ return ns;
}
- up_read(&tomoyo_domain_keeper_list_lock);
- return flag;
+ return NULL;
}
-/*
- * tomoyo_alias_list is used for holding list of symlink's pathnames which are
- * allowed to be passed to an execve() request. Normally, the domainname which
- * the current process will belong to after execve() succeeds is calculated
- * using dereferenced pathnames. But some programs behave differently depending
- * on the name passed to argv[0]. For busybox, calculating domainname using
- * dereferenced pathnames will cause all programs in the busybox to belong to
- * the same domain. Thus, TOMOYO provides a way to allow use of symlink's
- * pathname for checking execve()'s permission and calculating domainname which
- * the current process will belong to after execve() succeeds.
- *
- * An entry is added by
- *
- * # echo 'alias /bin/busybox /bin/cat' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and is deleted by
- *
- * # echo 'delete alias /bin/busybox /bin/cat' > \
- * /sys/kernel/security/tomoyo/exception_policy
- *
- * and all entries are retrieved by
- *
- * # grep ^alias /sys/kernel/security/tomoyo/exception_policy
- *
- * In the example above, if /bin/cat is a symlink to /bin/busybox and execution
- * of /bin/cat is requested, permission is checked for /bin/cat rather than
- * /bin/busybox and domainname which the current process will belong to after
- * execve() succeeds is calculated using /bin/cat rather than /bin/busybox .
- */
-static LIST_HEAD(tomoyo_alias_list);
-static DECLARE_RWSEM(tomoyo_alias_list_lock);
-
/**
- * tomoyo_update_alias_entry - Update "struct tomoyo_alias_entry" list.
+ * tomoyo_assign_namespace - Create a new namespace.
*
- * @original_name: The original program's real name.
- * @aliased_name: The symbolic program's symbolic link's name.
- * @is_delete: True if it is a delete request.
+ * @domainname: Name of namespace to create.
*
- * Returns 0 on success, negative value otherwise.
+ * Returns pointer to "struct tomoyo_policy_namespace" on success,
+ * NULL otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
*/
-static int tomoyo_update_alias_entry(const char *original_name,
- const char *aliased_name,
- const bool is_delete)
+struct tomoyo_policy_namespace *tomoyo_assign_namespace(const char *domainname)
{
- struct tomoyo_alias_entry *new_entry;
- struct tomoyo_alias_entry *ptr;
- const struct tomoyo_path_info *saved_original_name;
- const struct tomoyo_path_info *saved_aliased_name;
- int error = -ENOMEM;
-
- if (!tomoyo_is_correct_path(original_name, 1, -1, -1, __func__) ||
- !tomoyo_is_correct_path(aliased_name, 1, -1, -1, __func__))
- return -EINVAL; /* No patterns allowed. */
- saved_original_name = tomoyo_save_name(original_name);
- saved_aliased_name = tomoyo_save_name(aliased_name);
- if (!saved_original_name || !saved_aliased_name)
- return -ENOMEM;
- down_write(&tomoyo_alias_list_lock);
- list_for_each_entry(ptr, &tomoyo_alias_list, list) {
- if (ptr->original_name != saved_original_name ||
- ptr->aliased_name != saved_aliased_name)
- continue;
- ptr->is_deleted = is_delete;
- error = 0;
- goto out;
- }
- if (is_delete) {
- error = -ENOENT;
+ struct tomoyo_policy_namespace *ptr;
+ struct tomoyo_policy_namespace *entry;
+ const char *cp = domainname;
+ unsigned int len = 0;
+ while (*cp && *cp++ != ' ')
+ len++;
+ ptr = tomoyo_find_namespace(domainname, len);
+ if (ptr)
+ return ptr;
+ if (len >= TOMOYO_EXEC_TMPSIZE - 10 || !tomoyo_domain_def(domainname))
+ return NULL;
+ entry = kzalloc(sizeof(*entry) + len + 1, GFP_NOFS);
+ if (!entry)
+ return NULL;
+ if (mutex_lock_interruptible(&tomoyo_policy_lock))
goto out;
+ ptr = tomoyo_find_namespace(domainname, len);
+ if (!ptr && tomoyo_memory_ok(entry)) {
+ char *name = (char *) (entry + 1);
+ ptr = entry;
+ memmove(name, domainname, len);
+ name[len] = '\0';
+ entry->name = name;
+ tomoyo_init_policy_namespace(entry);
+ entry = NULL;
}
- new_entry = tomoyo_alloc_element(sizeof(*new_entry));
- if (!new_entry)
- goto out;
- new_entry->original_name = saved_original_name;
- new_entry->aliased_name = saved_aliased_name;
- list_add_tail(&new_entry->list, &tomoyo_alias_list);
- error = 0;
- out:
- up_write(&tomoyo_alias_list_lock);
- return error;
+ mutex_unlock(&tomoyo_policy_lock);
+out:
+ kfree(entry);
+ return ptr;
}
/**
- * tomoyo_read_alias_policy - Read "struct tomoyo_alias_entry" list.
+ * tomoyo_namespace_jump - Check for namespace jump.
*
- * @head: Pointer to "struct tomoyo_io_buffer".
+ * @domainname: Name of domain.
*
- * Returns true on success, false otherwise.
+ * Returns true if namespace differs, false otherwise.
*/
-bool tomoyo_read_alias_policy(struct tomoyo_io_buffer *head)
+static bool tomoyo_namespace_jump(const char *domainname)
{
- struct list_head *pos;
- bool done = true;
-
- down_read(&tomoyo_alias_list_lock);
- list_for_each_cookie(pos, head->read_var2, &tomoyo_alias_list) {
- struct tomoyo_alias_entry *ptr;
-
- ptr = list_entry(pos, struct tomoyo_alias_entry, list);
- if (ptr->is_deleted)
- continue;
- done = tomoyo_io_printf(head, TOMOYO_KEYWORD_ALIAS "%s %s\n",
- ptr->original_name->name,
- ptr->aliased_name->name);
- if (!done)
- break;
- }
- up_read(&tomoyo_alias_list_lock);
- return done;
+ const char *namespace = tomoyo_current_namespace()->name;
+ const int len = strlen(namespace);
+ return strncmp(domainname, namespace, len) ||
+ (domainname[len] && domainname[len] != ' ');
}
/**
- * tomoyo_write_alias_policy - Write "struct tomoyo_alias_entry" list.
+ * tomoyo_assign_domain - Create a domain or a namespace.
*
- * @data: String to parse.
- * @is_delete: True if it is a delete request.
+ * @domainname: The name of domain.
+ * @transit: True if transit to domain found or created.
*
- * Returns 0 on success, negative value otherwise.
+ * Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
*/
-int tomoyo_write_alias_policy(char *data, const bool is_delete)
+struct tomoyo_domain_info *tomoyo_assign_domain(const char *domainname,
+ const bool transit)
{
- char *cp = strchr(data, ' ');
-
- if (!cp)
- return -EINVAL;
- *cp++ = '\0';
- return tomoyo_update_alias_entry(data, cp, is_delete);
+ struct tomoyo_domain_info e = { };
+ struct tomoyo_domain_info *entry = tomoyo_find_domain(domainname);
+ bool created = false;
+ if (entry) {
+ if (transit) {
+ /*
+ * Since namespace is created at runtime, profiles may
+ * not be created by the moment the process transits to
+ * that domain. Do not perform domain transition if
+ * profile for that domain is not yet created.
+ */
+ if (tomoyo_policy_loaded &&
+ !entry->ns->profile_ptr[entry->profile])
+ return NULL;
+ }
+ return entry;
+ }
+ /* Requested domain does not exist. */
+ /* Don't create requested domain if domainname is invalid. */
+ if (strlen(domainname) >= TOMOYO_EXEC_TMPSIZE - 10 ||
+ !tomoyo_correct_domain(domainname))
+ return NULL;
+ /*
+ * Since definition of profiles and acl_groups may differ across
+ * namespaces, do not inherit "use_profile" and "use_group" settings
+ * by automatically creating requested domain upon domain transition.
+ */
+ if (transit && tomoyo_namespace_jump(domainname))
+ return NULL;
+ e.ns = tomoyo_assign_namespace(domainname);
+ if (!e.ns)
+ return NULL;
+ /*
+ * "use_profile" and "use_group" settings for automatically created
+ * domains are inherited from current domain. These are 0 for manually
+ * created domains.
+ */
+ if (transit) {
+ const struct tomoyo_domain_info *domain = tomoyo_domain();
+ e.profile = domain->profile;
+ e.group = domain->group;
+ }
+ e.domainname = tomoyo_get_name(domainname);
+ if (!e.domainname)
+ return NULL;
+ if (mutex_lock_interruptible(&tomoyo_policy_lock))
+ goto out;
+ entry = tomoyo_find_domain(domainname);
+ if (!entry) {
+ entry = tomoyo_commit_ok(&e, sizeof(e));
+ if (entry) {
+ INIT_LIST_HEAD(&entry->acl_info_list);
+ list_add_tail_rcu(&entry->list, &tomoyo_domain_list);
+ created = true;
+ }
+ }
+ mutex_unlock(&tomoyo_policy_lock);
+out:
+ tomoyo_put_name(e.domainname);
+ if (entry && transit) {
+ if (created) {
+ struct tomoyo_request_info r;
+ tomoyo_init_request_info(&r, entry,
+ TOMOYO_MAC_FILE_EXECUTE);
+ r.granted = false;
+ tomoyo_write_log(&r, "use_profile %u\n",
+ entry->profile);
+ tomoyo_write_log(&r, "use_group %u\n", entry->group);
+ tomoyo_update_stat(TOMOYO_STAT_POLICY_UPDATES);
+ }
+ }
+ return entry;
}
/**
- * tomoyo_find_or_assign_new_domain - Create a domain.
+ * tomoyo_environ - Check permission for environment variable names.
*
- * @domainname: The name of domain.
- * @profile: Profile number to assign if the domain was newly created.
+ * @ee: Pointer to "struct tomoyo_execve".
*
- * Returns pointer to "struct tomoyo_domain_info" on success, NULL otherwise.
+ * Returns 0 on success, negative value otherwise.
*/
-struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
- domainname,
- const u8 profile)
+static int tomoyo_environ(struct tomoyo_execve *ee)
{
- struct tomoyo_domain_info *domain = NULL;
- const struct tomoyo_path_info *saved_domainname;
+ struct tomoyo_request_info *r = &ee->r;
+ struct linux_binprm *bprm = ee->bprm;
+ /* env_page.data is allocated by tomoyo_dump_page(). */
+ struct tomoyo_page_dump env_page = { };
+ char *arg_ptr; /* Size is TOMOYO_EXEC_TMPSIZE bytes */
+ int arg_len = 0;
+ unsigned long pos = bprm->p;
+ int offset = pos % PAGE_SIZE;
+ int argv_count = bprm->argc;
+ int envp_count = bprm->envc;
+ int error = -ENOMEM;
- down_write(&tomoyo_domain_list_lock);
- domain = tomoyo_find_domain(domainname);
- if (domain)
- goto out;
- if (!tomoyo_is_correct_domain(domainname, __func__))
+ ee->r.type = TOMOYO_MAC_ENVIRON;
+ ee->r.profile = r->domain->profile;
+ ee->r.mode = tomoyo_get_mode(r->domain->ns, ee->r.profile,
+ TOMOYO_MAC_ENVIRON);
+ if (!r->mode || !envp_count)
+ return 0;
+ arg_ptr = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS);
+ if (!arg_ptr)
goto out;
- saved_domainname = tomoyo_save_name(domainname);
- if (!saved_domainname)
- goto out;
- /* Can I reuse memory of deleted domain? */
- list_for_each_entry(domain, &tomoyo_domain_list, list) {
- struct task_struct *p;
- struct tomoyo_acl_info *ptr;
- bool flag;
- if (!domain->is_deleted ||
- domain->domainname != saved_domainname)
- continue;
- flag = false;
- read_lock(&tasklist_lock);
- for_each_process(p) {
- if (tomoyo_real_domain(p) != domain)
- continue;
- flag = true;
- break;
+ while (error == -ENOMEM) {
+ if (!tomoyo_dump_page(bprm, pos, &env_page))
+ goto out;
+ pos += PAGE_SIZE - offset;
+ /* Read. */
+ while (argv_count && offset < PAGE_SIZE) {
+ if (!env_page.data[offset++])
+ argv_count--;
}
- read_unlock(&tasklist_lock);
- if (flag)
+ if (argv_count) {
+ offset = 0;
continue;
- list_for_each_entry(ptr, &domain->acl_info_list, list) {
- ptr->type |= TOMOYO_ACL_DELETED;
}
- tomoyo_set_domain_flag(domain, true, domain->flags);
- domain->profile = profile;
- domain->quota_warned = false;
- mb(); /* Avoid out-of-order execution. */
- domain->is_deleted = false;
- goto out;
- }
- /* No memory reusable. Create using new memory. */
- domain = tomoyo_alloc_element(sizeof(*domain));
- if (domain) {
- INIT_LIST_HEAD(&domain->acl_info_list);
- domain->domainname = saved_domainname;
- domain->profile = profile;
- list_add_tail(&domain->list, &tomoyo_domain_list);
+ while (offset < PAGE_SIZE) {
+ const unsigned char c = env_page.data[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:
- up_write(&tomoyo_domain_list_lock);
- return domain;
+out:
+ if (r->mode != TOMOYO_CONFIG_ENFORCING)
+ error = 0;
+ kfree(env_page.data);
+ kfree(arg_ptr);
+ return error;
}
/**
@@ -789,134 +670,232 @@ struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
* @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)
{
- /*
- * This function assumes that the size of buffer returned by
- * tomoyo_realpath() = TOMOYO_MAX_PATHNAME_LEN.
- */
- struct tomoyo_page_buffer *tmp = tomoyo_alloc(sizeof(*tmp));
struct tomoyo_domain_info *old_domain = tomoyo_domain();
struct tomoyo_domain_info *domain = NULL;
- const char *old_domain_name = old_domain->domainname->name;
const char *original_name = bprm->filename;
- char *new_domain_name = NULL;
- char *real_program_name = NULL;
- char *symlink_program_name = NULL;
- const u8 mode = tomoyo_check_flags(old_domain, TOMOYO_MAC_FOR_FILE);
- const bool is_enforce = (mode == 3);
int retval = -ENOMEM;
- struct tomoyo_path_info r; /* real name */
- struct tomoyo_path_info s; /* symlink name */
- struct tomoyo_path_info l; /* last name */
- static bool initialized;
-
- if (!tmp)
- goto out;
+ bool reject_on_transition_failure = false;
+ const struct tomoyo_path_info *candidate;
+ struct tomoyo_path_info exename;
+ struct tomoyo_execve *ee = kzalloc(sizeof(*ee), GFP_NOFS);
- if (!initialized) {
- /*
- * Built-in initializers. This is needed because policies are
- * not loaded until starting /sbin/init.
- */
- tomoyo_update_domain_initializer_entry(NULL, "/sbin/hotplug",
- false, false);
- tomoyo_update_domain_initializer_entry(NULL, "/sbin/modprobe",
- false, false);
- initialized = true;
+ if (!ee)
+ return -ENOMEM;
+ ee->tmp = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS);
+ if (!ee->tmp) {
+ kfree(ee);
+ return -ENOMEM;
}
-
- /* Get tomoyo_realpath of program. */
+ /* ee->dump->data is allocated by tomoyo_dump_page(). */
+ 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 = bprm->file->f_path;
+ /* Get symlink's pathname of program. */
retval = -ENOENT;
- /* I hope tomoyo_realpath() won't fail with -ENOMEM. */
- real_program_name = tomoyo_realpath(original_name);
- if (!real_program_name)
+ exename.name = tomoyo_realpath_nofollow(original_name);
+ if (!exename.name)
goto out;
- /* Get tomoyo_realpath of symbolic link. */
- symlink_program_name = tomoyo_realpath_nofollow(original_name);
- if (!symlink_program_name)
- goto out;
-
- r.name = real_program_name;
- tomoyo_fill_path_info(&r);
- s.name = symlink_program_name;
- tomoyo_fill_path_info(&s);
- l.name = tomoyo_get_last_name(old_domain);
- tomoyo_fill_path_info(&l);
-
- /* Check 'alias' directive. */
- if (tomoyo_pathcmp(&r, &s)) {
- struct tomoyo_alias_entry *ptr;
- /* Is this program allowed to be called via symbolic links? */
- down_read(&tomoyo_alias_list_lock);
- list_for_each_entry(ptr, &tomoyo_alias_list, list) {
- if (ptr->is_deleted ||
- tomoyo_pathcmp(&r, ptr->original_name) ||
- tomoyo_pathcmp(&s, ptr->aliased_name))
+ tomoyo_fill_path_info(&exename);
+retry:
+ /* Check 'aggregator' directive. */
+ {
+ struct tomoyo_aggregator *ptr;
+ struct list_head *list =
+ &old_domain->ns->policy_list[TOMOYO_ID_AGGREGATOR];
+ /* Check 'aggregator' directive. */
+ candidate = &exename;
+ list_for_each_entry_rcu(ptr, list, head.list) {
+ if (ptr->head.is_deleted ||
+ !tomoyo_path_matches_pattern(&exename,
+ ptr->original_name))
continue;
- memset(real_program_name, 0, TOMOYO_MAX_PATHNAME_LEN);
- strncpy(real_program_name, ptr->aliased_name->name,
- TOMOYO_MAX_PATHNAME_LEN - 1);
- tomoyo_fill_path_info(&r);
+ candidate = ptr->aggregated_name;
break;
}
- up_read(&tomoyo_alias_list_lock);
}
/* Check execute permission. */
- retval = tomoyo_check_exec_perm(old_domain, &r);
+ retval = tomoyo_execute_permission(&ee->r, candidate);
+ 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 (ee->r.param.path.matched_path)
+ candidate = ee->r.param.path.matched_path;
- new_domain_name = tmp->buffer;
- if (tomoyo_is_domain_initializer(old_domain->domainname, &r, &l)) {
- /* Transit to the child of tomoyo_kernel_domain domain. */
- snprintf(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1,
- TOMOYO_ROOT_NAME " " "%s", real_program_name);
- } else if (old_domain == &tomoyo_kernel_domain &&
- !tomoyo_policy_loaded) {
+ /*
+ * Check for domain transition preference if "file execute" matched.
+ * If preference is given, make do_execve() fail if domain transition
+ * has failed, for domain transition preference should be used with
+ * destination domain defined.
+ */
+ if (ee->transition) {
+ const char *domainname = ee->transition->name;
+ reject_on_transition_failure = true;
+ if (!strcmp(domainname, "keep"))
+ goto force_keep_domain;
+ if (!strcmp(domainname, "child"))
+ goto force_child_domain;
+ if (!strcmp(domainname, "reset"))
+ goto force_reset_domain;
+ if (!strcmp(domainname, "initialize"))
+ goto force_initialize_domain;
+ if (!strcmp(domainname, "parent")) {
+ char *cp;
+ strncpy(ee->tmp, old_domain->domainname->name,
+ TOMOYO_EXEC_TMPSIZE - 1);
+ cp = strrchr(ee->tmp, ' ');
+ if (cp)
+ *cp = '\0';
+ } else if (*domainname == '<')
+ strncpy(ee->tmp, domainname, TOMOYO_EXEC_TMPSIZE - 1);
+ else
+ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s",
+ old_domain->domainname->name, domainname);
+ goto force_jump_domain;
+ }
+ /*
+ * No domain transition preference specified.
+ * Calculate domain to transit to.
+ */
+ switch (tomoyo_transition_type(old_domain->ns, old_domain->domainname,
+ candidate)) {
+ case TOMOYO_TRANSITION_CONTROL_RESET:
+force_reset_domain:
+ /* Transit to the root of specified namespace. */
+ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>",
+ candidate->name);
/*
- * 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.
+ * Make do_execve() fail if domain transition across namespaces
+ * has failed.
*/
- domain = old_domain;
- } else if (tomoyo_is_domain_keeper(old_domain->domainname, &r, &l)) {
+ reject_on_transition_failure = true;
+ break;
+ case TOMOYO_TRANSITION_CONTROL_INITIALIZE:
+force_initialize_domain:
+ /* Transit to the child of current namespace's root. */
+ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s",
+ old_domain->ns->name, candidate->name);
+ break;
+ case TOMOYO_TRANSITION_CONTROL_KEEP:
+force_keep_domain:
/* Keep current domain. */
domain = old_domain;
- } else {
+ 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;
+ break;
+ }
+force_child_domain:
/* Normal domain transition. */
- snprintf(new_domain_name, TOMOYO_MAX_PATHNAME_LEN + 1,
- "%s %s", old_domain_name, real_program_name);
+ snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s",
+ old_domain->domainname->name, candidate->name);
+ break;
}
- if (domain || strlen(new_domain_name) >= TOMOYO_MAX_PATHNAME_LEN)
- goto done;
- down_read(&tomoyo_domain_list_lock);
- domain = tomoyo_find_domain(new_domain_name);
- up_read(&tomoyo_domain_list_lock);
- if (domain)
- goto done;
- if (is_enforce)
- goto done;
- domain = tomoyo_find_or_assign_new_domain(new_domain_name,
- old_domain->profile);
- done:
+force_jump_domain:
+ if (!domain)
+ domain = tomoyo_assign_domain(ee->tmp, true);
if (domain)
- goto out;
- printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n",
- new_domain_name);
- if (is_enforce)
- retval = -EPERM;
- else
- tomoyo_set_domain_flag(old_domain, false,
- TOMOYO_DOMAIN_FLAGS_TRANSITION_FAILED);
+ retval = 0;
+ else if (reject_on_transition_failure) {
+ printk(KERN_WARNING "ERROR: Domain '%s' not ready.\n",
+ ee->tmp);
+ retval = -ENOMEM;
+ } else if (ee->r.mode == TOMOYO_CONFIG_ENFORCING)
+ retval = -ENOMEM;
+ else {
+ retval = 0;
+ if (!old_domain->flags[TOMOYO_DIF_TRANSITION_FAILED]) {
+ old_domain->flags[TOMOYO_DIF_TRANSITION_FAILED] = true;
+ ee->r.granted = false;
+ tomoyo_write_log(&ee->r, "%s", tomoyo_dif
+ [TOMOYO_DIF_TRANSITION_FAILED]);
+ printk(KERN_WARNING
+ "ERROR: Domain '%s' not defined.\n", ee->tmp);
+ }
+ }
out:
if (!domain)
domain = old_domain;
+ /* Update reference count on "struct tomoyo_domain_info". */
+ atomic_inc(&domain->users);
bprm->cred->security = domain;
- tomoyo_free(real_program_name);
- tomoyo_free(symlink_program_name);
- tomoyo_free(tmp);
+ kfree(exename.name);
+ if (!retval) {
+ ee->r.domain = domain;
+ retval = tomoyo_environ(ee);
+ }
+ kfree(ee->tmp);
+ kfree(ee->dump.data);
+ kfree(ee);
return retval;
}
+
+/**
+ * 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_find_next_domain(). */
+ if (!dump->data) {
+ dump->data = kzalloc(PAGE_SIZE, GFP_NOFS);
+ if (!dump->data)
+ return false;
+ }
+ /* Same with get_arg_page(bprm, pos, 0) in fs/exec.c */
+#ifdef 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);
+
+ dump->page = page;
+ memcpy(dump->data + offset, kaddr + offset,
+ PAGE_SIZE - offset);
+ kunmap_atomic(kaddr);
+ }
+ /* Same with put_arg_page(page) in fs/exec.c */
+#ifdef CONFIG_MMU
+ put_page(page);
+#endif
+ return true;
+}