diff options
Diffstat (limited to 'security/tomoyo/common.c')
-rw-r--r-- | security/tomoyo/common.c | 2081 |
1 files changed, 2081 insertions, 0 deletions
diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c new file mode 100644 index 00000000..a0d09e56 --- /dev/null +++ b/security/tomoyo/common.c @@ -0,0 +1,2081 @@ +/* + * security/tomoyo/common.c + * + * Common functions for TOMOYO. + * + * Copyright (C) 2005-2010 NTT DATA CORPORATION + */ + +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/security.h> +#include "common.h" + +static struct tomoyo_profile tomoyo_default_profile = { + .learning = &tomoyo_default_profile.preference, + .permissive = &tomoyo_default_profile.preference, + .enforcing = &tomoyo_default_profile.preference, + .preference.enforcing_verbose = true, + .preference.learning_max_entry = 2048, + .preference.learning_verbose = false, + .preference.permissive_verbose = true +}; + +/* Profile version. Currently only 20090903 is defined. */ +static unsigned int tomoyo_profile_version; + +/* Profile table. Memory is allocated as needed. */ +static struct tomoyo_profile *tomoyo_profile_ptr[TOMOYO_MAX_PROFILES]; + +/* String table for functionality that takes 4 modes. */ +static const char *tomoyo_mode[4] = { + "disabled", "learning", "permissive", "enforcing" +}; + +/* String table for /sys/kernel/security/tomoyo/profile */ +static const char *tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX] = { + [TOMOYO_MAC_FILE_EXECUTE] = "file::execute", + [TOMOYO_MAC_FILE_OPEN] = "file::open", + [TOMOYO_MAC_FILE_CREATE] = "file::create", + [TOMOYO_MAC_FILE_UNLINK] = "file::unlink", + [TOMOYO_MAC_FILE_MKDIR] = "file::mkdir", + [TOMOYO_MAC_FILE_RMDIR] = "file::rmdir", + [TOMOYO_MAC_FILE_MKFIFO] = "file::mkfifo", + [TOMOYO_MAC_FILE_MKSOCK] = "file::mksock", + [TOMOYO_MAC_FILE_TRUNCATE] = "file::truncate", + [TOMOYO_MAC_FILE_SYMLINK] = "file::symlink", + [TOMOYO_MAC_FILE_REWRITE] = "file::rewrite", + [TOMOYO_MAC_FILE_MKBLOCK] = "file::mkblock", + [TOMOYO_MAC_FILE_MKCHAR] = "file::mkchar", + [TOMOYO_MAC_FILE_LINK] = "file::link", + [TOMOYO_MAC_FILE_RENAME] = "file::rename", + [TOMOYO_MAC_FILE_CHMOD] = "file::chmod", + [TOMOYO_MAC_FILE_CHOWN] = "file::chown", + [TOMOYO_MAC_FILE_CHGRP] = "file::chgrp", + [TOMOYO_MAC_FILE_IOCTL] = "file::ioctl", + [TOMOYO_MAC_FILE_CHROOT] = "file::chroot", + [TOMOYO_MAC_FILE_MOUNT] = "file::mount", + [TOMOYO_MAC_FILE_UMOUNT] = "file::umount", + [TOMOYO_MAC_FILE_PIVOT_ROOT] = "file::pivot_root", + [TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_FILE] = "file", +}; + +/* Permit policy management by non-root user? */ +static bool tomoyo_manage_by_non_root; + +/* Utility functions. */ + +/** + * tomoyo_yesno - Return "yes" or "no". + * + * @value: Bool value. + */ +static const char *tomoyo_yesno(const unsigned int value) +{ + return value ? "yes" : "no"; +} + +static void tomoyo_addprintf(char *buffer, int len, const char *fmt, ...) +{ + va_list args; + const int pos = strlen(buffer); + va_start(args, fmt); + vsnprintf(buffer + pos, len - pos - 1, fmt, args); + va_end(args); +} + +/** + * tomoyo_flush - Flush queued string to userspace's buffer. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns true if all data was flushed, false otherwise. + */ +static bool tomoyo_flush(struct tomoyo_io_buffer *head) +{ + while (head->r.w_pos) { + const char *w = head->r.w[0]; + int len = strlen(w); + if (len) { + if (len > head->read_user_buf_avail) + len = head->read_user_buf_avail; + if (!len) + return false; + if (copy_to_user(head->read_user_buf, w, len)) + return false; + head->read_user_buf_avail -= len; + head->read_user_buf += len; + w += len; + } + head->r.w[0] = w; + if (*w) + return false; + /* Add '\0' for query. */ + if (head->poll) { + if (!head->read_user_buf_avail || + copy_to_user(head->read_user_buf, "", 1)) + return false; + head->read_user_buf_avail--; + head->read_user_buf++; + } + head->r.w_pos--; + for (len = 0; len < head->r.w_pos; len++) + head->r.w[len] = head->r.w[len + 1]; + } + head->r.avail = 0; + return true; +} + +/** + * tomoyo_set_string - Queue string to "struct tomoyo_io_buffer" structure. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @string: String to print. + * + * Note that @string has to be kept valid until @head is kfree()d. + * This means that char[] allocated on stack memory cannot be passed to + * this function. Use tomoyo_io_printf() for char[] allocated on stack memory. + */ +static void tomoyo_set_string(struct tomoyo_io_buffer *head, const char *string) +{ + if (head->r.w_pos < TOMOYO_MAX_IO_READ_QUEUE) { + head->r.w[head->r.w_pos++] = string; + tomoyo_flush(head); + } else + WARN_ON(1); +} + +/** + * tomoyo_io_printf - printf() to "struct tomoyo_io_buffer" structure. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @fmt: The printf()'s format string, followed by parameters. + */ +void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) +{ + va_list args; + int len; + int pos = head->r.avail; + int size = head->readbuf_size - pos; + if (size <= 0) + return; + va_start(args, fmt); + len = vsnprintf(head->read_buf + pos, size, fmt, args) + 1; + va_end(args); + if (pos + len >= head->readbuf_size) { + WARN_ON(1); + return; + } + head->r.avail += len; + tomoyo_set_string(head, head->read_buf + pos); +} + +static void tomoyo_set_space(struct tomoyo_io_buffer *head) +{ + tomoyo_set_string(head, " "); +} + +static bool tomoyo_set_lf(struct tomoyo_io_buffer *head) +{ + tomoyo_set_string(head, "\n"); + return !head->r.w_pos; +} + +/** + * tomoyo_print_name_union - Print a tomoyo_name_union. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @ptr: Pointer to "struct tomoyo_name_union". + */ +static void tomoyo_print_name_union(struct tomoyo_io_buffer *head, + const struct tomoyo_name_union *ptr) +{ + tomoyo_set_space(head); + if (ptr->is_group) { + tomoyo_set_string(head, "@"); + tomoyo_set_string(head, ptr->group->group_name->name); + } else { + tomoyo_set_string(head, ptr->filename->name); + } +} + +/** + * tomoyo_print_number_union - Print a tomoyo_number_union. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @ptr: Pointer to "struct tomoyo_number_union". + */ +static void tomoyo_print_number_union(struct tomoyo_io_buffer *head, + const struct tomoyo_number_union *ptr) +{ + tomoyo_set_space(head); + if (ptr->is_group) { + tomoyo_set_string(head, "@"); + tomoyo_set_string(head, ptr->group->group_name->name); + } else { + int i; + unsigned long min = ptr->values[0]; + const unsigned long max = ptr->values[1]; + u8 min_type = ptr->min_type; + const u8 max_type = ptr->max_type; + char buffer[128]; + buffer[0] = '\0'; + for (i = 0; i < 2; i++) { + switch (min_type) { + case TOMOYO_VALUE_TYPE_HEXADECIMAL: + tomoyo_addprintf(buffer, sizeof(buffer), + "0x%lX", min); + break; + case TOMOYO_VALUE_TYPE_OCTAL: + tomoyo_addprintf(buffer, sizeof(buffer), + "0%lo", min); + break; + default: + tomoyo_addprintf(buffer, sizeof(buffer), + "%lu", min); + break; + } + if (min == max && min_type == max_type) + break; + tomoyo_addprintf(buffer, sizeof(buffer), "-"); + min_type = max_type; + min = max; + } + tomoyo_io_printf(head, "%s", buffer); + } +} + +/** + * tomoyo_assign_profile - Create a new profile. + * + * @profile: Profile number to create. + * + * Returns pointer to "struct tomoyo_profile" on success, NULL otherwise. + */ +static struct tomoyo_profile *tomoyo_assign_profile(const unsigned int profile) +{ + struct tomoyo_profile *ptr; + struct tomoyo_profile *entry; + if (profile >= TOMOYO_MAX_PROFILES) + return NULL; + ptr = tomoyo_profile_ptr[profile]; + if (ptr) + return ptr; + entry = kzalloc(sizeof(*entry), GFP_NOFS); + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + goto out; + ptr = tomoyo_profile_ptr[profile]; + if (!ptr && tomoyo_memory_ok(entry)) { + ptr = entry; + ptr->learning = &tomoyo_default_profile.preference; + ptr->permissive = &tomoyo_default_profile.preference; + ptr->enforcing = &tomoyo_default_profile.preference; + ptr->default_config = TOMOYO_CONFIG_DISABLED; + memset(ptr->config, TOMOYO_CONFIG_USE_DEFAULT, + sizeof(ptr->config)); + mb(); /* Avoid out-of-order execution. */ + tomoyo_profile_ptr[profile] = ptr; + entry = NULL; + } + mutex_unlock(&tomoyo_policy_lock); + out: + kfree(entry); + return ptr; +} + +/** + * tomoyo_profile - Find a profile. + * + * @profile: Profile number to find. + * + * Returns pointer to "struct tomoyo_profile". + */ +struct tomoyo_profile *tomoyo_profile(const u8 profile) +{ + struct tomoyo_profile *ptr = tomoyo_profile_ptr[profile]; + if (!tomoyo_policy_loaded) + return &tomoyo_default_profile; + BUG_ON(!ptr); + return ptr; +} + +static s8 tomoyo_find_yesno(const char *string, const char *find) +{ + const char *cp = strstr(string, find); + if (cp) { + cp += strlen(find); + if (!strncmp(cp, "=yes", 4)) + return 1; + else if (!strncmp(cp, "=no", 3)) + return 0; + } + return -1; +} + +static void tomoyo_set_bool(bool *b, const char *string, const char *find) +{ + switch (tomoyo_find_yesno(string, find)) { + case 1: + *b = true; + break; + case 0: + *b = false; + break; + } +} + +static void tomoyo_set_uint(unsigned int *i, const char *string, + const char *find) +{ + const char *cp = strstr(string, find); + if (cp) + sscanf(cp + strlen(find), "=%u", i); +} + +static void tomoyo_set_pref(const char *name, const char *value, + const bool use_default, + struct tomoyo_profile *profile) +{ + struct tomoyo_preference **pref; + bool *verbose; + if (!strcmp(name, "enforcing")) { + if (use_default) { + pref = &profile->enforcing; + goto set_default; + } + profile->enforcing = &profile->preference; + verbose = &profile->preference.enforcing_verbose; + goto set_verbose; + } + if (!strcmp(name, "permissive")) { + if (use_default) { + pref = &profile->permissive; + goto set_default; + } + profile->permissive = &profile->preference; + verbose = &profile->preference.permissive_verbose; + goto set_verbose; + } + if (!strcmp(name, "learning")) { + if (use_default) { + pref = &profile->learning; + goto set_default; + } + profile->learning = &profile->preference; + tomoyo_set_uint(&profile->preference.learning_max_entry, value, + "max_entry"); + verbose = &profile->preference.learning_verbose; + goto set_verbose; + } + return; + set_default: + *pref = &tomoyo_default_profile.preference; + return; + set_verbose: + tomoyo_set_bool(verbose, value, "verbose"); +} + +static int tomoyo_set_mode(char *name, const char *value, + const bool use_default, + struct tomoyo_profile *profile) +{ + u8 i; + u8 config; + if (!strcmp(name, "CONFIG")) { + i = TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX; + config = profile->default_config; + } else if (tomoyo_str_starts(&name, "CONFIG::")) { + config = 0; + for (i = 0; i < TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX; i++) { + if (strcmp(name, tomoyo_mac_keywords[i])) + continue; + config = profile->config[i]; + break; + } + if (i == TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX) + return -EINVAL; + } else { + return -EINVAL; + } + if (use_default) { + config = TOMOYO_CONFIG_USE_DEFAULT; + } else { + u8 mode; + for (mode = 0; mode < 4; mode++) + if (strstr(value, tomoyo_mode[mode])) + /* + * Update lower 3 bits in order to distinguish + * 'config' from 'TOMOYO_CONFIG_USE_DEAFULT'. + */ + config = (config & ~7) | mode; + } + if (i < TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX) + profile->config[i] = config; + else if (config != TOMOYO_CONFIG_USE_DEFAULT) + profile->default_config = config; + return 0; +} + +/** + * tomoyo_write_profile - Write profile table. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_write_profile(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + unsigned int i; + bool use_default = false; + char *cp; + struct tomoyo_profile *profile; + if (sscanf(data, "PROFILE_VERSION=%u", &tomoyo_profile_version) == 1) + return 0; + i = simple_strtoul(data, &cp, 10); + if (data == cp) { + profile = &tomoyo_default_profile; + } else { + if (*cp != '-') + return -EINVAL; + data = cp + 1; + profile = tomoyo_assign_profile(i); + if (!profile) + return -EINVAL; + } + cp = strchr(data, '='); + if (!cp) + return -EINVAL; + *cp++ = '\0'; + if (profile != &tomoyo_default_profile) + use_default = strstr(cp, "use_default") != NULL; + if (tomoyo_str_starts(&data, "PREFERENCE::")) { + tomoyo_set_pref(data, cp, use_default, profile); + return 0; + } + if (profile == &tomoyo_default_profile) + return -EINVAL; + if (!strcmp(data, "COMMENT")) { + static DEFINE_SPINLOCK(lock); + const struct tomoyo_path_info *new_comment + = tomoyo_get_name(cp); + const struct tomoyo_path_info *old_comment; + if (!new_comment) + return -ENOMEM; + spin_lock(&lock); + old_comment = profile->comment; + profile->comment = new_comment; + spin_unlock(&lock); + tomoyo_put_name(old_comment); + return 0; + } + return tomoyo_set_mode(data, cp, use_default, profile); +} + +static void tomoyo_print_preference(struct tomoyo_io_buffer *head, + const int idx) +{ + struct tomoyo_preference *pref = &tomoyo_default_profile.preference; + const struct tomoyo_profile *profile = idx >= 0 ? + tomoyo_profile_ptr[idx] : NULL; + char buffer[16] = ""; + if (profile) { + buffer[sizeof(buffer) - 1] = '\0'; + snprintf(buffer, sizeof(buffer) - 1, "%u-", idx); + } + if (profile) { + pref = profile->learning; + if (pref == &tomoyo_default_profile.preference) + goto skip1; + } + tomoyo_io_printf(head, "%sPREFERENCE::%s={ " + "verbose=%s max_entry=%u }\n", + buffer, "learning", + tomoyo_yesno(pref->learning_verbose), + pref->learning_max_entry); + skip1: + if (profile) { + pref = profile->permissive; + if (pref == &tomoyo_default_profile.preference) + goto skip2; + } + tomoyo_io_printf(head, "%sPREFERENCE::%s={ verbose=%s }\n", + buffer, "permissive", + tomoyo_yesno(pref->permissive_verbose)); + skip2: + if (profile) { + pref = profile->enforcing; + if (pref == &tomoyo_default_profile.preference) + return; + } + tomoyo_io_printf(head, "%sPREFERENCE::%s={ verbose=%s }\n", + buffer, "enforcing", + tomoyo_yesno(pref->enforcing_verbose)); +} + +static void tomoyo_print_config(struct tomoyo_io_buffer *head, const u8 config) +{ + tomoyo_io_printf(head, "={ mode=%s }\n", tomoyo_mode[config & 3]); +} + +/** + * tomoyo_read_profile - Read profile table. + * + * @head: Pointer to "struct tomoyo_io_buffer". + */ +static void tomoyo_read_profile(struct tomoyo_io_buffer *head) +{ + u8 index; + const struct tomoyo_profile *profile; + next: + index = head->r.index; + profile = tomoyo_profile_ptr[index]; + switch (head->r.step) { + case 0: + tomoyo_io_printf(head, "PROFILE_VERSION=%s\n", "20090903"); + tomoyo_print_preference(head, -1); + head->r.step++; + break; + case 1: + for ( ; head->r.index < TOMOYO_MAX_PROFILES; + head->r.index++) + if (tomoyo_profile_ptr[head->r.index]) + break; + if (head->r.index == TOMOYO_MAX_PROFILES) + return; + head->r.step++; + break; + case 2: + { + const struct tomoyo_path_info *comment = + profile->comment; + tomoyo_io_printf(head, "%u-COMMENT=", index); + tomoyo_set_string(head, comment ? comment->name : ""); + tomoyo_set_lf(head); + head->r.step++; + } + break; + case 3: + { + tomoyo_io_printf(head, "%u-%s", index, "CONFIG"); + tomoyo_print_config(head, profile->default_config); + head->r.bit = 0; + head->r.step++; + } + break; + case 4: + for ( ; head->r.bit < TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX; head->r.bit++) { + const u8 i = head->r.bit; + const u8 config = profile->config[i]; + if (config == TOMOYO_CONFIG_USE_DEFAULT) + continue; + tomoyo_io_printf(head, "%u-%s%s", index, "CONFIG::", + tomoyo_mac_keywords[i]); + tomoyo_print_config(head, config); + head->r.bit++; + break; + } + if (head->r.bit == TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX) { + tomoyo_print_preference(head, index); + head->r.index++; + head->r.step = 1; + } + break; + } + if (tomoyo_flush(head)) + goto next; +} + +static bool tomoyo_same_manager(const struct tomoyo_acl_head *a, + const struct tomoyo_acl_head *b) +{ + return container_of(a, struct tomoyo_manager, head)->manager == + container_of(b, struct tomoyo_manager, head)->manager; +} + +/** + * tomoyo_update_manager_entry - Add a manager entry. + * + * @manager: The path to manager or the domainnamme. + * @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_manager_entry(const char *manager, + const bool is_delete) +{ + struct tomoyo_manager e = { }; + int error; + + if (tomoyo_domain_def(manager)) { + if (!tomoyo_correct_domain(manager)) + return -EINVAL; + e.is_domain = true; + } else { + if (!tomoyo_correct_path(manager)) + return -EINVAL; + } + e.manager = tomoyo_get_name(manager); + if (!e.manager) + return -ENOMEM; + error = tomoyo_update_policy(&e.head, sizeof(e), is_delete, + &tomoyo_policy_list[TOMOYO_ID_MANAGER], + tomoyo_same_manager); + tomoyo_put_name(e.manager); + return error; +} + +/** + * tomoyo_write_manager - Write manager policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_manager(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE); + + if (!strcmp(data, "manage_by_non_root")) { + tomoyo_manage_by_non_root = !is_delete; + return 0; + } + return tomoyo_update_manager_entry(data, is_delete); +} + +/** + * tomoyo_read_manager - Read manager policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_manager(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.acl, + &tomoyo_policy_list[TOMOYO_ID_MANAGER]) { + struct tomoyo_manager *ptr = + list_entry(head->r.acl, typeof(*ptr), head.list); + if (ptr->head.is_deleted) + continue; + if (!tomoyo_flush(head)) + return; + tomoyo_set_string(head, ptr->manager->name); + tomoyo_set_lf(head); + } + head->r.eof = true; +} + +/** + * tomoyo_manager - Check whether the current process is a policy manager. + * + * Returns true if the current process is permitted to modify policy + * via /sys/kernel/security/tomoyo/ interface. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_manager(void) +{ + struct tomoyo_manager *ptr; + const char *exe; + const struct task_struct *task = current; + const struct tomoyo_path_info *domainname = tomoyo_domain()->domainname; + bool found = false; + + if (!tomoyo_policy_loaded) + return true; + if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid)) + return false; + list_for_each_entry_rcu(ptr, &tomoyo_policy_list[TOMOYO_ID_MANAGER], + head.list) { + if (!ptr->head.is_deleted && ptr->is_domain + && !tomoyo_pathcmp(domainname, ptr->manager)) { + found = true; + break; + } + } + if (found) + return true; + exe = tomoyo_get_exe(); + if (!exe) + return false; + list_for_each_entry_rcu(ptr, &tomoyo_policy_list[TOMOYO_ID_MANAGER], + head.list) { + if (!ptr->head.is_deleted && !ptr->is_domain + && !strcmp(exe, ptr->manager->name)) { + found = true; + break; + } + } + if (!found) { /* Reduce error messages. */ + static pid_t last_pid; + const pid_t pid = current->pid; + if (last_pid != pid) { + printk(KERN_WARNING "%s ( %s ) is not permitted to " + "update policies.\n", domainname->name, exe); + last_pid = pid; + } + } + kfree(exe); + return found; +} + +/** + * tomoyo_select_one - Parse select command. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @data: String to parse. + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_select_one(struct tomoyo_io_buffer *head, const char *data) +{ + unsigned int pid; + struct tomoyo_domain_info *domain = NULL; + bool global_pid = false; + + if (!strcmp(data, "allow_execute")) { + head->r.print_execute_only = true; + return true; + } + if (sscanf(data, "pid=%u", &pid) == 1 || + (global_pid = true, sscanf(data, "global-pid=%u", &pid) == 1)) { + struct task_struct *p; + rcu_read_lock(); + read_lock(&tasklist_lock); + if (global_pid) + p = find_task_by_pid_ns(pid, &init_pid_ns); + else + p = find_task_by_vpid(pid); + if (p) + domain = tomoyo_real_domain(p); + read_unlock(&tasklist_lock); + rcu_read_unlock(); + } else if (!strncmp(data, "domain=", 7)) { + if (tomoyo_domain_def(data + 7)) + domain = tomoyo_find_domain(data + 7); + } else + return false; + head->write_var1 = domain; + /* Accessing read_buf is safe because head->io_sem is held. */ + if (!head->read_buf) + return true; /* Do nothing if open(O_WRONLY). */ + memset(&head->r, 0, sizeof(head->r)); + head->r.print_this_domain_only = true; + if (domain) + head->r.domain = &domain->list; + else + head->r.eof = 1; + tomoyo_io_printf(head, "# select %s\n", data); + if (domain && domain->is_deleted) + tomoyo_io_printf(head, "# This is a deleted domain.\n"); + return true; +} + +/** + * tomoyo_delete_domain - Delete a domain. + * + * @domainname: The name of domain. + * + * Returns 0. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_delete_domain(char *domainname) +{ + struct tomoyo_domain_info *domain; + struct tomoyo_path_info name; + + name.name = domainname; + tomoyo_fill_path_info(&name); + if (mutex_lock_interruptible(&tomoyo_policy_lock)) + return 0; + /* Is there an active domain? */ + list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { + /* Never delete tomoyo_kernel_domain */ + if (domain == &tomoyo_kernel_domain) + continue; + if (domain->is_deleted || + tomoyo_pathcmp(domain->domainname, &name)) + continue; + domain->is_deleted = true; + break; + } + mutex_unlock(&tomoyo_policy_lock); + return 0; +} + +/** + * tomoyo_write_domain2 - Write domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_domain2(char *data, struct tomoyo_domain_info *domain, + const bool is_delete) +{ + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALLOW_MOUNT)) + return tomoyo_write_mount(data, domain, is_delete); + return tomoyo_write_file(data, domain, is_delete); +} + +/** + * tomoyo_write_domain - Write domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_domain(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + struct tomoyo_domain_info *domain = head->write_var1; + bool is_delete = false; + bool is_select = false; + unsigned int profile; + + if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE)) + is_delete = true; + else if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_SELECT)) + is_select = true; + if (is_select && tomoyo_select_one(head, data)) + return 0; + /* Don't allow updating policies by non manager programs. */ + if (!tomoyo_manager()) + return -EPERM; + if (tomoyo_domain_def(data)) { + domain = NULL; + if (is_delete) + tomoyo_delete_domain(data); + else if (is_select) + domain = tomoyo_find_domain(data); + else + domain = tomoyo_assign_domain(data, 0); + head->write_var1 = domain; + return 0; + } + if (!domain) + return -EINVAL; + + if (sscanf(data, TOMOYO_KEYWORD_USE_PROFILE "%u", &profile) == 1 + && profile < TOMOYO_MAX_PROFILES) { + if (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded) + domain->profile = (u8) profile; + return 0; + } + if (!strcmp(data, TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ)) { + domain->ignore_global_allow_read = !is_delete; + return 0; + } + if (!strcmp(data, TOMOYO_KEYWORD_QUOTA_EXCEEDED)) { + domain->quota_warned = !is_delete; + return 0; + } + if (!strcmp(data, TOMOYO_KEYWORD_TRANSITION_FAILED)) { + domain->transition_failed = !is_delete; + return 0; + } + return tomoyo_write_domain2(data, domain, is_delete); +} + +/** + * tomoyo_fns - Find next set bit. + * + * @perm: 8 bits value. + * @bit: First bit to find. + * + * Returns next on-bit on success, 8 otherwise. + */ +static u8 tomoyo_fns(const u8 perm, u8 bit) +{ + for ( ; bit < 8; bit++) + if (perm & (1 << bit)) + break; + return bit; +} + +/** + * tomoyo_print_entry - Print an ACL entry. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @acl: Pointer to an ACL entry. + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_print_entry(struct tomoyo_io_buffer *head, + struct tomoyo_acl_info *acl) +{ + const u8 acl_type = acl->type; + u8 bit; + + if (acl->is_deleted) + return true; + next: + bit = head->r.bit; + if (!tomoyo_flush(head)) + return false; + else if (acl_type == TOMOYO_TYPE_PATH_ACL) { + struct tomoyo_path_acl *ptr = + container_of(acl, typeof(*ptr), head); + const u16 perm = ptr->perm; + for ( ; bit < TOMOYO_MAX_PATH_OPERATION; bit++) { + if (!(perm & (1 << bit))) + continue; + if (head->r.print_execute_only && + bit != TOMOYO_TYPE_EXECUTE) + continue; + /* Print "read/write" instead of "read" and "write". */ + if ((bit == TOMOYO_TYPE_READ || + bit == TOMOYO_TYPE_WRITE) + && (perm & (1 << TOMOYO_TYPE_READ_WRITE))) + continue; + break; + } + if (bit >= TOMOYO_MAX_PATH_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", tomoyo_path_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name); + } else if (head->r.print_execute_only) { + return true; + } else if (acl_type == TOMOYO_TYPE_PATH2_ACL) { + struct tomoyo_path2_acl *ptr = + container_of(acl, typeof(*ptr), head); + bit = tomoyo_fns(ptr->perm, bit); + if (bit >= TOMOYO_MAX_PATH2_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", tomoyo_path2_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name1); + tomoyo_print_name_union(head, &ptr->name2); + } else if (acl_type == TOMOYO_TYPE_PATH_NUMBER_ACL) { + struct tomoyo_path_number_acl *ptr = + container_of(acl, typeof(*ptr), head); + bit = tomoyo_fns(ptr->perm, bit); + if (bit >= TOMOYO_MAX_PATH_NUMBER_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", + tomoyo_path_number_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name); + tomoyo_print_number_union(head, &ptr->number); + } else if (acl_type == TOMOYO_TYPE_MKDEV_ACL) { + struct tomoyo_mkdev_acl *ptr = + container_of(acl, typeof(*ptr), head); + bit = tomoyo_fns(ptr->perm, bit); + if (bit >= TOMOYO_MAX_MKDEV_OPERATION) + goto done; + tomoyo_io_printf(head, "allow_%s", tomoyo_mkdev_keyword[bit]); + tomoyo_print_name_union(head, &ptr->name); + tomoyo_print_number_union(head, &ptr->mode); + tomoyo_print_number_union(head, &ptr->major); + tomoyo_print_number_union(head, &ptr->minor); + } else if (acl_type == TOMOYO_TYPE_MOUNT_ACL) { + struct tomoyo_mount_acl *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_io_printf(head, "allow_mount"); + tomoyo_print_name_union(head, &ptr->dev_name); + tomoyo_print_name_union(head, &ptr->dir_name); + tomoyo_print_name_union(head, &ptr->fs_type); + tomoyo_print_number_union(head, &ptr->flags); + } + head->r.bit = bit + 1; + tomoyo_io_printf(head, "\n"); + if (acl_type != TOMOYO_TYPE_MOUNT_ACL) + goto next; + done: + head->r.bit = 0; + return true; +} + +/** + * tomoyo_read_domain2 - Read domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @domain: Pointer to "struct tomoyo_domain_info". + * + * Caller holds tomoyo_read_lock(). + * + * Returns true on success, false otherwise. + */ +static bool tomoyo_read_domain2(struct tomoyo_io_buffer *head, + struct tomoyo_domain_info *domain) +{ + list_for_each_cookie(head->r.acl, &domain->acl_info_list) { + struct tomoyo_acl_info *ptr = + list_entry(head->r.acl, typeof(*ptr), list); + if (!tomoyo_print_entry(head, ptr)) + return false; + } + head->r.acl = NULL; + return true; +} + +/** + * tomoyo_read_domain - Read domain policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_domain(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.domain, &tomoyo_domain_list) { + struct tomoyo_domain_info *domain = + list_entry(head->r.domain, typeof(*domain), list); + switch (head->r.step) { + case 0: + if (domain->is_deleted && + !head->r.print_this_domain_only) + continue; + /* Print domainname and flags. */ + tomoyo_set_string(head, domain->domainname->name); + tomoyo_set_lf(head); + tomoyo_io_printf(head, + TOMOYO_KEYWORD_USE_PROFILE "%u\n", + domain->profile); + if (domain->quota_warned) + tomoyo_set_string(head, "quota_exceeded\n"); + if (domain->transition_failed) + tomoyo_set_string(head, "transition_failed\n"); + if (domain->ignore_global_allow_read) + tomoyo_set_string(head, + TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ + "\n"); + head->r.step++; + tomoyo_set_lf(head); + /* fall through */ + case 1: + if (!tomoyo_read_domain2(head, domain)) + return; + head->r.step++; + if (!tomoyo_set_lf(head)) + return; + /* fall through */ + case 2: + head->r.step = 0; + if (head->r.print_this_domain_only) + goto done; + } + } + done: + head->r.eof = true; +} + +/** + * tomoyo_write_domain_profile - Assign profile for specified domain. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, -EINVAL otherwise. + * + * This is equivalent to doing + * + * ( echo "select " $domainname; echo "use_profile " $profile ) | + * /usr/sbin/tomoyo-loadpolicy -d + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_domain_profile(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + char *cp = strchr(data, ' '); + struct tomoyo_domain_info *domain; + unsigned long profile; + + if (!cp) + return -EINVAL; + *cp = '\0'; + domain = tomoyo_find_domain(cp + 1); + if (strict_strtoul(data, 10, &profile)) + return -EINVAL; + if (domain && profile < TOMOYO_MAX_PROFILES + && (tomoyo_profile_ptr[profile] || !tomoyo_policy_loaded)) + domain->profile = (u8) profile; + return 0; +} + +/** + * tomoyo_read_domain_profile - Read only domainname and profile. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns list of profile number and domainname pairs. + * + * This is equivalent to doing + * + * grep -A 1 '^<kernel>' /sys/kernel/security/tomoyo/domain_policy | + * awk ' { if ( domainname == "" ) { if ( $1 == "<kernel>" ) + * domainname = $0; } else if ( $1 == "use_profile" ) { + * print $2 " " domainname; domainname = ""; } } ; ' + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_domain_profile(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + list_for_each_cookie(head->r.domain, &tomoyo_domain_list) { + struct tomoyo_domain_info *domain = + list_entry(head->r.domain, typeof(*domain), list); + if (domain->is_deleted) + continue; + if (!tomoyo_flush(head)) + return; + tomoyo_io_printf(head, "%u ", domain->profile); + tomoyo_set_string(head, domain->domainname->name); + tomoyo_set_lf(head); + } + head->r.eof = true; +} + +/** + * tomoyo_write_pid: Specify PID to obtain domainname. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0. + */ +static int tomoyo_write_pid(struct tomoyo_io_buffer *head) +{ + head->r.eof = false; + return 0; +} + +/** + * tomoyo_read_pid - Get domainname of the specified PID. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns the domainname which the specified PID is in on success, + * empty string otherwise. + * The PID is specified by tomoyo_write_pid() so that the user can obtain + * using read()/write() interface rather than sysctl() interface. + */ +static void tomoyo_read_pid(struct tomoyo_io_buffer *head) +{ + char *buf = head->write_buf; + bool global_pid = false; + unsigned int pid; + struct task_struct *p; + struct tomoyo_domain_info *domain = NULL; + + /* Accessing write_buf is safe because head->io_sem is held. */ + if (!buf) { + head->r.eof = true; + return; /* Do nothing if open(O_RDONLY). */ + } + if (head->r.w_pos || head->r.eof) + return; + head->r.eof = true; + if (tomoyo_str_starts(&buf, "global-pid ")) + global_pid = true; + pid = (unsigned int) simple_strtoul(buf, NULL, 10); + rcu_read_lock(); + read_lock(&tasklist_lock); + if (global_pid) + p = find_task_by_pid_ns(pid, &init_pid_ns); + else + p = find_task_by_vpid(pid); + if (p) + domain = tomoyo_real_domain(p); + read_unlock(&tasklist_lock); + rcu_read_unlock(); + if (!domain) + return; + tomoyo_io_printf(head, "%u %u ", pid, domain->profile); + tomoyo_set_string(head, domain->domainname->name); +} + +static const char *tomoyo_transition_type[TOMOYO_MAX_TRANSITION_TYPE] = { + [TOMOYO_TRANSITION_CONTROL_NO_INITIALIZE] + = TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN, + [TOMOYO_TRANSITION_CONTROL_INITIALIZE] + = TOMOYO_KEYWORD_INITIALIZE_DOMAIN, + [TOMOYO_TRANSITION_CONTROL_NO_KEEP] = TOMOYO_KEYWORD_NO_KEEP_DOMAIN, + [TOMOYO_TRANSITION_CONTROL_KEEP] = TOMOYO_KEYWORD_KEEP_DOMAIN +}; + +static const char *tomoyo_group_name[TOMOYO_MAX_GROUP] = { + [TOMOYO_PATH_GROUP] = TOMOYO_KEYWORD_PATH_GROUP, + [TOMOYO_NUMBER_GROUP] = TOMOYO_KEYWORD_NUMBER_GROUP +}; + +/** + * tomoyo_write_exception - Write exception policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_exception(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE); + u8 i; + static const struct { + const char *keyword; + int (*write) (char *, const bool); + } tomoyo_callback[4] = { + { TOMOYO_KEYWORD_AGGREGATOR, tomoyo_write_aggregator }, + { TOMOYO_KEYWORD_FILE_PATTERN, tomoyo_write_pattern }, + { TOMOYO_KEYWORD_DENY_REWRITE, tomoyo_write_no_rewrite }, + { TOMOYO_KEYWORD_ALLOW_READ, tomoyo_write_globally_readable }, + }; + + for (i = 0; i < TOMOYO_MAX_TRANSITION_TYPE; i++) + if (tomoyo_str_starts(&data, tomoyo_transition_type[i])) + return tomoyo_write_transition_control(data, is_delete, + i); + for (i = 0; i < 4; i++) + if (tomoyo_str_starts(&data, tomoyo_callback[i].keyword)) + return tomoyo_callback[i].write(data, is_delete); + for (i = 0; i < TOMOYO_MAX_GROUP; i++) + if (tomoyo_str_starts(&data, tomoyo_group_name[i])) + return tomoyo_write_group(data, is_delete, i); + return -EINVAL; +} + +/** + * tomoyo_read_group - Read "struct tomoyo_path_group"/"struct tomoyo_number_group" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @idx: Index number. + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx) +{ + list_for_each_cookie(head->r.group, &tomoyo_group_list[idx]) { + struct tomoyo_group *group = + list_entry(head->r.group, typeof(*group), list); + list_for_each_cookie(head->r.acl, &group->member_list) { + struct tomoyo_acl_head *ptr = + list_entry(head->r.acl, typeof(*ptr), list); + if (ptr->is_deleted) + continue; + if (!tomoyo_flush(head)) + return false; + tomoyo_set_string(head, tomoyo_group_name[idx]); + tomoyo_set_string(head, group->group_name->name); + if (idx == TOMOYO_PATH_GROUP) { + tomoyo_set_space(head); + tomoyo_set_string(head, container_of + (ptr, struct tomoyo_path_group, + head)->member_name->name); + } else if (idx == TOMOYO_NUMBER_GROUP) { + tomoyo_print_number_union(head, &container_of + (ptr, + struct tomoyo_number_group, + head)->number); + } + tomoyo_set_lf(head); + } + head->r.acl = NULL; + } + head->r.group = NULL; + return true; +} + +/** + * tomoyo_read_policy - Read "struct tomoyo_..._entry" list. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * @idx: Index number. + * + * Returns true on success, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static bool tomoyo_read_policy(struct tomoyo_io_buffer *head, const int idx) +{ + list_for_each_cookie(head->r.acl, &tomoyo_policy_list[idx]) { + struct tomoyo_acl_head *acl = + container_of(head->r.acl, typeof(*acl), list); + if (acl->is_deleted) + continue; + if (!tomoyo_flush(head)) + return false; + switch (idx) { + case TOMOYO_ID_TRANSITION_CONTROL: + { + struct tomoyo_transition_control *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + tomoyo_transition_type + [ptr->type]); + if (ptr->program) + tomoyo_set_string(head, + ptr->program->name); + if (ptr->program && ptr->domainname) + tomoyo_set_string(head, " from "); + if (ptr->domainname) + tomoyo_set_string(head, + ptr->domainname-> + name); + } + break; + case TOMOYO_ID_GLOBALLY_READABLE: + { + struct tomoyo_readable_file *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_ALLOW_READ); + tomoyo_set_string(head, ptr->filename->name); + } + break; + case TOMOYO_ID_AGGREGATOR: + { + struct tomoyo_aggregator *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_AGGREGATOR); + tomoyo_set_string(head, + ptr->original_name->name); + tomoyo_set_space(head); + tomoyo_set_string(head, + ptr->aggregated_name->name); + } + break; + case TOMOYO_ID_PATTERN: + { + struct tomoyo_no_pattern *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_FILE_PATTERN); + tomoyo_set_string(head, ptr->pattern->name); + } + break; + case TOMOYO_ID_NO_REWRITE: + { + struct tomoyo_no_rewrite *ptr = + container_of(acl, typeof(*ptr), head); + tomoyo_set_string(head, + TOMOYO_KEYWORD_DENY_REWRITE); + tomoyo_set_string(head, ptr->pattern->name); + } + break; + default: + continue; + } + tomoyo_set_lf(head); + } + head->r.acl = NULL; + return true; +} + +/** + * tomoyo_read_exception - Read exception policy. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Caller holds tomoyo_read_lock(). + */ +static void tomoyo_read_exception(struct tomoyo_io_buffer *head) +{ + if (head->r.eof) + return; + while (head->r.step < TOMOYO_MAX_POLICY && + tomoyo_read_policy(head, head->r.step)) + head->r.step++; + if (head->r.step < TOMOYO_MAX_POLICY) + return; + while (head->r.step < TOMOYO_MAX_POLICY + TOMOYO_MAX_GROUP && + tomoyo_read_group(head, head->r.step - TOMOYO_MAX_POLICY)) + head->r.step++; + if (head->r.step < TOMOYO_MAX_POLICY + TOMOYO_MAX_GROUP) + return; + head->r.eof = true; +} + +/** + * tomoyo_print_header - Get header line of audit log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns string representation. + * + * This function uses kmalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static char *tomoyo_print_header(struct tomoyo_request_info *r) +{ + struct timeval tv; + const pid_t gpid = task_pid_nr(current); + static const int tomoyo_buffer_len = 4096; + char *buffer = kmalloc(tomoyo_buffer_len, GFP_NOFS); + pid_t ppid; + if (!buffer) + return NULL; + do_gettimeofday(&tv); + rcu_read_lock(); + ppid = task_tgid_vnr(current->real_parent); + rcu_read_unlock(); + snprintf(buffer, tomoyo_buffer_len - 1, + "#timestamp=%lu profile=%u mode=%s (global-pid=%u)" + " task={ pid=%u ppid=%u uid=%u gid=%u euid=%u" + " egid=%u suid=%u sgid=%u fsuid=%u fsgid=%u }", + tv.tv_sec, r->profile, tomoyo_mode[r->mode], gpid, + task_tgid_vnr(current), ppid, + current_uid(), current_gid(), current_euid(), + current_egid(), current_suid(), current_sgid(), + current_fsuid(), current_fsgid()); + return buffer; +} + +/** + * tomoyo_init_audit_log - Allocate buffer for audit logs. + * + * @len: Required size. + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns pointer to allocated memory. + * + * The @len is updated to add the header lines' size on success. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +static char *tomoyo_init_audit_log(int *len, struct tomoyo_request_info *r) +{ + char *buf = NULL; + const char *header; + const char *domainname; + if (!r->domain) + r->domain = tomoyo_domain(); + domainname = r->domain->domainname->name; + header = tomoyo_print_header(r); + if (!header) + return NULL; + *len += strlen(domainname) + strlen(header) + 10; + buf = kzalloc(*len, GFP_NOFS); + if (buf) + snprintf(buf, (*len) - 1, "%s\n%s\n", header, domainname); + kfree(header); + return buf; +} + +/* Wait queue for tomoyo_query_list. */ +static DECLARE_WAIT_QUEUE_HEAD(tomoyo_query_wait); + +/* Lock for manipulating tomoyo_query_list. */ +static DEFINE_SPINLOCK(tomoyo_query_list_lock); + +/* Structure for query. */ +struct tomoyo_query { + struct list_head list; + char *query; + int query_len; + unsigned int serial; + int timer; + int answer; +}; + +/* The list for "struct tomoyo_query". */ +static LIST_HEAD(tomoyo_query_list); + +/* + * Number of "struct file" referring /sys/kernel/security/tomoyo/query + * interface. + */ +static atomic_t tomoyo_query_observers = ATOMIC_INIT(0); + +/** + * tomoyo_supervisor - Ask for the supervisor's decision. + * + * @r: Pointer to "struct tomoyo_request_info". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns 0 if the supervisor decided to permit the access request which + * violated the policy in enforcing mode, TOMOYO_RETRY_REQUEST if the + * supervisor decided to retry the access request which violated the policy in + * enforcing mode, 0 if it is not in enforcing mode, -EPERM otherwise. + */ +int tomoyo_supervisor(struct tomoyo_request_info *r, const char *fmt, ...) +{ + va_list args; + int error = -EPERM; + int pos; + int len; + static unsigned int tomoyo_serial; + struct tomoyo_query *entry = NULL; + bool quota_exceeded = false; + char *header; + switch (r->mode) { + char *buffer; + case TOMOYO_CONFIG_LEARNING: + if (!tomoyo_domain_quota_is_ok(r)) + return 0; + va_start(args, fmt); + len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 4; + va_end(args); + buffer = kmalloc(len, GFP_NOFS); + if (!buffer) + return 0; + va_start(args, fmt); + vsnprintf(buffer, len - 1, fmt, args); + va_end(args); + tomoyo_normalize_line(buffer); + tomoyo_write_domain2(buffer, r->domain, false); + kfree(buffer); + /* fall through */ + case TOMOYO_CONFIG_PERMISSIVE: + return 0; + } + if (!r->domain) + r->domain = tomoyo_domain(); + if (!atomic_read(&tomoyo_query_observers)) + return -EPERM; + va_start(args, fmt); + len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 32; + va_end(args); + header = tomoyo_init_audit_log(&len, r); + if (!header) + goto out; + entry = kzalloc(sizeof(*entry), GFP_NOFS); + if (!entry) + goto out; + entry->query = kzalloc(len, GFP_NOFS); + if (!entry->query) + goto out; + len = ksize(entry->query); + spin_lock(&tomoyo_query_list_lock); + if (tomoyo_quota_for_query && tomoyo_query_memory_size + len + + sizeof(*entry) >= tomoyo_quota_for_query) { + quota_exceeded = true; + } else { + tomoyo_query_memory_size += len + sizeof(*entry); + entry->serial = tomoyo_serial++; + } + spin_unlock(&tomoyo_query_list_lock); + if (quota_exceeded) + goto out; + pos = snprintf(entry->query, len - 1, "Q%u-%hu\n%s", + entry->serial, r->retry, header); + kfree(header); + header = NULL; + va_start(args, fmt); + vsnprintf(entry->query + pos, len - 1 - pos, fmt, args); + entry->query_len = strlen(entry->query) + 1; + va_end(args); + spin_lock(&tomoyo_query_list_lock); + list_add_tail(&entry->list, &tomoyo_query_list); + spin_unlock(&tomoyo_query_list_lock); + /* Give 10 seconds for supervisor's opinion. */ + for (entry->timer = 0; + atomic_read(&tomoyo_query_observers) && entry->timer < 100; + entry->timer++) { + wake_up(&tomoyo_query_wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 10); + if (entry->answer) + break; + } + spin_lock(&tomoyo_query_list_lock); + list_del(&entry->list); + tomoyo_query_memory_size -= len + sizeof(*entry); + spin_unlock(&tomoyo_query_list_lock); + switch (entry->answer) { + case 3: /* Asked to retry by administrator. */ + error = TOMOYO_RETRY_REQUEST; + r->retry++; + break; + case 1: + /* Granted by administrator. */ + error = 0; + break; + case 0: + /* Timed out. */ + break; + default: + /* Rejected by administrator. */ + break; + } + out: + if (entry) + kfree(entry->query); + kfree(entry); + kfree(header); + return error; +} + +/** + * tomoyo_poll_query - poll() for /sys/kernel/security/tomoyo/query. + * + * @file: Pointer to "struct file". + * @wait: Pointer to "poll_table". + * + * Returns POLLIN | POLLRDNORM when ready to read, 0 otherwise. + * + * Waits for access requests which violated policy in enforcing mode. + */ +static int tomoyo_poll_query(struct file *file, poll_table *wait) +{ + struct list_head *tmp; + bool found = false; + u8 i; + for (i = 0; i < 2; i++) { + spin_lock(&tomoyo_query_list_lock); + list_for_each(tmp, &tomoyo_query_list) { + struct tomoyo_query *ptr = + list_entry(tmp, typeof(*ptr), list); + if (ptr->answer) + continue; + found = true; + break; + } + spin_unlock(&tomoyo_query_list_lock); + if (found) + return POLLIN | POLLRDNORM; + if (i) + break; + poll_wait(file, &tomoyo_query_wait, wait); + } + return 0; +} + +/** + * tomoyo_read_query - Read access requests which violated policy in enforcing mode. + * + * @head: Pointer to "struct tomoyo_io_buffer". + */ +static void tomoyo_read_query(struct tomoyo_io_buffer *head) +{ + struct list_head *tmp; + int pos = 0; + int len = 0; + char *buf; + if (head->r.w_pos) + return; + if (head->read_buf) { + kfree(head->read_buf); + head->read_buf = NULL; + } + spin_lock(&tomoyo_query_list_lock); + list_for_each(tmp, &tomoyo_query_list) { + struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); + if (ptr->answer) + continue; + if (pos++ != head->r.query_index) + continue; + len = ptr->query_len; + break; + } + spin_unlock(&tomoyo_query_list_lock); + if (!len) { + head->r.query_index = 0; + return; + } + buf = kzalloc(len, GFP_NOFS); + if (!buf) + return; + pos = 0; + spin_lock(&tomoyo_query_list_lock); + list_for_each(tmp, &tomoyo_query_list) { + struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); + if (ptr->answer) + continue; + if (pos++ != head->r.query_index) + continue; + /* + * Some query can be skipped because tomoyo_query_list + * can change, but I don't care. + */ + if (len == ptr->query_len) + memmove(buf, ptr->query, len); + break; + } + spin_unlock(&tomoyo_query_list_lock); + if (buf[0]) { + head->read_buf = buf; + head->r.w[head->r.w_pos++] = buf; + head->r.query_index++; + } else { + kfree(buf); + } +} + +/** + * tomoyo_write_answer - Write the supervisor's decision. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns 0 on success, -EINVAL otherwise. + */ +static int tomoyo_write_answer(struct tomoyo_io_buffer *head) +{ + char *data = head->write_buf; + struct list_head *tmp; + unsigned int serial; + unsigned int answer; + spin_lock(&tomoyo_query_list_lock); + list_for_each(tmp, &tomoyo_query_list) { + struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); + ptr->timer = 0; + } + spin_unlock(&tomoyo_query_list_lock); + if (sscanf(data, "A%u=%u", &serial, &answer) != 2) + return -EINVAL; + spin_lock(&tomoyo_query_list_lock); + list_for_each(tmp, &tomoyo_query_list) { + struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); + if (ptr->serial != serial) + continue; + if (!ptr->answer) + ptr->answer = answer; + break; + } + spin_unlock(&tomoyo_query_list_lock); + return 0; +} + +/** + * tomoyo_read_version: Get version. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns version information. + */ +static void tomoyo_read_version(struct tomoyo_io_buffer *head) +{ + if (!head->r.eof) { + tomoyo_io_printf(head, "2.3.0"); + head->r.eof = true; + } +} + +/** + * tomoyo_read_self_domain - Get the current process's domainname. + * + * @head: Pointer to "struct tomoyo_io_buffer". + * + * Returns the current process's domainname. + */ +static void tomoyo_read_self_domain(struct tomoyo_io_buffer *head) +{ + if (!head->r.eof) { + /* + * tomoyo_domain()->domainname != NULL + * because every process belongs to a domain and + * the domain's name cannot be NULL. + */ + tomoyo_io_printf(head, "%s", tomoyo_domain()->domainname->name); + head->r.eof = true; + } +} + +/** + * tomoyo_open_control - open() for /sys/kernel/security/tomoyo/ interface. + * + * @type: Type of interface. + * @file: Pointer to "struct file". + * + * Associates policy handler and returns 0 on success, -ENOMEM otherwise. + * + * Caller acquires tomoyo_read_lock(). + */ +int tomoyo_open_control(const u8 type, struct file *file) +{ + struct tomoyo_io_buffer *head = kzalloc(sizeof(*head), GFP_NOFS); + + if (!head) + return -ENOMEM; + mutex_init(&head->io_sem); + head->type = type; + switch (type) { + case TOMOYO_DOMAINPOLICY: + /* /sys/kernel/security/tomoyo/domain_policy */ + head->write = tomoyo_write_domain; + head->read = tomoyo_read_domain; + break; + case TOMOYO_EXCEPTIONPOLICY: + /* /sys/kernel/security/tomoyo/exception_policy */ + head->write = tomoyo_write_exception; + head->read = tomoyo_read_exception; + break; + case TOMOYO_SELFDOMAIN: + /* /sys/kernel/security/tomoyo/self_domain */ + head->read = tomoyo_read_self_domain; + break; + case TOMOYO_DOMAIN_STATUS: + /* /sys/kernel/security/tomoyo/.domain_status */ + head->write = tomoyo_write_domain_profile; + head->read = tomoyo_read_domain_profile; + break; + case TOMOYO_PROCESS_STATUS: + /* /sys/kernel/security/tomoyo/.process_status */ + head->write = tomoyo_write_pid; + head->read = tomoyo_read_pid; + break; + case TOMOYO_VERSION: + /* /sys/kernel/security/tomoyo/version */ + head->read = tomoyo_read_version; + head->readbuf_size = 128; + break; + case TOMOYO_MEMINFO: + /* /sys/kernel/security/tomoyo/meminfo */ + head->write = tomoyo_write_memory_quota; + head->read = tomoyo_read_memory_counter; + head->readbuf_size = 512; + break; + case TOMOYO_PROFILE: + /* /sys/kernel/security/tomoyo/profile */ + head->write = tomoyo_write_profile; + head->read = tomoyo_read_profile; + break; + case TOMOYO_QUERY: /* /sys/kernel/security/tomoyo/query */ + head->poll = tomoyo_poll_query; + head->write = tomoyo_write_answer; + head->read = tomoyo_read_query; + break; + case TOMOYO_MANAGER: + /* /sys/kernel/security/tomoyo/manager */ + head->write = tomoyo_write_manager; + head->read = tomoyo_read_manager; + break; + } + if (!(file->f_mode & FMODE_READ)) { + /* + * No need to allocate read_buf since it is not opened + * for reading. + */ + head->read = NULL; + head->poll = NULL; + } else if (!head->poll) { + /* Don't allocate read_buf for poll() access. */ + if (!head->readbuf_size) + head->readbuf_size = 4096 * 2; + head->read_buf = kzalloc(head->readbuf_size, GFP_NOFS); + if (!head->read_buf) { + kfree(head); + return -ENOMEM; + } + } + if (!(file->f_mode & FMODE_WRITE)) { + /* + * No need to allocate write_buf since it is not opened + * for writing. + */ + head->write = NULL; + } else if (head->write) { + head->writebuf_size = 4096 * 2; + head->write_buf = kzalloc(head->writebuf_size, GFP_NOFS); + if (!head->write_buf) { + kfree(head->read_buf); + kfree(head); + return -ENOMEM; + } + } + if (type != TOMOYO_QUERY) + head->reader_idx = tomoyo_read_lock(); + file->private_data = head; + /* + * Call the handler now if the file is + * /sys/kernel/security/tomoyo/self_domain + * so that the user can use + * cat < /sys/kernel/security/tomoyo/self_domain" + * to know the current process's domainname. + */ + if (type == TOMOYO_SELFDOMAIN) + tomoyo_read_control(file, NULL, 0); + /* + * If the file is /sys/kernel/security/tomoyo/query , increment the + * observer counter. + * The obserber counter is used by tomoyo_supervisor() to see if + * there is some process monitoring /sys/kernel/security/tomoyo/query. + */ + else if (type == TOMOYO_QUERY) + atomic_inc(&tomoyo_query_observers); + return 0; +} + +/** + * tomoyo_poll_control - poll() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @wait: Pointer to "poll_table". + * + * Waits for read readiness. + * /sys/kernel/security/tomoyo/query is handled by /usr/sbin/tomoyo-queryd . + */ +int tomoyo_poll_control(struct file *file, poll_table *wait) +{ + struct tomoyo_io_buffer *head = file->private_data; + if (!head->poll) + return -ENOSYS; + return head->poll(file, wait); +} + +/** + * tomoyo_read_control - read() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buffer: Poiner to buffer to write to. + * @buffer_len: Size of @buffer. + * + * Returns bytes read on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_read_control(struct file *file, char __user *buffer, + const int buffer_len) +{ + int len; + struct tomoyo_io_buffer *head = file->private_data; + + if (!head->read) + return -ENOSYS; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + head->read_user_buf = buffer; + head->read_user_buf_avail = buffer_len; + if (tomoyo_flush(head)) + /* Call the policy handler. */ + head->read(head); + tomoyo_flush(head); + len = head->read_user_buf - buffer; + mutex_unlock(&head->io_sem); + return len; +} + +/** + * tomoyo_write_control - write() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * @buffer: Pointer to buffer to read from. + * @buffer_len: Size of @buffer. + * + * Returns @buffer_len on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_control(struct file *file, const char __user *buffer, + const int buffer_len) +{ + struct tomoyo_io_buffer *head = file->private_data; + int error = buffer_len; + int avail_len = buffer_len; + char *cp0 = head->write_buf; + + if (!head->write) + return -ENOSYS; + if (!access_ok(VERIFY_READ, buffer, buffer_len)) + return -EFAULT; + /* Don't allow updating policies by non manager programs. */ + if (head->write != tomoyo_write_pid && + head->write != tomoyo_write_domain && !tomoyo_manager()) + return -EPERM; + if (mutex_lock_interruptible(&head->io_sem)) + return -EINTR; + /* Read a line and dispatch it to the policy handler. */ + while (avail_len > 0) { + char c; + if (head->write_avail >= head->writebuf_size - 1) { + error = -ENOMEM; + break; + } else if (get_user(c, buffer)) { + error = -EFAULT; + break; + } + buffer++; + avail_len--; + cp0[head->write_avail++] = c; + if (c != '\n') + continue; + cp0[head->write_avail - 1] = '\0'; + head->write_avail = 0; + tomoyo_normalize_line(cp0); + head->write(head); + } + mutex_unlock(&head->io_sem); + return error; +} + +/** + * tomoyo_close_control - close() for /sys/kernel/security/tomoyo/ interface. + * + * @file: Pointer to "struct file". + * + * Releases memory and returns 0. + * + * Caller looses tomoyo_read_lock(). + */ +int tomoyo_close_control(struct file *file) +{ + struct tomoyo_io_buffer *head = file->private_data; + const bool is_write = !!head->write_buf; + + /* + * If the file is /sys/kernel/security/tomoyo/query , decrement the + * observer counter. + */ + if (head->type == TOMOYO_QUERY) + atomic_dec(&tomoyo_query_observers); + else + tomoyo_read_unlock(head->reader_idx); + /* Release memory used for policy I/O. */ + kfree(head->read_buf); + head->read_buf = NULL; + kfree(head->write_buf); + head->write_buf = NULL; + kfree(head); + head = NULL; + file->private_data = NULL; + if (is_write) + tomoyo_run_gc(); + return 0; +} + +/** + * tomoyo_check_profile - Check all profiles currently assigned to domains are defined. + */ +void tomoyo_check_profile(void) +{ + struct tomoyo_domain_info *domain; + const int idx = tomoyo_read_lock(); + tomoyo_policy_loaded = true; + /* Check all profiles currently assigned to domains are defined. */ + list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { + const u8 profile = domain->profile; + if (tomoyo_profile_ptr[profile]) + continue; + printk(KERN_ERR "You need to define profile %u before using it.\n", + profile); + printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.3/ " + "for more information.\n"); + panic("Profile %u (used by '%s') not defined.\n", + profile, domain->domainname->name); + } + tomoyo_read_unlock(idx); + if (tomoyo_profile_version != 20090903) { + printk(KERN_ERR "You need to install userland programs for " + "TOMOYO 2.3 and initialize policy configuration.\n"); + printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.3/ " + "for more information.\n"); + panic("Profile version %u is not supported.\n", + tomoyo_profile_version); + } + printk(KERN_INFO "TOMOYO: 2.3.0\n"); + printk(KERN_INFO "Mandatory Access Control activated.\n"); +} |