Discussion:
[PATCH] TaskTracker : Simplified thread information tracker.
Tetsuo Handa
2014-05-23 12:44:43 UTC
Permalink
Yesterday I went to LinuxCon Japan 2014 and stopped at Red Hat's booth
and Oracle's booth. I explained about this module ( using page 92 of
http://I-love.SAKURA.ne.jp/tomoyo/LCJ2014-en.pdf ) and got positive
responses from persons who have experienced troubleshooting jobs.
I was convinced that I am not the only person who is bothered by lack of
process history information in the logs. Therefore, I repost this module
toward inclusion into mainline Linux kernel.

Changes from previous version ( http://lwn.net/Articles/575044/ ):

(1) Assign a value to "u32 *seclen" in addition to "char *secdata"
at security_task_getsecid() hook.

(2) Make calculation of time stamp a bit faster.

Background:

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.

I sometimes receive "which application triggered the event" questions on RHEL
systems. TOMOYO security module can track how the program was executed, but
TOMOYO is not yet available in Fedora/RHEL distributions.

Although subj= field is added to the audit log if SELinux is not disabled,
SELinux is too difficult to customize as fine grained as I expect in order to
reflect how the program was executed. Therefore, I'm currently using AKARI
and SystemTap for emulating TOMOYO-like tracing.

But AKARI and SystemTap do not help unless the kernel module is loaded before
the unexpected system event occurs. Generally, the administrator is failing
to record the first event, and has to wait for the same event to occur again
after loading the kernel module and/or configuring auditing. I came to think
that we want a built-in kernel routine which is automatically started upon
boot so that we don't fail to record the first event.

What I did:

Assuming that multiple concurrent LSM support comes in the near future,
I wrote a trivial LSM module which emits TOMOYO-like information into the
audit logs.

Usage:

Just register this LSM module. No configuration is needed. You will get
history of current thread in the form of comm name and time stamp pairs
in the subj= field of audit logs like examples shown in the patch
description.
----------
From ff68d3a4cd496bd263d2939848777fffc30cbc0b Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-***@I-love.SAKURA.ne.jp>
Date: Fri, 23 May 2014 21:31:56 +0900
Subject: [PATCH] TaskTracker : Simplified thread information tracker.

Existing audit logs generated via system call auditing functionality include
current thread's comm name. But it is not always sufficient for identifying
which application has requested specific operations because comm name does not
reflect history of current thread.

This security module adds functionality for adding current thread's history
information like TOMOYO security module does, expecting that this module can
help us getting more information from system call auditing functionality.

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'

type=SYSCALL msg=audit(1400880014.444:26): arch=40000003 syscall=11
success=yes exit=0 a0=8140f78 a1=812b7d8 a2=812b248 a3=812b7d8 items=2
ppid=4323 pid=4355 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0
fsgid=0 tty=pts0 ses=1 comm="tail" exe="/usr/bin/tail"
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:37)=>rc(2014/05/23-21:17:37)=>
S55sshd(2014/05/23-21:17:53)=>sshd(2014/05/23-21:17:53)=>
sshd(2014/05/23-21:18:17)=>bash(2014/05/23-21:18:21)=>
tail(2014/05/23-21:20:14)" key=(null)

Signed-off-by: Tetsuo Handa <penguin-***@I-love.SAKURA.ne.jp>
---
security/Kconfig | 6 +
security/Makefile | 2 +
security/tasktracker/Kconfig | 35 +++++
security/tasktracker/Makefile | 1 +
security/tasktracker/tasktracker.c | 282 ++++++++++++++++++++++++++++++++++++
5 files changed, 326 insertions(+), 0 deletions(-)
create mode 100644 security/tasktracker/Kconfig
create mode 100644 security/tasktracker/Makefile
create mode 100644 security/tasktracker/tasktracker.c

diff --git a/security/Kconfig b/security/Kconfig
index beb86b5..14e7d27 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -122,6 +122,7 @@ source security/smack/Kconfig
source security/tomoyo/Kconfig
source security/apparmor/Kconfig
source security/yama/Kconfig
+source security/tasktracker/Kconfig

source security/integrity/Kconfig

@@ -132,6 +133,7 @@ choice
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
default DEFAULT_SECURITY_YAMA if SECURITY_YAMA
+ default DEFAULT_SECURITY_TT if SECURITY_TT
default DEFAULT_SECURITY_DAC

help
@@ -153,6 +155,9 @@ choice
config DEFAULT_SECURITY_YAMA
bool "Yama" if SECURITY_YAMA=y

+ config DEFAULT_SECURITY_TT
+ bool "TaskTracker" if SECURITY_TT=y
+
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"

@@ -165,6 +170,7 @@ config DEFAULT_SECURITY
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
default "apparmor" if DEFAULT_SECURITY_APPARMOR
default "yama" if DEFAULT_SECURITY_YAMA
+ default "tt" if DEFAULT_SECURITY_TT
default "" if DEFAULT_SECURITY_DAC

endmenu
diff --git a/security/Makefile b/security/Makefile
index 05f1c93..28a90ed 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
+subdir-$(CONFIG_SECURITY_TT) += tasktracker

# always enable default capabilities
obj-y += commoncap.o
@@ -22,6 +23,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
+obj-$(CONFIG_SECURITY_TT) += tasktracker/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o

# Object integrity file lists
diff --git a/security/tasktracker/Kconfig b/security/tasktracker/Kconfig
new file mode 100644
index 0000000..6de5354
--- /dev/null
+++ b/security/tasktracker/Kconfig
@@ -0,0 +1,35 @@
+config SECURITY_TT
+ bool "TaskTracker support"
+ depends on SECURITY
+ default n
+ help
+ Existing audit logs generated via system call auditing functionality
+ include current thread's comm name. But it is not always sufficient
+ for identifying which application has requested specific operations
+ because comm name does not reflect history of current thread.
+
+ This security module adds functionality for adding current thread's
+ history information like TOMOYO security module does, expecting that
+ this module can help us getting more information from system call
+ auditing functionality.
+
+ If you are unsure how to answer this question, answer N.
+
+ Usage:
+
+ Just register this module. No configuration is needed.
+
+ You will get history of current thread in the form of
+ comm name and time stamp pairs in the subj= field of audit logs
+ like an example shown below.
+
+ type=SYSCALL msg=audit(1400880014.444:26): arch=40000003 syscall=11
+ success=yes exit=0 a0=8140f78 a1=812b7d8 a2=812b248 a3=812b7d8
+ items=2 ppid=4323 pid=4355 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0
+ egid=0 sgid=0 fsgid=0 tty=pts0 ses=1 comm="tail"
+ exe="/usr/bin/tail" 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:37)=>
+ rc(2014/05/23-21:17:37)=>S55sshd(2014/05/23-21:17:53)=>
+ sshd(2014/05/23-21:17:53)=>sshd(2014/05/23-21:18:17)=>
+ bash(2014/05/23-21:18:21)=>tail(2014/05/23-21:20:14)" key=(null)
diff --git a/security/tasktracker/Makefile b/security/tasktracker/Makefile
new file mode 100644
index 0000000..15d03ce
--- /dev/null
+++ b/security/tasktracker/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SECURITY_TT) := tasktracker.o
diff --git a/security/tasktracker/tasktracker.c b/security/tasktracker/tasktracker.c
new file mode 100644
index 0000000..ec4eb0c
--- /dev/null
+++ b/security/tasktracker/tasktracker.c
@@ -0,0 +1,282 @@
+/*
+ * tasktracker.c - Simplified thread information tracker.
+ *
+ * Copyright (C) 2010-2014 Tetsuo Handa <penguin-***@I-love.SAKURA.ne.jp>
+ */
+#include <linux/security.h>
+#include <linux/binfmts.h>
+
+/* Wrapper structure for passing string buffer. */
+struct tt_record {
+ char history[1024];
+};
+
+/* Structure for representing YYYY/MM/DD hh/mm/ss. */
+struct tt_time {
+ u16 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 min;
+ u8 sec;
+};
+
+/**
+ * tt_get_time - Get current time in YYYY/MM/DD hh/mm/ss format.
+ *
+ * @stamp: Pointer to "struct tt_time".
+ *
+ * Returns nothing.
+ *
+ * This function does not handle Y2038 problem.
+ */
+static void tt_get_time(struct tt_time *stamp)
+{
+ struct timeval tv;
+ static const u16 tt_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 y = 1970;
+ u8 m;
+ bool r;
+ time_t time;
+ do_gettimeofday(&tv);
+ time = tv.tv_sec;
+ stamp->sec = time % 60;
+ time /= 60;
+ stamp->min = time % 60;
+ time /= 60;
+ stamp->hour = time % 24;
+ time /= 24;
+ if (time >= 16071) {
+ /* Start from 2014/01/01 rather than 1970/01/01. */
+ time -= 16071;
+ y += 44;
+ }
+ while (1) {
+ const unsigned short days = (y & 3) ? 365 : 366;
+ if (time < days)
+ break;
+ time -= days;
+ y++;
+ }
+ r = (y & 3) == 0;
+ for (m = 0; m < 11 && time >= tt_eom[r][m]; m++)
+ ;
+ if (m)
+ time -= tt_eom[r][m - 1];
+ stamp->year = y;
+ stamp->month = ++m;
+ stamp->day = ++time;
+}
+
+/**
+ * tt_update_record - Update "struct tt_record" for given credential.
+ *
+ * @record: Pointer to "struct tt_record".
+ *
+ * Returns nothing.
+ */
+static void tt_update_record(struct tt_record *record)
+{
+ char *cp;
+ int i;
+ struct tt_time stamp;
+ tt_get_time(&stamp);
+ /*
+ * Lockless update because current thread's record is not concurrently
+ * accessible, for "struct cred"->security is not visible from other
+ * threads because this function is called upon only boot up and
+ * successful execve() operation.
+ */
+ cp = record->history;
+ i = strlen(cp);
+ while (i >= sizeof(record->history) - (TASK_COMM_LEN * 4 + 30)) {
+ /*
+ * Since this record is not for making security decision,
+ * I don't care by-chance matching "=>" in task's commname.
+ */
+ char *cp2 = strstr(cp + 2, "=>");
+ if (!cp2)
+ return;
+ memmove(cp + 1, cp2, strlen(cp2) + 1);
+ i = strlen(cp);
+ }
+ if (!i)
+ *cp++ = '"';
+ else {
+ cp += i - 1;
+ *cp++ = '=';
+ *cp++ = '>';
+ }
+ /*
+ * Lockless read because this is current thread and being unexpectedly
+ * modified by other thread is not a fatal problem.
+ */
+ for (i = 0; i < TASK_COMM_LEN; i++) {
+ const unsigned char c = current->comm[i];
+ if (!c)
+ break;
+ else if (c == '"' || c == '\\' || c < 0x21 || c > 0x7e) {
+ *cp++ = '\\';
+ *cp++ = (c >> 6) + '0';
+ *cp++ = ((c >> 3) & 7) + '0';
+ *cp++ = (c & 7) + '0';
+ } else
+ *cp++ = c;
+ }
+ sprintf(cp, "(%04u/%02u/%02u-%02u:%02u:%02u)\"", stamp.year,
+ stamp.month, stamp.day, stamp.hour, stamp.min, stamp.sec);
+}
+
+/**
+ * tt_find_record - Find "struct tt_record" for given credential.
+ *
+ * @cred: Pointer to "struct cred".
+ *
+ * Returns pointer to "struct tt_record".
+ */
+static inline struct tt_record *tt_find_record(const struct cred *cred)
+{
+ return cred->security;
+}
+
+/**
+ * tt_cred_alloc_blank - Allocate memory for new credentials.
+ *
+ * @new: Pointer to "struct cred".
+ * @gfp: Memory allocation flags.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tt_cred_alloc_blank(struct cred *new, gfp_t gfp)
+{
+ new->security = kzalloc(sizeof(struct tt_record), gfp);
+ return new->security ? 0 : -ENOMEM;
+}
+
+/**
+ * tt_cred_prepare - Allocate memory for new credentials.
+ *
+ * @new: Pointer to "struct cred".
+ * @old: Pointer to "struct cred".
+ * @gfp: Memory allocation flags.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tt_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{
+ if (tt_cred_alloc_blank(new, gfp))
+ return -ENOMEM;
+ strcpy(tt_find_record(new)->history, tt_find_record(old)->history);
+ return 0;
+}
+
+/**
+ * tt_cred_free - Release memory used by credentials.
+ *
+ * @cred: Pointer to "struct cred".
+ *
+ * Returns nothing.
+ */
+static void tt_cred_free(struct cred *cred)
+{
+ kfree(cred->security);
+}
+
+/**
+ * tt_cred_transfer - Transfer "struct tt_record" between credentials.
+ *
+ * @new: Pointer to "struct cred".
+ * @old: Pointer to "struct cred".
+ *
+ * Returns nothing.
+ */
+static void tt_cred_transfer(struct cred *new, const struct cred *old)
+{
+ strcpy(tt_find_record(new)->history, tt_find_record(old)->history);
+}
+
+/**
+ * tt_bprm_committing_creds - A hook which is called when do_execve() succeeded.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns nothing.
+ */
+static void tt_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ tt_update_record(tt_find_record(bprm->cred));
+}
+
+/**
+ * tt_task_getsecid - Check whether to audit or not.
+ *
+ * @p: Pointer to "struct task_struct".
+ * @secid: Pointer to flag.
+ */
+static void tt_task_getsecid(struct task_struct *p, u32 *secid)
+{
+ *secid = (p == current);
+}
+
+/**
+ * tt_secid_to_secctx - Allocate memory used for auditing.
+ *
+ * @secid: Bool flag to allocate.
+ * @secdata: Pointer to allocate memory.
+ * @seclen: Size of allocated memory.
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int tt_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
+{
+ struct tt_record *record;
+ /* Ignore unless current thread's record is requested. */
+ if (secid != 1)
+ return -EINVAL;
+ /*
+ * We don't need to duplicate the string because current thread's
+ * record is updated upon only boot up and successful execve()
+ * operation, even if current thread's record is shared between
+ * multiple threads.
+ */
+ record = tt_find_record(current->real_cred);
+ *secdata = record->history;
+ *seclen = strlen(record->history);
+ return 0;
+}
+
+/* List of hooks. */
+static struct security_operations tasktracker_ops = {
+ .name = "tt",
+ .secid_to_secctx = tt_secid_to_secctx,
+ .task_getsecid = tt_task_getsecid,
+ .cred_prepare = tt_cred_prepare,
+ .cred_free = tt_cred_free,
+ .cred_alloc_blank = tt_cred_alloc_blank,
+ .cred_transfer = tt_cred_transfer,
+ .bprm_committing_creds = tt_bprm_committing_creds,
+};
+
+/**
+ * tt_init - Initialize this module.
+ *
+ * Returns 0 on success, panic otherwise.
+ */
+static int __init tt_init(void)
+{
+ struct cred *cred = (struct cred *) current_cred();
+ if (!security_module_enable(&tasktracker_ops))
+ return 0;
+ if (tt_cred_alloc_blank(cred, GFP_ATOMIC) ||
+ register_security(&tasktracker_ops))
+ panic("Failure registering TaskTracker");
+ tt_update_record(tt_find_record(cred));
+ pr_info("TaskTracker initialized\n");
+ return 0;
+}
+
+security_initcall(tt_init);
--
1.7.1
Tetsuo Handa
2014-06-23 12:14:35 UTC
Permalink
Any comments on this proposal?

For now this module is occupying the exclusive LSM hook. But since this
module is for tracking per a "struct task_struct" attributes rather than
for making security decisions, I expect that this module can co-exist with
other LSM modules by not occupying the exclusive LSM hook.
Post by Tetsuo Handa
Yesterday I went to LinuxCon Japan 2014 and stopped at Red Hat's booth
and Oracle's booth. I explained about this module ( using page 92 of
http://I-love.SAKURA.ne.jp/tomoyo/LCJ2014-en.pdf ) and got positive
responses from persons who have experienced troubleshooting jobs.
I was convinced that I am not the only person who is bothered by lack of
process history information in the logs. Therefore, I repost this module
toward inclusion into mainline Linux kernel.
(1) Assign a value to "u32 *seclen" in addition to "char *secdata"
at security_task_getsecid() hook.
(2) Make calculation of time stamp a bit faster.
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.
I sometimes receive "which application triggered the event" questions on RHEL
systems. TOMOYO security module can track how the program was executed, but
TOMOYO is not yet available in Fedora/RHEL distributions.
Although subj= field is added to the audit log if SELinux is not disabled,
SELinux is too difficult to customize as fine grained as I expect in order to
reflect how the program was executed. Therefore, I'm currently using AKARI
and SystemTap for emulating TOMOYO-like tracing.
But AKARI and SystemTap do not help unless the kernel module is loaded before
the unexpected system event occurs. Generally, the administrator is failing
to record the first event, and has to wait for the same event to occur again
after loading the kernel module and/or configuring auditing. I came to think
that we want a built-in kernel routine which is automatically started upon
boot so that we don't fail to record the first event.
Assuming that multiple concurrent LSM support comes in the near future,
I wrote a trivial LSM module which emits TOMOYO-like information into the
audit logs.
Just register this LSM module. No configuration is needed. You will get
history of current thread in the form of comm name and time stamp pairs
in the subj= field of audit logs like examples shown in the patch
description.
----------
From ff68d3a4cd496bd263d2939848777fffc30cbc0b Mon Sep 17 00:00:00 2001
Date: Fri, 23 May 2014 21:31:56 +0900
Subject: [PATCH] TaskTracker : Simplified thread information tracker.
Existing audit logs generated via system call auditing functionality include
current thread's comm name. But it is not always sufficient for identifying
which application has requested specific operations because comm name does not
reflect history of current thread.
This security module adds functionality for adding current thread's history
information like TOMOYO security module does, expecting that this module can
help us getting more information from system call auditing functionality.
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'
type=SYSCALL msg=audit(1400880014.444:26): arch=40000003 syscall=11
success=yes exit=0 a0=8140f78 a1=812b7d8 a2=812b248 a3=812b7d8 items=2
ppid=4323 pid=4355 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0
fsgid=0 tty=pts0 ses=1 comm="tail" exe="/usr/bin/tail"
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:37)=>rc(2014/05/23-21:17:37)=>
S55sshd(2014/05/23-21:17:53)=>sshd(2014/05/23-21:17:53)=>
sshd(2014/05/23-21:18:17)=>bash(2014/05/23-21:18:21)=>
tail(2014/05/23-21:20:14)" key=(null)
---
security/Kconfig | 6 +
security/Makefile | 2 +
security/tasktracker/Kconfig | 35 +++++
security/tasktracker/Makefile | 1 +
security/tasktracker/tasktracker.c | 282 ++++++++++++++++++++++++++++++++++++
5 files changed, 326 insertions(+), 0 deletions(-)
create mode 100644 security/tasktracker/Kconfig
create mode 100644 security/tasktracker/Makefile
create mode 100644 security/tasktracker/tasktracker.c
diff --git a/security/Kconfig b/security/Kconfig
index beb86b5..14e7d27 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -122,6 +122,7 @@ source security/smack/Kconfig
source security/tomoyo/Kconfig
source security/apparmor/Kconfig
source security/yama/Kconfig
+source security/tasktracker/Kconfig
source security/integrity/Kconfig
@@ -132,6 +133,7 @@ choice
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
default DEFAULT_SECURITY_YAMA if SECURITY_YAMA
+ default DEFAULT_SECURITY_TT if SECURITY_TT
default DEFAULT_SECURITY_DAC
help
@@ -153,6 +155,9 @@ choice
config DEFAULT_SECURITY_YAMA
bool "Yama" if SECURITY_YAMA=y
+ config DEFAULT_SECURITY_TT
+ bool "TaskTracker" if SECURITY_TT=y
+
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"
@@ -165,6 +170,7 @@ config DEFAULT_SECURITY
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
default "apparmor" if DEFAULT_SECURITY_APPARMOR
default "yama" if DEFAULT_SECURITY_YAMA
+ default "tt" if DEFAULT_SECURITY_TT
default "" if DEFAULT_SECURITY_DAC
endmenu
diff --git a/security/Makefile b/security/Makefile
index 05f1c93..28a90ed 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
+subdir-$(CONFIG_SECURITY_TT) += tasktracker
# always enable default capabilities
obj-y += commoncap.o
@@ -22,6 +23,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
+obj-$(CONFIG_SECURITY_TT) += tasktracker/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
# Object integrity file lists
diff --git a/security/tasktracker/Kconfig b/security/tasktracker/Kconfig
new file mode 100644
index 0000000..6de5354
--- /dev/null
+++ b/security/tasktracker/Kconfig
@@ -0,0 +1,35 @@
+config SECURITY_TT
+ bool "TaskTracker support"
+ depends on SECURITY
+ default n
+ help
+ Existing audit logs generated via system call auditing functionality
+ include current thread's comm name. But it is not always sufficient
+ for identifying which application has requested specific operations
+ because comm name does not reflect history of current thread.
+
+ This security module adds functionality for adding current thread's
+ history information like TOMOYO security module does, expecting that
+ this module can help us getting more information from system call
+ auditing functionality.
+
+ If you are unsure how to answer this question, answer N.
+
+
+ Just register this module. No configuration is needed.
+
+ You will get history of current thread in the form of
+ comm name and time stamp pairs in the subj= field of audit logs
+ like an example shown below.
+
+ type=SYSCALL msg=audit(1400880014.444:26): arch=40000003 syscall=11
+ success=yes exit=0 a0=8140f78 a1=812b7d8 a2=812b248 a3=812b7d8
+ items=2 ppid=4323 pid=4355 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0
+ egid=0 sgid=0 fsgid=0 tty=pts0 ses=1 comm="tail"
+ exe="/usr/bin/tail" 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:37)=>
+ rc(2014/05/23-21:17:37)=>S55sshd(2014/05/23-21:17:53)=>
+ sshd(2014/05/23-21:17:53)=>sshd(2014/05/23-21:18:17)=>
+ bash(2014/05/23-21:18:21)=>tail(2014/05/23-21:20:14)" key=(null)
diff --git a/security/tasktracker/Makefile b/security/tasktracker/Makefile
new file mode 100644
index 0000000..15d03ce
--- /dev/null
+++ b/security/tasktracker/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SECURITY_TT) := tasktracker.o
diff --git a/security/tasktracker/tasktracker.c b/security/tasktracker/tasktracker.c
new file mode 100644
index 0000000..ec4eb0c
--- /dev/null
+++ b/security/tasktracker/tasktracker.c
@@ -0,0 +1,282 @@
+/*
+ * tasktracker.c - Simplified thread information tracker.
+ *
+ */
+#include <linux/security.h>
+#include <linux/binfmts.h>
+
+/* Wrapper structure for passing string buffer. */
+struct tt_record {
+ char history[1024];
+};
+
+/* Structure for representing YYYY/MM/DD hh/mm/ss. */
+struct tt_time {
+ u16 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 min;
+ u8 sec;
+};
+
+/**
+ * tt_get_time - Get current time in YYYY/MM/DD hh/mm/ss format.
+ *
+ *
+ * Returns nothing.
+ *
+ * This function does not handle Y2038 problem.
+ */
+static void tt_get_time(struct tt_time *stamp)
+{
+ struct timeval tv;
+ static const u16 tt_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 y = 1970;
+ u8 m;
+ bool r;
+ time_t time;
+ do_gettimeofday(&tv);
+ time = tv.tv_sec;
+ stamp->sec = time % 60;
+ time /= 60;
+ stamp->min = time % 60;
+ time /= 60;
+ stamp->hour = time % 24;
+ time /= 24;
+ if (time >= 16071) {
+ /* Start from 2014/01/01 rather than 1970/01/01. */
+ time -= 16071;
+ y += 44;
+ }
+ while (1) {
+ const unsigned short days = (y & 3) ? 365 : 366;
+ if (time < days)
+ break;
+ time -= days;
+ y++;
+ }
+ r = (y & 3) == 0;
+ for (m = 0; m < 11 && time >= tt_eom[r][m]; m++)
+ ;
+ if (m)
+ time -= tt_eom[r][m - 1];
+ stamp->year = y;
+ stamp->month = ++m;
+ stamp->day = ++time;
+}
+
+/**
+ * tt_update_record - Update "struct tt_record" for given credential.
+ *
+ *
+ * Returns nothing.
+ */
+static void tt_update_record(struct tt_record *record)
+{
+ char *cp;
+ int i;
+ struct tt_time stamp;
+ tt_get_time(&stamp);
+ /*
+ * Lockless update because current thread's record is not concurrently
+ * accessible, for "struct cred"->security is not visible from other
+ * threads because this function is called upon only boot up and
+ * successful execve() operation.
+ */
+ cp = record->history;
+ i = strlen(cp);
+ while (i >= sizeof(record->history) - (TASK_COMM_LEN * 4 + 30)) {
+ /*
+ * Since this record is not for making security decision,
+ * I don't care by-chance matching "=>" in task's commname.
+ */
+ char *cp2 = strstr(cp + 2, "=>");
+ if (!cp2)
+ return;
+ memmove(cp + 1, cp2, strlen(cp2) + 1);
+ i = strlen(cp);
+ }
+ if (!i)
+ *cp++ = '"';
+ else {
+ cp += i - 1;
+ *cp++ = '=';
+ *cp++ = '>';
+ }
+ /*
+ * Lockless read because this is current thread and being unexpectedly
+ * modified by other thread is not a fatal problem.
+ */
+ for (i = 0; i < TASK_COMM_LEN; i++) {
+ const unsigned char c = current->comm[i];
+ if (!c)
+ break;
+ else if (c == '"' || c == '\\' || c < 0x21 || c > 0x7e) {
+ *cp++ = '\\';
+ *cp++ = (c >> 6) + '0';
+ *cp++ = ((c >> 3) & 7) + '0';
+ *cp++ = (c & 7) + '0';
+ } else
+ *cp++ = c;
+ }
+ sprintf(cp, "(%04u/%02u/%02u-%02u:%02u:%02u)\"", stamp.year,
+ stamp.month, stamp.day, stamp.hour, stamp.min, stamp.sec);
+}
+
+/**
+ * tt_find_record - Find "struct tt_record" for given credential.
+ *
+ *
+ * Returns pointer to "struct tt_record".
+ */
+static inline struct tt_record *tt_find_record(const struct cred *cred)
+{
+ return cred->security;
+}
+
+/**
+ * tt_cred_alloc_blank - Allocate memory for new credentials.
+ *
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tt_cred_alloc_blank(struct cred *new, gfp_t gfp)
+{
+ new->security = kzalloc(sizeof(struct tt_record), gfp);
+ return new->security ? 0 : -ENOMEM;
+}
+
+/**
+ * tt_cred_prepare - Allocate memory for new credentials.
+ *
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tt_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{
+ if (tt_cred_alloc_blank(new, gfp))
+ return -ENOMEM;
+ strcpy(tt_find_record(new)->history, tt_find_record(old)->history);
+ return 0;
+}
+
+/**
+ * tt_cred_free - Release memory used by credentials.
+ *
+ *
+ * Returns nothing.
+ */
+static void tt_cred_free(struct cred *cred)
+{
+ kfree(cred->security);
+}
+
+/**
+ * tt_cred_transfer - Transfer "struct tt_record" between credentials.
+ *
+ *
+ * Returns nothing.
+ */
+static void tt_cred_transfer(struct cred *new, const struct cred *old)
+{
+ strcpy(tt_find_record(new)->history, tt_find_record(old)->history);
+}
+
+/**
+ * tt_bprm_committing_creds - A hook which is called when do_execve() succeeded.
+ *
+ *
+ * Returns nothing.
+ */
+static void tt_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ tt_update_record(tt_find_record(bprm->cred));
+}
+
+/**
+ * tt_task_getsecid - Check whether to audit or not.
+ *
+ */
+static void tt_task_getsecid(struct task_struct *p, u32 *secid)
+{
+ *secid = (p == current);
+}
+
+/**
+ * tt_secid_to_secctx - Allocate memory used for auditing.
+ *
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int tt_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
+{
+ struct tt_record *record;
+ /* Ignore unless current thread's record is requested. */
+ if (secid != 1)
+ return -EINVAL;
+ /*
+ * We don't need to duplicate the string because current thread's
+ * record is updated upon only boot up and successful execve()
+ * operation, even if current thread's record is shared between
+ * multiple threads.
+ */
+ record = tt_find_record(current->real_cred);
+ *secdata = record->history;
+ *seclen = strlen(record->history);
+ return 0;
+}
+
+/* List of hooks. */
+static struct security_operations tasktracker_ops = {
+ .name = "tt",
+ .secid_to_secctx = tt_secid_to_secctx,
+ .task_getsecid = tt_task_getsecid,
+ .cred_prepare = tt_cred_prepare,
+ .cred_free = tt_cred_free,
+ .cred_alloc_blank = tt_cred_alloc_blank,
+ .cred_transfer = tt_cred_transfer,
+ .bprm_committing_creds = tt_bprm_committing_creds,
+};
+
+/**
+ * tt_init - Initialize this module.
+ *
+ * Returns 0 on success, panic otherwise.
+ */
+static int __init tt_init(void)
+{
+ struct cred *cred = (struct cred *) current_cred();
+ if (!security_module_enable(&tasktracker_ops))
+ return 0;
+ if (tt_cred_alloc_blank(cred, GFP_ATOMIC) ||
+ register_security(&tasktracker_ops))
+ panic("Failure registering TaskTracker");
+ tt_update_record(tt_find_record(cred));
+ pr_info("TaskTracker initialized\n");
+ return 0;
+}
+
+security_initcall(tt_init);
--
1.7.1
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Steve Grubb
2014-06-24 14:00:25 UTC
Permalink
Post by Tetsuo Handa
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.

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?

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?

-Steve
Post by Tetsuo Handa
For now this module is occupying the exclusive LSM hook. But since this
module is for tracking per a "struct task_struct" attributes rather than
for making security decisions, I expect that this module can co-exist with
other LSM modules by not occupying the exclusive LSM hook.
Post by Tetsuo Handa
Yesterday I went to LinuxCon Japan 2014 and stopped at Red Hat's booth
and Oracle's booth. I explained about this module ( using page 92 of
http://I-love.SAKURA.ne.jp/tomoyo/LCJ2014-en.pdf ) and got positive
responses from persons who have experienced troubleshooting jobs.
I was convinced that I am not the only person who is bothered by lack of
process history information in the logs. Therefore, I repost this module
toward inclusion into mainline Linux kernel.
(1) Assign a value to "u32 *seclen" in addition to "char *secdata"
at security_task_getsecid() hook.
(2) Make calculation of time stamp a bit faster.
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.
I sometimes receive "which application triggered the event" questions on
RHEL systems. TOMOYO security module can track how the program was
executed, but TOMOYO is not yet available in Fedora/RHEL distributions.
Although subj= field is added to the audit log if SELinux is not disabled,
SELinux is too difficult to customize as fine grained as I expect in
order to reflect how the program was executed. Therefore, I'm currently
using AKARI and SystemTap for emulating TOMOYO-like tracing.
But AKARI and SystemTap do not help unless the kernel module is loaded
before the unexpected system event occurs. Generally, the administrator
is failing to record the first event, and has to wait for the same
event to occur again after loading the kernel module and/or configuring
auditing. I came to think that we want a built-in kernel routine which
is automatically started upon boot so that we don't fail to record the
first event.
Assuming that multiple concurrent LSM support comes in the near future,
I wrote a trivial LSM module which emits TOMOYO-like information into the
audit logs.
Just register this LSM module. No configuration is needed. You will get
history of current thread in the form of comm name and time stamp pairs
in the subj= field of audit logs like examples shown in the patch
description.
----------
From ff68d3a4cd496bd263d2939848777fffc30cbc0b Mon Sep 17 00:00:00 2001
Date: Fri, 23 May 2014 21:31:56 +0900
Subject: [PATCH] TaskTracker : Simplified thread information tracker.
Existing audit logs generated via system call auditing functionality
include current thread's comm name. But it is not always sufficient for
identifying which application has requested specific operations because
comm name does not reflect history of current thread.
This security module adds functionality for adding current thread's history
information like TOMOYO security module does, expecting that this module
can help us getting more information from system call auditing
functionality.>
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'
type=SYSCALL msg=audit(1400880014.444:26): arch=40000003 syscall=11
success=yes exit=0 a0=8140f78 a1=812b7d8 a2=812b248 a3=812b7d8 items=2
ppid=4323 pid=4355 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0
fsgid=0 tty=pts0 ses=1 comm="tail" exe="/usr/bin/tail"
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:37)=>rc(2014/05/23-21:17:37)=>
S55sshd(2014/05/23-21:17:53)=>sshd(2014/05/23-21:17:53)=>
sshd(2014/05/23-21:18:17)=>bash(2014/05/23-21:18:21)=>
tail(2014/05/23-21:20:14)" key=(null)
---
security/Kconfig | 6 +
security/Makefile | 2 +
security/tasktracker/Kconfig | 35 +++++
security/tasktracker/Makefile | 1 +
security/tasktracker/tasktracker.c | 282
++++++++++++++++++++++++++++++++++++ 5 files changed, 326 insertions(+),
0 deletions(-)
create mode 100644 security/tasktracker/Kconfig
create mode 100644 security/tasktracker/Makefile
create mode 100644 security/tasktracker/tasktracker.c
diff --git a/security/Kconfig b/security/Kconfig
index beb86b5..14e7d27 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -122,6 +122,7 @@ source security/smack/Kconfig
source security/tomoyo/Kconfig
source security/apparmor/Kconfig
source security/yama/Kconfig
+source security/tasktracker/Kconfig
source security/integrity/Kconfig
@@ -132,6 +133,7 @@ choice
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
default DEFAULT_SECURITY_YAMA if SECURITY_YAMA
+ default DEFAULT_SECURITY_TT if SECURITY_TT
default DEFAULT_SECURITY_DAC
help
@@ -153,6 +155,9 @@ choice
config DEFAULT_SECURITY_YAMA
bool "Yama" if SECURITY_YAMA=y
+ config DEFAULT_SECURITY_TT
+ bool "TaskTracker" if SECURITY_TT=y
+
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"
@@ -165,6 +170,7 @@ config DEFAULT_SECURITY
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
default "apparmor" if DEFAULT_SECURITY_APPARMOR
default "yama" if DEFAULT_SECURITY_YAMA
+ default "tt" if DEFAULT_SECURITY_TT
default "" if DEFAULT_SECURITY_DAC
endmenu
diff --git a/security/Makefile b/security/Makefile
index 05f1c93..28a90ed 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
+subdir-$(CONFIG_SECURITY_TT) += tasktracker
# always enable default capabilities
obj-y += commoncap.o
@@ -22,6 +23,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
+obj-$(CONFIG_SECURITY_TT) += tasktracker/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
# Object integrity file lists
diff --git a/security/tasktracker/Kconfig b/security/tasktracker/Kconfig
new file mode 100644
index 0000000..6de5354
--- /dev/null
+++ b/security/tasktracker/Kconfig
@@ -0,0 +1,35 @@
+config SECURITY_TT
+ bool "TaskTracker support"
+ depends on SECURITY
+ default n
+ help
+ Existing audit logs generated via system call auditing
functionality
Post by Tetsuo Handa
Post by Tetsuo Handa
+ include current thread's comm name. But it is not always sufficient
+ for identifying which application has requested specific operations
+ because comm name does not reflect history of current thread.
+
+ This security module adds functionality for adding current thread's
+ history information like TOMOYO security module does, expecting that
+ this module can help us getting more information from system call
+ auditing functionality.
+
+ If you are unsure how to answer this question, answer N.
+
+
+ Just register this module. No configuration is needed.
+
+ You will get history of current thread in the form of
+ comm name and time stamp pairs in the subj= field of audit logs
+ like an example shown below.
+
+ type=SYSCALL msg=audit(1400880014.444:26): arch=40000003 syscall=11
+ success=yes exit=0 a0=8140f78 a1=812b7d8 a2=812b248 a3=812b7d8
+ items=2 ppid=4323 pid=4355 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0
+ egid=0 sgid=0 fsgid=0 tty=pts0 ses=1 comm="tail"
+ exe="/usr/bin/tail" 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:37)=>
+ rc(2014/05/23-21:17:37)=>S55sshd(2014/05/23-21:17:53)=>
+ sshd(2014/05/23-21:17:53)=>sshd(2014/05/23-21:18:17)=>
+ bash(2014/05/23-21:18:21)=>tail(2014/05/23-21:20:14)" key=(null)
diff --git a/security/tasktracker/Makefile b/security/tasktracker/Makefile
new file mode 100644
index 0000000..15d03ce
--- /dev/null
+++ b/security/tasktracker/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SECURITY_TT) := tasktracker.o
diff --git a/security/tasktracker/tasktracker.c
b/security/tasktracker/tasktracker.c new file mode 100644
index 0000000..ec4eb0c
--- /dev/null
+++ b/security/tasktracker/tasktracker.c
@@ -0,0 +1,282 @@
+/*
+ * tasktracker.c - Simplified thread information tracker.
+ *
+ * Copyright (C) 2010-2014 Tetsuo Handa
+#include <linux/security.h>
+#include <linux/binfmts.h>
+
+/* Wrapper structure for passing string buffer. */
+struct tt_record {
+ char history[1024];
+};
+
+/* Structure for representing YYYY/MM/DD hh/mm/ss. */
+struct tt_time {
+ u16 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 min;
+ u8 sec;
+};
+
+/**
+ * tt_get_time - Get current time in YYYY/MM/DD hh/mm/ss format.
+ *
+ *
+ * Returns nothing.
+ *
+ * This function does not handle Y2038 problem.
+ */
+static void tt_get_time(struct tt_time *stamp)
+{
+ struct timeval tv;
+ static const u16 tt_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 y = 1970;
+ u8 m;
+ bool r;
+ time_t time;
+ do_gettimeofday(&tv);
+ time = tv.tv_sec;
+ stamp->sec = time % 60;
+ time /= 60;
+ stamp->min = time % 60;
+ time /= 60;
+ stamp->hour = time % 24;
+ time /= 24;
+ if (time >= 16071) {
+ /* Start from 2014/01/01 rather than 1970/01/01. */
+ time -= 16071;
+ y += 44;
+ }
+ while (1) {
+ const unsigned short days = (y & 3) ? 365 : 366;
+ if (time < days)
+ break;
+ time -= days;
+ y++;
+ }
+ r = (y & 3) == 0;
+ for (m = 0; m < 11 && time >= tt_eom[r][m]; m++)
+ ;
+ if (m)
+ time -= tt_eom[r][m - 1];
+ stamp->year = y;
+ stamp->month = ++m;
+ stamp->day = ++time;
+}
+
+/**
+ * tt_update_record - Update "struct tt_record" for given credential.
+ *
+ *
+ * Returns nothing.
+ */
+static void tt_update_record(struct tt_record *record)
+{
+ char *cp;
+ int i;
+ struct tt_time stamp;
+ tt_get_time(&stamp);
+ /*
+ * Lockless update because current thread's record is not
concurrently
Post by Tetsuo Handa
Post by Tetsuo Handa
+ * accessible, for "struct cred"->security is not visible from other
+ * threads because this function is called upon only boot up and
+ * successful execve() operation.
+ */
+ cp = record->history;
+ i = strlen(cp);
+ while (i >= sizeof(record->history) - (TASK_COMM_LEN * 4 + 30)) {
+ /*
+ * Since this record is not for making security decision,
+ * I don't care by-chance matching "=>" in task's commname.
+ */
+ char *cp2 = strstr(cp + 2, "=>");
+ if (!cp2)
+ return;
+ memmove(cp + 1, cp2, strlen(cp2) + 1);
+ i = strlen(cp);
+ }
+ if (!i)
+ *cp++ = '"';
+ else {
+ cp += i - 1;
+ *cp++ = '=';
+ *cp++ = '>';
+ }
+ /*
+ * Lockless read because this is current thread and being
unexpectedly
Post by Tetsuo Handa
Post by Tetsuo Handa
+ * modified by other thread is not a fatal problem.
+ */
+ for (i = 0; i < TASK_COMM_LEN; i++) {
+ const unsigned char c = current->comm[i];
+ if (!c)
+ break;
+ else if (c == '"' || c == '\\' || c < 0x21 || c > 0x7e) {
+ *cp++ = '\\';
+ *cp++ = (c >> 6) + '0';
+ *cp++ = ((c >> 3) & 7) + '0';
+ *cp++ = (c & 7) + '0';
+ } else
+ *cp++ = c;
+ }
+ sprintf(cp, "(%04u/%02u/%02u-%02u:%02u:%02u)\"", stamp.year,
+ stamp.month, stamp.day, stamp.hour, stamp.min, stamp.sec);
+}
+
+/**
+ * tt_find_record - Find "struct tt_record" for given credential.
+ *
+ *
+ * Returns pointer to "struct tt_record".
+ */
+static inline struct tt_record *tt_find_record(const struct cred *cred)
+{
+ return cred->security;
+}
+
+/**
+ * tt_cred_alloc_blank - Allocate memory for new credentials.
+ *
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tt_cred_alloc_blank(struct cred *new, gfp_t gfp)
+{
+ new->security = kzalloc(sizeof(struct tt_record), gfp);
+ return new->security ? 0 : -ENOMEM;
+}
+
+/**
+ * tt_cred_prepare - Allocate memory for new credentials.
+ *
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int tt_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{
+ if (tt_cred_alloc_blank(new, gfp))
+ return -ENOMEM;
+ strcpy(tt_find_record(new)->history, tt_find_record(old)->history);
+ return 0;
+}
+
+/**
+ * tt_cred_free - Release memory used by credentials.
+ *
+ *
+ * Returns nothing.
+ */
+static void tt_cred_free(struct cred *cred)
+{
+ kfree(cred->security);
+}
+
+/**
+ * tt_cred_transfer - Transfer "struct tt_record" between credentials.
+ *
+ *
+ * Returns nothing.
+ */
+static void tt_cred_transfer(struct cred *new, const struct cred *old)
+{
+ strcpy(tt_find_record(new)->history, tt_find_record(old)->history);
+}
+
+/**
+ * tt_bprm_committing_creds - A hook which is called when do_execve()
succeeded. + *
+ *
+ * Returns nothing.
+ */
+static void tt_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ tt_update_record(tt_find_record(bprm->cred));
+}
+
+/**
+ * tt_task_getsecid - Check whether to audit or not.
+ *
+ */
+static void tt_task_getsecid(struct task_struct *p, u32 *secid)
+{
+ *secid = (p == current);
+}
+
+/**
+ * tt_secid_to_secctx - Allocate memory used for auditing.
+ *
+ *
+ * Returns 0 on success, -EINVAL otherwise.
+ */
+static int tt_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
+{
+ struct tt_record *record;
+ /* Ignore unless current thread's record is requested. */
+ if (secid != 1)
+ return -EINVAL;
+ /*
+ * We don't need to duplicate the string because current thread's
+ * record is updated upon only boot up and successful execve()
+ * operation, even if current thread's record is shared between
+ * multiple threads.
+ */
+ record = tt_find_record(current->real_cred);
+ *secdata = record->history;
+ *seclen = strlen(record->history);
+ return 0;
+}
+
+/* List of hooks. */
+static struct security_operations tasktracker_ops = {
+ .name = "tt",
+ .secid_to_secctx = tt_secid_to_secctx,
+ .task_getsecid = tt_task_getsecid,
+ .cred_prepare = tt_cred_prepare,
+ .cred_free = tt_cred_free,
+ .cred_alloc_blank = tt_cred_alloc_blank,
+ .cred_transfer = tt_cred_transfer,
+ .bprm_committing_creds = tt_bprm_committing_creds,
+};
+
+/**
+ * tt_init - Initialize this module.
+ *
+ * Returns 0 on success, panic otherwise.
+ */
+static int __init tt_init(void)
+{
+ struct cred *cred = (struct cred *) current_cred();
+ if (!security_module_enable(&tasktracker_ops))
+ return 0;
+ if (tt_cred_alloc_blank(cred, GFP_ATOMIC) ||
+ register_security(&tasktracker_ops))
+ panic("Failure registering TaskTracker");
+ tt_update_record(tt_find_record(cred));
+ pr_info("TaskTracker initialized\n");
+ return 0;
+}
+
+security_initcall(tt_init);
--
Linux-audit mailing list
https://www.redhat.com/mailman/listinfo/linux-audit
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tetsuo Handa
2014-06-26 11:40:54 UTC
Permalink
Thank you for your comment, Steve.
Post by Steve Grubb
Post by Tetsuo Handa
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.
Post by Steve Grubb
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?
Post by Steve Grubb
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.
----------
Post by Steve Grubb
From d015533ce544feb8922fcbf023017c82bd79a9ac Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-***@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-***@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
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tetsuo Handa
2014-09-27 01:02:44 UTC
Permalink
May I continue proposing this functionality? I want to identify where bash is
executed from in the RHEL servers in order to assess possibility of damage
caused by CVE-2014-6271.
Post by Tetsuo Handa
Thank you for your comment, Steve.
Post by Steve Grubb
Post by Tetsuo Handa
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.
Post by Steve Grubb
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?
Post by Steve Grubb
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.
----------
Post by Steve Grubb
From d015533ce544feb8922fcbf023017c82bd79a9ac Mon Sep 17 00:00:00 2001
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)'
---
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(-)
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Steve Grubb
2014-09-27 13:14:40 UTC
Permalink
On Sat, 27 Sep 2014 10:02:44 +0900
Post by Tetsuo Handa
May I continue proposing this functionality?
From the audit perspective, sure. I think we were expecting a revised
patch after the comments. Other groups may have different thoughts,
though.

-Steve
Post by Tetsuo Handa
I want to identify where
bash is executed from in the RHEL servers in order to assess
possibility of damage caused by CVE-2014-6271.
Post by Tetsuo Handa
Thank you for your comment, Steve.
Post by Steve Grubb
Post by Tetsuo Handa
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.
Post by Steve Grubb
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?
Post by Steve Grubb
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.
----------
Post by Steve Grubb
From d015533ce544feb8922fcbf023017c82bd79a9ac Mon Sep 17 00:00:00 2001
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)'
---
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(-)
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tetsuo Handa
2014-09-27 15:13:14 UTC
Permalink
Post by Steve Grubb
On Sat, 27 Sep 2014 10:02:44 +0900
Post by Tetsuo Handa
May I continue proposing this functionality?
From the audit perspective, sure. I think we were expecting a revised
patch after the comments. Other groups may have different thoughts,
though.
-Steve
OK, thank you. Before posting a revised patch, can I hear answers about
specification questions listed below?

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

(Q2) Does auxiliary record work with only type=SYSCALL case?

Regards.
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Steve Grubb
2014-09-27 15:40:27 UTC
Permalink
Hello,

On Sun, 28 Sep 2014 00:13:14 +0900
Post by Tetsuo Handa
Post by Steve Grubb
On Sat, 27 Sep 2014 10:02:44 +0900
Post by Tetsuo Handa
May I continue proposing this functionality?
From the audit perspective, sure. I think we were expecting a
revised patch after the comments. Other groups may have different
thoughts, though.
-Steve
OK, thank you. Before posting a revised patch, can I hear answers
about specification questions listed below?
Sure.
Post by Tetsuo Handa
(Q1) Where can I find which bytes in $value need to be escaped when
emitting a record like name='$value' ?
I have written a specification that describes how to write well formed
audit events to help with questions like this. You can find it here:

http://people.redhat.com/sgrubb/audit/audit-events.txt

If you know that a field is under user control, it must be escaped so
that they cannot try to trick the parser.
Post by Tetsuo Handa
Is 0x20 in $value permitted?
No. That is the separator between fields, so it cannot be allowed. What
we suggest is to use a dash or hyphen between if you are logging a
phrase that cannot be altered by the user. For example, you may have an
op field saying it deleted a rule. You would do it as op=rule-deleted.
However, we do not suggest that for user controlled fields. Just escape
it by calling audit_log_untrustedstring() if in the kernel. There are
examples in the page I mention above.
Post by Tetsuo Handa
(Q2) Does auxiliary record work with only type=SYSCALL case?
Auxiliary records compliment a syscall record by adding extra
information. PATH, IPC, CWD, and EXECVE are some examples. They get
emitted in audit_log_exit() if you wanted to look at them in more
detail.

HTH...

-Steve
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tetsuo Handa
2014-09-28 10:24:52 UTC
Permalink
Thank you, Steve. Here is a revised patch.
----------
From 2beb93e870e9c1a6391d8706aa84a608b8353c2a Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-***@I-love.SAKURA.ne.jp>
Date: Sun, 28 Sep 2014 19:20:16 +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)'

Note that only char < 0x21, char > 0x7e, '\'', '\\' and '=' are escaped
using \ooo style octal value rather than converting all characters to XX
style hexadecimal value.

Signed-off-by: Tetsuo Handa <penguin-***@I-love.SAKURA.ne.jp>
---
fs/exec.c | 1 +
include/linux/audit.h | 4 ++
include/linux/init_task.h | 5 ++
include/linux/sched.h | 3 +
include/uapi/linux/audit.h | 1 +
kernel/audit.c | 1 +
kernel/auditsc.c | 113 ++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 128 insertions(+), 0 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index a2b42a9..1e81709 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1191,6 +1191,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..97d08e1 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -206,6 +206,8 @@ static inline void audit_ptrace(struct task_struct *t)
__audit_ptrace(t);
}

+extern void audit_update_history(void);
+
/* Private API (for audit.c only) */
extern unsigned int audit_serial(void);
extern int auditsc_get_stamp(struct audit_context *ctx,
@@ -419,6 +421,8 @@ static inline void audit_mmap_fd(int fd, int flags)
{ }
static inline void audit_ptrace(struct task_struct *t)
{ }
+static inline void audit_update_history(void)
+{ }
#define audit_n_rules 0
#define audit_signals 0
#endif /* CONFIG_AUDITSYSCALL */
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 2bb4c4f..7a5695b 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -98,8 +98,12 @@ extern struct group_info init_groups;
#define INIT_IDS \
.loginuid = INVALID_UID, \
.sessionid = (unsigned int)-1,
+extern char init_task_history[];
+#define INIT_THREAD_HISTORY \
+ .comm_history = init_task_history,
#else
#define INIT_IDS
+#define INIT_THREAD_HISTORY
#endif

#ifdef CONFIG_TREE_PREEMPT_RCU
@@ -227,6 +231,7 @@ extern struct task_group root_task_group;
INIT_CPUSET_SEQ(tsk) \
INIT_RT_MUTEXES(tsk) \
INIT_VTIME(tsk) \
+ INIT_THREAD_HISTORY \
}


diff --git a/include/linux/sched.h b/include/linux/sched.h
index b867a4d..fd3cdaf 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1644,6 +1644,9 @@ struct task_struct {
unsigned int sequential_io;
unsigned int sequential_io_avg;
#endif
+#ifdef CONFIG_AUDITSYSCALL
+ 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 3b9ff33..a6a8ee8 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 ba2ff5a..252544a 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1163,6 +1163,7 @@ 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..2e5ee14 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -85,6 +85,9 @@
/* max length to print of cmdline/proctitle value during audit */
#define MAX_PROCTITLE_AUDIT_LEN 128

+/* thread's comm name history length */
+#define COMM_HISTORY_SIZE 1024
+
/* number of audit rules */
int audit_n_rules;

@@ -950,6 +953,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 +968,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 +1359,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 +1488,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);
@@ -1486,6 +1508,8 @@ void __audit_free(struct task_struct *tsk)
{
struct audit_context *context;

+ kfree(tsk->comm_history);
+ tsk->comm_history = NULL;
context = audit_take_context(tsk, 0, 0);
if (!context)
return;
@@ -2503,3 +2527,92 @@ struct list_head *audit_killed_trees(void)
return NULL;
return &ctx->killed_trees;
}
+
+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 = strchr(history + 1, '=');
+
+ if (unlikely(!cp))
+ return;
+ pos -= cp - history;
+ memmove(history, cp, pos + 1);
+ }
+ 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];
+ snprintf(history, 22, "(%04u/%02u/%02u-%02u:%02u:%02u)", year,
+ month + 1, day + 1, hour, minute, second);
+}
--
1.7.1
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Richard Guy Briggs
2014-10-07 21:30:54 UTC
Permalink
Post by Tetsuo Handa
(Q2) Does auxiliary record work with only type=SYSCALL case?
Auxiliary records don't work with AUDIT_LOGIN because that record has a
NULL context. Similarly for core dumps (AUDIT_ANOM_ABEND), AUDIT_SECCOMP,
configuration changes (AUDIT_CONFIG_CHANGE, AUDIT_FEATURE_CHANGE), most
(all?) AUDIT_USER_* messages.
Post by Tetsuo Handa
Regards.
- RGB

--
Richard Guy Briggs <***@redhat.com>
Senior Software Engineer, Kernel Security, AMER ENG Base Operating Systems, Red Hat
Remote, Ottawa, Canada
Voice: +1.647.777.2635, Internal: (81) 32635, Alt: +1.613.693.0684x3545
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tetsuo Handa
2014-10-10 12:40:11 UTC
Permalink
Post by Steve Grubb
Hello,
On Sun, 28 Sep 2014 00:13:14 +0900
Post by Tetsuo Handa
Post by Steve Grubb
On Sat, 27 Sep 2014 10:02:44 +0900
Post by Tetsuo Handa
May I continue proposing this functionality?
From the audit perspective, sure. I think we were expecting a
revised patch after the comments. Other groups may have different
thoughts, though.
-Steve
OK, thank you. Before posting a revised patch, can I hear answers
about specification questions listed below?
Sure.
Post by Tetsuo Handa
(Q1) Where can I find which bytes in $value need to be escaped when
emitting a record like name='$value' ?
I have written a specification that describes how to write well formed
http://people.redhat.com/sgrubb/audit/audit-events.txt
If you know that a field is under user control, it must be escaped so
that they cannot try to trick the parser.
Post by Tetsuo Handa
Is 0x20 in $value permitted?
No. That is the separator between fields, so it cannot be allowed. What
we suggest is to use a dash or hyphen between if you are logging a
phrase that cannot be altered by the user. For example, you may have an
op field saying it deleted a rule. You would do it as op=rule-deleted.
However, we do not suggest that for user controlled fields. Just escape
it by calling audit_log_untrustedstring() if in the kernel. There are
examples in the page I mention above.
Post by Tetsuo Handa
(Q2) Does auxiliary record work with only type=SYSCALL case?
Auxiliary records compliment a syscall record by adding extra
information. PATH, IPC, CWD, and EXECVE are some examples. They get
emitted in audit_log_exit() if you wanted to look at them in more
detail.
HTH...
Post by Tetsuo Handa
(Q2) Does auxiliary record work with only type=SYSCALL case?
Auxiliary records don't work with AUDIT_LOGIN because that record has a
NULL context. Similarly for core dumps (AUDIT_ANOM_ABEND), AUDIT_SECCOMP,
configuration changes (AUDIT_CONFIG_CHANGE, AUDIT_FEATURE_CHANGE), most
(all?) AUDIT_USER_* messages.
I see, thank you.

Although I feel that, from the point of view of troubleshooting, emitting
history of thread's comm name into NULL-context records would help sysadmin
to map login session and operations a user did from that login session,
I'm OK with starting history of thread's comm name as auxiliary records
(i.e. not emitted into NULL-context records).

Adding LKML for reviewers. What else can I do for merging this patch?
----------
Post by Steve Grubb
From 2beb93e870e9c1a6391d8706aa84a608b8353c2a Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-***@I-love.SAKURA.ne.jp>
Date: Sun, 28 Sep 2014 19:20:16 +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)'

Note that only char < 0x21, char > 0x7e, '\'', '\\' and '=' are escaped
using \ooo style octal value rather than converting all characters to XX
style hexadecimal value.

Signed-off-by: Tetsuo Handa <penguin-***@I-love.SAKURA.ne.jp>
---
fs/exec.c | 1 +
include/linux/audit.h | 4 ++
include/linux/init_task.h | 5 ++
include/linux/sched.h | 3 +
include/uapi/linux/audit.h | 1 +
kernel/audit.c | 1 +
kernel/auditsc.c | 113 ++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 128 insertions(+), 0 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index a2b42a9..1e81709 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1191,6 +1191,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..97d08e1 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -206,6 +206,8 @@ static inline void audit_ptrace(struct task_struct *t)
__audit_ptrace(t);
}

+extern void audit_update_history(void);
+
/* Private API (for audit.c only) */
extern unsigned int audit_serial(void);
extern int auditsc_get_stamp(struct audit_context *ctx,
@@ -419,6 +421,8 @@ static inline void audit_mmap_fd(int fd, int flags)
{ }
static inline void audit_ptrace(struct task_struct *t)
{ }
+static inline void audit_update_history(void)
+{ }
#define audit_n_rules 0
#define audit_signals 0
#endif /* CONFIG_AUDITSYSCALL */
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 2bb4c4f..7a5695b 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -98,8 +98,12 @@ extern struct group_info init_groups;
#define INIT_IDS \
.loginuid = INVALID_UID, \
.sessionid = (unsigned int)-1,
+extern char init_task_history[];
+#define INIT_THREAD_HISTORY \
+ .comm_history = init_task_history,
#else
#define INIT_IDS
+#define INIT_THREAD_HISTORY
#endif

#ifdef CONFIG_TREE_PREEMPT_RCU
@@ -227,6 +231,7 @@ extern struct task_group root_task_group;
INIT_CPUSET_SEQ(tsk) \
INIT_RT_MUTEXES(tsk) \
INIT_VTIME(tsk) \
+ INIT_THREAD_HISTORY \
}


diff --git a/include/linux/sched.h b/include/linux/sched.h
index b867a4d..fd3cdaf 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1644,6 +1644,9 @@ struct task_struct {
unsigned int sequential_io;
unsigned int sequential_io_avg;
#endif
+#ifdef CONFIG_AUDITSYSCALL
+ 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 3b9ff33..a6a8ee8 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 ba2ff5a..252544a 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1163,6 +1163,7 @@ 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..2e5ee14 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -85,6 +85,9 @@
/* max length to print of cmdline/proctitle value during audit */
#define MAX_PROCTITLE_AUDIT_LEN 128

+/* thread's comm name history length */
+#define COMM_HISTORY_SIZE 1024
+
/* number of audit rules */
int audit_n_rules;

@@ -950,6 +953,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 +968,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 +1359,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 +1488,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);
@@ -1486,6 +1508,8 @@ void __audit_free(struct task_struct *tsk)
{
struct audit_context *context;

+ kfree(tsk->comm_history);
+ tsk->comm_history = NULL;
context = audit_take_context(tsk, 0, 0);
if (!context)
return;
@@ -2503,3 +2527,92 @@ struct list_head *audit_killed_trees(void)
return NULL;
return &ctx->killed_trees;
}
+
+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 = strchr(history + 1, '=');
+
+ if (unlikely(!cp))
+ return;
+ pos -= cp - history;
+ memmove(history, cp, pos + 1);
+ }
+ 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];
+ snprintf(history, 22, "(%04u/%02u/%02u-%02u:%02u:%02u)", year,
+ month + 1, day + 1, hour, minute, second);
+}
--
1.7.1
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Richard Guy Briggs
2014-10-10 12:49:23 UTC
Permalink
Post by Tetsuo Handa
Post by Steve Grubb
Hello,
On Sun, 28 Sep 2014 00:13:14 +0900
Post by Tetsuo Handa
Post by Steve Grubb
On Sat, 27 Sep 2014 10:02:44 +0900
Post by Tetsuo Handa
May I continue proposing this functionality?
From the audit perspective, sure. I think we were expecting a
revised patch after the comments. Other groups may have different
thoughts, though.
-Steve
OK, thank you. Before posting a revised patch, can I hear answers
about specification questions listed below?
Sure.
Post by Tetsuo Handa
(Q1) Where can I find which bytes in $value need to be escaped when
emitting a record like name='$value' ?
I have written a specification that describes how to write well formed
http://people.redhat.com/sgrubb/audit/audit-events.txt
If you know that a field is under user control, it must be escaped so
that they cannot try to trick the parser.
Post by Tetsuo Handa
Is 0x20 in $value permitted?
No. That is the separator between fields, so it cannot be allowed. What
we suggest is to use a dash or hyphen between if you are logging a
phrase that cannot be altered by the user. For example, you may have an
op field saying it deleted a rule. You would do it as op=rule-deleted.
However, we do not suggest that for user controlled fields. Just escape
it by calling audit_log_untrustedstring() if in the kernel. There are
examples in the page I mention above.
Post by Tetsuo Handa
(Q2) Does auxiliary record work with only type=SYSCALL case?
Auxiliary records compliment a syscall record by adding extra
information. PATH, IPC, CWD, and EXECVE are some examples. They get
emitted in audit_log_exit() if you wanted to look at them in more
detail.
HTH...
Post by Tetsuo Handa
(Q2) Does auxiliary record work with only type=SYSCALL case?
Auxiliary records don't work with AUDIT_LOGIN because that record has a
NULL context. Similarly for core dumps (AUDIT_ANOM_ABEND), AUDIT_SECCOMP,
configuration changes (AUDIT_CONFIG_CHANGE, AUDIT_FEATURE_CHANGE), most
(all?) AUDIT_USER_* messages.
I see, thank you.
Although I feel that, from the point of view of troubleshooting, emitting
history of thread's comm name into NULL-context records would help sysadmin
to map login session and operations a user did from that login session,
I'm OK with starting history of thread's comm name as auxiliary records
(i.e. not emitted into NULL-context records).
Adding LKML for reviewers. What else can I do for merging this patch?
I'm willing to take it with some reflection and no significant
objections, in particular from userspace audit. I'll have a closer look
at it.
Post by Tetsuo Handa
----------
Post by Steve Grubb
From 2beb93e870e9c1a6391d8706aa84a608b8353c2a Mon Sep 17 00:00:00 2001
Date: Sun, 28 Sep 2014 19:20:16 +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)'
Note that only char < 0x21, char > 0x7e, '\'', '\\' and '=' are escaped
using \ooo style octal value rather than converting all characters to XX
style hexadecimal value.
---
fs/exec.c | 1 +
include/linux/audit.h | 4 ++
include/linux/init_task.h | 5 ++
include/linux/sched.h | 3 +
include/uapi/linux/audit.h | 1 +
kernel/audit.c | 1 +
kernel/auditsc.c | 113 ++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 128 insertions(+), 0 deletions(-)
diff --git a/fs/exec.c b/fs/exec.c
index a2b42a9..1e81709 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1191,6 +1191,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..97d08e1 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -206,6 +206,8 @@ static inline void audit_ptrace(struct task_struct *t)
__audit_ptrace(t);
}
+extern void audit_update_history(void);
+
/* Private API (for audit.c only) */
extern unsigned int audit_serial(void);
extern int auditsc_get_stamp(struct audit_context *ctx,
@@ -419,6 +421,8 @@ static inline void audit_mmap_fd(int fd, int flags)
{ }
static inline void audit_ptrace(struct task_struct *t)
{ }
+static inline void audit_update_history(void)
+{ }
#define audit_n_rules 0
#define audit_signals 0
#endif /* CONFIG_AUDITSYSCALL */
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 2bb4c4f..7a5695b 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -98,8 +98,12 @@ extern struct group_info init_groups;
#define INIT_IDS \
.loginuid = INVALID_UID, \
.sessionid = (unsigned int)-1,
+extern char init_task_history[];
+#define INIT_THREAD_HISTORY \
+ .comm_history = init_task_history,
#else
#define INIT_IDS
+#define INIT_THREAD_HISTORY
#endif
#ifdef CONFIG_TREE_PREEMPT_RCU
@@ -227,6 +231,7 @@ extern struct task_group root_task_group;
INIT_CPUSET_SEQ(tsk) \
INIT_RT_MUTEXES(tsk) \
INIT_VTIME(tsk) \
+ INIT_THREAD_HISTORY \
}
diff --git a/include/linux/sched.h b/include/linux/sched.h
index b867a4d..fd3cdaf 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1644,6 +1644,9 @@ struct task_struct {
unsigned int sequential_io;
unsigned int sequential_io_avg;
#endif
+#ifdef CONFIG_AUDITSYSCALL
+ 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 3b9ff33..a6a8ee8 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 ba2ff5a..252544a 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1163,6 +1163,7 @@ 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..2e5ee14 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -85,6 +85,9 @@
/* max length to print of cmdline/proctitle value during audit */
#define MAX_PROCTITLE_AUDIT_LEN 128
+/* thread's comm name history length */
+#define COMM_HISTORY_SIZE 1024
+
/* number of audit rules */
int audit_n_rules;
@@ -950,6 +953,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 +968,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;
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 +1488,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);
@@ -1486,6 +1508,8 @@ void __audit_free(struct task_struct *tsk)
{
struct audit_context *context;
+ kfree(tsk->comm_history);
+ tsk->comm_history = NULL;
context = audit_take_context(tsk, 0, 0);
if (!context)
return;
@@ -2503,3 +2527,92 @@ struct list_head *audit_killed_trees(void)
return NULL;
return &ctx->killed_trees;
}
+
+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 = strchr(history + 1, '=');
+
+ if (unlikely(!cp))
+ return;
+ pos -= cp - history;
+ memmove(history, cp, pos + 1);
+ }
+ 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];
+ snprintf(history, 22, "(%04u/%02u/%02u-%02u:%02u:%02u)", year,
+ month + 1, day + 1, hour, minute, second);
+}
--
1.7.1
- RGB

--
Richard Guy Briggs <***@redhat.com>
Senior Software Engineer, Kernel Security, AMER ENG Base Operating Systems, Red Hat
Remote, Ottawa, Canada
Voice: +1.647.777.2635, Internal: (81) 32635, Alt: +1.613.693.0684x3545
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Loading...