From mboxrd@z Thu Jan 1 00:00:00 1970 From: "Timothy R. Chavez" Subject: [PATCH] audit file restructuring in kernel/ Date: Fri, 28 Apr 2006 10:46:29 -0500 Message-ID: <1146239190.24265.65.camel@localhost.localdomain> Mime-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: 7bit Return-path: Received: from mx3.redhat.com (mx3.redhat.com [172.16.48.32]) by int-mx1.corp.redhat.com (8.12.11.20060308/8.11.6) with ESMTP id k3SFlACd019106 for ; Fri, 28 Apr 2006 11:47:10 -0400 Received: from e34.co.us.ibm.com (e34.co.us.ibm.com [32.97.110.152]) by mx3.redhat.com (8.13.1/8.13.1) with ESMTP id k3SFkjrH004684 for ; Fri, 28 Apr 2006 11:46:45 -0400 Received: from westrelay02.boulder.ibm.com (westrelay02.boulder.ibm.com [9.17.195.11]) by e34.co.us.ibm.com (8.12.11.20060308/8.12.11) with ESMTP id k3SFkdA0009844 for ; Fri, 28 Apr 2006 11:46:39 -0400 Received: from d03av01.boulder.ibm.com (d03av01.boulder.ibm.com [9.17.195.167]) by westrelay02.boulder.ibm.com (8.12.10/NCO/VER6.8) with ESMTP id k3SFkVvY269722 for ; Fri, 28 Apr 2006 09:46:39 -0600 Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1]) by d03av01.boulder.ibm.com (8.12.11/8.13.3) with ESMTP id k3SFkVcJ025429 for ; Fri, 28 Apr 2006 09:46:31 -0600 Received: from dyn95340151.austin.ibm.com (dyn95340151.austin.ibm.com [9.53.40.151]) by d03av01.boulder.ibm.com (8.12.11/8.12.11) with ESMTP id k3SFkUFm025365 for ; Fri, 28 Apr 2006 09:46:30 -0600 List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-audit-bounces@redhat.com Errors-To: linux-audit-bounces@redhat.com To: linux-audit@redhat.com List-Id: linux-audit@redhat.com Hi, This patch makes audit look respectable :) -tim diff --git a/kernel/Makefile b/kernel/Makefile index 58908f9..88ca434 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -10,6 +10,7 @@ obj-y = sched.o fork.o exec_domain.o kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \ hrtimer.o +obj-$(CONFIG_AUDIT) += audit/ obj-$(CONFIG_DEBUG_MUTEXES) += mutex-debug.o obj-$(CONFIG_FUTEX) += futex.o ifeq ($(CONFIG_COMPAT),y) @@ -29,8 +30,6 @@ obj-$(CONFIG_COMPAT) += compat.o obj-$(CONFIG_CPUSETS) += cpuset.o obj-$(CONFIG_IKCONFIG) += configs.o obj-$(CONFIG_STOP_MACHINE) += stop_machine.o -obj-$(CONFIG_AUDIT) += audit.o auditfilter.o -obj-$(CONFIG_AUDITSYSCALL) += auditsc.o obj-$(CONFIG_KPROBES) += kprobes.o obj-$(CONFIG_SYSFS) += ksysfs.o obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o diff --git a/kernel/audit.c b/kernel/audit.c deleted file mode 100644 index 7637410..0000000 --- a/kernel/audit.c +++ /dev/null @@ -1,1133 +0,0 @@ -/* audit.c -- Auditing support - * Gateway between the kernel (e.g., selinux) and the user-space audit daemon. - * System-call specific features have moved to auditsc.c - * - * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina. - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Written by Rickard E. (Rik) Faith - * - * Goals: 1) Integrate fully with SELinux. - * 2) Minimal run-time overhead: - * a) Minimal when syscall auditing is disabled (audit_enable=0). - * b) Small when syscall auditing is enabled and no audit record - * is generated (defer as much work as possible to record - * generation time): - * i) context is allocated, - * ii) names from getname are stored without a copy, and - * iii) inode information stored from path_lookup. - * 3) Ability to disable syscall auditing at boot time (audit=0). - * 4) Usable by other parts of the kernel (if audit_log* is called, - * then a syscall record will be generated automatically for the - * current syscall). - * 5) Netlink interface to user-space. - * 6) Support low-overhead kernel-based filtering to minimize the - * information that must be passed to user-space. - * - * Example user-space utilities: http://people.redhat.com/sgrubb/audit/ - */ - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include "audit.h" - -/* No auditing will take place until audit_initialized != 0. - * (Initialization happens after skb_init is called.) */ -static int audit_initialized; - -/* No syscall auditing will take place unless audit_enabled != 0. */ -int audit_enabled; - -/* Default state when kernel boots without any parameters. */ -static int audit_default; - -/* If auditing cannot proceed, audit_failure selects what happens. */ -static int audit_failure = AUDIT_FAIL_PRINTK; - -/* If audit records are to be written to the netlink socket, audit_pid - * contains the (non-zero) pid. */ -int audit_pid; - -/* If audit_rate_limit is non-zero, limit the rate of sending audit records - * to that number per second. This prevents DoS attacks, but results in - * audit records being dropped. */ -static int audit_rate_limit; - -/* Number of outstanding audit_buffers allowed. */ -static int audit_backlog_limit = 64; -static int audit_backlog_wait_time = 60 * HZ; -static int audit_backlog_wait_overflow = 0; - -/* The identity of the user shutting down the audit system. */ -uid_t audit_sig_uid = -1; -pid_t audit_sig_pid = -1; - -/* Records can be lost in several ways: - 0) [suppressed in audit_alloc] - 1) out of memory in audit_log_start [kmalloc of struct audit_buffer] - 2) out of memory in audit_log_move [alloc_skb] - 3) suppressed due to audit_rate_limit - 4) suppressed due to audit_backlog_limit -*/ -static atomic_t audit_lost = ATOMIC_INIT(0); - -/* The netlink socket. */ -static struct sock *audit_sock; - -/* Inotify handle. */ -struct inotify_handle *audit_ih; - -/* The audit_freelist is a list of pre-allocated audit buffers (if more - * than AUDIT_MAXFREE are in use, the audit buffer is freed instead of - * being placed on the freelist). */ -static DEFINE_SPINLOCK(audit_freelist_lock); -static int audit_freelist_count; -static LIST_HEAD(audit_freelist); - -static struct sk_buff_head audit_skb_queue; -static struct task_struct *kauditd_task; -static DECLARE_WAIT_QUEUE_HEAD(kauditd_wait); -static DECLARE_WAIT_QUEUE_HEAD(audit_backlog_wait); - -/* Serialize requests from userspace. */ -static DEFINE_MUTEX(audit_cmd_mutex); - -/* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting - * audit records. Since printk uses a 1024 byte buffer, this buffer - * should be at least that large. */ -#define AUDIT_BUFSIZ 1024 - -/* AUDIT_MAXFREE is the number of empty audit_buffers we keep on the - * audit_freelist. Doing so eliminates many kmalloc/kfree calls. */ -#define AUDIT_MAXFREE (2*NR_CPUS) - -/* The audit_buffer is used when formatting an audit record. The caller - * locks briefly to get the record off the freelist or to allocate the - * buffer, and locks briefly to send the buffer to the netlink layer or - * to place it on a transmit queue. Multiple audit_buffers can be in - * use simultaneously. */ -struct audit_buffer { - struct list_head list; - struct sk_buff *skb; /* formatted skb ready to send */ - struct audit_context *ctx; /* NULL or associated context */ - gfp_t gfp_mask; -}; - -static void audit_set_pid(struct audit_buffer *ab, pid_t pid) -{ - struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data; - nlh->nlmsg_pid = pid; -} - -void audit_panic(const char *message) -{ - switch (audit_failure) - { - case AUDIT_FAIL_SILENT: - break; - case AUDIT_FAIL_PRINTK: - printk(KERN_ERR "audit: %s\n", message); - break; - case AUDIT_FAIL_PANIC: - panic("audit: %s\n", message); - break; - } -} - -static inline int audit_rate_check(void) -{ - static unsigned long last_check = 0; - static int messages = 0; - static DEFINE_SPINLOCK(lock); - unsigned long flags; - unsigned long now; - unsigned long elapsed; - int retval = 0; - - if (!audit_rate_limit) return 1; - - spin_lock_irqsave(&lock, flags); - if (++messages < audit_rate_limit) { - retval = 1; - } else { - now = jiffies; - elapsed = now - last_check; - if (elapsed > HZ) { - last_check = now; - messages = 0; - retval = 1; - } - } - spin_unlock_irqrestore(&lock, flags); - - return retval; -} - -/** - * audit_log_lost - conditionally log lost audit message event - * @message: the message stating reason for lost audit message - * - * Emit at least 1 message per second, even if audit_rate_check is - * throttling. - * Always increment the lost messages counter. -*/ -void audit_log_lost(const char *message) -{ - static unsigned long last_msg = 0; - static DEFINE_SPINLOCK(lock); - unsigned long flags; - unsigned long now; - int print; - - atomic_inc(&audit_lost); - - print = (audit_failure == AUDIT_FAIL_PANIC || !audit_rate_limit); - - if (!print) { - spin_lock_irqsave(&lock, flags); - now = jiffies; - if (now - last_msg > HZ) { - print = 1; - last_msg = now; - } - spin_unlock_irqrestore(&lock, flags); - } - - if (print) { - printk(KERN_WARNING - "audit: audit_lost=%d audit_rate_limit=%d audit_backlog_limit=%d\n", - atomic_read(&audit_lost), - audit_rate_limit, - audit_backlog_limit); - audit_panic(message); - } -} - -static int audit_set_rate_limit(int limit, uid_t loginuid, u32 sid) -{ - int old = audit_rate_limit; - - if (sid) { - char *ctx = NULL; - u32 len; - int rc; - if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) - return rc; - else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_rate_limit=%d old=%d by auid=%u subj=%s", - limit, old, loginuid, ctx); - kfree(ctx); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_rate_limit=%d old=%d by auid=%u", - limit, old, loginuid); - audit_rate_limit = limit; - return old; -} - -static int audit_set_backlog_limit(int limit, uid_t loginuid, u32 sid) -{ - int old = audit_backlog_limit; - - if (sid) { - char *ctx = NULL; - u32 len; - int rc; - if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) - return rc; - else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_backlog_limit=%d old=%d by auid=%u subj=%s", - limit, old, loginuid, ctx); - kfree(ctx); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_backlog_limit=%d old=%d by auid=%u", - limit, old, loginuid); - audit_backlog_limit = limit; - return old; -} - -static int audit_set_enabled(int state, uid_t loginuid, u32 sid) -{ - int old = audit_enabled; - - if (state != 0 && state != 1) - return -EINVAL; - - if (sid) { - char *ctx = NULL; - u32 len; - int rc; - if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) - return rc; - else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_enabled=%d old=%d by auid=%u subj=%s", - state, old, loginuid, ctx); - kfree(ctx); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_enabled=%d old=%d by auid=%u", - state, old, loginuid); - audit_enabled = state; - return old; -} - -static int audit_set_failure(int state, uid_t loginuid, u32 sid) -{ - int old = audit_failure; - - if (state != AUDIT_FAIL_SILENT - && state != AUDIT_FAIL_PRINTK - && state != AUDIT_FAIL_PANIC) - return -EINVAL; - - if (sid) { - char *ctx = NULL; - u32 len; - int rc; - if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) - return rc; - else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_failure=%d old=%d by auid=%u subj=%s", - state, old, loginuid, ctx); - kfree(ctx); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_failure=%d old=%d by auid=%u", - state, old, loginuid); - audit_failure = state; - return old; -} - -static int kauditd_thread(void *dummy) -{ - struct sk_buff *skb; - - while (1) { - skb = skb_dequeue(&audit_skb_queue); - wake_up(&audit_backlog_wait); - if (skb) { - if (audit_pid) { - int err = netlink_unicast(audit_sock, skb, audit_pid, 0); - if (err < 0) { - BUG_ON(err != -ECONNREFUSED); /* Shoudn't happen */ - printk(KERN_ERR "audit: *NO* daemon at audit_pid=%d\n", audit_pid); - audit_pid = 0; - } - } else { - printk(KERN_NOTICE "%s\n", skb->data + NLMSG_SPACE(0)); - kfree_skb(skb); - } - } else { - DECLARE_WAITQUEUE(wait, current); - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&kauditd_wait, &wait); - - if (!skb_queue_len(&audit_skb_queue)) { - try_to_freeze(); - schedule(); - } - - __set_current_state(TASK_RUNNING); - remove_wait_queue(&kauditd_wait, &wait); - } - } - return 0; -} - -int audit_send_list(void *_dest) -{ - struct audit_netlink_list *dest = _dest; - int pid = dest->pid; - struct sk_buff *skb; - - while ((skb = __skb_dequeue(&dest->q)) != NULL) - netlink_unicast(audit_sock, skb, pid, 0); - - kfree(dest); - - return 0; -} - -struct sk_buff *audit_make_reply(int pid, int seq, int type, int done, - int multi, void *payload, int size) -{ - struct sk_buff *skb; - struct nlmsghdr *nlh; - int len = NLMSG_SPACE(size); - void *data; - int flags = multi ? NLM_F_MULTI : 0; - int t = done ? NLMSG_DONE : type; - - skb = alloc_skb(len, GFP_KERNEL); - if (!skb) - return NULL; - - nlh = NLMSG_PUT(skb, pid, seq, t, size); - nlh->nlmsg_flags = flags; - data = NLMSG_DATA(nlh); - memcpy(data, payload, size); - return skb; - -nlmsg_failure: /* Used by NLMSG_PUT */ - if (skb) - kfree_skb(skb); - return NULL; -} - -/** - * audit_send_reply - send an audit reply message via netlink - * @pid: process id to send reply to - * @seq: sequence number - * @type: audit message type - * @done: done (last) flag - * @multi: multi-part message flag - * @payload: payload data - * @size: payload size - * - * Allocates an skb, builds the netlink message, and sends it to the pid. - * No failure notifications. - */ -void audit_send_reply(int pid, int seq, int type, int done, int multi, - void *payload, int size) -{ - struct sk_buff *skb; - skb = audit_make_reply(pid, seq, type, done, multi, payload, size); - if (!skb) - return; - /* Ignore failure. It'll only happen if the sender goes away, - because our timeout is set to infinite. */ - netlink_unicast(audit_sock, skb, pid, 0); - return; -} - -/* - * Check for appropriate CAP_AUDIT_ capabilities on incoming audit - * control messages. - */ -static int audit_netlink_ok(kernel_cap_t eff_cap, u16 msg_type) -{ - int err = 0; - - switch (msg_type) { - case AUDIT_GET: - case AUDIT_LIST: - case AUDIT_LIST_RULES: - case AUDIT_SET: - case AUDIT_ADD: - case AUDIT_ADD_RULE: - case AUDIT_DEL: - case AUDIT_DEL_RULE: - case AUDIT_SIGNAL_INFO: - if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL)) - err = -EPERM; - break; - case AUDIT_USER: - case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG: - case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2: - if (!cap_raised(eff_cap, CAP_AUDIT_WRITE)) - err = -EPERM; - break; - default: /* bad msg */ - err = -EINVAL; - } - - return err; -} - -static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh) -{ - u32 uid, pid, seq, sid; - void *data; - struct audit_status *status_get, status_set; - int err; - struct audit_buffer *ab; - u16 msg_type = nlh->nlmsg_type; - uid_t loginuid; /* loginuid of sender */ - struct audit_sig_info sig_data; - - err = audit_netlink_ok(NETLINK_CB(skb).eff_cap, msg_type); - if (err) - return err; - - /* As soon as there's any sign of userspace auditd, - * start kauditd to talk to it */ - if (!kauditd_task) - kauditd_task = kthread_run(kauditd_thread, NULL, "kauditd"); - if (IS_ERR(kauditd_task)) { - err = PTR_ERR(kauditd_task); - kauditd_task = NULL; - return err; - } - - pid = NETLINK_CREDS(skb)->pid; - uid = NETLINK_CREDS(skb)->uid; - loginuid = NETLINK_CB(skb).loginuid; - sid = NETLINK_CB(skb).sid; - seq = nlh->nlmsg_seq; - data = NLMSG_DATA(nlh); - - switch (msg_type) { - case AUDIT_GET: - status_set.enabled = audit_enabled; - status_set.failure = audit_failure; - status_set.pid = audit_pid; - status_set.rate_limit = audit_rate_limit; - status_set.backlog_limit = audit_backlog_limit; - status_set.lost = atomic_read(&audit_lost); - status_set.backlog = skb_queue_len(&audit_skb_queue); - audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_GET, 0, 0, - &status_set, sizeof(status_set)); - break; - case AUDIT_SET: - if (nlh->nlmsg_len < sizeof(struct audit_status)) - return -EINVAL; - status_get = (struct audit_status *)data; - if (status_get->mask & AUDIT_STATUS_ENABLED) { - err = audit_set_enabled(status_get->enabled, - loginuid, sid); - if (err < 0) return err; - } - if (status_get->mask & AUDIT_STATUS_FAILURE) { - err = audit_set_failure(status_get->failure, - loginuid, sid); - if (err < 0) return err; - } - if (status_get->mask & AUDIT_STATUS_PID) { - int old = audit_pid; - if (sid) { - char *ctx = NULL; - u32 len; - int rc; - if ((rc = selinux_ctxid_to_string( - sid, &ctx, &len))) - return rc; - else - audit_log(NULL, GFP_KERNEL, - AUDIT_CONFIG_CHANGE, - "audit_pid=%d old=%d by auid=%u subj=%s", - status_get->pid, old, - loginuid, ctx); - kfree(ctx); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit_pid=%d old=%d by auid=%u", - status_get->pid, old, loginuid); - audit_pid = status_get->pid; - } - if (status_get->mask & AUDIT_STATUS_RATE_LIMIT) - audit_set_rate_limit(status_get->rate_limit, - loginuid, sid); - if (status_get->mask & AUDIT_STATUS_BACKLOG_LIMIT) - audit_set_backlog_limit(status_get->backlog_limit, - loginuid, sid); - break; - case AUDIT_USER: - case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG: - case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2: - if (!audit_enabled && msg_type != AUDIT_USER_AVC) - return 0; - - err = audit_filter_user(&NETLINK_CB(skb), msg_type); - if (err == 1) { - err = 0; - ab = audit_log_start(NULL, GFP_KERNEL, msg_type); - if (ab) { - audit_log_format(ab, - "user pid=%d uid=%u auid=%u", - pid, uid, loginuid); - if (sid) { - char *ctx = NULL; - u32 len; - if (selinux_ctxid_to_string( - sid, &ctx, &len)) { - audit_log_format(ab, - " ssid=%u", sid); - /* Maybe call audit_panic? */ - } else - audit_log_format(ab, - " subj=%s", ctx); - kfree(ctx); - } - audit_log_format(ab, " msg='%.1024s'", - (char *)data); - audit_set_pid(ab, pid); - audit_log_end(ab); - } - } - break; - case AUDIT_ADD: - case AUDIT_DEL: - if (nlmsg_len(nlh) < sizeof(struct audit_rule)) - return -EINVAL; - /* fallthrough */ - case AUDIT_LIST: - err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid, - uid, seq, data, nlmsg_len(nlh), - loginuid, sid); - break; - case AUDIT_ADD_RULE: - case AUDIT_DEL_RULE: - if (nlmsg_len(nlh) < sizeof(struct audit_rule_data)) - return -EINVAL; - /* fallthrough */ - case AUDIT_LIST_RULES: - err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid, - uid, seq, data, nlmsg_len(nlh), - loginuid, sid); - break; - case AUDIT_SIGNAL_INFO: - sig_data.uid = audit_sig_uid; - sig_data.pid = audit_sig_pid; - audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_SIGNAL_INFO, - 0, 0, &sig_data, sizeof(sig_data)); - break; - default: - err = -EINVAL; - break; - } - - return err < 0 ? err : 0; -} - -/* - * Get message from skb (based on rtnetlink_rcv_skb). Each message is - * processed by audit_receive_msg. Malformed skbs with wrong length are - * discarded silently. - */ -static void audit_receive_skb(struct sk_buff *skb) -{ - int err; - struct nlmsghdr *nlh; - u32 rlen; - - while (skb->len >= NLMSG_SPACE(0)) { - nlh = (struct nlmsghdr *)skb->data; - if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len) - return; - rlen = NLMSG_ALIGN(nlh->nlmsg_len); - if (rlen > skb->len) - rlen = skb->len; - if ((err = audit_receive_msg(skb, nlh))) { - netlink_ack(skb, nlh, err); - } else if (nlh->nlmsg_flags & NLM_F_ACK) - netlink_ack(skb, nlh, 0); - skb_pull(skb, rlen); - } -} - -/* Receive messages from netlink socket. */ -static void audit_receive(struct sock *sk, int length) -{ - struct sk_buff *skb; - unsigned int qlen; - - mutex_lock(&audit_cmd_mutex); - - for (qlen = skb_queue_len(&sk->sk_receive_queue); qlen; qlen--) { - skb = skb_dequeue(&sk->sk_receive_queue); - audit_receive_skb(skb); - kfree_skb(skb); - } - mutex_unlock(&audit_cmd_mutex); -} - - -/* Initialize audit support at boot time. */ -static int __init audit_init(void) -{ - printk(KERN_INFO "audit: initializing netlink socket (%s)\n", - audit_default ? "enabled" : "disabled"); - audit_sock = netlink_kernel_create(NETLINK_AUDIT, 0, audit_receive, - THIS_MODULE); - if (!audit_sock) - audit_panic("cannot initialize netlink socket"); - else - audit_sock->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT; - - skb_queue_head_init(&audit_skb_queue); - audit_initialized = 1; - audit_enabled = audit_default; - - /* Register the callback with selinux. This callback will be invoked - * when a new policy is loaded. */ - selinux_audit_set_callback(&selinux_audit_rule_update); - - audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized"); - -#ifdef CONFIG_AUDITSYSCALL - audit_ih = inotify_init(audit_handle_ievent); - if (IS_ERR(audit_ih)) - audit_panic("cannot initialize inotify handle"); -#endif - - return 0; -} -__initcall(audit_init); - -/* Process kernel command-line parameter at boot time. audit=0 or audit=1. */ -static int __init audit_enable(char *str) -{ - audit_default = !!simple_strtol(str, NULL, 0); - printk(KERN_INFO "audit: %s%s\n", - audit_default ? "enabled" : "disabled", - audit_initialized ? "" : " (after initialization)"); - if (audit_initialized) - audit_enabled = audit_default; - return 1; -} - -__setup("audit=", audit_enable); - -static void audit_buffer_free(struct audit_buffer *ab) -{ - unsigned long flags; - - if (!ab) - return; - - if (ab->skb) - kfree_skb(ab->skb); - - spin_lock_irqsave(&audit_freelist_lock, flags); - if (++audit_freelist_count > AUDIT_MAXFREE) - kfree(ab); - else - list_add(&ab->list, &audit_freelist); - spin_unlock_irqrestore(&audit_freelist_lock, flags); -} - -static struct audit_buffer * audit_buffer_alloc(struct audit_context *ctx, - gfp_t gfp_mask, int type) -{ - unsigned long flags; - struct audit_buffer *ab = NULL; - struct nlmsghdr *nlh; - - spin_lock_irqsave(&audit_freelist_lock, flags); - if (!list_empty(&audit_freelist)) { - ab = list_entry(audit_freelist.next, - struct audit_buffer, list); - list_del(&ab->list); - --audit_freelist_count; - } - spin_unlock_irqrestore(&audit_freelist_lock, flags); - - if (!ab) { - ab = kmalloc(sizeof(*ab), gfp_mask); - if (!ab) - goto err; - } - - ab->skb = alloc_skb(AUDIT_BUFSIZ, gfp_mask); - if (!ab->skb) - goto err; - - ab->ctx = ctx; - ab->gfp_mask = gfp_mask; - nlh = (struct nlmsghdr *)skb_put(ab->skb, NLMSG_SPACE(0)); - nlh->nlmsg_type = type; - nlh->nlmsg_flags = 0; - nlh->nlmsg_pid = 0; - nlh->nlmsg_seq = 0; - return ab; -err: - audit_buffer_free(ab); - return NULL; -} - -/** - * audit_serial - compute a serial number for the audit record - * - * Compute a serial number for the audit record. Audit records are - * written to user-space as soon as they are generated, so a complete - * audit record may be written in several pieces. The timestamp of the - * record and this serial number are used by the user-space tools to - * determine which pieces belong to the same audit record. The - * (timestamp,serial) tuple is unique for each syscall and is live from - * syscall entry to syscall exit. - * - * NOTE: Another possibility is to store the formatted records off the - * audit context (for those records that have a context), and emit them - * all at syscall exit. However, this could delay the reporting of - * significant errors until syscall exit (or never, if the system - * halts). - */ -unsigned int audit_serial(void) -{ - static spinlock_t serial_lock = SPIN_LOCK_UNLOCKED; - static unsigned int serial = 0; - - unsigned long flags; - unsigned int ret; - - spin_lock_irqsave(&serial_lock, flags); - do { - ret = ++serial; - } while (unlikely(!ret)); - spin_unlock_irqrestore(&serial_lock, flags); - - return ret; -} - -static inline void audit_get_stamp(struct audit_context *ctx, - struct timespec *t, unsigned int *serial) -{ - if (ctx) - auditsc_get_stamp(ctx, t, serial); - else { - *t = CURRENT_TIME; - *serial = audit_serial(); - } -} - -/* Obtain an audit buffer. This routine does locking to obtain the - * audit buffer, but then no locking is required for calls to - * audit_log_*format. If the tsk is a task that is currently in a - * syscall, then the syscall is marked as auditable and an audit record - * will be written at syscall exit. If there is no associated task, tsk - * should be NULL. */ - -/** - * audit_log_start - obtain an audit buffer - * @ctx: audit_context (may be NULL) - * @gfp_mask: type of allocation - * @type: audit message type - * - * Returns audit_buffer pointer on success or NULL on error. - * - * Obtain an audit buffer. This routine does locking to obtain the - * audit buffer, but then no locking is required for calls to - * audit_log_*format. If the task (ctx) is a task that is currently in a - * syscall, then the syscall is marked as auditable and an audit record - * will be written at syscall exit. If there is no associated task, then - * task context (ctx) should be NULL. - */ -struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, - int type) -{ - struct audit_buffer *ab = NULL; - struct timespec t; - unsigned int serial; - int reserve; - unsigned long timeout_start = jiffies; - - if (!audit_initialized) - return NULL; - - if (unlikely(audit_filter_type(type))) - return NULL; - - if (gfp_mask & __GFP_WAIT) - reserve = 0; - else - reserve = 5; /* Allow atomic callers to go up to five - entries over the normal backlog limit */ - - while (audit_backlog_limit - && skb_queue_len(&audit_skb_queue) > audit_backlog_limit + reserve) { - if (gfp_mask & __GFP_WAIT && audit_backlog_wait_time - && time_before(jiffies, timeout_start + audit_backlog_wait_time)) { - - /* Wait for auditd to drain the queue a little */ - DECLARE_WAITQUEUE(wait, current); - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&audit_backlog_wait, &wait); - - if (audit_backlog_limit && - skb_queue_len(&audit_skb_queue) > audit_backlog_limit) - schedule_timeout(timeout_start + audit_backlog_wait_time - jiffies); - - __set_current_state(TASK_RUNNING); - remove_wait_queue(&audit_backlog_wait, &wait); - continue; - } - if (audit_rate_check()) - printk(KERN_WARNING - "audit: audit_backlog=%d > " - "audit_backlog_limit=%d\n", - skb_queue_len(&audit_skb_queue), - audit_backlog_limit); - audit_log_lost("backlog limit exceeded"); - audit_backlog_wait_time = audit_backlog_wait_overflow; - wake_up(&audit_backlog_wait); - return NULL; - } - - ab = audit_buffer_alloc(ctx, gfp_mask, type); - if (!ab) { - audit_log_lost("out of memory in audit_log_start"); - return NULL; - } - - audit_get_stamp(ab->ctx, &t, &serial); - - audit_log_format(ab, "audit(%lu.%03lu:%u): ", - t.tv_sec, t.tv_nsec/1000000, serial); - return ab; -} - -/** - * audit_expand - expand skb in the audit buffer - * @ab: audit_buffer - * @extra: space to add at tail of the skb - * - * Returns 0 (no space) on failed expansion, or available space if - * successful. - */ -static inline int audit_expand(struct audit_buffer *ab, int extra) -{ - struct sk_buff *skb = ab->skb; - int ret = pskb_expand_head(skb, skb_headroom(skb), extra, - ab->gfp_mask); - if (ret < 0) { - audit_log_lost("out of memory in audit_expand"); - return 0; - } - return skb_tailroom(skb); -} - -/* - * Format an audit message into the audit buffer. If there isn't enough - * room in the audit buffer, more room will be allocated and vsnprint - * will be called a second time. Currently, we assume that a printk - * can't format message larger than 1024 bytes, so we don't either. - */ -static void audit_log_vformat(struct audit_buffer *ab, const char *fmt, - va_list args) -{ - int len, avail; - struct sk_buff *skb; - va_list args2; - - if (!ab) - return; - - BUG_ON(!ab->skb); - skb = ab->skb; - avail = skb_tailroom(skb); - if (avail == 0) { - avail = audit_expand(ab, AUDIT_BUFSIZ); - if (!avail) - goto out; - } - va_copy(args2, args); - len = vsnprintf(skb->tail, avail, fmt, args); - if (len >= avail) { - /* The printk buffer is 1024 bytes long, so if we get - * here and AUDIT_BUFSIZ is at least 1024, then we can - * log everything that printk could have logged. */ - avail = audit_expand(ab, - max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail)); - if (!avail) - goto out; - len = vsnprintf(skb->tail, avail, fmt, args2); - } - if (len > 0) - skb_put(skb, len); -out: - return; -} - -/** - * audit_log_format - format a message into the audit buffer. - * @ab: audit_buffer - * @fmt: format string - * @...: optional parameters matching @fmt string - * - * All the work is done in audit_log_vformat. - */ -void audit_log_format(struct audit_buffer *ab, const char *fmt, ...) -{ - va_list args; - - if (!ab) - return; - va_start(args, fmt); - audit_log_vformat(ab, fmt, args); - va_end(args); -} - -/** - * audit_log_hex - convert a buffer to hex and append it to the audit skb - * @ab: the audit_buffer - * @buf: buffer to convert to hex - * @len: length of @buf to be converted - * - * No return value; failure to expand is silently ignored. - * - * This function will take the passed buf and convert it into a string of - * ascii hex digits. The new string is placed onto the skb. - */ -void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf, - size_t len) -{ - int i, avail, new_len; - unsigned char *ptr; - struct sk_buff *skb; - static const unsigned char *hex = "0123456789ABCDEF"; - - BUG_ON(!ab->skb); - skb = ab->skb; - avail = skb_tailroom(skb); - new_len = len<<1; - if (new_len >= avail) { - /* Round the buffer request up to the next multiple */ - new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1); - avail = audit_expand(ab, new_len); - if (!avail) - return; - } - - ptr = skb->tail; - for (i=0; i>4]; /* Upper nibble */ - *ptr++ = hex[buf[i] & 0x0F]; /* Lower nibble */ - } - *ptr = 0; - skb_put(skb, len << 1); /* new string is twice the old string */ -} - -/** - * audit_log_unstrustedstring - log a string that may contain random characters - * @ab: audit_buffer - * @string: string to be logged - * - * This code will escape a string that is passed to it if the string - * contains a control character, unprintable character, double quote mark, - * or a space. Unescaped strings will start and end with a double quote mark. - * Strings that are escaped are printed in hex (2 digits per char). - */ -const char *audit_log_untrustedstring(struct audit_buffer *ab, const char *string) -{ - const unsigned char *p = string; - size_t len = strlen(string); - - while (*p) { - if (*p == '"' || *p < 0x21 || *p > 0x7f) { - audit_log_hex(ab, string, len); - return string + len + 1; - } - p++; - } - audit_log_format(ab, "\"%s\"", string); - return p + 1; -} - -/* This is a helper-function to print the escaped d_path */ -void audit_log_d_path(struct audit_buffer *ab, const char *prefix, - struct dentry *dentry, struct vfsmount *vfsmnt) -{ - char *p, *path; - - if (prefix) - audit_log_format(ab, " %s", prefix); - - /* We will allow 11 spaces for ' (deleted)' to be appended */ - path = kmalloc(PATH_MAX+11, ab->gfp_mask); - if (!path) { - audit_log_format(ab, ""); - return; - } - p = d_path(dentry, vfsmnt, path, PATH_MAX+11); - if (IS_ERR(p)) { /* Should never happen since we send PATH_MAX */ - /* FIXME: can we save some information here? */ - audit_log_format(ab, ""); - } else - audit_log_untrustedstring(ab, p); - kfree(path); -} - -/** - * audit_log_end - end one audit record - * @ab: the audit_buffer - * - * The netlink_* functions cannot be called inside an irq context, so - * the audit buffer is placed on a queue and a tasklet is scheduled to - * remove them from the queue outside the irq context. May be called in - * any context. - */ -void audit_log_end(struct audit_buffer *ab) -{ - if (!ab) - return; - if (!audit_rate_check()) { - audit_log_lost("rate limit exceeded"); - } else { - if (audit_pid) { - struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data; - nlh->nlmsg_len = ab->skb->len - NLMSG_SPACE(0); - skb_queue_tail(&audit_skb_queue, ab->skb); - ab->skb = NULL; - wake_up_interruptible(&kauditd_wait); - } else { - printk(KERN_NOTICE "%s\n", ab->skb->data + NLMSG_SPACE(0)); - } - } - audit_buffer_free(ab); -} - -/** - * audit_log - Log an audit record - * @ctx: audit context - * @gfp_mask: type of allocation - * @type: audit message type - * @fmt: format string to use - * @...: variable parameters matching the format string - * - * This is a convenience function that calls audit_log_start, - * audit_log_vformat, and audit_log_end. It may be called - * in any context. - */ -void audit_log(struct audit_context *ctx, gfp_t gfp_mask, int type, - const char *fmt, ...) -{ - struct audit_buffer *ab; - va_list args; - - ab = audit_log_start(ctx, gfp_mask, type); - if (ab) { - va_start(args, fmt); - audit_log_vformat(ab, fmt, args); - va_end(args); - audit_log_end(ab); - } -} - -EXPORT_SYMBOL(audit_log_start); -EXPORT_SYMBOL(audit_log_end); -EXPORT_SYMBOL(audit_log_format); -EXPORT_SYMBOL(audit_log); diff --git a/kernel/audit.h b/kernel/audit.h deleted file mode 100644 index 771833d..0000000 --- a/kernel/audit.h +++ /dev/null @@ -1,118 +0,0 @@ -/* audit -- definition of audit_context structure and supporting types - * - * Copyright 2003-2004 Red Hat, Inc. - * Copyright 2005 Hewlett-Packard Development Company, L.P. - * Copyright 2005 IBM Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include - -/* 0 = no checking - 1 = put_count checking - 2 = verbose put_count checking -*/ -#define AUDIT_DEBUG 0 - -/* At task start time, the audit_state is set in the audit_context using - a per-task filter. At syscall entry, the audit_state is augmented by - the syscall filter. */ -enum audit_state { - AUDIT_DISABLED, /* Do not create per-task audit_context. - * No syscall-specific audit records can - * be generated. */ - AUDIT_SETUP_CONTEXT, /* Create the per-task audit_context, - * but don't necessarily fill it in at - * syscall entry time (i.e., filter - * instead). */ - AUDIT_BUILD_CONTEXT, /* Create the per-task audit_context, - * and always fill it in at syscall - * entry time. This makes a full - * syscall record available if some - * other part of the kernel decides it - * should be recorded. */ - AUDIT_RECORD_CONTEXT /* Create the per-task audit_context, - * always fill it in at syscall entry - * time, and always write out the audit - * record at syscall exit time. */ -}; - -/* Rule lists */ -struct audit_parent; - -struct audit_watch { - atomic_t count; /* reference count */ - char *path; /* insertion path */ - dev_t dev; /* associated superblock device */ - unsigned long ino; /* associated inode number */ - struct audit_parent *parent; /* associated parent */ - struct list_head wlist; /* entry in parent->watches list */ - struct list_head rules; /* associated rules */ -}; - -struct audit_field { - u32 type; - u32 val; - u32 op; - char *se_str; - struct selinux_audit_rule *se_rule; -}; - -struct audit_krule { - int vers_ops; - u32 flags; - u32 listnr; - u32 action; - u32 mask[AUDIT_BITMASK_SIZE]; - u32 buflen; /* for data alloc on list rules */ - u32 field_count; - struct audit_field *fields; - struct audit_watch *watch; /* associated watch */ - struct list_head rlist; /* entry in audit_watch.rules list */ -}; - -struct audit_entry { - struct list_head list; - struct rcu_head rcu; - struct audit_krule rule; -}; - - -extern int audit_pid; -extern int audit_comparator(const u32 left, const u32 op, const u32 right); -extern int audit_compare_dname_path(const char *dname, const char *path); -extern struct sk_buff * audit_make_reply(int pid, int seq, int type, - int done, int multi, - void *payload, int size); -extern void audit_send_reply(int pid, int seq, int type, - int done, int multi, - void *payload, int size); -extern void audit_log_lost(const char *message); -extern void audit_panic(const char *message); - -struct audit_netlink_list { - int pid; - struct sk_buff_head q; -}; - -int audit_send_list(void *); - -struct inotify_watch; -extern void audit_handle_ievent(struct inotify_watch *, u32, u32, u32, - const char *, struct inode *); -extern int selinux_audit_rule_update(void); - diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c deleted file mode 100644 index 35dca7e..0000000 --- a/kernel/auditfilter.c +++ /dev/null @@ -1,1466 +0,0 @@ -/* auditfilter.c -- filtering of audit events - * - * Copyright 2003-2004 Red Hat, Inc. - * Copyright 2005 Hewlett-Packard Development Company, L.P. - * Copyright 2005 IBM Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "audit.h" - -/* - * Locking model: - * - * audit_filter_mutex: - * Synchronizes writes and blocking reads of audit's filterlist - * data. Rcu is used to traverse the filterlist and access - * contents of structs audit_entry, audit_watch and opaque - * selinux rules during filtering. If modified, these structures - * must be copied and replace their counterparts in the filterlist. - * An audit_parent struct is not accessed during filtering, so may - * be written directly provided audit_filter_mutex is held. - */ - -/* - * Reference counting: - * - * audit_parent: lifetime is from audit_init_parent() to receipt of an IN_IGNORED - * event. Each audit_watch holds a reference to its associated parent. - * - * audit_watch: if added to lists, lifetime is from audit_init_watch() to one - * of: audit_remove_watch() [user removes], audit_update_watch() [kernel - * replaces], or audit_remove_parent_watches() [kernel removes]. - * Additionally, an audit_watch may exist temporarily to assist in - * searching existing filter data. Each audit_krule holds a reference to - * its associated watch. - */ - -struct audit_parent { - atomic_t count; /* reference count */ - struct list_head ilist; /* entry in inotify registration list */ - struct list_head watches; /* associated watches */ - struct inotify_watch wdata; /* inotify watch data */ - unsigned flags; /* status flags */ -}; - -/* - * audit_parent status flags: - * - * AUDIT_PARENT_INVALID - set anytime rules/watches are auto-removed due to - * a filesystem event. Technically not needed for IN_DELETE_SELF or IN_UNMOUNT - * events, as we cannot receive them while we have nameidata (during rule add) - * and the audit_parent is immediately removed when processing the following - * IN_IGNORED event. The IN_MOVE_SELF event is different. We can receive it - * while holding nameidata, and inotify will not send us the IN_IGNORED so we - * must later remove the inotify watch on audit_parent ourselves. - */ -#define AUDIT_PARENT_INVALID 0x001 - -/* Audit filter lists, defined in */ -struct list_head audit_filter_list[AUDIT_NR_FILTERS] = { - LIST_HEAD_INIT(audit_filter_list[0]), - LIST_HEAD_INIT(audit_filter_list[1]), - LIST_HEAD_INIT(audit_filter_list[2]), - LIST_HEAD_INIT(audit_filter_list[3]), - LIST_HEAD_INIT(audit_filter_list[4]), - LIST_HEAD_INIT(audit_filter_list[5]), -#if AUDIT_NR_FILTERS != 6 -#error Fix audit_filter_list initialiser -#endif -}; - -DEFINE_MUTEX(audit_filter_mutex); - -/* Inotify handle */ -extern struct inotify_handle *audit_ih; - -/* Inotify events we care about. */ -#define AUDIT_IN_WATCH IN_MOVE|IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF -#define AUDIT_IN_SELF IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT - -static inline void audit_get_parent(struct audit_parent *parent) -{ - atomic_inc(&parent->count); -} - -static inline void audit_put_parent(struct audit_parent *parent) -{ - if (atomic_dec_and_test(&parent->count)) { - WARN_ON(!list_empty(&parent->watches)); - kfree(parent); - } -} - -static inline void audit_get_watch(struct audit_watch *watch) -{ - atomic_inc(&watch->count); -} - -static inline void audit_put_watch(struct audit_watch *watch) -{ - if (atomic_dec_and_test(&watch->count)) { - WARN_ON(!list_empty(&watch->rules)); - /* watches that were never added don't have a parent */ - if (watch->parent) - audit_put_parent(watch->parent); - kfree(watch->path); - kfree(watch); - } -} - -static inline void audit_free_rule(struct audit_entry *e) -{ - int i; - - /* some rules don't have associated watches */ - if (e->rule.watch) - audit_put_watch(e->rule.watch); - if (e->rule.fields) - for (i = 0; i < e->rule.field_count; i++) { - struct audit_field *f = &e->rule.fields[i]; - kfree(f->se_str); - selinux_audit_rule_free(f->se_rule); - } - kfree(e->rule.fields); - kfree(e); -} - -static inline void audit_free_rule_rcu(struct rcu_head *head) -{ - struct audit_entry *e = container_of(head, struct audit_entry, rcu); - audit_free_rule(e); -} - -/* Initialize a parent watch entry. */ -static inline struct audit_parent *audit_init_parent(void) -{ - struct audit_parent *parent; - - parent = kzalloc(sizeof(*parent), GFP_KERNEL); - if (unlikely(!parent)) - return ERR_PTR(-ENOMEM); - - INIT_LIST_HEAD(&parent->watches); - atomic_set(&parent->count, 1); - parent->flags = 0; - - return parent; -} - -/* Initialize a watch entry. */ -static inline struct audit_watch *audit_init_watch(char *path) -{ - struct audit_watch *watch; - - watch = kzalloc(sizeof(*watch), GFP_KERNEL); - if (unlikely(!watch)) - return ERR_PTR(-ENOMEM); - - INIT_LIST_HEAD(&watch->rules); - atomic_set(&watch->count, 1); - watch->path = path; - watch->dev = (dev_t)-1; - watch->ino = (unsigned long)-1; - - return watch; -} - -/* Initialize an audit filterlist entry. */ -static inline struct audit_entry *audit_init_entry(u32 field_count) -{ - struct audit_entry *entry; - struct audit_field *fields; - - entry = kzalloc(sizeof(*entry), GFP_KERNEL); - if (unlikely(!entry)) - return NULL; - - fields = kzalloc(sizeof(*fields) * field_count, GFP_KERNEL); - if (unlikely(!fields)) { - kfree(entry); - return NULL; - } - entry->rule.fields = fields; - - return entry; -} - -/* Unpack a filter field's string representation from user-space - * buffer. */ -static char *audit_unpack_string(void **bufp, size_t *remain, size_t len) -{ - char *str; - - if (!*bufp || (len == 0) || (len > *remain)) - return ERR_PTR(-EINVAL); - - /* Of the currently implemented string fields, PATH_MAX - * defines the longest valid length. - */ - if (len > PATH_MAX) - return ERR_PTR(-ENAMETOOLONG); - - str = kmalloc(len + 1, GFP_KERNEL); - if (unlikely(!str)) - return ERR_PTR(-ENOMEM); - - memcpy(str, *bufp, len); - str[len] = 0; - *bufp += len; - *remain -= len; - - return str; -} - -/* Translate a watch string to kernel respresentation. */ -static int audit_to_watch(struct audit_krule *krule, char *path, int len, - u32 op) -{ - struct audit_watch *watch; - - if (path[0] != '/' || path[len-1] == '/' || - krule->listnr != AUDIT_FILTER_EXIT || - op & ~AUDIT_EQUAL || - krule->watch) /* allow only 1 watch per rule */ - return -EINVAL; - - /* ensure inotify handle was initialized */ - if (!audit_ih) - return -EOPNOTSUPP; - - watch = audit_init_watch(path); - if (unlikely(IS_ERR(watch))) - return PTR_ERR(watch); - - audit_get_watch(watch); - krule->watch = watch; - - return 0; -} - -/* Common user-space to kernel rule translation. */ -static inline struct audit_entry *audit_to_entry_common(struct audit_rule *rule) -{ - unsigned listnr; - struct audit_entry *entry; - int i, err; - - err = -EINVAL; - listnr = rule->flags & ~AUDIT_FILTER_PREPEND; - switch(listnr) { - default: - goto exit_err; - case AUDIT_FILTER_USER: - case AUDIT_FILTER_TYPE: -#ifdef CONFIG_AUDITSYSCALL - case AUDIT_FILTER_ENTRY: - case AUDIT_FILTER_EXIT: - case AUDIT_FILTER_TASK: -#endif - ; - } - if (rule->action != AUDIT_NEVER && rule->action != AUDIT_POSSIBLE && - rule->action != AUDIT_ALWAYS) - goto exit_err; - if (rule->field_count > AUDIT_MAX_FIELDS) - goto exit_err; - - err = -ENOMEM; - entry = audit_init_entry(rule->field_count); - if (!entry) - goto exit_err; - - entry->rule.flags = rule->flags & AUDIT_FILTER_PREPEND; - entry->rule.listnr = listnr; - entry->rule.action = rule->action; - entry->rule.field_count = rule->field_count; - - for (i = 0; i < AUDIT_BITMASK_SIZE; i++) - entry->rule.mask[i] = rule->mask[i]; - - return entry; - -exit_err: - return ERR_PTR(err); -} - -/* Translate struct audit_rule to kernel's rule respresentation. - * Exists for backward compatibility with userspace. */ -static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule) -{ - struct audit_entry *entry; - int err = 0; - int i; - - entry = audit_to_entry_common(rule); - if (IS_ERR(entry)) - goto exit_nofree; - - for (i = 0; i < rule->field_count; i++) { - struct audit_field *f = &entry->rule.fields[i]; - - f->op = rule->fields[i] & (AUDIT_NEGATE|AUDIT_OPERATORS); - f->type = rule->fields[i] & ~(AUDIT_NEGATE|AUDIT_OPERATORS); - f->val = rule->values[i]; - - if (f->type & AUDIT_UNUSED_BITS || - f->type == AUDIT_SE_USER || - f->type == AUDIT_SE_ROLE || - f->type == AUDIT_SE_TYPE || - f->type == AUDIT_SE_SEN || - f->type == AUDIT_SE_CLR || - f->type == AUDIT_WATCH) { - err = -EINVAL; - goto exit_free; - } - - entry->rule.vers_ops = (f->op & AUDIT_OPERATORS) ? 2 : 1; - - /* Support for legacy operators where - * AUDIT_NEGATE bit signifies != and otherwise assumes == */ - if (f->op & AUDIT_NEGATE) - f->op = AUDIT_NOT_EQUAL; - else if (!f->op) - f->op = AUDIT_EQUAL; - else if (f->op == AUDIT_OPERATORS) { - err = -EINVAL; - goto exit_free; - } - } - -exit_nofree: - return entry; - -exit_free: - audit_free_rule(entry); - return ERR_PTR(err); -} - -/* Translate struct audit_rule_data to kernel's rule respresentation. */ -static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data, - size_t datasz) -{ - int err = 0; - struct audit_entry *entry; - void *bufp; - size_t remain = datasz - sizeof(struct audit_rule_data); - int i; - char *str; - - entry = audit_to_entry_common((struct audit_rule *)data); - if (IS_ERR(entry)) - goto exit_nofree; - - bufp = data->buf; - entry->rule.vers_ops = 2; - for (i = 0; i < data->field_count; i++) { - struct audit_field *f = &entry->rule.fields[i]; - - err = -EINVAL; - if (!(data->fieldflags[i] & AUDIT_OPERATORS) || - data->fieldflags[i] & ~AUDIT_OPERATORS) - goto exit_free; - - f->op = data->fieldflags[i] & AUDIT_OPERATORS; - f->type = data->fields[i]; - f->val = data->values[i]; - f->se_str = NULL; - f->se_rule = NULL; - switch(f->type) { - case AUDIT_SE_USER: - case AUDIT_SE_ROLE: - case AUDIT_SE_TYPE: - case AUDIT_SE_SEN: - case AUDIT_SE_CLR: - str = audit_unpack_string(&bufp, &remain, f->val); - if (IS_ERR(str)) - goto exit_free; - entry->rule.buflen += f->val; - - err = selinux_audit_rule_init(f->type, f->op, str, - &f->se_rule); - /* Keep currently invalid fields around in case they - * become valid after a policy reload. */ - if (err == -EINVAL) { - printk(KERN_WARNING "audit rule for selinux " - "\'%s\' is invalid\n", str); - err = 0; - } - if (err) { - kfree(str); - goto exit_free; - } else - f->se_str = str; - break; - case AUDIT_WATCH: - str = audit_unpack_string(&bufp, &remain, f->val); - if (IS_ERR(str)) - goto exit_free; - entry->rule.buflen += f->val; - - err = audit_to_watch(&entry->rule, str, f->val, f->op); - if (err) { - kfree(str); - goto exit_free; - } - break; - } - } - -exit_nofree: - return entry; - -exit_free: - audit_free_rule(entry); - return ERR_PTR(err); -} - -/* Pack a filter field's string representation into data block. */ -static inline size_t audit_pack_string(void **bufp, char *str) -{ - size_t len = strlen(str); - - memcpy(*bufp, str, len); - *bufp += len; - - return len; -} - -/* Translate kernel rule respresentation to struct audit_rule. - * Exists for backward compatibility with userspace. */ -static struct audit_rule *audit_krule_to_rule(struct audit_krule *krule) -{ - struct audit_rule *rule; - int i; - - rule = kmalloc(sizeof(*rule), GFP_KERNEL); - if (unlikely(!rule)) - return ERR_PTR(-ENOMEM); - memset(rule, 0, sizeof(*rule)); - - rule->flags = krule->flags | krule->listnr; - rule->action = krule->action; - rule->field_count = krule->field_count; - for (i = 0; i < rule->field_count; i++) { - rule->values[i] = krule->fields[i].val; - rule->fields[i] = krule->fields[i].type; - - if (krule->vers_ops == 1) { - if (krule->fields[i].op & AUDIT_NOT_EQUAL) - rule->fields[i] |= AUDIT_NEGATE; - } else { - rule->fields[i] |= krule->fields[i].op; - } - } - for (i = 0; i < AUDIT_BITMASK_SIZE; i++) rule->mask[i] = krule->mask[i]; - - return rule; -} - -/* Translate kernel rule respresentation to struct audit_rule_data. */ -static struct audit_rule_data *audit_krule_to_data(struct audit_krule *krule) -{ - struct audit_rule_data *data; - void *bufp; - int i; - - data = kmalloc(sizeof(*data) + krule->buflen, GFP_KERNEL); - if (unlikely(!data)) - return ERR_PTR(-ENOMEM); - memset(data, 0, sizeof(*data)); - - data->flags = krule->flags | krule->listnr; - data->action = krule->action; - data->field_count = krule->field_count; - bufp = data->buf; - for (i = 0; i < data->field_count; i++) { - struct audit_field *f = &krule->fields[i]; - - data->fields[i] = f->type; - data->fieldflags[i] = f->op; - switch(f->type) { - case AUDIT_SE_USER: - case AUDIT_SE_ROLE: - case AUDIT_SE_TYPE: - case AUDIT_SE_SEN: - case AUDIT_SE_CLR: - data->buflen += data->values[i] = - audit_pack_string(&bufp, f->se_str); - break; - case AUDIT_WATCH: - data->buflen += data->values[i] = - audit_pack_string(&bufp, krule->watch->path); - break; - default: - data->values[i] = f->val; - } - } - for (i = 0; i < AUDIT_BITMASK_SIZE; i++) data->mask[i] = krule->mask[i]; - - return data; -} - -/* Compare two rules in kernel format. Considered success if rules - * don't match. */ -static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b) -{ - int i; - - if (a->flags != b->flags || - a->listnr != b->listnr || - a->action != b->action || - a->field_count != b->field_count) - return 1; - - for (i = 0; i < a->field_count; i++) { - if (a->fields[i].type != b->fields[i].type || - a->fields[i].op != b->fields[i].op) - return 1; - - switch(a->fields[i].type) { - case AUDIT_SE_USER: - case AUDIT_SE_ROLE: - case AUDIT_SE_TYPE: - case AUDIT_SE_SEN: - case AUDIT_SE_CLR: - if (strcmp(a->fields[i].se_str, b->fields[i].se_str)) - return 1; - break; - case AUDIT_WATCH: - if (strcmp(a->watch->path, b->watch->path)) - return 1; - break; - default: - if (a->fields[i].val != b->fields[i].val) - return 1; - } - } - - for (i = 0; i < AUDIT_BITMASK_SIZE; i++) - if (a->mask[i] != b->mask[i]) - return 1; - - return 0; -} - -/* Duplicate the given audit watch. The new watch's rules list is initialized - * to an empty list and wlist is undefined. */ -static inline struct audit_watch *audit_dupe_watch(struct audit_watch *old) -{ - char *path; - struct audit_watch *new; - - path = kstrdup(old->path, GFP_KERNEL); - if (unlikely(!path)) - return ERR_PTR(-ENOMEM); - - new = audit_init_watch(path); - if (unlikely(IS_ERR(new))) { - kfree(path); - goto out; - } - - new->dev = old->dev; - new->ino = old->ino; - audit_get_parent(old->parent); - new->parent = old->parent; - -out: - return new; -} - -/* Duplicate selinux field information. The se_rule is opaque, so must be - * re-initialized. */ -static inline int audit_dupe_selinux_field(struct audit_field *df, - struct audit_field *sf) -{ - int ret = 0; - char *se_str; - - /* our own copy of se_str */ - se_str = kstrdup(sf->se_str, GFP_KERNEL); - if (unlikely(IS_ERR(se_str))) - return -ENOMEM; - df->se_str = se_str; - - /* our own (refreshed) copy of se_rule */ - ret = selinux_audit_rule_init(df->type, df->op, df->se_str, - &df->se_rule); - /* Keep currently invalid fields around in case they - * become valid after a policy reload. */ - if (ret == -EINVAL) { - printk(KERN_WARNING "audit rule for selinux \'%s\' is " - "invalid\n", df->se_str); - ret = 0; - } - - return ret; -} - -/* Duplicate an audit rule. This will be a deep copy with the exception - * of the watch - that pointer is carried over. The selinux specific fields - * will be updated in the copy. The point is to be able to replace the old - * rule with the new rule in the filterlist, then free the old rule. - * The rlist element is undefined; list manipulations are handled apart from - * the initial copy. */ -static struct audit_entry *audit_dupe_rule(struct audit_krule *old, - struct audit_watch *watch) -{ - u32 fcount = old->field_count; - struct audit_entry *entry; - struct audit_krule *new; - int i, err = 0; - - entry = audit_init_entry(fcount); - if (unlikely(!entry)) - return ERR_PTR(-ENOMEM); - - new = &entry->rule; - new->vers_ops = old->vers_ops; - new->flags = old->flags; - new->listnr = old->listnr; - new->action = old->action; - for (i = 0; i < AUDIT_BITMASK_SIZE; i++) - new->mask[i] = old->mask[i]; - new->buflen = old->buflen; - new->watch = NULL; - new->field_count = old->field_count; - memcpy(new->fields, old->fields, sizeof(struct audit_field) * fcount); - - /* deep copy this information, updating the se_rule fields, because - * the originals will all be freed when the old rule is freed. */ - for (i = 0; i < fcount; i++) { - switch (new->fields[i].type) { - case AUDIT_SE_USER: - case AUDIT_SE_ROLE: - case AUDIT_SE_TYPE: - case AUDIT_SE_SEN: - case AUDIT_SE_CLR: - err = audit_dupe_selinux_field(&new->fields[i], - &old->fields[i]); - } - if (err) { - audit_free_rule(entry); - return ERR_PTR(err); - } - } - - if (watch) { - audit_get_watch(watch); - new->watch = watch; - } - - return entry; -} - -/* Update inode info in audit rules based on filesystem event. */ -static inline void audit_update_watch(struct audit_parent *parent, - const char *dname, dev_t dev, - unsigned long ino) -{ - struct audit_watch *owatch, *nwatch, *nextw; - struct audit_krule *r, *nextr; - struct audit_entry *oentry, *nentry; - struct audit_buffer *ab; - - mutex_lock(&audit_filter_mutex); - list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) { - if (audit_compare_dname_path(dname, owatch->path)) - continue; - - nwatch = audit_dupe_watch(owatch); - if (unlikely(IS_ERR(nwatch))) { - mutex_unlock(&audit_filter_mutex); - audit_panic("error updating watch, skipping"); - return; - } - nwatch->dev = dev; - nwatch->ino = ino; - - list_for_each_entry_safe(r, nextr, &owatch->rules, rlist) { - oentry = container_of(r, struct audit_entry, rule); - - nentry = audit_dupe_rule(&oentry->rule, nwatch); - if (unlikely(IS_ERR(nentry))) { - audit_panic("error updating watch, removing"); - list_del(&oentry->rule.rlist); - list_del_rcu(&oentry->list); - } else { - list_add(&nentry->rule.rlist, &nwatch->rules); - list_del(&oentry->rule.rlist); - list_replace_rcu(&oentry->list, &nentry->list); - } - call_rcu(&oentry->rcu, audit_free_rule_rcu); - } - - ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE); - audit_log_format(ab, "audit updated rules specifying watch="); - audit_log_untrustedstring(ab, owatch->path); - audit_log_format(ab, " with dev=%u ino=%lu\n", dev, ino); - audit_log_end(ab); - - list_del(&owatch->wlist); - audit_put_watch(owatch); /* matches initial get */ - goto add_watch_to_parent; /* event applies to a single watch */ - } - mutex_unlock(&audit_filter_mutex); - return; - -add_watch_to_parent: - list_add(&nwatch->wlist, &parent->watches); - mutex_unlock(&audit_filter_mutex); - return; -} - -/* Remove all watches & rules associated with a parent that is going away. */ -static inline void audit_remove_parent_watches(struct audit_parent *parent) -{ - struct audit_watch *w, *nextw; - struct audit_krule *r, *nextr; - struct audit_entry *e; - - mutex_lock(&audit_filter_mutex); - parent->flags |= AUDIT_PARENT_INVALID; - list_for_each_entry_safe(w, nextw, &parent->watches, wlist) { - list_for_each_entry_safe(r, nextr, &w->rules, rlist) { - e = container_of(r, struct audit_entry, rule); - list_del(&r->rlist); - list_del_rcu(&e->list); - call_rcu(&e->rcu, audit_free_rule_rcu); - - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "audit implicitly removed rule from list=%d\n", - AUDIT_FILTER_EXIT); - } - list_del(&w->wlist); - audit_put_watch(w); /* matches initial get */ - } - mutex_unlock(&audit_filter_mutex); -} - -/* Register inotify watches for parents on in_list. */ -static int audit_inotify_register(struct nameidata *nd, - struct list_head *in_list) -{ - struct audit_parent *p; - s32 wd; - int ret = 0; - - list_for_each_entry(p, in_list, ilist) { - wd = inotify_add_watch(audit_ih, &p->wdata, nd->dentry->d_inode, - AUDIT_IN_WATCH); - if (wd < 0) { - audit_remove_parent_watches(p); - /* the put matching the get in audit_init_parent() */ - audit_put_parent(p); - /* save the first error for return value */ - if (!ret) - ret = wd; - } - } - - return ret; -} - -/* Unregister inotify watches for parents on in_list. - * Generates an IN_IGNORED event. */ -static void audit_inotify_unregister(struct list_head *in_list) -{ - struct audit_parent *p; - - list_for_each_entry(p, in_list, ilist) { - inotify_rm_watch(audit_ih, &p->wdata); - /* the put matching the get in audit_remove_watch() */ - audit_put_parent(p); - } -} - -/* Get path information necessary for adding watches. */ -static int audit_get_nd(char *path, struct nameidata **ndp, - struct nameidata **ndw) -{ - struct nameidata *ndparent, *ndwatch; - int err; - - ndparent = kmalloc(sizeof(*ndparent), GFP_KERNEL); - if (unlikely(!ndparent)) - return -ENOMEM; - - ndwatch = kmalloc(sizeof(*ndwatch), GFP_KERNEL); - if (unlikely(!ndwatch)) { - kfree(ndparent); - return -ENOMEM; - } - - err = path_lookup(path, LOOKUP_PARENT, ndparent); - if (err) { - kfree(ndparent); - kfree(ndwatch); - return err; - } - - err = path_lookup(path, 0, ndwatch); - if (err) { - kfree(ndwatch); - ndwatch = NULL; - } - - *ndp = ndparent; - *ndw = ndwatch; - - return 0; -} - -/* Release resources used for watch path information. */ -static inline void audit_put_nd(struct nameidata *ndp, struct nameidata *ndw) -{ - if (ndp) { - path_release(ndp); - kfree(ndp); - } - if (ndw) { - path_release(ndw); - kfree(ndw); - } -} - -/* Add a parent inotify_watch for the given rule. */ -static int audit_add_parent(struct audit_krule *krule, - struct list_head *inotify_list) -{ - struct audit_parent *parent; - struct audit_watch *watch = krule->watch; - - parent = audit_init_parent(); - if (IS_ERR(parent)) - return PTR_ERR(parent); - - audit_get_parent(parent); - watch->parent = parent; - - /* krule, watch and parent have not been added to any global - * lists, so we don't need to take audit_filter_mutex. */ - list_add(&watch->wlist, &parent->watches); - list_add(&krule->rlist, &watch->rules); - - /* add parent to inotify registration list */ - list_add(&parent->ilist, inotify_list); - - return 0; -} - -/* Associate the given rule with an existing parent inotify_watch. - * Caller must hold audit_filter_mutex. */ -static int audit_add_to_parent(struct audit_krule *krule, - struct inotify_watch *iwatch) -{ - struct audit_parent *parent; - struct audit_watch *w, *watch = krule->watch; - int watch_found = 0; - - parent = container_of(iwatch, struct audit_parent, wdata); - - /* parent was moved before we took audit_filter_mutex */ - if (parent->flags & AUDIT_PARENT_INVALID) - return -ENOENT; - - list_for_each_entry(w, &parent->watches, wlist) { - if (strcmp(watch->path, w->path)) - continue; - - watch_found = 1; - - /* put krule's and initial refs to temporary watch */ - audit_put_watch(watch); - audit_put_watch(watch); - - audit_get_watch(w); - krule->watch = watch = w; - break; - } - - if (!watch_found) { - audit_get_parent(parent); - watch->parent = parent; - - list_add(&watch->wlist, &parent->watches); - } - - list_add(&krule->rlist, &watch->rules); - - return 0; -} - -/* Find a matching watch entry, or add this one. - * Caller must hold audit_filter_mutex. */ -static int audit_add_watch(struct audit_krule *krule, struct nameidata *ndp, - struct nameidata *ndw, - struct list_head *inotify_list) -{ - struct audit_watch *watch = krule->watch; - struct inotify_watch *iwatch; - int ret; - - /* update watch filter fields */ - if (ndw) { - watch->dev = ndw->dentry->d_inode->i_sb->s_dev; - watch->ino = ndw->dentry->d_inode->i_ino; - } - - /* The audit_filter_mutex must not be held during inotify calls because - * we hold it during inotify event callback processing. - * We can trust iwatch to stick around because we hold nameidata (ndp). */ - mutex_unlock(&audit_filter_mutex); - - if (inotify_find_watch(audit_ih, ndp->dentry->d_inode, &iwatch) < 0) { - ret = audit_add_parent(krule, inotify_list); - mutex_lock(&audit_filter_mutex); - } else { - mutex_lock(&audit_filter_mutex); - ret = audit_add_to_parent(krule, iwatch); - } - - return ret; -} - -/* Add rule to given filterlist if not a duplicate. */ -static inline int audit_add_rule(struct audit_entry *entry, - struct list_head *list) -{ - struct audit_entry *e; - struct audit_watch *watch = entry->rule.watch; - struct nameidata *ndp, *ndw; - LIST_HEAD(inotify_list); - int err; - - /* Taking audit_filter_mutex protects from stale rule data. */ - mutex_lock(&audit_filter_mutex); - list_for_each_entry(e, list, list) { - if (!audit_compare_rule(&entry->rule, &e->rule)) { - err = -EEXIST; - mutex_unlock(&audit_filter_mutex); - goto error; - } - } - mutex_unlock(&audit_filter_mutex); - - /* Avoid calling path_lookup under audit_filter_mutex. */ - if (watch) { - err = audit_get_nd(watch->path, &ndp, &ndw); - if (err) - goto error; - } - - mutex_lock(&audit_filter_mutex); - if (watch) { - /* audit_filter_mutex is dropped and re-taken during this call */ - err = audit_add_watch(&entry->rule, ndp, ndw, &inotify_list); - if (err) { - audit_put_nd(ndp, ndw); - goto error; - } - } - - if (entry->rule.flags & AUDIT_FILTER_PREPEND) { - list_add_rcu(&entry->list, list); - } else { - list_add_tail_rcu(&entry->list, list); - } - mutex_unlock(&audit_filter_mutex); - - if (!list_empty(&inotify_list)) { - err = audit_inotify_register(ndp, &inotify_list); - if (err) - goto error; - audit_put_nd(ndp, ndw); - } - - return 0; - -error: - if (watch) - audit_put_watch(watch); /* tmp watch, matches initial get */ - return err; -} - -/* Remove given krule from its associated watch's rules list and clean up any - * last instances of associated watch and parent. - * Caller must hold audit_filter_mutex. */ -static inline void audit_remove_watch(struct audit_krule *krule, - struct list_head *in_list) -{ - struct audit_watch *watch = krule->watch; - struct audit_parent *parent = watch->parent; - - list_del(&krule->rlist); - if (list_empty(&watch->rules)) { - list_del(&watch->wlist); - audit_put_watch(watch); /* matches initial get */ - - if (list_empty(&parent->watches)) { - /* Put parent on the inotify un-registration list. - * Grab a reference before releasing audit_filter_mutex, - * to be released in audit_inotify_unregister(). */ - list_add(&parent->ilist, in_list); - audit_get_parent(parent); - } - } -} - -/* Remove an existing rule from filterlist. */ -static inline int audit_del_rule(struct audit_entry *entry, - struct list_head *list) -{ - struct audit_entry *e; - LIST_HEAD(inotify_list); - - mutex_lock(&audit_filter_mutex); - list_for_each_entry(e, list, list) { - if (audit_compare_rule(&entry->rule, &e->rule)) - continue; - - if (e->rule.watch) { - audit_remove_watch(&e->rule, &inotify_list); - /* match initial get for tmp watch */ - audit_put_watch(entry->rule.watch); - } - - list_del_rcu(&e->list); - call_rcu(&e->rcu, audit_free_rule_rcu); - mutex_unlock(&audit_filter_mutex); - - if (!list_empty(&inotify_list)) - audit_inotify_unregister(&inotify_list); - - return 0; - } - mutex_unlock(&audit_filter_mutex); - /* match initial get for tmp watch */ - if (entry->rule.watch) - audit_put_watch(entry->rule.watch); - return -ENOENT; /* No matching rule */ -} - -/* List rules using struct audit_rule. Exists for backward - * compatibility with userspace. */ -static void audit_list(int pid, int seq, struct sk_buff_head *q) -{ - struct sk_buff *skb; - struct audit_entry *entry; - int i; - - /* This is a blocking read, so use audit_filter_mutex instead of rcu - * iterator to sync with list writers. */ - for (i=0; irule); - if (unlikely(!rule)) - break; - skb = audit_make_reply(pid, seq, AUDIT_LIST, 0, 1, - rule, sizeof(*rule)); - if (skb) - skb_queue_tail(q, skb); - kfree(rule); - } - } - skb = audit_make_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0); - if (skb) - skb_queue_tail(q, skb); -} - -/* List rules using struct audit_rule_data. */ -static void audit_list_rules(int pid, int seq, struct sk_buff_head *q) -{ - struct sk_buff *skb; - struct audit_entry *e; - int i; - - /* This is a blocking read, so use audit_filter_mutex instead of rcu - * iterator to sync with list writers. */ - for (i=0; irule); - if (unlikely(!data)) - break; - skb = audit_make_reply(pid, seq, AUDIT_LIST_RULES, 0, 1, - data, sizeof(*data) + data->buflen); - if (skb) - skb_queue_tail(q, skb); - kfree(data); - } - } - skb = audit_make_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0); - if (skb) - skb_queue_tail(q, skb); -} - -/** - * audit_receive_filter - apply all rules to the specified message type - * @type: audit message type - * @pid: target pid for netlink audit messages - * @uid: target uid for netlink audit messages - * @seq: netlink audit message sequence (serial) number - * @data: payload data - * @datasz: size of payload data - * @loginuid: loginuid of sender - * @sid: SE Linux Security ID of sender - */ -int audit_receive_filter(int type, int pid, int uid, int seq, void *data, - size_t datasz, uid_t loginuid, u32 sid) -{ - struct task_struct *tsk; - struct audit_netlink_list *dest; - int err = 0; - struct audit_entry *entry; - - switch (type) { - case AUDIT_LIST: - case AUDIT_LIST_RULES: - /* We can't just spew out the rules here because we might fill - * the available socket buffer space and deadlock waiting for - * auditctl to read from it... which isn't ever going to - * happen if we're actually running in the context of auditctl - * trying to _send_ the stuff */ - - dest = kmalloc(sizeof(struct audit_netlink_list), GFP_KERNEL); - if (!dest) - return -ENOMEM; - dest->pid = pid; - skb_queue_head_init(&dest->q); - - mutex_lock(&audit_filter_mutex); - if (type == AUDIT_LIST) - audit_list(pid, seq, &dest->q); - else - audit_list_rules(pid, seq, &dest->q); - mutex_unlock(&audit_filter_mutex); - - tsk = kthread_run(audit_send_list, dest, "audit_send_list"); - if (IS_ERR(tsk)) { - skb_queue_purge(&dest->q); - kfree(dest); - err = PTR_ERR(tsk); - } - break; - case AUDIT_ADD: - case AUDIT_ADD_RULE: - if (type == AUDIT_ADD) - entry = audit_rule_to_entry(data); - else - entry = audit_data_to_entry(data, datasz); - if (IS_ERR(entry)) - return PTR_ERR(entry); - - err = audit_add_rule(entry, - &audit_filter_list[entry->rule.listnr]); - - if (sid) { - char *ctx = NULL; - u32 len; - if (selinux_ctxid_to_string(sid, &ctx, &len)) { - /* Maybe call audit_panic? */ - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "auid=%u ssid=%u add rule to list=%d res=%d", - loginuid, sid, entry->rule.listnr, !err); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "auid=%u subj=%s add rule to list=%d res=%d", - loginuid, ctx, entry->rule.listnr, !err); - kfree(ctx); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "auid=%u add rule to list=%d res=%d", - loginuid, entry->rule.listnr, !err); - - if (err) - audit_free_rule(entry); - break; - case AUDIT_DEL: - case AUDIT_DEL_RULE: - if (type == AUDIT_DEL) - entry = audit_rule_to_entry(data); - else - entry = audit_data_to_entry(data, datasz); - if (IS_ERR(entry)) - return PTR_ERR(entry); - - err = audit_del_rule(entry, - &audit_filter_list[entry->rule.listnr]); - - if (sid) { - char *ctx = NULL; - u32 len; - if (selinux_ctxid_to_string(sid, &ctx, &len)) { - /* Maybe call audit_panic? */ - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "auid=%u ssid=%u remove rule from list=%d res=%d", - loginuid, sid, entry->rule.listnr, !err); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "auid=%u subj=%s remove rule from list=%d res=%d", - loginuid, ctx, entry->rule.listnr, !err); - kfree(ctx); - } else - audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, - "auid=%u remove rule from list=%d res=%d", - loginuid, entry->rule.listnr, !err); - - audit_free_rule(entry); - break; - default: - return -EINVAL; - } - - return err; -} - -int audit_comparator(const u32 left, const u32 op, const u32 right) -{ - switch (op) { - case AUDIT_EQUAL: - return (left == right); - case AUDIT_NOT_EQUAL: - return (left != right); - case AUDIT_LESS_THAN: - return (left < right); - case AUDIT_LESS_THAN_OR_EQUAL: - return (left <= right); - case AUDIT_GREATER_THAN: - return (left > right); - case AUDIT_GREATER_THAN_OR_EQUAL: - return (left >= right); - } - BUG(); - return 0; -} - -/* Compare given dentry name with last component in given path, - * return of 0 indicates a match. */ -int audit_compare_dname_path(const char *dname, const char *path) -{ - int dlen, plen; - const char *p; - - if (!dname || !path) - return 1; - - dlen = strlen(dname); - plen = strlen(path); - if (plen < dlen) - return 1; - - /* disregard trailing slashes */ - p = path + plen - 1; - while ((*p == '/') && (p > path)) - p--; - - /* find last path component */ - p = p - dlen + 1; - if (p < path) - return 1; - else if (p > path) { - if (*--p != '/') - return 1; - else - p++; - } - - return strncmp(p, dname, dlen); -} - -static int audit_filter_user_rules(struct netlink_skb_parms *cb, - struct audit_krule *rule, - enum audit_state *state) -{ - int i; - - for (i = 0; i < rule->field_count; i++) { - struct audit_field *f = &rule->fields[i]; - int result = 0; - - switch (f->type) { - case AUDIT_PID: - result = audit_comparator(cb->creds.pid, f->op, f->val); - break; - case AUDIT_UID: - result = audit_comparator(cb->creds.uid, f->op, f->val); - break; - case AUDIT_GID: - result = audit_comparator(cb->creds.gid, f->op, f->val); - break; - case AUDIT_LOGINUID: - result = audit_comparator(cb->loginuid, f->op, f->val); - break; - } - - if (!result) - return 0; - } - switch (rule->action) { - case AUDIT_NEVER: *state = AUDIT_DISABLED; break; - case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break; - case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break; - } - return 1; -} - -int audit_filter_user(struct netlink_skb_parms *cb, int type) -{ - struct audit_entry *e; - enum audit_state state; - int ret = 1; - - rcu_read_lock(); - list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) { - if (audit_filter_user_rules(cb, &e->rule, &state)) { - if (state == AUDIT_DISABLED) - ret = 0; - break; - } - } - rcu_read_unlock(); - - return ret; /* Audit by default */ -} - -int audit_filter_type(int type) -{ - struct audit_entry *e; - int result = 0; - - rcu_read_lock(); - if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE])) - goto unlock_and_return; - - list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE], - list) { - int i; - for (i = 0; i < e->rule.field_count; i++) { - struct audit_field *f = &e->rule.fields[i]; - if (f->type == AUDIT_MSGTYPE) { - result = audit_comparator(type, f->op, f->val); - if (!result) - break; - } - } - if (result) - goto unlock_and_return; - } -unlock_and_return: - rcu_read_unlock(); - return result; -} - -/* Check to see if the rule contains any selinux fields. Returns 1 if there - are selinux fields specified in the rule, 0 otherwise. */ -static inline int audit_rule_has_selinux(struct audit_krule *rule) -{ - int i; - - for (i = 0; i < rule->field_count; i++) { - struct audit_field *f = &rule->fields[i]; - switch (f->type) { - case AUDIT_SE_USER: - case AUDIT_SE_ROLE: - case AUDIT_SE_TYPE: - case AUDIT_SE_SEN: - case AUDIT_SE_CLR: - return 1; - } - } - - return 0; -} - -/* This function will re-initialize the se_rule field of all applicable rules. - * It will traverse the filter lists serarching for rules that contain selinux - * specific filter fields. When such a rule is found, it is copied, the - * selinux field is re-initialized, and the old rule is replaced with the - * updated rule. */ -int selinux_audit_rule_update(void) -{ - struct audit_entry *entry, *n, *nentry; - struct audit_watch *watch; - int i, err = 0; - - /* audit_filter_mutex synchronizes the writers */ - mutex_lock(&audit_filter_mutex); - - for (i = 0; i < AUDIT_NR_FILTERS; i++) { - list_for_each_entry_safe(entry, n, &audit_filter_list[i], list) { - if (!audit_rule_has_selinux(&entry->rule)) - continue; - - watch = entry->rule.watch; - nentry = audit_dupe_rule(&entry->rule, watch); - if (unlikely(IS_ERR(nentry))) { - /* save the first error encountered for the - * return value */ - if (!err) - err = PTR_ERR(nentry); - audit_panic("error updating selinux filters"); - if (watch) - list_del(&entry->rule.rlist); - list_del_rcu(&entry->list); - } else { - if (watch) { - list_add(&nentry->rule.rlist, - &watch->rules); - list_del(&entry->rule.rlist); - } - list_replace_rcu(&entry->list, &nentry->list); - } - call_rcu(&entry->rcu, audit_free_rule_rcu); - } - } - - mutex_unlock(&audit_filter_mutex); - - return err; -} - -/* Update watch data in audit rules based on inotify events. */ -void audit_handle_ievent(struct inotify_watch *iwatch, u32 wd, u32 mask, - u32 cookie, const char *dname, struct inode *inode) -{ - struct audit_parent *parent = container_of(iwatch, struct audit_parent, wdata); - - if (mask & (IN_CREATE|IN_MOVED_TO) && inode) - audit_update_watch(parent, dname, inode->i_sb->s_dev, - inode->i_ino); - else if (mask & (IN_DELETE|IN_MOVED_FROM)) - audit_update_watch(parent, dname, (dev_t)-1, (unsigned long)-1); - /* Note: Inotify doesn't remove the watch for the IN_MOVE_SELF event. - * Work around this by leaving the parent around with an empty - * watchlist. It will be re-used if new watches are added. */ - else if (mask & (AUDIT_IN_SELF)) - audit_remove_parent_watches(parent); - else if (mask & IN_IGNORED) - audit_put_parent(parent); /* match get in audit_init_parent() */ -} diff --git a/kernel/auditsc.c b/kernel/auditsc.c deleted file mode 100644 index 43512c1..0000000 --- a/kernel/auditsc.c +++ /dev/null @@ -1,1401 +0,0 @@ -/* auditsc.c -- System-call auditing support - * Handles all system-call specific auditing features. - * - * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina. - * Copyright 2005 Hewlett-Packard Development Company, L.P. - * Copyright (C) 2005 IBM Corporation - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Written by Rickard E. (Rik) Faith - * - * Many of the ideas implemented here are from Stephen C. Tweedie, - * especially the idea of avoiding a copy by using getname. - * - * The method for actual interception of syscall entry and exit (not in - * this file -- see entry.S) is based on a GPL'd patch written by - * okir@suse.de and Copyright 2003 SuSE Linux AG. - * - * The support of additional filter rules compares (>, <, >=, <=) was - * added by Dustin Kirkland , 2005. - * - * Modified by Amy Griffis to collect additional - * filesystem information. - * - * Subject and object context labeling support added by - * and for LSPP certification compliance. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "audit.h" - -extern struct list_head audit_filter_list[]; - -/* No syscall auditing will take place unless audit_enabled != 0. */ -extern int audit_enabled; - -/* AUDIT_NAMES is the number of slots we reserve in the audit_context - * for saving names from getname(). */ -#define AUDIT_NAMES 20 - -/* AUDIT_NAMES_RESERVED is the number of slots we reserve in the - * audit_context from being used for nameless inodes from - * path_lookup. */ -#define AUDIT_NAMES_RESERVED 7 - -/* When fs/namei.c:getname() is called, we store the pointer in name and - * we don't let putname() free it (instead we free all of the saved - * pointers at syscall exit time). - * - * Further, in fs/namei.c:path_lookup() we store the inode and device. */ -struct audit_names { - const char *name; - unsigned long ino; - unsigned long pino; - dev_t dev; - umode_t mode; - uid_t uid; - gid_t gid; - dev_t rdev; - u32 osid; -}; - -struct audit_aux_data { - struct audit_aux_data *next; - int type; -}; - -#define AUDIT_AUX_IPCPERM 0 - -struct audit_aux_data_ipcctl { - struct audit_aux_data d; - struct ipc_perm p; - unsigned long qbytes; - uid_t uid; - gid_t gid; - mode_t mode; - u32 osid; -}; - -struct audit_aux_data_execve { - struct audit_aux_data d; - int argc; - int envc; - char mem[0]; -}; - -struct audit_aux_data_socketcall { - struct audit_aux_data d; - int nargs; - unsigned long args[0]; -}; - -struct audit_aux_data_sockaddr { - struct audit_aux_data d; - int len; - char a[0]; -}; - -struct audit_aux_data_path { - struct audit_aux_data d; - struct dentry *dentry; - struct vfsmount *mnt; -}; - -/* The per-task audit context. */ -struct audit_context { - int in_syscall; /* 1 if task is in a syscall */ - enum audit_state state; - unsigned int serial; /* serial number for record */ - struct timespec ctime; /* time of syscall entry */ - uid_t loginuid; /* login uid (identity) */ - int major; /* syscall number */ - unsigned long argv[4]; /* syscall arguments */ - int return_valid; /* return code is valid */ - long return_code;/* syscall return code */ - int auditable; /* 1 if record should be written */ - int name_count; - struct audit_names names[AUDIT_NAMES]; - struct dentry * pwd; - struct vfsmount * pwdmnt; - struct audit_context *previous; /* For nested syscalls */ - struct audit_aux_data *aux; - - /* Save things to print about task_struct */ - pid_t pid; - uid_t uid, euid, suid, fsuid; - gid_t gid, egid, sgid, fsgid; - unsigned long personality; - int arch; - -#if AUDIT_DEBUG - int put_count; - int ino_count; -#endif -}; - -/* Determine if any context name data matches a rule's watch data */ -static inline int audit_match_watch(struct audit_context *ctx, - struct audit_watch *watch) -{ - int i; - - if (!ctx) - return 0; - - if (watch->ino == (unsigned long)-1) - return 0; - - for (i = 0; i < ctx->name_count; i++) { - if (ctx->names[i].dev == watch->dev && - (ctx->names[i].ino == watch->ino || - ctx->names[i].pino == watch->ino)) - return 1; - } - - return 0; -} - -/* Compare a task_struct with an audit_rule. Return 1 on match, 0 - * otherwise. */ -static int audit_filter_rules(struct task_struct *tsk, - struct audit_krule *rule, - struct audit_context *ctx, - enum audit_state *state) -{ - int i, j, need_sid = 1; - u32 sid; - - for (i = 0; i < rule->field_count; i++) { - struct audit_field *f = &rule->fields[i]; - int result = 0; - - switch (f->type) { - case AUDIT_PID: - result = audit_comparator(tsk->pid, f->op, f->val); - break; - case AUDIT_UID: - result = audit_comparator(tsk->uid, f->op, f->val); - break; - case AUDIT_EUID: - result = audit_comparator(tsk->euid, f->op, f->val); - break; - case AUDIT_SUID: - result = audit_comparator(tsk->suid, f->op, f->val); - break; - case AUDIT_FSUID: - result = audit_comparator(tsk->fsuid, f->op, f->val); - break; - case AUDIT_GID: - result = audit_comparator(tsk->gid, f->op, f->val); - break; - case AUDIT_EGID: - result = audit_comparator(tsk->egid, f->op, f->val); - break; - case AUDIT_SGID: - result = audit_comparator(tsk->sgid, f->op, f->val); - break; - case AUDIT_FSGID: - result = audit_comparator(tsk->fsgid, f->op, f->val); - break; - case AUDIT_PERS: - result = audit_comparator(tsk->personality, f->op, f->val); - break; - case AUDIT_ARCH: - if (ctx) - result = audit_comparator(ctx->arch, f->op, f->val); - break; - - case AUDIT_EXIT: - if (ctx && ctx->return_valid) - result = audit_comparator(ctx->return_code, f->op, f->val); - break; - case AUDIT_SUCCESS: - if (ctx && ctx->return_valid) { - if (f->val) - result = audit_comparator(ctx->return_valid, f->op, AUDITSC_SUCCESS); - else - result = audit_comparator(ctx->return_valid, f->op, AUDITSC_FAILURE); - } - break; - case AUDIT_DEVMAJOR: - if (ctx) { - for (j = 0; j < ctx->name_count; j++) { - if (audit_comparator(MAJOR(ctx->names[j].dev), f->op, f->val)) { - ++result; - break; - } - } - } - break; - case AUDIT_DEVMINOR: - if (ctx) { - for (j = 0; j < ctx->name_count; j++) { - if (audit_comparator(MINOR(ctx->names[j].dev), f->op, f->val)) { - ++result; - break; - } - } - } - break; - case AUDIT_INODE: - if (ctx) { - for (j = 0; j < ctx->name_count; j++) { - if (audit_comparator(ctx->names[j].ino, f->op, f->val) || - audit_comparator(ctx->names[j].pino, f->op, f->val)) { - ++result; - break; - } - } - } - break; - case AUDIT_WATCH: - result = audit_match_watch(ctx, rule->watch); - break; - case AUDIT_LOGINUID: - result = 0; - if (ctx) - result = audit_comparator(ctx->loginuid, f->op, f->val); - break; - case AUDIT_SE_USER: - case AUDIT_SE_ROLE: - case AUDIT_SE_TYPE: - case AUDIT_SE_SEN: - case AUDIT_SE_CLR: - /* NOTE: this may return negative values indicating - a temporary error. We simply treat this as a - match for now to avoid losing information that - may be wanted. An error message will also be - logged upon error */ - if (f->se_rule) { - if (need_sid) { - selinux_task_ctxid(tsk, &sid); - need_sid = 0; - } - result = selinux_audit_rule_match(sid, f->type, - f->op, - f->se_rule, - ctx); - } - break; - case AUDIT_ARG0: - case AUDIT_ARG1: - case AUDIT_ARG2: - case AUDIT_ARG3: - if (ctx) - result = audit_comparator(ctx->argv[f->type-AUDIT_ARG0], f->op, f->val); - break; - } - - if (!result) - return 0; - } - switch (rule->action) { - case AUDIT_NEVER: *state = AUDIT_DISABLED; break; - case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break; - case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break; - } - return 1; -} - -/* At process creation time, we can determine if system-call auditing is - * completely disabled for this task. Since we only have the task - * structure at this point, we can only check uid and gid. - */ -static enum audit_state audit_filter_task(struct task_struct *tsk) -{ - struct audit_entry *e; - enum audit_state state; - - rcu_read_lock(); - list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) { - if (audit_filter_rules(tsk, &e->rule, NULL, &state)) { - rcu_read_unlock(); - return state; - } - } - rcu_read_unlock(); - return AUDIT_BUILD_CONTEXT; -} - -/* At syscall entry and exit time, this filter is called if the - * audit_state is not low enough that auditing cannot take place, but is - * also not high enough that we already know we have to write an audit - * record (i.e., the state is AUDIT_SETUP_CONTEXT or AUDIT_BUILD_CONTEXT). - */ -static enum audit_state audit_filter_syscall(struct task_struct *tsk, - struct audit_context *ctx, - struct list_head *list) -{ - struct audit_entry *e; - enum audit_state state; - - if (audit_pid && tsk->tgid == audit_pid) - return AUDIT_DISABLED; - - rcu_read_lock(); - if (!list_empty(list)) { - int word = AUDIT_WORD(ctx->major); - int bit = AUDIT_BIT(ctx->major); - - list_for_each_entry_rcu(e, list, list) { - if ((e->rule.mask[word] & bit) == bit - && audit_filter_rules(tsk, &e->rule, ctx, &state)) { - rcu_read_unlock(); - return state; - } - } - } - rcu_read_unlock(); - return AUDIT_BUILD_CONTEXT; -} - -static inline struct audit_context *audit_get_context(struct task_struct *tsk, - int return_valid, - int return_code) -{ - struct audit_context *context = tsk->audit_context; - - if (likely(!context)) - return NULL; - context->return_valid = return_valid; - context->return_code = return_code; - - if (context->in_syscall && !context->auditable) { - enum audit_state state; - state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]); - if (state == AUDIT_RECORD_CONTEXT) - context->auditable = 1; - } - - context->pid = tsk->pid; - context->uid = tsk->uid; - context->gid = tsk->gid; - context->euid = tsk->euid; - context->suid = tsk->suid; - context->fsuid = tsk->fsuid; - context->egid = tsk->egid; - context->sgid = tsk->sgid; - context->fsgid = tsk->fsgid; - context->personality = tsk->personality; - tsk->audit_context = NULL; - return context; -} - -static inline void audit_free_names(struct audit_context *context) -{ - int i; - -#if AUDIT_DEBUG == 2 - if (context->auditable - ||context->put_count + context->ino_count != context->name_count) { - printk(KERN_ERR "%s:%d(:%d): major=%d in_syscall=%d" - " name_count=%d put_count=%d" - " ino_count=%d [NOT freeing]\n", - __FILE__, __LINE__, - context->serial, context->major, context->in_syscall, - context->name_count, context->put_count, - context->ino_count); - for (i = 0; i < context->name_count; i++) { - printk(KERN_ERR "names[%d] = %p = %s\n", i, - context->names[i].name, - context->names[i].name ?: "(null)"); - } - dump_stack(); - return; - } -#endif -#if AUDIT_DEBUG - context->put_count = 0; - context->ino_count = 0; -#endif - - for (i = 0; i < context->name_count; i++) { - if (context->names[i].name) - __putname(context->names[i].name); - } - context->name_count = 0; - if (context->pwd) - dput(context->pwd); - if (context->pwdmnt) - mntput(context->pwdmnt); - context->pwd = NULL; - context->pwdmnt = NULL; -} - -static inline void audit_free_aux(struct audit_context *context) -{ - struct audit_aux_data *aux; - - while ((aux = context->aux)) { - if (aux->type == AUDIT_AVC_PATH) { - struct audit_aux_data_path *axi = (void *)aux; - dput(axi->dentry); - mntput(axi->mnt); - } - - context->aux = aux->next; - kfree(aux); - } -} - -static inline void audit_zero_context(struct audit_context *context, - enum audit_state state) -{ - uid_t loginuid = context->loginuid; - - memset(context, 0, sizeof(*context)); - context->state = state; - context->loginuid = loginuid; -} - -static inline struct audit_context *audit_alloc_context(enum audit_state state) -{ - struct audit_context *context; - - if (!(context = kmalloc(sizeof(*context), GFP_KERNEL))) - return NULL; - audit_zero_context(context, state); - return context; -} - -/** - * audit_alloc - allocate an audit context block for a task - * @tsk: task - * - * Filter on the task information and allocate a per-task audit context - * if necessary. Doing so turns on system call auditing for the - * specified task. This is called from copy_process, so no lock is - * needed. - */ -int audit_alloc(struct task_struct *tsk) -{ - struct audit_context *context; - enum audit_state state; - - if (likely(!audit_enabled)) - return 0; /* Return if not auditing. */ - - state = audit_filter_task(tsk); - if (likely(state == AUDIT_DISABLED)) - return 0; - - if (!(context = audit_alloc_context(state))) { - audit_log_lost("out of memory in audit_alloc"); - return -ENOMEM; - } - - /* Preserve login uid */ - context->loginuid = -1; - if (current->audit_context) - context->loginuid = current->audit_context->loginuid; - - tsk->audit_context = context; - set_tsk_thread_flag(tsk, TIF_SYSCALL_AUDIT); - return 0; -} - -static inline void audit_free_context(struct audit_context *context) -{ - struct audit_context *previous; - int count = 0; - - do { - previous = context->previous; - if (previous || (count && count < 10)) { - ++count; - printk(KERN_ERR "audit(:%d): major=%d name_count=%d:" - " freeing multiple contexts (%d)\n", - context->serial, context->major, - context->name_count, count); - } - audit_free_names(context); - audit_free_aux(context); - kfree(context); - context = previous; - } while (context); - if (count >= 10) - printk(KERN_ERR "audit: freed %d contexts\n", count); -} - -static void audit_log_task_context(struct audit_buffer *ab) -{ - char *ctx = NULL; - ssize_t len = 0; - - len = security_getprocattr(current, "current", NULL, 0); - if (len < 0) { - if (len != -EINVAL) - goto error_path; - return; - } - - ctx = kmalloc(len, GFP_KERNEL); - if (!ctx) - goto error_path; - - len = security_getprocattr(current, "current", ctx, len); - if (len < 0 ) - goto error_path; - - audit_log_format(ab, " subj=%s", ctx); - return; - -error_path: - if (ctx) - kfree(ctx); - audit_panic("error in audit_log_task_context"); - return; -} - -static void audit_log_task_info(struct audit_buffer *ab, struct task_struct *tsk) -{ - char name[sizeof(tsk->comm)]; - struct mm_struct *mm = tsk->mm; - struct vm_area_struct *vma; - - /* tsk == current */ - - get_task_comm(name, tsk); - audit_log_format(ab, " comm="); - audit_log_untrustedstring(ab, name); - - if (mm) { - down_read(&mm->mmap_sem); - vma = mm->mmap; - while (vma) { - if ((vma->vm_flags & VM_EXECUTABLE) && - vma->vm_file) { - audit_log_d_path(ab, "exe=", - vma->vm_file->f_dentry, - vma->vm_file->f_vfsmnt); - break; - } - vma = vma->vm_next; - } - up_read(&mm->mmap_sem); - } - audit_log_task_context(ab); -} - -static void audit_log_exit(struct audit_context *context, struct task_struct *tsk) -{ - int i, call_panic = 0; - struct audit_buffer *ab; - struct audit_aux_data *aux; - const char *tty; - - /* tsk == current */ - - ab = audit_log_start(context, GFP_KERNEL, AUDIT_SYSCALL); - if (!ab) - return; /* audit_panic has been called */ - audit_log_format(ab, "arch=%x syscall=%d", - context->arch, context->major); - if (context->personality != PER_LINUX) - audit_log_format(ab, " per=%lx", context->personality); - if (context->return_valid) - audit_log_format(ab, " success=%s exit=%ld", - (context->return_valid==AUDITSC_SUCCESS)?"yes":"no", - context->return_code); - if (tsk->signal && tsk->signal->tty && tsk->signal->tty->name) - tty = tsk->signal->tty->name; - else - tty = "(none)"; - audit_log_format(ab, - " a0=%lx a1=%lx a2=%lx a3=%lx items=%d" - " pid=%d auid=%u uid=%u gid=%u" - " euid=%u suid=%u fsuid=%u" - " egid=%u sgid=%u fsgid=%u tty=%s", - context->argv[0], - context->argv[1], - context->argv[2], - context->argv[3], - context->name_count, - context->pid, - context->loginuid, - context->uid, - context->gid, - context->euid, context->suid, context->fsuid, - context->egid, context->sgid, context->fsgid, tty); - audit_log_task_info(ab, tsk); - audit_log_end(ab); - - for (aux = context->aux; aux; aux = aux->next) { - - ab = audit_log_start(context, GFP_KERNEL, aux->type); - if (!ab) - continue; /* audit_panic has been called */ - - switch (aux->type) { - case AUDIT_IPC: { - struct audit_aux_data_ipcctl *axi = (void *)aux; - audit_log_format(ab, - " qbytes=%lx iuid=%u igid=%u mode=%x", - axi->qbytes, axi->uid, axi->gid, axi->mode); - if (axi->osid != 0) { - char *ctx = NULL; - u32 len; - if (selinux_ctxid_to_string( - axi->osid, &ctx, &len)) { - audit_log_format(ab, " osid=%u", - axi->osid); - call_panic = 1; - } else - audit_log_format(ab, " obj=%s", ctx); - kfree(ctx); - } - break; } - - case AUDIT_IPC_SET_PERM: { - struct audit_aux_data_ipcctl *axi = (void *)aux; - audit_log_format(ab, - " new qbytes=%lx new iuid=%u new igid=%u new mode=%x", - axi->qbytes, axi->uid, axi->gid, axi->mode); - if (axi->osid != 0) { - char *ctx = NULL; - u32 len; - if (selinux_ctxid_to_string( - axi->osid, &ctx, &len)) { - audit_log_format(ab, " osid=%u", - axi->osid); - call_panic = 1; - } else - audit_log_format(ab, " obj=%s", ctx); - kfree(ctx); - } - break; } - case AUDIT_EXECVE: { - struct audit_aux_data_execve *axi = (void *)aux; - int i; - const char *p; - for (i = 0, p = axi->mem; i < axi->argc; i++) { - audit_log_format(ab, "a%d=", i); - p = audit_log_untrustedstring(ab, p); - audit_log_format(ab, "\n"); - } - break; } - - case AUDIT_SOCKETCALL: { - int i; - struct audit_aux_data_socketcall *axs = (void *)aux; - audit_log_format(ab, "nargs=%d", axs->nargs); - for (i=0; inargs; i++) - audit_log_format(ab, " a%d=%lx", i, axs->args[i]); - break; } - - case AUDIT_SOCKADDR: { - struct audit_aux_data_sockaddr *axs = (void *)aux; - - audit_log_format(ab, "saddr="); - audit_log_hex(ab, axs->a, axs->len); - break; } - - case AUDIT_AVC_PATH: { - struct audit_aux_data_path *axi = (void *)aux; - audit_log_d_path(ab, "path=", axi->dentry, axi->mnt); - break; } - - } - audit_log_end(ab); - } - - if (context->pwd && context->pwdmnt) { - ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD); - if (ab) { - audit_log_d_path(ab, "cwd=", context->pwd, context->pwdmnt); - audit_log_end(ab); - } - } - for (i = 0; i < context->name_count; i++) { - unsigned long ino = context->names[i].ino; - unsigned long pino = context->names[i].pino; - - ab = audit_log_start(context, GFP_KERNEL, AUDIT_PATH); - if (!ab) - continue; /* audit_panic has been called */ - - audit_log_format(ab, "item=%d", i); - - audit_log_format(ab, " name="); - if (context->names[i].name) - audit_log_untrustedstring(ab, context->names[i].name); - else - audit_log_format(ab, "(null)"); - - if (pino != (unsigned long)-1) - audit_log_format(ab, " parent=%lu", pino); - if (ino != (unsigned long)-1) - audit_log_format(ab, " inode=%lu", ino); - if ((pino != (unsigned long)-1) || (ino != (unsigned long)-1)) - audit_log_format(ab, " dev=%02x:%02x mode=%#o" - " ouid=%u ogid=%u rdev=%02x:%02x", - MAJOR(context->names[i].dev), - MINOR(context->names[i].dev), - context->names[i].mode, - context->names[i].uid, - context->names[i].gid, - MAJOR(context->names[i].rdev), - MINOR(context->names[i].rdev)); - if (context->names[i].osid != 0) { - char *ctx = NULL; - u32 len; - if (selinux_ctxid_to_string( - context->names[i].osid, &ctx, &len)) { - audit_log_format(ab, " osid=%u", - context->names[i].osid); - call_panic = 2; - } else - audit_log_format(ab, " obj=%s", ctx); - kfree(ctx); - } - - audit_log_end(ab); - } - if (call_panic) - audit_panic("error converting sid to string"); -} - -/** - * audit_free - free a per-task audit context - * @tsk: task whose audit context block to free - * - * Called from copy_process and do_exit - */ -void audit_free(struct task_struct *tsk) -{ - struct audit_context *context; - - context = audit_get_context(tsk, 0, 0); - if (likely(!context)) - return; - - /* Check for system calls that do not go through the exit - * function (e.g., exit_group), then free context block. - * We use GFP_ATOMIC here because we might be doing this - * in the context of the idle thread */ - /* that can happen only if we are called from do_exit() */ - if (context->in_syscall && context->auditable) - audit_log_exit(context, tsk); - - audit_free_context(context); -} - -/** - * audit_syscall_entry - fill in an audit record at syscall entry - * @tsk: task being audited - * @arch: architecture type - * @major: major syscall type (function) - * @a1: additional syscall register 1 - * @a2: additional syscall register 2 - * @a3: additional syscall register 3 - * @a4: additional syscall register 4 - * - * Fill in audit context at syscall entry. This only happens if the - * audit context was created when the task was created and the state or - * filters demand the audit context be built. If the state from the - * per-task filter or from the per-syscall filter is AUDIT_RECORD_CONTEXT, - * then the record will be written at syscall exit time (otherwise, it - * will only be written if another part of the kernel requests that it - * be written). - */ -void audit_syscall_entry(int arch, int major, - unsigned long a1, unsigned long a2, - unsigned long a3, unsigned long a4) -{ - struct task_struct *tsk = current; - struct audit_context *context = tsk->audit_context; - enum audit_state state; - - BUG_ON(!context); - - /* - * This happens only on certain architectures that make system - * calls in kernel_thread via the entry.S interface, instead of - * with direct calls. (If you are porting to a new - * architecture, hitting this condition can indicate that you - * got the _exit/_leave calls backward in entry.S.) - * - * i386 no - * x86_64 no - * ppc64 yes (see arch/powerpc/platforms/iseries/misc.S) - * - * This also happens with vm86 emulation in a non-nested manner - * (entries without exits), so this case must be caught. - */ - if (context->in_syscall) { - struct audit_context *newctx; - -#if AUDIT_DEBUG - printk(KERN_ERR - "audit(:%d) pid=%d in syscall=%d;" - " entering syscall=%d\n", - context->serial, tsk->pid, context->major, major); -#endif - newctx = audit_alloc_context(context->state); - if (newctx) { - newctx->previous = context; - context = newctx; - tsk->audit_context = newctx; - } else { - /* If we can't alloc a new context, the best we - * can do is to leak memory (any pending putname - * will be lost). The only other alternative is - * to abandon auditing. */ - audit_zero_context(context, context->state); - } - } - BUG_ON(context->in_syscall || context->name_count); - - if (!audit_enabled) - return; - - context->arch = arch; - context->major = major; - context->argv[0] = a1; - context->argv[1] = a2; - context->argv[2] = a3; - context->argv[3] = a4; - - state = context->state; - if (state == AUDIT_SETUP_CONTEXT || state == AUDIT_BUILD_CONTEXT) - state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]); - if (likely(state == AUDIT_DISABLED)) - return; - - context->serial = 0; - context->ctime = CURRENT_TIME; - context->in_syscall = 1; - context->auditable = !!(state == AUDIT_RECORD_CONTEXT); -} - -/** - * audit_syscall_exit - deallocate audit context after a system call - * @tsk: task being audited - * @valid: success/failure flag - * @return_code: syscall return value - * - * Tear down after system call. If the audit context has been marked as - * auditable (either because of the AUDIT_RECORD_CONTEXT state from - * filtering, or because some other part of the kernel write an audit - * message), then write out the syscall information. In call cases, - * free the names stored from getname(). - */ -void audit_syscall_exit(int valid, long return_code) -{ - struct task_struct *tsk = current; - struct audit_context *context; - - context = audit_get_context(tsk, valid, return_code); - - if (likely(!context)) - return; - - if (context->in_syscall && context->auditable) - audit_log_exit(context, tsk); - - context->in_syscall = 0; - context->auditable = 0; - - if (context->previous) { - struct audit_context *new_context = context->previous; - context->previous = NULL; - audit_free_context(context); - tsk->audit_context = new_context; - } else { - audit_free_names(context); - audit_free_aux(context); - tsk->audit_context = context; - } -} - -/** - * audit_getname - add a name to the list - * @name: name to add - * - * Add a name to the list of audit names for this context. - * Called from fs/namei.c:getname(). - */ -void audit_getname(const char *name) -{ - struct audit_context *context = current->audit_context; - - if (!context || IS_ERR(name) || !name) - return; - - if (!context->in_syscall) { -#if AUDIT_DEBUG == 2 - printk(KERN_ERR "%s:%d(:%d): ignoring getname(%p)\n", - __FILE__, __LINE__, context->serial, name); - dump_stack(); -#endif - return; - } - BUG_ON(context->name_count >= AUDIT_NAMES); - context->names[context->name_count].name = name; - context->names[context->name_count].ino = (unsigned long)-1; - ++context->name_count; - if (!context->pwd) { - read_lock(¤t->fs->lock); - context->pwd = dget(current->fs->pwd); - context->pwdmnt = mntget(current->fs->pwdmnt); - read_unlock(¤t->fs->lock); - } - -} - -/* audit_putname - intercept a putname request - * @name: name to intercept and delay for putname - * - * If we have stored the name from getname in the audit context, - * then we delay the putname until syscall exit. - * Called from include/linux/fs.h:putname(). - */ -void audit_putname(const char *name) -{ - struct audit_context *context = current->audit_context; - - BUG_ON(!context); - if (!context->in_syscall) { -#if AUDIT_DEBUG == 2 - printk(KERN_ERR "%s:%d(:%d): __putname(%p)\n", - __FILE__, __LINE__, context->serial, name); - if (context->name_count) { - int i; - for (i = 0; i < context->name_count; i++) - printk(KERN_ERR "name[%d] = %p = %s\n", i, - context->names[i].name, - context->names[i].name ?: "(null)"); - } -#endif - __putname(name); - } -#if AUDIT_DEBUG - else { - ++context->put_count; - if (context->put_count > context->name_count) { - printk(KERN_ERR "%s:%d(:%d): major=%d" - " in_syscall=%d putname(%p) name_count=%d" - " put_count=%d\n", - __FILE__, __LINE__, - context->serial, context->major, - context->in_syscall, name, context->name_count, - context->put_count); - dump_stack(); - } - } -#endif -} - -static void audit_inode_context(int idx, const struct inode *inode) -{ - struct audit_context *context = current->audit_context; - - selinux_get_inode_sid(inode, &context->names[idx].osid); -} - - -/** - * audit_inode - store the inode and device from a lookup - * @name: name being audited - * @inode: inode being audited - * @flags: lookup flags (as used in path_lookup()) - * - * Called from fs/namei.c:path_lookup(). - */ -void __audit_inode(const char *name, const struct inode *inode, unsigned flags) -{ - int idx; - struct audit_context *context = current->audit_context; - - if (!context->in_syscall) - return; - if (context->name_count - && context->names[context->name_count-1].name - && context->names[context->name_count-1].name == name) - idx = context->name_count - 1; - else if (context->name_count > 1 - && context->names[context->name_count-2].name - && context->names[context->name_count-2].name == name) - idx = context->name_count - 2; - else { - /* FIXME: how much do we care about inodes that have no - * associated name? */ - if (context->name_count >= AUDIT_NAMES - AUDIT_NAMES_RESERVED) - return; - idx = context->name_count++; - context->names[idx].name = NULL; -#if AUDIT_DEBUG - ++context->ino_count; -#endif - } - context->names[idx].dev = inode->i_sb->s_dev; - context->names[idx].mode = inode->i_mode; - context->names[idx].uid = inode->i_uid; - context->names[idx].gid = inode->i_gid; - context->names[idx].rdev = inode->i_rdev; - audit_inode_context(idx, inode); - if ((flags & LOOKUP_PARENT) && (strcmp(name, "/") != 0) && - (strcmp(name, ".") != 0)) { - context->names[idx].ino = (unsigned long)-1; - context->names[idx].pino = inode->i_ino; - } else { - context->names[idx].ino = inode->i_ino; - context->names[idx].pino = (unsigned long)-1; - } -} - -/** - * audit_inode_child - collect inode info for created/removed objects - * @dname: inode's dentry name - * @inode: inode being audited - * @pino: inode number of dentry parent - * - * For syscalls that create or remove filesystem objects, audit_inode - * can only collect information for the filesystem object's parent. - * This call updates the audit context with the child's information. - * Syscalls that create a new filesystem object must be hooked after - * the object is created. Syscalls that remove a filesystem object - * must be hooked prior, in order to capture the target inode during - * unsuccessful attempts. - */ -void __audit_inode_child(const char *dname, const struct inode *inode, - unsigned long pino) -{ - int idx; - struct audit_context *context = current->audit_context; - - if (!context->in_syscall) - return; - - /* determine matching parent */ - if (!dname) - goto no_match; - for (idx = 0; idx < context->name_count; idx++) - if (context->names[idx].pino == pino) { - const char *name = context->names[idx].name; - - if (!name) - continue; - - if (audit_compare_dname_path(dname, name) == 0) - goto update_context; - } - -no_match: - /* catch-all in case match not found */ - idx = context->name_count++; - context->names[idx].name = NULL; - context->names[idx].pino = pino; -#if AUDIT_DEBUG - context->ino_count++; -#endif - -update_context: - if (inode) { - context->names[idx].ino = inode->i_ino; - context->names[idx].dev = inode->i_sb->s_dev; - context->names[idx].mode = inode->i_mode; - context->names[idx].uid = inode->i_uid; - context->names[idx].gid = inode->i_gid; - context->names[idx].rdev = inode->i_rdev; - audit_inode_context(idx, inode); - } -} - -/** - * auditsc_get_stamp - get local copies of audit_context values - * @ctx: audit_context for the task - * @t: timespec to store time recorded in the audit_context - * @serial: serial value that is recorded in the audit_context - * - * Also sets the context as auditable. - */ -void auditsc_get_stamp(struct audit_context *ctx, - struct timespec *t, unsigned int *serial) -{ - if (!ctx->serial) - ctx->serial = audit_serial(); - t->tv_sec = ctx->ctime.tv_sec; - t->tv_nsec = ctx->ctime.tv_nsec; - *serial = ctx->serial; - ctx->auditable = 1; -} - -/** - * audit_set_loginuid - set a task's audit_context loginuid - * @task: task whose audit context is being modified - * @loginuid: loginuid value - * - * Returns 0. - * - * Called (set) from fs/proc/base.c::proc_loginuid_write(). - */ -int audit_set_loginuid(struct task_struct *task, uid_t loginuid) -{ - if (task->audit_context) { - struct audit_buffer *ab; - - ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_LOGIN); - if (ab) { - audit_log_format(ab, "login pid=%d uid=%u " - "old auid=%u new auid=%u", - task->pid, task->uid, - task->audit_context->loginuid, loginuid); - audit_log_end(ab); - } - task->audit_context->loginuid = loginuid; - } - return 0; -} - -/** - * audit_get_loginuid - get the loginuid for an audit_context - * @ctx: the audit_context - * - * Returns the context's loginuid or -1 if @ctx is NULL. - */ -uid_t audit_get_loginuid(struct audit_context *ctx) -{ - return ctx ? ctx->loginuid : -1; -} - -/** - * audit_ipc_obj - record audit data for ipc object - * @ipcp: ipc permissions - * - * Returns 0 for success or NULL context or < 0 on error. - */ -int audit_ipc_obj(struct kern_ipc_perm *ipcp) -{ - struct audit_aux_data_ipcctl *ax; - struct audit_context *context = current->audit_context; - - if (likely(!context)) - return 0; - - ax = kmalloc(sizeof(*ax), GFP_ATOMIC); - if (!ax) - return -ENOMEM; - - ax->uid = ipcp->uid; - ax->gid = ipcp->gid; - ax->mode = ipcp->mode; - selinux_get_ipc_sid(ipcp, &ax->osid); - - ax->d.type = AUDIT_IPC; - ax->d.next = context->aux; - context->aux = (void *)ax; - return 0; -} - -/** - * audit_ipc_set_perm - record audit data for new ipc permissions - * @qbytes: msgq bytes - * @uid: msgq user id - * @gid: msgq group id - * @mode: msgq mode (permissions) - * - * Returns 0 for success or NULL context or < 0 on error. - */ -int audit_ipc_set_perm(unsigned long qbytes, uid_t uid, gid_t gid, mode_t mode, struct kern_ipc_perm *ipcp) -{ - struct audit_aux_data_ipcctl *ax; - struct audit_context *context = current->audit_context; - - if (likely(!context)) - return 0; - - ax = kmalloc(sizeof(*ax), GFP_ATOMIC); - if (!ax) - return -ENOMEM; - - ax->qbytes = qbytes; - ax->uid = uid; - ax->gid = gid; - ax->mode = mode; - selinux_get_ipc_sid(ipcp, &ax->osid); - - ax->d.type = AUDIT_IPC_SET_PERM; - ax->d.next = context->aux; - context->aux = (void *)ax; - return 0; -} - -int audit_bprm(struct linux_binprm *bprm) -{ - struct audit_aux_data_execve *ax; - struct audit_context *context = current->audit_context; - unsigned long p, next; - void *to; - - if (likely(!audit_enabled || !context)) - return 0; - - ax = kmalloc(sizeof(*ax) + PAGE_SIZE * MAX_ARG_PAGES - bprm->p, - GFP_KERNEL); - if (!ax) - return -ENOMEM; - - ax->argc = bprm->argc; - ax->envc = bprm->envc; - for (p = bprm->p, to = ax->mem; p < MAX_ARG_PAGES*PAGE_SIZE; p = next) { - struct page *page = bprm->page[p / PAGE_SIZE]; - void *kaddr = kmap(page); - next = (p + PAGE_SIZE) & ~(PAGE_SIZE - 1); - memcpy(to, kaddr + (p & (PAGE_SIZE - 1)), next - p); - to += next - p; - kunmap(page); - } - - ax->d.type = AUDIT_EXECVE; - ax->d.next = context->aux; - context->aux = (void *)ax; - return 0; -} - - -/** - * audit_socketcall - record audit data for sys_socketcall - * @nargs: number of args - * @args: args array - * - * Returns 0 for success or NULL context or < 0 on error. - */ -int audit_socketcall(int nargs, unsigned long *args) -{ - struct audit_aux_data_socketcall *ax; - struct audit_context *context = current->audit_context; - - if (likely(!context)) - return 0; - - ax = kmalloc(sizeof(*ax) + nargs * sizeof(unsigned long), GFP_KERNEL); - if (!ax) - return -ENOMEM; - - ax->nargs = nargs; - memcpy(ax->args, args, nargs * sizeof(unsigned long)); - - ax->d.type = AUDIT_SOCKETCALL; - ax->d.next = context->aux; - context->aux = (void *)ax; - return 0; -} - -/** - * audit_sockaddr - record audit data for sys_bind, sys_connect, sys_sendto - * @len: data length in user space - * @a: data address in kernel space - * - * Returns 0 for success or NULL context or < 0 on error. - */ -int audit_sockaddr(int len, void *a) -{ - struct audit_aux_data_sockaddr *ax; - struct audit_context *context = current->audit_context; - - if (likely(!context)) - return 0; - - ax = kmalloc(sizeof(*ax) + len, GFP_KERNEL); - if (!ax) - return -ENOMEM; - - ax->len = len; - memcpy(ax->a, a, len); - - ax->d.type = AUDIT_SOCKADDR; - ax->d.next = context->aux; - context->aux = (void *)ax; - return 0; -} - -/** - * audit_avc_path - record the granting or denial of permissions - * @dentry: dentry to record - * @mnt: mnt to record - * - * Returns 0 for success or NULL context or < 0 on error. - * - * Called from security/selinux/avc.c::avc_audit() - */ -int audit_avc_path(struct dentry *dentry, struct vfsmount *mnt) -{ - struct audit_aux_data_path *ax; - struct audit_context *context = current->audit_context; - - if (likely(!context)) - return 0; - - ax = kmalloc(sizeof(*ax), GFP_ATOMIC); - if (!ax) - return -ENOMEM; - - ax->dentry = dget(dentry); - ax->mnt = mntget(mnt); - - ax->d.type = AUDIT_AVC_PATH; - ax->d.next = context->aux; - context->aux = (void *)ax; - return 0; -} - -/** - * audit_signal_info - record signal info for shutting down audit subsystem - * @sig: signal value - * @t: task being signaled - * - * If the audit subsystem is being terminated, record the task (pid) - * and uid that is doing that. - */ -void audit_signal_info(int sig, struct task_struct *t) -{ - extern pid_t audit_sig_pid; - extern uid_t audit_sig_uid; - - if (unlikely(audit_pid && t->tgid == audit_pid)) { - if (sig == SIGTERM || sig == SIGHUP) { - struct audit_context *ctx = current->audit_context; - audit_sig_pid = current->pid; - if (ctx) - audit_sig_uid = ctx->loginuid; - else - audit_sig_uid = current->uid; - } - } -} diff --git a/kernel/audit/Makefile b/kernel/audit/Makefile new file mode 100644 index 0000000..69234c5 --- /dev/null +++ b/kernel/audit/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the audit subsystem. +# +obj-$(CONFIG_AUDIT) += audit.o filter.o +obj-$(CONFIG_AUDITSYSCALL) += syscall.o diff --git a/kernel/audit/audit.c b/kernel/audit/audit.c new file mode 100644 index 0000000..7637410 --- /dev/null +++ b/kernel/audit/audit.c @@ -0,0 +1,1133 @@ +/* audit.c -- Auditing support + * Gateway between the kernel (e.g., selinux) and the user-space audit daemon. + * System-call specific features have moved to auditsc.c + * + * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Written by Rickard E. (Rik) Faith + * + * Goals: 1) Integrate fully with SELinux. + * 2) Minimal run-time overhead: + * a) Minimal when syscall auditing is disabled (audit_enable=0). + * b) Small when syscall auditing is enabled and no audit record + * is generated (defer as much work as possible to record + * generation time): + * i) context is allocated, + * ii) names from getname are stored without a copy, and + * iii) inode information stored from path_lookup. + * 3) Ability to disable syscall auditing at boot time (audit=0). + * 4) Usable by other parts of the kernel (if audit_log* is called, + * then a syscall record will be generated automatically for the + * current syscall). + * 5) Netlink interface to user-space. + * 6) Support low-overhead kernel-based filtering to minimize the + * information that must be passed to user-space. + * + * Example user-space utilities: http://people.redhat.com/sgrubb/audit/ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "audit.h" + +/* No auditing will take place until audit_initialized != 0. + * (Initialization happens after skb_init is called.) */ +static int audit_initialized; + +/* No syscall auditing will take place unless audit_enabled != 0. */ +int audit_enabled; + +/* Default state when kernel boots without any parameters. */ +static int audit_default; + +/* If auditing cannot proceed, audit_failure selects what happens. */ +static int audit_failure = AUDIT_FAIL_PRINTK; + +/* If audit records are to be written to the netlink socket, audit_pid + * contains the (non-zero) pid. */ +int audit_pid; + +/* If audit_rate_limit is non-zero, limit the rate of sending audit records + * to that number per second. This prevents DoS attacks, but results in + * audit records being dropped. */ +static int audit_rate_limit; + +/* Number of outstanding audit_buffers allowed. */ +static int audit_backlog_limit = 64; +static int audit_backlog_wait_time = 60 * HZ; +static int audit_backlog_wait_overflow = 0; + +/* The identity of the user shutting down the audit system. */ +uid_t audit_sig_uid = -1; +pid_t audit_sig_pid = -1; + +/* Records can be lost in several ways: + 0) [suppressed in audit_alloc] + 1) out of memory in audit_log_start [kmalloc of struct audit_buffer] + 2) out of memory in audit_log_move [alloc_skb] + 3) suppressed due to audit_rate_limit + 4) suppressed due to audit_backlog_limit +*/ +static atomic_t audit_lost = ATOMIC_INIT(0); + +/* The netlink socket. */ +static struct sock *audit_sock; + +/* Inotify handle. */ +struct inotify_handle *audit_ih; + +/* The audit_freelist is a list of pre-allocated audit buffers (if more + * than AUDIT_MAXFREE are in use, the audit buffer is freed instead of + * being placed on the freelist). */ +static DEFINE_SPINLOCK(audit_freelist_lock); +static int audit_freelist_count; +static LIST_HEAD(audit_freelist); + +static struct sk_buff_head audit_skb_queue; +static struct task_struct *kauditd_task; +static DECLARE_WAIT_QUEUE_HEAD(kauditd_wait); +static DECLARE_WAIT_QUEUE_HEAD(audit_backlog_wait); + +/* Serialize requests from userspace. */ +static DEFINE_MUTEX(audit_cmd_mutex); + +/* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting + * audit records. Since printk uses a 1024 byte buffer, this buffer + * should be at least that large. */ +#define AUDIT_BUFSIZ 1024 + +/* AUDIT_MAXFREE is the number of empty audit_buffers we keep on the + * audit_freelist. Doing so eliminates many kmalloc/kfree calls. */ +#define AUDIT_MAXFREE (2*NR_CPUS) + +/* The audit_buffer is used when formatting an audit record. The caller + * locks briefly to get the record off the freelist or to allocate the + * buffer, and locks briefly to send the buffer to the netlink layer or + * to place it on a transmit queue. Multiple audit_buffers can be in + * use simultaneously. */ +struct audit_buffer { + struct list_head list; + struct sk_buff *skb; /* formatted skb ready to send */ + struct audit_context *ctx; /* NULL or associated context */ + gfp_t gfp_mask; +}; + +static void audit_set_pid(struct audit_buffer *ab, pid_t pid) +{ + struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data; + nlh->nlmsg_pid = pid; +} + +void audit_panic(const char *message) +{ + switch (audit_failure) + { + case AUDIT_FAIL_SILENT: + break; + case AUDIT_FAIL_PRINTK: + printk(KERN_ERR "audit: %s\n", message); + break; + case AUDIT_FAIL_PANIC: + panic("audit: %s\n", message); + break; + } +} + +static inline int audit_rate_check(void) +{ + static unsigned long last_check = 0; + static int messages = 0; + static DEFINE_SPINLOCK(lock); + unsigned long flags; + unsigned long now; + unsigned long elapsed; + int retval = 0; + + if (!audit_rate_limit) return 1; + + spin_lock_irqsave(&lock, flags); + if (++messages < audit_rate_limit) { + retval = 1; + } else { + now = jiffies; + elapsed = now - last_check; + if (elapsed > HZ) { + last_check = now; + messages = 0; + retval = 1; + } + } + spin_unlock_irqrestore(&lock, flags); + + return retval; +} + +/** + * audit_log_lost - conditionally log lost audit message event + * @message: the message stating reason for lost audit message + * + * Emit at least 1 message per second, even if audit_rate_check is + * throttling. + * Always increment the lost messages counter. +*/ +void audit_log_lost(const char *message) +{ + static unsigned long last_msg = 0; + static DEFINE_SPINLOCK(lock); + unsigned long flags; + unsigned long now; + int print; + + atomic_inc(&audit_lost); + + print = (audit_failure == AUDIT_FAIL_PANIC || !audit_rate_limit); + + if (!print) { + spin_lock_irqsave(&lock, flags); + now = jiffies; + if (now - last_msg > HZ) { + print = 1; + last_msg = now; + } + spin_unlock_irqrestore(&lock, flags); + } + + if (print) { + printk(KERN_WARNING + "audit: audit_lost=%d audit_rate_limit=%d audit_backlog_limit=%d\n", + atomic_read(&audit_lost), + audit_rate_limit, + audit_backlog_limit); + audit_panic(message); + } +} + +static int audit_set_rate_limit(int limit, uid_t loginuid, u32 sid) +{ + int old = audit_rate_limit; + + if (sid) { + char *ctx = NULL; + u32 len; + int rc; + if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) + return rc; + else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_rate_limit=%d old=%d by auid=%u subj=%s", + limit, old, loginuid, ctx); + kfree(ctx); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_rate_limit=%d old=%d by auid=%u", + limit, old, loginuid); + audit_rate_limit = limit; + return old; +} + +static int audit_set_backlog_limit(int limit, uid_t loginuid, u32 sid) +{ + int old = audit_backlog_limit; + + if (sid) { + char *ctx = NULL; + u32 len; + int rc; + if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) + return rc; + else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_backlog_limit=%d old=%d by auid=%u subj=%s", + limit, old, loginuid, ctx); + kfree(ctx); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_backlog_limit=%d old=%d by auid=%u", + limit, old, loginuid); + audit_backlog_limit = limit; + return old; +} + +static int audit_set_enabled(int state, uid_t loginuid, u32 sid) +{ + int old = audit_enabled; + + if (state != 0 && state != 1) + return -EINVAL; + + if (sid) { + char *ctx = NULL; + u32 len; + int rc; + if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) + return rc; + else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_enabled=%d old=%d by auid=%u subj=%s", + state, old, loginuid, ctx); + kfree(ctx); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_enabled=%d old=%d by auid=%u", + state, old, loginuid); + audit_enabled = state; + return old; +} + +static int audit_set_failure(int state, uid_t loginuid, u32 sid) +{ + int old = audit_failure; + + if (state != AUDIT_FAIL_SILENT + && state != AUDIT_FAIL_PRINTK + && state != AUDIT_FAIL_PANIC) + return -EINVAL; + + if (sid) { + char *ctx = NULL; + u32 len; + int rc; + if ((rc = selinux_ctxid_to_string(sid, &ctx, &len))) + return rc; + else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_failure=%d old=%d by auid=%u subj=%s", + state, old, loginuid, ctx); + kfree(ctx); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_failure=%d old=%d by auid=%u", + state, old, loginuid); + audit_failure = state; + return old; +} + +static int kauditd_thread(void *dummy) +{ + struct sk_buff *skb; + + while (1) { + skb = skb_dequeue(&audit_skb_queue); + wake_up(&audit_backlog_wait); + if (skb) { + if (audit_pid) { + int err = netlink_unicast(audit_sock, skb, audit_pid, 0); + if (err < 0) { + BUG_ON(err != -ECONNREFUSED); /* Shoudn't happen */ + printk(KERN_ERR "audit: *NO* daemon at audit_pid=%d\n", audit_pid); + audit_pid = 0; + } + } else { + printk(KERN_NOTICE "%s\n", skb->data + NLMSG_SPACE(0)); + kfree_skb(skb); + } + } else { + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&kauditd_wait, &wait); + + if (!skb_queue_len(&audit_skb_queue)) { + try_to_freeze(); + schedule(); + } + + __set_current_state(TASK_RUNNING); + remove_wait_queue(&kauditd_wait, &wait); + } + } + return 0; +} + +int audit_send_list(void *_dest) +{ + struct audit_netlink_list *dest = _dest; + int pid = dest->pid; + struct sk_buff *skb; + + while ((skb = __skb_dequeue(&dest->q)) != NULL) + netlink_unicast(audit_sock, skb, pid, 0); + + kfree(dest); + + return 0; +} + +struct sk_buff *audit_make_reply(int pid, int seq, int type, int done, + int multi, void *payload, int size) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + int len = NLMSG_SPACE(size); + void *data; + int flags = multi ? NLM_F_MULTI : 0; + int t = done ? NLMSG_DONE : type; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return NULL; + + nlh = NLMSG_PUT(skb, pid, seq, t, size); + nlh->nlmsg_flags = flags; + data = NLMSG_DATA(nlh); + memcpy(data, payload, size); + return skb; + +nlmsg_failure: /* Used by NLMSG_PUT */ + if (skb) + kfree_skb(skb); + return NULL; +} + +/** + * audit_send_reply - send an audit reply message via netlink + * @pid: process id to send reply to + * @seq: sequence number + * @type: audit message type + * @done: done (last) flag + * @multi: multi-part message flag + * @payload: payload data + * @size: payload size + * + * Allocates an skb, builds the netlink message, and sends it to the pid. + * No failure notifications. + */ +void audit_send_reply(int pid, int seq, int type, int done, int multi, + void *payload, int size) +{ + struct sk_buff *skb; + skb = audit_make_reply(pid, seq, type, done, multi, payload, size); + if (!skb) + return; + /* Ignore failure. It'll only happen if the sender goes away, + because our timeout is set to infinite. */ + netlink_unicast(audit_sock, skb, pid, 0); + return; +} + +/* + * Check for appropriate CAP_AUDIT_ capabilities on incoming audit + * control messages. + */ +static int audit_netlink_ok(kernel_cap_t eff_cap, u16 msg_type) +{ + int err = 0; + + switch (msg_type) { + case AUDIT_GET: + case AUDIT_LIST: + case AUDIT_LIST_RULES: + case AUDIT_SET: + case AUDIT_ADD: + case AUDIT_ADD_RULE: + case AUDIT_DEL: + case AUDIT_DEL_RULE: + case AUDIT_SIGNAL_INFO: + if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL)) + err = -EPERM; + break; + case AUDIT_USER: + case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG: + case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2: + if (!cap_raised(eff_cap, CAP_AUDIT_WRITE)) + err = -EPERM; + break; + default: /* bad msg */ + err = -EINVAL; + } + + return err; +} + +static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + u32 uid, pid, seq, sid; + void *data; + struct audit_status *status_get, status_set; + int err; + struct audit_buffer *ab; + u16 msg_type = nlh->nlmsg_type; + uid_t loginuid; /* loginuid of sender */ + struct audit_sig_info sig_data; + + err = audit_netlink_ok(NETLINK_CB(skb).eff_cap, msg_type); + if (err) + return err; + + /* As soon as there's any sign of userspace auditd, + * start kauditd to talk to it */ + if (!kauditd_task) + kauditd_task = kthread_run(kauditd_thread, NULL, "kauditd"); + if (IS_ERR(kauditd_task)) { + err = PTR_ERR(kauditd_task); + kauditd_task = NULL; + return err; + } + + pid = NETLINK_CREDS(skb)->pid; + uid = NETLINK_CREDS(skb)->uid; + loginuid = NETLINK_CB(skb).loginuid; + sid = NETLINK_CB(skb).sid; + seq = nlh->nlmsg_seq; + data = NLMSG_DATA(nlh); + + switch (msg_type) { + case AUDIT_GET: + status_set.enabled = audit_enabled; + status_set.failure = audit_failure; + status_set.pid = audit_pid; + status_set.rate_limit = audit_rate_limit; + status_set.backlog_limit = audit_backlog_limit; + status_set.lost = atomic_read(&audit_lost); + status_set.backlog = skb_queue_len(&audit_skb_queue); + audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_GET, 0, 0, + &status_set, sizeof(status_set)); + break; + case AUDIT_SET: + if (nlh->nlmsg_len < sizeof(struct audit_status)) + return -EINVAL; + status_get = (struct audit_status *)data; + if (status_get->mask & AUDIT_STATUS_ENABLED) { + err = audit_set_enabled(status_get->enabled, + loginuid, sid); + if (err < 0) return err; + } + if (status_get->mask & AUDIT_STATUS_FAILURE) { + err = audit_set_failure(status_get->failure, + loginuid, sid); + if (err < 0) return err; + } + if (status_get->mask & AUDIT_STATUS_PID) { + int old = audit_pid; + if (sid) { + char *ctx = NULL; + u32 len; + int rc; + if ((rc = selinux_ctxid_to_string( + sid, &ctx, &len))) + return rc; + else + audit_log(NULL, GFP_KERNEL, + AUDIT_CONFIG_CHANGE, + "audit_pid=%d old=%d by auid=%u subj=%s", + status_get->pid, old, + loginuid, ctx); + kfree(ctx); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit_pid=%d old=%d by auid=%u", + status_get->pid, old, loginuid); + audit_pid = status_get->pid; + } + if (status_get->mask & AUDIT_STATUS_RATE_LIMIT) + audit_set_rate_limit(status_get->rate_limit, + loginuid, sid); + if (status_get->mask & AUDIT_STATUS_BACKLOG_LIMIT) + audit_set_backlog_limit(status_get->backlog_limit, + loginuid, sid); + break; + case AUDIT_USER: + case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG: + case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2: + if (!audit_enabled && msg_type != AUDIT_USER_AVC) + return 0; + + err = audit_filter_user(&NETLINK_CB(skb), msg_type); + if (err == 1) { + err = 0; + ab = audit_log_start(NULL, GFP_KERNEL, msg_type); + if (ab) { + audit_log_format(ab, + "user pid=%d uid=%u auid=%u", + pid, uid, loginuid); + if (sid) { + char *ctx = NULL; + u32 len; + if (selinux_ctxid_to_string( + sid, &ctx, &len)) { + audit_log_format(ab, + " ssid=%u", sid); + /* Maybe call audit_panic? */ + } else + audit_log_format(ab, + " subj=%s", ctx); + kfree(ctx); + } + audit_log_format(ab, " msg='%.1024s'", + (char *)data); + audit_set_pid(ab, pid); + audit_log_end(ab); + } + } + break; + case AUDIT_ADD: + case AUDIT_DEL: + if (nlmsg_len(nlh) < sizeof(struct audit_rule)) + return -EINVAL; + /* fallthrough */ + case AUDIT_LIST: + err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid, + uid, seq, data, nlmsg_len(nlh), + loginuid, sid); + break; + case AUDIT_ADD_RULE: + case AUDIT_DEL_RULE: + if (nlmsg_len(nlh) < sizeof(struct audit_rule_data)) + return -EINVAL; + /* fallthrough */ + case AUDIT_LIST_RULES: + err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid, + uid, seq, data, nlmsg_len(nlh), + loginuid, sid); + break; + case AUDIT_SIGNAL_INFO: + sig_data.uid = audit_sig_uid; + sig_data.pid = audit_sig_pid; + audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_SIGNAL_INFO, + 0, 0, &sig_data, sizeof(sig_data)); + break; + default: + err = -EINVAL; + break; + } + + return err < 0 ? err : 0; +} + +/* + * Get message from skb (based on rtnetlink_rcv_skb). Each message is + * processed by audit_receive_msg. Malformed skbs with wrong length are + * discarded silently. + */ +static void audit_receive_skb(struct sk_buff *skb) +{ + int err; + struct nlmsghdr *nlh; + u32 rlen; + + while (skb->len >= NLMSG_SPACE(0)) { + nlh = (struct nlmsghdr *)skb->data; + if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len) + return; + rlen = NLMSG_ALIGN(nlh->nlmsg_len); + if (rlen > skb->len) + rlen = skb->len; + if ((err = audit_receive_msg(skb, nlh))) { + netlink_ack(skb, nlh, err); + } else if (nlh->nlmsg_flags & NLM_F_ACK) + netlink_ack(skb, nlh, 0); + skb_pull(skb, rlen); + } +} + +/* Receive messages from netlink socket. */ +static void audit_receive(struct sock *sk, int length) +{ + struct sk_buff *skb; + unsigned int qlen; + + mutex_lock(&audit_cmd_mutex); + + for (qlen = skb_queue_len(&sk->sk_receive_queue); qlen; qlen--) { + skb = skb_dequeue(&sk->sk_receive_queue); + audit_receive_skb(skb); + kfree_skb(skb); + } + mutex_unlock(&audit_cmd_mutex); +} + + +/* Initialize audit support at boot time. */ +static int __init audit_init(void) +{ + printk(KERN_INFO "audit: initializing netlink socket (%s)\n", + audit_default ? "enabled" : "disabled"); + audit_sock = netlink_kernel_create(NETLINK_AUDIT, 0, audit_receive, + THIS_MODULE); + if (!audit_sock) + audit_panic("cannot initialize netlink socket"); + else + audit_sock->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT; + + skb_queue_head_init(&audit_skb_queue); + audit_initialized = 1; + audit_enabled = audit_default; + + /* Register the callback with selinux. This callback will be invoked + * when a new policy is loaded. */ + selinux_audit_set_callback(&selinux_audit_rule_update); + + audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized"); + +#ifdef CONFIG_AUDITSYSCALL + audit_ih = inotify_init(audit_handle_ievent); + if (IS_ERR(audit_ih)) + audit_panic("cannot initialize inotify handle"); +#endif + + return 0; +} +__initcall(audit_init); + +/* Process kernel command-line parameter at boot time. audit=0 or audit=1. */ +static int __init audit_enable(char *str) +{ + audit_default = !!simple_strtol(str, NULL, 0); + printk(KERN_INFO "audit: %s%s\n", + audit_default ? "enabled" : "disabled", + audit_initialized ? "" : " (after initialization)"); + if (audit_initialized) + audit_enabled = audit_default; + return 1; +} + +__setup("audit=", audit_enable); + +static void audit_buffer_free(struct audit_buffer *ab) +{ + unsigned long flags; + + if (!ab) + return; + + if (ab->skb) + kfree_skb(ab->skb); + + spin_lock_irqsave(&audit_freelist_lock, flags); + if (++audit_freelist_count > AUDIT_MAXFREE) + kfree(ab); + else + list_add(&ab->list, &audit_freelist); + spin_unlock_irqrestore(&audit_freelist_lock, flags); +} + +static struct audit_buffer * audit_buffer_alloc(struct audit_context *ctx, + gfp_t gfp_mask, int type) +{ + unsigned long flags; + struct audit_buffer *ab = NULL; + struct nlmsghdr *nlh; + + spin_lock_irqsave(&audit_freelist_lock, flags); + if (!list_empty(&audit_freelist)) { + ab = list_entry(audit_freelist.next, + struct audit_buffer, list); + list_del(&ab->list); + --audit_freelist_count; + } + spin_unlock_irqrestore(&audit_freelist_lock, flags); + + if (!ab) { + ab = kmalloc(sizeof(*ab), gfp_mask); + if (!ab) + goto err; + } + + ab->skb = alloc_skb(AUDIT_BUFSIZ, gfp_mask); + if (!ab->skb) + goto err; + + ab->ctx = ctx; + ab->gfp_mask = gfp_mask; + nlh = (struct nlmsghdr *)skb_put(ab->skb, NLMSG_SPACE(0)); + nlh->nlmsg_type = type; + nlh->nlmsg_flags = 0; + nlh->nlmsg_pid = 0; + nlh->nlmsg_seq = 0; + return ab; +err: + audit_buffer_free(ab); + return NULL; +} + +/** + * audit_serial - compute a serial number for the audit record + * + * Compute a serial number for the audit record. Audit records are + * written to user-space as soon as they are generated, so a complete + * audit record may be written in several pieces. The timestamp of the + * record and this serial number are used by the user-space tools to + * determine which pieces belong to the same audit record. The + * (timestamp,serial) tuple is unique for each syscall and is live from + * syscall entry to syscall exit. + * + * NOTE: Another possibility is to store the formatted records off the + * audit context (for those records that have a context), and emit them + * all at syscall exit. However, this could delay the reporting of + * significant errors until syscall exit (or never, if the system + * halts). + */ +unsigned int audit_serial(void) +{ + static spinlock_t serial_lock = SPIN_LOCK_UNLOCKED; + static unsigned int serial = 0; + + unsigned long flags; + unsigned int ret; + + spin_lock_irqsave(&serial_lock, flags); + do { + ret = ++serial; + } while (unlikely(!ret)); + spin_unlock_irqrestore(&serial_lock, flags); + + return ret; +} + +static inline void audit_get_stamp(struct audit_context *ctx, + struct timespec *t, unsigned int *serial) +{ + if (ctx) + auditsc_get_stamp(ctx, t, serial); + else { + *t = CURRENT_TIME; + *serial = audit_serial(); + } +} + +/* Obtain an audit buffer. This routine does locking to obtain the + * audit buffer, but then no locking is required for calls to + * audit_log_*format. If the tsk is a task that is currently in a + * syscall, then the syscall is marked as auditable and an audit record + * will be written at syscall exit. If there is no associated task, tsk + * should be NULL. */ + +/** + * audit_log_start - obtain an audit buffer + * @ctx: audit_context (may be NULL) + * @gfp_mask: type of allocation + * @type: audit message type + * + * Returns audit_buffer pointer on success or NULL on error. + * + * Obtain an audit buffer. This routine does locking to obtain the + * audit buffer, but then no locking is required for calls to + * audit_log_*format. If the task (ctx) is a task that is currently in a + * syscall, then the syscall is marked as auditable and an audit record + * will be written at syscall exit. If there is no associated task, then + * task context (ctx) should be NULL. + */ +struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, + int type) +{ + struct audit_buffer *ab = NULL; + struct timespec t; + unsigned int serial; + int reserve; + unsigned long timeout_start = jiffies; + + if (!audit_initialized) + return NULL; + + if (unlikely(audit_filter_type(type))) + return NULL; + + if (gfp_mask & __GFP_WAIT) + reserve = 0; + else + reserve = 5; /* Allow atomic callers to go up to five + entries over the normal backlog limit */ + + while (audit_backlog_limit + && skb_queue_len(&audit_skb_queue) > audit_backlog_limit + reserve) { + if (gfp_mask & __GFP_WAIT && audit_backlog_wait_time + && time_before(jiffies, timeout_start + audit_backlog_wait_time)) { + + /* Wait for auditd to drain the queue a little */ + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&audit_backlog_wait, &wait); + + if (audit_backlog_limit && + skb_queue_len(&audit_skb_queue) > audit_backlog_limit) + schedule_timeout(timeout_start + audit_backlog_wait_time - jiffies); + + __set_current_state(TASK_RUNNING); + remove_wait_queue(&audit_backlog_wait, &wait); + continue; + } + if (audit_rate_check()) + printk(KERN_WARNING + "audit: audit_backlog=%d > " + "audit_backlog_limit=%d\n", + skb_queue_len(&audit_skb_queue), + audit_backlog_limit); + audit_log_lost("backlog limit exceeded"); + audit_backlog_wait_time = audit_backlog_wait_overflow; + wake_up(&audit_backlog_wait); + return NULL; + } + + ab = audit_buffer_alloc(ctx, gfp_mask, type); + if (!ab) { + audit_log_lost("out of memory in audit_log_start"); + return NULL; + } + + audit_get_stamp(ab->ctx, &t, &serial); + + audit_log_format(ab, "audit(%lu.%03lu:%u): ", + t.tv_sec, t.tv_nsec/1000000, serial); + return ab; +} + +/** + * audit_expand - expand skb in the audit buffer + * @ab: audit_buffer + * @extra: space to add at tail of the skb + * + * Returns 0 (no space) on failed expansion, or available space if + * successful. + */ +static inline int audit_expand(struct audit_buffer *ab, int extra) +{ + struct sk_buff *skb = ab->skb; + int ret = pskb_expand_head(skb, skb_headroom(skb), extra, + ab->gfp_mask); + if (ret < 0) { + audit_log_lost("out of memory in audit_expand"); + return 0; + } + return skb_tailroom(skb); +} + +/* + * Format an audit message into the audit buffer. If there isn't enough + * room in the audit buffer, more room will be allocated and vsnprint + * will be called a second time. Currently, we assume that a printk + * can't format message larger than 1024 bytes, so we don't either. + */ +static void audit_log_vformat(struct audit_buffer *ab, const char *fmt, + va_list args) +{ + int len, avail; + struct sk_buff *skb; + va_list args2; + + if (!ab) + return; + + BUG_ON(!ab->skb); + skb = ab->skb; + avail = skb_tailroom(skb); + if (avail == 0) { + avail = audit_expand(ab, AUDIT_BUFSIZ); + if (!avail) + goto out; + } + va_copy(args2, args); + len = vsnprintf(skb->tail, avail, fmt, args); + if (len >= avail) { + /* The printk buffer is 1024 bytes long, so if we get + * here and AUDIT_BUFSIZ is at least 1024, then we can + * log everything that printk could have logged. */ + avail = audit_expand(ab, + max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail)); + if (!avail) + goto out; + len = vsnprintf(skb->tail, avail, fmt, args2); + } + if (len > 0) + skb_put(skb, len); +out: + return; +} + +/** + * audit_log_format - format a message into the audit buffer. + * @ab: audit_buffer + * @fmt: format string + * @...: optional parameters matching @fmt string + * + * All the work is done in audit_log_vformat. + */ +void audit_log_format(struct audit_buffer *ab, const char *fmt, ...) +{ + va_list args; + + if (!ab) + return; + va_start(args, fmt); + audit_log_vformat(ab, fmt, args); + va_end(args); +} + +/** + * audit_log_hex - convert a buffer to hex and append it to the audit skb + * @ab: the audit_buffer + * @buf: buffer to convert to hex + * @len: length of @buf to be converted + * + * No return value; failure to expand is silently ignored. + * + * This function will take the passed buf and convert it into a string of + * ascii hex digits. The new string is placed onto the skb. + */ +void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf, + size_t len) +{ + int i, avail, new_len; + unsigned char *ptr; + struct sk_buff *skb; + static const unsigned char *hex = "0123456789ABCDEF"; + + BUG_ON(!ab->skb); + skb = ab->skb; + avail = skb_tailroom(skb); + new_len = len<<1; + if (new_len >= avail) { + /* Round the buffer request up to the next multiple */ + new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1); + avail = audit_expand(ab, new_len); + if (!avail) + return; + } + + ptr = skb->tail; + for (i=0; i>4]; /* Upper nibble */ + *ptr++ = hex[buf[i] & 0x0F]; /* Lower nibble */ + } + *ptr = 0; + skb_put(skb, len << 1); /* new string is twice the old string */ +} + +/** + * audit_log_unstrustedstring - log a string that may contain random characters + * @ab: audit_buffer + * @string: string to be logged + * + * This code will escape a string that is passed to it if the string + * contains a control character, unprintable character, double quote mark, + * or a space. Unescaped strings will start and end with a double quote mark. + * Strings that are escaped are printed in hex (2 digits per char). + */ +const char *audit_log_untrustedstring(struct audit_buffer *ab, const char *string) +{ + const unsigned char *p = string; + size_t len = strlen(string); + + while (*p) { + if (*p == '"' || *p < 0x21 || *p > 0x7f) { + audit_log_hex(ab, string, len); + return string + len + 1; + } + p++; + } + audit_log_format(ab, "\"%s\"", string); + return p + 1; +} + +/* This is a helper-function to print the escaped d_path */ +void audit_log_d_path(struct audit_buffer *ab, const char *prefix, + struct dentry *dentry, struct vfsmount *vfsmnt) +{ + char *p, *path; + + if (prefix) + audit_log_format(ab, " %s", prefix); + + /* We will allow 11 spaces for ' (deleted)' to be appended */ + path = kmalloc(PATH_MAX+11, ab->gfp_mask); + if (!path) { + audit_log_format(ab, ""); + return; + } + p = d_path(dentry, vfsmnt, path, PATH_MAX+11); + if (IS_ERR(p)) { /* Should never happen since we send PATH_MAX */ + /* FIXME: can we save some information here? */ + audit_log_format(ab, ""); + } else + audit_log_untrustedstring(ab, p); + kfree(path); +} + +/** + * audit_log_end - end one audit record + * @ab: the audit_buffer + * + * The netlink_* functions cannot be called inside an irq context, so + * the audit buffer is placed on a queue and a tasklet is scheduled to + * remove them from the queue outside the irq context. May be called in + * any context. + */ +void audit_log_end(struct audit_buffer *ab) +{ + if (!ab) + return; + if (!audit_rate_check()) { + audit_log_lost("rate limit exceeded"); + } else { + if (audit_pid) { + struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data; + nlh->nlmsg_len = ab->skb->len - NLMSG_SPACE(0); + skb_queue_tail(&audit_skb_queue, ab->skb); + ab->skb = NULL; + wake_up_interruptible(&kauditd_wait); + } else { + printk(KERN_NOTICE "%s\n", ab->skb->data + NLMSG_SPACE(0)); + } + } + audit_buffer_free(ab); +} + +/** + * audit_log - Log an audit record + * @ctx: audit context + * @gfp_mask: type of allocation + * @type: audit message type + * @fmt: format string to use + * @...: variable parameters matching the format string + * + * This is a convenience function that calls audit_log_start, + * audit_log_vformat, and audit_log_end. It may be called + * in any context. + */ +void audit_log(struct audit_context *ctx, gfp_t gfp_mask, int type, + const char *fmt, ...) +{ + struct audit_buffer *ab; + va_list args; + + ab = audit_log_start(ctx, gfp_mask, type); + if (ab) { + va_start(args, fmt); + audit_log_vformat(ab, fmt, args); + va_end(args); + audit_log_end(ab); + } +} + +EXPORT_SYMBOL(audit_log_start); +EXPORT_SYMBOL(audit_log_end); +EXPORT_SYMBOL(audit_log_format); +EXPORT_SYMBOL(audit_log); diff --git a/kernel/audit/audit.h b/kernel/audit/audit.h new file mode 100644 index 0000000..771833d --- /dev/null +++ b/kernel/audit/audit.h @@ -0,0 +1,118 @@ +/* audit -- definition of audit_context structure and supporting types + * + * Copyright 2003-2004 Red Hat, Inc. + * Copyright 2005 Hewlett-Packard Development Company, L.P. + * Copyright 2005 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +/* 0 = no checking + 1 = put_count checking + 2 = verbose put_count checking +*/ +#define AUDIT_DEBUG 0 + +/* At task start time, the audit_state is set in the audit_context using + a per-task filter. At syscall entry, the audit_state is augmented by + the syscall filter. */ +enum audit_state { + AUDIT_DISABLED, /* Do not create per-task audit_context. + * No syscall-specific audit records can + * be generated. */ + AUDIT_SETUP_CONTEXT, /* Create the per-task audit_context, + * but don't necessarily fill it in at + * syscall entry time (i.e., filter + * instead). */ + AUDIT_BUILD_CONTEXT, /* Create the per-task audit_context, + * and always fill it in at syscall + * entry time. This makes a full + * syscall record available if some + * other part of the kernel decides it + * should be recorded. */ + AUDIT_RECORD_CONTEXT /* Create the per-task audit_context, + * always fill it in at syscall entry + * time, and always write out the audit + * record at syscall exit time. */ +}; + +/* Rule lists */ +struct audit_parent; + +struct audit_watch { + atomic_t count; /* reference count */ + char *path; /* insertion path */ + dev_t dev; /* associated superblock device */ + unsigned long ino; /* associated inode number */ + struct audit_parent *parent; /* associated parent */ + struct list_head wlist; /* entry in parent->watches list */ + struct list_head rules; /* associated rules */ +}; + +struct audit_field { + u32 type; + u32 val; + u32 op; + char *se_str; + struct selinux_audit_rule *se_rule; +}; + +struct audit_krule { + int vers_ops; + u32 flags; + u32 listnr; + u32 action; + u32 mask[AUDIT_BITMASK_SIZE]; + u32 buflen; /* for data alloc on list rules */ + u32 field_count; + struct audit_field *fields; + struct audit_watch *watch; /* associated watch */ + struct list_head rlist; /* entry in audit_watch.rules list */ +}; + +struct audit_entry { + struct list_head list; + struct rcu_head rcu; + struct audit_krule rule; +}; + + +extern int audit_pid; +extern int audit_comparator(const u32 left, const u32 op, const u32 right); +extern int audit_compare_dname_path(const char *dname, const char *path); +extern struct sk_buff * audit_make_reply(int pid, int seq, int type, + int done, int multi, + void *payload, int size); +extern void audit_send_reply(int pid, int seq, int type, + int done, int multi, + void *payload, int size); +extern void audit_log_lost(const char *message); +extern void audit_panic(const char *message); + +struct audit_netlink_list { + int pid; + struct sk_buff_head q; +}; + +int audit_send_list(void *); + +struct inotify_watch; +extern void audit_handle_ievent(struct inotify_watch *, u32, u32, u32, + const char *, struct inode *); +extern int selinux_audit_rule_update(void); + diff --git a/kernel/audit/filter.c b/kernel/audit/filter.c new file mode 100644 index 0000000..35dca7e --- /dev/null +++ b/kernel/audit/filter.c @@ -0,0 +1,1466 @@ +/* auditfilter.c -- filtering of audit events + * + * Copyright 2003-2004 Red Hat, Inc. + * Copyright 2005 Hewlett-Packard Development Company, L.P. + * Copyright 2005 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audit.h" + +/* + * Locking model: + * + * audit_filter_mutex: + * Synchronizes writes and blocking reads of audit's filterlist + * data. Rcu is used to traverse the filterlist and access + * contents of structs audit_entry, audit_watch and opaque + * selinux rules during filtering. If modified, these structures + * must be copied and replace their counterparts in the filterlist. + * An audit_parent struct is not accessed during filtering, so may + * be written directly provided audit_filter_mutex is held. + */ + +/* + * Reference counting: + * + * audit_parent: lifetime is from audit_init_parent() to receipt of an IN_IGNORED + * event. Each audit_watch holds a reference to its associated parent. + * + * audit_watch: if added to lists, lifetime is from audit_init_watch() to one + * of: audit_remove_watch() [user removes], audit_update_watch() [kernel + * replaces], or audit_remove_parent_watches() [kernel removes]. + * Additionally, an audit_watch may exist temporarily to assist in + * searching existing filter data. Each audit_krule holds a reference to + * its associated watch. + */ + +struct audit_parent { + atomic_t count; /* reference count */ + struct list_head ilist; /* entry in inotify registration list */ + struct list_head watches; /* associated watches */ + struct inotify_watch wdata; /* inotify watch data */ + unsigned flags; /* status flags */ +}; + +/* + * audit_parent status flags: + * + * AUDIT_PARENT_INVALID - set anytime rules/watches are auto-removed due to + * a filesystem event. Technically not needed for IN_DELETE_SELF or IN_UNMOUNT + * events, as we cannot receive them while we have nameidata (during rule add) + * and the audit_parent is immediately removed when processing the following + * IN_IGNORED event. The IN_MOVE_SELF event is different. We can receive it + * while holding nameidata, and inotify will not send us the IN_IGNORED so we + * must later remove the inotify watch on audit_parent ourselves. + */ +#define AUDIT_PARENT_INVALID 0x001 + +/* Audit filter lists, defined in */ +struct list_head audit_filter_list[AUDIT_NR_FILTERS] = { + LIST_HEAD_INIT(audit_filter_list[0]), + LIST_HEAD_INIT(audit_filter_list[1]), + LIST_HEAD_INIT(audit_filter_list[2]), + LIST_HEAD_INIT(audit_filter_list[3]), + LIST_HEAD_INIT(audit_filter_list[4]), + LIST_HEAD_INIT(audit_filter_list[5]), +#if AUDIT_NR_FILTERS != 6 +#error Fix audit_filter_list initialiser +#endif +}; + +DEFINE_MUTEX(audit_filter_mutex); + +/* Inotify handle */ +extern struct inotify_handle *audit_ih; + +/* Inotify events we care about. */ +#define AUDIT_IN_WATCH IN_MOVE|IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF +#define AUDIT_IN_SELF IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT + +static inline void audit_get_parent(struct audit_parent *parent) +{ + atomic_inc(&parent->count); +} + +static inline void audit_put_parent(struct audit_parent *parent) +{ + if (atomic_dec_and_test(&parent->count)) { + WARN_ON(!list_empty(&parent->watches)); + kfree(parent); + } +} + +static inline void audit_get_watch(struct audit_watch *watch) +{ + atomic_inc(&watch->count); +} + +static inline void audit_put_watch(struct audit_watch *watch) +{ + if (atomic_dec_and_test(&watch->count)) { + WARN_ON(!list_empty(&watch->rules)); + /* watches that were never added don't have a parent */ + if (watch->parent) + audit_put_parent(watch->parent); + kfree(watch->path); + kfree(watch); + } +} + +static inline void audit_free_rule(struct audit_entry *e) +{ + int i; + + /* some rules don't have associated watches */ + if (e->rule.watch) + audit_put_watch(e->rule.watch); + if (e->rule.fields) + for (i = 0; i < e->rule.field_count; i++) { + struct audit_field *f = &e->rule.fields[i]; + kfree(f->se_str); + selinux_audit_rule_free(f->se_rule); + } + kfree(e->rule.fields); + kfree(e); +} + +static inline void audit_free_rule_rcu(struct rcu_head *head) +{ + struct audit_entry *e = container_of(head, struct audit_entry, rcu); + audit_free_rule(e); +} + +/* Initialize a parent watch entry. */ +static inline struct audit_parent *audit_init_parent(void) +{ + struct audit_parent *parent; + + parent = kzalloc(sizeof(*parent), GFP_KERNEL); + if (unlikely(!parent)) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&parent->watches); + atomic_set(&parent->count, 1); + parent->flags = 0; + + return parent; +} + +/* Initialize a watch entry. */ +static inline struct audit_watch *audit_init_watch(char *path) +{ + struct audit_watch *watch; + + watch = kzalloc(sizeof(*watch), GFP_KERNEL); + if (unlikely(!watch)) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&watch->rules); + atomic_set(&watch->count, 1); + watch->path = path; + watch->dev = (dev_t)-1; + watch->ino = (unsigned long)-1; + + return watch; +} + +/* Initialize an audit filterlist entry. */ +static inline struct audit_entry *audit_init_entry(u32 field_count) +{ + struct audit_entry *entry; + struct audit_field *fields; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (unlikely(!entry)) + return NULL; + + fields = kzalloc(sizeof(*fields) * field_count, GFP_KERNEL); + if (unlikely(!fields)) { + kfree(entry); + return NULL; + } + entry->rule.fields = fields; + + return entry; +} + +/* Unpack a filter field's string representation from user-space + * buffer. */ +static char *audit_unpack_string(void **bufp, size_t *remain, size_t len) +{ + char *str; + + if (!*bufp || (len == 0) || (len > *remain)) + return ERR_PTR(-EINVAL); + + /* Of the currently implemented string fields, PATH_MAX + * defines the longest valid length. + */ + if (len > PATH_MAX) + return ERR_PTR(-ENAMETOOLONG); + + str = kmalloc(len + 1, GFP_KERNEL); + if (unlikely(!str)) + return ERR_PTR(-ENOMEM); + + memcpy(str, *bufp, len); + str[len] = 0; + *bufp += len; + *remain -= len; + + return str; +} + +/* Translate a watch string to kernel respresentation. */ +static int audit_to_watch(struct audit_krule *krule, char *path, int len, + u32 op) +{ + struct audit_watch *watch; + + if (path[0] != '/' || path[len-1] == '/' || + krule->listnr != AUDIT_FILTER_EXIT || + op & ~AUDIT_EQUAL || + krule->watch) /* allow only 1 watch per rule */ + return -EINVAL; + + /* ensure inotify handle was initialized */ + if (!audit_ih) + return -EOPNOTSUPP; + + watch = audit_init_watch(path); + if (unlikely(IS_ERR(watch))) + return PTR_ERR(watch); + + audit_get_watch(watch); + krule->watch = watch; + + return 0; +} + +/* Common user-space to kernel rule translation. */ +static inline struct audit_entry *audit_to_entry_common(struct audit_rule *rule) +{ + unsigned listnr; + struct audit_entry *entry; + int i, err; + + err = -EINVAL; + listnr = rule->flags & ~AUDIT_FILTER_PREPEND; + switch(listnr) { + default: + goto exit_err; + case AUDIT_FILTER_USER: + case AUDIT_FILTER_TYPE: +#ifdef CONFIG_AUDITSYSCALL + case AUDIT_FILTER_ENTRY: + case AUDIT_FILTER_EXIT: + case AUDIT_FILTER_TASK: +#endif + ; + } + if (rule->action != AUDIT_NEVER && rule->action != AUDIT_POSSIBLE && + rule->action != AUDIT_ALWAYS) + goto exit_err; + if (rule->field_count > AUDIT_MAX_FIELDS) + goto exit_err; + + err = -ENOMEM; + entry = audit_init_entry(rule->field_count); + if (!entry) + goto exit_err; + + entry->rule.flags = rule->flags & AUDIT_FILTER_PREPEND; + entry->rule.listnr = listnr; + entry->rule.action = rule->action; + entry->rule.field_count = rule->field_count; + + for (i = 0; i < AUDIT_BITMASK_SIZE; i++) + entry->rule.mask[i] = rule->mask[i]; + + return entry; + +exit_err: + return ERR_PTR(err); +} + +/* Translate struct audit_rule to kernel's rule respresentation. + * Exists for backward compatibility with userspace. */ +static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule) +{ + struct audit_entry *entry; + int err = 0; + int i; + + entry = audit_to_entry_common(rule); + if (IS_ERR(entry)) + goto exit_nofree; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &entry->rule.fields[i]; + + f->op = rule->fields[i] & (AUDIT_NEGATE|AUDIT_OPERATORS); + f->type = rule->fields[i] & ~(AUDIT_NEGATE|AUDIT_OPERATORS); + f->val = rule->values[i]; + + if (f->type & AUDIT_UNUSED_BITS || + f->type == AUDIT_SE_USER || + f->type == AUDIT_SE_ROLE || + f->type == AUDIT_SE_TYPE || + f->type == AUDIT_SE_SEN || + f->type == AUDIT_SE_CLR || + f->type == AUDIT_WATCH) { + err = -EINVAL; + goto exit_free; + } + + entry->rule.vers_ops = (f->op & AUDIT_OPERATORS) ? 2 : 1; + + /* Support for legacy operators where + * AUDIT_NEGATE bit signifies != and otherwise assumes == */ + if (f->op & AUDIT_NEGATE) + f->op = AUDIT_NOT_EQUAL; + else if (!f->op) + f->op = AUDIT_EQUAL; + else if (f->op == AUDIT_OPERATORS) { + err = -EINVAL; + goto exit_free; + } + } + +exit_nofree: + return entry; + +exit_free: + audit_free_rule(entry); + return ERR_PTR(err); +} + +/* Translate struct audit_rule_data to kernel's rule respresentation. */ +static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data, + size_t datasz) +{ + int err = 0; + struct audit_entry *entry; + void *bufp; + size_t remain = datasz - sizeof(struct audit_rule_data); + int i; + char *str; + + entry = audit_to_entry_common((struct audit_rule *)data); + if (IS_ERR(entry)) + goto exit_nofree; + + bufp = data->buf; + entry->rule.vers_ops = 2; + for (i = 0; i < data->field_count; i++) { + struct audit_field *f = &entry->rule.fields[i]; + + err = -EINVAL; + if (!(data->fieldflags[i] & AUDIT_OPERATORS) || + data->fieldflags[i] & ~AUDIT_OPERATORS) + goto exit_free; + + f->op = data->fieldflags[i] & AUDIT_OPERATORS; + f->type = data->fields[i]; + f->val = data->values[i]; + f->se_str = NULL; + f->se_rule = NULL; + switch(f->type) { + case AUDIT_SE_USER: + case AUDIT_SE_ROLE: + case AUDIT_SE_TYPE: + case AUDIT_SE_SEN: + case AUDIT_SE_CLR: + str = audit_unpack_string(&bufp, &remain, f->val); + if (IS_ERR(str)) + goto exit_free; + entry->rule.buflen += f->val; + + err = selinux_audit_rule_init(f->type, f->op, str, + &f->se_rule); + /* Keep currently invalid fields around in case they + * become valid after a policy reload. */ + if (err == -EINVAL) { + printk(KERN_WARNING "audit rule for selinux " + "\'%s\' is invalid\n", str); + err = 0; + } + if (err) { + kfree(str); + goto exit_free; + } else + f->se_str = str; + break; + case AUDIT_WATCH: + str = audit_unpack_string(&bufp, &remain, f->val); + if (IS_ERR(str)) + goto exit_free; + entry->rule.buflen += f->val; + + err = audit_to_watch(&entry->rule, str, f->val, f->op); + if (err) { + kfree(str); + goto exit_free; + } + break; + } + } + +exit_nofree: + return entry; + +exit_free: + audit_free_rule(entry); + return ERR_PTR(err); +} + +/* Pack a filter field's string representation into data block. */ +static inline size_t audit_pack_string(void **bufp, char *str) +{ + size_t len = strlen(str); + + memcpy(*bufp, str, len); + *bufp += len; + + return len; +} + +/* Translate kernel rule respresentation to struct audit_rule. + * Exists for backward compatibility with userspace. */ +static struct audit_rule *audit_krule_to_rule(struct audit_krule *krule) +{ + struct audit_rule *rule; + int i; + + rule = kmalloc(sizeof(*rule), GFP_KERNEL); + if (unlikely(!rule)) + return ERR_PTR(-ENOMEM); + memset(rule, 0, sizeof(*rule)); + + rule->flags = krule->flags | krule->listnr; + rule->action = krule->action; + rule->field_count = krule->field_count; + for (i = 0; i < rule->field_count; i++) { + rule->values[i] = krule->fields[i].val; + rule->fields[i] = krule->fields[i].type; + + if (krule->vers_ops == 1) { + if (krule->fields[i].op & AUDIT_NOT_EQUAL) + rule->fields[i] |= AUDIT_NEGATE; + } else { + rule->fields[i] |= krule->fields[i].op; + } + } + for (i = 0; i < AUDIT_BITMASK_SIZE; i++) rule->mask[i] = krule->mask[i]; + + return rule; +} + +/* Translate kernel rule respresentation to struct audit_rule_data. */ +static struct audit_rule_data *audit_krule_to_data(struct audit_krule *krule) +{ + struct audit_rule_data *data; + void *bufp; + int i; + + data = kmalloc(sizeof(*data) + krule->buflen, GFP_KERNEL); + if (unlikely(!data)) + return ERR_PTR(-ENOMEM); + memset(data, 0, sizeof(*data)); + + data->flags = krule->flags | krule->listnr; + data->action = krule->action; + data->field_count = krule->field_count; + bufp = data->buf; + for (i = 0; i < data->field_count; i++) { + struct audit_field *f = &krule->fields[i]; + + data->fields[i] = f->type; + data->fieldflags[i] = f->op; + switch(f->type) { + case AUDIT_SE_USER: + case AUDIT_SE_ROLE: + case AUDIT_SE_TYPE: + case AUDIT_SE_SEN: + case AUDIT_SE_CLR: + data->buflen += data->values[i] = + audit_pack_string(&bufp, f->se_str); + break; + case AUDIT_WATCH: + data->buflen += data->values[i] = + audit_pack_string(&bufp, krule->watch->path); + break; + default: + data->values[i] = f->val; + } + } + for (i = 0; i < AUDIT_BITMASK_SIZE; i++) data->mask[i] = krule->mask[i]; + + return data; +} + +/* Compare two rules in kernel format. Considered success if rules + * don't match. */ +static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b) +{ + int i; + + if (a->flags != b->flags || + a->listnr != b->listnr || + a->action != b->action || + a->field_count != b->field_count) + return 1; + + for (i = 0; i < a->field_count; i++) { + if (a->fields[i].type != b->fields[i].type || + a->fields[i].op != b->fields[i].op) + return 1; + + switch(a->fields[i].type) { + case AUDIT_SE_USER: + case AUDIT_SE_ROLE: + case AUDIT_SE_TYPE: + case AUDIT_SE_SEN: + case AUDIT_SE_CLR: + if (strcmp(a->fields[i].se_str, b->fields[i].se_str)) + return 1; + break; + case AUDIT_WATCH: + if (strcmp(a->watch->path, b->watch->path)) + return 1; + break; + default: + if (a->fields[i].val != b->fields[i].val) + return 1; + } + } + + for (i = 0; i < AUDIT_BITMASK_SIZE; i++) + if (a->mask[i] != b->mask[i]) + return 1; + + return 0; +} + +/* Duplicate the given audit watch. The new watch's rules list is initialized + * to an empty list and wlist is undefined. */ +static inline struct audit_watch *audit_dupe_watch(struct audit_watch *old) +{ + char *path; + struct audit_watch *new; + + path = kstrdup(old->path, GFP_KERNEL); + if (unlikely(!path)) + return ERR_PTR(-ENOMEM); + + new = audit_init_watch(path); + if (unlikely(IS_ERR(new))) { + kfree(path); + goto out; + } + + new->dev = old->dev; + new->ino = old->ino; + audit_get_parent(old->parent); + new->parent = old->parent; + +out: + return new; +} + +/* Duplicate selinux field information. The se_rule is opaque, so must be + * re-initialized. */ +static inline int audit_dupe_selinux_field(struct audit_field *df, + struct audit_field *sf) +{ + int ret = 0; + char *se_str; + + /* our own copy of se_str */ + se_str = kstrdup(sf->se_str, GFP_KERNEL); + if (unlikely(IS_ERR(se_str))) + return -ENOMEM; + df->se_str = se_str; + + /* our own (refreshed) copy of se_rule */ + ret = selinux_audit_rule_init(df->type, df->op, df->se_str, + &df->se_rule); + /* Keep currently invalid fields around in case they + * become valid after a policy reload. */ + if (ret == -EINVAL) { + printk(KERN_WARNING "audit rule for selinux \'%s\' is " + "invalid\n", df->se_str); + ret = 0; + } + + return ret; +} + +/* Duplicate an audit rule. This will be a deep copy with the exception + * of the watch - that pointer is carried over. The selinux specific fields + * will be updated in the copy. The point is to be able to replace the old + * rule with the new rule in the filterlist, then free the old rule. + * The rlist element is undefined; list manipulations are handled apart from + * the initial copy. */ +static struct audit_entry *audit_dupe_rule(struct audit_krule *old, + struct audit_watch *watch) +{ + u32 fcount = old->field_count; + struct audit_entry *entry; + struct audit_krule *new; + int i, err = 0; + + entry = audit_init_entry(fcount); + if (unlikely(!entry)) + return ERR_PTR(-ENOMEM); + + new = &entry->rule; + new->vers_ops = old->vers_ops; + new->flags = old->flags; + new->listnr = old->listnr; + new->action = old->action; + for (i = 0; i < AUDIT_BITMASK_SIZE; i++) + new->mask[i] = old->mask[i]; + new->buflen = old->buflen; + new->watch = NULL; + new->field_count = old->field_count; + memcpy(new->fields, old->fields, sizeof(struct audit_field) * fcount); + + /* deep copy this information, updating the se_rule fields, because + * the originals will all be freed when the old rule is freed. */ + for (i = 0; i < fcount; i++) { + switch (new->fields[i].type) { + case AUDIT_SE_USER: + case AUDIT_SE_ROLE: + case AUDIT_SE_TYPE: + case AUDIT_SE_SEN: + case AUDIT_SE_CLR: + err = audit_dupe_selinux_field(&new->fields[i], + &old->fields[i]); + } + if (err) { + audit_free_rule(entry); + return ERR_PTR(err); + } + } + + if (watch) { + audit_get_watch(watch); + new->watch = watch; + } + + return entry; +} + +/* Update inode info in audit rules based on filesystem event. */ +static inline void audit_update_watch(struct audit_parent *parent, + const char *dname, dev_t dev, + unsigned long ino) +{ + struct audit_watch *owatch, *nwatch, *nextw; + struct audit_krule *r, *nextr; + struct audit_entry *oentry, *nentry; + struct audit_buffer *ab; + + mutex_lock(&audit_filter_mutex); + list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) { + if (audit_compare_dname_path(dname, owatch->path)) + continue; + + nwatch = audit_dupe_watch(owatch); + if (unlikely(IS_ERR(nwatch))) { + mutex_unlock(&audit_filter_mutex); + audit_panic("error updating watch, skipping"); + return; + } + nwatch->dev = dev; + nwatch->ino = ino; + + list_for_each_entry_safe(r, nextr, &owatch->rules, rlist) { + oentry = container_of(r, struct audit_entry, rule); + + nentry = audit_dupe_rule(&oentry->rule, nwatch); + if (unlikely(IS_ERR(nentry))) { + audit_panic("error updating watch, removing"); + list_del(&oentry->rule.rlist); + list_del_rcu(&oentry->list); + } else { + list_add(&nentry->rule.rlist, &nwatch->rules); + list_del(&oentry->rule.rlist); + list_replace_rcu(&oentry->list, &nentry->list); + } + call_rcu(&oentry->rcu, audit_free_rule_rcu); + } + + ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE); + audit_log_format(ab, "audit updated rules specifying watch="); + audit_log_untrustedstring(ab, owatch->path); + audit_log_format(ab, " with dev=%u ino=%lu\n", dev, ino); + audit_log_end(ab); + + list_del(&owatch->wlist); + audit_put_watch(owatch); /* matches initial get */ + goto add_watch_to_parent; /* event applies to a single watch */ + } + mutex_unlock(&audit_filter_mutex); + return; + +add_watch_to_parent: + list_add(&nwatch->wlist, &parent->watches); + mutex_unlock(&audit_filter_mutex); + return; +} + +/* Remove all watches & rules associated with a parent that is going away. */ +static inline void audit_remove_parent_watches(struct audit_parent *parent) +{ + struct audit_watch *w, *nextw; + struct audit_krule *r, *nextr; + struct audit_entry *e; + + mutex_lock(&audit_filter_mutex); + parent->flags |= AUDIT_PARENT_INVALID; + list_for_each_entry_safe(w, nextw, &parent->watches, wlist) { + list_for_each_entry_safe(r, nextr, &w->rules, rlist) { + e = container_of(r, struct audit_entry, rule); + list_del(&r->rlist); + list_del_rcu(&e->list); + call_rcu(&e->rcu, audit_free_rule_rcu); + + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "audit implicitly removed rule from list=%d\n", + AUDIT_FILTER_EXIT); + } + list_del(&w->wlist); + audit_put_watch(w); /* matches initial get */ + } + mutex_unlock(&audit_filter_mutex); +} + +/* Register inotify watches for parents on in_list. */ +static int audit_inotify_register(struct nameidata *nd, + struct list_head *in_list) +{ + struct audit_parent *p; + s32 wd; + int ret = 0; + + list_for_each_entry(p, in_list, ilist) { + wd = inotify_add_watch(audit_ih, &p->wdata, nd->dentry->d_inode, + AUDIT_IN_WATCH); + if (wd < 0) { + audit_remove_parent_watches(p); + /* the put matching the get in audit_init_parent() */ + audit_put_parent(p); + /* save the first error for return value */ + if (!ret) + ret = wd; + } + } + + return ret; +} + +/* Unregister inotify watches for parents on in_list. + * Generates an IN_IGNORED event. */ +static void audit_inotify_unregister(struct list_head *in_list) +{ + struct audit_parent *p; + + list_for_each_entry(p, in_list, ilist) { + inotify_rm_watch(audit_ih, &p->wdata); + /* the put matching the get in audit_remove_watch() */ + audit_put_parent(p); + } +} + +/* Get path information necessary for adding watches. */ +static int audit_get_nd(char *path, struct nameidata **ndp, + struct nameidata **ndw) +{ + struct nameidata *ndparent, *ndwatch; + int err; + + ndparent = kmalloc(sizeof(*ndparent), GFP_KERNEL); + if (unlikely(!ndparent)) + return -ENOMEM; + + ndwatch = kmalloc(sizeof(*ndwatch), GFP_KERNEL); + if (unlikely(!ndwatch)) { + kfree(ndparent); + return -ENOMEM; + } + + err = path_lookup(path, LOOKUP_PARENT, ndparent); + if (err) { + kfree(ndparent); + kfree(ndwatch); + return err; + } + + err = path_lookup(path, 0, ndwatch); + if (err) { + kfree(ndwatch); + ndwatch = NULL; + } + + *ndp = ndparent; + *ndw = ndwatch; + + return 0; +} + +/* Release resources used for watch path information. */ +static inline void audit_put_nd(struct nameidata *ndp, struct nameidata *ndw) +{ + if (ndp) { + path_release(ndp); + kfree(ndp); + } + if (ndw) { + path_release(ndw); + kfree(ndw); + } +} + +/* Add a parent inotify_watch for the given rule. */ +static int audit_add_parent(struct audit_krule *krule, + struct list_head *inotify_list) +{ + struct audit_parent *parent; + struct audit_watch *watch = krule->watch; + + parent = audit_init_parent(); + if (IS_ERR(parent)) + return PTR_ERR(parent); + + audit_get_parent(parent); + watch->parent = parent; + + /* krule, watch and parent have not been added to any global + * lists, so we don't need to take audit_filter_mutex. */ + list_add(&watch->wlist, &parent->watches); + list_add(&krule->rlist, &watch->rules); + + /* add parent to inotify registration list */ + list_add(&parent->ilist, inotify_list); + + return 0; +} + +/* Associate the given rule with an existing parent inotify_watch. + * Caller must hold audit_filter_mutex. */ +static int audit_add_to_parent(struct audit_krule *krule, + struct inotify_watch *iwatch) +{ + struct audit_parent *parent; + struct audit_watch *w, *watch = krule->watch; + int watch_found = 0; + + parent = container_of(iwatch, struct audit_parent, wdata); + + /* parent was moved before we took audit_filter_mutex */ + if (parent->flags & AUDIT_PARENT_INVALID) + return -ENOENT; + + list_for_each_entry(w, &parent->watches, wlist) { + if (strcmp(watch->path, w->path)) + continue; + + watch_found = 1; + + /* put krule's and initial refs to temporary watch */ + audit_put_watch(watch); + audit_put_watch(watch); + + audit_get_watch(w); + krule->watch = watch = w; + break; + } + + if (!watch_found) { + audit_get_parent(parent); + watch->parent = parent; + + list_add(&watch->wlist, &parent->watches); + } + + list_add(&krule->rlist, &watch->rules); + + return 0; +} + +/* Find a matching watch entry, or add this one. + * Caller must hold audit_filter_mutex. */ +static int audit_add_watch(struct audit_krule *krule, struct nameidata *ndp, + struct nameidata *ndw, + struct list_head *inotify_list) +{ + struct audit_watch *watch = krule->watch; + struct inotify_watch *iwatch; + int ret; + + /* update watch filter fields */ + if (ndw) { + watch->dev = ndw->dentry->d_inode->i_sb->s_dev; + watch->ino = ndw->dentry->d_inode->i_ino; + } + + /* The audit_filter_mutex must not be held during inotify calls because + * we hold it during inotify event callback processing. + * We can trust iwatch to stick around because we hold nameidata (ndp). */ + mutex_unlock(&audit_filter_mutex); + + if (inotify_find_watch(audit_ih, ndp->dentry->d_inode, &iwatch) < 0) { + ret = audit_add_parent(krule, inotify_list); + mutex_lock(&audit_filter_mutex); + } else { + mutex_lock(&audit_filter_mutex); + ret = audit_add_to_parent(krule, iwatch); + } + + return ret; +} + +/* Add rule to given filterlist if not a duplicate. */ +static inline int audit_add_rule(struct audit_entry *entry, + struct list_head *list) +{ + struct audit_entry *e; + struct audit_watch *watch = entry->rule.watch; + struct nameidata *ndp, *ndw; + LIST_HEAD(inotify_list); + int err; + + /* Taking audit_filter_mutex protects from stale rule data. */ + mutex_lock(&audit_filter_mutex); + list_for_each_entry(e, list, list) { + if (!audit_compare_rule(&entry->rule, &e->rule)) { + err = -EEXIST; + mutex_unlock(&audit_filter_mutex); + goto error; + } + } + mutex_unlock(&audit_filter_mutex); + + /* Avoid calling path_lookup under audit_filter_mutex. */ + if (watch) { + err = audit_get_nd(watch->path, &ndp, &ndw); + if (err) + goto error; + } + + mutex_lock(&audit_filter_mutex); + if (watch) { + /* audit_filter_mutex is dropped and re-taken during this call */ + err = audit_add_watch(&entry->rule, ndp, ndw, &inotify_list); + if (err) { + audit_put_nd(ndp, ndw); + goto error; + } + } + + if (entry->rule.flags & AUDIT_FILTER_PREPEND) { + list_add_rcu(&entry->list, list); + } else { + list_add_tail_rcu(&entry->list, list); + } + mutex_unlock(&audit_filter_mutex); + + if (!list_empty(&inotify_list)) { + err = audit_inotify_register(ndp, &inotify_list); + if (err) + goto error; + audit_put_nd(ndp, ndw); + } + + return 0; + +error: + if (watch) + audit_put_watch(watch); /* tmp watch, matches initial get */ + return err; +} + +/* Remove given krule from its associated watch's rules list and clean up any + * last instances of associated watch and parent. + * Caller must hold audit_filter_mutex. */ +static inline void audit_remove_watch(struct audit_krule *krule, + struct list_head *in_list) +{ + struct audit_watch *watch = krule->watch; + struct audit_parent *parent = watch->parent; + + list_del(&krule->rlist); + if (list_empty(&watch->rules)) { + list_del(&watch->wlist); + audit_put_watch(watch); /* matches initial get */ + + if (list_empty(&parent->watches)) { + /* Put parent on the inotify un-registration list. + * Grab a reference before releasing audit_filter_mutex, + * to be released in audit_inotify_unregister(). */ + list_add(&parent->ilist, in_list); + audit_get_parent(parent); + } + } +} + +/* Remove an existing rule from filterlist. */ +static inline int audit_del_rule(struct audit_entry *entry, + struct list_head *list) +{ + struct audit_entry *e; + LIST_HEAD(inotify_list); + + mutex_lock(&audit_filter_mutex); + list_for_each_entry(e, list, list) { + if (audit_compare_rule(&entry->rule, &e->rule)) + continue; + + if (e->rule.watch) { + audit_remove_watch(&e->rule, &inotify_list); + /* match initial get for tmp watch */ + audit_put_watch(entry->rule.watch); + } + + list_del_rcu(&e->list); + call_rcu(&e->rcu, audit_free_rule_rcu); + mutex_unlock(&audit_filter_mutex); + + if (!list_empty(&inotify_list)) + audit_inotify_unregister(&inotify_list); + + return 0; + } + mutex_unlock(&audit_filter_mutex); + /* match initial get for tmp watch */ + if (entry->rule.watch) + audit_put_watch(entry->rule.watch); + return -ENOENT; /* No matching rule */ +} + +/* List rules using struct audit_rule. Exists for backward + * compatibility with userspace. */ +static void audit_list(int pid, int seq, struct sk_buff_head *q) +{ + struct sk_buff *skb; + struct audit_entry *entry; + int i; + + /* This is a blocking read, so use audit_filter_mutex instead of rcu + * iterator to sync with list writers. */ + for (i=0; irule); + if (unlikely(!rule)) + break; + skb = audit_make_reply(pid, seq, AUDIT_LIST, 0, 1, + rule, sizeof(*rule)); + if (skb) + skb_queue_tail(q, skb); + kfree(rule); + } + } + skb = audit_make_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0); + if (skb) + skb_queue_tail(q, skb); +} + +/* List rules using struct audit_rule_data. */ +static void audit_list_rules(int pid, int seq, struct sk_buff_head *q) +{ + struct sk_buff *skb; + struct audit_entry *e; + int i; + + /* This is a blocking read, so use audit_filter_mutex instead of rcu + * iterator to sync with list writers. */ + for (i=0; irule); + if (unlikely(!data)) + break; + skb = audit_make_reply(pid, seq, AUDIT_LIST_RULES, 0, 1, + data, sizeof(*data) + data->buflen); + if (skb) + skb_queue_tail(q, skb); + kfree(data); + } + } + skb = audit_make_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0); + if (skb) + skb_queue_tail(q, skb); +} + +/** + * audit_receive_filter - apply all rules to the specified message type + * @type: audit message type + * @pid: target pid for netlink audit messages + * @uid: target uid for netlink audit messages + * @seq: netlink audit message sequence (serial) number + * @data: payload data + * @datasz: size of payload data + * @loginuid: loginuid of sender + * @sid: SE Linux Security ID of sender + */ +int audit_receive_filter(int type, int pid, int uid, int seq, void *data, + size_t datasz, uid_t loginuid, u32 sid) +{ + struct task_struct *tsk; + struct audit_netlink_list *dest; + int err = 0; + struct audit_entry *entry; + + switch (type) { + case AUDIT_LIST: + case AUDIT_LIST_RULES: + /* We can't just spew out the rules here because we might fill + * the available socket buffer space and deadlock waiting for + * auditctl to read from it... which isn't ever going to + * happen if we're actually running in the context of auditctl + * trying to _send_ the stuff */ + + dest = kmalloc(sizeof(struct audit_netlink_list), GFP_KERNEL); + if (!dest) + return -ENOMEM; + dest->pid = pid; + skb_queue_head_init(&dest->q); + + mutex_lock(&audit_filter_mutex); + if (type == AUDIT_LIST) + audit_list(pid, seq, &dest->q); + else + audit_list_rules(pid, seq, &dest->q); + mutex_unlock(&audit_filter_mutex); + + tsk = kthread_run(audit_send_list, dest, "audit_send_list"); + if (IS_ERR(tsk)) { + skb_queue_purge(&dest->q); + kfree(dest); + err = PTR_ERR(tsk); + } + break; + case AUDIT_ADD: + case AUDIT_ADD_RULE: + if (type == AUDIT_ADD) + entry = audit_rule_to_entry(data); + else + entry = audit_data_to_entry(data, datasz); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + err = audit_add_rule(entry, + &audit_filter_list[entry->rule.listnr]); + + if (sid) { + char *ctx = NULL; + u32 len; + if (selinux_ctxid_to_string(sid, &ctx, &len)) { + /* Maybe call audit_panic? */ + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "auid=%u ssid=%u add rule to list=%d res=%d", + loginuid, sid, entry->rule.listnr, !err); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "auid=%u subj=%s add rule to list=%d res=%d", + loginuid, ctx, entry->rule.listnr, !err); + kfree(ctx); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "auid=%u add rule to list=%d res=%d", + loginuid, entry->rule.listnr, !err); + + if (err) + audit_free_rule(entry); + break; + case AUDIT_DEL: + case AUDIT_DEL_RULE: + if (type == AUDIT_DEL) + entry = audit_rule_to_entry(data); + else + entry = audit_data_to_entry(data, datasz); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + err = audit_del_rule(entry, + &audit_filter_list[entry->rule.listnr]); + + if (sid) { + char *ctx = NULL; + u32 len; + if (selinux_ctxid_to_string(sid, &ctx, &len)) { + /* Maybe call audit_panic? */ + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "auid=%u ssid=%u remove rule from list=%d res=%d", + loginuid, sid, entry->rule.listnr, !err); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "auid=%u subj=%s remove rule from list=%d res=%d", + loginuid, ctx, entry->rule.listnr, !err); + kfree(ctx); + } else + audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, + "auid=%u remove rule from list=%d res=%d", + loginuid, entry->rule.listnr, !err); + + audit_free_rule(entry); + break; + default: + return -EINVAL; + } + + return err; +} + +int audit_comparator(const u32 left, const u32 op, const u32 right) +{ + switch (op) { + case AUDIT_EQUAL: + return (left == right); + case AUDIT_NOT_EQUAL: + return (left != right); + case AUDIT_LESS_THAN: + return (left < right); + case AUDIT_LESS_THAN_OR_EQUAL: + return (left <= right); + case AUDIT_GREATER_THAN: + return (left > right); + case AUDIT_GREATER_THAN_OR_EQUAL: + return (left >= right); + } + BUG(); + return 0; +} + +/* Compare given dentry name with last component in given path, + * return of 0 indicates a match. */ +int audit_compare_dname_path(const char *dname, const char *path) +{ + int dlen, plen; + const char *p; + + if (!dname || !path) + return 1; + + dlen = strlen(dname); + plen = strlen(path); + if (plen < dlen) + return 1; + + /* disregard trailing slashes */ + p = path + plen - 1; + while ((*p == '/') && (p > path)) + p--; + + /* find last path component */ + p = p - dlen + 1; + if (p < path) + return 1; + else if (p > path) { + if (*--p != '/') + return 1; + else + p++; + } + + return strncmp(p, dname, dlen); +} + +static int audit_filter_user_rules(struct netlink_skb_parms *cb, + struct audit_krule *rule, + enum audit_state *state) +{ + int i; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &rule->fields[i]; + int result = 0; + + switch (f->type) { + case AUDIT_PID: + result = audit_comparator(cb->creds.pid, f->op, f->val); + break; + case AUDIT_UID: + result = audit_comparator(cb->creds.uid, f->op, f->val); + break; + case AUDIT_GID: + result = audit_comparator(cb->creds.gid, f->op, f->val); + break; + case AUDIT_LOGINUID: + result = audit_comparator(cb->loginuid, f->op, f->val); + break; + } + + if (!result) + return 0; + } + switch (rule->action) { + case AUDIT_NEVER: *state = AUDIT_DISABLED; break; + case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break; + case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break; + } + return 1; +} + +int audit_filter_user(struct netlink_skb_parms *cb, int type) +{ + struct audit_entry *e; + enum audit_state state; + int ret = 1; + + rcu_read_lock(); + list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) { + if (audit_filter_user_rules(cb, &e->rule, &state)) { + if (state == AUDIT_DISABLED) + ret = 0; + break; + } + } + rcu_read_unlock(); + + return ret; /* Audit by default */ +} + +int audit_filter_type(int type) +{ + struct audit_entry *e; + int result = 0; + + rcu_read_lock(); + if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE])) + goto unlock_and_return; + + list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE], + list) { + int i; + for (i = 0; i < e->rule.field_count; i++) { + struct audit_field *f = &e->rule.fields[i]; + if (f->type == AUDIT_MSGTYPE) { + result = audit_comparator(type, f->op, f->val); + if (!result) + break; + } + } + if (result) + goto unlock_and_return; + } +unlock_and_return: + rcu_read_unlock(); + return result; +} + +/* Check to see if the rule contains any selinux fields. Returns 1 if there + are selinux fields specified in the rule, 0 otherwise. */ +static inline int audit_rule_has_selinux(struct audit_krule *rule) +{ + int i; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &rule->fields[i]; + switch (f->type) { + case AUDIT_SE_USER: + case AUDIT_SE_ROLE: + case AUDIT_SE_TYPE: + case AUDIT_SE_SEN: + case AUDIT_SE_CLR: + return 1; + } + } + + return 0; +} + +/* This function will re-initialize the se_rule field of all applicable rules. + * It will traverse the filter lists serarching for rules that contain selinux + * specific filter fields. When such a rule is found, it is copied, the + * selinux field is re-initialized, and the old rule is replaced with the + * updated rule. */ +int selinux_audit_rule_update(void) +{ + struct audit_entry *entry, *n, *nentry; + struct audit_watch *watch; + int i, err = 0; + + /* audit_filter_mutex synchronizes the writers */ + mutex_lock(&audit_filter_mutex); + + for (i = 0; i < AUDIT_NR_FILTERS; i++) { + list_for_each_entry_safe(entry, n, &audit_filter_list[i], list) { + if (!audit_rule_has_selinux(&entry->rule)) + continue; + + watch = entry->rule.watch; + nentry = audit_dupe_rule(&entry->rule, watch); + if (unlikely(IS_ERR(nentry))) { + /* save the first error encountered for the + * return value */ + if (!err) + err = PTR_ERR(nentry); + audit_panic("error updating selinux filters"); + if (watch) + list_del(&entry->rule.rlist); + list_del_rcu(&entry->list); + } else { + if (watch) { + list_add(&nentry->rule.rlist, + &watch->rules); + list_del(&entry->rule.rlist); + } + list_replace_rcu(&entry->list, &nentry->list); + } + call_rcu(&entry->rcu, audit_free_rule_rcu); + } + } + + mutex_unlock(&audit_filter_mutex); + + return err; +} + +/* Update watch data in audit rules based on inotify events. */ +void audit_handle_ievent(struct inotify_watch *iwatch, u32 wd, u32 mask, + u32 cookie, const char *dname, struct inode *inode) +{ + struct audit_parent *parent = container_of(iwatch, struct audit_parent, wdata); + + if (mask & (IN_CREATE|IN_MOVED_TO) && inode) + audit_update_watch(parent, dname, inode->i_sb->s_dev, + inode->i_ino); + else if (mask & (IN_DELETE|IN_MOVED_FROM)) + audit_update_watch(parent, dname, (dev_t)-1, (unsigned long)-1); + /* Note: Inotify doesn't remove the watch for the IN_MOVE_SELF event. + * Work around this by leaving the parent around with an empty + * watchlist. It will be re-used if new watches are added. */ + else if (mask & (AUDIT_IN_SELF)) + audit_remove_parent_watches(parent); + else if (mask & IN_IGNORED) + audit_put_parent(parent); /* match get in audit_init_parent() */ +} diff --git a/kernel/audit/syscall.c b/kernel/audit/syscall.c new file mode 100644 index 0000000..43512c1 --- /dev/null +++ b/kernel/audit/syscall.c @@ -0,0 +1,1401 @@ +/* auditsc.c -- System-call auditing support + * Handles all system-call specific auditing features. + * + * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina. + * Copyright 2005 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2005 IBM Corporation + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Written by Rickard E. (Rik) Faith + * + * Many of the ideas implemented here are from Stephen C. Tweedie, + * especially the idea of avoiding a copy by using getname. + * + * The method for actual interception of syscall entry and exit (not in + * this file -- see entry.S) is based on a GPL'd patch written by + * okir@suse.de and Copyright 2003 SuSE Linux AG. + * + * The support of additional filter rules compares (>, <, >=, <=) was + * added by Dustin Kirkland , 2005. + * + * Modified by Amy Griffis to collect additional + * filesystem information. + * + * Subject and object context labeling support added by + * and for LSPP certification compliance. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audit.h" + +extern struct list_head audit_filter_list[]; + +/* No syscall auditing will take place unless audit_enabled != 0. */ +extern int audit_enabled; + +/* AUDIT_NAMES is the number of slots we reserve in the audit_context + * for saving names from getname(). */ +#define AUDIT_NAMES 20 + +/* AUDIT_NAMES_RESERVED is the number of slots we reserve in the + * audit_context from being used for nameless inodes from + * path_lookup. */ +#define AUDIT_NAMES_RESERVED 7 + +/* When fs/namei.c:getname() is called, we store the pointer in name and + * we don't let putname() free it (instead we free all of the saved + * pointers at syscall exit time). + * + * Further, in fs/namei.c:path_lookup() we store the inode and device. */ +struct audit_names { + const char *name; + unsigned long ino; + unsigned long pino; + dev_t dev; + umode_t mode; + uid_t uid; + gid_t gid; + dev_t rdev; + u32 osid; +}; + +struct audit_aux_data { + struct audit_aux_data *next; + int type; +}; + +#define AUDIT_AUX_IPCPERM 0 + +struct audit_aux_data_ipcctl { + struct audit_aux_data d; + struct ipc_perm p; + unsigned long qbytes; + uid_t uid; + gid_t gid; + mode_t mode; + u32 osid; +}; + +struct audit_aux_data_execve { + struct audit_aux_data d; + int argc; + int envc; + char mem[0]; +}; + +struct audit_aux_data_socketcall { + struct audit_aux_data d; + int nargs; + unsigned long args[0]; +}; + +struct audit_aux_data_sockaddr { + struct audit_aux_data d; + int len; + char a[0]; +}; + +struct audit_aux_data_path { + struct audit_aux_data d; + struct dentry *dentry; + struct vfsmount *mnt; +}; + +/* The per-task audit context. */ +struct audit_context { + int in_syscall; /* 1 if task is in a syscall */ + enum audit_state state; + unsigned int serial; /* serial number for record */ + struct timespec ctime; /* time of syscall entry */ + uid_t loginuid; /* login uid (identity) */ + int major; /* syscall number */ + unsigned long argv[4]; /* syscall arguments */ + int return_valid; /* return code is valid */ + long return_code;/* syscall return code */ + int auditable; /* 1 if record should be written */ + int name_count; + struct audit_names names[AUDIT_NAMES]; + struct dentry * pwd; + struct vfsmount * pwdmnt; + struct audit_context *previous; /* For nested syscalls */ + struct audit_aux_data *aux; + + /* Save things to print about task_struct */ + pid_t pid; + uid_t uid, euid, suid, fsuid; + gid_t gid, egid, sgid, fsgid; + unsigned long personality; + int arch; + +#if AUDIT_DEBUG + int put_count; + int ino_count; +#endif +}; + +/* Determine if any context name data matches a rule's watch data */ +static inline int audit_match_watch(struct audit_context *ctx, + struct audit_watch *watch) +{ + int i; + + if (!ctx) + return 0; + + if (watch->ino == (unsigned long)-1) + return 0; + + for (i = 0; i < ctx->name_count; i++) { + if (ctx->names[i].dev == watch->dev && + (ctx->names[i].ino == watch->ino || + ctx->names[i].pino == watch->ino)) + return 1; + } + + return 0; +} + +/* Compare a task_struct with an audit_rule. Return 1 on match, 0 + * otherwise. */ +static int audit_filter_rules(struct task_struct *tsk, + struct audit_krule *rule, + struct audit_context *ctx, + enum audit_state *state) +{ + int i, j, need_sid = 1; + u32 sid; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &rule->fields[i]; + int result = 0; + + switch (f->type) { + case AUDIT_PID: + result = audit_comparator(tsk->pid, f->op, f->val); + break; + case AUDIT_UID: + result = audit_comparator(tsk->uid, f->op, f->val); + break; + case AUDIT_EUID: + result = audit_comparator(tsk->euid, f->op, f->val); + break; + case AUDIT_SUID: + result = audit_comparator(tsk->suid, f->op, f->val); + break; + case AUDIT_FSUID: + result = audit_comparator(tsk->fsuid, f->op, f->val); + break; + case AUDIT_GID: + result = audit_comparator(tsk->gid, f->op, f->val); + break; + case AUDIT_EGID: + result = audit_comparator(tsk->egid, f->op, f->val); + break; + case AUDIT_SGID: + result = audit_comparator(tsk->sgid, f->op, f->val); + break; + case AUDIT_FSGID: + result = audit_comparator(tsk->fsgid, f->op, f->val); + break; + case AUDIT_PERS: + result = audit_comparator(tsk->personality, f->op, f->val); + break; + case AUDIT_ARCH: + if (ctx) + result = audit_comparator(ctx->arch, f->op, f->val); + break; + + case AUDIT_EXIT: + if (ctx && ctx->return_valid) + result = audit_comparator(ctx->return_code, f->op, f->val); + break; + case AUDIT_SUCCESS: + if (ctx && ctx->return_valid) { + if (f->val) + result = audit_comparator(ctx->return_valid, f->op, AUDITSC_SUCCESS); + else + result = audit_comparator(ctx->return_valid, f->op, AUDITSC_FAILURE); + } + break; + case AUDIT_DEVMAJOR: + if (ctx) { + for (j = 0; j < ctx->name_count; j++) { + if (audit_comparator(MAJOR(ctx->names[j].dev), f->op, f->val)) { + ++result; + break; + } + } + } + break; + case AUDIT_DEVMINOR: + if (ctx) { + for (j = 0; j < ctx->name_count; j++) { + if (audit_comparator(MINOR(ctx->names[j].dev), f->op, f->val)) { + ++result; + break; + } + } + } + break; + case AUDIT_INODE: + if (ctx) { + for (j = 0; j < ctx->name_count; j++) { + if (audit_comparator(ctx->names[j].ino, f->op, f->val) || + audit_comparator(ctx->names[j].pino, f->op, f->val)) { + ++result; + break; + } + } + } + break; + case AUDIT_WATCH: + result = audit_match_watch(ctx, rule->watch); + break; + case AUDIT_LOGINUID: + result = 0; + if (ctx) + result = audit_comparator(ctx->loginuid, f->op, f->val); + break; + case AUDIT_SE_USER: + case AUDIT_SE_ROLE: + case AUDIT_SE_TYPE: + case AUDIT_SE_SEN: + case AUDIT_SE_CLR: + /* NOTE: this may return negative values indicating + a temporary error. We simply treat this as a + match for now to avoid losing information that + may be wanted. An error message will also be + logged upon error */ + if (f->se_rule) { + if (need_sid) { + selinux_task_ctxid(tsk, &sid); + need_sid = 0; + } + result = selinux_audit_rule_match(sid, f->type, + f->op, + f->se_rule, + ctx); + } + break; + case AUDIT_ARG0: + case AUDIT_ARG1: + case AUDIT_ARG2: + case AUDIT_ARG3: + if (ctx) + result = audit_comparator(ctx->argv[f->type-AUDIT_ARG0], f->op, f->val); + break; + } + + if (!result) + return 0; + } + switch (rule->action) { + case AUDIT_NEVER: *state = AUDIT_DISABLED; break; + case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break; + case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break; + } + return 1; +} + +/* At process creation time, we can determine if system-call auditing is + * completely disabled for this task. Since we only have the task + * structure at this point, we can only check uid and gid. + */ +static enum audit_state audit_filter_task(struct task_struct *tsk) +{ + struct audit_entry *e; + enum audit_state state; + + rcu_read_lock(); + list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) { + if (audit_filter_rules(tsk, &e->rule, NULL, &state)) { + rcu_read_unlock(); + return state; + } + } + rcu_read_unlock(); + return AUDIT_BUILD_CONTEXT; +} + +/* At syscall entry and exit time, this filter is called if the + * audit_state is not low enough that auditing cannot take place, but is + * also not high enough that we already know we have to write an audit + * record (i.e., the state is AUDIT_SETUP_CONTEXT or AUDIT_BUILD_CONTEXT). + */ +static enum audit_state audit_filter_syscall(struct task_struct *tsk, + struct audit_context *ctx, + struct list_head *list) +{ + struct audit_entry *e; + enum audit_state state; + + if (audit_pid && tsk->tgid == audit_pid) + return AUDIT_DISABLED; + + rcu_read_lock(); + if (!list_empty(list)) { + int word = AUDIT_WORD(ctx->major); + int bit = AUDIT_BIT(ctx->major); + + list_for_each_entry_rcu(e, list, list) { + if ((e->rule.mask[word] & bit) == bit + && audit_filter_rules(tsk, &e->rule, ctx, &state)) { + rcu_read_unlock(); + return state; + } + } + } + rcu_read_unlock(); + return AUDIT_BUILD_CONTEXT; +} + +static inline struct audit_context *audit_get_context(struct task_struct *tsk, + int return_valid, + int return_code) +{ + struct audit_context *context = tsk->audit_context; + + if (likely(!context)) + return NULL; + context->return_valid = return_valid; + context->return_code = return_code; + + if (context->in_syscall && !context->auditable) { + enum audit_state state; + state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]); + if (state == AUDIT_RECORD_CONTEXT) + context->auditable = 1; + } + + context->pid = tsk->pid; + context->uid = tsk->uid; + context->gid = tsk->gid; + context->euid = tsk->euid; + context->suid = tsk->suid; + context->fsuid = tsk->fsuid; + context->egid = tsk->egid; + context->sgid = tsk->sgid; + context->fsgid = tsk->fsgid; + context->personality = tsk->personality; + tsk->audit_context = NULL; + return context; +} + +static inline void audit_free_names(struct audit_context *context) +{ + int i; + +#if AUDIT_DEBUG == 2 + if (context->auditable + ||context->put_count + context->ino_count != context->name_count) { + printk(KERN_ERR "%s:%d(:%d): major=%d in_syscall=%d" + " name_count=%d put_count=%d" + " ino_count=%d [NOT freeing]\n", + __FILE__, __LINE__, + context->serial, context->major, context->in_syscall, + context->name_count, context->put_count, + context->ino_count); + for (i = 0; i < context->name_count; i++) { + printk(KERN_ERR "names[%d] = %p = %s\n", i, + context->names[i].name, + context->names[i].name ?: "(null)"); + } + dump_stack(); + return; + } +#endif +#if AUDIT_DEBUG + context->put_count = 0; + context->ino_count = 0; +#endif + + for (i = 0; i < context->name_count; i++) { + if (context->names[i].name) + __putname(context->names[i].name); + } + context->name_count = 0; + if (context->pwd) + dput(context->pwd); + if (context->pwdmnt) + mntput(context->pwdmnt); + context->pwd = NULL; + context->pwdmnt = NULL; +} + +static inline void audit_free_aux(struct audit_context *context) +{ + struct audit_aux_data *aux; + + while ((aux = context->aux)) { + if (aux->type == AUDIT_AVC_PATH) { + struct audit_aux_data_path *axi = (void *)aux; + dput(axi->dentry); + mntput(axi->mnt); + } + + context->aux = aux->next; + kfree(aux); + } +} + +static inline void audit_zero_context(struct audit_context *context, + enum audit_state state) +{ + uid_t loginuid = context->loginuid; + + memset(context, 0, sizeof(*context)); + context->state = state; + context->loginuid = loginuid; +} + +static inline struct audit_context *audit_alloc_context(enum audit_state state) +{ + struct audit_context *context; + + if (!(context = kmalloc(sizeof(*context), GFP_KERNEL))) + return NULL; + audit_zero_context(context, state); + return context; +} + +/** + * audit_alloc - allocate an audit context block for a task + * @tsk: task + * + * Filter on the task information and allocate a per-task audit context + * if necessary. Doing so turns on system call auditing for the + * specified task. This is called from copy_process, so no lock is + * needed. + */ +int audit_alloc(struct task_struct *tsk) +{ + struct audit_context *context; + enum audit_state state; + + if (likely(!audit_enabled)) + return 0; /* Return if not auditing. */ + + state = audit_filter_task(tsk); + if (likely(state == AUDIT_DISABLED)) + return 0; + + if (!(context = audit_alloc_context(state))) { + audit_log_lost("out of memory in audit_alloc"); + return -ENOMEM; + } + + /* Preserve login uid */ + context->loginuid = -1; + if (current->audit_context) + context->loginuid = current->audit_context->loginuid; + + tsk->audit_context = context; + set_tsk_thread_flag(tsk, TIF_SYSCALL_AUDIT); + return 0; +} + +static inline void audit_free_context(struct audit_context *context) +{ + struct audit_context *previous; + int count = 0; + + do { + previous = context->previous; + if (previous || (count && count < 10)) { + ++count; + printk(KERN_ERR "audit(:%d): major=%d name_count=%d:" + " freeing multiple contexts (%d)\n", + context->serial, context->major, + context->name_count, count); + } + audit_free_names(context); + audit_free_aux(context); + kfree(context); + context = previous; + } while (context); + if (count >= 10) + printk(KERN_ERR "audit: freed %d contexts\n", count); +} + +static void audit_log_task_context(struct audit_buffer *ab) +{ + char *ctx = NULL; + ssize_t len = 0; + + len = security_getprocattr(current, "current", NULL, 0); + if (len < 0) { + if (len != -EINVAL) + goto error_path; + return; + } + + ctx = kmalloc(len, GFP_KERNEL); + if (!ctx) + goto error_path; + + len = security_getprocattr(current, "current", ctx, len); + if (len < 0 ) + goto error_path; + + audit_log_format(ab, " subj=%s", ctx); + return; + +error_path: + if (ctx) + kfree(ctx); + audit_panic("error in audit_log_task_context"); + return; +} + +static void audit_log_task_info(struct audit_buffer *ab, struct task_struct *tsk) +{ + char name[sizeof(tsk->comm)]; + struct mm_struct *mm = tsk->mm; + struct vm_area_struct *vma; + + /* tsk == current */ + + get_task_comm(name, tsk); + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, name); + + if (mm) { + down_read(&mm->mmap_sem); + vma = mm->mmap; + while (vma) { + if ((vma->vm_flags & VM_EXECUTABLE) && + vma->vm_file) { + audit_log_d_path(ab, "exe=", + vma->vm_file->f_dentry, + vma->vm_file->f_vfsmnt); + break; + } + vma = vma->vm_next; + } + up_read(&mm->mmap_sem); + } + audit_log_task_context(ab); +} + +static void audit_log_exit(struct audit_context *context, struct task_struct *tsk) +{ + int i, call_panic = 0; + struct audit_buffer *ab; + struct audit_aux_data *aux; + const char *tty; + + /* tsk == current */ + + ab = audit_log_start(context, GFP_KERNEL, AUDIT_SYSCALL); + if (!ab) + return; /* audit_panic has been called */ + audit_log_format(ab, "arch=%x syscall=%d", + context->arch, context->major); + if (context->personality != PER_LINUX) + audit_log_format(ab, " per=%lx", context->personality); + if (context->return_valid) + audit_log_format(ab, " success=%s exit=%ld", + (context->return_valid==AUDITSC_SUCCESS)?"yes":"no", + context->return_code); + if (tsk->signal && tsk->signal->tty && tsk->signal->tty->name) + tty = tsk->signal->tty->name; + else + tty = "(none)"; + audit_log_format(ab, + " a0=%lx a1=%lx a2=%lx a3=%lx items=%d" + " pid=%d auid=%u uid=%u gid=%u" + " euid=%u suid=%u fsuid=%u" + " egid=%u sgid=%u fsgid=%u tty=%s", + context->argv[0], + context->argv[1], + context->argv[2], + context->argv[3], + context->name_count, + context->pid, + context->loginuid, + context->uid, + context->gid, + context->euid, context->suid, context->fsuid, + context->egid, context->sgid, context->fsgid, tty); + audit_log_task_info(ab, tsk); + audit_log_end(ab); + + for (aux = context->aux; aux; aux = aux->next) { + + ab = audit_log_start(context, GFP_KERNEL, aux->type); + if (!ab) + continue; /* audit_panic has been called */ + + switch (aux->type) { + case AUDIT_IPC: { + struct audit_aux_data_ipcctl *axi = (void *)aux; + audit_log_format(ab, + " qbytes=%lx iuid=%u igid=%u mode=%x", + axi->qbytes, axi->uid, axi->gid, axi->mode); + if (axi->osid != 0) { + char *ctx = NULL; + u32 len; + if (selinux_ctxid_to_string( + axi->osid, &ctx, &len)) { + audit_log_format(ab, " osid=%u", + axi->osid); + call_panic = 1; + } else + audit_log_format(ab, " obj=%s", ctx); + kfree(ctx); + } + break; } + + case AUDIT_IPC_SET_PERM: { + struct audit_aux_data_ipcctl *axi = (void *)aux; + audit_log_format(ab, + " new qbytes=%lx new iuid=%u new igid=%u new mode=%x", + axi->qbytes, axi->uid, axi->gid, axi->mode); + if (axi->osid != 0) { + char *ctx = NULL; + u32 len; + if (selinux_ctxid_to_string( + axi->osid, &ctx, &len)) { + audit_log_format(ab, " osid=%u", + axi->osid); + call_panic = 1; + } else + audit_log_format(ab, " obj=%s", ctx); + kfree(ctx); + } + break; } + case AUDIT_EXECVE: { + struct audit_aux_data_execve *axi = (void *)aux; + int i; + const char *p; + for (i = 0, p = axi->mem; i < axi->argc; i++) { + audit_log_format(ab, "a%d=", i); + p = audit_log_untrustedstring(ab, p); + audit_log_format(ab, "\n"); + } + break; } + + case AUDIT_SOCKETCALL: { + int i; + struct audit_aux_data_socketcall *axs = (void *)aux; + audit_log_format(ab, "nargs=%d", axs->nargs); + for (i=0; inargs; i++) + audit_log_format(ab, " a%d=%lx", i, axs->args[i]); + break; } + + case AUDIT_SOCKADDR: { + struct audit_aux_data_sockaddr *axs = (void *)aux; + + audit_log_format(ab, "saddr="); + audit_log_hex(ab, axs->a, axs->len); + break; } + + case AUDIT_AVC_PATH: { + struct audit_aux_data_path *axi = (void *)aux; + audit_log_d_path(ab, "path=", axi->dentry, axi->mnt); + break; } + + } + audit_log_end(ab); + } + + if (context->pwd && context->pwdmnt) { + ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD); + if (ab) { + audit_log_d_path(ab, "cwd=", context->pwd, context->pwdmnt); + audit_log_end(ab); + } + } + for (i = 0; i < context->name_count; i++) { + unsigned long ino = context->names[i].ino; + unsigned long pino = context->names[i].pino; + + ab = audit_log_start(context, GFP_KERNEL, AUDIT_PATH); + if (!ab) + continue; /* audit_panic has been called */ + + audit_log_format(ab, "item=%d", i); + + audit_log_format(ab, " name="); + if (context->names[i].name) + audit_log_untrustedstring(ab, context->names[i].name); + else + audit_log_format(ab, "(null)"); + + if (pino != (unsigned long)-1) + audit_log_format(ab, " parent=%lu", pino); + if (ino != (unsigned long)-1) + audit_log_format(ab, " inode=%lu", ino); + if ((pino != (unsigned long)-1) || (ino != (unsigned long)-1)) + audit_log_format(ab, " dev=%02x:%02x mode=%#o" + " ouid=%u ogid=%u rdev=%02x:%02x", + MAJOR(context->names[i].dev), + MINOR(context->names[i].dev), + context->names[i].mode, + context->names[i].uid, + context->names[i].gid, + MAJOR(context->names[i].rdev), + MINOR(context->names[i].rdev)); + if (context->names[i].osid != 0) { + char *ctx = NULL; + u32 len; + if (selinux_ctxid_to_string( + context->names[i].osid, &ctx, &len)) { + audit_log_format(ab, " osid=%u", + context->names[i].osid); + call_panic = 2; + } else + audit_log_format(ab, " obj=%s", ctx); + kfree(ctx); + } + + audit_log_end(ab); + } + if (call_panic) + audit_panic("error converting sid to string"); +} + +/** + * audit_free - free a per-task audit context + * @tsk: task whose audit context block to free + * + * Called from copy_process and do_exit + */ +void audit_free(struct task_struct *tsk) +{ + struct audit_context *context; + + context = audit_get_context(tsk, 0, 0); + if (likely(!context)) + return; + + /* Check for system calls that do not go through the exit + * function (e.g., exit_group), then free context block. + * We use GFP_ATOMIC here because we might be doing this + * in the context of the idle thread */ + /* that can happen only if we are called from do_exit() */ + if (context->in_syscall && context->auditable) + audit_log_exit(context, tsk); + + audit_free_context(context); +} + +/** + * audit_syscall_entry - fill in an audit record at syscall entry + * @tsk: task being audited + * @arch: architecture type + * @major: major syscall type (function) + * @a1: additional syscall register 1 + * @a2: additional syscall register 2 + * @a3: additional syscall register 3 + * @a4: additional syscall register 4 + * + * Fill in audit context at syscall entry. This only happens if the + * audit context was created when the task was created and the state or + * filters demand the audit context be built. If the state from the + * per-task filter or from the per-syscall filter is AUDIT_RECORD_CONTEXT, + * then the record will be written at syscall exit time (otherwise, it + * will only be written if another part of the kernel requests that it + * be written). + */ +void audit_syscall_entry(int arch, int major, + unsigned long a1, unsigned long a2, + unsigned long a3, unsigned long a4) +{ + struct task_struct *tsk = current; + struct audit_context *context = tsk->audit_context; + enum audit_state state; + + BUG_ON(!context); + + /* + * This happens only on certain architectures that make system + * calls in kernel_thread via the entry.S interface, instead of + * with direct calls. (If you are porting to a new + * architecture, hitting this condition can indicate that you + * got the _exit/_leave calls backward in entry.S.) + * + * i386 no + * x86_64 no + * ppc64 yes (see arch/powerpc/platforms/iseries/misc.S) + * + * This also happens with vm86 emulation in a non-nested manner + * (entries without exits), so this case must be caught. + */ + if (context->in_syscall) { + struct audit_context *newctx; + +#if AUDIT_DEBUG + printk(KERN_ERR + "audit(:%d) pid=%d in syscall=%d;" + " entering syscall=%d\n", + context->serial, tsk->pid, context->major, major); +#endif + newctx = audit_alloc_context(context->state); + if (newctx) { + newctx->previous = context; + context = newctx; + tsk->audit_context = newctx; + } else { + /* If we can't alloc a new context, the best we + * can do is to leak memory (any pending putname + * will be lost). The only other alternative is + * to abandon auditing. */ + audit_zero_context(context, context->state); + } + } + BUG_ON(context->in_syscall || context->name_count); + + if (!audit_enabled) + return; + + context->arch = arch; + context->major = major; + context->argv[0] = a1; + context->argv[1] = a2; + context->argv[2] = a3; + context->argv[3] = a4; + + state = context->state; + if (state == AUDIT_SETUP_CONTEXT || state == AUDIT_BUILD_CONTEXT) + state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]); + if (likely(state == AUDIT_DISABLED)) + return; + + context->serial = 0; + context->ctime = CURRENT_TIME; + context->in_syscall = 1; + context->auditable = !!(state == AUDIT_RECORD_CONTEXT); +} + +/** + * audit_syscall_exit - deallocate audit context after a system call + * @tsk: task being audited + * @valid: success/failure flag + * @return_code: syscall return value + * + * Tear down after system call. If the audit context has been marked as + * auditable (either because of the AUDIT_RECORD_CONTEXT state from + * filtering, or because some other part of the kernel write an audit + * message), then write out the syscall information. In call cases, + * free the names stored from getname(). + */ +void audit_syscall_exit(int valid, long return_code) +{ + struct task_struct *tsk = current; + struct audit_context *context; + + context = audit_get_context(tsk, valid, return_code); + + if (likely(!context)) + return; + + if (context->in_syscall && context->auditable) + audit_log_exit(context, tsk); + + context->in_syscall = 0; + context->auditable = 0; + + if (context->previous) { + struct audit_context *new_context = context->previous; + context->previous = NULL; + audit_free_context(context); + tsk->audit_context = new_context; + } else { + audit_free_names(context); + audit_free_aux(context); + tsk->audit_context = context; + } +} + +/** + * audit_getname - add a name to the list + * @name: name to add + * + * Add a name to the list of audit names for this context. + * Called from fs/namei.c:getname(). + */ +void audit_getname(const char *name) +{ + struct audit_context *context = current->audit_context; + + if (!context || IS_ERR(name) || !name) + return; + + if (!context->in_syscall) { +#if AUDIT_DEBUG == 2 + printk(KERN_ERR "%s:%d(:%d): ignoring getname(%p)\n", + __FILE__, __LINE__, context->serial, name); + dump_stack(); +#endif + return; + } + BUG_ON(context->name_count >= AUDIT_NAMES); + context->names[context->name_count].name = name; + context->names[context->name_count].ino = (unsigned long)-1; + ++context->name_count; + if (!context->pwd) { + read_lock(¤t->fs->lock); + context->pwd = dget(current->fs->pwd); + context->pwdmnt = mntget(current->fs->pwdmnt); + read_unlock(¤t->fs->lock); + } + +} + +/* audit_putname - intercept a putname request + * @name: name to intercept and delay for putname + * + * If we have stored the name from getname in the audit context, + * then we delay the putname until syscall exit. + * Called from include/linux/fs.h:putname(). + */ +void audit_putname(const char *name) +{ + struct audit_context *context = current->audit_context; + + BUG_ON(!context); + if (!context->in_syscall) { +#if AUDIT_DEBUG == 2 + printk(KERN_ERR "%s:%d(:%d): __putname(%p)\n", + __FILE__, __LINE__, context->serial, name); + if (context->name_count) { + int i; + for (i = 0; i < context->name_count; i++) + printk(KERN_ERR "name[%d] = %p = %s\n", i, + context->names[i].name, + context->names[i].name ?: "(null)"); + } +#endif + __putname(name); + } +#if AUDIT_DEBUG + else { + ++context->put_count; + if (context->put_count > context->name_count) { + printk(KERN_ERR "%s:%d(:%d): major=%d" + " in_syscall=%d putname(%p) name_count=%d" + " put_count=%d\n", + __FILE__, __LINE__, + context->serial, context->major, + context->in_syscall, name, context->name_count, + context->put_count); + dump_stack(); + } + } +#endif +} + +static void audit_inode_context(int idx, const struct inode *inode) +{ + struct audit_context *context = current->audit_context; + + selinux_get_inode_sid(inode, &context->names[idx].osid); +} + + +/** + * audit_inode - store the inode and device from a lookup + * @name: name being audited + * @inode: inode being audited + * @flags: lookup flags (as used in path_lookup()) + * + * Called from fs/namei.c:path_lookup(). + */ +void __audit_inode(const char *name, const struct inode *inode, unsigned flags) +{ + int idx; + struct audit_context *context = current->audit_context; + + if (!context->in_syscall) + return; + if (context->name_count + && context->names[context->name_count-1].name + && context->names[context->name_count-1].name == name) + idx = context->name_count - 1; + else if (context->name_count > 1 + && context->names[context->name_count-2].name + && context->names[context->name_count-2].name == name) + idx = context->name_count - 2; + else { + /* FIXME: how much do we care about inodes that have no + * associated name? */ + if (context->name_count >= AUDIT_NAMES - AUDIT_NAMES_RESERVED) + return; + idx = context->name_count++; + context->names[idx].name = NULL; +#if AUDIT_DEBUG + ++context->ino_count; +#endif + } + context->names[idx].dev = inode->i_sb->s_dev; + context->names[idx].mode = inode->i_mode; + context->names[idx].uid = inode->i_uid; + context->names[idx].gid = inode->i_gid; + context->names[idx].rdev = inode->i_rdev; + audit_inode_context(idx, inode); + if ((flags & LOOKUP_PARENT) && (strcmp(name, "/") != 0) && + (strcmp(name, ".") != 0)) { + context->names[idx].ino = (unsigned long)-1; + context->names[idx].pino = inode->i_ino; + } else { + context->names[idx].ino = inode->i_ino; + context->names[idx].pino = (unsigned long)-1; + } +} + +/** + * audit_inode_child - collect inode info for created/removed objects + * @dname: inode's dentry name + * @inode: inode being audited + * @pino: inode number of dentry parent + * + * For syscalls that create or remove filesystem objects, audit_inode + * can only collect information for the filesystem object's parent. + * This call updates the audit context with the child's information. + * Syscalls that create a new filesystem object must be hooked after + * the object is created. Syscalls that remove a filesystem object + * must be hooked prior, in order to capture the target inode during + * unsuccessful attempts. + */ +void __audit_inode_child(const char *dname, const struct inode *inode, + unsigned long pino) +{ + int idx; + struct audit_context *context = current->audit_context; + + if (!context->in_syscall) + return; + + /* determine matching parent */ + if (!dname) + goto no_match; + for (idx = 0; idx < context->name_count; idx++) + if (context->names[idx].pino == pino) { + const char *name = context->names[idx].name; + + if (!name) + continue; + + if (audit_compare_dname_path(dname, name) == 0) + goto update_context; + } + +no_match: + /* catch-all in case match not found */ + idx = context->name_count++; + context->names[idx].name = NULL; + context->names[idx].pino = pino; +#if AUDIT_DEBUG + context->ino_count++; +#endif + +update_context: + if (inode) { + context->names[idx].ino = inode->i_ino; + context->names[idx].dev = inode->i_sb->s_dev; + context->names[idx].mode = inode->i_mode; + context->names[idx].uid = inode->i_uid; + context->names[idx].gid = inode->i_gid; + context->names[idx].rdev = inode->i_rdev; + audit_inode_context(idx, inode); + } +} + +/** + * auditsc_get_stamp - get local copies of audit_context values + * @ctx: audit_context for the task + * @t: timespec to store time recorded in the audit_context + * @serial: serial value that is recorded in the audit_context + * + * Also sets the context as auditable. + */ +void auditsc_get_stamp(struct audit_context *ctx, + struct timespec *t, unsigned int *serial) +{ + if (!ctx->serial) + ctx->serial = audit_serial(); + t->tv_sec = ctx->ctime.tv_sec; + t->tv_nsec = ctx->ctime.tv_nsec; + *serial = ctx->serial; + ctx->auditable = 1; +} + +/** + * audit_set_loginuid - set a task's audit_context loginuid + * @task: task whose audit context is being modified + * @loginuid: loginuid value + * + * Returns 0. + * + * Called (set) from fs/proc/base.c::proc_loginuid_write(). + */ +int audit_set_loginuid(struct task_struct *task, uid_t loginuid) +{ + if (task->audit_context) { + struct audit_buffer *ab; + + ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_LOGIN); + if (ab) { + audit_log_format(ab, "login pid=%d uid=%u " + "old auid=%u new auid=%u", + task->pid, task->uid, + task->audit_context->loginuid, loginuid); + audit_log_end(ab); + } + task->audit_context->loginuid = loginuid; + } + return 0; +} + +/** + * audit_get_loginuid - get the loginuid for an audit_context + * @ctx: the audit_context + * + * Returns the context's loginuid or -1 if @ctx is NULL. + */ +uid_t audit_get_loginuid(struct audit_context *ctx) +{ + return ctx ? ctx->loginuid : -1; +} + +/** + * audit_ipc_obj - record audit data for ipc object + * @ipcp: ipc permissions + * + * Returns 0 for success or NULL context or < 0 on error. + */ +int audit_ipc_obj(struct kern_ipc_perm *ipcp) +{ + struct audit_aux_data_ipcctl *ax; + struct audit_context *context = current->audit_context; + + if (likely(!context)) + return 0; + + ax = kmalloc(sizeof(*ax), GFP_ATOMIC); + if (!ax) + return -ENOMEM; + + ax->uid = ipcp->uid; + ax->gid = ipcp->gid; + ax->mode = ipcp->mode; + selinux_get_ipc_sid(ipcp, &ax->osid); + + ax->d.type = AUDIT_IPC; + ax->d.next = context->aux; + context->aux = (void *)ax; + return 0; +} + +/** + * audit_ipc_set_perm - record audit data for new ipc permissions + * @qbytes: msgq bytes + * @uid: msgq user id + * @gid: msgq group id + * @mode: msgq mode (permissions) + * + * Returns 0 for success or NULL context or < 0 on error. + */ +int audit_ipc_set_perm(unsigned long qbytes, uid_t uid, gid_t gid, mode_t mode, struct kern_ipc_perm *ipcp) +{ + struct audit_aux_data_ipcctl *ax; + struct audit_context *context = current->audit_context; + + if (likely(!context)) + return 0; + + ax = kmalloc(sizeof(*ax), GFP_ATOMIC); + if (!ax) + return -ENOMEM; + + ax->qbytes = qbytes; + ax->uid = uid; + ax->gid = gid; + ax->mode = mode; + selinux_get_ipc_sid(ipcp, &ax->osid); + + ax->d.type = AUDIT_IPC_SET_PERM; + ax->d.next = context->aux; + context->aux = (void *)ax; + return 0; +} + +int audit_bprm(struct linux_binprm *bprm) +{ + struct audit_aux_data_execve *ax; + struct audit_context *context = current->audit_context; + unsigned long p, next; + void *to; + + if (likely(!audit_enabled || !context)) + return 0; + + ax = kmalloc(sizeof(*ax) + PAGE_SIZE * MAX_ARG_PAGES - bprm->p, + GFP_KERNEL); + if (!ax) + return -ENOMEM; + + ax->argc = bprm->argc; + ax->envc = bprm->envc; + for (p = bprm->p, to = ax->mem; p < MAX_ARG_PAGES*PAGE_SIZE; p = next) { + struct page *page = bprm->page[p / PAGE_SIZE]; + void *kaddr = kmap(page); + next = (p + PAGE_SIZE) & ~(PAGE_SIZE - 1); + memcpy(to, kaddr + (p & (PAGE_SIZE - 1)), next - p); + to += next - p; + kunmap(page); + } + + ax->d.type = AUDIT_EXECVE; + ax->d.next = context->aux; + context->aux = (void *)ax; + return 0; +} + + +/** + * audit_socketcall - record audit data for sys_socketcall + * @nargs: number of args + * @args: args array + * + * Returns 0 for success or NULL context or < 0 on error. + */ +int audit_socketcall(int nargs, unsigned long *args) +{ + struct audit_aux_data_socketcall *ax; + struct audit_context *context = current->audit_context; + + if (likely(!context)) + return 0; + + ax = kmalloc(sizeof(*ax) + nargs * sizeof(unsigned long), GFP_KERNEL); + if (!ax) + return -ENOMEM; + + ax->nargs = nargs; + memcpy(ax->args, args, nargs * sizeof(unsigned long)); + + ax->d.type = AUDIT_SOCKETCALL; + ax->d.next = context->aux; + context->aux = (void *)ax; + return 0; +} + +/** + * audit_sockaddr - record audit data for sys_bind, sys_connect, sys_sendto + * @len: data length in user space + * @a: data address in kernel space + * + * Returns 0 for success or NULL context or < 0 on error. + */ +int audit_sockaddr(int len, void *a) +{ + struct audit_aux_data_sockaddr *ax; + struct audit_context *context = current->audit_context; + + if (likely(!context)) + return 0; + + ax = kmalloc(sizeof(*ax) + len, GFP_KERNEL); + if (!ax) + return -ENOMEM; + + ax->len = len; + memcpy(ax->a, a, len); + + ax->d.type = AUDIT_SOCKADDR; + ax->d.next = context->aux; + context->aux = (void *)ax; + return 0; +} + +/** + * audit_avc_path - record the granting or denial of permissions + * @dentry: dentry to record + * @mnt: mnt to record + * + * Returns 0 for success or NULL context or < 0 on error. + * + * Called from security/selinux/avc.c::avc_audit() + */ +int audit_avc_path(struct dentry *dentry, struct vfsmount *mnt) +{ + struct audit_aux_data_path *ax; + struct audit_context *context = current->audit_context; + + if (likely(!context)) + return 0; + + ax = kmalloc(sizeof(*ax), GFP_ATOMIC); + if (!ax) + return -ENOMEM; + + ax->dentry = dget(dentry); + ax->mnt = mntget(mnt); + + ax->d.type = AUDIT_AVC_PATH; + ax->d.next = context->aux; + context->aux = (void *)ax; + return 0; +} + +/** + * audit_signal_info - record signal info for shutting down audit subsystem + * @sig: signal value + * @t: task being signaled + * + * If the audit subsystem is being terminated, record the task (pid) + * and uid that is doing that. + */ +void audit_signal_info(int sig, struct task_struct *t) +{ + extern pid_t audit_sig_pid; + extern uid_t audit_sig_uid; + + if (unlikely(audit_pid && t->tgid == audit_pid)) { + if (sig == SIGTERM || sig == SIGHUP) { + struct audit_context *ctx = current->audit_context; + audit_sig_pid = current->pid; + if (ctx) + audit_sig_uid = ctx->loginuid; + else + audit_sig_uid = current->uid; + } + } +}