Watch Performance
by Steve Grubb
Hello,
Over the last day or two, I re-worked the user space audit code to be able to
control the new file system audit subsystem. As I was doing the work, I
became concerned about the performance impact since it appears to be using
the syscall exit filter.
The syscall exit filter (and entry filter) is expensive to use except in cases
where you need to use it. This is because each rule in it must be examined
during each syscall to see if the current syscall is of interest. The current
lspp configuration has 10 syscall audit rules.
I became curious what the measured impact would be with the current file
system audit implementation. I decide to run the same performance test that I
tested the audit system with a couple weeks ago when inode and IPC problems
were noticed. I used the lspp.16 kernel with profile=2 boot param. The
following table shows the results:
rules seconds
0 49
10 56
25 75
50 115
75 143
90 185
0 rules had this for function usage:
1284 __d_lookup 4.7380
1170 __link_path_walk 0.3098
1065 avc_has_perm_noaudit 1.2144
706 _atomic_dec_and_lock 8.4048
612 do_path_lookup 0.8204
561 dput 1.2986
509 _raw_spin_lock 2.1477
10 rules had this:
1295 __d_lookup 4.7786
1089 audit_filter_syscall 6.3684
1081 __link_path_walk 0.2862
889 avc_has_perm_noaudit 1.0137
676 audit_getname 2.6000
658 do_path_lookup 0.8820
596 _atomic_dec_and_lock 7.0952
25 rules had this:
3193 audit_filter_rules 3.0009
2178 audit_filter_syscall 12.7368
1280 __d_lookup 4.7232
1131 __link_path_walk 0.2994
956 avc_has_perm_noaudit 1.0901
652 _atomic_dec_and_lock 7.7619
530 dput 1.2269
50 rules had this:
11213 audit_filter_rules 10.5385
4654 audit_filter_syscall 27.2164
4100 selinux_task_ctxid 141.3793
1212 __d_lookup 4.4723
1103 __link_path_walk 0.2920
1012 avc_has_perm_noaudit 1.1539
788 _atomic_dec_and_lock 9.3810
75 had this:
15351 audit_filter_rules 14.4276
6032 audit_filter_syscall 35.2749
2066 selinux_task_ctxid 71.2414
1237 __d_lookup 4.5646
1184 __link_path_walk 0.3135
1014 avc_has_perm_noaudit 1.1562
592 _atomic_dec_and_lock 7.0476
and 90 rules had this:
18287 audit_filter_rules 17.1870
9173 audit_filter_syscall 53.6433
4346 selinux_task_ctxid 149.8621
1314 __link_path_walk 0.3479
1218 __d_lookup 4.4945
1070 avc_has_perm_noaudit 1.2201
682 _atomic_dec_and_lock 8.1190
As you can see, the audit_filter_rules and audit_filter_syscall overwhelmed
the profile quickly. It would not be unreasonable for a system to have 40
watches. The lspp rules have 56 of them. With 10 syscall rules added, the
performance of a correctly configured lspp machine will be similar to the 75
rules test. This represents a 186% performance hit compared to no audit
rules.
I do not believe optimizing the audit_filter_rules function will solve the
problem. I think the file system audit algorithm needs to be re-thought. It
simply cannot penalize every syscall.
There are several ways to solve the problem. Maybe what we need to do is use
the watch list to store watches on and add a new field to the context. If a
watch is triggered it sets the flag in the context. When syscall exit is
done, it checks the flag and if set, does both the watch list and the exit
list. Otherwise, it skips the watch list. I don't know if this is feasible,
or a preferred solution, but we need to start looking at how to decouple the
exit list and watches.
-Steve
18 years, 7 months
[PATCH 1/2] fix auditctl -D
by Joy Latten
The fix for the problem of auditctl -D not working
consists of two patches. One is the userspace patch
and the other is for the kernel.
Below is the userspace patch. I added AUDIT_DEL_ALL flag.
Regards,
Joy
diff -urpN audit-1.1.5.orig/lib/msg_typetab.h audit-1.1.5/lib/msg_typetab.h
--- audit-1.1.5.orig/lib/msg_typetab.h 2006-04-27 15:46:56.000000000 -0500
+++ audit-1.1.5/lib/msg_typetab.h 2006-04-28 09:53:13.000000000 -0500
@@ -31,6 +31,7 @@
//_S(AUDIT_LIST, "LIST" )
//_S(AUDIT_ADD, "ADD" )
//_S(AUDIT_DEL, "DEL" )
+//_S(AUDIT_DEL_ALL, "DEL_ALL" )
_S(AUDIT_USER, "USER" )
_S(AUDIT_LOGIN, "LOGIN" )
//_S(AUDIT_SIGNAL_INFO, "SIGNAL_INFO" )
diff -urpN audit-1.1.5.orig/src/auditctl.c audit-1.1.5/src/auditctl.c
--- audit-1.1.5.orig/src/auditctl.c 2006-04-27 15:46:56.000000000 -0500
+++ audit-1.1.5/src/auditctl.c 2006-04-28 09:51:06.000000000 -0500
@@ -1104,62 +1104,12 @@ static int audit_print_reply(struct audi
/* Returns 0 for success and -1 for failure */
static int delete_all_rules(void)
{
- int seq, i;
- int timeout = 40; /* tenths of seconds */
- struct audit_reply rep;
- fd_set read_mask;
+ int rc = 0;
- /* list the rules */
- seq = audit_request_rules_list(fd);
- if (seq <= 0)
+ rc = audit_send(fd, AUDIT_DEL_ALL, NULL, 0);
+ if (rc < 0) {
+ fprintf(stderr, "Error deleting rule (%s)\n", strerror(-rc));
return -1;
-
- FD_ZERO(&read_mask);
- FD_SET(fd, &read_mask);
-
- for (i = 0; i < timeout; i++) {
- struct timeval t;
- int rc;
-
- t.tv_sec = 0;
- t.tv_usec = 100000; /* .1 second */
- do {
- rc = select(fd+1, &read_mask, NULL, NULL, &t);
- } while (rc < 0 && errno == EINTR);
- // We'll try to read just in case
- rc = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
- if (rc > 0) {
- /* Reset timeout */
- i = 0;
-
- /* Don't make decisions based on wrong packet */
- if (rep.nlh->nlmsg_seq != seq)
- continue;
-
- /* If we get done or error, break out */
- if (rep.type == NLMSG_DONE)
- break;
-
- if (rep.type == NLMSG_ERROR && rep.error->error) {
- fprintf(stderr,
- "Error receiving rules list (%s)\n",
- strerror(-rep.error->error));
- return -1;
- }
-
- /* If its not what we are expecting, keep looping */
- if (rep.type != AUDIT_LIST)
- continue;
-
- /* Found it, bounce it right back with delete */
- rc = audit_send(fd, AUDIT_DEL, rep.rule,
- sizeof(struct audit_rule));
- if (rc < 0) {
- fprintf(stderr, "Error deleting rule (%s)\n",
- strerror(-rc));
- return -1;
- }
- }
}
return 0;
18 years, 7 months
[PATCH 2/2] fix auditctl -D
by Joy Latten
The fix for the problem of auditctl -D not working
consists of two patches. One is the userspace patch
and the other is for the kernel.
Below is the kernel patch. I added AUDIT_DEL_ALL flag.
Regards,
Joy
diff -urpN linux-2.6.orig/include/linux/audit.h linux-2.6.patch/include/linux/audit.h
--- linux-2.6.orig/include/linux/audit.h 2006-04-28 15:01:38.000000000 -0500
+++ linux-2.6.patch/include/linux/audit.h 2006-04-28 16:10:06.000000000 -0500
@@ -63,6 +63,7 @@
#define AUDIT_ADD_RULE 1011 /* Add syscall filtering rule */
#define AUDIT_DEL_RULE 1012 /* Delete syscall filtering rule */
#define AUDIT_LIST_RULES 1013 /* List syscall filtering rules */
+#define AUDIT_DEL_ALL 1014 /* Delete all syscall filtering rules */
#define AUDIT_FIRST_USER_MSG 1100 /* Userspace messages mostly uninteresting to kernel */
#define AUDIT_USER_AVC 1107 /* We filter this differently */
diff -urpN linux-2.6.orig/kernel/audit.c linux-2.6.patch/kernel/audit.c
--- linux-2.6.orig/kernel/audit.c 2006-04-28 15:01:37.000000000 -0500
+++ linux-2.6.patch/kernel/audit.c 2006-04-28 16:09:03.000000000 -0500
@@ -451,6 +451,7 @@ static int audit_netlink_ok(kernel_cap_t
case AUDIT_ADD_RULE:
case AUDIT_DEL:
case AUDIT_DEL_RULE:
+ case AUDIT_DEL_ALL:
case AUDIT_SIGNAL_INFO:
if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
err = -EPERM;
@@ -604,6 +605,7 @@ static int audit_receive_msg(struct sk_b
if (nlmsg_len(nlh) < sizeof(struct audit_rule_data))
return -EINVAL;
/* fallthrough */
+ case AUDIT_DEL_ALL:
case AUDIT_LIST_RULES:
err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
uid, seq, data, nlmsg_len(nlh),
diff -urpN linux-2.6.orig/kernel/auditfilter.c linux-2.6.patch/kernel/auditfilter.c
--- linux-2.6.orig/kernel/auditfilter.c 2006-04-28 15:01:37.000000000 -0500
+++ linux-2.6.patch/kernel/auditfilter.c 2006-04-28 16:09:13.000000000 -0500
@@ -1063,6 +1063,21 @@ static inline int audit_del_rule(struct
return -ENOENT; /* No matching rule */
}
+/* Remove all rules from all filterlists. Protected by
+ * audit_netlink_mutex. */
+static void audit_del_all_rules(void)
+{
+ struct audit_entry *e, *e2;
+ int i;
+
+ for (i=0; i<AUDIT_NR_FILTERS; i++) {
+ list_for_each_entry_safe(e, e2, &audit_filter_list[i], list) {
+ list_del_rcu(&e->list);
+ call_rcu(&e->rcu, audit_free_rule_rcu);
+ }
+ }
+}
+
/* 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)
@@ -1233,6 +1248,12 @@ int audit_receive_filter(int type, int p
audit_free_rule(entry);
break;
+ case AUDIT_DEL_ALL:
+ audit_del_all_rules();
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "auid=%u remove all rules res=%d\n",
+ loginuid, !err);
+ break;
default:
return -EINVAL;
}
diff -urpN linux-2.6.orig/security/selinux/nlmsgtab.c linux-2.6.patch/security/selinux/nlmsgtab.c
--- linux-2.6.orig/security/selinux/nlmsgtab.c 2006-04-28 15:02:20.000000000 -0500
+++ linux-2.6.patch/security/selinux/nlmsgtab.c 2006-04-28 16:08:23.000000000 -0500
@@ -109,6 +109,7 @@ static struct nlmsg_perm nlmsg_audit_per
{ AUDIT_LIST_RULES, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV },
{ AUDIT_ADD_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_DEL_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+ { AUDIT_DEL_ALL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_RELAY },
{ AUDIT_SIGNAL_INFO, NETLINK_AUDIT_SOCKET__NLMSG_READ },
};
18 years, 7 months
[PATCH] audit file restructuring in kernel/
by Timothy R. Chavez
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 <faith(a)redhat.com>
- *
- * 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 <linux/init.h>
-#include <asm/types.h>
-#include <asm/atomic.h>
-#include <linux/mm.h>
-#include <linux/module.h>
-#include <linux/err.h>
-#include <linux/kthread.h>
-
-#include <linux/audit.h>
-
-#include <net/sock.h>
-#include <net/netlink.h>
-#include <linux/skbuff.h>
-#include <linux/netlink.h>
-#include <linux/selinux.h>
-#include <linux/inotify.h>
-
-#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<len; i++) {
- *ptr++ = hex[(buf[i] & 0xF0)>>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, "<no memory>");
- 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, "<too long>");
- } 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 <linux/fs.h>
-#include <linux/audit.h>
-
-/* 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 <linux/kernel.h>
-#include <linux/audit.h>
-#include <linux/kthread.h>
-#include <linux/mutex.h>
-#include <linux/fs.h>
-#include <linux/namei.h>
-#include <linux/netlink.h>
-#include <linux/inotify.h>
-#include <linux/selinux.h>
-#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 <linux/audit.h> */
-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; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list) {
- struct audit_rule *rule;
-
- rule = audit_krule_to_rule(&entry->rule);
- 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; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(e, &audit_filter_list[i], list) {
- struct audit_rule_data *data;
-
- data = audit_krule_to_data(&e->rule);
- 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 <faith(a)redhat.com>
- *
- * 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(a)suse.de and Copyright 2003 SuSE Linux AG.
- *
- * The support of additional filter rules compares (>, <, >=, <=) was
- * added by Dustin Kirkland <dustin.kirkland(a)us.ibm.com>, 2005.
- *
- * Modified by Amy Griffis <amy.griffis(a)hp.com> to collect additional
- * filesystem information.
- *
- * Subject and object context labeling support added by <danjones(a)us.ibm.com>
- * and <dustin.kirkland(a)us.ibm.com> for LSPP certification compliance.
- */
-
-#include <linux/init.h>
-#include <asm/types.h>
-#include <asm/atomic.h>
-#include <asm/types.h>
-#include <linux/fs.h>
-#include <linux/namei.h>
-#include <linux/mm.h>
-#include <linux/module.h>
-#include <linux/mount.h>
-#include <linux/socket.h>
-#include <linux/audit.h>
-#include <linux/personality.h>
-#include <linux/time.h>
-#include <linux/netlink.h>
-#include <linux/compiler.h>
-#include <asm/unistd.h>
-#include <linux/security.h>
-#include <linux/list.h>
-#include <linux/tty.h>
-#include <linux/selinux.h>
-#include <linux/binfmts.h>
-
-#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; i<axs->nargs; 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 <faith(a)redhat.com>
+ *
+ * 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 <linux/init.h>
+#include <asm/types.h>
+#include <asm/atomic.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/kthread.h>
+
+#include <linux/audit.h>
+
+#include <net/sock.h>
+#include <net/netlink.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/selinux.h>
+#include <linux/inotify.h>
+
+#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<len; i++) {
+ *ptr++ = hex[(buf[i] & 0xF0)>>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, "<no memory>");
+ 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, "<too long>");
+ } 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 <linux/fs.h>
+#include <linux/audit.h>
+
+/* 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 <linux/kernel.h>
+#include <linux/audit.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/netlink.h>
+#include <linux/inotify.h>
+#include <linux/selinux.h>
+#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 <linux/audit.h> */
+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; i<AUDIT_NR_FILTERS; i++) {
+ list_for_each_entry(entry, &audit_filter_list[i], list) {
+ struct audit_rule *rule;
+
+ rule = audit_krule_to_rule(&entry->rule);
+ 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; i<AUDIT_NR_FILTERS; i++) {
+ list_for_each_entry(e, &audit_filter_list[i], list) {
+ struct audit_rule_data *data;
+
+ data = audit_krule_to_data(&e->rule);
+ 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 <faith(a)redhat.com>
+ *
+ * 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(a)suse.de and Copyright 2003 SuSE Linux AG.
+ *
+ * The support of additional filter rules compares (>, <, >=, <=) was
+ * added by Dustin Kirkland <dustin.kirkland(a)us.ibm.com>, 2005.
+ *
+ * Modified by Amy Griffis <amy.griffis(a)hp.com> to collect additional
+ * filesystem information.
+ *
+ * Subject and object context labeling support added by <danjones(a)us.ibm.com>
+ * and <dustin.kirkland(a)us.ibm.com> for LSPP certification compliance.
+ */
+
+#include <linux/init.h>
+#include <asm/types.h>
+#include <asm/atomic.h>
+#include <asm/types.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mount.h>
+#include <linux/socket.h>
+#include <linux/audit.h>
+#include <linux/personality.h>
+#include <linux/time.h>
+#include <linux/netlink.h>
+#include <linux/compiler.h>
+#include <asm/unistd.h>
+#include <linux/security.h>
+#include <linux/list.h>
+#include <linux/tty.h>
+#include <linux/selinux.h>
+#include <linux/binfmts.h>
+
+#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; i<axs->nargs; 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;
+ }
+ }
+}
18 years, 7 months
[PATCH] minor audit updates
by Serge E. Hallyn
Just a few minor proposed updates. Only the last one will
actually affect behavior. The rest are just misleading
code.
Several AUDIT_SET functions return 'old' value, but only
return value <0 is checked for. So just return 0.
propagate audit_set_rate_limit and audit_set_backlog_limit
error values
In audit_buffer_free, the audit_freelist_count was being
incremented even when we discard the return buffer, so
audit_freelist_count can end up wrong. This could cause
the actual freelist to shrink over time, eventually
threatening to degrate audit performance.
Signed-off-by: Serge E. Hallyn <serue(a)us.ibm.com>
---
kernel/audit.c | 19 ++++++++++---------
1 files changed, 10 insertions(+), 9 deletions(-)
829f7e451ed013d596ef695f8d8601f6391812cc
diff --git a/kernel/audit.c b/kernel/audit.c
index 7637410..64f43a9 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -252,7 +252,7 @@ static int audit_set_rate_limit(int limi
"audit_rate_limit=%d old=%d by auid=%u",
limit, old, loginuid);
audit_rate_limit = limit;
- return old;
+ return 0;
}
static int audit_set_backlog_limit(int limit, uid_t loginuid, u32 sid)
@@ -275,7 +275,7 @@ static int audit_set_backlog_limit(int l
"audit_backlog_limit=%d old=%d by auid=%u",
limit, old, loginuid);
audit_backlog_limit = limit;
- return old;
+ return 0;
}
static int audit_set_enabled(int state, uid_t loginuid, u32 sid)
@@ -301,7 +301,7 @@ static int audit_set_enabled(int state,
"audit_enabled=%d old=%d by auid=%u",
state, old, loginuid);
audit_enabled = state;
- return old;
+ return 0;
}
static int audit_set_failure(int state, uid_t loginuid, u32 sid)
@@ -329,7 +329,7 @@ static int audit_set_failure(int state,
"audit_failure=%d old=%d by auid=%u",
state, old, loginuid);
audit_failure = state;
- return old;
+ return 0;
}
static int kauditd_thread(void *dummy)
@@ -365,7 +365,6 @@ static int kauditd_thread(void *dummy)
remove_wait_queue(&kauditd_wait, &wait);
}
}
- return 0;
}
int audit_send_list(void *_dest)
@@ -549,10 +548,10 @@ static int audit_receive_msg(struct sk_b
audit_pid = status_get->pid;
}
if (status_get->mask & AUDIT_STATUS_RATE_LIMIT)
- audit_set_rate_limit(status_get->rate_limit,
+ err = 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,
+ err = audit_set_backlog_limit(status_get->backlog_limit,
loginuid, sid);
break;
case AUDIT_USER:
@@ -723,10 +722,12 @@ static void audit_buffer_free(struct aud
kfree_skb(ab->skb);
spin_lock_irqsave(&audit_freelist_lock, flags);
- if (++audit_freelist_count > AUDIT_MAXFREE)
+ if (audit_freelist_count > AUDIT_MAXFREE)
kfree(ab);
- else
+ else {
+ audit_freelist_count++;
list_add(&ab->list, &audit_freelist);
+ }
spin_unlock_irqrestore(&audit_freelist_lock, flags);
}
--
1.3.0
18 years, 7 months
Re: deadlock fixes
by Alexander Viro
On Thu, Apr 27, 2006 at 07:27:25AM -0400, Alexander Viro wrote:
> handling, as discussed on IRC yesterday.
Patch in question (thanks to Steve for reminding to send it to list):
diff --git a/kernel/audit.c b/kernel/audit.c
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -368,6 +368,46 @@ static int kauditd_thread(void *dummy)
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
@@ -385,29 +425,13 @@ void audit_send_reply(int pid, int seq,
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);
+ skb = audit_make_reply(pid, seq, type, done, multi, payload, size);
if (!skb)
return;
-
- nlh = NLMSG_PUT(skb, pid, seq, t, size);
- nlh->nlmsg_flags = flags;
- data = NLMSG_DATA(nlh);
- memcpy(data, payload, size);
-
/* 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;
-
-nlmsg_failure: /* Used by NLMSG_PUT */
- if (skb)
- kfree_skb(skb);
}
/*
diff --git a/kernel/audit.h b/kernel/audit.h
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -95,12 +95,22 @@ struct audit_entry {
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 *);
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -1065,19 +1065,12 @@ static inline int audit_del_rule(struct
/* List rules using struct audit_rule. Exists for backward
* compatibility with userspace. */
-static int audit_list(void *_dest)
+static void audit_list(int pid, int seq, struct sk_buff_head *q)
{
- int pid, seq;
- int *dest = _dest;
+ struct sk_buff *skb;
struct audit_entry *entry;
int i;
- pid = dest[0];
- seq = dest[1];
- kfree(dest);
-
- mutex_lock(&audit_filter_mutex);
-
/* This is a blocking read, so use audit_filter_mutex instead of rcu
* iterator to sync with list writers. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
@@ -1087,31 +1080,25 @@ static int audit_list(void *_dest)
rule = audit_krule_to_rule(&entry->rule);
if (unlikely(!rule))
break;
- audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
+ skb = audit_make_reply(pid, seq, AUDIT_LIST, 0, 1,
rule, sizeof(*rule));
+ if (skb)
+ skb_queue_tail(q, skb);
kfree(rule);
}
}
- audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
-
- mutex_unlock(&audit_filter_mutex);
- return 0;
+ 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 int audit_list_rules(void *_dest)
+static void audit_list_rules(int pid, int seq, struct sk_buff_head *q)
{
- int pid, seq;
- int *dest = _dest;
+ struct sk_buff *skb;
struct audit_entry *e;
int i;
- pid = dest[0];
- seq = dest[1];
- kfree(dest);
-
- mutex_lock(&audit_filter_mutex);
-
/* This is a blocking read, so use audit_filter_mutex instead of rcu
* iterator to sync with list writers. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
@@ -1121,15 +1108,16 @@ static int audit_list_rules(void *_dest)
data = audit_krule_to_data(&e->rule);
if (unlikely(!data))
break;
- audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
+ 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);
}
}
- audit_send_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0);
-
- mutex_unlock(&audit_filter_mutex);
- return 0;
+ skb = audit_make_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0);
+ if (skb)
+ skb_queue_tail(q, skb);
}
/**
@@ -1147,7 +1135,7 @@ int audit_receive_filter(int type, int p
size_t datasz, uid_t loginuid, u32 sid)
{
struct task_struct *tsk;
- int *dest;
+ struct audit_netlink_list *dest;
int err = 0;
struct audit_entry *entry;
@@ -1160,18 +1148,22 @@ int audit_receive_filter(int type, int p
* happen if we're actually running in the context of auditctl
* trying to _send_ the stuff */
- dest = kmalloc(2 * sizeof(int), GFP_KERNEL);
+ dest = kmalloc(sizeof(struct audit_netlink_list), GFP_KERNEL);
if (!dest)
return -ENOMEM;
- dest[0] = pid;
- dest[1] = seq;
+ dest->pid = pid;
+ skb_queue_head_init(&dest->q);
+ mutex_lock(&audit_filter_mutex);
if (type == AUDIT_LIST)
- tsk = kthread_run(audit_list, dest, "audit_list");
+ audit_list(pid, seq, &dest->q);
else
- tsk = kthread_run(audit_list_rules, dest,
- "audit_list_rules");
+ 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);
}
--
0.99.9.GIT
18 years, 7 months
deadlock fixes
by Alexander Viro
* Amy's patch removing audit_add_rm_mutex applied and folded.
* Added a fix for deadlocks in audit_list_rules()/audit_list()
handling, as discussed on IRC yesterday.
All that stuff is in lspp.b9 (starts off the tip of audit.b8, same as
lspp.b8). Patches on devserv updated.
Note: it builds, but it's untested. I'm going to hold off with moving
it to audit.b* (i.e. reordering and feeding to -mm) until we know it
really solves the problem.
18 years, 7 months
[PATCH git] revert audit_netlink_mutex change in filesystem audit patch
by Amy Griffis
Replacing the audit_netlink_mutex with audit_add_rm_mutex wasn't a
good idea, so put it back. While we're at it, use a more descriptive
name.
Please fold in with lspp.b8 d4bae8540266d609990e7c60acaca488c9ee45c2.
Signed-off-by: Amy Griffis <amy.griffis(a)hp.com>
--
audit.c | 6 ++++++
auditfilter.c | 12 ------------
2 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/kernel/audit.c b/kernel/audit.c
index 7addbf9..0246f44 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -118,6 +118,9 @@ 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. */
+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. */
@@ -628,11 +631,14 @@ static void audit_receive(struct sock *s
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);
}
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index eb102ff..d056173 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -41,13 +41,6 @@ #include "audit.h"
* 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.
- *
- * audit_add_rm_mutex:
- * Prevents a removal request for a rule that is currently being
- * added. The audit_filter_mutex must be dropped to do some parts
- * of add/remove processing, so may not be used for this purpose.
- * This situation could be mitigated by referencing rules by id
- * numbers.
*/
/*
@@ -99,7 +92,6 @@ #endif
};
DEFINE_MUTEX(audit_filter_mutex);
-DEFINE_MUTEX(audit_add_rm_mutex);
/* Inotify handle */
extern struct inotify_handle *audit_ih;
@@ -1193,10 +1185,8 @@ int audit_receive_filter(int type, int p
if (IS_ERR(entry))
return PTR_ERR(entry);
- mutex_lock(&audit_add_rm_mutex);
err = audit_add_rule(entry,
&audit_filter_list[entry->rule.listnr]);
- mutex_unlock(&audit_add_rm_mutex);
if (sid) {
char *ctx = NULL;
@@ -1228,10 +1218,8 @@ int audit_receive_filter(int type, int p
if (IS_ERR(entry))
return PTR_ERR(entry);
- mutex_lock(&audit_add_rm_mutex);
err = audit_del_rule(entry,
&audit_filter_list[entry->rule.listnr]);
- mutex_unlock(&audit_add_rm_mutex);
if (sid) {
char *ctx = NULL;
18 years, 7 months
Subject: [PATCH] Fix audit_list{,_rules} deadlock
by George C. Wilson
Following is Serge's patch:
From: Serge Hallyn <serue(a)us.ibm.com>
Subject: [PATCH] Fix audit_list{,_rules} deadlock
The audit_list and audit_list_rules functions send all stored audit
rules to userspace over netlink from a kthread. They do this entirely
from under the audit_netlink_mutex. If the netlink buffer gets to
being too large, then netlink will wait for the receiver to grab the
contents. But the receiver is in audit_receive, which also grabs
the audit_netlink_mutex. Deadlock.
This patch makes audit_list{,_rules} form a list of rules under the
mutex, then drop the mutex, then send the list.
Signed-off-by: Serge Hallyn <serue(a)us.ibm.com>
---
kernel/auditfilter.c | 54
+++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 46 insertions(+), 8 deletions(-)
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index 63aa039..b2e656f 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -510,12 +510,22 @@ static inline int audit_del_rule(struct
/* List rules using struct audit_rule. Exists for backward
* compatibility with userspace. */
+struct au_rule_list {
+ union {
+ struct audit_rule *rule;
+ struct audit_rule_data *data;
+ } u;
+ struct list_head next;
+};
+
static int audit_list(void *_dest)
{
int pid, seq;
int *dest = _dest;
struct audit_entry *entry;
int i;
+ LIST_HEAD(sendlist);
+ struct au_rule_list *r, *r1;
pid = dest[0];
seq = dest[1];
@@ -532,14 +542,27 @@ static int audit_list(void *_dest)
rule = audit_krule_to_rule(&entry->rule);
if (unlikely(!rule))
break;
- audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
- rule, sizeof(*rule));
- kfree(rule);
+ r = kmalloc(sizeof(*r), GFP_KERNEL);
+ if (unlikely(!r)) {
+ kfree(rule);
+ break;
+ }
+ r->u.rule = rule;
+ list_add(&r->next, &sendlist);
}
}
- audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
mutex_unlock(&audit_netlink_mutex);
+
+ list_for_each_entry_safe(r, r1, &sendlist, next) {
+ audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
+ r->u.rule, sizeof(*r->u.rule));
+ list_del(&r->next);
+ kfree(r->u.rule);
+ kfree(r);
+ }
+
+ audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
return 0;
}
@@ -549,6 +572,8 @@ static int audit_list_rules(void *_dest)
int pid, seq;
int *dest = _dest;
struct audit_entry *e;
+ LIST_HEAD(sendlist);
+ struct au_rule_list *r, *r1;
int i;
pid = dest[0];
@@ -566,14 +591,27 @@ static int audit_list_rules(void *_dest)
data = audit_krule_to_data(&e->rule);
if (unlikely(!data))
break;
- audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
- data, sizeof(*data));
- kfree(data);
+ r = kmalloc(sizeof(*r), GFP_KERNEL);
+ if (unlikely(!r)) {
+ kfree(data);
+ break;
+ }
+ r->u.data = data;
+ list_add(&r->next, &sendlist);
}
}
+ mutex_unlock(&audit_netlink_mutex);
+
+ list_for_each_entry_safe(r, r1, &sendlist, next) {
+ audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
+ r->u.data, sizeof(*r->u.data));
+ list_del(&r->next);
+ kfree(r->u.data);
+ kfree(r);
+ }
+
audit_send_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0);
- mutex_unlock(&audit_netlink_mutex);
return 0;
}
--
George Wilson <ltcgcw(a)us.ibm.com>
IBM Linux Technology Center
18 years, 7 months
[PATCH git] fix race conditions in filesystem audit patch
by Amy Griffis
This patch fixes the following issues in the latest filesystem audit
patch:
- fix error handling on call to audit_init_watch()
- remove unnecessary check for watch in audit_add_rule()
- in audit_del_rule(), don't check e->rule.watch after calling
audit_free_rule_rcu
- always update watch filter fields in audit_add_watch(), in case
we don't find an existing watch struct
- don't drop audit_filter_mutex between adding to existing
watches/parents and adding the rule to the filterlist
If these fixes look correct, please fold them in with lspp.b7
f11fcf7556067b41d592d970f5f5c6ffbdd1e8fd on next rebase.
Signed-off-by: Amy Griffis <amy.griffis(a)hp.com>
---
auditfilter.c | 144 ++++++++++++++++++++++++++++++++++------------------------
1 files changed, 86 insertions(+), 58 deletions(-)
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index f1151a2..eb102ff 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -585,9 +585,9 @@ static inline struct audit_watch *audit_
return ERR_PTR(-ENOMEM);
new = audit_init_watch(path);
- if (unlikely(!new)) {
+ if (unlikely(IS_ERR(new))) {
kfree(path);
- return ERR_PTR(-ENOMEM);
+ goto out;
}
new->dev = old->dev;
@@ -595,6 +595,7 @@ static inline struct audit_watch *audit_
audit_get_parent(old->parent);
new->parent = old->parent;
+out:
return new;
}
@@ -853,77 +854,103 @@ static inline void audit_put_nd(struct n
}
}
-/* Find a matching watch entry, or add this one. */
-static inline int audit_add_watch(struct audit_krule *krule,
- struct nameidata *ndp, struct nameidata *ndw,
- struct list_head *inotify_list)
+/* 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)
{
- int ret;
- struct inotify_watch *iwatch;
struct audit_parent *parent;
struct audit_watch *w, *watch = krule->watch;
int watch_found = 0;
- ret = inotify_find_watch(audit_ih, ndp->dentry->d_inode, &iwatch);
- if (ret < 0) {
- parent = audit_init_parent();
- if (IS_ERR(parent))
- return PTR_ERR(parent);
+ parent = container_of(iwatch, struct audit_parent, wdata);
- audit_get_parent(parent);
- watch->parent = parent;
+ /* parent was moved before we took audit_filter_mutex */
+ if (parent->flags & AUDIT_PARENT_INVALID)
+ return -ENOENT;
- /* 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);
+ list_for_each_entry(w, &parent->watches, wlist) {
+ if (strcmp(watch->path, w->path))
+ continue;
- /* update watch filter fields */
- if (ndw) {
- watch->dev = ndw->dentry->d_inode->i_sb->s_dev;
- watch->ino = ndw->dentry->d_inode->i_ino;
- }
-
- /* add parent to inotify registration list */
- list_add(&parent->ilist, inotify_list);
- } else {
- parent = container_of(iwatch, struct audit_parent, wdata);
+ watch_found = 1;
- mutex_lock(&audit_filter_mutex);
- if (parent->flags & AUDIT_PARENT_INVALID) {
- /* parent was moved before we took the lock */
- mutex_unlock(&audit_filter_mutex);
- return -ENOENT;
- }
+ /* put krule's and initial refs to temporary watch */
+ audit_put_watch(watch);
+ audit_put_watch(watch);
- list_for_each_entry(w, &parent->watches, wlist) {
- if (strcmp(watch->path, w->path))
- continue;
+ audit_get_watch(w);
+ krule->watch = watch = w;
+ break;
+ }
- watch_found = 1;
+ if (!watch_found) {
+ audit_get_parent(parent);
+ watch->parent = parent;
- /* put krule's and initial refs to temporary watch */
- audit_put_watch(watch);
- audit_put_watch(watch);
+ list_add(&watch->wlist, &parent->watches);
+ }
- audit_get_watch(w);
- krule->watch = watch = w;
- break;
- }
+ list_add(&krule->rlist, &watch->rules);
- if (!watch_found) {
- audit_get_parent(parent);
- watch->parent = parent;
+ return 0;
+}
- list_add(&watch->wlist, &parent->watches);
- }
+/* 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;
- list_add(&krule->rlist, &watch->rules);
- mutex_unlock(&audit_filter_mutex);
+ /* update watch filter fields */
+ if (ndw) {
+ watch->dev = ndw->dentry->d_inode->i_sb->s_dev;
+ watch->ino = ndw->dentry->d_inode->i_ino;
}
- return 0;
+ /* 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. */
@@ -954,7 +981,9 @@ static inline int audit_add_rule(struct
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);
@@ -962,7 +991,6 @@ static inline int audit_add_rule(struct
}
}
- mutex_lock(&audit_filter_mutex);
if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
list_add_rcu(&entry->list, list);
} else {
@@ -970,7 +998,7 @@ static inline int audit_add_rule(struct
}
mutex_unlock(&audit_filter_mutex);
- if (watch && !list_empty(&inotify_list)) {
+ if (!list_empty(&inotify_list)) {
err = audit_inotify_register(ndp, &inotify_list);
if (err)
goto error;
@@ -1031,7 +1059,7 @@ static inline int audit_del_rule(struct
call_rcu(&e->rcu, audit_free_rule_rcu);
mutex_unlock(&audit_filter_mutex);
- if (e->rule.watch && !list_empty(&inotify_list))
+ if (!list_empty(&inotify_list))
audit_inotify_unregister(&inotify_list);
return 0;
18 years, 7 months