(#2) file system auditing
Subsystems (3)
RFC PATCH 1/1 (#2) file system auditing
Hello,
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 on the (device, inode)-based filters to ensure persistence of auditing across transactions. This patch adds the necessary functionality to the audit subsystem and VFS to support file system auditing in which an object is audited based on its location and name. This work is being done to make the audit subsystem compliant with Common Criteria's Controlled Access Protection Profile (CAPP) specification.
This is the second (#2) RFC on linux-fsdevel.
I've made some updates to the file system auditing code. The file system hooks remain unchanged, but I would still appreciate feedback. Since the audit patchset was temporarily removed from the mm tree, the patch, which appears at the bottom of this message, was diffed against linux-2.6.12-rc2-mm1 again.
The most notable updates/changes are as follows:
1.
Updated handling of watch data between user and kernel. Introduced a new struct, audit_transport, which is used as a common means by the kernel and user to send audit_watch information via netlink to each other.
2.
A master watchlist was added. I've added a global linked list that keeps tabs on all the watches in the file system. This list is currently used to send back to the user space a list of all the watches. The path used to add the watch and the device the watch was added on are now stored with the watch. Before sending the watch back to user space the path is walked and checked for the watch. If the path can be walked from this [view of the] file system and the watch is found, the watch is returned as 'valid' otherwise it is returned as 'invalid'.
3.
A bug was fixed by which one could create a hardlink to a file, watch the file, and access the hardlink first, subverting the audit subsystem. Now, when a watch is inserted, the inode is updated ASAP.
-tim
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_attach_watch (dentry,1 ); 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_attach_watch (entry,0 );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_attach_watch (new,0 );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_attach_watch (dentry,0 );spin_unlock (&dcache_lock);security_d_instantiate (dentry, inode);d_rehash (dentry);
@@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentryif (!d_unhashed (dentry)) {atomic_inc (&dentry->d_count); found = dentry; +audit_attach_watch (found,0 ); }spin_unlock (&dentry->d_lock);break ;
@@ -1299,6 +1305,8 @@ void d_move(struct dentry * dentry, struspin_lock (&target->d_lock); } +audit_attach_watch (dentry,1 ); +/* 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_attach_watch (dentry,0 );list_add (&dentry->d_child, &dentry->d_parent->d_subdirs);spin_unlock (&target->d_lock);spin_unlock (&dentry->d_lock);
fs/inode.c +5 -1
@@ -22,6 +22,7 @@#include <linux/cdev.h> #include <linux/bootmem.h> #include <linux/inotify.h> +#include <linux/audit.h> /* * This is needed for the following functions:
@@ -140,9 +141,11 @@ static struct inode *alloc_inode(struct inode->i_bdev =NULL ; inode->i_cdev =NULL ; inode->i_rdev =0 ; + inode->i_audit =NULL ; inode->i_security =NULL ; inode->dirtied_when =0 ; -if (security_inode_alloc (inode)) { +if (audit_inode_alloc (inode) ||security_inode_alloc (inode)) { +audit_inode_free (inode);if (inode->i_sb->s_op->destroy_inode) inode->i_sb->s_op->destroy_inode (inode);else
@@ -180,6 +183,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);
fs/namei.c +13 -0
@@ -225,6 +225,8 @@ int permission(struct inode *inode, int {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(sif (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 inodBUG_ON (victim->d_parent->d_inode != dir); +audit_notify_watch (victim->d_inode,MAY_WRITE ); + error =permission (dir,MAY_WRITE |MAY_EXEC ,NULL );if (error)return error;
@@ -1296,6 +1302,7 @@ int vfs_create(struct inode *dir, structDQUOT_INIT (dir); error = dir->i_op->create (dir, dentry, mode, nd);if (!error) { +audit_notify_watch (dentry->d_inode,MAY_WRITE );fsnotify_create (dir, dentry->d_name.name);security_inode_post_create (dir, dentry, mode); }
@@ -1601,6 +1608,7 @@ int vfs_mknod(struct inode *dir, structDQUOT_INIT (dir); error = dir->i_op->mknod (dir, dentry, mode, dev);if (!error) { +audit_notify_watch (dentry->d_inode,MAY_WRITE );fsnotify_create (dir, dentry->d_name.name);security_inode_post_mknod (dir, dentry, mode, dev); }
@@ -1674,6 +1682,7 @@ int vfs_mkdir(struct inode *dir, structDQUOT_INIT (dir); error = dir->i_op->mkdir (dir, dentry, mode);if (!error) { +audit_notify_watch (dentry->d_inode,MAY_WRITE );fsnotify_mkdir (dir, dentry->d_name.name);security_inode_post_mkdir (dir,dentry, mode); }
@@ -1915,6 +1924,7 @@ int vfs_symlink(struct inode *dir, strucDQUOT_INIT (dir); error = dir->i_op->symlink (dir, dentry, oldname);if (!error) { +audit_notify_watch (dentry->d_inode,MAY_WRITE );fsnotify_create (dir, dentry->d_name.name);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 );fsnotify_create (dir, new_dentry->d_name.name);security_inode_post_link (old_dentry, dir, new_dentry); }
@@ -2111,6 +2122,7 @@ int vfs_rename_dir(struct inode *old_dir }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 @@ int vfs_rename_other(struct inode *old_d/* 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 +77 -8
125 lines not on this page · raw message
include/linux/fs.h +2 -0
@@ -214,6 +214,7 @@ extern int dir_notify_enable;#include <linux/prio_tree.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/audit.h> #include <asm/atomic.h> #include <asm/semaphore.h>
@@ -477,6 +478,7 @@ struct inode {struct semaphore inotify_sem;/* protects the watches list */ #endif +struct audit_data *i_audit;unsigned long i_state;unsigned long dirtied_when;/* jiffies of first dirtying */
init/Kconfig +10 -0
@@ -198,6 +198,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
@@ -25,6 +25,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_DETECT_SOFTLOCKUP) += softlockup.o
kernel/audit.c +14 -4
@@ -322,6 +322,9 @@ static int audit_netlink_ok(kernel_cap_tcase AUDIT_SET :case AUDIT_ADD :case AUDIT_DEL : +case AUDIT_WATCH_INS : +case AUDIT_WATCH_REM : +case AUDIT_WATCH_LIST :if (!cap_raised (eff_cap,CAP_AUDIT_CONTROL )) err = -EPERM ;break ;
@@ -409,12 +412,18 @@ static int audit_receive_msg(struct sk_breturn -EINVAL ;/* fallthrough */ case AUDIT_LIST : -#ifdef CONFIG_AUDITSYSCALL err =audit_receive_filter (nlh->nlmsg_type,NETLINK_CB (skb).pid, uid, seq, data); -#else - err = -EOPNOTSUPP ; -#endif +case AUDIT_WATCH_LIST : + err =audit_list_watches (NETLINK_CB (skb).pid, seq); +break ; +case AUDIT_WATCH_INS : +case AUDIT_WATCH_REM : +if (nlh->nlmsg_len <sizeof (struct audit_transport )) +return -EINVAL ; + err =audit_receive_watch (nlh->nlmsg_type, +NETLINK_CB (skb).pid, + uid, seq, data);break ;default : err = -EINVAL ;
@@ -560,6 +569,7 @@ static int __init audit_init(void) audit_initialized =1 ; audit_enabled = audit_default; +audit_filesystem_init ();audit_log (NULL ,"initialized" );return 0 ; }
kernel/auditfs.c +560 -0
560 lines not on this page · raw message
kernel/auditsc.c +78 -0
99 lines not on this page · raw message
security/selinux/nlmsgtab.c +3 -0
@@ -98,6 +98,9 @@ static struct nlmsg_perm nlmsg_audit_per { AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_LOGIN, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_INS, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_REM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_LIST, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, };
Quoted 12 lines · Timothy R. Chavez
Hello,
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 on the (device, inode)-based filters to ensure persistence of auditing across transactions. This patch adds the necessary functionality to the audit subsystem and VFS to support file system auditing in which an object is audited based on its location and name. This work is being done to make the audit subsystem compliant with Common Criteria's Controlled Access Protection Profile (CAPP) specification.
Quoted 2 lines
This is the second (#2) RFC on linux-fsdevel.
Please see the comments interspread below.
Thanks
Maneesh
PS: Sorry to miss earlier versions.
[..]
Quoted 9 lines
fs/dcache.c +1
-0@@ -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_attach_watch (dentry,0 );spin_unlock (&dcache_lock);security_d_instantiate (dentry, inode);d_rehash (dentry);
fs/dcache.c struct dentry * __d_lookup(struct dentry · line 1101
Quoted 7 lines
fs/dcache.c +1
-0@@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentryif (!d_unhashed (dentry)) {atomic_inc (&dentry->d_count); found = dentry; +audit_attach_watch (found,0 ); }spin_unlock (&dentry->d_lock);break ;
Are you sure about hooking __d_lookup()? I am not getting why would one want to watch every dcache lookup. IIUC, you probaly want to watch every fs object entering the dcache, so IMO, the right place is real_lookup() and not the __d_lookup() which is performance sensitive code.
[..]
kernel/auditfs.c line 96
Quoted 9 lines
kernel/auditfs.c +9
-0+ +static inline void audit_watch_free (struct audit_watch *watch) +{ +if (watch) { +kfree (watch->name); +kfree (watch->filterkey); +kmem_cache_free (audit_watch_cache, watch); + } +}
I think you forgot, kfree(watch->path) here.
[..]
Quoted 8 lines
kernel/auditfs.c +8
-0+static inline int audit_insert_watch (char *path,char *filterkey,__u32 perms) +{ +int ret; +struct nameidata nd; + + ret =path_lookup (path,LOOKUP_PARENT |LOOKUP_FOLLOW , &nd); +if (ret <0 ) + goto audit_insert_watch_exit;
kernel/auditfs.c line 299
Quoted 10 lines
kernel/auditfs.c +10
-0+ + ret = -EPERM ; +if (nd.last_type !=LAST_NORM || !nd.last.name) + goto audit_insert_watch_release; + + ret =audit_create_wentry (path, nd.last.name, filterkey, perms, + nd.dentry->d_inode->i_sb->s_dev, + nd.dentry->d_inode->i_audit); +/* __d_lookup will attach the audit data, if nd.last.name exists. */ +d_lookup (nd.dentry, &nd.last);
Do you mean, if nd.last.name exists in dcache() or exists in the filesystem?
[..]
Quoted 20 lines
kernel/auditfs.c +20
-0+void audit_attach_watch (struct dentry *dentry,int remove) +{ +struct audit_wentry *wentry; +struct audit_data *data, *parent; + +if (!dentry || !dentry->d_inode) +return ; + +if (!dentry->d_parent || !dentry->d_parent->d_inode) +return ; + + data = dentry->d_inode->i_audit; + parent = dentry->d_parent->d_inode->i_audit; + + wentry =audit_wentry_fetch_lock (dentry->d_name.name, parent); + +write_lock (&data->lock); +/* FIXME: long watchlist == too much spinning? */ +if (remove) { +audit_drain_watchlist (data);
kernel/auditfs.c line 395
Quoted 10 lines
kernel/auditfs.c +10
-0+if (wentry && data->wentry) { +if (!strcmp (wentry->w_watch->name, + data->wentry->w_watch->name)) { +audit_wentry_put (data->wentry); + dentry->d_inode->i_audit->wentry =NULL ; + } + } + }else if (!data->wentry ||hlist_unhashed (&data->wentry->w_node)) { +audit_wentry_put (data->wentry); + dentry->d_inode->i_audit->wentry =audit_wentry_get (wentry);
why not just, data->wentry = audit_wentry_get(wentry);
[..]
Quoted 9 lines
kernel/auditfs.c +9
-0+int audit_list_watches (int pid,int seq) +{ +int ret =0 ; +struct audit_wentry *wentry; +struct hlist_node *pos; + +spin_lock (&audit_master_watchlist_lock); +hlist_for_each_entry (wentry, pos, &audit_master_watchlist, w_master) { + ret =audit_send_watch (pid, seq, wentry->w_watch);
kernel/auditfs.c line 469
Quoted 10 lines
kernel/auditfs.c +10
-0+if (ret <0 ) +break ; + } +spin_unlock (&audit_master_watchlist_lock); + +audit_send_reply (pid, seq,AUDIT_WATCH_LIST ,1 ,1 ,NULL ,0 ); + +return ret; +} +
Looks like there is lock heirarachy issue here between audit_master_watchlist_lock and data->lock().
audit_list_watches()---> audit_master_watchlist_lock
audit_send_watch()
audit_wentry_fetch_lock()---> data->lock
audit_insert_watch()
audit_create_wentry()---> data->lock
audit_master_insert()---> audit_master_watchlist_lock
OR
audit_attach_watch()---> data->lock
audit_drain_watchlist()
audit_destroy_watch()
audit_master_remove()---> audit_master_watchlist_lockQuoted 209 lines
kernel/auditfs.c +91
-0+int audit_receive_watch (int type,int pid,int uid,int seq, +struct audit_transport *req) +{ +int ret =0 ; +char *path =NULL ; +char *filterkey =NULL ; + + ret = -ENOMEM ; + path =kmalloc (req->pathlen,GFP_KERNEL ); +if (!path) + goto audit_receive_watch_exit; +strncpy (path, req->buf, req->pathlen); + +if (req->fklen) { + filterkey =kmalloc (req->fklen,GFP_KERNEL ); +if (!filterkey) + goto audit_receive_watch_exit; + } +strncpy (filterkey, req->buf+req->pathlen, req->fklen); + + ret = -EINVAL ; +if (!path ||strlen (path) +1 >PATH_MAX ) + goto audit_receive_watch_exit; + +/* Includes terminating '\0' */ +if (req->fklen >AUDIT_FILTERKEY_MAX ) + goto audit_receive_watch_exit; + +if (req->perms > (MAY_READ |MAY_WRITE |MAY_EXEC |MAY_APPEND )) + goto audit_receive_watch_exit; + +switch (type) { +case AUDIT_WATCH_INS : + ret =audit_insert_watch (path, filterkey, req->perms); +break ; +case AUDIT_WATCH_REM : + ret =audit_remove_watch (path); +break ; +default : + ret = -EINVAL ; + } + +audit_receive_watch_exit: +kfree (path); +kfree (filterkey); +return ret; +} + +int audit_inode_alloc (struct inode *inode) +{ +if (inode) { + inode->i_audit =audit_data_alloc (); +if (!inode->i_audit) +return ENOMEM ; + } + +return 0 ; +} + +void audit_inode_free (struct inode *inode) +{ +if (inode) +audit_data_free (inode->i_audit); +} + +int audit_filesystem_init () +{ +int ret =0 ; + + audit_watch_cache = +kmem_cache_create ("audit_watch_cache" , +sizeof (struct audit_watch ),0 ,0 ,NULL ,NULL ); +if (!audit_watch_cache) + goto audit_filesystem_init_fail; + + audit_wentry_cache = +kmem_cache_create ("audit_wentry_cache" , +sizeof (struct audit_wentry ),0 ,0 ,NULL ,NULL ); +if (!audit_wentry_cache) + goto audit_filesystem_init_fail; + + goto audit_filesystem_init_exit; + +audit_filesystem_init_fail: + ret = -ENOMEM ; +kmem_cache_destroy (audit_watch_cache); +kmem_cache_destroy (audit_wentry_cache); +audit_filesystem_init_exit: +return ret; + +}diff -Nurp
linux-2.6.12-rc2-mm1~orig/kernel/auditsc.clinux-2.6.12-rc2-mm1~audit/kernel/auditsc.ckernel/auditsc.c +78
-0@@ -102,6 +102,7 @@ struct audit_aux_data { };#define AUDIT_AUX_IPCPERM 0 +#define AUDIT_AUX_WATCH 1struct audit_aux_data_ipcctl {struct audit_aux_data d;@@ -112,6 +113,16 @@ struct audit_aux_data_ipcctl {mode_t mode; }; +struct audit_aux_data_watched { +struct audit_aux_data link; +struct audit_wentry *wentry; +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 {@@ -665,6 +676,24 @@ static void audit_log_exit(struct audit_audit_log_format (ab," qbytes=%lx uid=%d gid=%d mode=%x" , axi->qbytes, axi->uid, axi->gid, axi->mode); +break ; + } + +case AUDIT_AUX_WATCH : { +struct audit_aux_data_watched *axi = (void *)aux; +audit_log_format (ab," name=" ); +audit_log_untrustedstring (ab, axi->wentry->w_watch->name); +audit_log_format (ab, +" filterkey=%s perm=%u perm_mask=%d" +" inode=%lu inode_uid=%d inode_gid=%d" +" inode_dev=%02x:%02x inode_rdev=%02x:%02x" , + axi->wentry->w_watch->filterkey, + axi->wentry->w_watch->perms, + axi->mask, axi->ino, axi->uid, axi->gid, +MAJOR (axi->dev),MINOR (axi->dev), +MAJOR (axi->rdev),MINOR (axi->rdev)); +audit_wentry_put (axi->wentry); +break ; } }audit_log_end (ab);@@ -1024,3 +1053,52 @@ int audit_ipc_perms(unsigned long qbytes context->aux = (void *)ax;return 0 ; } + +int audit_notify_watch (struct inode *inode,int mask) +{ +int ret =0 ; +struct audit_context *context = current->audit_context; +struct audit_aux_data_watched *ax; +struct audit_wentry *wentry =NULL ; + +if (likely (!context)) + goto audit_notify_watch_fail; + +if (!inode) + goto audit_notify_watch_fail; + + wentry =audit_wentry_get (inode->i_audit->wentry); +if (!wentry) + goto audit_notify_watch_fail; + +if (mask && (wentry->w_watch->perms && !(wentry->w_watch->perms&mask))) + goto audit_notify_watch_fail; + + ret = -ENOMEM ; + ax =kmalloc (sizeof (*ax),GFP_KERNEL ); +if (!ax) + goto audit_notify_watch_fail; + + ret =0 ; +if (context->in_syscall && !context->auditable) + context->auditable =1 ; + + ax->wentry = wentry; + 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_AUX_WATCH ; + ax->link.next = context->aux; + context->aux = (void *)ax; + + goto audit_notify_watch_exit; + +audit_notify_watch_fail: +audit_wentry_put (wentry); +audit_notify_watch_exit: +return ret; +}diff -Nurp
linux-2.6.12-rc2-mm1~orig/security/selinux/nlmsgtab.clinux-2.6.12-rc2-mm1~audit/security/selinux/nlmsgtab.csecurity/selinux/nlmsgtab.c +3
-0@@ -98,6 +98,9 @@ static struct nlmsg_perm nlmsg_audit_per { AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_LOGIN, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_INS, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_REM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_LIST, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, };
Signature · 6 lines
Maneesh Soni Linux Technology Center, IBM India Software Labs, Bangalore, India email: [email protected] Phone: 91-80-25044990
Quoted 13 lines · Maneesh Soni
Quoted 12 lines · Timothy R. Chavez
Hello,
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 on the (device, inode)-based filters to ensure persistence of auditing across transactions. This patch adds the necessary functionality to the audit subsystem and VFS to support file system auditing in which an object is audited based on its location and name. This work is being done to make the audit subsystem compliant with Common Criteria's Controlled Access Protection Profile (CAPP) specification.
Quoted 5 lines
Quoted 2 lines
This is the second (#2) RFC on linux-fsdevel.
Please see the comments interspread below.
Hi Maneesh,
Thank you for the comments. I apologize for the late response. My responses are embedded below. Also, I've updated the patch to incorporate much of the feedback I've received from you and others in addition to some fixes for various bugs that I carelessly introduced between the first and second patch I submitted to linux-fsdevel. I'll submit this patch for RFC (noting the changes) momentarily to linux-fsdevel.
-tim
Quoted 21 lines
Thanks
ManeeshPS: Sorry to miss earlier versions.
[..]
Quoted 13 lines
fs/dcache.c +2
-0@@ -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_attach_watch (dentry,0 );spin_unlock (&dcache_lock);security_d_instantiate (dentry, inode);d_rehash (dentry);@@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentryif (!d_unhashed (dentry)) {atomic_inc (&dentry->d_count); found = dentry; +audit_attach_watch (found,0 );
Quoted 8 lines
fs/dcache.c struct dentry * __d_lookup(struct dentry · line 1101
Quoted 3 lines
fs/dcache.c +0
-0}spin_unlock (&dentry->d_lock);break ;Are you sure about hooking
__d_lookup()? I am not getting why would one want to watch every dcache lookup. IIUC, you probaly want to watch every fs object entering the dcache, so IMO, the right place isreal_lookup()and not the__d_lookup()which is performance sensitive code.
Well the purpose of using __d_lookup is to make updates to the inode's audit data, not to watch every dcache lookup. The __d_lookup hook is strategic for getting us to a place where we can determine whether or not the inode's audit data needs updating. When we remove a watch from the file system, all we're doing is unhashing it from the local watch list and decrementing its reference count. An object that's pointing at an unhashed watchlist entry is no longer watched, so it has to drop its reference to the watchlist entry, reset its pointer to NULL, or in the case of a hard link, poll its current location for a watch it can attach too (Note: A hard link can be implicitly watched by linking to a watched file. Should the hard link itself exist in a watched location, it retains its original watch until the inode becomes unwatched. The hard link then has the oppurtunity to be watched at its own location).
It doesn't really seem to me that real_lookup() is suited for the purpose stated above. However, I do realize that having to potentially waiting for two locks in the __d_lookup routine is not a good thing. Perhaps a better stradegy, in this regard, can be devised.
Quoted 13 lines
[..]
kernel/auditfs.c line 96
Quoted 9 lines
kernel/auditfs.c +9
-0+ +static inline void audit_watch_free (struct audit_watch *watch) +{ +if (watch) { +kfree (watch->name); +kfree (watch->filterkey); +kmem_cache_free (audit_watch_cache, watch); + } +}I think you forgot,
kfree(watch->path)here.
Yep. Good eye, thank you.
Quoted 8 lines
[..]
kernel/auditfs.c line 287
Quoted 6 lines
kernel/auditfs.c +6
-0+static inline int audit_insert_watch (char *path,char *filterkey,__u32 perms) +{ +int ret; +struct nameidata nd; + + ret =path_lookup (path,LOOKUP_PARENT |LOOKUP_FOLLOW , &nd);
Note: I've discarded the LOOKUP_FOLLOWS
Quoted 6 lines
Quoted 6 lines
kernel/auditfs.c +6
-0+if (ret <0 ) + goto audit_insert_watch_exit; + + ret = -EPERM ; +if (nd.last_type !=LAST_NORM || !nd.last.name) + goto audit_insert_watch_release;
Quoted 8 lines
kernel/auditfs.c line 299
Quoted 6 lines
kernel/auditfs.c +6
-0+ + ret =audit_create_wentry (path, nd.last.name, filterkey, perms, + nd.dentry->d_inode->i_sb->s_dev, + nd.dentry->d_inode->i_audit); +/* __d_lookup will attach the audit data, if nd.last.name exists. */ +d_lookup (nd.dentry, &nd.last);Do you mean, if nd.last.name exists in
dcache()or exists in the filesystem?
Dcache. Is my thinking flawed? I'm under the impression that all the relevant dentries I need will be in the dcache. The reason I called d_lookup here is because d_lookup will call __d_lookup which has the audit_attach_watch() hook. I know this is not too obvious, but to call the hook directly from here would require I do the d_lookup anyway (to get the dentry).
Quoted 26 lines
[..]
Quoted 24 lines
kernel/auditfs.c +24
-0+void audit_attach_watch (struct dentry *dentry,int remove) +{ +struct audit_wentry *wentry; +struct audit_data *data, *parent; + +if (!dentry || !dentry->d_inode) +return ; + +if (!dentry->d_parent || !dentry->d_parent->d_inode) +return ; + + data = dentry->d_inode->i_audit; + parent = dentry->d_parent->d_inode->i_audit; + + wentry =audit_wentry_fetch_lock (dentry->d_name.name, parent); + +write_lock (&data->lock); +/* FIXME: long watchlist == too much spinning? */ +if (remove) { +audit_drain_watchlist (data); +if (wentry && data->wentry) { +if (!strcmp (wentry->w_watch->name, + data->wentry->w_watch->name)) { +audit_wentry_put (data->wentry);
Quoted 8 lines
kernel/auditfs.c line 395
Quoted 6 lines
kernel/auditfs.c +6
-0+ dentry->d_inode->i_audit->wentry =NULL ; + } + } + }else if (!data->wentry ||hlist_unhashed (&data->wentry->w_node)) { +audit_wentry_put (data->wentry); + dentry->d_inode->i_audit->wentry =audit_wentry_get (wentry);why not just,
data->wentry=audit_wentry_get(wentry);
Oops this one slipped by me on this latest update, I'll add this to the list of things I have to do.. Thanks
Quoted 31 lines
[..]
Quoted 9 lines
kernel/auditfs.c +9
-0+int audit_list_watches (int pid,int seq) +{ +int ret =0 ; +struct audit_wentry *wentry; +struct hlist_node *pos; + +spin_lock (&audit_master_watchlist_lock); +hlist_for_each_entry (wentry, pos, &audit_master_watchlist, w_master) { + ret =audit_send_watch (pid, seq, wentry->w_watch);kernel/auditfs.c line 469
Quoted 10 lines
kernel/auditfs.c +10
-0+if (ret <0 ) +break ; + } +spin_unlock (&audit_master_watchlist_lock); + +audit_send_reply (pid, seq,AUDIT_WATCH_LIST ,1 ,1 ,NULL ,0 ); + +return ret; +} +Looks like there is lock heirarachy issue here between
audit_master_watchlist_lockanddata->lock().audit_list_watches()--->
audit_master_watchlist_lock
audit_send_watch()audit_wentry_fetch_lock()---> data->lock
audit_insert_watch()
audit_create_wentry()--->data->lock
Quoted 9 lines
audit_master_insert()---> audit_master_watchlist_lockOR
audit_attach_watch()--->
data->lock
audit_drain_watchlist()audit_destroy_watch() audit_master_remove()---> audit_master_watchlist_lock
Yes, this was definately an issue (and there was a problem with deadlock on SMP). I believe I resolved this by making the locking much tighter (ie: audit_master_watchlist locking no longer spans function calls and conditionals).
Thanks once again for your comments.
Quoted 210 lines
Quoted 209 lines
kernel/auditfs.c +91
-0+int audit_receive_watch (int type,int pid,int uid,int seq, +struct audit_transport *req) +{ +int ret =0 ; +char *path =NULL ; +char *filterkey =NULL ; + + ret = -ENOMEM ; + path =kmalloc (req->pathlen,GFP_KERNEL ); +if (!path) + goto audit_receive_watch_exit; +strncpy (path, req->buf, req->pathlen); + +if (req->fklen) { + filterkey =kmalloc (req->fklen,GFP_KERNEL ); +if (!filterkey) + goto audit_receive_watch_exit; + } +strncpy (filterkey, req->buf+req->pathlen, req->fklen); + + ret = -EINVAL ; +if (!path ||strlen (path) +1 >PATH_MAX ) + goto audit_receive_watch_exit; + +/* Includes terminating '\0' */ +if (req->fklen >AUDIT_FILTERKEY_MAX ) + goto audit_receive_watch_exit; + +if (req->perms > (MAY_READ |MAY_WRITE |MAY_EXEC |MAY_APPEND )) + goto audit_receive_watch_exit; + +switch (type) { +case AUDIT_WATCH_INS : + ret =audit_insert_watch (path, filterkey, req->perms); +break ; +case AUDIT_WATCH_REM : + ret =audit_remove_watch (path); +break ; +default : + ret = -EINVAL ; + } + +audit_receive_watch_exit: +kfree (path); +kfree (filterkey); +return ret; +} + +int audit_inode_alloc (struct inode *inode) +{ +if (inode) { + inode->i_audit =audit_data_alloc (); +if (!inode->i_audit) +return ENOMEM ; + } + +return 0 ; +} + +void audit_inode_free (struct inode *inode) +{ +if (inode) +audit_data_free (inode->i_audit); +} + +int audit_filesystem_init () +{ +int ret =0 ; + + audit_watch_cache = +kmem_cache_create ("audit_watch_cache" , +sizeof (struct audit_watch ),0 ,0 ,NULL ,NULL ); +if (!audit_watch_cache) + goto audit_filesystem_init_fail; + + audit_wentry_cache = +kmem_cache_create ("audit_wentry_cache" , +sizeof (struct audit_wentry ),0 ,0 ,NULL ,NULL ); +if (!audit_wentry_cache) + goto audit_filesystem_init_fail; + + goto audit_filesystem_init_exit; + +audit_filesystem_init_fail: + ret = -ENOMEM ; +kmem_cache_destroy (audit_watch_cache); +kmem_cache_destroy (audit_wentry_cache); +audit_filesystem_init_exit: +return ret; + +}diff -Nurp
linux-2.6.12-rc2-mm1~orig/kernel/auditsc.clinux-2.6.12-rc2-mm1~audit/kernel/auditsc.ckernel/auditsc.c +78
-0@@ -102,6 +102,7 @@ struct audit_aux_data { };#define AUDIT_AUX_IPCPERM 0 +#define AUDIT_AUX_WATCH 1struct audit_aux_data_ipcctl {struct audit_aux_data d;@@ -112,6 +113,16 @@ struct audit_aux_data_ipcctl {mode_t mode; }; +struct audit_aux_data_watched { +struct audit_aux_data link; +struct audit_wentry *wentry; +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 {@@ -665,6 +676,24 @@ static void audit_log_exit(struct audit_audit_log_format (ab," qbytes=%lx uid=%d gid=%d mode=%x" , axi->qbytes, axi->uid, axi->gid, axi->mode); +break ; + } + +case AUDIT_AUX_WATCH : { +struct audit_aux_data_watched *axi = (void *)aux; +audit_log_format (ab," name=" ); +audit_log_untrustedstring (ab, axi->wentry->w_watch->name); +audit_log_format (ab, +" filterkey=%s perm=%u perm_mask=%d" +" inode=%lu inode_uid=%d inode_gid=%d" +" inode_dev=%02x:%02x inode_rdev=%02x:%02x" , + axi->wentry->w_watch->filterkey, + axi->wentry->w_watch->perms, + axi->mask, axi->ino, axi->uid, axi->gid, +MAJOR (axi->dev),MINOR (axi->dev), +MAJOR (axi->rdev),MINOR (axi->rdev)); +audit_wentry_put (axi->wentry); +break ; } }audit_log_end (ab);@@ -1024,3 +1053,52 @@ int audit_ipc_perms(unsigned long qbytes context->aux = (void *)ax;return 0 ; } + +int audit_notify_watch (struct inode *inode,int mask) +{ +int ret =0 ; +struct audit_context *context = current->audit_context; +struct audit_aux_data_watched *ax; +struct audit_wentry *wentry =NULL ; + +if (likely (!context)) + goto audit_notify_watch_fail; + +if (!inode) + goto audit_notify_watch_fail; + + wentry =audit_wentry_get (inode->i_audit->wentry); +if (!wentry) + goto audit_notify_watch_fail; + +if (mask && (wentry->w_watch->perms && !(wentry->w_watch->perms&mask))) + goto audit_notify_watch_fail; + + ret = -ENOMEM ; + ax =kmalloc (sizeof (*ax),GFP_KERNEL ); +if (!ax) + goto audit_notify_watch_fail; + + ret =0 ; +if (context->in_syscall && !context->auditable) + context->auditable =1 ; + + ax->wentry = wentry; + 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_AUX_WATCH ; + ax->link.next = context->aux; + context->aux = (void *)ax; + + goto audit_notify_watch_exit; + +audit_notify_watch_fail: +audit_wentry_put (wentry); +audit_notify_watch_exit: +return ret; +}diff -Nurp
linux-2.6.12-rc2-mm1~orig/security/selinux/nlmsgtab.clinux-2.6.12-rc2-mm1~audit/security/selinux/nlmsgtab.csecurity/selinux/nlmsgtab.c +3
-0@@ -98,6 +98,9 @@ static struct nlmsg_perm nlmsg_audit_per { AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_LOGIN, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_INS, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_REM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_WATCH_LIST, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, };