[PATCH] TaskTracker : Simplified thread information tracker.

Tetsuo Handa penguin-kernel at I-love.SAKURA.ne.jp
Thu Jun 26 11:40:54 UTC 2014


Thank you for your comment, Steve.

Steve Grubb wrote:
> On Monday, June 23, 2014 09:14:35 PM Tetsuo Handa wrote:
>> Any comments on this proposal?
>
> subj= is the wrong way to record this. The subj field name is for process 
> labels. When field names get re-used for different purposes, it causes lots of 
> problems in being able to assign meaning and correctly use it in analysis. I 
> would suggest using phist= for process history or something like that. Please 
> don't re-use subj for this.

This was just a sample implementation. If this proposal is acceptable as a
patch to auditing subsystem, I'm happy to update not to re-use subj= field
and not to occupy LSM. An updated version is attached.

> Also, the comm file is under control of the user. What if they create a program 
> "sshd=>crond"? Would that throw off the analysis? How do you ensure user 
> supplied names do not contain symbols that you are using to denote parentage?

OK. I added '=' in comm name to the list of need-to-escape bytes.

By the way, audit_string_contains_control() treats *p == '"' || *p < 0x21 ||
*p > 0x7e as need-to-escape bytes. Thus, 0x20 from audit_log_untrustedstring()
is a need-to-escape byte. However, I can see that 0x20 from userspace programs
is emitted without escaping.

  type=USER_START msg=audit(1403741835.270:16): user pid=1870 uid=0 auid=0 ses=1
  msg='op=login id=0 exe="/usr/sbin/sshd" hostname=192.168.0.1 addr=192.168.0.1 
  terminal=/dev/pts/0 res=success'

Where can I find which bytes in $value need to be escaped when emitting
a record like name='$value' ? Is 0x20 in $value permitted?

> Also, would you consider adding this information as a auxiliary record rather 
> than as part of a syscall record? The advantage is it can be filtered or 
> searched for. We recently did this for PROCTITLE information. Perhaps this fits 
> better as a PROCHIST auxiliary record?

I changed to use auxiliary record and noticed a difference.
The previous version emitted the history for type=USER_LOGIN case

  type=USER_LOGIN msg=audit(1400879947.084:24): pid=4308 uid=0 auid=0 ses=2
  subj="swapper/0(2014/05/23-21:17:30)=>init(2014/05/23-21:17:33)=>
  switch_root(2014/05/23-21:17:34)=>init(2014/05/23-21:17:34)=>
  sh(2014/05/23-21:17:56)=>mingetty(2014/05/23-21:17:56)=>
  login(2014/05/23-21:19:05)" msg='op=login id=0 exe="/bin/login" hostname=?
  addr=? terminal=tty1 res=success'

but current version does not emit it for type=USER_LOGIN case.
Does auxiliary record work with only type=SYSCALL case (and therefore
I should use CONFIG_AUDITSYSCALL rather than CONFIG_AUDIT in the patch
below) ?

Regards.
----------
>From d015533ce544feb8922fcbf023017c82bd79a9ac Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel at I-love.SAKURA.ne.jp>
Date: Thu, 26 Jun 2014 09:39:14 +0900
Subject: [PATCH] audit: Emit history of thread's comm name.

When an unexpected system event (e.g. reboot) occurs, the administrator may
want to identify which application triggered the event. System call auditing
could be used for recording such event. However, the audit log may not be
able to provide sufficient information for identifying the application
because the audit log does not reflect how the program was executed.

This patch adds ability to trace how the program was executed and emit it
as an auxiliary record in the form of comm name and time stamp pairs as of
execve().

  type=UNKNOWN[1329] msg=audit(1403741314.019:22): history='
  swapper/0(2014/06/26-09:06:04)=>init(2014/06/26-09:06:10)=>
  switch_root(2014/06/26-09:06:13)=>init(2014/06/26-09:06:13)=>
  sh(2014/06/26-00:06:27)=>rc(2014/06/26-00:06:27)=>
  S55sshd(2014/06/26-00:06:35)=>sshd(2014/06/26-00:06:35)=>
  sshd(2014/06/26-00:06:40)=>bash(2014/06/26-00:06:43)=>
  tail(2014/06/26-00:08:34)'

Signed-off-by: Tetsuo Handa <penguin-kernel at I-love.SAKURA.ne.jp>
---
 fs/exec.c                  |    1 +
 include/linux/audit.h      |   23 +++++++++++-
 include/linux/init_task.h  |    9 ++++
 include/linux/sched.h      |    5 ++
 include/uapi/linux/audit.h |    1 +
 kernel/audit.c             |   90 ++++++++++++++++++++++++++++++++++++++++++++
 kernel/auditsc.c           |   19 +++++++++
 7 files changed, 147 insertions(+), 1 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index a3d33fe..fcda589 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1195,6 +1195,7 @@ void install_exec_creds(struct linux_binprm *bprm)
 	commit_creds(bprm->cred);
 	bprm->cred = NULL;
 
+	audit_update_history();
 	/*
 	 * Disable monitoring for regular users
 	 * when executing setuid binaries. Must
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 22cfddb..526525b 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -138,6 +138,9 @@ static inline int audit_dummy_context(void)
 }
 static inline void audit_free(struct task_struct *task)
 {
+	extern void kfree(const void *);
+	kfree(task->comm_history);
+	task->comm_history = NULL;
 	if (unlikely(task->audit_context))
 		__audit_free(task);
 }
@@ -318,10 +321,22 @@ extern int audit_signals;
 #else /* CONFIG_AUDITSYSCALL */
 static inline int audit_alloc(struct task_struct *task)
 {
+#ifdef CONFIG_AUDIT
+	task->comm_history = kmemdup(current->comm_history, COMM_HISTORY_SIZE,
+				     GFP_KERNEL);
+	return task->comm_history ? 0 : -ENOMEM;
+#else
 	return 0;
+#endif
 }
 static inline void audit_free(struct task_struct *task)
-{ }
+{
+#ifdef CONFIG_AUDIT
+	extern void kfree(const void *);
+	kfree(task->comm_history);
+	task->comm_history = NULL;
+#endif
+}
 static inline void audit_syscall_entry(int arch, int major, unsigned long a0,
 				       unsigned long a1, unsigned long a2,
 				       unsigned long a3)
@@ -532,5 +547,11 @@ static inline void audit_log_string(struct audit_buffer *ab, const char *buf)
 {
 	audit_log_n_string(ab, buf, strlen(buf));
 }
+#ifdef CONFIG_AUDIT
+extern void audit_update_history(void);
+#else
+static inline void audit_update_history(void)
+{ }
+#endif
 
 #endif
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 6df7f9f..3bad194 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -164,6 +164,14 @@ extern struct task_group root_task_group;
 # define INIT_RT_MUTEXES(tsk)
 #endif
 
+#ifdef CONFIG_AUDIT
+extern char init_task_history[COMM_HISTORY_SIZE];
+#define INIT_THREAD_HISTORY(tsk)		\
+	.comm_history = init_task_history,
+#else
+#define INIT_THREAD_HISTORY(tsk)
+#endif
+
 /*
  *  INIT_TASK is used to set up the first task table, touch at
  * your own risk!. Base=0, limit=0x1fffff (=2MB)
@@ -234,6 +242,7 @@ extern struct task_group root_task_group;
 	INIT_CPUSET_SEQ(tsk)						\
 	INIT_RT_MUTEXES(tsk)						\
 	INIT_VTIME(tsk)							\
+	INIT_THREAD_HISTORY(tsk)					\
 }
 
 
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 306f4f0..f23fd73 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -264,6 +264,8 @@ extern char ___assert_task_state[1 - 2*!!(
 
 /* Task command name length */
 #define TASK_COMM_LEN 16
+/* Task command name history length */
+#define COMM_HISTORY_SIZE 1024
 
 #include <linux/spinlock.h>
 
@@ -1655,6 +1657,9 @@ struct task_struct {
 	unsigned int	sequential_io;
 	unsigned int	sequential_io_avg;
 #endif
+#ifdef CONFIG_AUDIT
+	char *comm_history;
+#endif
 };
 
 /* Future-safe accessor for struct task_struct's cpus_allowed. */
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index cf67147..b9e051c 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -110,6 +110,7 @@
 #define AUDIT_SECCOMP		1326	/* Secure Computing event */
 #define AUDIT_PROCTITLE		1327	/* Proctitle emit event */
 #define AUDIT_FEATURE_CHANGE	1328	/* audit log listing feature changes */
+#define AUDIT_PROCHISTORY	1329	/* Commname history emit event */
 
 #define AUDIT_AVC		1400	/* SE Linux avc denial or grant */
 #define AUDIT_SELINUX_ERR	1401	/* Internal SE Linux Errors */
diff --git a/kernel/audit.c b/kernel/audit.c
index 3ef2e0e..20441c9 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1158,11 +1158,101 @@ static struct pernet_operations audit_net_ops __net_initdata = {
 	.size = sizeof(struct audit_net),
 };
 
+char init_task_history[COMM_HISTORY_SIZE];
+
+/**
+ * audit_update_history - Update current->comm_history field.
+ *
+ * Returns nothing.
+ *
+ * Update is done locklessly because current thread's history is updated by
+ * only current thread upon boot up and successful execve() operation, and
+ * we don't read other thread's history.
+ */
+void audit_update_history(void)
+{
+	static const u16 eom[2][12] = {
+		{ 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+		{ 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+	};
+	u16 year = 1970;
+	u16 day;
+	u8 month;
+	u8 hour;
+	u8 minute;
+	u8 second;
+	bool r;
+	time_t now = get_seconds();
+	char *history = current->comm_history;
+	int pos = strlen(history);
+
+	/* Make some room by truncating old history. */
+	while (pos >= COMM_HISTORY_SIZE - (TASK_COMM_LEN * 4 + 30)) {
+		char *cp = strstr(history + 2, "=>");
+
+		if (!cp)
+			return;
+		memmove(history, cp, strlen(cp) + 1);
+		pos = strlen(history);
+	}
+	if (pos) {
+		history += pos;
+		*history++ = '=';
+		*history++ = '>';
+	}
+	/*
+	 * Read locklessly because this is current thread and being
+	 * unexpectedly modified by other thread is not a fatal problem.
+	 */
+	for (pos = 0; pos < TASK_COMM_LEN; pos++) {
+		const unsigned char c = current->comm[pos];
+
+		if (!c)
+			break;
+		else if (c == '\'' || c == '\\' || c == '=' || c < 0x21 ||
+			 c > 0x7e) {
+			*history++ = '\\';
+			*history++ = (c >> 6) + '0';
+			*history++ = ((c >> 3) & 7) + '0';
+			*history++ = (c & 7) + '0';
+		} else
+			*history++ = c;
+	}
+	/* Append current time in "(YYYY/MM/DD-hh:mm:ss)" format. */
+	second = now % 60;
+	now /= 60;
+	minute = now % 60;
+	now /= 60;
+	hour = now % 24;
+	day = now / 24;
+	if (day >= 16071) {
+		/* Start from 2014/01/01 rather than 1970/01/01. */
+		day -= 16071;
+		year += 44;
+	}
+	while (1) {
+		const u16 days = (year & 3) ? 365 : 366;
+
+		if (day < days)
+			break;
+		day -= days;
+		year++;
+	}
+	r = (year & 3) == 0;
+	for (month = 0; month < 11 && day >= eom[r][month]; month++)
+		;
+	if (month)
+		day -= eom[r][month - 1];
+	sprintf(history, "(%04u/%02u/%02u-%02u:%02u:%02u)", year, month + 1,
+		day + 1, hour, minute, second);
+}
+
 /* Initialize audit support at boot time. */
 static int __init audit_init(void)
 {
 	int i;
 
+	audit_update_history();
 	if (audit_initialized == AUDIT_DISABLED)
 		return 0;
 
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 21eae3c..4172633 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -950,6 +950,11 @@ int audit_alloc(struct task_struct *tsk)
 	enum audit_state     state;
 	char *key = NULL;
 
+	tsk->comm_history = kmemdup(current->comm_history, COMM_HISTORY_SIZE,
+				    GFP_KERNEL);
+	if (!tsk->comm_history)
+		return -ENOMEM;
+
 	if (likely(!audit_ever_enabled))
 		return 0; /* Return if not auditing. */
 
@@ -960,6 +965,8 @@ int audit_alloc(struct task_struct *tsk)
 	}
 
 	if (!(context = audit_alloc_context(state))) {
+		kfree(tsk->comm_history);
+		tsk->comm_history = NULL;
 		kfree(key);
 		audit_log_lost("out of memory in audit_alloc");
 		return -ENOMEM;
@@ -1349,6 +1356,17 @@ out:
 	audit_log_end(ab);
 }
 
+static void audit_log_history(struct audit_context *context)
+{
+	struct audit_buffer *ab;
+
+	ab = audit_log_start(context, GFP_KERNEL, AUDIT_PROCHISTORY);
+	if (!ab)
+		return;	/* audit_panic or being filtered */
+	audit_log_format(ab, "history='%s'", current->comm_history);
+	audit_log_end(ab);
+}
+
 static void audit_log_exit(struct audit_context *context, struct task_struct *tsk)
 {
 	int i, call_panic = 0;
@@ -1467,6 +1485,7 @@ static void audit_log_exit(struct audit_context *context, struct task_struct *ts
 	}
 
 	audit_log_proctitle(tsk, context);
+	audit_log_history(context);
 
 	/* Send end of event record to help user space know we are finished */
 	ab = audit_log_start(context, GFP_KERNEL, AUDIT_EOE);
-- 
1.7.1




More information about the Linux-audit mailing list