Help with setup
by Gene Dellinger
Hoping this is the right place to get help configuring auditd(laus) on Red
Hat.
I have 4 high security systems that I need to allow a new employee root
access to. I would like to see everything that is done by root or any other
users/processes, however the only thing I can seem to get it to do is tell
me when my cronjobs, the sa stuff runs and login info.
#############################################
filesets.conf:
# Set of files for which we track read access.
#
set secret-files = {
"/etc",
"/bin",
};
#############################################
filter.conf:
# Various primitive predicates
predicate is-null = eq(0);
predicate is-negative = lt(0);
predicate is-system-uid = lt(100);
predicate is-lower-1024 = lt(-1024);
#
# Predicate to check open(2) mode: true iff
# (mode & O_ACCMODE) == O_RDONLY
predicate is-rdonly = mask(O_ACCMODE, O_RDONLY);
#
# Predicates for testing file type, valid when applied
# to a file type argument
predicate __isreg = mask(S_IFMT, S_IFREG);
predicate __isdir = mask(S_IFMT, S_IFDIR);
predicate __ischr = mask(S_IFMT, S_IFCHR);
predicate __isblk = mask(S_IFMT, S_IFBLK);
predicate __issock = mask(S_IFMT, S_IFSOCK);
predicate __islnk = mask(S_IFMT, S_IFLNK);
predicate s_isreg = __isreg(file-mode);
predicate s_isdir = __isdir(file-mode);
predicate s_ischr = __ischr(file-mode);
predicate s_isblk = __isblk(file-mode);
predicate s_issock = __issock(file-mode);
predicate s_islnk = __islnk(file-mode);
predicate is-tempdir = mask(01777, 01777);
predicate is-world-writable = mask(0666, 0666);
#
# Predicates dealing with process exit code
predicate if-crash-signal =
!mask(__WSIGMASK, 0)
&& (mask(__WSIGMASK, __WSIGILL) ||
mask(__WSIGMASK, __WSIGABRT) ||
mask(__WSIGMASK, __WSIGSEGV) ||
mask(__WSIGMASK, __WSIGSTKFLT));
#
# Predicates for audit-tags
predicate is-o-creat = mask(O_CREAT, O_CREAT);
predicate is-ipc-remove = eq(IPC_RMID);
predicate is-ipc-setperms = eq(IPC_SET);
predicate is-ipc-creat = mask(IPC_CREAT, IPC_CREAT);
predicate is-auditdevice = prefix("/dev/audit");
predicate is-cmd-set-auditid = eq(AUIOCSETAUDITID);
predicate is-cmd-set-loginid = eq(AUIOCLOGIN);
#
# Misc filters
filter is-root = is-null(uid);
filter is-setuid = is-null(dumpable);
filter syscall-failed = is-negative(result);
filter syscall-addr-succeed = is-lower-1024(result);
predicate is-af-packet = eq(AF_PACKET);
predicate is-af-netlink = eq(AF_NETLINK);
predicate is-sock-raw = eq(SOCK_RAW);
#
# Include filesets.
#
include "filesets.conf";
#
# "Secret" files should not be read by everyone -
# we also log read access to these files
#
predicate is-secret = prefix(@secret-files);
#
# All regular files owned by a system uid are deemed sensitive
#
predicate is-system-file = is-system-uid(file-uid)
&& !prefix("/var")
&& !is-world-writable(file-mode);
#
# Define ioctls we track
#
set sysconf-ioctls = {
SIOCADDDLCI,
SIOCADDMULTI,
SIOCADDRT,
SIOCBONDCHANGEACTIVE,
SIOCBONDENSLAVE,
SIOCBONDRELEASE,
SIOCBONDSETHWADDR,
SIOCDARP,
SIOCDELDLCI,
SIOCDELMULTI,
SIOCDELRT,
SIOCDIFADDR,
SIOCDRARP,
SIOCETHTOOL,
SIOCGIFBR,
SIOCSARP,
SIOCSIFADDR,
SIOCSIFBR,
SIOCSIFBRDADDR,
SIOCSIFDSTADDR,
SIOCSIFENCAP,
SIOCSIFFLAGS,
SIOCSIFHWADDR,
SIOCSIFHWBROADCAST,
SIOCSIFLINK,
SIOCSIFMAP,
SIOCSIFMEM,
SIOCSIFMETRIC,
SIOCSIFMTU,
SIOCSIFNAME,
SIOCSIFNETMASK,
SIOCSIFPFLAGS,
SIOCSIFSLAVE,
SIOCSIFTXQLEN,
SIOCSMIIREG
};
predicate is-sysconf-ioctl = eq(@sysconf-ioctls);
#
# System calls on file names
#
set file-ops = {
"mkdir", "rmdir", "unlink",
"chmod",
"chown", "lchown",
"chown32", "lchown32",
};
#
# General system related ops
#
set system-ops = {
swapon, swapoff,
create_module, init_module, delete_module,
sethostname, setdomainname,
};
set priv-ops = {
"setuid",
"setuid32",
"seteuid",
"seteuid32",
"setreuid",
"setreuid32",
"setresuid",
"setresuid32",
"setgid",
"setgid32",
"setegid",
"setegid32",
"setregid",
"setregid32",
"setresgid",
"setresgid32",
"setgroups",
"setgroups32",
"capset",
};
#
# Audit-Tags (only syscall related tags are handled here)
#
# define sets of syscalls related to audit-tags
# System calls for changing file modes
set mode-ops = {
"chmod",
"fchmod",
};
# System calls for changing file owner
set owner-ops = {
"chown", "lchown",
"chown32", "lchown32",
"fchown",
};
# System calls doing file link operations
set link-ops = {
"link", "symlink",
};
# System calls for creating device files
set mknod-ops = {
"mknod",
};
# System calls for opening a file
set open-ops = {
"open",
};
# File renaming
set rename-ops = {
"rename",
};
# File truncation
set truncate-ops = {
"truncate", "truncate64",
"ftruncate", "ftruncate64",
};
# Unlink files
set unlink-ops = {
"unlink",
};
# Deletion of directories
set rmdir-ops = {
"rmdir",
};
# Mounting of filesystems
set mount-ops = {
"mount",
};
# Unounting of filesystems
set umount-ops = {
"umount",
"umount2"
};
# Changing user (-role)
set userchange-ops = {
"setuid",
"setuid32",
"seteuid",
"seteuid32",
"setreuid",
"setreuid32",
"setresuid",
"setresuid32",
};
# Execute another program
set execute-ops = {
"execve",
};
# Set real user-ID
set realuid-ops = {
"setuid",
"setuid32",
};
# Set user-IDS in gerneral
set setuserids-ops = {
"setuid",
"setuid32",
"seteuid",
"seteuid32",
"setreuid",
"setreuid32",
"setresuid",
"setresuid32",
};
# Set real group-ID
set realgid-ops = {
"setgid",
"setgid32",
"setgroups",
"setgroups32",
};
# Set group-IDs in gerneral
set setgroups-ops = {
"setgid",
"setgid32",
"setegid",
"setegid32",
"setregid",
"setregid32",
"setresgid",
"setresgid32",
"setgroups",
"setgroups32",
};
# Set other kind of privileges (capabilities)
set privilege-ops = {
"capset",
};
# Change system-time
set timechange-ops = {
"adjtimex",
"stime",
"settimeofday",
};
# bring sets and tags in conjunction
tag "FILE_mode"
syscall @mode-ops = always;
tag "FILE_owner"
syscall @owner-ops = always;
tag "FILE_link"
syscall @link-ops = always;
tag "FILE_mknod"
syscall @mknod-ops = always;
tag "FILE_create"
syscall open = is-o-creat(arg1);
tag "FILE_create"
syscall creat = always;
#tag "FILE_open"
#syscall @open-ops = always;
tag "FILE_open"
syscall @open-ops = (is-system-file(arg0) && !(is-rdonly(arg1)))
|| is-secret(arg0);
tag "FILE_rename"
syscall @rename-ops = always;
tag "FILE_truncate"
syscall @truncate-ops = always;
tag "FILE_unlink"
syscall @unlink-ops = always;
tag "FS_rmdir"
syscall @rmdir-ops = always;
tag "FS_mount"
syscall @mount-ops = always;
tag "FS_umount"
syscall @umount-ops = always;
# I think owner changing doesnt make much sense
tag "MSG_owner"
syscall msgctl = is-ipc-setperms(arg1);
tag "MSG_mode"
syscall msgctl = is-ipc-setperms(arg1);
tag "MSG_delete"
syscall msgctl = is-ipc-remove(arg1);
tag "MSG_create"
syscall msgget = always;
tag "SEM_owner"
syscall semctl = is-ipc-setperms(arg2);
tag "SEM_mode"
syscall semctl = is-ipc-setperms(arg2);
tag "SEM_delete"
syscall semctl = is-ipc-remove(arg2);
tag "SEM_create"
syscall semget = always;
tag "SHM_owner"
syscall shmctl = is-ipc-setperms(arg1);
tag "SHM_mode"
syscall shmctl = is-ipc-setperms(arg1);
tag "SHM_delete"
syscall shmctl = is-ipc-remove(arg1);
tag "SHM_create"
syscall shmget = always;
tag "PRIV_userchange"
syscall @userchange-ops = always;
tag "PROC_execute"
syscall @execute-ops = always;
tag "PROC_realuid"
syscall @realuid-ops = always;
tag "PROC_auditid"
syscall ioctl = (is-auditdevice(arg0) && is-cmd-set-auditid(arg1));
tag "PROC_loginid"
syscall ioctl = (is-auditdevice(arg0) && is-cmd-set-loginid(arg1));
tag "PROC_setuserids"
syscall @setuserids-ops = always;
tag "PROC_realgid"
syscall @realgid-ops = always;
tag "PROC_setgroups"
syscall @setgroups-ops = always;
tag "PROC_privilege"
syscall @privilege-ops = always;
tag "SYS_timechange"
syscall @timechange-ops = always;
# not required by CAPP
syscall ipc = always;
syscall socket = is-af-packet(arg0) || is-sock-raw(arg1);
syscall ioctl = is-sysconf-ioctl(arg1);
#
# Special filters for process/termination
event process-exit = if-crash-signal(exitcode);
#
# Events we want to log unconditionally:
event network-config = always;
event user-message = always;
event process-login = always;
# Custom
predicate is-root = prefix(/bin);
syscall execve = is-root(arg0);
#############################################
Thanks
Gene Dellinger
18 years, 9 months
Re: Linux audit v. Solaris audit
by schaufler-ca.com - Casey Schaufler
--- "Sponsler, Mike" <sponslerm(a)netcsc.com> wrote:
> From: "Sponsler, Mike" <sponslerm(a)netcsc.com>
> To: linux-audit(a)redhat.com
> Date: Thu, 16 Mar 2006 17:12:45 +0000
> Subject: Linux audit v. Solaris audit
>
> Is the audit daemon for linux similiar to the audit
> daemon for solaris
> 10? Specifically does it do BSM auditing?
BSM is Sun's way to say "C2" without actually
commiting to completely meeting the C2
requirements. C2 is the archaic security
specification that is the basis for the Common
Criteria Controlled Access protection Profile
(CAPP). Linux Audit is designed to exceed the
CAPP requirements.
BSM and Linux Audit are independent*
implementations of facilities that are
intended to meet the same need. BSM
is older and based on older criteria.
Linux Audit is newer and based on
modern (as of today) criteria. The two
mechanisms take different approaches
to the problem, but in the end are
more similar than they are different.**
------
* Well, there hasn't been much direct carry over.
** I wrote the original SunOS4.0 audit code.
The two schemes are not that different.
18 years, 9 months
Linux audit v. Solaris audit
by Sponsler, Mike
Hello,
I'm currently working on a project where Solaris 10 was the goal, but
due to some hardware incompatabilities (solaris x86) I'm looking at
using Red Hat enterprise Linux 4.
Is the audit daemon for linux similiar to the audit daemon for solaris
10? Specifically does it do BSM auditing?
Thanks.
Mike Sponsler
sponslerm(a)netcsc.com
18 years, 9 months
to support for wildcard notation
by Evren Kalayciklioglu
is there any way to support for wildcard notation. I
must watch and monitor lots of files in some
directories. This directories contain a lot of file
and also contents are changed by users, sometimes they
add another files, rarely they delete files, lots of
times they change contents of files that are in
directories.
So that, i want add path of directory only. if i
didn't use wildcard notation, audit.log shows only
added files.
Thanks,
Evren Kalayciklioglu
__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com
18 years, 9 months
[patch] fix syscall speedup patch mips typo
by Jason Baron
I think this one is vary obvious. oops.
thanks,
-Jason
--- audit-current/arch/mips/kernel/ptrace.c.bak 2006-03-03 10:46:51.000000000 -0500
+++ audit-current/arch/mips/kernel/ptrace.c 2006-03-03 10:55:46.000000000 -0500
@@ -468,7 +468,7 @@ static inline int audit_arch(void)
*/
asmlinkage void do_syscall_trace(struct pt_regs *regs, int entryexit)
{
- if (audit_invoke_exit && entryexit)
+ if (audit_invoke_exit() && entryexit)
audit_syscall_exit(current, AUDITSC_RESULT(regs->regs[2]),
regs->regs[2]);
@@ -492,7 +492,7 @@ asmlinkage void do_syscall_trace(struct
current->exit_code = 0;
}
out:
- if (audit_invoke_entry && !entryexit)
+ if (audit_invoke_entry() && !entryexit)
audit_syscall_entry(current, audit_arch(), regs->regs[2],
regs->regs[4], regs->regs[5],
regs->regs[6], regs->regs[7]);
18 years, 9 months
[PATCH] return data buffer in audit_list_rules()
by Darrel Goeddel
The following patch will return the data buffer for each rule
when listing the rules via AUDIT_LIST_RULES.
Signed-off-by: Darrel Goeddel <dgoeddel(a)trustedcs.com>
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index c895de7..c8af774 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -588,7 +588,7 @@ static int audit_list_rules(void *_dest)
if (unlikely(!data))
break;
audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
- data, sizeof(*data));
+ data, sizeof(*data) + data->buflen);
kfree(data);
}
}
--
Darrel
18 years, 9 months
Changes to Audit record format
by Loulwa Salem
Hi,
As per the talk we had Monday on the call, here are some of the
formatting issues we would like to see resolved for consistency and
sanity of the parsers we have to write...
Basically .. here is what I am proposing we change in the formatting ..
Unnecessary stray symbols (,:() ..etc surrounded by spaces on both
sides) are a bit inconsistent and problematic (we deal with them
currently, but would be nice to get rid of them and thus the use of all
the exception code to handle them).
All two word fields should have an "_" between the words rather than a
space (since we use the space as a delimeter which makes the most sense,
we end up with lonely words that need to be ignored currently). Using
"_" would make life easier instead.
I am breaking this by audit type and grouping those types that share the
same format together ...
1- DAEMON_START
Remove "," between fields, leave spaces only
Change "auditd pid=" to "auditd_pid="
2- DAEMON_END
Remove "," between fields, leave spaces only
Change "sending auid=" to "sending_auid=" or just "auid="
Change "auditd pid=" to "auditd_pid="
3- CONFIG_CHANGE
type=CONFIG_CHANGE ... audit_enabled=1 old=1 by auid=0
Is there a reason we have the "by" word in there?
type=CONFIG_CHANGE ... auid=0 add rule to list=2 res=1
this is how I am understanding this.. the message is "add rule to
list=2". however the fact that we have "list=2" makes it sound like the
message is "add rule to" and a field is "list=2".
Can we change that to something like (auid=0 add rule to list 2 res=1)
or (auid=0 add rule to list_2 res=1)?
4- USER_CHAUTHTOK
type=USER_CHAUTHTOK ... user pid=13827 uid=0 auid=0 msg='op=changing
name acct=laf_c exe="/usr/sbin/usermod" (hostname=?, addr=?,
terminal=pts/1 res=success)'
Remove "," between fields, leave spaces only
Change "user pid=" to "user_pid="
What happened to msg='SomeString. For example, it might be gpasswd, or
passwd, or some PAM msg .. etc. our cases were checking for that string,
so what happened to it? In some cases it still prints, but not others;
is there a reason for that?
type=USER_CHAUTHTOK ... user pid=12862 uid=0 auid=0 msg='password
aging data updated - acct=laf_a, uid=500, min=-2, max=60, warn=-2,
inact=-2: exe="/usr/bin/passwd" (hostname=?, addr=?, terminal=pts/1
res=success)'
Please remove all those "," and just leave spaces
Remove "-" before "acct="
Note that msg='SomeString is shown, unlike previous example.
5- USER_ACCT, USER_START, USER_END, USER_AUTH, USER_LOGIN
Change "user pid=" to "user_pid="
Remove the lonely ":" after "acct=" field
Remove "," and just leave spaces
Again, some of these have a "msg=" field with no value.
6- CRED_DISP, CRED_ACQ, CRED_REFR
Change "user pid=" to "user_pid="
Remove the lonely ":" after "acct=" field
7- USYS_CONFIG
Change "user pid=" to "user_pid="
Remove "," and just leave spaces
These are the records I see right now. At the moment I am not seeing any
watch records so I don't know if those have formatting issues... I'll
add to this list as I find more.
Thanks,
- Loulwa
18 years, 9 months
Re: Audit Parsing Library Requirements
by schaufler-ca.com - Casey Schaufler
"Steve Grubb" <sgrubb(a)redhat.com> wrote:
> The name will be 7-bit ASCII, however, I cannot
> guarantee what the value will be. It comes from
> the kernel. If someone in another country uses
> their native language to name files and it shows
> up in the audit record, what should we do? This
> subject has come up before and I believe we
> have to make an effort to support them. I don't
> believe CC requires this, our user's do.
Some UNIX audit systems provide mechanism
for (potentially) binary audit data that uses the
hex of the data. Not used when the data is known
to be well behaved text, of course.
name=0x001122BEEF
ugly as it might be, it's character set
independent and always safe.
------------------------
Casey Schaufler
casey(a)schaufler-ca.com
650.906.1780
18 years, 9 months
I can't create true log file.
by Evren Kalayciklioglu
Hello, i am new for audit and its configuration. But i
have to do some configurations for a projects.
What i want to do that: when a user changes a file or
its contains the system make a log file containing
when it be done, who did it, which user did it.
Someone said me "use audit". I tried and also i try
but i can't success. Because i want user name but it
give user id, i want file name but it give a number.I
also want to add printing jobs in this log file the
same conditions.
On the other hand; i think i can't be successful for
configuration and rules files.
How can i do this? can you show me the way,please? is
there any book or e-book? or is there any simple or
different program, way or choice to make for like that
log file.
Regards,
Evren Kalayciklioglu
__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com
18 years, 9 months
[PATCH] filesystem location based auditing
by Amy Griffis
Here is another iteration of the filesystem location based auditing patch,
previous version was posted here:
https://www.redhat.com/archives/linux-audit/2006-February/msg00152.html
The patch is based off of the current audit git tree plus the latest versions
of the context based audit filtering work, posted here:
https://www.redhat.com/archives/linux-audit/2006-February/msg00160.html
https://www.redhat.com/archives/linux-audit/2006-March/msg00071.html
I believe I've addressed all of the feedback I received last round. Here is a
laundry list:
- protect against uninitialized audit_idev (similar fix for audit_sock in
separate patch)
- add INOTIFY dependency to AUDITSYSCALL in Kconfig
- use atomic_set 1 for struct initialization
- move path_lookup and path_release out from under spinlock
- don't call path_release on path_lookup failure
- post-process inotify registration/de-registration instead of using
kthread_run
- remove no longer needed AUDIT_PARENT_DEL and AUDIT_ENTRY_ADD flags
- use a combination of superblock dev and inode # to distinguish watches and
parents; this results in replacing the watch in addition to the watch's rules
in audit_update_watch()
- fix memory corruption due to list_add while traversing same list
- incorporate selinux field copy into audit_dupe_rule()
- modify selinux_audit_rule_update() to use audit_dupe_rule() and do
necessary watch.rules list manipulation
- modify selinux_audit_rule_update() to use filterlist locks instead of
audit_netlink_mutex
- remove the replacement pointer from audit_entry; it doesn't seem to be needed
since we always search for list elements under lock
I'm a bit uncertain about the per-filterlist spinlocks. It may suffice to just use the audit_netlink_mutex as Darrel has been doing. Any advice?
If ok, please include this patch in the next version of the lspp test kernel,
along with the latest inotify kernel API patch. Please don't include either of these patches in -mm yet.
Thanks,
Amy
---
include/linux/audit.h | 1
init/Kconfig | 2
kernel/audit.c | 24 +
kernel/audit.h | 37 +-
kernel/auditfilter.c | 886 ++++++++++++++++++++++++++++++++++++++++++--------
kernel/auditsc.c | 76 ++--
6 files changed, 848 insertions(+), 178 deletions(-)
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 76feae3..4fb7fbd 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -159,6 +159,7 @@
#define AUDIT_INODE 102
#define AUDIT_EXIT 103
#define AUDIT_SUCCESS 104 /* exit >= 0; value ignored */
+#define AUDIT_WATCH 105
#define AUDIT_ARG0 200
#define AUDIT_ARG1 (AUDIT_ARG0+1)
diff --git a/init/Kconfig b/init/Kconfig
index 38416a1..7fc7b20 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -177,7 +177,7 @@ config AUDIT
config AUDITSYSCALL
bool "Enable system-call auditing support"
- depends on AUDIT && (X86 || PPC || PPC64 || S390 || IA64 || UML || SPARC64)
+ depends on AUDIT && INOTIFY && (X86 || PPC || PPC64 || S390 || IA64 || UML || SPARC64)
default y if SECURITY_SELINUX
help
Enable low-overhead system-call auditing infrastructure that
diff --git a/kernel/audit.c b/kernel/audit.c
index 6a44e0a..b0d7fb7 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -55,6 +55,9 @@
#include <net/netlink.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
+#include <linux/inotify.h>
+
+#include "audit.h"
/* No auditing will take place until audit_initialized != 0.
* (Initialization happens after skb_init is called.) */
@@ -99,6 +102,12 @@ static atomic_t audit_lost = ATOMIC_I
/* The netlink socket. */
static struct sock *audit_sock;
+/* Inotify device. */
+struct inotify_device *audit_idev;
+
+/* Audit filter lists, initialized in audit_init() */
+extern struct audit_flist audit_filter_list[];
+
/* 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). */
@@ -552,6 +561,14 @@ static void audit_receive(struct sock *s
/* Initialize audit support at boot time. */
static int __init audit_init(void)
{
+ int i;
+
+ /* must be initialized before any audit_log calls */
+ for (i = 0; i < AUDIT_NR_FILTERS; i++) {
+ INIT_LIST_HEAD(&audit_filter_list[i].head);
+ spin_lock_init(&audit_filter_list[i].lock);
+ }
+
printk(KERN_INFO "audit: initializing netlink socket (%s)\n",
audit_default ? "enabled" : "disabled");
audit_sock = netlink_kernel_create(NETLINK_AUDIT, 0, audit_receive,
@@ -564,6 +581,13 @@ static int __init audit_init(void)
audit_initialized = 1;
audit_enabled = audit_default;
audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized");
+
+#ifdef CONFIG_AUDITSYSCALL
+ audit_idev = inotify_init(audit_handle_ievent);
+ if (IS_ERR(audit_idev))
+ audit_panic("cannot initialize inotify device");
+#endif
+
return 0;
}
__initcall(audit_init);
diff --git a/kernel/audit.h b/kernel/audit.h
index 051ac2a..795fb72 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -23,6 +23,8 @@
#include <linux/fs.h>
#include <linux/audit.h>
+struct inotify_event;
+
/* 0 = no checking
1 = put_count checking
2 = verbose put_count checking
@@ -53,6 +55,27 @@ enum audit_state {
};
/* Rule lists */
+struct audit_parent {
+ atomic_t count; /* reference count */
+ unsigned int flags; /* flag in-process removals */
+ u32 wd; /* inotify watch descriptor */
+ dev_t dev; /* associated superblock device */
+ unsigned long ino; /* associated inode number */
+ struct list_head mlist; /* entry in master_parents */
+ struct list_head ilist; /* entry in inotify registration list*/
+ struct list_head watches; /* associated watches */
+};
+
+struct audit_watch {
+ atomic_t count; /* reference count */
+ char *path; /* watch 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 audit_parent.watches list*/
+ struct list_head rules; /* associated rules */
+};
+
struct audit_field {
u32 type;
u32 val;
@@ -70,18 +93,28 @@ struct audit_krule {
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;
+ unsigned int flags; /* flag list manips in progress */
+ struct audit_krule rule; /* audit rule data */
};
+struct audit_flist {
+ struct list_head head;
+ spinlock_t lock; /* syncs filter data manipulation */
+};
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 void audit_handle_ievent(struct inotify_event *event,
+ const char *dname, struct inode * inode,
+ void *ptr);
extern void audit_send_reply(int pid, int seq, int type,
int done, int multi,
void *payload, int size);
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index b115f51..9ae8be9 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -22,28 +22,66 @@
#include <linux/kernel.h>
#include <linux/audit.h>
#include <linux/kthread.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
#include <linux/netlink.h>
+#include <linux/inotify.h>
#include <linux/selinux.h>
#include "audit.h"
-/* There are three lists of rules -- one to search at task creation
- * time, one to search at syscall entry time, and another to search at
- * syscall exit time. */
-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
-};
+/* Audit filter lists */
+struct audit_flist audit_filter_list[AUDIT_NR_FILTERS];
+
+static LIST_HEAD(master_parents);
+static DEFINE_SPINLOCK(master_parents_lock);
+
+/* Inotify device. */
+extern struct inotify_device *audit_idev;
+
+/* 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
+
+/* Flags for stale filterlist data */
+#define AUDIT_ENTRY_DEL 0x01 /* Rule entry deletion in progress. */
+
+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)) {
+ BUG_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)) {
+ BUG_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];
@@ -60,6 +98,65 @@ static inline void audit_free_rule_rcu(s
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_ATOMIC);
+ if (unlikely(!parent))
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&parent->watches);
+ atomic_set(&parent->count, 1);
+
+ spin_lock(&master_parents_lock);
+ list_add(&parent->mlist, &master_parents);
+ spin_unlock(&master_parents_lock);
+
+ return parent;
+}
+
+/* Initialize a watch entry. */
+static inline struct audit_watch *audit_init_watch(char *path,
+ gfp_t gfp_mask)
+{
+ struct audit_watch *watch;
+
+ watch = kzalloc(sizeof(*watch), gfp_mask);
+ 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,
+ gfp_t gfp_mask)
+{
+ struct audit_entry *entry;
+ struct audit_field *fields;
+
+ entry = kzalloc(sizeof(*entry), gfp_mask);
+ if (unlikely(!entry))
+ return NULL;
+
+ fields = kzalloc(sizeof(*fields) * field_count, gfp_mask);
+ 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)
@@ -87,12 +184,32 @@ static char *audit_unpack_string(void **
return str;
}
+/* Translate a watch string to kernel respresentation. */
+static int audit_to_watch(char *path, struct audit_krule *krule, int fidx)
+{
+ struct audit_field *f = &krule->fields[fidx];
+ struct audit_watch *watch;
+
+ if (path[0] != '/' || path[f->val-1] == '/' ||
+ krule->listnr != AUDIT_FILTER_EXIT ||
+ f->op & ~AUDIT_EQUAL)
+ return -EINVAL;
+
+ watch = audit_init_watch(path, GFP_KERNEL);
+ 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;
- struct audit_field *fields;
int i, err;
err = -EINVAL;
@@ -116,23 +233,14 @@ static inline struct audit_entry *audit_
goto exit_err;
err = -ENOMEM;
- entry = kmalloc(sizeof(*entry), GFP_KERNEL);
- if (unlikely(!entry))
+ entry = audit_init_entry(rule->field_count, GFP_KERNEL);
+ if (!entry)
goto exit_err;
- fields = kmalloc(sizeof(*fields) * rule->field_count, GFP_KERNEL);
- if (unlikely(!fields)) {
- kfree(entry);
- goto exit_err;
- }
-
- memset(&entry->rule, 0, sizeof(struct audit_krule));
- memset(fields, 0, sizeof(struct audit_field));
entry->rule.flags = rule->flags & AUDIT_FILTER_PREPEND;
entry->rule.listnr = listnr;
entry->rule.action = rule->action;
entry->rule.field_count = rule->field_count;
- entry->rule.fields = fields;
for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
entry->rule.mask[i] = rule->mask[i];
@@ -167,7 +275,8 @@ static struct audit_entry *audit_rule_to
f->type == AUDIT_SE_ROLE ||
f->type == AUDIT_SE_TYPE ||
f->type == AUDIT_SE_SEN ||
- f->type == AUDIT_SE_CLR) {
+ f->type == AUDIT_SE_CLR ||
+ f->type == AUDIT_WATCH) {
err = -EINVAL;
goto exit_free;
}
@@ -249,6 +358,18 @@ static struct audit_entry *audit_data_to
} 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(str, &entry->rule, i);
+ if (err) {
+ kfree(str);
+ goto exit_free;
+ }
+ break;
}
}
@@ -278,7 +399,8 @@ static struct audit_rule *audit_krule_to
struct audit_rule *rule;
int i;
- rule = kmalloc(sizeof(*rule), GFP_KERNEL);
+ /* use GFP_ATOMIC because we're under rcu_read_lock() */
+ rule = kmalloc(sizeof(*rule), GFP_ATOMIC);
if (unlikely(!rule))
return ERR_PTR(-ENOMEM);
memset(rule, 0, sizeof(*rule));
@@ -309,7 +431,8 @@ static struct audit_rule_data *audit_kru
void *bufp;
int i;
- data = kmalloc(sizeof(*data) + krule->buflen, GFP_KERNEL);
+ /* use GFP_ATOMIC because we're under rcu_read_lock() */
+ data = kmalloc(sizeof(*data) + krule->buflen, GFP_ATOMIC);
if (unlikely(!data))
return ERR_PTR(-ENOMEM);
memset(data, 0, sizeof(*data));
@@ -332,6 +455,10 @@ static struct audit_rule_data *audit_kru
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;
}
@@ -341,6 +468,12 @@ static struct audit_rule_data *audit_kru
return data;
}
+/* Compare two watches. Considered success if rules don't match. */
+static inline int audit_compare_watch(struct audit_watch *a, struct audit_watch *b)
+{
+ return strcmp(a->path, b->path);
+}
+
/* 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)
@@ -367,6 +500,10 @@ static int audit_compare_rule(struct aud
if (strcmp(a->fields[i].se_str, b->fields[i].se_str))
return 1;
break;
+ case AUDIT_WATCH:
+ if (audit_compare_watch(a->watch, b->watch))
+ return 1;
+ break;
default:
if (a->fields[i].val != b->fields[i].val)
return 1;
@@ -380,22 +517,416 @@ static int audit_compare_rule(struct aud
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_ATOMIC);
+ if (unlikely(!path))
+ return ERR_PTR(-ENOMEM);
+
+ new = audit_init_watch(path, GFP_ATOMIC);
+ if (unlikely(!new)) {
+ kfree(path);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ new->dev = old->dev;
+ new->ino = old->ino;
+ audit_get_parent(old->parent);
+ new->parent = old->parent;
+
+ 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_ATOMIC);
+ 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 should happen elsewhere. */
+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, GFP_ATOMIC);
+ 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 numbers 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_flist *flist = &audit_filter_list[AUDIT_FILTER_EXIT];
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+ struct audit_entry *oentry, *nentry;
+ struct audit_buffer *ab;
+
+ spin_lock(&flist->lock);
+ 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))) {
+ audit_panic("error updating watch");
+ ab = audit_log_start(NULL, GFP_ATOMIC,
+ AUDIT_CONFIG_CHANGE);
+ audit_log_format(ab,
+ "audit skipped update for rules specifying watch=");
+ audit_log_untrustedstring(ab, owatch->path);
+ audit_log_format(ab, " with ino=%lu\n", ino);
+ audit_log_end(ab);
+ 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);
+ if (oentry->flags & AUDIT_ENTRY_DEL)
+ continue;
+
+ nentry = audit_dupe_rule(&oentry->rule, nwatch);
+ if (unlikely(IS_ERR(nentry))) {
+ audit_panic("error updating watch");
+ audit_log(NULL, GFP_ATOMIC, AUDIT_CONFIG_CHANGE,
+ "audit removed rule to be updated\n");
+ 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);
+ }
+ oentry->flags |= AUDIT_ENTRY_DEL;
+ call_rcu(&oentry->rcu, audit_free_rule_rcu);
+ }
+
+ ab = audit_log_start(NULL, GFP_ATOMIC, AUDIT_CONFIG_CHANGE);
+ audit_log_format(ab, "audit updated rules specifying watch=");
+ audit_log_untrustedstring(ab, owatch->path);
+ audit_log_format(ab, " with ino=%lu\n", ino);
+ audit_log_end(ab);
+
+ list_del(&owatch->wlist);
+ audit_put_watch(owatch);
+ goto add_watch_to_parent; /* event applies to a single watch */
+ }
+ spin_unlock(&flist->lock);
+ return;
+
+add_watch_to_parent:
+ list_add(&nwatch->wlist, &parent->watches);
+ spin_unlock(&flist->lock);
+ 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;
+ struct audit_flist *flist = &audit_filter_list[AUDIT_FILTER_EXIT];
+
+ spin_lock(&flist->lock);
+ 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);
+ if (e->flags & AUDIT_ENTRY_DEL)
+ continue;
+
+ list_del(&r->rlist);
+ list_del_rcu(&e->list);
+ e->flags |= AUDIT_ENTRY_DEL;
+ call_rcu(&e->rcu, audit_free_rule_rcu);
+ audit_log(NULL, GFP_ATOMIC, AUDIT_CONFIG_CHANGE,
+ "audit implicitly removed rule from list=%d\n",
+ AUDIT_FILTER_EXIT);
+ }
+ list_del(&w->wlist);
+ audit_put_watch(w);
+ }
+ spin_unlock(&flist->lock);
+}
+
+/* Actually remove the parent; inotify has acknowleged the removal. */
+static inline void audit_remove_parent(struct audit_parent *parent)
+{
+ BUG_ON(!list_empty(&parent->watches));
+ spin_lock(&master_parents_lock);
+ list_del(&parent->mlist);
+ audit_put_parent(parent);
+ spin_unlock(&master_parents_lock);
+}
+
+/* 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) {
+ audit_get_parent(p);
+
+ if (!audit_idev)
+ wd = -EOPNOTSUPP;
+ else
+ wd = inotify_add_watch(audit_idev, nd->dentry->d_inode,
+ AUDIT_IN_WATCH, p);
+ if (wd < 0) {
+ audit_remove_parent_watches(p);
+ audit_remove_parent(p);
+ audit_put_parent(p);
+ ret = wd;
+ } else {
+ /* These values only read during operations which are
+ * currently prevented by audit_netlink_sem. */
+ struct inode *inode = nd->dentry->d_inode;
+ p->wd = wd;
+ p->dev = inode->i_sb->s_dev;
+ p->ino = inode->i_ino;
+ }
+
+ audit_put_parent(p);
+ }
+
+ 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) {
+ if (audit_idev)
+ inotify_ignore(audit_idev, p->wd);
+ 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);
+ }
+}
+
+/* Find an existing parent entry for this watch, or create a new one.
+ * Caller must hold exit filterlist lock. */
+static inline struct audit_parent *audit_find_parent(struct nameidata *nd,
+ struct list_head *in_list)
+{
+ struct audit_parent *p, *parent, *next;
+ struct inode *inode = nd->dentry->d_inode;
+
+ list_for_each_entry_safe(p, next, &master_parents, mlist) {
+ if (p->ino != inode->i_ino ||
+ p->dev != inode->i_sb->s_dev)
+ continue;
+
+ parent = p;
+ goto out;
+ }
+
+ parent = audit_init_parent();
+ if (IS_ERR(parent))
+ goto out;
+ /* add new parent to inotify registration list */
+ list_add(&parent->ilist, in_list);
+
+out:
+ return parent;
+}
+
+/* Find a matching watch entry, or add this one.
+ * Caller must hold exit filterlist lock. */
+static inline int audit_add_watch(struct audit_krule *krule,
+ struct nameidata *ndp, struct nameidata *ndw,
+ struct list_head *list)
+{
+ struct audit_parent *parent;
+ struct audit_watch *w, *watch = krule->watch;
+
+ parent = audit_find_parent(ndp, list);
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+
+ list_for_each_entry(w, &parent->watches, wlist) {
+ if (audit_compare_watch(watch, w))
+ continue;
+
+ audit_put_watch(watch); /* krule's ref */
+ audit_put_watch(watch); /* destroy */
+
+ audit_get_watch(w);
+ krule->watch = watch = w;
+ goto add_rule;
+ }
+
+ audit_get_parent(parent);
+ watch->parent = parent;
+ list_add(&watch->wlist, &parent->watches);
+
+add_rule:
+ list_add(&krule->rlist, &watch->rules);
+
+ if (ndw) {
+ watch->dev = ndw->dentry->d_inode->i_sb->s_dev;
+ watch->ino = ndw->dentry->d_inode->i_ino;
+ }
+
+ return 0;
+}
+
/* Add rule to given filterlist if not a duplicate. Protected by
* audit_netlink_mutex. */
static inline int audit_add_rule(struct audit_entry *entry,
- struct list_head *list)
+ struct audit_flist *flist)
{
struct audit_entry *e;
+ struct audit_watch *watch = entry->rule.watch;
+ struct nameidata *ndp, *ndw;
+ LIST_HEAD(inotify_list);
+ int err;
#ifdef CONFIG_AUDITSYSCALL
int dont_count = 0;
#endif
- /* Do not use the _rcu iterator here, since this is the only
- * addition routine. */
- list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(&entry->rule, &e->rule))
- return -EEXIST;
+ /* The *_rcu iterator is needed to protect from automatic filterlist
+ * updates or removals. */
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &flist->head, list) {
+ if (e->flags & AUDIT_ENTRY_DEL)
+ continue;
+ if (!audit_compare_rule(&entry->rule, &e->rule)) {
+ err = -EEXIST;
+ rcu_read_unlock();
+ goto error;
+ }
}
+ rcu_read_unlock();
/* If either of these, don't count towards total */
#ifdef CONFIG_AUDITSYSCALL
@@ -403,43 +934,111 @@ static inline int audit_add_rule(struct
entry->rule.listnr == AUDIT_FILTER_TYPE)
dont_count = 1;
#endif
+ /* Get watch nameidata before taking spinlock */
+ if (watch) {
+ err = audit_get_nd(watch->path, &ndp, &ndw);
+ if (err)
+ goto error;
+ }
+
+ spin_lock(&flist->lock);
+ if (watch) {
+ 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);
+ list_add_rcu(&entry->list, &flist->head);
} else {
- list_add_tail_rcu(&entry->list, list);
+ list_add_tail_rcu(&entry->list, &flist->head);
}
+ spin_unlock(&flist->lock);
+
#ifdef CONFIG_AUDITSYSCALL
if (!dont_count)
audit_n_rules++;
#endif
+ if (watch) {
+ 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);
+ 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 exit filterlist lock */
+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);
+
+ if (list_empty(&parent->watches)) {
+ /* put parent on the inotify un-registration list */
+ list_add(&parent->ilist, in_list);
+ audit_get_parent(parent);
+ }
+ }
}
/* Remove an existing rule from filterlist. Protected by
* audit_netlink_mutex. */
static inline int audit_del_rule(struct audit_entry *entry,
- struct list_head *list)
+ struct audit_flist *flist)
{
struct audit_entry *e;
+ LIST_HEAD(inotify_list);
- /* Do not use the _rcu iterator here, since this is the only
- * deletion routine. */
- list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(&entry->rule, &e->rule)) {
- list_del_rcu(&e->list);
+ spin_lock(&flist->lock);
+ list_for_each_entry(e, &flist->head, list) {
+ if (e->flags & AUDIT_ENTRY_DEL ||
+ audit_compare_rule(&entry->rule, &e->rule))
+ continue;
+
+ if (e->rule.watch) {
+ audit_remove_watch(&e->rule, &inotify_list);
+ audit_put_watch(entry->rule.watch);
+ }
+
+ list_del_rcu(&e->list);
+ e->flags |= AUDIT_ENTRY_DEL;
#ifdef CONFIG_AUDITSYSCALL
- if (entry->rule.listnr == AUDIT_FILTER_USER ||
- entry->rule.listnr == AUDIT_FILTER_TYPE)
- audit_n_rules++;
+ if (entry->rule.listnr == AUDIT_FILTER_USER ||
+ entry->rule.listnr == AUDIT_FILTER_TYPE)
+ audit_n_rules++;
#endif
- call_rcu(&e->rcu, audit_free_rule_rcu);
+ call_rcu(&e->rcu, audit_free_rule_rcu);
#ifdef CONFIG_AUDITSYSCALL
- audit_n_rules--;
+ audit_n_rules--;
#endif
- return 0;
- }
+ spin_unlock(&flist->lock);
+ audit_n_rules--;
+
+ if (e->rule.watch)
+ audit_inotify_unregister(&inotify_list);
+
+ return 0;
}
+ spin_unlock(&flist->lock);
+ if (entry->rule.watch)
+ audit_put_watch(entry->rule.watch);
return -ENOENT; /* No matching rule */
}
@@ -458,10 +1057,12 @@ static int audit_list(void *_dest)
mutex_lock(&audit_netlink_mutex);
- /* The *_rcu iterators not needed here because we are
- always called with audit_netlink_mutex held. */
+ /* The *_rcu iterator is needed to protect from filesystem
+ * updates or removals. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list) {
+ rcu_read_lock();
+ list_for_each_entry_rcu(entry, &audit_filter_list[i].head,
+ list) {
struct audit_rule *rule;
rule = audit_krule_to_rule(&entry->rule);
@@ -471,6 +1072,7 @@ static int audit_list(void *_dest)
rule, sizeof(*rule));
kfree(rule);
}
+ rcu_read_unlock();
}
audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
@@ -492,19 +1094,21 @@ static int audit_list_rules(void *_dest)
mutex_lock(&audit_netlink_mutex);
- /* The *_rcu iterators not needed here because we are
- always called with audit_netlink_mutex held. */
+ /* The *_rcu iterator is needed to protect from filesystem
+ * updates or removals. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(e, &audit_filter_list[i], list) {
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &audit_filter_list[i].head, list) {
struct audit_rule_data *data;
data = audit_krule_to_data(&e->rule);
if (unlikely(!data))
break;
audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
- data, sizeof(*data));
+ data, sizeof(*data) + data->buflen);
kfree(data);
}
+ rcu_read_unlock();
}
audit_send_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0);
@@ -597,6 +1201,32 @@ int audit_receive_filter(int type, int p
return err;
}
+/**
+ * audit_handle_ievent - handler for Inotify events
+ * @event: information about the event
+ * @dname: dentry name associated with event
+ * @inode: inode associated with event
+ * @ptr: kernel's version of a watch descriptor
+ */
+void audit_handle_ievent(struct inotify_event *event, const char *dname,
+ struct inode *inode, void *ptr)
+{
+ struct audit_parent *parent = (struct audit_parent *)ptr;
+
+ if (event->mask & (IN_CREATE|IN_MOVED_TO) && inode)
+ audit_update_watch(parent, dname, inode->i_sb->s_dev,
+ inode->i_ino);
+ else if (event->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 (event->mask & (AUDIT_IN_SELF))
+ audit_remove_parent_watches(parent);
+ else if (event->mask & IN_IGNORED)
+ audit_remove_parent(parent);
+}
+
int audit_comparator(const u32 left, const u32 op, const u32 right)
{
switch (op) {
@@ -617,7 +1247,39 @@ int audit_comparator(const u32 left, con
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,
@@ -662,7 +1324,8 @@ int audit_filter_user(struct netlink_skb
int ret = 1;
rcu_read_lock();
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) {
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER].head,
+ list) {
if (audit_filter_user_rules(cb, &e->rule, &state)) {
if (state == AUDIT_DISABLED)
ret = 0;
@@ -680,10 +1343,10 @@ int audit_filter_type(int type)
int result = 0;
rcu_read_lock();
- if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE]))
+ if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE].head))
goto unlock_and_return;
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE],
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE].head,
list) {
int i;
for (i = 0; i < e->rule.field_count; i++) {
@@ -723,62 +1386,6 @@ static inline int audit_rule_has_selinux
return 0;
}
-/* Make a copy of src in dest. 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 src
- rule with the dest rule in the list, then free the dest rule. */
-static inline int selinux_audit_rule_update_helper(struct audit_krule *dest,
- struct audit_krule *src)
-{
- int i, err = 0;
-
- dest->vers_ops = src->vers_ops;
- dest->flags = src->flags;
- dest->listnr = src->listnr;
- dest->action = src->action;
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
- dest->mask[i] = src->mask[i];
- dest->buflen = src->buflen;
- dest->field_count = src->field_count;
-
- /* deep copy this information, updating the se_rule fields, because
- the originals will all be freed when the old rule is freed. */
- dest->fields = kzalloc(sizeof(struct audit_field) * dest->field_count,
- GFP_ATOMIC);
- if (!dest->fields)
- return -ENOMEM;
- memcpy(dest->fields, src->fields,
- sizeof(struct audit_field) * dest->field_count);
- for (i = 0; i < dest->field_count; i++) {
- struct audit_field *df = &dest->fields[i];
- struct audit_field *sf = &src->fields[i];
- switch (df->type) {
- case AUDIT_SE_USER:
- case AUDIT_SE_ROLE:
- case AUDIT_SE_TYPE:
- case AUDIT_SE_SEN:
- case AUDIT_SE_CLR:
- /* our own copy of se_str */
- df->se_str = kstrdup(sf->se_str, GFP_ATOMIC);
- if (!df->se_str)
- return -ENOMEM;
- /* our own (refreshed) copy of se_rule */
- err = 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 (err == -EINVAL) {
- printk(KERN_WARNING "selinux audit rule for item %s is invalid\n", df->se_str);
- err = 0;
- }
- if (err)
- return err;
- }
- }
-
- 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
@@ -787,44 +1394,41 @@ static inline int selinux_audit_rule_upd
static int selinux_audit_rule_update(void)
{
struct audit_entry *entry, *nentry;
- int i, err = 0, tmperr;
-
- /* audit_netlink_mutex synchronizes the writers */
- mutex_lock(&audit_netlink_mutex);
+ struct audit_watch *watch;
+ int i, err = 0;
for (i = 0; i < AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list) {
+ /* filterlist lock synchronizes the writers */
+ spin_lock(&audit_filter_list[i].lock);
+ list_for_each_entry(entry, &audit_filter_list[i].head, list) {
if (!audit_rule_has_selinux(&entry->rule))
continue;
- nentry = kmalloc(sizeof(*entry), GFP_ATOMIC);
- if (!nentry) {
+ 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 */
+ * return value */
if (!err)
- err = -ENOMEM;
+ err = PTR_ERR(nentry);
audit_panic("error updating selinux filters");
- continue;
+ 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);
}
-
- tmperr = selinux_audit_rule_update_helper(&nentry->rule,
- &entry->rule);
- if (tmperr) {
- /* save the first error encountered for the
- return value */
- if (!err)
- err = tmperr;
- audit_free_rule(nentry);
- audit_panic("error updating selinux filters");
- continue;
- }
- list_replace_rcu(&entry->list, &nentry->list);
+ entry->flags |= AUDIT_ENTRY_DEL;
call_rcu(&entry->rcu, audit_free_rule_rcu);
}
+ spin_unlock(&audit_filter_list[i].lock);
}
- mutex_unlock(&audit_netlink_mutex);
-
return err;
}
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 05a2dc1..921e79b 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -62,7 +62,7 @@
#include "audit.h"
-extern struct list_head audit_filter_list[];
+extern struct audit_flist audit_filter_list[];
/* No syscall auditing will take place unless audit_enabled != 0. */
extern int audit_enabled;
@@ -166,6 +166,27 @@ struct audit_context {
#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. */
@@ -252,7 +273,7 @@ static int audit_filter_rules(struct tas
}
break;
case AUDIT_INODE:
- if (ctx) {
+ if (ctx && f->val != (unsigned int)-1) {
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)) {
@@ -262,6 +283,9 @@ static int audit_filter_rules(struct tas
}
}
break;
+ case AUDIT_WATCH:
+ result = audit_match_watch(ctx, rule->watch);
+ break;
case AUDIT_LOGINUID:
result = 0;
if (ctx)
@@ -313,7 +337,8 @@ static enum audit_state audit_filter_tas
enum audit_state state;
rcu_read_lock();
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) {
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK].head,
+ list) {
if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
rcu_read_unlock();
return state;
@@ -369,7 +394,7 @@ static inline struct audit_context *audi
if (context->in_syscall && !context->auditable) {
enum audit_state state;
- state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]);
+ state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT].head);
if (state == AUDIT_RECORD_CONTEXT)
context->auditable = 1;
}
@@ -847,7 +872,7 @@ void audit_syscall_entry(struct task_str
state = context->state;
if (state == AUDIT_SETUP_CONTEXT || state == AUDIT_BUILD_CONTEXT)
- state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]);
+ state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY].head);
if (likely(state == AUDIT_DISABLED))
return;
@@ -1091,37 +1116,20 @@ void __audit_inode_child(const char *dna
return;
/* determine matching parent */
- if (dname)
- for (idx = 0; idx < context->name_count; idx++)
- if (context->names[idx].pino == pino) {
- const char *n;
- const char *name = context->names[idx].name;
- int dlen = strlen(dname);
- int nlen = name ? strlen(name) : 0;
-
- if (nlen < dlen)
- continue;
-
- /* disregard trailing slashes */
- n = name + nlen - 1;
- while ((*n == '/') && (n > name))
- n--;
-
- /* find last path component */
- n = n - dlen + 1;
- if (n < name)
- continue;
- else if (n > name) {
- if (*--n != '/')
- continue;
- else
- n++;
- }
+ 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 (strncmp(n, dname, dlen) == 0)
- goto update_context;
- }
+ 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;
18 years, 9 months