Skip to contentSkip to conversation

filesystem auditing by location+name

RFC PATCH In review Timothy R. Chavez 3 participants 7 messages Jun 10, 2005 – Jun 13, 2005

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 filesystem auditing by location+name

Hello,

This patch augments the audit subsystem and VFS to support file system auditing in which an object is audited based on its location and name. Depending on the administrator's requirement, this feature can be used standalone or in conjunction with the (device,inode)-based filters.  The audit subsystem is currently incapable of auditing a file system object based on its location and name.  This is critical for auditing well-defined and security-relevant locations such as /etc/shadow, where the file is re-created on each transaction and cannot rely solely on the (device, inode)-based filters to ensure persistence of auditing across transactions.

This work is being done to make the Linux audit subsystem compliant with Common Criteria's Controlled Access Protection Profile (CAPP) specification. CAPP requires that the administrator is able to audit a file's content in two fundamental ways:

1)  Where content confidentiality is important.

This includes security-relevant databases like /etc/passwd and the audit logs of which the contents are security sensitive. The (device,inode)-based system-call filters are sufficient to cover this requirement and are already available to the administrator.

2)  Where content integrity is important.

This includes configuration files that affect the operation of trusted programs like /etc/shadow.  Content integrity ensures audit persists across transactions where the underlying inode may change, but the content remains relevant.  This is not doable without this patch.

We've submitted this patch to LKML with the ultimate goal of upstream acceptance.  All questions and comments are welcomed.

This patch is against audit-2.6.git and appears below.

-tim

---
 fs/dcache.c                 |    9
 fs/inode.c                  |    7
 fs/namei.c                  |   32 +
 include/linux/audit.h       |   74 +++
 include/linux/fs.h          |    2
 init/Kconfig                |   12
 kernel/Makefile             |    1
 kernel/audit.c              |   14
 kernel/auditfs.c            |  852 ++++++++++++++++++++++++++++++++++++++++++++
 kernel/auditsc.c            |  109 +++++
 security/selinux/nlmsgtab.c |    3
 11 files changed, 1107 insertions(+), 8 deletions(-)
fs/dcache.c +9 -0
diff -Nurp audit-2.6.git/fs/dcache.c audit-2.6.git~tc/fs/dcache.c
@@ -32,6 +32,7 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /* #define DCACHE_DEBUG 1 */
 
@@ -97,6 +98,7 @@ static inline void dentry_iput(struct de
 {
 	struct inode *inode = dentry->d_inode;
 	if (inode) {
+		audit_update_watch(dentry, AUDIT_UPDATE_REMOVE);
 		dentry->d_inode = NULL;
 		list_del_init(&dentry->d_alias);
 		spin_unlock(&dentry->d_lock);
@@ -802,6 +804,7 @@ void d_instantiate(struct dentry *entry,
 	if (inode)
 		list_add(&entry->d_alias, &inode->i_dentry);
 	entry->d_inode = inode;
+	audit_update_watch(entry, AUDIT_UPDATE_NORMAL);
 	spin_unlock(&dcache_lock);
 	security_d_instantiate(entry, inode);
 }
@@ -978,6 +981,7 @@ struct dentry *d_splice_alias(struct ino
 		new = __d_find_alias(inode, 1);
 		if (new) {
 			BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
+			audit_update_watch(new, AUDIT_UPDATE_NORMAL);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(new, inode);
 			d_rehash(dentry);
@@ -987,6 +991,7 @@ struct dentry *d_splice_alias(struct ino
 			/* d_instantiate takes dcache_lock, so we do it by hand */
 			list_add(&dentry->d_alias, &inode->i_dentry);
 			dentry->d_inode = inode;
+			audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
@@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentry
 		if (!d_unhashed(dentry)) {
 			atomic_inc(&dentry->d_count);
 			found = dentry;
+			audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 		}
 		spin_unlock(&dentry->d_lock);
 		break;
@@ -1299,6 +1305,8 @@ void d_move(struct dentry * dentry, stru
 		spin_lock(&target->d_lock);
 	}
 
+	audit_update_watch(dentry, AUDIT_UPDATE_REMOVE);
+
 	/* Move the dentry to the target hash queue, if on different bucket */
 	if (dentry->d_flags & DCACHE_UNHASHED)
 		goto already_unhashed;
@@ -1332,6 +1340,7 @@ already_unhashed:
 		list_add(&target->d_child, &target->d_parent->d_subdirs);
 	}
 
+	audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 	list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
 	spin_unlock(&target->d_lock);
 	spin_unlock(&dentry->d_lock);
fs/inode.c +5 -2
diff -Nurp audit-2.6.git/fs/inode.c audit-2.6.git~tc/fs/inode.c
@@ -21,6 +21,7 @@
 #include <linux/pagemap.h>
 #include <linux/cdev.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /*
  * This is needed for the following functions:
@@ -172,6 +173,7 @@ void destroy_inode(struct inode *inode) 
 {
 	if (inode_has_buffers(inode))
 		BUG();
+	audit_inode_free(inode);
 	security_inode_free(inode);
 	if (inode->i_sb->s_op->destroy_inode)
 		inode->i_sb->s_op->destroy_inode(inode);
@@ -257,7 +259,7 @@ void clear_inode(struct inode *inode)
 		bd_forget(inode);
 	if (inode->i_cdev)
 		cd_forget(inode);
-	inode->i_state = I_CLEAR;
+	inode->i_state = I_CLEAR | (inode->i_state & I_AUDIT);
 }
 
 EXPORT_SYMBOL(clear_inode);
@@ -1009,7 +1011,7 @@ void generic_delete_inode(struct inode *
 	hlist_del_init(&inode->i_hash);
 	spin_unlock(&inode_lock);
 	wake_up_inode(inode);
-	if (inode->i_state != I_CLEAR)
+	if ((inode->i_state & ~I_AUDIT) != I_CLEAR)
 		BUG();
 	destroy_inode(inode);
 }
@@ -1323,6 +1325,7 @@ void __init inode_init(unsigned long mem
 	inode_cachep = kmem_cache_create("inode_cache", sizeof(struct inode),
 				0, SLAB_RECLAIM_ACCOUNT|SLAB_PANIC, init_once, NULL);
 	set_shrinker(DEFAULT_SEEKS, shrink_icache_memory);
+	audit_filesystem_init();
 
 	/* Hash may have been set up in inode_init_early */
 	if (!hashdist)
fs/namei.c +29 -3
diff -Nurp audit-2.6.git/fs/namei.c audit-2.6.git~tc/fs/namei.c
@@ -179,6 +179,9 @@ int generic_permission(struct inode *ino
 		int (*check_acl)(struct inode *inode, int mask))
 {
 	umode_t			mode = inode->i_mode;
+	
+	if (audit_notify_watch(inode, mask))
+		return -ENOMEM;
 
 	if (current->fsuid == inode->i_uid)
 		mode >>= 6;
@@ -358,6 +361,9 @@ static inline int exec_permission_lite(s
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
+	if (audit_notify_watch(inode, MAY_EXEC))
+		return -ENOMEM;
+
 	if (current->fsuid == inode->i_uid)
 		mode >>= 6;
 	else if (in_group_p(inode->i_gid))
@@ -1172,6 +1178,10 @@ static inline int may_delete(struct inod
 
 	BUG_ON(victim->d_parent->d_inode != dir);
 
+	error = audit_notify_watch(victim->d_inode, MAY_WRITE);
+	if (error)
+		return error;
+
 	error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
 	if (error)
 		return error;
@@ -1295,6 +1305,8 @@ int vfs_create(struct inode *dir, struct
 		return error;
 	DQUOT_INIT(dir);
 	error = dir->i_op->create(dir, dentry, mode, nd);
+	if (!error)
+		error =	audit_notify_watch(dentry->d_inode, MAY_WRITE);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_create(dir, dentry, mode);
@@ -1601,6 +1613,8 @@ int vfs_mknod(struct inode *dir, struct 
 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mknod(dir, dentry, mode, dev);
+	if (!error)
+		error = audit_notify_watch(dentry->d_inode, MAY_WRITE);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mknod(dir, dentry, mode, dev);
@@ -1674,6 +1688,8 @@ int vfs_mkdir(struct inode *dir, struct 
 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mkdir(dir, dentry, mode);
+	if (!error)
+		error = audit_notify_watch(dentry->d_inode, MAY_WRITE);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mkdir(dir,dentry, mode);
@@ -1914,6 +1930,8 @@ int vfs_symlink(struct inode *dir, struc
 
 	DQUOT_INIT(dir);
 	error = dir->i_op->symlink(dir, dentry, oldname);
+	if (!error)
+		error = audit_notify_watch(dentry->d_inode, MAY_WRITE);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_symlink(dir, dentry, oldname);
@@ -1987,6 +2005,8 @@ int vfs_link(struct dentry *old_dentry, 
 	DQUOT_INIT(dir);
 	error = dir->i_op->link(old_dentry, dir, new_dentry);
 	up(&old_dentry->d_inode->i_sem);
+	if (!error)
+		error = audit_notify_watch(new_dentry->d_inode, MAY_WRITE);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_link(old_dentry, dir, new_dentry);
@@ -2109,11 +2129,13 @@ static int vfs_rename_dir(struct inode *
 			d_rehash(new_dentry);
 		dput(new_dentry);
 	}
-	if (!error) {
+	if (!error)
 		d_move(old_dentry,new_dentry);
+
+	error = audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
+	if (!error)
 		security_inode_post_rename(old_dir, old_dentry,
 					   new_dir, new_dentry);
-	}
 	return error;
 }
 
@@ -2139,8 +2161,12 @@ static int vfs_rename_other(struct inode
 		/* The following d_move() should become unconditional */
 		if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME))
 			d_move(old_dentry, new_dentry);
-		security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry);
 	}
+
+	error = audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
+	if (!error)
+		security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry);
+
 	if (target)
 		up(&target->i_sem);
 	dput(new_dentry);
include/linux/audit.h +74 -0

114 lines not on this page · raw message

include/linux/fs.h +2 -0
diff -Nurp audit-2.6.git/include/linux/fs.h audit-2.6.git~tc/include/linux/fs.h
@@ -225,6 +225,7 @@ struct poll_table_struct;
 struct kstatfs;
 struct vm_area_struct;
 struct vfsmount;
+struct audit_inode_data;
 
 /* Used to be a macro which just called the function, now just a function */
 extern void update_atime (struct inode *);
@@ -1024,6 +1025,7 @@ struct super_operations {
 #define I_FREEING		16
 #define I_CLEAR			32
 #define I_NEW			64
+#define I_AUDIT			128
 
 #define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
 
init/Kconfig +12 -0
diff -Nurp audit-2.6.git/init/Kconfig audit-2.6.git~tc/init/Kconfig
@@ -166,6 +166,7 @@ config AUDIT
 	bool "Auditing support"
 	depends on NET
 	default y if SECURITY_SELINUX
+	default n
 	help
 	  Enable auditing infrastructure that can be used with another
 	  kernel subsystem, such as SELinux (which requires this for
@@ -176,11 +177,22 @@ config AUDITSYSCALL
 	bool "Enable system-call auditing support"
 	depends on AUDIT && (X86 || PPC || PPC64 || ARCH_S390 || IA64 || UML)
 	default y if SECURITY_SELINUX
+	default n
 	help
 	  Enable low-overhead system-call auditing infrastructure that
 	  can be used independently or with another kernel subsystem,
 	  such as SELinux.
 
+config AUDITFILESYSTEM
+	bool "Enable file system auditing support"
+	depends on AUDITSYSCALL
+	default n
+	help
+	  Enable file system auditing for regular files and directories.
+	  When a targeted file or directory is accessed, an audit record
+	  is generated describing the inode accessed, how it was accessed,
+	  and by whom (ie: pid and system call).
+
 config HOTPLUG
 	bool "Support for hot-pluggable devices" if !ARCH_S390
 	default ARCH_S390
kernel/Makefile +1 -0
diff -Nurp audit-2.6.git/kernel/Makefile audit-2.6.git~tc/kernel/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
 obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
 obj-$(CONFIG_AUDIT) += audit.o
 obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_SYSFS) += ksysfs.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
kernel/audit.c +14 -0
diff -Nurp audit-2.6.git/kernel/audit.c audit-2.6.git~tc/kernel/audit.c
@@ -350,6 +350,9 @@ static int audit_netlink_ok(kernel_cap_t
 	case AUDIT_SET:
 	case AUDIT_ADD:
 	case AUDIT_DEL:
+	case AUDIT_WATCH_LIST:
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
 	case AUDIT_SIGNAL_INFO:
 		if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
 			err = -EPERM;
@@ -454,6 +457,17 @@ static int audit_receive_msg(struct sk_b
 		err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
 					   uid, seq, data, loginuid);
 		break;
+	case AUDIT_WATCH_LIST:
+		err = audit_list_watches(pid, seq);
+		break;
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		if (nlh->nlmsg_len < sizeof(struct watch_transport))
+			return -EINVAL;
+		err = audit_receive_watch(nlh->nlmsg_type,
+					  NETLINK_CB(skb).pid,
+					  uid, seq, data, loginuid);
+		break;
 	case AUDIT_SIGNAL_INFO:
 		sig_data.uid = audit_sig_uid;
 		sig_data.pid = audit_sig_pid;
kernel/auditfs.c +852 -0

852 lines not on this page · raw message

kernel/auditsc.c +106 -3

160 lines not on this page · raw message

security/selinux/nlmsgtab.c +3 -0
diff -Nurp audit-2.6.git/security/selinux/nlmsgtab.c audit-2.6.git~tc/security/selinux/nlmsgtab.c
@@ -98,6 +98,9 @@ static struct nlmsg_perm nlmsg_audit_per
 	{ AUDIT_DEL,		NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
 	{ AUDIT_USER,		NETLINK_AUDIT_SOCKET__NLMSG_RELAY    },
 	{ AUDIT_SIGNAL_INFO,	NETLINK_AUDIT_SOCKET__NLMSG_READ     },
+	{ AUDIT_WATCH_INS,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
+	{ AUDIT_WATCH_REM,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
+	{ AUDIT_WATCH_LIST,	NETLINK_AUDIT_SOCKET__NLMSG_READPRIV },
 };
 
 
#1 lore raw

most of this looks like a copy & paste of the inotify code. Could you please arrange with the inotify folks to merge the patches?

#5 lore raw
In reply to Timothy R. Chavez

Perhaps you could describe any specific reasons inotify isn't sufficient, as it's similar in many ways.

Also, could drop some of that extra debugging code (heh, like that MKDEV in audit_inode_free ;-)

Anyway, some quick comments below...

Quoted 117 lines

diff -Nurp audit-2.6.git/fs/dcache.c audit-2.6.git~tc/fs/dcache.c

fs/dcache.c +9 -0
@@ -32,6 +32,7 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /* #define DCACHE_DEBUG 1 */
 
@@ -97,6 +98,7 @@ static inline void dentry_iput(struct de
 {
 	struct inode *inode = dentry->d_inode;
 	if (inode) {
+		audit_update_watch(dentry, AUDIT_UPDATE_REMOVE);
 		dentry->d_inode = NULL;
 		list_del_init(&dentry->d_alias);
 		spin_unlock(&dentry->d_lock);
@@ -802,6 +804,7 @@ void d_instantiate(struct dentry *entry,
 	if (inode)
 		list_add(&entry->d_alias, &inode->i_dentry);
 	entry->d_inode = inode;
+	audit_update_watch(entry, AUDIT_UPDATE_NORMAL);
 	spin_unlock(&dcache_lock);
 	security_d_instantiate(entry, inode);
 }
@@ -978,6 +981,7 @@ struct dentry *d_splice_alias(struct ino
 		new = __d_find_alias(inode, 1);
 		if (new) {
 			BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
+			audit_update_watch(new, AUDIT_UPDATE_NORMAL);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(new, inode);
 			d_rehash(dentry);
@@ -987,6 +991,7 @@ struct dentry *d_splice_alias(struct ino
 			/* d_instantiate takes dcache_lock, so we do it by hand */
 			list_add(&dentry->d_alias, &inode->i_dentry);
 			dentry->d_inode = inode;
+			audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
@@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentry
 		if (!d_unhashed(dentry)) {
 			atomic_inc(&dentry->d_count);
 			found = dentry;
+			audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 		}
 		spin_unlock(&dentry->d_lock);
 		break;
@@ -1299,6 +1305,8 @@ void d_move(struct dentry * dentry, stru
 		spin_lock(&target->d_lock);
 	}
 
+	audit_update_watch(dentry, AUDIT_UPDATE_REMOVE);
+
 	/* Move the dentry to the target hash queue, if on different bucket */
 	if (dentry->d_flags & DCACHE_UNHASHED)
 		goto already_unhashed;
@@ -1332,6 +1340,7 @@ already_unhashed:
 		list_add(&target->d_child, &target->d_parent->d_subdirs);
 	}
 
+	audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 	list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
 	spin_unlock(&target->d_lock);
 	spin_unlock(&dentry->d_lock);

diff -Nurp audit-2.6.git/fs/inode.c audit-2.6.git~tc/fs/inode.c

fs/inode.c +5 -2
@@ -21,6 +21,7 @@
 #include <linux/pagemap.h>
 #include <linux/cdev.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /*
  * This is needed for the following functions:
@@ -172,6 +173,7 @@ void destroy_inode(struct inode *inode) 
 {
 	if (inode_has_buffers(inode))
 		BUG();
+	audit_inode_free(inode);
 	security_inode_free(inode);
 	if (inode->i_sb->s_op->destroy_inode)
 		inode->i_sb->s_op->destroy_inode(inode);
@@ -257,7 +259,7 @@ void clear_inode(struct inode *inode)
 		bd_forget(inode);
 	if (inode->i_cdev)
 		cd_forget(inode);
-	inode->i_state = I_CLEAR;
+	inode->i_state = I_CLEAR | (inode->i_state & I_AUDIT);
 }
 
 EXPORT_SYMBOL(clear_inode);
@@ -1009,7 +1011,7 @@ void generic_delete_inode(struct inode *
 	hlist_del_init(&inode->i_hash);
 	spin_unlock(&inode_lock);
 	wake_up_inode(inode);
-	if (inode->i_state != I_CLEAR)
+	if ((inode->i_state & ~I_AUDIT) != I_CLEAR)
 		BUG();
 	destroy_inode(inode);
 }
@@ -1323,6 +1325,7 @@ void __init inode_init(unsigned long mem
 	inode_cachep = kmem_cache_create("inode_cache", sizeof(struct inode),
 				0, SLAB_RECLAIM_ACCOUNT|SLAB_PANIC, init_once, NULL);
 	set_shrinker(DEFAULT_SEEKS, shrink_icache_memory);
+	audit_filesystem_init();
 
 	/* Hash may have been set up in inode_init_early */
 	if (!hashdist)

diff -Nurp audit-2.6.git/fs/namei.c audit-2.6.git~tc/fs/namei.c

fs/namei.c int generic_permission(struct inode *ino · line 184

Quoted 6 lines
fs/namei.c +3 -0
@@ -179,6 +179,9 @@ int generic_permission(struct inode *ino
 		int (*check_acl)(struct inode *inode, int mask))
 {
 	umode_t			mode = inode->i_mode;
+	
+	if (audit_notify_watch(inode, mask))
+		return -ENOMEM;

Why here? Filesystems that don't use generic_permission() won't get watched.

Quoted 13 lines
fs/namei.c +3 -0
 	if (current->fsuid == inode->i_uid)
 		mode >>= 6;
@@ -358,6 +361,9 @@ static inline int exec_permission_lite(s
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
+	if (audit_notify_watch(inode, MAY_EXEC))
+		return -ENOMEM;
+
 	if (current->fsuid == inode->i_uid)
 		mode >>= 6;
 	else if (in_group_p(inode->i_gid))

fs/namei.c static inline int may_delete(struct inod · line 1183

Quoted 6 lines
fs/namei.c +3 -0
@@ -1172,6 +1178,10 @@ static inline int may_delete(struct inod
 
 	BUG_ON(victim->d_parent->d_inode != dir);
 
+	error = audit_notify_watch(victim->d_inode, MAY_WRITE);
+	if (error)
+		return error;

This looks like you'd potentially get two audit records, one for file and one for parent dir (the second coming from normal permission() call below). Intentional?

fs/namei.c int vfs_create(struct inode *dir, struct · line 1309

Quoted 10 lines
fs/namei.c +3 -0
+
 	error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
 	if (error)
 		return error;
@@ -1295,6 +1305,8 @@ int vfs_create(struct inode *dir, struct
 		return error;
 	DQUOT_INIT(dir);
 	error = dir->i_op->create(dir, dentry, mode, nd);
+	if (!error)
+		error =	audit_notify_watch(dentry->d_inode, MAY_WRITE);

This doesn't look right. The thing was created, but just because audit_notify_watch() failed, dnotify and security modules are never informed.

fs/namei.c int vfs_mknod(struct inode *dir, struct · line 1617

Quoted 9 lines
fs/namei.c +2 -0
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_create(dir, dentry, mode);
@@ -1601,6 +1613,8 @@ int vfs_mknod(struct inode *dir, struct 
 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mknod(dir, dentry, mode, dev);
+	if (!error)
+		error = audit_notify_watch(dentry->d_inode, MAY_WRITE);

same here.

fs/namei.c int vfs_mkdir(struct inode *dir, struct · line 1692

Quoted 9 lines
fs/namei.c +2 -0
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mknod(dir, dentry, mode, dev);
@@ -1674,6 +1688,8 @@ int vfs_mkdir(struct inode *dir, struct 
 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mkdir(dir, dentry, mode);
+	if (!error)
+		error = audit_notify_watch(dentry->d_inode, MAY_WRITE);

and here

fs/namei.c int vfs_symlink(struct inode *dir, struc · line 1934

Quoted 9 lines
fs/namei.c +2 -0
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mkdir(dir,dentry, mode);
@@ -1914,6 +1930,8 @@ int vfs_symlink(struct inode *dir, struc
 
 	DQUOT_INIT(dir);
 	error = dir->i_op->symlink(dir, dentry, oldname);
+	if (!error)
+		error = audit_notify_watch(dentry->d_inode, MAY_WRITE);

and the rest of these...

Quoted 55 lines
fs/namei.c +11 -3
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_symlink(dir, dentry, oldname);
@@ -1987,6 +2005,8 @@ int vfs_link(struct dentry *old_dentry, 
 	DQUOT_INIT(dir);
 	error = dir->i_op->link(old_dentry, dir, new_dentry);
 	up(&old_dentry->d_inode->i_sem);
+	if (!error)
+		error = audit_notify_watch(new_dentry->d_inode, MAY_WRITE);
 	if (!error) {
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_link(old_dentry, dir, new_dentry);
@@ -2109,11 +2129,13 @@ static int vfs_rename_dir(struct inode *
 			d_rehash(new_dentry);
 		dput(new_dentry);
 	}
-	if (!error) {
+	if (!error)
 		d_move(old_dentry,new_dentry);
+
+	error = audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
+	if (!error)
 		security_inode_post_rename(old_dir, old_dentry,
 					   new_dir, new_dentry);
-	}
 	return error;
 }
 
@@ -2139,8 +2161,12 @@ static int vfs_rename_other(struct inode
 		/* The following d_move() should become unconditional */
 		if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME))
 			d_move(old_dentry, new_dentry);
-		security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry);
 	}
+
+	error = audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
+	if (!error)
+		security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry);
+
 	if (target)
 		up(&target->i_sem);
 	dput(new_dentry);

diff -Nurp audit-2.6.git/init/Kconfig audit-2.6.git~tc/init/Kconfig

init/Kconfig +1 -0
@@ -166,6 +166,7 @@ config AUDIT
 	bool "Auditing support"
 	depends on NET
 	default y if SECURITY_SELINUX
+	default n
 	help
 	  Enable auditing infrastructure that can be used with another
 	  kernel subsystem, such as SELinux (which requires this for

init/Kconfig config AUDITSYSCALL · line 180

Quoted 4 lines
init/Kconfig +1 -0
@@ -176,11 +177,22 @@ config AUDITSYSCALL
 	bool "Enable system-call auditing support"
 	depends on AUDIT && (X86 || PPC || PPC64 || ARCH_S390 || IA64 || UML)
 	default y if SECURITY_SELINUX
+	default n

Why these default changes?

Quoted 21 lines
init/Kconfig +10 -0
 	help
 	  Enable low-overhead system-call auditing infrastructure that
 	  can be used independently or with another kernel subsystem,
 	  such as SELinux.
 
+config AUDITFILESYSTEM
+	bool "Enable file system auditing support"
+	depends on AUDITSYSCALL
+	default n
+	help
+	  Enable file system auditing for regular files and directories.
+	  When a targeted file or directory is accessed, an audit record
+	  is generated describing the inode accessed, how it was accessed,
+	  and by whom (ie: pid and system call).
+
 config HOTPLUG
 	bool "Support for hot-pluggable devices" if !ARCH_S390
 	default ARCH_S390

diff -Nurp audit-2.6.git/kernel/auditfs.c audit-2.6.git~tc/kernel/auditfs.c

kernel/auditfs.c line 224

Quoted 2 lines
kernel/auditfs.c +2 -0
+/* Convert a watch_transport structure into a kernel audit_watch structure. */
+static inline struct audit_watch *audit_to_watch(void *memblk)

This should not be untyped.

Quoted 18 lines
kernel/auditfs.c +18 -0
+{
+	unsigned int offset;
+	struct watch_transport *t;
+	struct audit_watch *watch;
+
+	watch = audit_watch_alloc();
+	if (!watch)
+		goto audit_to_watch_exit;
+
+	t = memblk;
+
+	watch->w_perms = t->perms;
+
+	offset = sizeof(struct watch_transport);
+	watch->w_filterkey = kmalloc(t->fklen+1, GFP_KERNEL);
+	if (!watch->w_filterkey)
+		goto audit_to_watch_fail;
+	watch->w_filterkey[t->fklen] = 0;

kernel/auditfs.c line 252

Quoted 10 lines
kernel/auditfs.c +10 -0
+	memcpy(watch->w_filterkey, memblk + offset, t->fklen);
+
+	offset += t->fklen;
+	watch->w_path = kmalloc(t->pathlen+1, GFP_KERNEL);
+	if (!watch->w_path)
+		goto audit_to_watch_fail;
+	watch->w_path[t->pathlen] = 0;
+	memcpy(watch->w_path, memblk + offset, t->pathlen);
+
+	goto audit_to_watch_exit;

typically label and return would just go here.

audit_to_watch_exit:

	return watch;
Quoted 12 lines
kernel/auditfs.c +8 -0
+
+audit_to_watch_fail:
+	audit_watch_free(watch);
+	watch = NULL;
+audit_to_watch_exit:
+	return watch;
+}
+

diff -Nurp audit-2.6.git/kernel/auditsc.c audit-2.6.git~tc/kernel/auditsc.c

kernel/auditsc.c struct audit_aux_data { · line 106

Quoted 5 lines
kernel/auditsc.c +0 -2
@@ -102,8 +102,6 @@ struct audit_aux_data {
 	int			type;
 };
 
-#define AUDIT_AUX_IPCPERM	0
-

This looks unrelated (albeit unused)

Quoted 22 lines
kernel/auditsc.c +11 -0
 struct audit_aux_data_ipcctl {
 	struct audit_aux_data	d;
 	struct ipc_perm		p;
@@ -131,6 +129,17 @@ struct audit_aux_data_path {
 	struct vfsmount		*mnt;
 };
 
+struct audit_aux_data_watched {
+	struct audit_aux_data	link;
+	struct hlist_head	watches;
+	unsigned long		ino;
+	int			mask;
+	uid_t			uid;
+	gid_t			gid;
+	dev_t			dev;
+	dev_t			rdev;
+};
+
 /* The per-task audit context. */
 struct audit_context {
 	int		    in_syscall;	/* 1 if task is in a syscall */

kernel/auditsc.c static inline int audit_del_rule(struct · line 260

Quoted 4 lines
kernel/auditsc.c +1 -0
@@ -248,6 +257,7 @@ static inline int audit_del_rule(struct 
 	return -EFAULT;		/* No matching rule */
 }
 
+#ifdef CONFIG_NET

We've dropped these #ifdefs, we rely on netlink and skb's.

Quoted 50 lines
kernel/auditsc.c +41 -0
 /* Copy rule from user-space to kernel-space.  Called during
  * AUDIT_ADD. */
 static int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
@@ -328,6 +338,7 @@ int audit_receive_filter(int type, int p
 
 	return err;
 }
+#endif
 
 
+#ifdef CONFIG_AUDITFILESYSTEM
+/* This has to be here instead of in auditfs.c, because it needs to
+   see the audit context */
+int auditfs_attach_wdata(struct inode *inode, struct hlist_head *watches,
+			 int mask)
+{
+	struct audit_context *context = current->audit_context;
+	struct audit_aux_data_watched *ax;
+	struct audit_watch *watch;
+	struct audit_watch_info *this, *winfo;
+	struct hlist_node *pos, *tmp;
+
+	ax = kmalloc(sizeof(*ax), GFP_KERNEL);
+	if (!ax)
+		return -ENOMEM;
+
+	if (context->in_syscall && !context->auditable)
+		context->auditable = 1;
+
+	INIT_HLIST_HEAD(&ax->watches);
+
+	hlist_for_each_entry(watch, pos, watches, w_watched) {
+		winfo = kmalloc(sizeof(struct audit_watch_info), GFP_KERNEL);
+		if (!winfo)
+			goto auditfs_attach_wdata_fail;
+		if (mask && (watch->w_perms && !(watch->w_perms&mask)))
+			continue;
+		winfo->watch = audit_watch_get(watch);
+		hlist_add_head(&winfo->node, &ax->watches);
+	}
+
+	ax->mask = mask;
+	ax->ino = inode->i_ino;
+	ax->uid = inode->i_uid;
+	ax->gid = inode->i_gid;
+	ax->dev = inode->i_sb->s_dev;
+	ax->rdev = inode->i_rdev;
+
+	ax->link.type = AUDIT_FS_INODE;
+	ax->link.next = context->aux;

kernel/auditsc.c int audit_receive_filter(int type, int p · line 1265

Quoted 10 lines
kernel/auditsc.c +10 -0
+	context->aux = (void *)ax;
+
+	return 0;
+
+auditfs_attach_wdata_fail:
+	hlist_for_each_entry_safe(this, pos, tmp, &ax->watches, node) {
+		hlist_del(&this->node);
+		audit_watch_put(this->watch);
+		kfree(this);
+	}

looks like it leaks 'ax' in error path

Quoted 2 lines
kernel/auditsc.c +2 -0
+	return -ENOMEM;
+}
#2 lore raw
In reply to Christoph Hellwig
Quoted 3 lines · Christoph Hellwig

most of this looks like a copy & paste of the inotify code. Could you please arrange with the inotify folks to merge the patches?

Christoph,

Thank you for the response.

When we first started this project we had assessed whether or not inotify would be a feasible starting point, but ultimately rejected it. What it came down to was that the objectives of inotify were distinctly different from the objectives of auditfs. These distinctions were rooted in the very design of inotify. Why? Inotify is not a generic fs events notification subsystem.

Some of the cursory problems with using inotify which would need changing were:

1) Coverage

Inotify is only interested in a portion of the fs ops that we are interested in. Most notably, we need the ability to generate records every time the [exec_]permission[_lite]() function is called.

2) Processing

The processing of events has to occur in the kernel, not in user space. Inotify collects events and tosses them back over the fence for processing. We cannot introduce any window of opportunity for audit subversion.

You see, auditfs is interested in the inode at location+name, whatever it may be when the auditable event occurs. Thus, we've had to hook dcache to help us with this processing. This is something Inotify does not care about nor should it.

Inotify will resolve a path to its inode and watch _that_ inode, we're saying "watch any inode at this path"

To build auditfs on top of inotify would require watching directories for events on its children and processing the events in the kernel (and not simply tossing them over the fence to user space). This gets very... hackish ;)

3) Queue

Depending on the work, there may be a requirement that no audit record can be lost. The message queue structure of inotify has the potential to overflow and drop events. This is a no-go for us, but perfectly reasonable for inotify.

To make this work for us we'd have to hook the queueing functions to forward events on to the audit subsystem. This would basically mean fitting a system that has been designed for interaction with userspace to also have interaction with other parts of the kernel... again, hackish.

4) Context

We require information regarding the process and system call associated with the fs event. This is not necessarily evident from where the event occurred (ie: permission()). Inotify would have to hook into the audit subsystem to get at this information.

The only place that the two projects converge is in fs/namei.c where they both collect information about events. One project notifies the audit subsystem and the other notifies user space. I think it is here where a collaboration could take place without skewing the objectives of either project.

-tim

#3 lore raw
Quoted 5 lines · Timothy R. Chavez

1) Coverage

Inotify is only interested in a portion of the fs ops that we are interested in. Most notably, we need the ability to generate records every time the [exec_]permission[_lite]() function is called.

That still means you could use the same infrastructure and now forward these events to inotify-ish clients, right?

Quoted 5 lines

2) Processing

The processing of events has to occur in the kernel, not in user space. Inotify collects events and tosses them back over the fence for processing. We cannot introduce any window of opportunity for audit subversion.

still means it could be a client to the same core event processing code and filesystem hooks, right?

Quoted 5 lines

You see, auditfs is interested in the inode at location+name, whatever it may be when the auditable event occurs. Thus, we've had to hook dcache to help us with this processing. This is something Inotify does not care about nor should it.

Nor should the auditing code. There's no way to find a good name for an inode _at all_, and only the last pathname component for a dentry. If you try anything else your code is broken and will not get anywhere the mainline kernel.

Quoted 11 lines

3) Queue

Depending on the work, there may be a requirement that no audit record can be lost. The message queue structure of inotify has the potential to overflow and drop events. This is a no-go for us, but perfectly reasonable for inotify.

To make this work for us we'd have to hook the queueing functions to forward events on to the audit subsystem. This would basically mean fitting a system that has been designed for interaction with userspace to also have interaction with other parts of the kernel... again, hackish.

Or build inotify ontop of your system. It's not like inotify is perfect as-is (it's not, the userland interface is extremly horrible)

#4 lore raw
Quoted 9 lines · Christoph Hellwig
Quoted 5 lines · Timothy R. Chavez

1) Coverage

Inotify is only interested in a portion of the fs ops that we are interested in. Most notably, we need the ability to generate records every time the [exec_]permission[_lite]() function is called.

That still means you could use the same infrastructure and now forward these events to inotify-ish clients, right?

I believe a common infrastructure is appropriate, yes. I think it should forward on events to whomever is interested, yes ;)

Quoted 9 lines
Quoted 6 lines

2) Processing

The processing of events has to occur in the kernel, not in user space. Inotify collects events and tosses them back over the fence for processing. We cannot introduce any window of opportunity for audit subversion.

still means it could be a client to the same core event processing code and filesystem hooks, right?

Yes, this too is workable. I do not believe, however, that this should be merged into inotify's logic though. I believe that auditfs and inotify would each be their own discreet client. I think a common set of filesystem hooks is appropriate, but I think processing of the events being channeled through those hooks should be left up to the recipient/client (like it already is).

Quoted 11 lines
Quoted 6 lines

You see, auditfs is interested in the inode at location+name, whatever it may be when the auditable event occurs. Thus, we've had to hook dcache to help us with this processing. This is something Inotify does not care about nor should it.

Nor should the auditing code. There's no way to find a good name for an inode _at all_, and only the last pathname component for a dentry. If you try anything else your code is broken and will not get anywhere the mainline kernel.

Sure it should :) The point is, you have a security relevant file like /etc/shadow at a well defined path and you want to audit it. If you rely solely on (inode,dev)- based filtering, you'll lose audit before your "passwd" completes its execution because the underlying inode associated with "shadow" would has changed. This is no-good.

There are other locations+names you'd also like to audit as well, such as the audit logs themselves, configuration files for the various trusted programs, etc.

Quoted 1 line

From an auditing stand point it makes perfect sense to audit a database as

such. Or at the very least, have the option to do so. Where auditing strictly on (inode,device) makes sense or meets the administrators goals, it is encouraged.

However, being able to have a complete and comprehensive record of a security relevant database is crucial, I think ;)

Quoted 8 lines
Quoted 8 lines

3) Queue

Depending on the work, there may be a requirement that no audit record can be lost. The message queue structure of inotify has the potential to overflow and drop events. This is a no-go for us, but perfectly reasonable for inotify.

Quoted 7 lines
Quoted 4 lines

To make this work for us we'd have to hook the queueing functions to forward events on to the audit subsystem. This would basically mean fitting a system that has been designed for interaction with userspace to also have interaction with other parts of the kernel... again, hackish.

Or build inotify ontop of your system. It's not like inotify is perfect as-is (it's not, the userland interface is extremly horrible)

I think inotify's has sufficiently distinct goals that both projects would benefit more from a common framework rather then a merger. I think Robert would agree.

I really do appreciate your comments. The main goal here is to give the mainline kernel a robust and useful audit subsystem.

-tim

#6 lore raw
In reply to Chris Wright
Quoted 2 lines · Chris Wright

Perhaps you could describe any specific reasons inotify isn't sufficient, as it's similar in many ways.

Hi Chris,

I replied to Cristoph with my attempt at explaining this to some degree. I believe that the objectives of both auditfs and inotify are actually quite different and should not be implemented in terms of one another or merged. However, they do share some commonality in that they are both interested in receiving event notification from the file system. In this light, I do believe that having some generic hooking framework that inotify, auditfs, and whatever else comes in the future can use to mitigate duplicating code and function while not compromising objective.

Quoted 3 lines

Also, could drop some of that extra debugging code (heh, like that MKDEV in audit_inode_free ;-)

Done.

Quoted 3 lines

Anyway, some quick comments below...

Thanks, this patch incorporates most of those comments.

-tim

fs/dcache.c +9 -0
diff -Nurp audit-2.6.git/fs/dcache.c audit-2.6.git~tc/fs/dcache.c
@@ -32,6 +32,7 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /* #define DCACHE_DEBUG 1 */
 
@@ -97,6 +98,7 @@ static inline void dentry_iput(struct de
 {
 	struct inode *inode = dentry->d_inode;
 	if (inode) {
+		audit_update_watch(dentry, AUDIT_UPDATE_REMOVE);
 		dentry->d_inode = NULL;
 		list_del_init(&dentry->d_alias);
 		spin_unlock(&dentry->d_lock);
@@ -802,6 +804,7 @@ void d_instantiate(struct dentry *entry,
 	if (inode)
 		list_add(&entry->d_alias, &inode->i_dentry);
 	entry->d_inode = inode;
+	audit_update_watch(entry, AUDIT_UPDATE_NORMAL);
 	spin_unlock(&dcache_lock);
 	security_d_instantiate(entry, inode);
 }
@@ -978,6 +981,7 @@ struct dentry *d_splice_alias(struct ino
 		new = __d_find_alias(inode, 1);
 		if (new) {
 			BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
+			audit_update_watch(new, AUDIT_UPDATE_NORMAL);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(new, inode);
 			d_rehash(dentry);
@@ -987,6 +991,7 @@ struct dentry *d_splice_alias(struct ino
 			/* d_instantiate takes dcache_lock, so we do it by hand */
 			list_add(&dentry->d_alias, &inode->i_dentry);
 			dentry->d_inode = inode;
+			audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
@@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentry
 		if (!d_unhashed(dentry)) {
 			atomic_inc(&dentry->d_count);
 			found = dentry;
+			audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 		}
 		spin_unlock(&dentry->d_lock);
 		break;
@@ -1299,6 +1305,8 @@ void d_move(struct dentry * dentry, stru
 		spin_lock(&target->d_lock);
 	}
 
+	audit_update_watch(dentry, AUDIT_UPDATE_REMOVE);
+
 	/* Move the dentry to the target hash queue, if on different bucket */
 	if (dentry->d_flags & DCACHE_UNHASHED)
 		goto already_unhashed;
@@ -1332,6 +1340,7 @@ already_unhashed:
 		list_add(&target->d_child, &target->d_parent->d_subdirs);
 	}
 
+	audit_update_watch(dentry, AUDIT_UPDATE_NORMAL);
 	list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
 	spin_unlock(&target->d_lock);
 	spin_unlock(&dentry->d_lock);
fs/inode.c +5 -2
diff -Nurp audit-2.6.git/fs/inode.c audit-2.6.git~tc/fs/inode.c
@@ -21,6 +21,7 @@
 #include <linux/pagemap.h>
 #include <linux/cdev.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /*
  * This is needed for the following functions:
@@ -172,6 +173,7 @@ void destroy_inode(struct inode *inode) 
 {
 	if (inode_has_buffers(inode))
 		BUG();
+	audit_inode_free(inode);
 	security_inode_free(inode);
 	if (inode->i_sb->s_op->destroy_inode)
 		inode->i_sb->s_op->destroy_inode(inode);
@@ -257,7 +259,7 @@ void clear_inode(struct inode *inode)
 		bd_forget(inode);
 	if (inode->i_cdev)
 		cd_forget(inode);
-	inode->i_state = I_CLEAR;
+	inode->i_state = I_CLEAR | (inode->i_state & I_AUDIT);
 }
 
 EXPORT_SYMBOL(clear_inode);
@@ -1009,7 +1011,7 @@ void generic_delete_inode(struct inode *
 	hlist_del_init(&inode->i_hash);
 	spin_unlock(&inode_lock);
 	wake_up_inode(inode);
-	if (inode->i_state != I_CLEAR)
+	if ((inode->i_state & ~I_AUDIT) != I_CLEAR)
 		BUG();
 	destroy_inode(inode);
 }
@@ -1323,6 +1325,7 @@ void __init inode_init(unsigned long mem
 	inode_cachep = kmem_cache_create("inode_cache", sizeof(struct inode),
 				0, SLAB_RECLAIM_ACCOUNT|SLAB_PANIC, init_once, NULL);
 	set_shrinker(DEFAULT_SEEKS, shrink_icache_memory);
+	audit_filesystem_init();
 
 	/* Hash may have been set up in inode_init_early */
 	if (!hashdist)
fs/namei.c +13 -0
diff -Nurp audit-2.6.git/fs/namei.c audit-2.6.git~tc/fs/namei.c
@@ -224,6 +224,8 @@ int generic_permission(struct inode *ino
 int permission(struct inode *inode, int mask, struct nameidata *nd)
 {
 	int retval, submask;
+	
+	audit_notify_watch(inode, mask);
 
 	if (mask & MAY_WRITE) {
 		umode_t mode = inode->i_mode;
@@ -358,6 +360,8 @@ static inline int exec_permission_lite(s
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
+	audit_notify_watch(inode, MAY_EXEC);
+
 	if (current->fsuid == inode->i_uid)
 		mode >>= 6;
 	else if (in_group_p(inode->i_gid))
@@ -1172,6 +1176,8 @@ static inline int may_delete(struct inod
 
 	BUG_ON(victim->d_parent->d_inode != dir);
 
+	audit_notify_watch(victim->d_inode, 0);
+
 	error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
 	if (error)
 		return error;
@@ -1296,6 +1302,7 @@ int vfs_create(struct inode *dir, struct
 	DQUOT_INIT(dir);
 	error = dir->i_op->create(dir, dentry, mode, nd);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_create(dir, dentry, mode);
 	}
@@ -1602,6 +1609,7 @@ int vfs_mknod(struct inode *dir, struct 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mknod(dir, dentry, mode, dev);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mknod(dir, dentry, mode, dev);
 	}
@@ -1675,6 +1683,7 @@ int vfs_mkdir(struct inode *dir, struct 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mkdir(dir, dentry, mode);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mkdir(dir,dentry, mode);
 	}
@@ -1915,6 +1924,7 @@ int vfs_symlink(struct inode *dir, struc
 	DQUOT_INIT(dir);
 	error = dir->i_op->symlink(dir, dentry, oldname);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_symlink(dir, dentry, oldname);
 	}
@@ -1988,6 +1998,7 @@ int vfs_link(struct dentry *old_dentry, 
 	error = dir->i_op->link(old_dentry, dir, new_dentry);
 	up(&old_dentry->d_inode->i_sem);
 	if (!error) {
+		audit_notify_watch(new_dentry->d_inode, MAY_WRITE);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_link(old_dentry, dir, new_dentry);
 	}
@@ -2111,6 +2122,7 @@ static int vfs_rename_dir(struct inode *
 	}
 	if (!error) {
 		d_move(old_dentry,new_dentry);
+		audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
 		security_inode_post_rename(old_dir, old_dentry,
 					   new_dir, new_dentry);
 	}
@@ -2139,6 +2151,7 @@ static int vfs_rename_other(struct inode
 		/* The following d_move() should become unconditional */
 		if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME))
 			d_move(old_dentry, new_dentry);
+		audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
 		security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry);
 	}
 	if (target)
include/linux/audit.h +74 -0

114 lines not on this page · raw message

include/linux/fs.h +2 -0
diff -Nurp audit-2.6.git/include/linux/fs.h audit-2.6.git~tc/include/linux/fs.h
@@ -225,6 +225,7 @@ struct poll_table_struct;
 struct kstatfs;
 struct vm_area_struct;
 struct vfsmount;
+struct audit_inode_data;
 
 /* Used to be a macro which just called the function, now just a function */
 extern void update_atime (struct inode *);
@@ -1024,6 +1025,7 @@ struct super_operations {
 #define I_FREEING		16
 #define I_CLEAR			32
 #define I_NEW			64
+#define I_AUDIT			128
 
 #define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
 
init/Kconfig +10 -0
diff -Nurp audit-2.6.git/init/Kconfig audit-2.6.git~tc/init/Kconfig
@@ -181,6 +181,16 @@ config AUDITSYSCALL
 	  can be used independently or with another kernel subsystem,
 	  such as SELinux.
 
+config AUDITFILESYSTEM
+	bool "Enable file system auditing support"
+	depends on AUDITSYSCALL
+	default n
+	help
+	  Enable file system auditing for regular files and directories.
+	  When a targeted file or directory is accessed, an audit record
+	  is generated describing the inode accessed, how it was accessed,
+	  and by whom (ie: pid and system call).
+
 config HOTPLUG
 	bool "Support for hot-pluggable devices" if !ARCH_S390
 	default ARCH_S390
kernel/Makefile +1 -0
diff -Nurp audit-2.6.git/kernel/Makefile audit-2.6.git~tc/kernel/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
 obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
 obj-$(CONFIG_AUDIT) += audit.o
 obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_SYSFS) += ksysfs.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
kernel/audit.c +14 -0
diff -Nurp audit-2.6.git/kernel/audit.c audit-2.6.git~tc/kernel/audit.c
@@ -350,6 +350,9 @@ static int audit_netlink_ok(kernel_cap_t
 	case AUDIT_SET:
 	case AUDIT_ADD:
 	case AUDIT_DEL:
+	case AUDIT_WATCH_LIST:
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
 	case AUDIT_SIGNAL_INFO:
 		if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
 			err = -EPERM;
@@ -454,6 +457,17 @@ static int audit_receive_msg(struct sk_b
 		err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
 					   uid, seq, data, loginuid);
 		break;
+	case AUDIT_WATCH_LIST:
+		err = audit_list_watches(pid, seq);
+		break;
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		if (nlh->nlmsg_len < sizeof(struct watch_transport))
+			return -EINVAL;
+		err = audit_receive_watch(nlh->nlmsg_type,
+					  NETLINK_CB(skb).pid,
+					  uid, seq, data, loginuid);
+		break;
 	case AUDIT_SIGNAL_INFO:
 		sig_data.uid = audit_sig_uid;
 		sig_data.pid = audit_sig_pid;
kernel/auditfs.c +844 -0

844 lines not on this page · raw message

kernel/auditsc.c +105 -1

139 lines not on this page · raw message

security/selinux/nlmsgtab.c +3 -0
diff -Nurp audit-2.6.git/security/selinux/nlmsgtab.c audit-2.6.git~tc/security/selinux/nlmsgtab.c
@@ -98,6 +98,9 @@ static struct nlmsg_perm nlmsg_audit_per
 	{ AUDIT_DEL,		NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
 	{ AUDIT_USER,		NETLINK_AUDIT_SOCKET__NLMSG_RELAY    },
 	{ AUDIT_SIGNAL_INFO,	NETLINK_AUDIT_SOCKET__NLMSG_READ     },
+	{ AUDIT_WATCH_INS,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
+	{ AUDIT_WATCH_REM,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
+	{ AUDIT_WATCH_LIST,	NETLINK_AUDIT_SOCKET__NLMSG_READPRIV },
 };