Hello,
the attached patches propose a way to audit administrative commands.

Summary
-------
A per-process "audit TTY input" attribute is added.  The attribute is
inherited across fork ().  A new PAM module is used to turn the
attribute on or off on login.  Data read from TTYs by processes with the
attribute is sent to the audit subsystem by the kernel.  Optionally,
user-space applications can send advisory audit events describing the
"meaning" of the TTY input.


Fundamental limitations
-----------------------
Only TTY input is logged, so an administrator may execute unknown code
by downloading shell scripts over the network.  The act of downloading
the shell script would be audited, however.

For GUI or a complex TUI applications (e.g. emacs or mc), auditing the
TTY input probably does not save enough information to reproduce the
sequence of executed commands.  If necessary, these applications may be
extended to send advisory audit events.  (Any approach to administrative
action auditing would have to extend these applications).

Why auditing needs to be done by the kernel
-------------------------------------------
If system call auditing is not an option, there are simply too many
applications that can be used to perform non-trivial administrative
tasks that would have to be extended.  All shells, most programming
language interpreters, awk, m4, ... .  In the worst case, the user might
be using a proprietary shell.  The system should also be able to handle
at least the trivial workarounds like (cat | sh).

So, if we can't audit the program actions (system calls), and we can't
in general modify the programs themselves, the only remaining option is
to audit the inputs to the programs - TTY input.

This could be done in user-space by running all administrative sessions
in a pseudo-TTY and auditing the data sent to the pseudo-TTY.
Unfortunately that's not transparent enough, and changes behavior (after
logging on to a text console, /dev/stdin is not a VT and can't be used
to send VT ioctls - for a simple example, see /etc/profile.d/lang.sh on
Fedora/RHEL).

Auditing processes, not TTYs
----------------------------
If actions of ordinary users are not audited, after (su -) there are
both administrative and non-administrative processes with the TTY open.
     The answer to the question "should this particular byte of input to
the TTY be audited" depends on whether the byte is processed by an
"administrative process", not on whether the TTY is /dev/tty1 or a PTY
representing a ssh connection, or on whether an administrative process
has ever been executed on the TTY since last hangup.

Audit event generation based on a process-inherited flag has one
additional advantage:  If root within a (su -) session runs (su -
unprivileged user), root's actions as the unprivileged user are audited.

A potential problem with is approach is unwanted auditing of TTY input
to system daemons run (or restarted) by an administrator;  if the
administrator restarts an *getty daemon, all inputs to the daemon would
be audited.  As a special hack, opening a TTY in a process that has no
TTY currently open automatically disables the "audit TTY input" flag.
Closing the current TTY and opening another one does not really make any
sense in a regular application, but daemons which close all file
descriptors on startup would be handled by the hack.  If the hack
doesn't handle a specific daemon automatically, the daemon could either
be modified to disable auditing, or its startup scripts could explicitly
close TTYs to activate the hack.

Semantics of the logged data
----------------------------
The data is not logged byte by byte; a per-process buffer of data to be
audited is kept, collecting the characters as they are read by the
application.  The contents of the buffer are audited if:
- the buffer is full
- ICANON is enabled and an EOL or EOF character is "delivered" to the
  application ("delivering EOF" doesn't actually provide any bytes)
- ICANON is enabled or disabled
- auditing TTY input is disabled for the process
- the process exits
- the process sends an advisory TTY input audit event.

Thus, for applications using ICANON, input is audited line by line.  For
applications not using ICANON (e.g. uses readline), it is audited in
blocks of N_TTY_BUF bytes.  If the application is not using ICANON, it
may send advisory messages; in that case, each "command" is audited
using both the kernel's audit events containing the exact tty input
(e.g. C-r up RET) and the advisory message (e.g. "yum upgrade"), and the
raw input is always audited before the advisory messages.

As a special case, input read when the TTY is using ICANON without ECHO
is _not_ audited, to avoid storing passwords in the audit log.  On the
other hand, non-ICANON input is always audited (e.g. vim/emacs/mc input)
in full.  Note that passwords may still be audited if they are echoed,
e.g. when sending CREATE USER commands to a SQL server.

Attached code
-------------
- a kernel patch, against a current-ish Linux tree.
- a patch against audit-1.5.3 to recognize the new netlink message types
- a PAM module which allows enabling/disabling TTY auditing on login
- a patch against readline-5.2 to generate advisory audit events on
  returned strings.  The exact same patch can be used for the readline
  copy included in bash-3.2.

Unresolved questions:
---------------------
The advisory audit events may be emitted by any process for which TTY
input is audited, no additional privileges are necessary.  Is it
necessary to separately limit the rate of the generated events, or is
the current kernel rate limit sufficient?

Reading and modifying the "audit TTY input" attribute using a the audit
netlink socket works, but it feels unnatural.  Should it be done
differently (e.g. /proc, prctl ())?  Is it enough to allow an
administrative process to read/modify its own "audit TTY input"
attribute, or is it necessary to access the attribute of other processes?


I'll be grateful for any comments.
        Mirek
diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c
index b3d4ccc..dcf50b5 100644
--- a/drivers/char/n_tty.c
+++ b/drivers/char/n_tty.c
@@ -45,6 +45,8 @@
 #include <linux/slab.h>
 #include <linux/poll.h>
 #include <linux/bitops.h>
+#include <linux/audit.h>
+#include <linux/file.h>
 
 #include <asm/uaccess.h>
 #include <asm/system.h>
@@ -78,6 +80,343 @@ static inline void free_buf(unsigned char *buf)
 		free_page((unsigned long) buf);
 }
 
+#ifdef CONFIG_AUDIT
+struct tty_audit_buf {
+	atomic_t count;
+	struct mutex mutex;	/* Protects all data below */
+	int major, minor;	/* The TTY which the data is from */
+	unsigned icanon:1, echo:1;
+	size_t valid;
+	unsigned char *data;	/* Allocated size N_TTY_BUF_SIZE */
+};
+
+static struct tty_audit_buf *
+tty_audit_buf_alloc(int major, int minor, int icanon, int echo)
+{
+	struct tty_audit_buf *buf;
+
+	buf = kmalloc(sizeof (*buf), GFP_KERNEL);
+	if (!buf)
+		goto err;
+	buf->data = alloc_buf();
+	if (!buf->data)
+		goto err_buf;
+	atomic_set(&buf->count, 1);
+	mutex_init(&buf->mutex);
+	buf->major = major;
+	buf->minor = minor;
+	buf->icanon = icanon;
+	buf->echo = echo;
+	buf->valid = 0;
+	return buf;
+
+err_buf:
+	kfree(buf);
+err:
+	return NULL;
+}
+
+static void
+tty_audit_buf_free(struct tty_audit_buf *buf)
+{
+	WARN_ON(buf->valid != 0);
+	free_buf(buf->data);
+	kfree(buf);
+}
+
+static void
+tty_audit_buf_put(struct tty_audit_buf *buf)
+{
+	if (atomic_dec_and_test(&buf->count))
+		tty_audit_buf_free(buf);
+}
+
+/**
+ *	tty_audit_buf_push	-	Push buffered data out
+ *
+ *	Generate an audit message from the contents of &buf.  &buf->mutex must
+ *	be locked.
+ */
+static void
+tty_audit_buf_push(struct task_struct *tsk, struct tty_audit_buf *buf)
+{
+	struct audit_buffer *ab;
+
+	if (buf->valid == 0)
+		return;
+	if (audit_enabled == 0)
+		return;
+	ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_TTY);
+	if (ab) {
+		char name[sizeof(tsk->comm)];
+
+		audit_log_format(ab, "tty pid=%u uid=%u major=%d minor=%d "
+				 "comm=",
+				 tsk->pid, tsk->uid, buf->major, buf->minor);
+		get_task_comm(name, tsk);
+		audit_log_untrustedstring(ab, name);
+		audit_log_format(ab, " data=");
+		audit_log_n_untrustedstring(ab, buf->valid, buf->data);
+		audit_log_end(ab);
+	}
+	buf->valid = 0;
+}
+
+/**
+ *	tty_audit_exit	-	Handle a task exit
+ *
+ *	Make sure all buffered data is written out and deallocate the buffer.
+ *	Only needs to be called if current->signal->tty_audit_buf != %NULL.
+ */
+void
+tty_audit_exit(void)
+{
+	struct tty_audit_buf *buf;
+
+	spin_lock(&current->sighand->siglock);
+	buf = current->signal->tty_audit_buf;
+	current->signal->tty_audit_buf = NULL;
+	spin_unlock(&current->sighand->siglock);
+	if (!buf)
+		return;
+
+	mutex_lock(&buf->mutex);
+	tty_audit_buf_push(current, buf);
+	mutex_unlock(&buf->mutex);
+
+	tty_audit_buf_put(buf);
+}
+
+/**
+ *	tty_audit_push_task	-	Flush task's pending audit data
+ */
+void
+tty_audit_push_task(struct task_struct *tsk)
+{
+	struct tty_audit_buf *buf;
+
+	spin_lock(&tsk->sighand->siglock);
+	buf = tsk->signal->tty_audit_buf;
+	if (buf)
+		atomic_inc(&buf->count);
+	spin_unlock(&tsk->sighand->siglock);
+	if (!buf)
+		return;
+
+	mutex_lock(&buf->mutex);
+	tty_audit_buf_push(tsk, buf);
+	mutex_unlock(&buf->mutex);
+
+	tty_audit_buf_put(buf);
+}
+
+/**
+ *	tty_audit_buf_get	-	Get an audit buffer.
+ *
+ *	Get an audit buffer for @tty, allocate it if necessary.  Return %NULL
+ *	if TTY auditing is disabled or out of memory.  Otherwise, return a new
+ *	reference to the buffer.
+ */
+static struct tty_audit_buf *
+tty_audit_buf_get(struct tty_struct *tty)
+{
+	struct tty_audit_buf *buf, *buf2;
+
+	buf = NULL;
+	buf2 = NULL;
+	spin_lock(&current->sighand->siglock);
+	if (likely(!current->signal->audit_tty))
+		goto out;
+	buf = current->signal->tty_audit_buf;
+	if (buf) {
+		atomic_inc(&buf->count);
+		goto out;
+	}
+	spin_unlock(&current->sighand->siglock);
+
+	buf2 = tty_audit_buf_alloc(tty->driver->major,
+				   tty->driver->minor_start + tty->index,
+				   tty->icanon, L_ECHO(tty) != 0);
+	if (buf2 == NULL) {
+		audit_log_lost("out of memory in TTY auditing");
+		return NULL;
+	}
+
+	spin_lock(&current->sighand->siglock);
+	if (!current->signal->audit_tty)
+		goto out;
+	buf = current->signal->tty_audit_buf;
+	if (!buf) {
+		current->signal->tty_audit_buf = buf2;
+		buf = buf2;
+		buf2 = NULL;
+	}
+	atomic_inc(&buf->count);
+	/* Fall through */
+ out:
+	spin_unlock(&current->sighand->siglock);
+	if (buf2)
+		tty_audit_buf_free(buf2);
+	return buf;
+}
+
+/**
+ *	tty_audit_add_data	-	Add data for TTY auditing.
+ *
+ *	Audit @data of @size from @tty, if necessary.
+ */
+static void
+tty_audit_add_data(struct tty_struct *tty, unsigned char *data, size_t size)
+{
+	struct tty_audit_buf *buf;
+	int major, minor, echo;
+
+	if (unlikely(size == 0))
+		return;
+
+	buf = tty_audit_buf_get(tty);
+	if (!buf)
+		return;
+
+	mutex_lock(&buf->mutex);
+	major = tty->driver->major;
+	minor = tty->driver->minor_start + tty->index;
+	echo = L_ECHO(tty) != 0;
+	if (buf->major != major || buf->minor != minor
+	    || buf->icanon != tty->icanon || buf->echo != echo) {
+		tty_audit_buf_push(current, buf);
+		buf->major = major;
+		buf->minor = minor;
+		buf->icanon = tty->icanon;
+		buf->echo = echo;
+	}
+	if (L_ECHO(tty) || !tty->icanon) {
+		do {
+			size_t run;
+
+			run = N_TTY_BUF_SIZE - buf->valid;
+			if (run > size)
+				run = size;
+			memcpy(buf->data + buf->valid, data, run);
+			buf->valid += run;
+			data += run;
+			size -= run;
+			if (buf->valid == N_TTY_BUF_SIZE)
+				tty_audit_buf_push(current, buf);
+		} while (size != 0);
+	}
+	mutex_unlock(&buf->mutex);
+	tty_audit_buf_put(buf);
+}
+
+/**
+ *	tty_audit_buf_push	-	Push buffered data out
+ *
+ *	Make sure not audit data is pending for @tty on the current process.
+ */
+static void
+tty_audit_push(struct tty_struct *tty)
+{
+	struct tty_audit_buf *buf;
+
+	spin_lock(&current->sighand->siglock);
+	if (likely(!current->signal->audit_tty)) {
+		spin_unlock(&current->sighand->siglock);
+		return;
+	}
+	buf = current->signal->tty_audit_buf;
+	if (buf)
+		atomic_inc(&buf->count);
+	spin_unlock(&current->sighand->siglock);
+
+	if (buf) {
+		int major, minor;
+
+		major = tty->driver->major;
+		minor = tty->driver->minor_start + tty->index;
+		mutex_lock(&buf->mutex);
+		if (buf->major == major && buf->minor == minor)
+			tty_audit_buf_push(current, buf);
+		mutex_unlock(&buf->mutex);
+		tty_audit_buf_put(buf);
+	}
+}
+
+/* For checking whether a file is a TTY */
+extern ssize_t tty_read(struct file * file, char __user * buf, size_t count,
+			loff_t *ppos);
+
+/**
+ *	tty_audit_opening	-	A TTY is being opened.
+ *
+ *	As a special hack, tasks that close all their TTYs and open new ones
+ *	are assumed to be system daemons (e.g. getty) and auditing is
+ *	automatically disabled for them.
+ */
+void
+tty_audit_opening(void)
+{
+	int disable;
+
+	disable = 1;
+	spin_lock(&current->sighand->siglock);
+	if (current->signal->audit_tty == 0)
+		disable = 0;
+	spin_unlock(&current->sighand->siglock);
+	if (!disable)
+		return;
+
+	task_lock(current);
+	if (current->files) {
+		struct fdtable *fdt;
+		int i;
+
+		/*
+		 * We don't take a ref to the file, so we must hold ->file_lock
+		 * instead.
+		 */
+		spin_lock(&current->files->file_lock);
+		fdt = files_fdtable(current->files);
+		for (i=0; i < fdt->max_fds; i++) {
+			struct file *filp;
+
+			filp = fcheck_files(current->files, i);
+			if (!filp)
+				continue;
+			if (filp->f_op->read == tty_read) {
+				disable = 0;
+				break;
+			}
+		}
+		spin_unlock(&current->files->file_lock);
+	}
+	task_unlock(current);
+	if (!disable)
+		return;
+
+	spin_lock(&current->sighand->siglock);
+	current->signal->audit_tty = 0;
+	spin_unlock(&current->sighand->siglock);
+}
+#else
+inline static void
+tty_audit_add_data(struct tty_struct *tty, unsigned char *data, size_t size)
+{
+}
+
+static void
+tty_audit_push(struct tty_struct *tty)
+{
+}
+#endif
+
+static inline int tty_put_user(struct tty_struct *tty, unsigned char x,
+			       unsigned char __user *ptr)
+{
+	tty_audit_add_data(tty, &x, 1);
+	return put_user(x, ptr);
+}
+
 /**
  *	n_tty_set__room	-	receive space
  *	@tty: terminal
@@ -1153,6 +1492,7 @@ static int copy_from_read_buf(struct tty_struct *tty,
 	if (n) {
 		retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
 		n -= retval;
+		tty_audit_add_data(tty, &tty->read_buf[tty->read_tail], n);
 		spin_lock_irqsave(&tty->read_lock, flags);
 		tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
 		tty->read_cnt -= n;
@@ -1278,7 +1618,7 @@ do_it_again:
 				break;
 			cs = tty->link->ctrl_status;
 			tty->link->ctrl_status = 0;
-			if (put_user(cs, b++)) {
+			if (tty_put_user(tty, cs, b++)) {
 				retval = -EFAULT;
 				b--;
 				break;
@@ -1320,7 +1660,7 @@ do_it_again:
 
 		/* Deal with packet mode. */
 		if (tty->packet && b == buf) {
-			if (put_user(TIOCPKT_DATA, b++)) {
+			if (tty_put_user(tty, TIOCPKT_DATA, b++)) {
 				retval = -EFAULT;
 				b--;
 				break;
@@ -1351,15 +1691,17 @@ do_it_again:
 				spin_unlock_irqrestore(&tty->read_lock, flags);
 
 				if (!eol || (c != __DISABLED_CHAR)) {
-					if (put_user(c, b++)) {
+					if (tty_put_user(tty, c, b++)) {
 						retval = -EFAULT;
 						b--;
 						break;
 					}
 					nr--;
 				}
-				if (eol)
+				if (eol) {
+					tty_audit_push(tty);
 					break;
+				}
 			}
 			if (retval)
 				break;
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c
index 75d2a46..6a825e5 100644
--- a/drivers/char/tty_io.c
+++ b/drivers/char/tty_io.c
@@ -143,7 +143,7 @@ static int ptmx_open(struct inode *, struct file *);
 
 static void initialize_tty_struct(struct tty_struct *tty);
 
-static ssize_t tty_read(struct file *, char __user *, size_t, loff_t *);
+ssize_t tty_read(struct file *, char __user *, size_t, loff_t *);
 static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *);
 ssize_t redirected_tty_write(struct file *, const char __user *, size_t, loff_t *);
 static unsigned int tty_poll(struct file *, poll_table *);
@@ -1689,8 +1689,8 @@ EXPORT_SYMBOL(start_tty);
  *	in new code. Multiple read calls may be outstanding in parallel.
  */
 
-static ssize_t tty_read(struct file * file, char __user * buf, size_t count, 
-			loff_t *ppos)
+ssize_t tty_read(struct file * file, char __user * buf, size_t count,
+		 loff_t *ppos)
 {
 	int i;
 	struct tty_struct * tty;
@@ -2653,6 +2653,7 @@ got_driver:
 		__proc_set_tty(current, tty);
 	spin_unlock_irq(&current->sighand->siglock);
 	mutex_unlock(&tty_mutex);
+	tty_audit_opening();
 	return 0;
 }
 
diff --git a/include/linux/audit.h b/include/linux/audit.h
index fccc6e5..dbf2125 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -63,9 +63,12 @@
 #define AUDIT_ADD_RULE		1011	/* Add syscall filtering rule */
 #define AUDIT_DEL_RULE		1012	/* Delete syscall filtering rule */
 #define AUDIT_LIST_RULES	1013	/* List syscall filtering rules */
+#define AUDIT_TTY_GET		1014	/* Get TTY auditing status */
+#define AUDIT_TTY_SET		1015	/* Set TTY auditing status */
 
 #define AUDIT_FIRST_USER_MSG	1100	/* Userspace messages mostly uninteresting to kernel */
 #define AUDIT_USER_AVC		1107	/* We filter this differently */
+#define AUDIT_USER_TTY		1124	/* Non-ICANON TTY input meaning */
 #define AUDIT_LAST_USER_MSG	1199
 #define AUDIT_FIRST_USER_MSG2	2100	/* More user space messages */
 #define AUDIT_LAST_USER_MSG2	2999
@@ -92,6 +95,7 @@
 #define AUDIT_KERNEL_OTHER	1316	/* For use by 3rd party modules */
 #define AUDIT_FD_PAIR		1317    /* audit record for pipe/socketpair */
 #define AUDIT_OBJ_PID		1318	/* ptrace target */
+#define AUDIT_TTY		1319	/* Input on an administrative TTY */
 
 #define AUDIT_AVC		1400	/* SE Linux avc denial or grant */
 #define AUDIT_SELINUX_ERR	1401	/* Internal SE Linux Errors */
@@ -289,6 +293,10 @@ struct audit_status {
 	__u32		backlog;	/* messages waiting in queue */
 };
 
+struct audit_tty_status {
+	__u32		enabled; /* 1 = enabled, 0 = disabled */
+};
+
 /* audit_rule_data supports filter rules with both integer and string
  * fields.  It corresponds with AUDIT_ADD_RULE, AUDIT_DEL_RULE and
  * AUDIT_LIST_RULES requests.
@@ -455,6 +463,7 @@ static inline int audit_mq_getsetattr(mqd_t mqdes, struct mq_attr *mqstat)
 		return __audit_mq_getsetattr(mqdes, mqstat);
 	return 0;
 }
+extern int audit_enabled;
 extern int audit_n_rules;
 extern int audit_signals;
 #else
@@ -487,6 +496,7 @@ extern int audit_signals;
 #define audit_mq_notify(d,n) ({ 0; })
 #define audit_mq_getsetattr(d,s) ({ 0; })
 #define audit_ptrace(t) ((void)0)
+#define audit_enabled 0
 #define audit_n_rules 0
 #define audit_signals 0
 #endif
@@ -515,6 +525,7 @@ extern void		    audit_log_d_path(struct audit_buffer *ab,
 					     const char *prefix,
 					     struct dentry *dentry,
 					     struct vfsmount *vfsmnt);
+extern void		    audit_log_lost(const char *message);
 				/* Private API (for audit.c only) */
 extern int audit_filter_user(struct netlink_skb_parms *cb, int type);
 extern int audit_filter_type(int type);
diff --git a/include/linux/sched.h b/include/linux/sched.h
index d58e74b..d44cc51 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -506,6 +506,8 @@ struct signal_struct {
 #ifdef CONFIG_TASKSTATS
 	struct taskstats *stats;
 #endif
+	unsigned audit_tty:1;
+	struct tty_audit_buf *tty_audit_buf;
 };
 
 /* Context switch must be unlocked if interrupts are to be enabled */
@@ -805,6 +807,7 @@ static inline void prefetch_stack(struct task_struct *t) { }
 #endif
 
 struct audit_context;		/* See audit.c */
+struct tty_audit_buf;		/* See drivers/char/n_tty.c */
 struct mempolicy;
 struct pipe_inode_info;
 struct uts_namespace;
diff --git a/include/linux/tty.h b/include/linux/tty.h
index bb45760..5df562f 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -178,6 +178,7 @@ struct tty_bufhead {
 #define L_IEXTEN(tty)	_L_FLAG((tty),IEXTEN)
 
 struct device;
+struct tty_audit_buf;
 /*
  * Where all of the state associated with a tty is kept while the tty
  * is open.  Since the termios state should be kept even if the tty
@@ -340,6 +341,21 @@ extern struct mutex tty_mutex;
 
 /* n_tty.c */
 extern struct tty_ldisc tty_ldisc_N_TTY;
+#ifdef CONFIG_AUDIT
+extern void tty_audit_exit(void);
+extern void tty_audit_push_task(struct task_struct *tsk);
+extern void tty_audit_opening(void);
+#else
+static inline void tty_audit_exit(void)
+{
+}
+static inline void tty_audit_push_task(struct task_struct *tsk)
+{
+}
+static inline void tty_audit_opening(void)
+{
+}
+#endif
 
 /* tty_ioctl.c */
 extern int n_tty_ioctl(struct tty_struct * tty, struct file * file,
diff --git a/kernel/audit.c b/kernel/audit.c
index d13276d..813dac4 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -58,6 +58,7 @@
 #include <linux/selinux.h>
 #include <linux/inotify.h>
 #include <linux/freezer.h>
+#include <linux/tty.h>
 
 #include "audit.h"
 
@@ -423,6 +424,32 @@ static int kauditd_thread(void *dummy)
 	return 0;
 }
 
+static int
+audit_prepare_user_tty(pid_t pid)
+{
+	struct task_struct *tsk;
+	int err;
+
+	read_lock(&tasklist_lock);
+	tsk = find_task_by_pid(pid);
+	err = -ESRCH;
+	if (!tsk)
+		goto out;
+	err = 0;
+
+	spin_lock(&tsk->sighand->siglock);
+	if (!tsk->signal->audit_tty)
+		err = -EPERM;
+	spin_unlock(&tsk->sighand->siglock);
+	if (err)
+		goto out;
+
+	tty_audit_push_task(tsk);
+out:
+	read_unlock(&tasklist_lock);
+	return err;
+}
+
 int audit_send_list(void *_dest)
 {
 	struct audit_netlink_list *dest = _dest;
@@ -511,12 +538,16 @@ static int audit_netlink_ok(struct sk_buff *skb, u16 msg_type)
 	case AUDIT_DEL:
 	case AUDIT_DEL_RULE:
 	case AUDIT_SIGNAL_INFO:
+	case AUDIT_TTY_GET:
+	case AUDIT_TTY_SET:
 		if (security_netlink_recv(skb, CAP_AUDIT_CONTROL))
 			err = -EPERM;
 		break;
 	case AUDIT_USER:
 	case AUDIT_FIRST_USER_MSG ... AUDIT_LAST_USER_MSG:
 	case AUDIT_FIRST_USER_MSG2 ... AUDIT_LAST_USER_MSG2:
+		if (msg_type == AUDIT_USER_TTY)
+			break;	/* Checked in audit_prepare_user_tty. */
 		if (security_netlink_recv(skb, CAP_AUDIT_WRITE))
 			err = -EPERM;
 		break;
@@ -622,6 +653,11 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
 		err = audit_filter_user(&NETLINK_CB(skb), msg_type);
 		if (err == 1) {
 			err = 0;
+			if (msg_type == AUDIT_USER_TTY) {
+				err = audit_prepare_user_tty(pid);
+				if (err)
+					break;
+			}
 			ab = audit_log_start(NULL, GFP_KERNEL, msg_type);
 			if (ab) {
 				audit_log_format(ab,
@@ -638,8 +674,17 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
 							" subj=%s", ctx);
 					kfree(ctx);
 				}
-				audit_log_format(ab, " msg='%.1024s'",
-					 (char *)data);
+				if (msg_type != AUDIT_USER_TTY)
+					audit_log_format(ab, " msg='%.1024s'",
+							 (char *)data);
+				else {
+					int len;
+
+					audit_log_format(ab, " msg=");
+					len = nlmsg_len(nlh);
+					audit_log_n_untrustedstring(ab, len,
+								    data);
+				}
 				audit_set_pid(ab, pid);
 				audit_log_end(ab);
 			}
@@ -730,6 +775,45 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
 				0, 0, sig_data, sizeof(*sig_data) + len);
 		kfree(sig_data);
 		break;
+	case AUDIT_TTY_GET: {
+		struct audit_tty_status s;
+		struct task_struct *tsk;
+
+		read_lock(&tasklist_lock);
+		tsk = find_task_by_pid(pid);
+		if (!tsk)
+			err = -ESRCH;
+		else {
+			spin_lock(&tsk->sighand->siglock);
+			s.enabled = tsk->signal->audit_tty != 0;
+			spin_unlock(&tsk->sighand->siglock);
+		}
+		read_unlock(&tasklist_lock);
+		audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_TTY_GET, 0, 0,
+				 &s, sizeof(s));
+		break;
+	}
+	case AUDIT_TTY_SET: {
+		struct audit_tty_status *s;
+		struct task_struct *tsk;
+
+		if (nlh->nlmsg_len < sizeof(struct audit_tty_status))
+			return -EINVAL;
+		s = data;
+		if (s->enabled != 0 && s->enabled != 1)
+			return -EINVAL;
+		read_lock(&tasklist_lock);
+		tsk = find_task_by_pid(pid);
+		if (!tsk)
+			err = -ESRCH;
+		else {
+			spin_lock(&tsk->sighand->siglock);
+			tsk->signal->audit_tty = s->enabled != 0;
+			spin_unlock(&tsk->sighand->siglock);
+		}
+		read_unlock(&tasklist_lock);
+		break;
+	}
 	default:
 		err = -EINVAL;
 		break;
@@ -1185,7 +1269,7 @@ static void audit_log_n_string(struct audit_buffer *ab, size_t slen,
 }
 
 /**
- * audit_log_n_unstrustedstring - log a string that may contain random characters
+ * audit_log_n_untrustedstring - log a string that may contain random characters
  * @ab: audit_buffer
  * @len: lenth of string (not including trailing null)
  * @string: string to be logged
@@ -1201,25 +1285,24 @@ static void audit_log_n_string(struct audit_buffer *ab, size_t slen,
 const char *audit_log_n_untrustedstring(struct audit_buffer *ab, size_t len,
 					const char *string)
 {
-	const unsigned char *p = string;
+	const unsigned char *p;
 
-	while (*p) {
+	for (p = string; p < (const unsigned char *)string + len && *p; p++) {
 		if (*p == '"' || *p < 0x21 || *p > 0x7f) {
 			audit_log_hex(ab, string, len);
 			return string + len + 1;
 		}
-		p++;
 	}
 	audit_log_n_string(ab, len, string);
 	return p + 1;
 }
 
 /**
- * audit_log_unstrustedstring - log a string that may contain random characters
+ * audit_log_untrustedstring - log a string that may contain random characters
  * @ab: audit_buffer
  * @string: string to be logged
  *
- * Same as audit_log_n_unstrustedstring(), except that strlen is used to
+ * Same as audit_log_n_untrustedstring(), except that strlen is used to
  * determine string length.
  */
 const char *audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
diff --git a/kernel/audit.h b/kernel/audit.h
index 815d6f5..9587743 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -115,7 +115,6 @@ extern struct sk_buff *	    audit_make_reply(int pid, int seq, int type,
 extern void		    audit_send_reply(int pid, int seq, int type,
 					     int done, int multi,
 					     void *payload, int size);
-extern void		    audit_log_lost(const char *message);
 extern void		    audit_panic(const char *message);
 
 struct audit_netlink_list {
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index e36481e..7ccc3da 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -71,9 +71,6 @@
 
 extern struct list_head audit_filter_list[];
 
-/* No syscall auditing will take place unless audit_enabled != 0. */
-extern int audit_enabled;
-
 /* AUDIT_NAMES is the number of slots we reserve in the audit_context
  * for saving names from getname(). */
 #define AUDIT_NAMES    20
diff --git a/kernel/exit.c b/kernel/exit.c
index 5b888c2..413f6df 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -922,6 +922,8 @@ fastcall NORET_TYPE void do_exit(long code)
 	if (unlikely(tsk->compat_robust_list))
 		compat_exit_robust_list(tsk);
 #endif
+	if (group_dead && unlikely(tsk->signal->tty_audit_buf))
+		tty_audit_exit();
 	if (unlikely(tsk->audit_context))
 		audit_free(tsk);
 
diff --git a/kernel/fork.c b/kernel/fork.c
index 73ad5cd..52c449f 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -897,6 +897,11 @@ static inline int copy_signal(unsigned long clone_flags, struct task_struct * ts
 	}
 	acct_init_pacct(&sig->pacct);
 
+	spin_lock(&current->sighand->siglock);
+	sig->audit_tty = current->signal->audit_tty;
+	spin_unlock(&current->sighand->siglock);
+	sig->tty_audit_buf = NULL;
+
 	return 0;
 }
 
diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c
index ccfe875..eddc7b4 100644
--- a/security/selinux/nlmsgtab.c
+++ b/security/selinux/nlmsgtab.c
@@ -110,6 +110,8 @@ static struct nlmsg_perm nlmsg_audit_perms[] =
 	{ AUDIT_DEL_RULE,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
 	{ AUDIT_USER,		NETLINK_AUDIT_SOCKET__NLMSG_RELAY    },
 	{ AUDIT_SIGNAL_INFO,	NETLINK_AUDIT_SOCKET__NLMSG_READ     },
+	{ AUDIT_TTY_GET,	NETLINK_AUDIT_SOCKET__NLMSG_READ     },
+	{ AUDIT_TTY_SET,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE    },
 };
 
 
diff -ur audit/lib/msg_typetab.h audit-1.5.3/lib/msg_typetab.h
--- audit/lib/msg_typetab.h	2007-04-12 20:20:57.000000000 +0200
+++ audit-1.5.3/lib/msg_typetab.h	2007-05-31 15:25:49.000000000 +0200
@@ -37,6 +37,8 @@
 _S(AUDIT_LIST_RULES,                 "LIST_RULES"                    )
 //_S(AUDIT_ADD_RULE,                 "ADD_RULE"                      )
 //_S(AUDIT_DEL_RULE,                 "DEL_RULE"                      )
+_S(AUDIT_TTY_GET,                    "TTY_GET"                       )
+_S(AUDIT_TTY_SET,                    "TTY_SET"                       )
 _S(AUDIT_USER_AUTH,                  "USER_AUTH"                     )
 _S(AUDIT_USER_ACCT,                  "USER_ACCT"                     )
 _S(AUDIT_USER_MGMT,                  "USER_MGMT"                     )
@@ -61,6 +63,7 @@
 _S(AUDIT_TRUSTED_APP,                "TRUSTED_APP"                   )
 _S(AUDIT_USER_SELINUX_ERR,           "USER_SELINUX_ERR"              )
 _S(AUDIT_USER_CMD,                   "USER_CMD"                      )
+_S(AUDIT_USER_TTY,                   "USER_TTY"                      )
 _S(AUDIT_DAEMON_START,               "DAEMON_START"                  )
 _S(AUDIT_DAEMON_END,                 "DAEMON_END"                    )
 _S(AUDIT_DAEMON_ABORT,               "DAEMON_ABORT"                  )
@@ -85,6 +88,7 @@
 _S(AUDIT_KERNEL_OTHER,               "KERNEL_OTHER"                  )
 _S(AUDIT_FD_PAIR,                    "FD_PAIR"                       )
 _S(AUDIT_OBJ_PID,                    "OBJ_PID"                       )
+_S(AUDIT_TTY,                        "TTY"                           )
 _S(AUDIT_AVC,                        "AVC"                           )
 _S(AUDIT_SELINUX_ERR,                "SELINUX_ERR"                   )
 _S(AUDIT_AVC_PATH,                   "AVC_PATH"                      )
#include <errno.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/socket.h>
#include <unistd.h>

#include <linux/netlink.h>
#include <security/pam_ext.h>
#include <security/pam_modules.h>
#include <security/pam_modutil.h>

#define DATANAME "pam_tty_audit_last_state"

/* FIXME */
struct audit_tty_status {
	__u32		enabled; /* 1 = enabled, 0 = disabled */
};

#define AUDIT_TTY_GET		1014	/* Get TTY auditing status */
#define AUDIT_TTY_SET		1015	/* Set TTY auditing status */
#define AUDIT_USER_TTY		1124	/* Non-ICANON TTY input meaning */

/* Open an audit netlink socket */
static int
nl_open (void)
{
  return socket (AF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
}

static int
nl_send (int fd, unsigned type, unsigned flags, const void *data, size_t size)
{
  struct sockaddr_nl addr;
  struct msghdr msg;
  struct nlmsghdr nlm;
  struct iovec iov[2];
  ssize_t res;

  nlm.nlmsg_len = NLMSG_LENGTH (size);
  nlm.nlmsg_type = type;
  nlm.nlmsg_flags = NLM_F_REQUEST | flags;
  nlm.nlmsg_seq = 0;
  nlm.nlmsg_pid = 0;
  iov[0].iov_base = &nlm;
  iov[0].iov_len = sizeof (nlm);
  iov[1].iov_base = (void *)data;
  iov[1].iov_len = size;
  addr.nl_family = AF_NETLINK;
  addr.nl_pid = 0;
  addr.nl_groups = 0;
  msg.msg_name = &addr;
  msg.msg_namelen = sizeof (addr);
  msg.msg_iov = iov;
  msg.msg_iovlen = 2;
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
  msg.msg_flags = 0;
  res = sendmsg (fd, &msg, 0);
  if (res == -1)
    return -1;
  if ((size_t)res != nlm.nlmsg_len)
    {
      errno = EIO;
      return -1;
    }
  return 0;
}

static int
nl_recv (int fd, unsigned type, void *buf, size_t size)
{
  struct sockaddr_nl addr;
  struct msghdr msg;
  struct nlmsghdr nlm;
  struct iovec iov[2];
  ssize_t res;

 again:
  iov[0].iov_base = &nlm;
  iov[0].iov_len = sizeof (nlm);
  msg.msg_name = &addr;
  msg.msg_namelen = sizeof (addr);
  msg.msg_iov = iov;
  msg.msg_iovlen = 1;
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
  if (type != NLMSG_ERROR)
    {
      res = recvmsg (fd, &msg, MSG_PEEK);
      if (res == -1)
	return -1;
      if (res != NLMSG_LENGTH (0))
	{
	  errno = EIO;
	  return -1;
	}
      if (nlm.nlmsg_type == NLMSG_ERROR)
	{
	  struct nlmsgerr err;

	  iov[1].iov_base = &err;
	  iov[1].iov_len = sizeof (err);
	  msg.msg_iovlen = 2;
	  res = recvmsg (fd, &msg, 0);
	  if (res == -1)
	    return -1;
	  if ((size_t)res != NLMSG_LENGTH (sizeof (err))
	      || nlm.nlmsg_type != NLMSG_ERROR)
	    {
	      errno = EIO;
	      return -1;
	    }
	  if (err.error == 0)
	    goto again;
	  errno = -err.error;
	  return -1;
	}
    }
  if (size != 0)
    {
      iov[1].iov_base = buf;
      iov[1].iov_len = size;
      msg.msg_iovlen = 2;
    }
  res = recvmsg (fd, &msg, 0);
  if (res == -1)
    return -1;
  if ((size_t)res != NLMSG_LENGTH (size)
      || nlm.nlmsg_type != type)
    {
      errno = EIO;
      return -1;
    }
  return 0;
}

static int
nl_recv_ack (int fd)
{
  struct nlmsgerr err;

  if (nl_recv (fd, NLMSG_ERROR, &err, sizeof (err)) != 0)
    return -1;
  if (err.error != 0)
    {
      errno = -err.error;
      return -1;
    }
  return 0;
}

static void
cleanup_old_status (pam_handle_t *pamh, void *data, int error_status)
{
  (void)pamh;
  (void)error_status;
  free (data);
}

int
pam_sm_open_session (pam_handle_t *pamh, int flags, int argc, const char **argv)
{
  enum command { CMD_NONE, CMD_ENABLE, CMD_DISABLE };

  enum command command;
  struct audit_tty_status *old_status, new_status;
  const char *user;
  uid_t user_uid;
  struct passwd *pwd;
  int i, fd;

  command = CMD_NONE;

  if (pam_get_user (pamh, &user, NULL) != PAM_SUCCESS)
    {
      pam_syslog (pamh, LOG_ERR, "error determining target user's name");
      return PAM_SESSION_ERR;
    }
  pwd = pam_modutil_getpwnam (pamh, user);
  if (pwd == NULL)
    {
      pam_syslog (pamh, LOG_ERR, "error determining target user's UID: %m");
      return PAM_SESSION_ERR;
    }
  user_uid = pwd->pw_uid;

  /* FIXME: a config file? */
  for (i = 0; i < argc; i++)
    {
      if (strncmp (argv[i], "enable=", 7) == 0
	  || strncmp (argv[i], "disable=", 8) == 0)
	{
	  enum command this_command;
	  char *copy, *tok_data, *tok;

	  this_command = *argv[i] == 'e' ? CMD_ENABLE : CMD_DISABLE;
	  copy = strdup (strchr (argv[i], '=') + 1);
	  if (copy == NULL)
	    return PAM_SESSION_ERR;
	  for (tok = strtok_r (copy, ",", &tok_data); tok != NULL;
	       tok = strtok_r (NULL, ",", &tok_data))
	    {
	      pwd = pam_modutil_getpwnam (pamh, tok);
	      if (pwd == NULL)
		{
		  pam_syslog (pamh, LOG_WARNING, "unknown user %s", tok);
		  continue;
		}
	      if (pwd->pw_uid == user_uid)
		{
		  command = this_command;
		  break;
		}
	    }
	  free (copy);
	}
    }

  if (command == CMD_NONE)
    return PAM_SUCCESS;

  old_status = malloc (sizeof (*old_status));
  if (old_status == NULL)
    return PAM_SESSION_ERR;

  fd = nl_open ();
  if (fd == -1
      || nl_send (fd, AUDIT_TTY_GET, 0, NULL, 0) != 0
      || nl_recv (fd, AUDIT_TTY_GET, old_status, sizeof (*old_status)) != 0)
    {
      pam_syslog (pamh, LOG_ERR, "error reading current audit status: %m");
      if (fd != -1)
	close (fd);
      free (old_status);
      return PAM_SESSION_ERR;
    }

  if (old_status->enabled == (command == CMD_ENABLE ? 1 : 0))
    {
      free (old_status);
      goto ok_fd;
    }

  if (pam_set_data (pamh, DATANAME, old_status, cleanup_old_status)
      != PAM_SUCCESS)
    {
      pam_syslog (pamh, LOG_ERR, "error saving old audit status");
      close (fd);
      free (old_status);
      return PAM_SESSION_ERR;
    }

  new_status.enabled = (command == CMD_ENABLE ? 1 : 0);
  if (nl_send (fd, AUDIT_TTY_SET, NLM_F_ACK, &new_status,
	       sizeof (new_status)) != 0
      || nl_recv_ack (fd) != 0)
    {
      pam_syslog (pamh, LOG_ERR, "error setting current audit status: %m");
      close (fd);
      return PAM_SESSION_ERR;
    }
  /* Fall through */
 ok_fd:
  close (fd);
  /* FIXME */
  pam_syslog (pamh, LOG_ERR, "changed status from %d to %d",
	      old_status->enabled, new_status.enabled);
  return PAM_SUCCESS;
}

int
pam_sm_close_session (pam_handle_t *pamh, int flags, int argc,
		      const char **argv)
{
  const void *status_;

  if (pam_get_data (pamh, DATANAME, &status_) == PAM_SUCCESS)
    {
      const struct audit_tty_status *status;
      int fd;

      status = status_;

      fd = nl_open ();
      if (fd == -1
	  || nl_send (fd, AUDIT_TTY_SET, NLM_F_ACK, status,
		      sizeof (*status)) != 0
	  || nl_recv_ack (fd) != 0)
	{
	  pam_syslog (pamh, LOG_ERR, "error restoring audit status: %m");
	  if (fd != -1)
	    close (fd);
	  return PAM_SESSION_ERR;
	}
      close (fd);
      pam_syslog (pamh, LOG_ERR, "restored status to %d", status->enabled);
    }
  return PAM_SUCCESS;
}
--- readline-5.2/readline.c.audit	2006-08-16 21:00:36.000000000 +0200
+++ readline-5.2/readline.c	2007-06-01 18:06:26.000000000 +0200
@@ -50,6 +50,9 @@
 #include <stdio.h>
 #include "posixjmp.h"
 #include <errno.h>
+#include <sys/socket.h>
+#include <linux/audit.h>
+#include <linux/netlink.h>
 
 #if !defined (errno)
 extern int errno;
@@ -292,7 +295,45 @@ rl_set_prompt (prompt)
   rl_visible_prompt_length = rl_expand_prompt (rl_prompt);
   return 0;
 }
-  
+
+/* Report STRING to the audit system. */
+static void
+audit_tty (char *string)
+{
+  struct sockaddr_nl addr;
+  struct msghdr msg;
+  struct nlmsghdr nlm;
+  struct iovec iov[2];
+  size_t size;
+  int fd;
+
+  size = strlen (string) + 1;
+  fd = socket (AF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
+  if (fd < 0)
+    return;
+  nlm.nlmsg_len = NLMSG_LENGTH (size);
+  nlm.nlmsg_type = AUDIT_USER_TTY;
+  nlm.nlmsg_flags = NLM_F_REQUEST;
+  nlm.nlmsg_seq = 0;
+  nlm.nlmsg_pid = 0;
+  iov[0].iov_base = &nlm;
+  iov[0].iov_len = sizeof (nlm);
+  iov[1].iov_base = string;
+  iov[1].iov_len = size;
+  addr.nl_family = AF_NETLINK;
+  addr.nl_pid = 0;
+  addr.nl_groups = 0;
+  msg.msg_name = &addr;
+  msg.msg_namelen = sizeof (addr);
+  msg.msg_iov = iov;
+  msg.msg_iovlen = 2;
+  msg.msg_control = NULL;
+  msg.msg_controllen = 0;
+  msg.msg_flags = 0;
+  (void)sendmsg (fd, &msg, 0);
+  close (fd);
+}
+
 /* Read a line of input.  Prompt with PROMPT.  An empty PROMPT means
    none.  A return value of NULL means that EOF was encountered. */
 char *
@@ -326,6 +367,9 @@ readline (prompt)
   rl_clear_signals ();
 #endif
 
+  if (value != NULL)
+    audit_tty (value);
+
   return (value);
 }
 
--
Linux-audit mailing list
Linux-audit@redhat.com
https://www.redhat.com/mailman/listinfo/linux-audit

Reply via email to