This patch adds auditing code used by TOMOYO. TOMOYO generates audit logs ( /sys/kernel/security/tomoyo/grant_log and /sys/kernel/security/tomoyo/reject_log ) in the form of domain policy ( /sys/kernel/security/domain_policy ). Users can configure whether to generate audit logs or not via profile ( /sys/kernel/security/tomoyo/profile ). By default all information usable for "if" clause is audited. Users can suppress unneeded information. Memory quota for audit logs is configurable via profile and meminfo interface ( /sys/kernel/security/tomoyo/meminfo ). tomoyo_profile()->audit is guaranteed to point to either default configuration ( tomoyo_default_profile ) or per profile configuration. Signed-off-by: Tetsuo Handa --- security/tomoyo/audit.c | 561 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 561 insertions(+) --- /dev/null +++ security-testing-2.6/security/tomoyo/audit.c @@ -0,0 +1,561 @@ +/* + * security/tomoyo/audit.c + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + */ +#include "internal.h" + +/** + * tomoyo_print_bprm - Print "struct linux_binprm" for auditing. + * + * @bprm: Pointer to "struct linux_binprm". + * @dump: Pointer to "struct tomoyo_page_dump". + * + * Returns the contents of @bprm on success, NULL otherwise. + */ +static char *tomoyo_print_bprm(struct linux_binprm *bprm, + struct tomoyo_page_dump *dump) +{ + static const int tomoyo_buffer_len = 4096 * 2; + char *buffer = kzalloc(tomoyo_buffer_len, GFP_KERNEL); + char *cp; + char *last_start; + int len; + unsigned long pos = bprm->p; + int offset = pos % PAGE_SIZE; + int argv_count = bprm->argc; + int envp_count = bprm->envc; + bool truncated = false; + if (!buffer) + return NULL; + len = snprintf(buffer, tomoyo_buffer_len - 1, "argv[]={ "); + cp = buffer + len; + if (!argv_count) { + memmove(cp, "} envp[]={ ", 11); + cp += 11; + } + last_start = cp; + while (argv_count || envp_count) { + if (!tomoyo_dump_page(bprm, pos, dump)) + goto out; + pos += PAGE_SIZE - offset; + /* Read. */ + while (offset < PAGE_SIZE) { + const char *kaddr = dump->data; + const unsigned char c = kaddr[offset++]; + if (cp == last_start) + *cp++ = '"'; + if (cp >= buffer + tomoyo_buffer_len - 32) { + /* Reserve some room for "..." string. */ + truncated = true; + } else if (c == '\\') { + *cp++ = '\\'; + *cp++ = '\\'; + } else if (c > ' ' && c < 127) { + *cp++ = c; + } else if (!c) { + *cp++ = '"'; + *cp++ = ' '; + last_start = cp; + } else { + *cp++ = '\\'; + *cp++ = (c >> 6) + '0'; + *cp++ = ((c >> 3) & 7) + '0'; + *cp++ = (c & 7) + '0'; + } + if (c) + continue; + if (argv_count) { + if (--argv_count == 0) { + if (truncated) { + cp = last_start; + memmove(cp, "... ", 4); + cp += 4; + } + memmove(cp, "} envp[]={ ", 11); + cp += 11; + last_start = cp; + truncated = false; + } + } else if (envp_count) { + if (--envp_count == 0) { + if (truncated) { + cp = last_start; + memmove(cp, "... ", 4); + cp += 4; + } + } + } + if (!argv_count && !envp_count) + break; + } + offset = 0; + } + *cp++ = '}'; + *cp = '\0'; + return buffer; + out: + snprintf(buffer, tomoyo_buffer_len - 1, + "argv[]={ ... } envp[]= { ... }"); + return buffer; +} + +/** + * tomoyo_filetype - Get string representation of file type. + * + * @mode: Mode value for stat(). + * + * Returns header line on success, NULL otherwise. + */ +static const char *tomoyo_filetype(const mode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFREG: + case 0: + return "file"; + case S_IFDIR: + return "directory"; + case S_IFLNK: + return "symlink"; + case S_IFIFO: + return "fifo"; + case S_IFSOCK: + return "socket"; + case S_IFBLK: + return "block"; + case S_IFCHR: + return "char"; + } + return "unknown"; /* This should not happen. */ +} + +/** + * tomoyo_print_header - Get header line of audit log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns string representation. + */ +static char *tomoyo_print_header(struct tomoyo_request_info *r) +{ + static const char *tomoyo_mode_4[4] = { + "disabled", "learning", "permissive", "enforcing" + }; + struct timeval tv; + unsigned int dev; + mode_t mode; + struct tomoyo_obj_info *obj = r->obj; + const u32 tomoyo_flags = current->tomoyo_flags; + static const int tomoyo_buffer_len = 4096; + char *buffer = kmalloc(tomoyo_buffer_len, GFP_KERNEL); + int pos; + if (!buffer) + return NULL; + do_gettimeofday(&tv); + pos = snprintf(buffer, tomoyo_buffer_len - 1, + "#timestamp=%lu profile=%u mode=%s " + "(global-pid=%u)", tv.tv_sec, r->profile, + tomoyo_mode_4[r->mode], task_pid_nr(current)); + if (tomoyo_profile(r->profile)->audit->audit_task_info) { + pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, + " task={ pid=%u ppid=%u uid=%u gid=%u euid=%u" + " egid=%u suid=%u sgid=%u fsuid=%u fsgid=%u" + " state[0]=%u state[1]=%u state[2]=%u" + " type%s=execute_handler }", + (pid_t) sys_getpid(), (pid_t) sys_getppid(), + current_uid(), current_gid(), current_euid(), + current_egid(), current_suid(), current_sgid(), + current_fsuid(), current_fsgid(), + (u8) (tomoyo_flags >> 24), + (u8) (tomoyo_flags >> 16), + (u8) (tomoyo_flags >> 8), tomoyo_flags & + TOMOYO_TASK_IS_EXECUTE_HANDLER ? "" : "!"); + } + if (!obj || !tomoyo_profile(r->profile)->audit->audit_path_info) + goto no_obj_info; + if (!obj->validate_done) { + tomoyo_get_attributes(obj); + obj->validate_done = true; + } + if (obj->path1_valid) { + dev = obj->path1_stat.dev; + mode = obj->path1_stat.mode; + pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, + " path1={ uid=%u gid=%u ino=%lu major=%u" + " minor=%u perm=0%o type=%s", + obj->path1_stat.uid, obj->path1_stat.gid, + (unsigned long) obj->path1_stat.ino, + MAJOR(dev), MINOR(dev), mode & S_IALLUGO, + tomoyo_filetype(mode & S_IFMT)); + if (S_ISCHR(mode) || S_ISBLK(mode)) { + dev = obj->path1_stat.rdev; + pos += snprintf(buffer + pos, + tomoyo_buffer_len - 1 - pos, + " dev_major=%u dev_minor=%u", + MAJOR(dev), MINOR(dev)); + } + pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, + " }"); + } + if (obj->path1_parent_valid) { + pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, + " path1.parent={ uid=%u gid=%u ino=%lu" + " perm=0%o }", obj->path1_parent_stat.uid, + obj->path1_parent_stat.gid, + obj->path1_parent_stat.ino, + obj->path1_parent_stat.mode & S_IALLUGO); + } + if (obj->path2_valid) { + dev = obj->path2_stat.dev; + mode = obj->path2_stat.mode; + pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, + " path2={ uid=%u gid=%u ino=%lu major=%u" + " minor=%u perm=0%o type=%s", + obj->path2_stat.uid, obj->path2_stat.gid, + (unsigned long) obj->path2_stat.ino, + MAJOR(dev), MINOR(dev), mode & S_IALLUGO, + tomoyo_filetype(mode & S_IFMT)); + if (S_ISCHR(mode) || S_ISBLK(mode)) { + dev = obj->path2_stat.rdev; + pos += snprintf(buffer + pos, + tomoyo_buffer_len - 1 - pos, + " dev_major=%u dev_minor=%u", + MAJOR(dev), MINOR(dev)); + } + pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, + " }"); + } + if (obj->path2_parent_valid) { + pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, + " path2.parent={ uid=%u gid=%u ino=%lu" + " perm=0%o }", obj->path2_parent_stat.uid, + obj->path2_parent_stat.gid, + obj->path2_parent_stat.ino, + obj->path2_parent_stat.mode & S_IALLUGO); + } + no_obj_info: + if (pos < tomoyo_buffer_len - 1) + return buffer; + kfree(buffer); + return NULL; +} + +/** + * 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. + */ +char *tomoyo_init_audit_log(int *len, struct tomoyo_request_info *r) +{ + char *buf = NULL; + char *bprm_info = NULL; + char *realpath = NULL; + const char *symlink = NULL; + const char *header = NULL; + int pos; + const char *domainname; + if (!r->domain) + r->domain = tomoyo_current_domain(); + domainname = r->domain->domainname->name; + header = tomoyo_print_header(r); + if (!header) + return NULL; + *len += strlen(domainname) + strlen(header) + 10; + if (r->ee) { + struct file *file = r->ee->bprm->file; + realpath = tomoyo_realpath_from_path(&file->f_path); + bprm_info = tomoyo_print_bprm(r->ee->bprm, &r->ee->dump); + if (!realpath || !bprm_info) + goto out; + *len += strlen(realpath) + 64 + strlen(bprm_info); + } else if (r->obj && r->obj->symlink_target) { + symlink = r->obj->symlink_target->name; + *len += 18 + strlen(symlink); + } + buf = kzalloc(*len, GFP_KERNEL); + if (!buf) + goto out; + pos = snprintf(buf, (*len) - 1, "%s", header); + if (realpath) { + struct linux_binprm *bprm = r->ee->bprm; + pos += snprintf(buf + pos, (*len) - 1 - pos, + " exec={ realpath=\"%s\" argc=%d envc=%d %s }", + realpath, bprm->argc, bprm->envc, bprm_info); + } else if (symlink) + pos += snprintf(buf + pos, (*len) - 1 - pos, + " symlink.target=\"%s\"", symlink); + snprintf(buf + pos, (*len) - 1 - pos, "\n%s\n", domainname); + out: + kfree(realpath); + kfree(bprm_info); + kfree(header); + return buf; +} + +/** + * tomoyo_update_task_state - Update task's state. + * + * @r: Pointer to "struct tomoyo_request_info". + */ +static void tomoyo_update_task_state(struct tomoyo_request_info *r) +{ + /* + * Don't change the lowest byte because it is reserved for + * TOMOYO_TASK_IS_IN_EXECVE / + * TOMOYO_DONT_SLEEP_ON_ENFORCE_ERROR / + * TOMOYO_TASK_IS_EXECUTE_HANDLER / TOMOYO_TASK_IS_POLICY_MANAGER . + */ + const struct tomoyo_condition *ptr = r->cond; + if (ptr) { + struct task_struct *task = current; + const u8 flags = ptr->post_state[3]; + u32 tomoyo_flags = task->tomoyo_flags; + if (flags & 1) { + tomoyo_flags &= ~0xFF000000; + tomoyo_flags |= ptr->post_state[0] << 24; + } + if (flags & 2) { + tomoyo_flags &= ~0x00FF0000; + tomoyo_flags |= ptr->post_state[1] << 16; + } + if (flags & 4) { + tomoyo_flags &= ~0x0000FF00; + tomoyo_flags |= ptr->post_state[2] << 8; + } + task->tomoyo_flags = tomoyo_flags; + r->cond = NULL; + } +} + +#ifndef CONFIG_SECURITY_TOMOYO_AUDIT + +/** + * tomoyo_write_audit_log - Write audit log. + * + * @is_granted: True if this is a granted log. + * @r: Pointer to "struct tomoyo_request_info". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns 0 on success, -ENOMEM otherwise. + */ +int tomoyo_write_audit_log(const bool is_granted, + struct tomoyo_request_info *r, const char *fmt, ...) +{ + tomoyo_update_task_state(r); + return 0; +} + +#else + +static DECLARE_WAIT_QUEUE_HEAD(tomoyo_grant_log_wait); +static DECLARE_WAIT_QUEUE_HEAD(tomoyo_reject_log_wait); + +static DEFINE_SPINLOCK(tomoyo_audit_log_lock); + +/* Structure for audit log. */ +struct tomoyo_log_entry { + struct list_head list; + char *log; + int size; +}; + +/* The list for "struct tomoyo_log_entry". */ +static LIST_HEAD(tomoyo_grant_log); + +/* The list for "struct tomoyo_log_entry". */ +static LIST_HEAD(tomoyo_reject_log); + +static unsigned int tomoyo_grant_log_count; +static unsigned int tomoyo_reject_log_count; + +/** + * tomoyo_write_audit_log - Write audit log. + * + * @is_granted: True if this is a granted log. + * @r: Pointer to "struct tomoyo_request_info". + * @fmt: The printf()'s format string, followed by parameters. + * + * Returns 0 on success, -ENOMEM otherwise. + */ +int tomoyo_write_audit_log(const bool is_granted, + struct tomoyo_request_info *r, const char *fmt, ...) +{ + va_list args; + int error = -ENOMEM; + int pos; + int len; + char *buf; + struct tomoyo_log_entry *new_entry; + bool quota_exceeded = false; + if (!r->domain) + r->domain = tomoyo_current_domain(); + if (is_granted) { + if (tomoyo_grant_log_count >= + tomoyo_profile(r->domain->profile)-> + audit->audit_max_grant_log + || !tomoyo_get_audit(r->profile, r->type, true)) + goto out; + } else { + if (tomoyo_reject_log_count >= + tomoyo_profile(r->domain->profile)-> + audit->audit_max_reject_log + || !tomoyo_get_audit(r->profile, r->type, false)) + goto out; + } + va_start(args, fmt); + len = vsnprintf((char *) &pos, sizeof(pos) - 1, fmt, args) + 32; + va_end(args); + buf = tomoyo_init_audit_log(&len, r); + if (!buf) + goto out; + pos = strlen(buf); + va_start(args, fmt); + vsnprintf(buf + pos, len - pos - 1, fmt, args); + va_end(args); + new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) { + kfree(buf); + goto out; + } + new_entry->log = buf; + /* + * The new_entry->size is used for memory quota checks. + * Don't go beyond strlen(new_entry->log). + */ + new_entry->size = tomoyo_round2(len) + + tomoyo_round2(sizeof(*new_entry)); + spin_lock(&tomoyo_audit_log_lock); + if (tomoyo_quota_for_audit_log && tomoyo_audit_log_memory_size + + new_entry->size >= tomoyo_quota_for_audit_log) { + quota_exceeded = true; + } else { + tomoyo_audit_log_memory_size += new_entry->size; + if (is_granted) { + list_add_tail(&new_entry->list, &tomoyo_grant_log); + tomoyo_grant_log_count++; + } else { + list_add_tail(&new_entry->list, &tomoyo_reject_log); + tomoyo_reject_log_count++; + } + } + spin_unlock(&tomoyo_audit_log_lock); + if (quota_exceeded) { + kfree(buf); + kfree(new_entry); + goto out; + } + if (is_granted) + wake_up(&tomoyo_grant_log_wait); + else + wake_up(&tomoyo_reject_log_wait); + error = 0; + out: + tomoyo_update_task_state(r); + return error; +} + +/** + * tomoyo_read_grant_log - Read a grant log. + * + * @head: Pointer to "struct tomoyo_io_buffer". + */ +void tomoyo_read_grant_log(struct tomoyo_io_buffer *head) +{ + struct tomoyo_log_entry *ptr = NULL; + if (head->read_avail) + return; + if (head->read_buf) { + kfree(head->read_buf); + head->read_buf = NULL; + head->readbuf_size = 0; + } + spin_lock(&tomoyo_audit_log_lock); + if (!list_empty(&tomoyo_grant_log)) { + ptr = list_entry(tomoyo_grant_log.next, + struct tomoyo_log_entry, list); + list_del(&ptr->list); + tomoyo_grant_log_count--; + tomoyo_audit_log_memory_size -= ptr->size; + } + spin_unlock(&tomoyo_audit_log_lock); + if (ptr) { + head->read_buf = ptr->log; + head->read_avail = strlen(ptr->log) + 1; + head->readbuf_size = head->read_avail; + kfree(ptr); + } +} + +/** + * tomoyo_poll_grant_log - Wait for a grant log. + * + * @file: Pointer to "struct file". + * @wait: Pointer to "poll_table". + * + * Returns POLLIN | POLLRDNORM when ready to read a grant log. + */ +int tomoyo_poll_grant_log(struct file *file, poll_table *wait) +{ + if (tomoyo_grant_log_count) + return POLLIN | POLLRDNORM; + poll_wait(file, &tomoyo_grant_log_wait, wait); + if (tomoyo_grant_log_count) + return POLLIN | POLLRDNORM; + return 0; +} + +/** + * tomoyo_read_reject_log - Read a reject log. + * + * @head: Pointer to "struct tomoyo_io_buffer". + */ +void tomoyo_read_reject_log(struct tomoyo_io_buffer *head) +{ + struct tomoyo_log_entry *ptr = NULL; + if (head->read_avail) + return; + if (head->read_buf) { + kfree(head->read_buf); + head->read_buf = NULL; + head->readbuf_size = 0; + } + spin_lock(&tomoyo_audit_log_lock); + if (!list_empty(&tomoyo_reject_log)) { + ptr = list_entry(tomoyo_reject_log.next, + struct tomoyo_log_entry, list); + list_del(&ptr->list); + tomoyo_reject_log_count--; + tomoyo_audit_log_memory_size -= ptr->size; + } + spin_unlock(&tomoyo_audit_log_lock); + if (ptr) { + head->read_buf = ptr->log; + head->read_avail = strlen(ptr->log) + 1; + head->readbuf_size = head->read_avail; + kfree(ptr); + } +} + +/** + * tomoyo_poll_reject_log - Wait for a reject log. + * + * @file: Pointer to "struct file". + * @wait: Pointer to "poll_table". + * + * Returns POLLIN | POLLRDNORM when ready to read a reject log. + */ +int tomoyo_poll_reject_log(struct file *file, poll_table *wait) +{ + if (tomoyo_reject_log_count) + return POLLIN | POLLRDNORM; + poll_wait(file, &tomoyo_reject_log_wait, wait); + if (tomoyo_reject_log_count) + return POLLIN | POLLRDNORM; + return 0; +} +#endif -- -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/