/* * security/tomoyo/common.c * * Common functions for TOMOYO. * * Copyright (C) 2005-2010 NTT DATA CORPORATION */ #include #include #include #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[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"; } /** * tomoyo_print_name_union - Print a tomoyo_name_union. * * @head: Pointer to "struct tomoyo_io_buffer". * @ptr: Pointer to "struct tomoyo_name_union". * * Returns true on success, false otherwise. */ static bool tomoyo_print_name_union(struct tomoyo_io_buffer *head, const struct tomoyo_name_union *ptr) { int pos = head->read_avail; if (pos && head->read_buf[pos - 1] == ' ') head->read_avail--; if (ptr->is_group) return tomoyo_io_printf(head, " @%s", ptr->group->group_name->name); return tomoyo_io_printf(head, " %s", 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". * * Returns true on success, false otherwise. */ bool tomoyo_print_number_union(struct tomoyo_io_buffer *head, const struct tomoyo_number_union *ptr) { unsigned long min; unsigned long max; u8 min_type; u8 max_type; if (!tomoyo_io_printf(head, " ")) return false; if (ptr->is_group) return tomoyo_io_printf(head, "@%s", ptr->group->group_name->name); min_type = ptr->min_type; max_type = ptr->max_type; min = ptr->values[0]; max = ptr->values[1]; switch (min_type) { case TOMOYO_VALUE_TYPE_HEXADECIMAL: if (!tomoyo_io_printf(head, "0x%lX", min)) return false; break; case TOMOYO_VALUE_TYPE_OCTAL: if (!tomoyo_io_printf(head, "0%lo", min)) return false; break; default: if (!tomoyo_io_printf(head, "%lu", min)) return false; break; } if (min == max && min_type == max_type) return true; switch (max_type) { case TOMOYO_VALUE_TYPE_HEXADECIMAL: return tomoyo_io_printf(head, "-0x%lX", max); case TOMOYO_VALUE_TYPE_OCTAL: return tomoyo_io_printf(head, "-0%lo", max); default: return tomoyo_io_printf(head, "-%lu", max); } } /** * tomoyo_io_printf - Transactional printf() to "struct tomoyo_io_buffer" structure. * * @head: Pointer to "struct tomoyo_io_buffer". * @fmt: The printf()'s format string, followed by parameters. * * Returns true if output was written, false otherwise. * * The snprintf() will truncate, but tomoyo_io_printf() won't. */ bool tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) { va_list args; int len; int pos = head->read_avail; int size = head->readbuf_size - pos; if (size <= 0) return false; va_start(args, fmt); len = vsnprintf(head->read_buf + pos, size, fmt, args); va_end(args); if (pos + len >= head->readbuf_size) return false; head->read_avail += len; return true; } /** * tomoyo_find_or_assign_new_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_find_or_assign_new_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; } /** * 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; int value; int mode; u8 config; 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_find_or_assign_new_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 (strstr(cp, "verbose=yes")) value = 1; else if (strstr(cp, "verbose=no")) value = 0; else value = -1; if (!strcmp(data, "PREFERENCE::enforcing")) { if (use_default) { profile->enforcing = &tomoyo_default_profile.preference; return 0; } profile->enforcing = &profile->preference; if (value >= 0) profile->preference.enforcing_verbose = value; return 0; } if (!strcmp(data, "PREFERENCE::permissive")) { if (use_default) { profile->permissive = &tomoyo_default_profile.preference; return 0; } profile->permissive = &profile->preference; if (value >= 0) profile->preference.permissive_verbose = value; return 0; } if (!strcmp(data, "PREFERENCE::learning")) { char *cp2; if (use_default) { profile->learning = &tomoyo_default_profile.preference; return 0; } profile->learning = &profile->preference; if (value >= 0) profile->preference.learning_verbose = value; cp2 = strstr(cp, "max_entry="); if (cp2) sscanf(cp2 + 10, "%u", &profile->preference.learning_max_entry); return 0; } if (profile == &tomoyo_default_profile) return -EINVAL; if (!strcmp(data, "COMMENT")) { const struct tomoyo_path_info *old_comment = profile->comment; profile->comment = tomoyo_get_name(cp); tomoyo_put_name(old_comment); return 0; } if (!strcmp(data, "CONFIG")) { i = TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX; config = profile->default_config; } else if (tomoyo_str_starts(&data, "CONFIG::")) { config = 0; for (i = 0; i < TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX; i++) { if (strcmp(data, 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 { for (mode = 3; mode >= 0; mode--) if (strstr(cp, tomoyo_mode_4[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_read_profile - Read profile table. * * @head: Pointer to "struct tomoyo_io_buffer". */ static void tomoyo_read_profile(struct tomoyo_io_buffer *head) { int index; if (head->read_eof) return; if (head->read_bit) goto body; tomoyo_io_printf(head, "PROFILE_VERSION=%s\n", "20090903"); tomoyo_io_printf(head, "PREFERENCE::learning={ verbose=%s " "max_entry=%u }\n", tomoyo_yesno(tomoyo_default_profile.preference. learning_verbose), tomoyo_default_profile.preference.learning_max_entry); tomoyo_io_printf(head, "PREFERENCE::permissive={ verbose=%s }\n", tomoyo_yesno(tomoyo_default_profile.preference. permissive_verbose)); tomoyo_io_printf(head, "PREFERENCE::enforcing={ verbose=%s }\n", tomoyo_yesno(tomoyo_default_profile.preference. enforcing_verbose)); head->read_bit = 1; body: for (index = head->read_step; index < TOMOYO_MAX_PROFILES; index++) { bool done; u8 config; int i; int pos; const struct tomoyo_profile *profile = tomoyo_profile_ptr[index]; const struct tomoyo_path_info *comment; head->read_step = index; if (!profile) continue; pos = head->read_avail; comment = profile->comment; done = tomoyo_io_printf(head, "%u-COMMENT=%s\n", index, comment ? comment->name : ""); if (!done) goto out; config = profile->default_config; if (!tomoyo_io_printf(head, "%u-CONFIG={ mode=%s }\n", index, tomoyo_mode_4[config & 3])) goto out; for (i = 0; i < TOMOYO_MAX_MAC_INDEX + TOMOYO_MAX_MAC_CATEGORY_INDEX; i++) { config = profile->config[i]; if (config == TOMOYO_CONFIG_USE_DEFAULT) continue; if (!tomoyo_io_printf(head, "%u-CONFIG::%s={ mode=%s }\n", index, tomoyo_mac_keywords[i], tomoyo_mode_4[config & 3])) goto out; } if (profile->learning != &tomoyo_default_profile.preference && !tomoyo_io_printf(head, "%u-PREFERENCE::learning={ " "verbose=%s max_entry=%u }\n", index, tomoyo_yesno(profile->preference. learning_verbose), profile->preference.learning_max_entry)) goto out; if (profile->permissive != &tomoyo_default_profile.preference && !tomoyo_io_printf(head, "%u-PREFERENCE::permissive={ " "verbose=%s }\n", index, tomoyo_yesno(profile->preference. permissive_verbose))) goto out; if (profile->enforcing != &tomoyo_default_profile.preference && !tomoyo_io_printf(head, "%u-PREFERENCE::enforcing={ " "verbose=%s }\n", index, tomoyo_yesno(profile->preference. enforcing_verbose))) goto out; continue; out: head->read_avail = pos; break; } if (index == TOMOYO_MAX_PROFILES) head->read_eof = true; } static bool tomoyo_same_manager_entry(const struct tomoyo_acl_head *a, const struct tomoyo_acl_head *b) { return container_of(a, struct tomoyo_policy_manager_entry, head) ->manager == container_of(b, struct tomoyo_policy_manager_entry, 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_policy_manager_entry 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_entry); tomoyo_put_name(e.manager); return error; } /** * tomoyo_write_manager_policy - 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_policy(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_policy - Read manager policy. * * @head: Pointer to "struct tomoyo_io_buffer". * * Caller holds tomoyo_read_lock(). */ static void tomoyo_read_manager_policy(struct tomoyo_io_buffer *head) { bool done = true; if (head->read_eof) return; list_for_each_cookie(head->read_var2, &tomoyo_policy_list[TOMOYO_ID_MANAGER]) { struct tomoyo_policy_manager_entry *ptr = list_entry(head->read_var2, typeof(*ptr), head.list); if (ptr->head.is_deleted) continue; done = tomoyo_io_printf(head, "%s\n", ptr->manager->name); if (!done) break; } head->read_eof = done; } /** * tomoyo_policy_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_policy_manager(void) { struct tomoyo_policy_manager_entry *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 (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). */ head->read_avail = 0; tomoyo_io_printf(head, "# select %s\n", data); head->read_single_domain = true; head->read_eof = !domain; head->read_var1 = &domain->list; head->read_var2 = NULL; head->read_bit = 0; head->read_step = 0; 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_domain_policy2 - 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_policy2(char *data, struct tomoyo_domain_info *domain, const bool is_delete) { if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALLOW_MOUNT)) return tomoyo_write_mount_policy(data, domain, is_delete); return tomoyo_write_file_policy(data, domain, is_delete); } /** * tomoyo_write_domain_policy - 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_policy(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_policy_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_find_or_assign_new_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_domain_policy2(data, domain, is_delete); } /** * tomoyo_print_path_acl - Print a single path ACL entry. * * @head: Pointer to "struct tomoyo_io_buffer". * @ptr: Pointer to "struct tomoyo_path_acl". * * Returns true on success, false otherwise. */ static bool tomoyo_print_path_acl(struct tomoyo_io_buffer *head, struct tomoyo_path_acl *ptr) { int pos; u8 bit; const u16 perm = ptr->perm; for (bit = head->read_bit; bit < TOMOYO_MAX_PATH_OPERATION; bit++) { if (!(perm & (1 << bit))) 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; pos = head->read_avail; if (!tomoyo_io_printf(head, "allow_%s ", tomoyo_path_keyword[bit]) || !tomoyo_print_name_union(head, &ptr->name) || !tomoyo_io_printf(head, "\n")) goto out; } head->read_bit = 0; return true; out: head->read_bit = bit; head->read_avail = pos; return false; } /** * tomoyo_print_path2_acl - Print a double path ACL entry. * * @head: Pointer to "struct tomoyo_io_buffer". * @ptr: Pointer to "struct tomoyo_path2_acl". * * Returns true on success, false otherwise. */ static bool tomoyo_print_path2_acl(struct tomoyo_io_buffer *head, struct tomoyo_path2_acl *ptr) { int pos; const u8 perm = ptr->perm; u8 bit; for (bit = head->read_bit; bit < TOMOYO_MAX_PATH2_OPERATION; bit++) { if (!(perm & (1 << bit))) continue; pos = head->read_avail; if (!tomoyo_io_printf(head, "allow_%s ", tomoyo_path2_keyword[bit]) || !tomoyo_print_name_union(head, &ptr->name1) || !tomoyo_print_name_union(head, &ptr->name2) || !tomoyo_io_printf(head, "\n")) goto out; } head->read_bit = 0; return true; out: head->read_bit = bit; head->read_avail = pos; return false; } /** * tomoyo_print_path_number_acl - Print a path_number ACL entry. * * @head: Pointer to "struct tomoyo_io_buffer". * @ptr: Pointer to "struct tomoyo_path_number_acl". * * Returns true on success, false otherwise. */ static bool tomoyo_print_path_number_acl(struct tomoyo_io_buffer *head, struct tomoyo_path_number_acl *ptr) { int pos; u8 bit; const u8 perm = ptr->perm; for (bit = head->read_bit; bit < TOMOYO_MAX_PATH_NUMBER_OPERATION; bit++) { if (!(perm & (1 << bit))) continue; pos = head->read_avail; if (!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) || !tomoyo_io_printf(head, "\n")) goto out; } head->read_bit = 0; return true; out: head->read_bit = bit; head->read_avail = pos; return false; } /** * tomoyo_print_mkdev_acl - Print a mkdev ACL entry. * * @head: Pointer to "struct tomoyo_io_buffer". * @ptr: Pointer to "struct tomoyo_mkdev_acl". * * Returns true on success, false otherwise. */ static bool tomoyo_print_mkdev_acl(struct tomoyo_io_buffer *head, struct tomoyo_mkdev_acl *ptr) { int pos; u8 bit; const u16 perm = ptr->perm; for (bit = head->read_bit; bit < TOMOYO_MAX_MKDEV_OPERATION; bit++) { if (!(perm & (1 << bit))) continue; pos = head->read_avail; if (!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) || !tomoyo_io_printf(head, "\n")) goto out; } head->read_bit = 0; return true; out: head->read_bit = bit; head->read_avail = pos; return false; } /** * tomoyo_print_mount_acl - Print a mount ACL entry. * * @head: Pointer to "struct tomoyo_io_buffer". * @ptr: Pointer to "struct tomoyo_mount_acl". * * Returns true on success, false otherwise. */ static bool tomoyo_print_mount_acl(struct tomoyo_io_buffer *head, struct tomoyo_mount_acl *ptr) { const int pos = head->read_avail; if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_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) || !tomoyo_io_printf(head, "\n")) { head->read_avail = pos; return false; } return true; } /** * tomoyo_print_entry - Print an ACL entry. * * @head: Pointer to "struct tomoyo_io_buffer". * @ptr: 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 *ptr) { const u8 acl_type = ptr->type; if (ptr->is_deleted) return true; if (acl_type == TOMOYO_TYPE_PATH_ACL) { struct tomoyo_path_acl *acl = container_of(ptr, struct tomoyo_path_acl, head); return tomoyo_print_path_acl(head, acl); } if (acl_type == TOMOYO_TYPE_PATH2_ACL) { struct tomoyo_path2_acl *acl = container_of(ptr, struct tomoyo_path2_acl, head); return tomoyo_print_path2_acl(head, acl); } if (acl_type == TOMOYO_TYPE_PATH_NUMBER_ACL) { struct tomoyo_path_number_acl *acl = container_of(ptr, struct tomoyo_path_number_acl, head); return tomoyo_print_path_number_acl(head, acl); } if (acl_type == TOMOYO_TYPE_MKDEV_ACL) { struct tomoyo_mkdev_acl *acl = container_of(ptr, struct tomoyo_mkdev_acl, head); return tomoyo_print_mkdev_acl(head, acl); } if (acl_type == TOMOYO_TYPE_MOUNT_ACL) { struct tomoyo_mount_acl *acl = container_of(ptr, struct tomoyo_mount_acl, head); return tomoyo_print_mount_acl(head, acl); } BUG(); /* This must not happen. */ return false; } /** * tomoyo_read_domain_policy - Read domain policy. * * @head: Pointer to "struct tomoyo_io_buffer". * * Caller holds tomoyo_read_lock(). */ static void tomoyo_read_domain_policy(struct tomoyo_io_buffer *head) { bool done = true; if (head->read_eof) return; if (head->read_step == 0) head->read_step = 1; list_for_each_cookie(head->read_var1, &tomoyo_domain_list) { struct tomoyo_domain_info *domain = list_entry(head->read_var1, typeof(*domain), list); const char *quota_exceeded = ""; const char *transition_failed = ""; const char *ignore_global_allow_read = ""; if (head->read_step != 1) goto acl_loop; if (domain->is_deleted && !head->read_single_domain) continue; /* Print domainname and flags. */ if (domain->quota_warned) quota_exceeded = "quota_exceeded\n"; if (domain->transition_failed) transition_failed = "transition_failed\n"; if (domain->ignore_global_allow_read) ignore_global_allow_read = TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ "\n"; done = tomoyo_io_printf(head, "%s\n" TOMOYO_KEYWORD_USE_PROFILE "%u\n%s%s%s\n", domain->domainname->name, domain->profile, quota_exceeded, transition_failed, ignore_global_allow_read); if (!done) break; head->read_step = 2; acl_loop: if (head->read_step == 3) goto tail_mark; /* Print ACL entries in the domain. */ list_for_each_cookie(head->read_var2, &domain->acl_info_list) { struct tomoyo_acl_info *ptr = list_entry(head->read_var2, typeof(*ptr), list); done = tomoyo_print_entry(head, ptr); if (!done) break; } if (!done) break; head->read_var2 = NULL; head->read_step = 3; tail_mark: done = tomoyo_io_printf(head, "\n"); if (!done) break; head->read_step = 1; if (head->read_single_domain) break; } head->read_eof = done; } /** * 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 '^' /sys/kernel/security/tomoyo/domain_policy | * awk ' { if ( domainname == "" ) { if ( $1 == "" ) * 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) { bool done = true; if (head->read_eof) return; list_for_each_cookie(head->read_var1, &tomoyo_domain_list) { struct tomoyo_domain_info *domain = list_entry(head->read_var1, typeof(*domain), list); if (domain->is_deleted) continue; done = tomoyo_io_printf(head, "%u %s\n", domain->profile, domain->domainname->name); if (!done) break; } head->read_eof = done; } /** * 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) { unsigned long pid; /* No error check. */ strict_strtoul(head->write_buf, 10, &pid); head->read_step = (int) pid; head->read_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) { if (head->read_avail == 0 && !head->read_eof) { const int pid = head->read_step; struct task_struct *p; struct tomoyo_domain_info *domain = NULL; rcu_read_lock(); read_lock(&tasklist_lock); p = find_task_by_vpid(pid); if (p) domain = tomoyo_real_domain(p); read_unlock(&tasklist_lock); rcu_read_unlock(); if (domain) tomoyo_io_printf(head, "%d %u %s", pid, domain->profile, domain->domainname->name); head->read_eof = true; } } 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 }; /** * tomoyo_write_exception_policy - 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_policy(struct tomoyo_io_buffer *head) { char *data = head->write_buf; bool is_delete = tomoyo_str_starts(&data, TOMOYO_KEYWORD_DELETE); u8 i; 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); } if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_AGGREGATOR)) return tomoyo_write_aggregator_policy(data, is_delete); if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_ALLOW_READ)) return tomoyo_write_globally_readable_policy(data, is_delete); if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_FILE_PATTERN)) return tomoyo_write_pattern_policy(data, is_delete); if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DENY_REWRITE)) return tomoyo_write_no_rewrite_policy(data, is_delete); if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_PATH_GROUP)) return tomoyo_write_group(data, is_delete, TOMOYO_PATH_GROUP); if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_NUMBER_GROUP)) return tomoyo_write_group(data, is_delete, TOMOYO_NUMBER_GROUP); return -EINVAL; } static void tomoyo_print_number(char *buffer, int buffer_len, const struct tomoyo_number_union *ptr) { 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; memset(buffer, 0, buffer_len); buffer_len -= 2; for (i = 0; i < 2; i++) { int len; switch (min_type) { case TOMOYO_VALUE_TYPE_HEXADECIMAL: snprintf(buffer, buffer_len, "0x%lX", min); break; case TOMOYO_VALUE_TYPE_OCTAL: snprintf(buffer, buffer_len, "0%lo", min); break; default: snprintf(buffer, buffer_len, "%lu", min); break; } if (min == max && min_type == max_type) break; len = strlen(buffer); buffer[len++] = '-'; buffer += len; buffer_len -= len; min_type = max_type; min = max; } } static const char *tomoyo_group_name[TOMOYO_MAX_GROUP] = { [TOMOYO_PATH_GROUP] = TOMOYO_KEYWORD_PATH_GROUP, [TOMOYO_NUMBER_GROUP] = TOMOYO_KEYWORD_NUMBER_GROUP }; /** * 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) { const char *w[3] = { "", "", "" }; w[0] = tomoyo_group_name[idx]; list_for_each_cookie(head->read_var1, &tomoyo_group_list[idx]) { struct tomoyo_group *group = list_entry(head->read_var1, typeof(*group), list); w[1] = group->group_name->name; list_for_each_cookie(head->read_var2, &group->member_list) { char buffer[128]; struct tomoyo_acl_head *ptr = list_entry(head->read_var2, typeof(*ptr), list); if (ptr->is_deleted) continue; if (idx == TOMOYO_PATH_GROUP) { w[2] = container_of(ptr, struct tomoyo_path_group, head)->member_name->name; } else if (idx == TOMOYO_NUMBER_GROUP) { tomoyo_print_number(buffer, sizeof(buffer), &container_of (ptr, struct tomoyo_number_group, head)->number); w[2] = buffer; } if (!tomoyo_io_printf(head, "%s%s %s\n", w[0], w[1], w[2])) return false; } head->read_var2 = NULL; } head->read_var1 = 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->read_var2, &tomoyo_policy_list[idx]) { const char *w[4] = { "", "", "", "" }; struct tomoyo_acl_head *acl = container_of(head->read_var2, typeof(*acl), list); if (acl->is_deleted) continue; switch (idx) { case TOMOYO_ID_TRANSITION_CONTROL: { struct tomoyo_transition_control *ptr = container_of(acl, typeof(*ptr), head); w[0] = tomoyo_transition_type[ptr->type]; if (ptr->program) w[1] = ptr->program->name; if (ptr->domainname) w[3] = ptr->domainname->name; if (w[1][0] && w[3][0]) w[2] = " from "; } break; case TOMOYO_ID_GLOBALLY_READABLE: { struct tomoyo_globally_readable_file_entry *ptr = container_of(acl, typeof(*ptr), head); w[0] = TOMOYO_KEYWORD_ALLOW_READ; w[1] = ptr->filename->name; } break; case TOMOYO_ID_AGGREGATOR: { struct tomoyo_aggregator_entry *ptr = container_of(acl, typeof(*ptr), head); w[0] = TOMOYO_KEYWORD_AGGREGATOR; w[1] = ptr->original_name->name; w[2] = " "; w[3] = ptr->aggregated_name->name; } break; case TOMOYO_ID_PATTERN: { struct tomoyo_pattern_entry *ptr = container_of(acl, typeof(*ptr), head); w[0] = TOMOYO_KEYWORD_FILE_PATTERN; w[1] = ptr->pattern->name; } break; case TOMOYO_ID_NO_REWRITE: { struct tomoyo_no_rewrite_entry *ptr = container_of(acl, typeof(*ptr), head); w[0] = TOMOYO_KEYWORD_DENY_REWRITE; w[1] = ptr->pattern->name; } break; default: continue; } if (!tomoyo_io_printf(head, "%s%s%s%s\n", w[0], w[1], w[2], w[3])) return false; } head->read_var2 = NULL; return true; } /** * tomoyo_read_exception_policy - Read exception policy. * * @head: Pointer to "struct tomoyo_io_buffer". * * Caller holds tomoyo_read_lock(). */ static void tomoyo_read_exception_policy(struct tomoyo_io_buffer *head) { if (head->read_eof) return; while (head->read_step < TOMOYO_MAX_POLICY && tomoyo_read_policy(head, head->read_step)) head->read_step++; if (head->read_step < TOMOYO_MAX_POLICY) return; while (head->read_step < TOMOYO_MAX_POLICY + TOMOYO_MAX_GROUP && tomoyo_read_group(head, head->read_step - TOMOYO_MAX_POLICY)) head->read_step++; if (head->read_step < TOMOYO_MAX_POLICY + TOMOYO_MAX_GROUP) return; head->read_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) { static const char *tomoyo_mode_4[4] = { "disabled", "learning", "permissive", "enforcing" }; 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); if (!buffer) return NULL; do_gettimeofday(&tv); 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_4[r->mode], gpid, (pid_t) sys_getpid(), (pid_t) sys_getppid(), 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_entry { struct list_head list; char *query; int query_len; unsigned int serial; int timer; int answer; }; /* The list for "struct tomoyo_query_entry". */ 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 *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_domain_policy2(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; tomoyo_query_entry = kzalloc(sizeof(*tomoyo_query_entry), GFP_NOFS); if (!tomoyo_query_entry) goto out; tomoyo_query_entry->query = kzalloc(len, GFP_NOFS); if (!tomoyo_query_entry->query) goto out; len = ksize(tomoyo_query_entry->query); INIT_LIST_HEAD(&tomoyo_query_entry->list); spin_lock(&tomoyo_query_list_lock); if (tomoyo_quota_for_query && tomoyo_query_memory_size + len + sizeof(*tomoyo_query_entry) >= tomoyo_quota_for_query) { quota_exceeded = true; } else { tomoyo_query_memory_size += len + sizeof(*tomoyo_query_entry); tomoyo_query_entry->serial = tomoyo_serial++; } spin_unlock(&tomoyo_query_list_lock); if (quota_exceeded) goto out; pos = snprintf(tomoyo_query_entry->query, len - 1, "Q%u-%hu\n%s", tomoyo_query_entry->serial, r->retry, header); kfree(header); header = NULL; va_start(args, fmt); vsnprintf(tomoyo_query_entry->query + pos, len - 1 - pos, fmt, args); tomoyo_query_entry->query_len = strlen(tomoyo_query_entry->query) + 1; va_end(args); spin_lock(&tomoyo_query_list_lock); list_add_tail(&tomoyo_query_entry->list, &tomoyo_query_list); spin_unlock(&tomoyo_query_list_lock); /* Give 10 seconds for supervisor's opinion. */ for (tomoyo_query_entry->timer = 0; atomic_read(&tomoyo_query_observers) && tomoyo_query_entry->timer < 100; tomoyo_query_entry->timer++) { wake_up(&tomoyo_query_wait); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(HZ / 10); if (tomoyo_query_entry->answer) break; } spin_lock(&tomoyo_query_list_lock); list_del(&tomoyo_query_entry->list); tomoyo_query_memory_size -= len + sizeof(*tomoyo_query_entry); spin_unlock(&tomoyo_query_list_lock); switch (tomoyo_query_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 (tomoyo_query_entry) kfree(tomoyo_query_entry->query); kfree(tomoyo_query_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_entry *ptr = list_entry(tmp, struct tomoyo_query_entry, 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->read_avail) return; if (head->read_buf) { kfree(head->read_buf); head->read_buf = NULL; head->readbuf_size = 0; } spin_lock(&tomoyo_query_list_lock); list_for_each(tmp, &tomoyo_query_list) { struct tomoyo_query_entry *ptr = list_entry(tmp, struct tomoyo_query_entry, list); if (ptr->answer) continue; if (pos++ != head->read_step) continue; len = ptr->query_len; break; } spin_unlock(&tomoyo_query_list_lock); if (!len) { head->read_step = 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_entry *ptr = list_entry(tmp, struct tomoyo_query_entry, list); if (ptr->answer) continue; if (pos++ != head->read_step) 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_avail = len; head->readbuf_size = head->read_avail; head->read_buf = buf; head->read_step++; } 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_entry *ptr = list_entry(tmp, struct tomoyo_query_entry, 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_entry *ptr = list_entry(tmp, struct tomoyo_query_entry, 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->read_eof) { tomoyo_io_printf(head, "2.3.0-pre"); head->read_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->read_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->read_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_policy; head->read = tomoyo_read_domain_policy; break; case TOMOYO_EXCEPTIONPOLICY: /* /sys/kernel/security/tomoyo/exception_policy */ head->write = tomoyo_write_exception_policy; head->read = tomoyo_read_exception_policy; 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_policy; head->read = tomoyo_read_manager_policy; 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 = 0; struct tomoyo_io_buffer *head = file->private_data; char *cp; if (!head->read) return -ENOSYS; if (mutex_lock_interruptible(&head->io_sem)) return -EINTR; /* Call the policy handler. */ head->read(head); if (len < 0) goto out; /* Write to buffer. */ len = head->read_avail; if (len > buffer_len) len = buffer_len; if (!len) goto out; /* head->read_buf changes by some functions. */ cp = head->read_buf; if (copy_to_user(buffer, cp, len)) { len = -EFAULT; goto out; } head->read_avail -= len; memmove(cp, cp + len, head->read_avail); out: 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_policy && !tomoyo_policy_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; panic("Profile %u (used by '%s') not defined.\n", profile, domain->domainname->name); } tomoyo_read_unlock(idx); if (tomoyo_profile_version != 20090903) panic("Profile version %u is not supported.\n", tomoyo_profile_version); printk(KERN_INFO "TOMOYO: 2.3.0-pre 2010/06/03\n"); printk(KERN_INFO "Mandatory Access Control activated.\n"); }