AUDIT: Return correct result from audit_filter_rules()
[cascardo/linux.git] / kernel / auditsc.c
index 74c2ae8..7b123f0 100644 (file)
@@ -39,6 +39,7 @@
 #include <linux/audit.h>
 #include <linux/personality.h>
 #include <linux/time.h>
+#include <linux/kthread.h>
 #include <asm/unistd.h>
 
 /* 0 = no checking
@@ -95,6 +96,7 @@ struct audit_names {
        uid_t           uid;
        gid_t           gid;
        dev_t           rdev;
+       unsigned        flags;
 };
 
 struct audit_aux_data {
@@ -145,6 +147,8 @@ struct audit_context {
        int                 auditable;  /* 1 if record should be written */
        int                 name_count;
        struct audit_names  names[AUDIT_NAMES];
+       struct dentry *     pwd;
+       struct vfsmount *   pwdmnt;
        struct audit_context *previous; /* For nested syscalls */
        struct audit_aux_data *aux;
 
@@ -165,9 +169,16 @@ struct audit_context {
 /* There are three lists of rules -- one to search at task creation
  * time, one to search at syscall entry time, and another to search at
  * syscall exit time. */
-static LIST_HEAD(audit_tsklist);
-static LIST_HEAD(audit_entlist);
-static LIST_HEAD(audit_extlist);
+static struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
+       LIST_HEAD_INIT(audit_filter_list[0]),
+       LIST_HEAD_INIT(audit_filter_list[1]),
+       LIST_HEAD_INIT(audit_filter_list[2]),
+       LIST_HEAD_INIT(audit_filter_list[3]),
+       LIST_HEAD_INIT(audit_filter_list[4]),
+#if AUDIT_NR_FILTERS != 5
+#error Fix audit_filter_list initialiser
+#endif
+};
 
 struct audit_entry {
        struct list_head  list;
@@ -208,16 +219,15 @@ static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
 /* Note that audit_add_rule and audit_del_rule are called via
  * audit_receive() in audit.c, and are protected by
  * audit_netlink_sem. */
-static inline int audit_add_rule(struct audit_entry *entry,
-                                struct list_head *list)
+static inline void audit_add_rule(struct audit_entry *entry,
+                                 struct list_head *list)
 {
-       if (entry->rule.flags & AUDIT_PREPEND) {
-               entry->rule.flags &= ~AUDIT_PREPEND;
+       if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
+               entry->rule.flags &= ~AUDIT_FILTER_PREPEND;
                list_add_rcu(&entry->list, list);
        } else {
                list_add_tail_rcu(&entry->list, list);
        }
-       return 0;
 }
 
 static void audit_free_rule(struct rcu_head *head)
@@ -243,7 +253,7 @@ static inline int audit_del_rule(struct audit_rule *rule,
                        return 0;
                }
        }
-       return -EFAULT;         /* No matching rule */
+       return -ENOENT;         /* No matching rule */
 }
 
 /* Copy rule from user-space to kernel-space.  Called during
@@ -258,6 +268,8 @@ static int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
                return -1;
        if (s->field_count < 0 || s->field_count > AUDIT_MAX_FIELDS)
                return -1;
+       if ((s->flags & ~AUDIT_FILTER_PREPEND) >= AUDIT_NR_FILTERS)
+               return -1;
 
        d->flags        = s->flags;
        d->action       = s->action;
@@ -270,27 +282,60 @@ static int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
        return 0;
 }
 
+static int audit_list_rules(void *_dest)
+{
+       int pid, seq;
+       int *dest = _dest;
+       struct audit_entry *entry;
+       int i;
+
+       pid = dest[0];
+       seq = dest[1];
+       kfree(dest);
+
+       down(&audit_netlink_sem);
+
+       /* The *_rcu iterators not needed here because we are
+          always called with audit_netlink_sem held. */
+       for (i=0; i<AUDIT_NR_FILTERS; i++) {
+               list_for_each_entry(entry, &audit_filter_list[i], list)
+                       audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
+                                        &entry->rule, sizeof(entry->rule));
+       }
+       audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
+       
+       up(&audit_netlink_sem);
+       return 0;
+}
+
 int audit_receive_filter(int type, int pid, int uid, int seq, void *data,
                                                        uid_t loginuid)
 {
-       u32                flags;
        struct audit_entry *entry;
+       struct task_struct *tsk;
+       int *dest;
        int                err = 0;
+       unsigned listnr;
 
        switch (type) {
        case AUDIT_LIST:
-               /* The *_rcu iterators not needed here because we are
-                  always called with audit_netlink_sem held. */
-               list_for_each_entry(entry, &audit_tsklist, list)
-                       audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
-                                        &entry->rule, sizeof(entry->rule));
-               list_for_each_entry(entry, &audit_entlist, list)
-                       audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
-                                        &entry->rule, sizeof(entry->rule));
-               list_for_each_entry(entry, &audit_extlist, list)
-                       audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
-                                        &entry->rule, sizeof(entry->rule));
-               audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
+               /* We can't just spew out the rules here because we might fill
+                * the available socket buffer space and deadlock waiting for
+                * auditctl to read from it... which isn't ever going to
+                * happen if we're actually running in the context of auditctl
+                * trying to _send_ the stuff */
+                
+               dest = kmalloc(2 * sizeof(int), GFP_KERNEL);
+               if (!dest)
+                       return -ENOMEM;
+               dest[0] = pid;
+               dest[1] = seq;
+
+               tsk = kthread_run(audit_list_rules, dest, "audit_list_rules");
+               if (IS_ERR(tsk)) {
+                       kfree(dest);
+                       err = PTR_ERR(tsk);
+               }
                break;
        case AUDIT_ADD:
                if (!(entry = kmalloc(sizeof(*entry), GFP_KERNEL)))
@@ -299,26 +344,20 @@ int audit_receive_filter(int type, int pid, int uid, int seq, void *data,
                        kfree(entry);
                        return -EINVAL;
                }
-               flags = entry->rule.flags;
-               if (!err && (flags & AUDIT_PER_TASK))
-                       err = audit_add_rule(entry, &audit_tsklist);
-               if (!err && (flags & AUDIT_AT_ENTRY))
-                       err = audit_add_rule(entry, &audit_entlist);
-               if (!err && (flags & AUDIT_AT_EXIT))
-                       err = audit_add_rule(entry, &audit_extlist);
-               audit_log(NULL, AUDIT_CONFIG_CHANGE, 
-                               "auid %u added an audit rule\n", loginuid);
+               listnr = entry->rule.flags & ~AUDIT_FILTER_PREPEND;
+               audit_add_rule(entry, &audit_filter_list[listnr]);
+               audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE, 
+                               "auid=%u added an audit rule\n", loginuid);
                break;
        case AUDIT_DEL:
-               flags =((struct audit_rule *)data)->flags;
-               if (!err && (flags & AUDIT_PER_TASK))
-                       err = audit_del_rule(data, &audit_tsklist);
-               if (!err && (flags & AUDIT_AT_ENTRY))
-                       err = audit_del_rule(data, &audit_entlist);
-               if (!err && (flags & AUDIT_AT_EXIT))
-                       err = audit_del_rule(data, &audit_extlist);
-               audit_log(NULL, AUDIT_CONFIG_CHANGE,
-                               "auid %u removed an audit rule\n", loginuid);
+               listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
+               if (listnr >= AUDIT_NR_FILTERS)
+                       return -EINVAL;
+
+               err = audit_del_rule(data, &audit_filter_list[listnr]);
+               if (!err)
+                       audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+                                 "auid=%u removed an audit rule\n", loginuid);
                break;
        default:
                return -EINVAL;
@@ -452,7 +491,7 @@ static enum audit_state audit_filter_task(struct task_struct *tsk)
        enum audit_state   state;
 
        rcu_read_lock();
-       list_for_each_entry_rcu(e, &audit_tsklist, list) {
+       list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) {
                if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
                        rcu_read_unlock();
                        return state;
@@ -476,6 +515,9 @@ static enum audit_state audit_filter_syscall(struct task_struct *tsk,
        int                word = AUDIT_WORD(ctx->major);
        int                bit  = AUDIT_BIT(ctx->major);
 
+       if (audit_pid && ctx->pid == audit_pid)
+               return AUDIT_DISABLED;
+
        rcu_read_lock();
        list_for_each_entry_rcu(e, list, list) {
                if ((e->rule.mask[word] & bit) == bit
@@ -488,6 +530,37 @@ static enum audit_state audit_filter_syscall(struct task_struct *tsk,
        return AUDIT_BUILD_CONTEXT;
 }
 
+int audit_filter_user(int pid, int type)
+{
+       struct task_struct *tsk;
+       struct audit_entry *e;
+       enum audit_state   state;
+       int ret = 1;
+
+       read_lock(&tasklist_lock);
+       tsk = find_task_by_pid(pid);
+       if (tsk)
+               get_task_struct(tsk);
+       read_unlock(&tasklist_lock);
+
+       if (!tsk)
+               return -ESRCH;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) {
+               if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
+                       if (state == AUDIT_DISABLED)
+                               ret = 0;
+                       break;
+               }
+       }
+       rcu_read_unlock();
+       put_task_struct(tsk);
+
+       return ret; /* Audit by default */
+
+}
+
 /* This should be called with task_lock() held. */
 static inline struct audit_context *audit_get_context(struct task_struct *tsk,
                                                      int return_valid,
@@ -500,9 +573,9 @@ static inline struct audit_context *audit_get_context(struct task_struct *tsk,
        context->return_valid = return_valid;
        context->return_code  = return_code;
 
-       if (context->in_syscall && !context->auditable) {
+       if (context->in_syscall && !context->auditable && tsk->pid != audit_pid) {
                enum audit_state state;
-               state = audit_filter_syscall(tsk, context, &audit_extlist);
+               state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]);
                if (state == AUDIT_RECORD_CONTEXT)
                        context->auditable = 1;
        }
@@ -552,6 +625,12 @@ static inline void audit_free_names(struct audit_context *context)
                if (context->names[i].name)
                        __putname(context->names[i].name);
        context->name_count = 0;
+       if (context->pwd)
+               dput(context->pwd);
+       if (context->pwdmnt)
+               mntput(context->pwdmnt);
+       context->pwd = NULL;
+       context->pwdmnt = NULL;
 }
 
 static inline void audit_free_aux(struct audit_context *context)
@@ -650,7 +729,8 @@ static void audit_log_task_info(struct audit_buffer *ab)
        struct vm_area_struct *vma;
 
        get_task_comm(name, current);
-       audit_log_format(ab, " comm=%s", name);
+       audit_log_format(ab, " comm=");
+       audit_log_untrustedstring(ab, name);
 
        if (!mm)
                return;
@@ -674,14 +754,15 @@ static void audit_log_exit(struct audit_context *context)
 {
        int i;
        struct audit_buffer *ab;
+       struct audit_aux_data *aux;
 
-       ab = audit_log_start(context, AUDIT_SYSCALL);
+       ab = audit_log_start(context, GFP_KERNEL, AUDIT_SYSCALL);
        if (!ab)
                return;         /* audit_panic has been called */
-       audit_log_format(ab, "syscall=%d", context->major);
+       audit_log_format(ab, "arch=%x syscall=%d",
+                        context->arch, context->major);
        if (context->personality != PER_LINUX)
                audit_log_format(ab, " per=%lx", context->personality);
-       audit_log_format(ab, " arch=%x", context->arch);
        if (context->return_valid)
                audit_log_format(ab, " success=%s exit=%ld", 
                                 (context->return_valid==AUDITSC_SUCCESS)?"yes":"no",
@@ -704,12 +785,10 @@ static void audit_log_exit(struct audit_context *context)
                  context->egid, context->sgid, context->fsgid);
        audit_log_task_info(ab);
        audit_log_end(ab);
-       while (context->aux) {
-               struct audit_aux_data *aux;
 
-               aux = context->aux;
+       for (aux = context->aux; aux; aux = aux->next) {
 
-               ab = audit_log_start(context, aux->type);
+               ab = audit_log_start(context, GFP_KERNEL, aux->type);
                if (!ab)
                        continue; /* audit_panic has been called */
 
@@ -739,26 +818,31 @@ static void audit_log_exit(struct audit_context *context)
                case AUDIT_AVC_PATH: {
                        struct audit_aux_data_path *axi = (void *)aux;
                        audit_log_d_path(ab, "path=", axi->dentry, axi->mnt);
-                       dput(axi->dentry);
-                       mntput(axi->mnt);
                        break; }
 
                }
                audit_log_end(ab);
-
-               context->aux = aux->next;
-               kfree(aux);
        }
 
+       if (context->pwd && context->pwdmnt) {
+               ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD);
+               if (ab) {
+                       audit_log_d_path(ab, "cwd=", context->pwd, context->pwdmnt);
+                       audit_log_end(ab);
+               }
+       }
        for (i = 0; i < context->name_count; i++) {
-               ab = audit_log_start(context, AUDIT_PATH);
+               ab = audit_log_start(context, GFP_KERNEL, 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_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",
@@ -789,7 +873,7 @@ void audit_free(struct task_struct *tsk)
 
        /* Check for system calls that do not go through the exit
         * function (e.g., exit_group), then free context block. */
-       if (context->in_syscall && context->auditable && context->pid != audit_pid)
+       if (context->in_syscall && context->auditable)
                audit_log_exit(context);
 
        audit_free_context(context);
@@ -865,7 +949,7 @@ void audit_syscall_entry(struct task_struct *tsk, int arch, int major,
 
        state = context->state;
        if (state == AUDIT_SETUP_CONTEXT || state == AUDIT_BUILD_CONTEXT)
-               state = audit_filter_syscall(tsk, context, &audit_entlist);
+               state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]);
        if (likely(state == AUDIT_DISABLED))
                return;
 
@@ -894,7 +978,7 @@ void audit_syscall_exit(struct task_struct *tsk, int valid, long return_code)
        if (likely(!context))
                return;
 
-       if (context->in_syscall && context->auditable && context->pid != audit_pid)
+       if (context->in_syscall && context->auditable)
                audit_log_exit(context);
 
        context->in_syscall = 0;
@@ -934,6 +1018,13 @@ void audit_getname(const char *name)
        context->names[context->name_count].name = name;
        context->names[context->name_count].ino  = (unsigned long)-1;
        ++context->name_count;
+       if (!context->pwd) {
+               read_lock(&current->fs->lock);
+               context->pwd = dget(current->fs->pwd);
+               context->pwdmnt = mntget(current->fs->pwdmnt);
+               read_unlock(&current->fs->lock);
+       }
+               
 }
 
 /* Intercept a putname request.  Called from
@@ -978,7 +1069,7 @@ void audit_putname(const char *name)
 
 /* Store the inode and device from a lookup.  Called from
  * fs/namei.c:path_lookup(). */
-void audit_inode(const char *name, const struct inode *inode)
+void audit_inode(const char *name, const struct inode *inode, unsigned flags)
 {
        int idx;
        struct audit_context *context = current->audit_context;
@@ -1004,12 +1095,13 @@ void audit_inode(const char *name, const struct inode *inode)
                ++context->ino_count;
 #endif
        }
-       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;
+       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;
 }
 
 void auditsc_get_stamp(struct audit_context *ctx,
@@ -1026,7 +1118,7 @@ int audit_set_loginuid(struct task_struct *task, uid_t loginuid)
        if (task->audit_context) {
                struct audit_buffer *ab;
 
-               ab = audit_log_start(NULL, AUDIT_LOGIN);
+               ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_LOGIN);
                if (ab) {
                        audit_log_format(ab, "login pid=%d uid=%u "
                                "old auid=%u new auid=%u",