[PATCH] Collect more inode information during syscall processing.
authorAmy Griffis <amy.griffis@hp.com>
Thu, 3 Nov 2005 16:00:25 +0000 (16:00 +0000)
committerAl Viro <viro@zeniv.linux.org.uk>
Mon, 20 Mar 2006 19:08:53 +0000 (14:08 -0500)
This patch augments the collection of inode info during syscall
processing. It represents part of the functionality that was provided
by the auditfs patch included in RHEL4.

Specifically, it:

- Collects information for target inodes created or removed during
  syscalls.  Previous code only collects information for the target
  inode's parent.

- Adds the audit_inode() hook to syscalls that operate on a file
  descriptor (e.g. fchown), enabling audit to do inode filtering for
  these calls.

- Modifies filtering code to check audit context for either an inode #
  or a parent inode # matching a given rule.

- Modifies logging to provide inode # for both parent and child.

- Protect debug info from NULL audit_names.name.

[AV: folded a later typo fix from the same author]

Signed-off-by: Amy Griffis <amy.griffis@hp.com>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namei.c
fs/open.c
fs/xattr.c
include/linux/audit.h
include/linux/fsnotify.h
kernel/auditsc.c

index f6619af..51cfc9c 100644 (file)
@@ -1353,6 +1353,7 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
                return -ENOENT;
 
        BUG_ON(victim->d_parent->d_inode != dir);
+       audit_inode_child(victim->d_name.name, victim->d_inode, dir->i_ino);
 
        error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
        if (error)
index 70e0230..7051000 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
@@ -27,6 +27,7 @@
 #include <linux/pagemap.h>
 #include <linux/syscalls.h>
 #include <linux/rcupdate.h>
+#include <linux/audit.h>
 
 #include <asm/unistd.h>
 
@@ -626,6 +627,8 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
        dentry = file->f_dentry;
        inode = dentry->d_inode;
 
+       audit_inode(NULL, inode, 0);
+
        err = -EROFS;
        if (IS_RDONLY(inode))
                goto out_putf;
@@ -775,7 +778,10 @@ asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group)
 
        file = fget(fd);
        if (file) {
-               error = chown_common(file->f_dentry, user, group);
+               struct dentry * dentry;
+               dentry = file->f_dentry;
+               audit_inode(NULL, dentry->d_inode, 0);
+               error = chown_common(dentry, user, group);
                fput(file);
        }
        return error;
index 80eca7d..e416190 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/syscalls.h>
 #include <linux/module.h>
 #include <linux/fsnotify.h>
+#include <linux/audit.h>
 #include <asm/uaccess.h>
 
 
@@ -234,12 +235,15 @@ sys_fsetxattr(int fd, char __user *name, void __user *value,
              size_t size, int flags)
 {
        struct file *f;
+       struct dentry *dentry;
        int error = -EBADF;
 
        f = fget(fd);
        if (!f)
                return error;
-       error = setxattr(f->f_dentry, name, value, size, flags);
+       dentry = f->f_dentry;
+       audit_inode(NULL, dentry->d_inode, 0);
+       error = setxattr(dentry, name, value, size, flags);
        fput(f);
        return error;
 }
@@ -458,12 +462,15 @@ asmlinkage long
 sys_fremovexattr(int fd, char __user *name)
 {
        struct file *f;
+       struct dentry *dentry;
        int error = -EBADF;
 
        f = fget(fd);
        if (!f)
                return error;
-       error = removexattr(f->f_dentry, name);
+       dentry = f->f_dentry;
+       audit_inode(NULL, dentry->d_inode, 0);
+       error = removexattr(dentry, name);
        fput(f);
        return error;
 }
index fd65078..739b954 100644 (file)
@@ -260,7 +260,20 @@ extern void audit_syscall_entry(struct task_struct *task, int arch,
 extern void audit_syscall_exit(struct task_struct *task, int failed, long return_code);
 extern void audit_getname(const char *name);
 extern void audit_putname(const char *name);
-extern void audit_inode(const char *name, const struct inode *inode, unsigned flags);
+extern void __audit_inode(const char *name, const struct inode *inode, unsigned flags);
+extern void __audit_inode_child(const char *dname, const struct inode *inode,
+                               unsigned long pino);
+static inline void audit_inode(const char *name, const struct inode *inode,
+                              unsigned flags) {
+       if (unlikely(current->audit_context))
+               __audit_inode(name, inode, flags);
+}
+static inline void audit_inode_child(const char *dname, 
+                                    const struct inode *inode, 
+                                    unsigned long pino) {
+       if (unlikely(current->audit_context))
+               __audit_inode_child(dname, inode, pino);
+}
 
                                /* Private API (for audit.c only) */
 extern int  audit_receive_filter(int type, int pid, int uid, int seq,
@@ -283,7 +296,10 @@ extern int audit_filter_user(struct netlink_skb_parms *cb, int type);
 #define audit_syscall_exit(t,f,r) do { ; } while (0)
 #define audit_getname(n) do { ; } while (0)
 #define audit_putname(n) do { ; } while (0)
+#define __audit_inode(n,i,f) do { ; } while (0)
+#define __audit_inode_child(d,i,p) do { ; } while (0)
 #define audit_inode(n,i,f) do { ; } while (0)
+#define audit_inode_child(d,i,p) do { ; } while (0)
 #define audit_receive_filter(t,p,u,s,d,l) ({ -EOPNOTSUPP; })
 #define auditsc_get_stamp(c,t,s) do { BUG(); } while (0)
 #define audit_get_loginuid(c) ({ -1; })
index b5ff64d..94919c3 100644 (file)
@@ -15,6 +15,7 @@
 
 #include <linux/dnotify.h>
 #include <linux/inotify.h>
+#include <linux/audit.h>
 
 /*
  * fsnotify_move - file old_name at old_dir was moved to new_name at new_dir
@@ -45,6 +46,8 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
        if (source) {
                inotify_inode_queue_event(source, IN_MOVE_SELF, 0, NULL);
        }
+       audit_inode_child(old_name, source, old_dir->i_ino);
+       audit_inode_child(new_name, target, new_dir->i_ino);
 }
 
 /*
@@ -74,6 +77,7 @@ static inline void fsnotify_create(struct inode *inode, struct dentry *dentry)
 {
        inode_dir_notify(inode, DN_CREATE);
        inotify_inode_queue_event(inode, IN_CREATE, 0, dentry->d_name.name);
+       audit_inode_child(dentry->d_name.name, dentry->d_inode, inode->i_ino);
 }
 
 /*
@@ -84,6 +88,7 @@ static inline void fsnotify_mkdir(struct inode *inode, struct dentry *dentry)
        inode_dir_notify(inode, DN_CREATE);
        inotify_inode_queue_event(inode, IN_CREATE | IN_ISDIR, 0, 
                                  dentry->d_name.name);
+       audit_inode_child(dentry->d_name.name, dentry->d_inode, inode->i_ino);
 }
 
 /*
index 55ba331..73f932b 100644 (file)
@@ -2,6 +2,7 @@
  * Handles all system-call specific auditing features.
  *
  * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
+ * Copyright 2005 Hewlett-Packard Development Company, L.P.
  * Copyright (C) 2005 IBM Corporation
  * All Rights Reserved.
  *
  * The support of additional filter rules compares (>, <, >=, <=) was
  * added by Dustin Kirkland <dustin.kirkland@us.ibm.com>, 2005.
  *
+ * Modified by Amy Griffis <amy.griffis@hp.com> to collect additional
+ * filesystem information.
  */
 
 #include <linux/init.h>
 #include <asm/types.h>
 #include <asm/atomic.h>
+#include <asm/types.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
 #include <linux/mm.h>
 #include <linux/module.h>
 #include <linux/mount.h>
@@ -97,12 +103,12 @@ enum audit_state {
 struct audit_names {
        const char      *name;
        unsigned long   ino;
+       unsigned long   pino;
        dev_t           dev;
        umode_t         mode;
        uid_t           uid;
        gid_t           gid;
        dev_t           rdev;
-       unsigned        flags;
 };
 
 struct audit_aux_data {
@@ -515,7 +521,8 @@ static int audit_filter_rules(struct task_struct *tsk,
                case AUDIT_INODE:
                        if (ctx) {
                                for (j = 0; j < ctx->name_count; j++) {
-                                       if ( audit_comparator(ctx->names[j].ino, op, value)) {
+                                       if (audit_comparator(ctx->names[j].ino, op, value) ||
+                                           audit_comparator(ctx->names[j].pino, op, value)) {
                                                ++result;
                                                break;
                                        }
@@ -696,17 +703,17 @@ static inline void audit_free_names(struct audit_context *context)
 #if AUDIT_DEBUG == 2
        if (context->auditable
            ||context->put_count + context->ino_count != context->name_count) {
-               printk(KERN_ERR "audit.c:%d(:%d): major=%d in_syscall=%d"
+               printk(KERN_ERR "%s:%d(:%d): major=%d in_syscall=%d"
                       " name_count=%d put_count=%d"
                       " ino_count=%d [NOT freeing]\n",
-                      __LINE__,
+                      __FILE__, __LINE__,
                       context->serial, context->major, context->in_syscall,
                       context->name_count, context->put_count,
                       context->ino_count);
                for (i = 0; i < context->name_count; i++)
                        printk(KERN_ERR "names[%d] = %p = %s\n", i,
                               context->names[i].name,
-                              context->names[i].name);
+                              context->names[i].name ?: "(null)");
                dump_stack();
                return;
        }
@@ -932,27 +939,34 @@ static void audit_log_exit(struct audit_context *context, gfp_t gfp_mask)
                }
        }
        for (i = 0; i < context->name_count; i++) {
+               unsigned long ino  = context->names[i].ino;
+               unsigned long pino = context->names[i].pino;
+
                ab = audit_log_start(context, gfp_mask, AUDIT_PATH);
                if (!ab)
                        continue; /* audit_panic has been called */
 
                audit_log_format(ab, "item=%d", i);
-               if (context->names[i].name) {
-                       audit_log_format(ab, " name=");
+
+               audit_log_format(ab, " name=");
+               if (context->names[i].name)
                        audit_log_untrustedstring(ab, context->names[i].name);
-               }
-               audit_log_format(ab, " flags=%x\n", context->names[i].flags);
-                        
-               if (context->names[i].ino != (unsigned long)-1)
-                       audit_log_format(ab, " inode=%lu dev=%02x:%02x mode=%#o"
-                                            " ouid=%u ogid=%u rdev=%02x:%02x",
-                                        context->names[i].ino,
-                                        MAJOR(context->names[i].dev),
-                                        MINOR(context->names[i].dev),
-                                        context->names[i].mode,
-                                        context->names[i].uid,
-                                        context->names[i].gid,
-                                        MAJOR(context->names[i].rdev),
+               else
+                       audit_log_format(ab, "(null)");
+
+               if (pino != (unsigned long)-1)
+                       audit_log_format(ab, " parent=%lu",  pino);
+               if (ino != (unsigned long)-1)
+                       audit_log_format(ab, " inode=%lu",  ino);
+               if ((pino != (unsigned long)-1) || (ino != (unsigned long)-1))
+                       audit_log_format(ab, " dev=%02x:%02x mode=%#o" 
+                                        " ouid=%u ogid=%u rdev=%02x:%02x", 
+                                        MAJOR(context->names[i].dev), 
+                                        MINOR(context->names[i].dev), 
+                                        context->names[i].mode, 
+                                        context->names[i].uid, 
+                                        context->names[i].gid, 
+                                        MAJOR(context->names[i].rdev), 
                                         MINOR(context->names[i].rdev));
                audit_log_end(ab);
        }
@@ -1174,7 +1188,7 @@ void audit_putname(const char *name)
                        for (i = 0; i < context->name_count; i++)
                                printk(KERN_ERR "name[%d] = %p = %s\n", i,
                                       context->names[i].name,
-                                      context->names[i].name);
+                                      context->names[i].name ?: "(null)");
                }
 #endif
                __putname(name);
@@ -1204,7 +1218,7 @@ void audit_putname(const char *name)
  *
  * Called from fs/namei.c:path_lookup().
  */
-void audit_inode(const char *name, const struct inode *inode, unsigned flags)
+void __audit_inode(const char *name, const struct inode *inode, unsigned flags)
 {
        int idx;
        struct audit_context *context = current->audit_context;
@@ -1230,13 +1244,93 @@ void audit_inode(const char *name, const struct inode *inode, unsigned flags)
                ++context->ino_count;
 #endif
        }
-       context->names[idx].flags = flags;
-       context->names[idx].ino   = inode->i_ino;
        context->names[idx].dev   = inode->i_sb->s_dev;
        context->names[idx].mode  = inode->i_mode;
        context->names[idx].uid   = inode->i_uid;
        context->names[idx].gid   = inode->i_gid;
        context->names[idx].rdev  = inode->i_rdev;
+       if ((flags & LOOKUP_PARENT) && (strcmp(name, "/") != 0) && 
+           (strcmp(name, ".") != 0)) {
+               context->names[idx].ino   = (unsigned long)-1;
+               context->names[idx].pino  = inode->i_ino;
+       } else {
+               context->names[idx].ino   = inode->i_ino;
+               context->names[idx].pino  = (unsigned long)-1;
+       }
+}
+
+/**
+ * audit_inode_child - collect inode info for created/removed objects
+ * @dname: inode's dentry name
+ * @inode: inode being audited
+ * @pino: inode number of dentry parent
+ *
+ * For syscalls that create or remove filesystem objects, audit_inode
+ * can only collect information for the filesystem object's parent.
+ * This call updates the audit context with the child's information.
+ * Syscalls that create a new filesystem object must be hooked after
+ * the object is created.  Syscalls that remove a filesystem object
+ * must be hooked prior, in order to capture the target inode during
+ * unsuccessful attempts.
+ */
+void __audit_inode_child(const char *dname, const struct inode *inode,
+                        unsigned long pino)
+{
+       int idx;
+       struct audit_context *context = current->audit_context;
+
+       if (!context->in_syscall)
+               return;
+
+       /* determine matching parent */
+       if (dname)
+               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 (strncmp(n, dname, dlen) == 0)
+                                       goto update_context;
+                       }
+
+       /* catch-all in case match not found */
+       idx = context->name_count++;
+       context->names[idx].name  = NULL;
+       context->names[idx].pino  = pino;
+#if AUDIT_DEBUG
+       context->ino_count++;
+#endif
+
+update_context:
+       if (inode) {
+               context->names[idx].ino   = inode->i_ino;
+               context->names[idx].dev   = inode->i_sb->s_dev;
+               context->names[idx].mode  = inode->i_mode;
+               context->names[idx].uid   = inode->i_uid;
+               context->names[idx].gid   = inode->i_gid;
+               context->names[idx].rdev  = inode->i_rdev;
+       }
 }
 
 /**