Commit 847b173e authored by Tetsuo Handa's avatar Tetsuo Handa Committed by James Morris
Browse files

TOMOYO: Add garbage collector.



This patch adds garbage collector support to TOMOYO.
Elements are protected by "struct srcu_struct tomoyo_ss".
Signed-off-by: default avatarTetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Acked-by: default avatarSerge Hallyn <serue@us.ibm.com>
Signed-off-by: default avatarJames Morris <jmorris@namei.org>
parent ec8e6a4e
obj-y = common.o realpath.o tomoyo.o domain.o file.o obj-y = common.o realpath.o tomoyo.o domain.o file.o gc.o
...@@ -1067,7 +1067,7 @@ static int tomoyo_read_profile(struct tomoyo_io_buffer *head) ...@@ -1067,7 +1067,7 @@ static int tomoyo_read_profile(struct tomoyo_io_buffer *head)
* *
* # cat /sys/kernel/security/tomoyo/manager * # cat /sys/kernel/security/tomoyo/manager
*/ */
static LIST_HEAD(tomoyo_policy_manager_list); LIST_HEAD(tomoyo_policy_manager_list);
/** /**
* tomoyo_update_manager_entry - Add a manager entry. * tomoyo_update_manager_entry - Add a manager entry.
...@@ -2109,6 +2109,7 @@ static int tomoyo_write_control(struct file *file, const char __user *buffer, ...@@ -2109,6 +2109,7 @@ static int tomoyo_write_control(struct file *file, const char __user *buffer,
static int tomoyo_close_control(struct file *file) static int tomoyo_close_control(struct file *file)
{ {
struct tomoyo_io_buffer *head = file->private_data; struct tomoyo_io_buffer *head = file->private_data;
const bool is_write = !!head->write_buf;
tomoyo_read_unlock(head->reader_idx); tomoyo_read_unlock(head->reader_idx);
/* Release memory used for policy I/O. */ /* Release memory used for policy I/O. */
...@@ -2119,6 +2120,8 @@ static int tomoyo_close_control(struct file *file) ...@@ -2119,6 +2120,8 @@ static int tomoyo_close_control(struct file *file)
kfree(head); kfree(head);
head = NULL; head = NULL;
file->private_data = NULL; file->private_data = NULL;
if (is_write)
tomoyo_run_gc();
return 0; return 0;
} }
......
...@@ -638,6 +638,11 @@ int tomoyo_check_rewrite_permission(struct tomoyo_domain_info *domain, ...@@ -638,6 +638,11 @@ int tomoyo_check_rewrite_permission(struct tomoyo_domain_info *domain,
struct file *filp); struct file *filp);
int tomoyo_find_next_domain(struct linux_binprm *bprm); int tomoyo_find_next_domain(struct linux_binprm *bprm);
/* Run garbage collector. */
void tomoyo_run_gc(void);
void tomoyo_memory_free(void *ptr);
/********** External variable definitions. **********/ /********** External variable definitions. **********/
/* Lock for GC. */ /* Lock for GC. */
...@@ -646,6 +651,16 @@ extern struct srcu_struct tomoyo_ss; ...@@ -646,6 +651,16 @@ extern struct srcu_struct tomoyo_ss;
/* The list for "struct tomoyo_domain_info". */ /* The list for "struct tomoyo_domain_info". */
extern struct list_head tomoyo_domain_list; extern struct list_head tomoyo_domain_list;
extern struct list_head tomoyo_domain_initializer_list;
extern struct list_head tomoyo_domain_keeper_list;
extern struct list_head tomoyo_alias_list;
extern struct list_head tomoyo_globally_readable_list;
extern struct list_head tomoyo_pattern_list;
extern struct list_head tomoyo_no_rewrite_list;
extern struct list_head tomoyo_policy_manager_list;
extern struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
extern struct mutex tomoyo_name_list_lock;
/* Lock for protecting policy. */ /* Lock for protecting policy. */
extern struct mutex tomoyo_policy_lock; extern struct mutex tomoyo_policy_lock;
......
...@@ -110,7 +110,7 @@ const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain) ...@@ -110,7 +110,7 @@ const char *tomoyo_get_last_name(const struct tomoyo_domain_info *domain)
* will cause "/usr/sbin/httpd" to belong to "<kernel> /usr/sbin/httpd" domain * will cause "/usr/sbin/httpd" to belong to "<kernel> /usr/sbin/httpd" domain
* unless executed from "<kernel> /etc/rc.d/init.d/httpd" domain. * unless executed from "<kernel> /etc/rc.d/init.d/httpd" domain.
*/ */
static LIST_HEAD(tomoyo_domain_initializer_list); LIST_HEAD(tomoyo_domain_initializer_list);
/** /**
* tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list. * tomoyo_update_domain_initializer_entry - Update "struct tomoyo_domain_initializer_entry" list.
...@@ -330,7 +330,7 @@ static bool tomoyo_is_domain_initializer(const struct tomoyo_path_info * ...@@ -330,7 +330,7 @@ static bool tomoyo_is_domain_initializer(const struct tomoyo_path_info *
* "<kernel> /usr/sbin/sshd /bin/bash /usr/bin/passwd" domain, unless * "<kernel> /usr/sbin/sshd /bin/bash /usr/bin/passwd" domain, unless
* explicitly specified by "initialize_domain". * explicitly specified by "initialize_domain".
*/ */
static LIST_HEAD(tomoyo_domain_keeper_list); LIST_HEAD(tomoyo_domain_keeper_list);
/** /**
* tomoyo_update_domain_keeper_entry - Update "struct tomoyo_domain_keeper_entry" list. * tomoyo_update_domain_keeper_entry - Update "struct tomoyo_domain_keeper_entry" list.
...@@ -533,7 +533,7 @@ static bool tomoyo_is_domain_keeper(const struct tomoyo_path_info *domainname, ...@@ -533,7 +533,7 @@ static bool tomoyo_is_domain_keeper(const struct tomoyo_path_info *domainname,
* /bin/busybox and domainname which the current process will belong to after * /bin/busybox and domainname which the current process will belong to after
* execve() succeeds is calculated using /bin/cat rather than /bin/busybox . * execve() succeeds is calculated using /bin/cat rather than /bin/busybox .
*/ */
static LIST_HEAD(tomoyo_alias_list); LIST_HEAD(tomoyo_alias_list);
/** /**
* tomoyo_update_alias_entry - Update "struct tomoyo_alias_entry" list. * tomoyo_update_alias_entry - Update "struct tomoyo_alias_entry" list.
......
...@@ -148,7 +148,7 @@ static int tomoyo_update_single_path_acl(const u8 type, const char *filename, ...@@ -148,7 +148,7 @@ static int tomoyo_update_single_path_acl(const u8 type, const char *filename,
* given "allow_read /lib/libc-2.5.so" to the domain which current process * given "allow_read /lib/libc-2.5.so" to the domain which current process
* belongs to. * belongs to.
*/ */
static LIST_HEAD(tomoyo_globally_readable_list); LIST_HEAD(tomoyo_globally_readable_list);
/** /**
* tomoyo_update_globally_readable_entry - Update "struct tomoyo_globally_readable_file_entry" list. * tomoyo_update_globally_readable_entry - Update "struct tomoyo_globally_readable_file_entry" list.
...@@ -295,7 +295,7 @@ bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head) ...@@ -295,7 +295,7 @@ bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head)
* which pretends as if /proc/self/ is not a symlink; so that we can forbid * which pretends as if /proc/self/ is not a symlink; so that we can forbid
* current process from accessing other process's information. * current process from accessing other process's information.
*/ */
static LIST_HEAD(tomoyo_pattern_list); LIST_HEAD(tomoyo_pattern_list);
/** /**
* tomoyo_update_file_pattern_entry - Update "struct tomoyo_pattern_entry" list. * tomoyo_update_file_pattern_entry - Update "struct tomoyo_pattern_entry" list.
...@@ -448,7 +448,7 @@ bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head) ...@@ -448,7 +448,7 @@ bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head)
* " (deleted)" suffix if the file is already unlink()ed; so that we don't * " (deleted)" suffix if the file is already unlink()ed; so that we don't
* need to worry whether the file is already unlink()ed or not. * need to worry whether the file is already unlink()ed or not.
*/ */
static LIST_HEAD(tomoyo_no_rewrite_list); LIST_HEAD(tomoyo_no_rewrite_list);
/** /**
* tomoyo_update_no_rewrite_entry - Update "struct tomoyo_no_rewrite_entry" list. * tomoyo_update_no_rewrite_entry - Update "struct tomoyo_no_rewrite_entry" list.
......
/*
* security/tomoyo/gc.c
*
* Implementation of the Domain-Based Mandatory Access Control.
*
* Copyright (C) 2005-2010 NTT DATA CORPORATION
*
*/
#include "common.h"
#include <linux/kthread.h>
enum tomoyo_gc_id {
TOMOYO_ID_DOMAIN_INITIALIZER,
TOMOYO_ID_DOMAIN_KEEPER,
TOMOYO_ID_ALIAS,
TOMOYO_ID_GLOBALLY_READABLE,
TOMOYO_ID_PATTERN,
TOMOYO_ID_NO_REWRITE,
TOMOYO_ID_MANAGER,
TOMOYO_ID_NAME,
TOMOYO_ID_ACL,
TOMOYO_ID_DOMAIN
};
struct tomoyo_gc_entry {
struct list_head list;
int type;
void *element;
};
static LIST_HEAD(tomoyo_gc_queue);
static DEFINE_MUTEX(tomoyo_gc_mutex);
/* Caller holds tomoyo_policy_lock mutex. */
static bool tomoyo_add_to_gc(const int type, void *element)
{
struct tomoyo_gc_entry *entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
if (!entry)
return false;
entry->type = type;
entry->element = element;
list_add(&entry->list, &tomoyo_gc_queue);
return true;
}
static void tomoyo_del_allow_read
(struct tomoyo_globally_readable_file_entry *ptr)
{
tomoyo_put_name(ptr->filename);
}
static void tomoyo_del_file_pattern(struct tomoyo_pattern_entry *ptr)
{
tomoyo_put_name(ptr->pattern);
}
static void tomoyo_del_no_rewrite(struct tomoyo_no_rewrite_entry *ptr)
{
tomoyo_put_name(ptr->pattern);
}
static void tomoyo_del_domain_initializer
(struct tomoyo_domain_initializer_entry *ptr)
{
tomoyo_put_name(ptr->domainname);
tomoyo_put_name(ptr->program);
}
static void tomoyo_del_domain_keeper(struct tomoyo_domain_keeper_entry *ptr)
{
tomoyo_put_name(ptr->domainname);
tomoyo_put_name(ptr->program);
}
static void tomoyo_del_alias(struct tomoyo_alias_entry *ptr)
{
tomoyo_put_name(ptr->original_name);
tomoyo_put_name(ptr->aliased_name);
}
static void tomoyo_del_manager(struct tomoyo_policy_manager_entry *ptr)
{
tomoyo_put_name(ptr->manager);
}
static void tomoyo_del_acl(struct tomoyo_acl_info *acl)
{
switch (acl->type) {
case TOMOYO_TYPE_SINGLE_PATH_ACL:
{
struct tomoyo_single_path_acl_record *entry
= container_of(acl, typeof(*entry), head);
tomoyo_put_name(entry->filename);
}
break;
case TOMOYO_TYPE_DOUBLE_PATH_ACL:
{
struct tomoyo_double_path_acl_record *entry
= container_of(acl, typeof(*entry), head);
tomoyo_put_name(entry->filename1);
tomoyo_put_name(entry->filename2);
}
break;
default:
printk(KERN_WARNING "Unknown type\n");
break;
}
}
static bool tomoyo_del_domain(struct tomoyo_domain_info *domain)
{
struct tomoyo_acl_info *acl;
struct tomoyo_acl_info *tmp;
/*
* Since we don't protect whole execve() operation using SRCU,
* we need to recheck domain->users at this point.
*
* (1) Reader starts SRCU section upon execve().
* (2) Reader traverses tomoyo_domain_list and finds this domain.
* (3) Writer marks this domain as deleted.
* (4) Garbage collector removes this domain from tomoyo_domain_list
* because this domain is marked as deleted and used by nobody.
* (5) Reader saves reference to this domain into
* "struct linux_binprm"->cred->security .
* (6) Reader finishes SRCU section, although execve() operation has
* not finished yet.
* (7) Garbage collector waits for SRCU synchronization.
* (8) Garbage collector kfree() this domain because this domain is
* used by nobody.
* (9) Reader finishes execve() operation and restores this domain from
* "struct linux_binprm"->cred->security.
*
* By updating domain->users at (5), we can solve this race problem
* by rechecking domain->users at (8).
*/
if (atomic_read(&domain->users))
return false;
list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) {
tomoyo_del_acl(acl);
tomoyo_memory_free(acl);
}
tomoyo_put_name(domain->domainname);
return true;
}
static void tomoyo_del_name(const struct tomoyo_name_entry *ptr)
{
}
static void tomoyo_collect_entry(void)
{
mutex_lock(&tomoyo_policy_lock);
{
struct tomoyo_globally_readable_file_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_globally_readable_list,
list) {
if (!ptr->is_deleted)
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_GLOBALLY_READABLE, ptr))
list_del_rcu(&ptr->list);
else
break;
}
}
{
struct tomoyo_pattern_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_pattern_list, list) {
if (!ptr->is_deleted)
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_PATTERN, ptr))
list_del_rcu(&ptr->list);
else
break;
}
}
{
struct tomoyo_no_rewrite_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_no_rewrite_list, list) {
if (!ptr->is_deleted)
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_NO_REWRITE, ptr))
list_del_rcu(&ptr->list);
else
break;
}
}
{
struct tomoyo_domain_initializer_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_domain_initializer_list,
list) {
if (!ptr->is_deleted)
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN_INITIALIZER, ptr))
list_del_rcu(&ptr->list);
else
break;
}
}
{
struct tomoyo_domain_keeper_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_domain_keeper_list, list) {
if (!ptr->is_deleted)
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN_KEEPER, ptr))
list_del_rcu(&ptr->list);
else
break;
}
}
{
struct tomoyo_alias_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_alias_list, list) {
if (!ptr->is_deleted)
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_ALIAS, ptr))
list_del_rcu(&ptr->list);
else
break;
}
}
{
struct tomoyo_policy_manager_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_policy_manager_list,
list) {
if (!ptr->is_deleted)
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_MANAGER, ptr))
list_del_rcu(&ptr->list);
else
break;
}
}
{
struct tomoyo_domain_info *domain;
list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {
struct tomoyo_acl_info *acl;
list_for_each_entry_rcu(acl, &domain->acl_info_list,
list) {
switch (acl->type) {
case TOMOYO_TYPE_SINGLE_PATH_ACL:
if (container_of(acl,
struct tomoyo_single_path_acl_record,
head)->perm ||
container_of(acl,
struct tomoyo_single_path_acl_record,
head)->perm_high)
continue;
break;
case TOMOYO_TYPE_DOUBLE_PATH_ACL:
if (container_of(acl,
struct tomoyo_double_path_acl_record,
head)->perm)
continue;
break;
default:
continue;
}
if (tomoyo_add_to_gc(TOMOYO_ID_ACL, acl))
list_del_rcu(&acl->list);
else
break;
}
if (!domain->is_deleted || atomic_read(&domain->users))
continue;
/*
* Nobody is referring this domain. But somebody may
* refer this domain after successful execve().
* We recheck domain->users after SRCU synchronization.
*/
if (tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, domain))
list_del_rcu(&domain->list);
else
break;
}
}
mutex_unlock(&tomoyo_policy_lock);
mutex_lock(&tomoyo_name_list_lock);
{
int i;
for (i = 0; i < TOMOYO_MAX_HASH; i++) {
struct tomoyo_name_entry *ptr;
list_for_each_entry_rcu(ptr, &tomoyo_name_list[i],
list) {
if (atomic_read(&ptr->users))
continue;
if (tomoyo_add_to_gc(TOMOYO_ID_NAME, ptr))
list_del_rcu(&ptr->list);
else {
i = TOMOYO_MAX_HASH;
break;
}
}
}
}
mutex_unlock(&tomoyo_name_list_lock);
}
static void tomoyo_kfree_entry(void)
{
struct tomoyo_gc_entry *p;
struct tomoyo_gc_entry *tmp;
list_for_each_entry_safe(p, tmp, &tomoyo_gc_queue, list) {
switch (p->type) {
case TOMOYO_ID_DOMAIN_INITIALIZER:
tomoyo_del_domain_initializer(p->element);
break;
case TOMOYO_ID_DOMAIN_KEEPER:
tomoyo_del_domain_keeper(p->element);
break;
case TOMOYO_ID_ALIAS:
tomoyo_del_alias(p->element);
break;
case TOMOYO_ID_GLOBALLY_READABLE:
tomoyo_del_allow_read(p->element);
break;
case TOMOYO_ID_PATTERN:
tomoyo_del_file_pattern(p->element);
break;
case TOMOYO_ID_NO_REWRITE:
tomoyo_del_no_rewrite(p->element);
break;
case TOMOYO_ID_MANAGER:
tomoyo_del_manager(p->element);
break;
case TOMOYO_ID_NAME:
tomoyo_del_name(p->element);
break;
case TOMOYO_ID_ACL:
tomoyo_del_acl(p->element);
break;
case TOMOYO_ID_DOMAIN:
if (!tomoyo_del_domain(p->element))
continue;
break;
default:
printk(KERN_WARNING "Unknown type\n");
break;
}
tomoyo_memory_free(p->element);
list_del(&p->list);
kfree(p);
}
}
static int tomoyo_gc_thread(void *unused)
{
daemonize("GC for TOMOYO");
if (mutex_trylock(&tomoyo_gc_mutex)) {
int i;
for (i = 0; i < 10; i++) {
tomoyo_collect_entry();
if (list_empty(&tomoyo_gc_queue))
break;
synchronize_srcu(&tomoyo_ss);
tomoyo_kfree_entry();
}
mutex_unlock(&tomoyo_gc_mutex);
}
do_exit(0);
}
void tomoyo_run_gc(void)
{
struct task_struct *task = kthread_create(tomoyo_gc_thread, NULL,
"GC for TOMOYO");
if (!IS_ERR(task))
wake_up_process(task);
}
...@@ -205,9 +205,9 @@ char *tomoyo_realpath_nofollow(const char *pathname) ...@@ -205,9 +205,9 @@ char *tomoyo_realpath_nofollow(const char *pathname)
} }
/* Memory allocated for non-string data. */ /* Memory allocated for non-string data. */
static unsigned int tomoyo_allocated_memory_for_elements; static atomic_t tomoyo_policy_memory_size;
/* Quota for holding non-string data. */ /* Quota for holding policy. */
static unsigned int tomoyo_quota_for_elements; static unsigned int tomoyo_quota_for_policy;
/** /**
* tomoyo_memory_ok - Check memory quota. * tomoyo_memory_ok - Check memory quota.
...@@ -222,26 +222,30 @@ static unsigned int tomoyo_quota_for_elements; ...@@ -222,26 +222,30 @@ static unsigned int tomoyo_quota_for_elements;
bool tomoyo_memory_ok(void *ptr) bool tomoyo_memory_ok(void *ptr)
{ {
int allocated_len = ptr ? ksize(ptr) : 0; int allocated_len = ptr ? ksize(ptr) : 0;
bool result = false; atomic_add(allocated_len, &tomoyo_policy_memory_size);
if (!ptr || (tomoyo_quota_for_elements && if (ptr && (!tomoyo_quota_for_policy ||
tomoyo_allocated_memory_for_elements atomic_read(&tomoyo_policy_memory_size)
+ allocated_len > tomoyo_quota_for_elements)) { <= tomoyo_quota_for_policy)) {
printk(KERN_WARNING "ERROR: Out of memory "
"for tomoyo_alloc_element().\n");
if (!tomoyo_policy_loaded)
panic("MAC Initialization failed.\n");
} else {
result = true;
tomoyo_allocated_memory_for_elements += allocated_len;
memset(ptr, 0, allocated_len); memset(ptr, 0, allocated_len);
return true;
} }
return result; printk(KERN_WARNING "ERROR: Out of memory "
"for tomoyo_alloc_element().\n");
if (!tomoyo_policy_loaded)
panic("MAC Initialization failed.\n");
return false;
} }
/* Memory allocated for string data in bytes. */ /**
static unsigned int tomoyo_allocated_memory_for_savename; * tomoyo_memory_free - Free memory for elements.
/* Quota for holding string data in bytes. */ *
static unsigned int tomoyo_quota_for_savename; * @ptr: Pointer to allocated memory.
*/
void tomoyo_memory_free(void *ptr)
{
atomic_sub(ksize(ptr), &tomoyo_policy_memory_size);
kfree(ptr);
}
/* /*
* tomoyo_name_list is used for holding string data used by TOMOYO. * tomoyo_name_list is used for holding string data used by TOMOYO.
...@@ -249,7 +253,9 @@ static unsigned int tomoyo_quota_for_savename; ...@@ -249,7 +253,9 @@ static unsigned int tomoyo_quota_for_savename;
* "/lib/libc-2.5.so"), TOMOYO shares string data in the form of * "/lib/libc-2.5.so"), TOMOYO shares string data in the form of
* "const struct tomoyo_path_info *". * "const struct tomoyo_path_info *".
*/ */
static struct list_head tomoyo_name_list[TOMOYO_MAX_HASH]; struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
/* Lock for protecting tomoyo_name_list . */
DEFINE_MUTEX(tomoyo_name_list_lock);
/** /**
* tomoyo_get_name - Allocate permanent memory for string data. * tomoyo_get_name - Allocate permanent memory for string data.
...@@ -260,7 +266,6 @@ static struct list_head tomoyo_name_list[TOMOYO_MAX_HASH]; ...@@ -260,7 +266,6 @@ static struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
*/ */
const struct tomoyo_path_info *tomoyo_get_name(const char *name) const struct tomoyo_path_info *tomoyo_get_name(const char *name)
{ {
static DEFINE_MUTEX(lock);
struct tomoyo_name_entry *ptr; struct tomoyo_name_entry *ptr;
unsigned int hash; unsigned int hash;
int len; int len;
...@@ -272,7 +277,7 @@ const struct tomoyo_path_info *tomoyo_get_name(const char *name) ...@@ -272,7 +277,7 @@ const struct tomoyo_path_info *tomoyo_get_name(const char *name)
len = strlen(name) + 1; len = strlen(name) + 1;
hash = full_name_hash((const unsigned char *) name, len - 1); hash = full_name_hash((const unsigned char *) name, len - 1);
head = &tomoyo_name_list[hash_long(hash, TOMOYO_HASH_BITS)]; head = &tomoyo_name_list[hash_long(hash, TOMOYO_HASH_BITS)];
mutex_lock(&lock); mutex_lock(&tomoyo_name_list_lock);