aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/tty_audit.c
diff options
context:
space:
mode:
authorMiloslav Trmac <mitr@redhat.com>2007-07-16 02:40:56 -0400
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-07-16 12:05:47 -0400
commit522ed7767e800cff6c650ec64b0ee0677303119c (patch)
treef65ecb29f2cf885018d3557f840de3ef4be6ec64 /drivers/char/tty_audit.c
parent4f27c00bf80f122513d3a5be16ed851573164534 (diff)
Audit: add TTY input auditing
Add TTY input auditing, used to audit system administrator's actions. This is required by various security standards such as DCID 6/3 and PCI to provide non-repudiation of administrator's actions and to allow a review of past actions if the administrator seems to overstep their duties or if the system becomes misconfigured for unknown reasons. These requirements do not make it necessary to audit TTY output as well. Compared to an user-space keylogger, this approach records TTY input using the audit subsystem, correlated with other audit events, and it is completely transparent to the user-space application (e.g. the console ioctls still work). TTY input auditing works on a higher level than auditing all system calls within the session, which would produce an overwhelming amount of mostly useless audit events. Add an "audit_tty" attribute, inherited across fork (). Data read from TTYs by process with the attribute is sent to the audit subsystem by the kernel. The audit netlink interface is extended to allow modifying the audit_tty attribute, and to allow sending explanatory audit events from user-space (for example, a shell might send an event containing the final command, after the interactive command-line editing and history expansion is performed, which might be difficult to decipher from the TTY input alone). Because the "audit_tty" attribute is inherited across fork (), it would be set e.g. for sshd restarted within an audited session. To prevent this, the audit_tty attribute is cleared when a process with no open TTY file descriptors (e.g. after daemon startup) opens a TTY. See https://www.redhat.com/archives/linux-audit/2007-June/msg00000.html for a more detailed rationale document for an older version of this patch. [akpm@linux-foundation.org: build fix] Signed-off-by: Miloslav Trmac <mitr@redhat.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Cc: Paul Fulghum <paulkf@microgate.com> Cc: Casey Schaufler <casey@schaufler-ca.com> Cc: Steve Grubb <sgrubb@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/char/tty_audit.c')
-rw-r--r--drivers/char/tty_audit.c345
1 files changed, 345 insertions, 0 deletions
diff --git a/drivers/char/tty_audit.c b/drivers/char/tty_audit.c
new file mode 100644
index 000000000000..d222012c1b0c
--- /dev/null
+++ b/drivers/char/tty_audit.c
@@ -0,0 +1,345 @@
1/*
2 * Creating audit events from TTY input.
3 *
4 * Copyright (C) 2007 Red Hat, Inc. All rights reserved. This copyrighted
5 * material is made available to anyone wishing to use, modify, copy, or
6 * redistribute it subject to the terms and conditions of the GNU General
7 * Public License v.2.
8 *
9 * Authors: Miloslav Trmac <mitr@redhat.com>
10 */
11
12#include <linux/audit.h>
13#include <linux/file.h>
14#include <linux/tty.h>
15
16struct tty_audit_buf {
17 atomic_t count;
18 struct mutex mutex; /* Protects all data below */
19 int major, minor; /* The TTY which the data is from */
20 unsigned icanon:1;
21 size_t valid;
22 unsigned char *data; /* Allocated size N_TTY_BUF_SIZE */
23};
24
25static struct tty_audit_buf *tty_audit_buf_alloc(int major, int minor,
26 int icanon)
27{
28 struct tty_audit_buf *buf;
29
30 buf = kmalloc(sizeof (*buf), GFP_KERNEL);
31 if (!buf)
32 goto err;
33 if (PAGE_SIZE != N_TTY_BUF_SIZE)
34 buf->data = kmalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
35 else
36 buf->data = (unsigned char *)__get_free_page(GFP_KERNEL);
37 if (!buf->data)
38 goto err_buf;
39 atomic_set(&buf->count, 1);
40 mutex_init(&buf->mutex);
41 buf->major = major;
42 buf->minor = minor;
43 buf->icanon = icanon;
44 buf->valid = 0;
45 return buf;
46
47err_buf:
48 kfree(buf);
49err:
50 return NULL;
51}
52
53static void tty_audit_buf_free(struct tty_audit_buf *buf)
54{
55 WARN_ON(buf->valid != 0);
56 if (PAGE_SIZE != N_TTY_BUF_SIZE)
57 kfree(buf->data);
58 else
59 free_page((unsigned long)buf->data);
60 kfree(buf);
61}
62
63static void tty_audit_buf_put(struct tty_audit_buf *buf)
64{
65 if (atomic_dec_and_test(&buf->count))
66 tty_audit_buf_free(buf);
67}
68
69/**
70 * tty_audit_buf_push - Push buffered data out
71 *
72 * Generate an audit message from the contents of @buf, which is owned by
73 * @tsk with @loginuid. @buf->mutex must be locked.
74 */
75static void tty_audit_buf_push(struct task_struct *tsk, uid_t loginuid,
76 struct tty_audit_buf *buf)
77{
78 struct audit_buffer *ab;
79
80 if (buf->valid == 0)
81 return;
82 if (audit_enabled == 0)
83 return;
84 ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_TTY);
85 if (ab) {
86 char name[sizeof(tsk->comm)];
87
88 audit_log_format(ab, "tty pid=%u uid=%u auid=%u major=%d "
89 "minor=%d comm=", tsk->pid, tsk->uid,
90 loginuid, buf->major, buf->minor);
91 get_task_comm(name, tsk);
92 audit_log_untrustedstring(ab, name);
93 audit_log_format(ab, " data=");
94 audit_log_n_untrustedstring(ab, buf->valid, buf->data);
95 audit_log_end(ab);
96 }
97 buf->valid = 0;
98}
99
100/**
101 * tty_audit_buf_push_current - Push buffered data out
102 *
103 * Generate an audit message from the contents of @buf, which is owned by
104 * the current task. @buf->mutex must be locked.
105 */
106static void tty_audit_buf_push_current(struct tty_audit_buf *buf)
107{
108 tty_audit_buf_push(current, audit_get_loginuid(current->audit_context),
109 buf);
110}
111
112/**
113 * tty_audit_exit - Handle a task exit
114 *
115 * Make sure all buffered data is written out and deallocate the buffer.
116 * Only needs to be called if current->signal->tty_audit_buf != %NULL.
117 */
118void tty_audit_exit(void)
119{
120 struct tty_audit_buf *buf;
121
122 spin_lock_irq(&current->sighand->siglock);
123 buf = current->signal->tty_audit_buf;
124 current->signal->tty_audit_buf = NULL;
125 spin_unlock_irq(&current->sighand->siglock);
126 if (!buf)
127 return;
128
129 mutex_lock(&buf->mutex);
130 tty_audit_buf_push_current(buf);
131 mutex_unlock(&buf->mutex);
132
133 tty_audit_buf_put(buf);
134}
135
136/**
137 * tty_audit_fork - Copy TTY audit state for a new task
138 *
139 * Set up TTY audit state in @sig from current. @sig needs no locking.
140 */
141void tty_audit_fork(struct signal_struct *sig)
142{
143 spin_lock_irq(&current->sighand->siglock);
144 sig->audit_tty = current->signal->audit_tty;
145 spin_unlock_irq(&current->sighand->siglock);
146 sig->tty_audit_buf = NULL;
147}
148
149/**
150 * tty_audit_push_task - Flush task's pending audit data
151 */
152void tty_audit_push_task(struct task_struct *tsk, uid_t loginuid)
153{
154 struct tty_audit_buf *buf;
155
156 spin_lock_irq(&tsk->sighand->siglock);
157 buf = tsk->signal->tty_audit_buf;
158 if (buf)
159 atomic_inc(&buf->count);
160 spin_unlock_irq(&tsk->sighand->siglock);
161 if (!buf)
162 return;
163
164 mutex_lock(&buf->mutex);
165 tty_audit_buf_push(tsk, loginuid, buf);
166 mutex_unlock(&buf->mutex);
167
168 tty_audit_buf_put(buf);
169}
170
171/**
172 * tty_audit_buf_get - Get an audit buffer.
173 *
174 * Get an audit buffer for @tty, allocate it if necessary. Return %NULL
175 * if TTY auditing is disabled or out of memory. Otherwise, return a new
176 * reference to the buffer.
177 */
178static struct tty_audit_buf *tty_audit_buf_get(struct tty_struct *tty)
179{
180 struct tty_audit_buf *buf, *buf2;
181
182 buf = NULL;
183 buf2 = NULL;
184 spin_lock_irq(&current->sighand->siglock);
185 if (likely(!current->signal->audit_tty))
186 goto out;
187 buf = current->signal->tty_audit_buf;
188 if (buf) {
189 atomic_inc(&buf->count);
190 goto out;
191 }
192 spin_unlock_irq(&current->sighand->siglock);
193
194 buf2 = tty_audit_buf_alloc(tty->driver->major,
195 tty->driver->minor_start + tty->index,
196 tty->icanon);
197 if (buf2 == NULL) {
198 audit_log_lost("out of memory in TTY auditing");
199 return NULL;
200 }
201
202 spin_lock_irq(&current->sighand->siglock);
203 if (!current->signal->audit_tty)
204 goto out;
205 buf = current->signal->tty_audit_buf;
206 if (!buf) {
207 current->signal->tty_audit_buf = buf2;
208 buf = buf2;
209 buf2 = NULL;
210 }
211 atomic_inc(&buf->count);
212 /* Fall through */
213 out:
214 spin_unlock_irq(&current->sighand->siglock);
215 if (buf2)
216 tty_audit_buf_free(buf2);
217 return buf;
218}
219
220/**
221 * tty_audit_add_data - Add data for TTY auditing.
222 *
223 * Audit @data of @size from @tty, if necessary.
224 */
225void tty_audit_add_data(struct tty_struct *tty, unsigned char *data,
226 size_t size)
227{
228 struct tty_audit_buf *buf;
229 int major, minor;
230
231 if (unlikely(size == 0))
232 return;
233
234 buf = tty_audit_buf_get(tty);
235 if (!buf)
236 return;
237
238 mutex_lock(&buf->mutex);
239 major = tty->driver->major;
240 minor = tty->driver->minor_start + tty->index;
241 if (buf->major != major || buf->minor != minor
242 || buf->icanon != tty->icanon) {
243 tty_audit_buf_push_current(buf);
244 buf->major = major;
245 buf->minor = minor;
246 buf->icanon = tty->icanon;
247 }
248 do {
249 size_t run;
250
251 run = N_TTY_BUF_SIZE - buf->valid;
252 if (run > size)
253 run = size;
254 memcpy(buf->data + buf->valid, data, run);
255 buf->valid += run;
256 data += run;
257 size -= run;
258 if (buf->valid == N_TTY_BUF_SIZE)
259 tty_audit_buf_push_current(buf);
260 } while (size != 0);
261 mutex_unlock(&buf->mutex);
262 tty_audit_buf_put(buf);
263}
264
265/**
266 * tty_audit_push - Push buffered data out
267 *
268 * Make sure no audit data is pending for @tty on the current process.
269 */
270void tty_audit_push(struct tty_struct *tty)
271{
272 struct tty_audit_buf *buf;
273
274 spin_lock_irq(&current->sighand->siglock);
275 if (likely(!current->signal->audit_tty)) {
276 spin_unlock_irq(&current->sighand->siglock);
277 return;
278 }
279 buf = current->signal->tty_audit_buf;
280 if (buf)
281 atomic_inc(&buf->count);
282 spin_unlock_irq(&current->sighand->siglock);
283
284 if (buf) {
285 int major, minor;
286
287 major = tty->driver->major;
288 minor = tty->driver->minor_start + tty->index;
289 mutex_lock(&buf->mutex);
290 if (buf->major == major && buf->minor == minor)
291 tty_audit_buf_push_current(buf);
292 mutex_unlock(&buf->mutex);
293 tty_audit_buf_put(buf);
294 }
295}
296
297/**
298 * tty_audit_opening - A TTY is being opened.
299 *
300 * As a special hack, tasks that close all their TTYs and open new ones
301 * are assumed to be system daemons (e.g. getty) and auditing is
302 * automatically disabled for them.
303 */
304void tty_audit_opening(void)
305{
306 int disable;
307
308 disable = 1;
309 spin_lock_irq(&current->sighand->siglock);
310 if (current->signal->audit_tty == 0)
311 disable = 0;
312 spin_unlock_irq(&current->sighand->siglock);
313 if (!disable)
314 return;
315
316 task_lock(current);
317 if (current->files) {
318 struct fdtable *fdt;
319 unsigned i;
320
321 /*
322 * We don't take a ref to the file, so we must hold ->file_lock
323 * instead.
324 */
325 spin_lock(&current->files->file_lock);
326 fdt = files_fdtable(current->files);
327 for (i = 0; i < fdt->max_fds; i++) {
328 struct file *filp;
329
330 filp = fcheck_files(current->files, i);
331 if (filp && is_tty(filp)) {
332 disable = 0;
333 break;
334 }
335 }
336 spin_unlock(&current->files->file_lock);
337 }
338 task_unlock(current);
339 if (!disable)
340 return;
341
342 spin_lock_irq(&current->sighand->siglock);
343 current->signal->audit_tty = 0;
344 spin_unlock_irq(&current->sighand->siglock);
345}