[PATCH 26/26] audit: collect path information when possible

Eric Paris
2011-11-17T22:05:11Z

For _at type syscalls (like openat) we do not collect any information about
the dfd. This patch grabs a reference to the path of all fd's passed to
the kernel. We free those on syscall exit. We will then output those paths
as inode records and use the path information to generate better pathnames if
possible.

Signed-off-by: Eric Paris <eparis@redhat.com>
---

fs/file_table.c | 12 ++++++
include/linux/audit.h | 12 ++++++
kernel/auditsc.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 109 insertions(+), 7 deletions(-)

diff --git a/fs/file_table.c b/fs/file_table.c
index 6f19cf5..2dceb76 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -287,6 +287,9 @@ struct file *fget(unsigned int fd)
}
rcu_read_unlock();

+ if (file)
+ audit_path(&file->f_path);
+
return file;
}

@@ -306,6 +309,9 @@ struct file *fget_raw(unsigned int fd)
}
rcu_read_unlock();

+ if (file)
+ audit_path(&file->f_path);
+
return file;
}

@@ -351,6 +357,9 @@ struct file *fget_light(unsigned int fd, int *fput_needed)
rcu_read_unlock();
}

+ if (file)
+ audit_path(&file->f_path);
+
return file;
}

@@ -375,6 +384,9 @@ struct file *fget_raw_light(unsigned int fd, int *fput_needed)
rcu_read_unlock();
}

+ if (file)
+ audit_path(&file->f_path);
+
return file;
}

diff --git a/include/linux/audit.h b/include/linux/audit.h
index 1b4b109..86f4108 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -433,6 +433,7 @@ extern void audit_putname(const char *name);
extern void __audit_inode(const char *name, const struct dentry *dentry);
extern void __audit_inode_child(const struct dentry *dentry,
const struct inode *parent);
+extern void __audit_path(struct path *path);
extern void __audit_seccomp(unsigned long syscall);
extern void __audit_ptrace(struct task_struct *t);

@@ -476,6 +477,13 @@ static inline void audit_inode_child(const struct dentry *dentry,
if (unlikely(!audit_dummy_context()))
__audit_inode_child(dentry, parent);
}
+
+static inline void audit_path(struct path *path)
+{
+ if (unlikely(!audit_dummy_context()))
+ __audit_path(path);
+}
+
void audit_core_dumps(long signr);

static inline void audit_seccomp(unsigned long syscall)
@@ -599,10 +607,11 @@ extern int audit_signals;
#define audit_dummy_context() 1
#define audit_getname(n) do { ; } while (0)
#define audit_putname(n) do { ; } while (0)
-#define __audit_inode(n,d) do { ; } while (0)
+#define __audit_inode (n, d) do { ; } while (0)
#define __audit_inode_child(i,p) do { ; } while (0)
#define audit_inode(n,d) do { (void)(d); } while (0)
#define audit_inode_child(i,p) do { ; } while (0)
+#define audit_path(p) do { ; } while (0)
#define audit_core_dumps(i) do { ; } while (0)
#define audit_seccomp(i) do { ; } while (0)
#define auditsc_get_stamp(c,t,s) (0)
@@ -625,6 +634,7 @@ extern int audit_signals;
#define audit_ptrace(t) ((void)0)
#define audit_n_rules 0
#define audit_signals 0
+
#endif /* CONFIG_AUDITSYSCALL */

#ifdef CONFIG_AUDIT
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index fdcbc6b..f50c143 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -78,7 +78,7 @@
/* AUDIT_NAMES is the number of slots we reserve in the audit_context
* for saving names from getname(). If we get more names we will allocate
* a name dynamically and also add those to the list anchored by names_list. */
-#define AUDIT_NAMES 5
+#define AUDIT_NAMES 6

/* Indicates that audit should log the full pathname. */
#define AUDIT_NAME_FULL -1
@@ -109,6 +109,7 @@ struct audit_cap_data {
struct audit_names {
struct list_head list; /* audit_context->names_list */
const char *name;
+ struct path *path;
unsigned long ino;
dev_t dev;
umode_t mode;
@@ -198,6 +199,8 @@ struct audit_context {
struct audit_names preallocated_names[AUDIT_NAMES];
int name_count; /* total records in names_list */
struct list_head names_list; /* anchor for struct audit_names->list */
+ int path_count; /* number of paths in paths[] */
+ struct path paths[AUDIT_NAMES]; /* paths held to be merged with names_list */
char * filterkey; /* key for rule that triggered record */
struct path pwd;
struct audit_context *previous; /* For nested syscalls */
@@ -961,6 +964,16 @@ static inline void audit_free_names(struct audit_context *context)
context->pwd.mnt = NULL;
}

+static inline void audit_free_paths(struct audit_context *context)
+{
+ int i;
+
+ for (i = 0; i < context->path_count; i++)
+ path_put(&context->paths[i]);
+
+ context->path_count = 0;
+}
+
static inline void audit_free_aux(struct audit_context *context)
{
struct audit_aux_data *aux;
@@ -1044,6 +1057,7 @@ static inline void audit_free_context(struct audit_context *context)
context->name_count, count);
}
audit_free_names(context);
+ audit_free_paths(context);
unroll_tree_refs(context, NULL, 0);
free_tree_refs(context);
audit_free_aux(context);
@@ -1452,6 +1466,35 @@ static void show_special(struct audit_context *context, int *call_panic)
audit_log_end(ab);
}

+static struct audit_names *____audit_inode(struct audit_context *, const char *, const struct dentry*);
+
+static void audit_merge_paths_and_names(struct audit_context *context)
+{
+ struct audit_names *n;
+ int i;
+
+ for (i = 0; i < context->path_count; i++) {
+ struct path *p;
+
+ p = &context->paths[i];
+
+ /* if this path happens to also be part of the normal name
+ * collection, why not make the full path available? */
+ list_for_each_entry(n, &context->names_list, list) {
+ if (p->dentry->d_inode->i_ino == n->ino &&
+ p->dentry->d_inode->i_sb->s_dev == n->dev) {
+ n->path = p;
+ break;
+ }
+ }
+
+ /* create a new name record with this path */
+ n = ____audit_inode(context, NULL, p->dentry);
+ if (n)
+ n->path = p;
+ }
+}
+
static void audit_log_name(struct audit_context *context, struct audit_names *n,
int record_num, int *call_panic)
{
@@ -1483,6 +1526,11 @@ static void audit_log_name(struct audit_context *context, struct audit_names *n,
} else
audit_log_format(ab, " name=(null)");

+ if (n->path)
+ audit_log_d_path(ab, "path=", n->path);
+ else
+ audit_log_format(ab, " path=(null)");
+
if (n->ino != (unsigned long)-1) {
audit_log_format(ab, " inode=%lu"
" dev=%02x:%02x mode=%#o"
@@ -1662,6 +1710,8 @@ static void audit_log_exit(struct audit_context *context, struct task_struct *ts
}
}

+ audit_merge_paths_and_names(context);
+
i = 0;
list_for_each_entry(n, &context->names_list, list)
audit_log_name(context, n, i++, &call_panic);
@@ -1833,6 +1883,7 @@ void __audit_syscall_exit(int success, long return_code)
tsk->audit_context = new_context;
} else {
audit_free_names(context);
+ audit_free_paths(context);
unroll_tree_refs(context, NULL, 0);
audit_free_aux(context);
context->aux = NULL;
@@ -2082,14 +2133,14 @@ static void audit_copy_inode(struct audit_names *name, const struct dentry *dent
*
* Called from fs/namei.c:path_lookup().
*/
-void __audit_inode(const char *name, const struct dentry *dentry)
+static struct audit_names *____audit_inode(struct audit_context *context, const char *name,
+ const struct dentry *dentry)
{
- struct audit_context *context = current->audit_context;
const struct inode *inode = dentry->d_inode;
struct audit_names *n;

if (!context->in_syscall)
- return;
+ return NULL;

list_for_each_entry_reverse(n, &context->names_list, list) {
if (n->name && (n->name == name))
@@ -2099,10 +2150,18 @@ void __audit_inode(const char *name, const struct dentry *dentry)
/* unable to find the name from a previous getname() */
n = audit_alloc_name(context);
if (!n)
- return;
+ return NULL;
out:
- handle_path(dentry);
audit_copy_inode(n, dentry, inode);
+ return n;
+}
+
+void __audit_inode(const char *name, const struct dentry *dentry)
+{
+ struct audit_context *context = current->audit_context;
+
+ ____audit_inode(context, name, dentry);
+ handle_path(dentry);
}

/**
@@ -2193,6 +2252,27 @@ add_names:
}
EXPORT_SYMBOL_GPL(__audit_inode_child);

+void __audit_path(struct path *path)
+{
+ struct audit_context *context = current->audit_context;
+ int i, path_count;
+
+ path_count = context->path_count;
+
+ if (path_count >= AUDIT_NAMES - 1)
+ return;
+
+ for (i = 0; i < path_count; i++) {
+ if (context->paths[i].dentry == path->dentry &&
+ context->paths[i].mnt == path->mnt)
+ return;
+ }
+
+ context->paths[path_count] = *path;
+ path_get(&context->paths[path_count]);
+ context->path_count++;
+}
+
/**
* auditsc_get_stamp - get local copies of audit_context values
* @ctx: audit_context for the task