aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/audit_watch.c
diff options
context:
space:
mode:
authorEric Paris <eparis@redhat.com>2009-06-11 14:31:36 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2009-06-23 23:50:59 -0400
commitcfcad62c74abfef83762dc05a556d21bdf3980a2 (patch)
treed253dbf8dfa4d31379dcd886cc1b41c69921acdd /kernel/audit_watch.c
parentea7ae60bfe39aeedfb29571c47280bf0067ee5f3 (diff)
audit: seperate audit inode watches into a subfile
In preparation for converting audit to use fsnotify instead of inotify we seperate the inode watching code into it's own file. This is similar to how the audit tree watching code is already seperated into audit_tree.c Signed-off-by: Eric Paris <eparis@redhat.com>
Diffstat (limited to 'kernel/audit_watch.c')
-rw-r--r--kernel/audit_watch.c534
1 files changed, 534 insertions, 0 deletions
diff --git a/kernel/audit_watch.c b/kernel/audit_watch.c
new file mode 100644
index 000000000000..da8be6d39c1a
--- /dev/null
+++ b/kernel/audit_watch.c
@@ -0,0 +1,534 @@
1/* audit_watch.c -- watching inodes
2 *
3 * Copyright 2003-2009 Red Hat, Inc.
4 * Copyright 2005 Hewlett-Packard Development Company, L.P.
5 * Copyright 2005 IBM Corporation
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22#include <linux/kernel.h>
23#include <linux/audit.h>
24#include <linux/kthread.h>
25#include <linux/mutex.h>
26#include <linux/fs.h>
27#include <linux/namei.h>
28#include <linux/netlink.h>
29#include <linux/sched.h>
30#include <linux/inotify.h>
31#include <linux/security.h>
32#include "audit.h"
33
34/*
35 * Reference counting:
36 *
37 * audit_parent: lifetime is from audit_init_parent() to receipt of an IN_IGNORED
38 * event. Each audit_watch holds a reference to its associated parent.
39 *
40 * audit_watch: if added to lists, lifetime is from audit_init_watch() to
41 * audit_remove_watch(). Additionally, an audit_watch may exist
42 * temporarily to assist in searching existing filter data. Each
43 * audit_krule holds a reference to its associated watch.
44 */
45
46struct audit_watch {
47 atomic_t count; /* reference count */
48 char *path; /* insertion path */
49 dev_t dev; /* associated superblock device */
50 unsigned long ino; /* associated inode number */
51 struct audit_parent *parent; /* associated parent */
52 struct list_head wlist; /* entry in parent->watches list */
53 struct list_head rules; /* associated rules */
54};
55
56struct audit_parent {
57 struct list_head ilist; /* entry in inotify registration list */
58 struct list_head watches; /* associated watches */
59 struct inotify_watch wdata; /* inotify watch data */
60 unsigned flags; /* status flags */
61};
62
63/* Inotify handle. */
64struct inotify_handle *audit_ih;
65
66/*
67 * audit_parent status flags:
68 *
69 * AUDIT_PARENT_INVALID - set anytime rules/watches are auto-removed due to
70 * a filesystem event to ensure we're adding audit watches to a valid parent.
71 * Technically not needed for IN_DELETE_SELF or IN_UNMOUNT events, as we cannot
72 * receive them while we have nameidata, but must be used for IN_MOVE_SELF which
73 * we can receive while holding nameidata.
74 */
75#define AUDIT_PARENT_INVALID 0x001
76
77/* Inotify events we care about. */
78#define AUDIT_IN_WATCH IN_MOVE|IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF
79
80static void audit_free_parent(struct inotify_watch *i_watch)
81{
82 struct audit_parent *parent;
83
84 parent = container_of(i_watch, struct audit_parent, wdata);
85 WARN_ON(!list_empty(&parent->watches));
86 kfree(parent);
87}
88
89void audit_get_watch(struct audit_watch *watch)
90{
91 atomic_inc(&watch->count);
92}
93
94void audit_put_watch(struct audit_watch *watch)
95{
96 if (atomic_dec_and_test(&watch->count)) {
97 WARN_ON(watch->parent);
98 WARN_ON(!list_empty(&watch->rules));
99 kfree(watch->path);
100 kfree(watch);
101 }
102}
103
104void audit_remove_watch(struct audit_watch *watch)
105{
106 list_del(&watch->wlist);
107 put_inotify_watch(&watch->parent->wdata);
108 watch->parent = NULL;
109 audit_put_watch(watch); /* match initial get */
110}
111
112char *audit_watch_path(struct audit_watch *watch)
113{
114 return watch->path;
115}
116
117struct list_head *audit_watch_rules(struct audit_watch *watch)
118{
119 return &watch->rules;
120}
121
122unsigned long audit_watch_inode(struct audit_watch *watch)
123{
124 return watch->ino;
125}
126
127dev_t audit_watch_dev(struct audit_watch *watch)
128{
129 return watch->dev;
130}
131
132/* Initialize a parent watch entry. */
133static struct audit_parent *audit_init_parent(struct nameidata *ndp)
134{
135 struct audit_parent *parent;
136 s32 wd;
137
138 parent = kzalloc(sizeof(*parent), GFP_KERNEL);
139 if (unlikely(!parent))
140 return ERR_PTR(-ENOMEM);
141
142 INIT_LIST_HEAD(&parent->watches);
143 parent->flags = 0;
144
145 inotify_init_watch(&parent->wdata);
146 /* grab a ref so inotify watch hangs around until we take audit_filter_mutex */
147 get_inotify_watch(&parent->wdata);
148 wd = inotify_add_watch(audit_ih, &parent->wdata,
149 ndp->path.dentry->d_inode, AUDIT_IN_WATCH);
150 if (wd < 0) {
151 audit_free_parent(&parent->wdata);
152 return ERR_PTR(wd);
153 }
154
155 return parent;
156}
157
158/* Initialize a watch entry. */
159static struct audit_watch *audit_init_watch(char *path)
160{
161 struct audit_watch *watch;
162
163 watch = kzalloc(sizeof(*watch), GFP_KERNEL);
164 if (unlikely(!watch))
165 return ERR_PTR(-ENOMEM);
166
167 INIT_LIST_HEAD(&watch->rules);
168 atomic_set(&watch->count, 1);
169 watch->path = path;
170 watch->dev = (dev_t)-1;
171 watch->ino = (unsigned long)-1;
172
173 return watch;
174}
175
176/* Translate a watch string to kernel respresentation. */
177int audit_to_watch(struct audit_krule *krule, char *path, int len, u32 op)
178{
179 struct audit_watch *watch;
180
181 if (!audit_ih)
182 return -EOPNOTSUPP;
183
184 if (path[0] != '/' || path[len-1] == '/' ||
185 krule->listnr != AUDIT_FILTER_EXIT ||
186 op != Audit_equal ||
187 krule->inode_f || krule->watch || krule->tree)
188 return -EINVAL;
189
190 watch = audit_init_watch(path);
191 if (IS_ERR(watch))
192 return PTR_ERR(watch);
193
194 audit_get_watch(watch);
195 krule->watch = watch;
196
197 return 0;
198}
199
200/* Duplicate the given audit watch. The new watch's rules list is initialized
201 * to an empty list and wlist is undefined. */
202static struct audit_watch *audit_dupe_watch(struct audit_watch *old)
203{
204 char *path;
205 struct audit_watch *new;
206
207 path = kstrdup(old->path, GFP_KERNEL);
208 if (unlikely(!path))
209 return ERR_PTR(-ENOMEM);
210
211 new = audit_init_watch(path);
212 if (IS_ERR(new)) {
213 kfree(path);
214 goto out;
215 }
216
217 new->dev = old->dev;
218 new->ino = old->ino;
219 get_inotify_watch(&old->parent->wdata);
220 new->parent = old->parent;
221
222out:
223 return new;
224}
225
226static void audit_watch_log_rule_change(struct audit_krule *r, struct audit_watch *w, char *op)
227{
228 if (audit_enabled) {
229 struct audit_buffer *ab;
230 ab = audit_log_start(NULL, GFP_NOFS, AUDIT_CONFIG_CHANGE);
231 audit_log_format(ab, "auid=%u ses=%u op=",
232 audit_get_loginuid(current),
233 audit_get_sessionid(current));
234 audit_log_string(ab, op);
235 audit_log_format(ab, " path=");
236 audit_log_untrustedstring(ab, w->path);
237 if (r->filterkey) {
238 audit_log_format(ab, " key=");
239 audit_log_untrustedstring(ab, r->filterkey);
240 } else
241 audit_log_format(ab, " key=(null)");
242 audit_log_format(ab, " list=%d res=1", r->listnr);
243 audit_log_end(ab);
244 }
245}
246
247/* Update inode info in audit rules based on filesystem event. */
248static void audit_update_watch(struct audit_parent *parent,
249 const char *dname, dev_t dev,
250 unsigned long ino, unsigned invalidating)
251{
252 struct audit_watch *owatch, *nwatch, *nextw;
253 struct audit_krule *r, *nextr;
254 struct audit_entry *oentry, *nentry;
255
256 mutex_lock(&audit_filter_mutex);
257 list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) {
258 if (audit_compare_dname_path(dname, owatch->path, NULL))
259 continue;
260
261 /* If the update involves invalidating rules, do the inode-based
262 * filtering now, so we don't omit records. */
263 if (invalidating && current->audit_context)
264 audit_filter_inodes(current, current->audit_context);
265
266 nwatch = audit_dupe_watch(owatch);
267 if (IS_ERR(nwatch)) {
268 mutex_unlock(&audit_filter_mutex);
269 audit_panic("error updating watch, skipping");
270 return;
271 }
272 nwatch->dev = dev;
273 nwatch->ino = ino;
274
275 list_for_each_entry_safe(r, nextr, &owatch->rules, rlist) {
276
277 oentry = container_of(r, struct audit_entry, rule);
278 list_del(&oentry->rule.rlist);
279 list_del_rcu(&oentry->list);
280
281 nentry = audit_dupe_rule(&oentry->rule, nwatch);
282 if (IS_ERR(nentry)) {
283 list_del(&oentry->rule.list);
284 audit_panic("error updating watch, removing");
285 } else {
286 int h = audit_hash_ino((u32)ino);
287 list_add(&nentry->rule.rlist, &nwatch->rules);
288 list_add_rcu(&nentry->list, &audit_inode_hash[h]);
289 list_replace(&oentry->rule.list,
290 &nentry->rule.list);
291 }
292
293 audit_watch_log_rule_change(r, owatch, "updated rules");
294
295 call_rcu(&oentry->rcu, audit_free_rule_rcu);
296 }
297
298 audit_remove_watch(owatch);
299 goto add_watch_to_parent; /* event applies to a single watch */
300 }
301 mutex_unlock(&audit_filter_mutex);
302 return;
303
304add_watch_to_parent:
305 list_add(&nwatch->wlist, &parent->watches);
306 mutex_unlock(&audit_filter_mutex);
307 return;
308}
309
310/* Remove all watches & rules associated with a parent that is going away. */
311static void audit_remove_parent_watches(struct audit_parent *parent)
312{
313 struct audit_watch *w, *nextw;
314 struct audit_krule *r, *nextr;
315 struct audit_entry *e;
316
317 mutex_lock(&audit_filter_mutex);
318 parent->flags |= AUDIT_PARENT_INVALID;
319 list_for_each_entry_safe(w, nextw, &parent->watches, wlist) {
320 list_for_each_entry_safe(r, nextr, &w->rules, rlist) {
321 e = container_of(r, struct audit_entry, rule);
322 audit_watch_log_rule_change(r, w, "remove rule");
323 list_del(&r->rlist);
324 list_del(&r->list);
325 list_del_rcu(&e->list);
326 call_rcu(&e->rcu, audit_free_rule_rcu);
327 }
328 audit_remove_watch(w);
329 }
330 mutex_unlock(&audit_filter_mutex);
331}
332
333/* Unregister inotify watches for parents on in_list.
334 * Generates an IN_IGNORED event. */
335void audit_inotify_unregister(struct list_head *in_list)
336{
337 struct audit_parent *p, *n;
338
339 list_for_each_entry_safe(p, n, in_list, ilist) {
340 list_del(&p->ilist);
341 inotify_rm_watch(audit_ih, &p->wdata);
342 /* the unpin matching the pin in audit_do_del_rule() */
343 unpin_inotify_watch(&p->wdata);
344 }
345}
346
347/* Get path information necessary for adding watches. */
348int audit_get_nd(char *path, struct nameidata **ndp, struct nameidata **ndw)
349{
350 struct nameidata *ndparent, *ndwatch;
351 int err;
352
353 ndparent = kmalloc(sizeof(*ndparent), GFP_KERNEL);
354 if (unlikely(!ndparent))
355 return -ENOMEM;
356
357 ndwatch = kmalloc(sizeof(*ndwatch), GFP_KERNEL);
358 if (unlikely(!ndwatch)) {
359 kfree(ndparent);
360 return -ENOMEM;
361 }
362
363 err = path_lookup(path, LOOKUP_PARENT, ndparent);
364 if (err) {
365 kfree(ndparent);
366 kfree(ndwatch);
367 return err;
368 }
369
370 err = path_lookup(path, 0, ndwatch);
371 if (err) {
372 kfree(ndwatch);
373 ndwatch = NULL;
374 }
375
376 *ndp = ndparent;
377 *ndw = ndwatch;
378
379 return 0;
380}
381
382/* Release resources used for watch path information. */
383void audit_put_nd(struct nameidata *ndp, struct nameidata *ndw)
384{
385 if (ndp) {
386 path_put(&ndp->path);
387 kfree(ndp);
388 }
389 if (ndw) {
390 path_put(&ndw->path);
391 kfree(ndw);
392 }
393}
394
395/* Associate the given rule with an existing parent inotify_watch.
396 * Caller must hold audit_filter_mutex. */
397static void audit_add_to_parent(struct audit_krule *krule,
398 struct audit_parent *parent)
399{
400 struct audit_watch *w, *watch = krule->watch;
401 int watch_found = 0;
402
403 list_for_each_entry(w, &parent->watches, wlist) {
404 if (strcmp(watch->path, w->path))
405 continue;
406
407 watch_found = 1;
408
409 /* put krule's and initial refs to temporary watch */
410 audit_put_watch(watch);
411 audit_put_watch(watch);
412
413 audit_get_watch(w);
414 krule->watch = watch = w;
415 break;
416 }
417
418 if (!watch_found) {
419 get_inotify_watch(&parent->wdata);
420 watch->parent = parent;
421
422 list_add(&watch->wlist, &parent->watches);
423 }
424 list_add(&krule->rlist, &watch->rules);
425}
426
427/* Find a matching watch entry, or add this one.
428 * Caller must hold audit_filter_mutex. */
429int audit_add_watch(struct audit_krule *krule, struct nameidata *ndp,
430 struct nameidata *ndw)
431{
432 struct audit_watch *watch = krule->watch;
433 struct inotify_watch *i_watch;
434 struct audit_parent *parent;
435 int ret = 0;
436
437 /* update watch filter fields */
438 if (ndw) {
439 watch->dev = ndw->path.dentry->d_inode->i_sb->s_dev;
440 watch->ino = ndw->path.dentry->d_inode->i_ino;
441 }
442
443 /* The audit_filter_mutex must not be held during inotify calls because
444 * we hold it during inotify event callback processing. If an existing
445 * inotify watch is found, inotify_find_watch() grabs a reference before
446 * returning.
447 */
448 mutex_unlock(&audit_filter_mutex);
449
450 if (inotify_find_watch(audit_ih, ndp->path.dentry->d_inode,
451 &i_watch) < 0) {
452 parent = audit_init_parent(ndp);
453 if (IS_ERR(parent)) {
454 /* caller expects mutex locked */
455 mutex_lock(&audit_filter_mutex);
456 return PTR_ERR(parent);
457 }
458 } else
459 parent = container_of(i_watch, struct audit_parent, wdata);
460
461 mutex_lock(&audit_filter_mutex);
462
463 /* parent was moved before we took audit_filter_mutex */
464 if (parent->flags & AUDIT_PARENT_INVALID)
465 ret = -ENOENT;
466 else
467 audit_add_to_parent(krule, parent);
468
469 /* match get in audit_init_parent or inotify_find_watch */
470 put_inotify_watch(&parent->wdata);
471 return ret;
472}
473
474void audit_remove_watch_rule(struct audit_krule *krule, struct list_head *list)
475{
476 struct audit_watch *watch = krule->watch;
477 struct audit_parent *parent = watch->parent;
478
479 list_del(&krule->rlist);
480
481 if (list_empty(&watch->rules)) {
482 audit_remove_watch(watch);
483
484 if (list_empty(&parent->watches)) {
485 /* Put parent on the inotify un-registration
486 * list. Grab a reference before releasing
487 * audit_filter_mutex, to be released in
488 * audit_inotify_unregister().
489 * If filesystem is going away, just leave
490 * the sucker alone, eviction will take
491 * care of it. */
492 if (pin_inotify_watch(&parent->wdata))
493 list_add(&parent->ilist, list);
494 }
495 }
496}
497
498/* Update watch data in audit rules based on inotify events. */
499static void audit_handle_ievent(struct inotify_watch *i_watch, u32 wd, u32 mask,
500 u32 cookie, const char *dname, struct inode *inode)
501{
502 struct audit_parent *parent;
503
504 parent = container_of(i_watch, struct audit_parent, wdata);
505
506 if (mask & (IN_CREATE|IN_MOVED_TO) && inode)
507 audit_update_watch(parent, dname, inode->i_sb->s_dev,
508 inode->i_ino, 0);
509 else if (mask & (IN_DELETE|IN_MOVED_FROM))
510 audit_update_watch(parent, dname, (dev_t)-1, (unsigned long)-1, 1);
511 /* inotify automatically removes the watch and sends IN_IGNORED */
512 else if (mask & (IN_DELETE_SELF|IN_UNMOUNT))
513 audit_remove_parent_watches(parent);
514 /* inotify does not remove the watch, so remove it manually */
515 else if(mask & IN_MOVE_SELF) {
516 audit_remove_parent_watches(parent);
517 inotify_remove_watch_locked(audit_ih, i_watch);
518 } else if (mask & IN_IGNORED)
519 put_inotify_watch(i_watch);
520}
521
522static const struct inotify_operations audit_inotify_ops = {
523 .handle_event = audit_handle_ievent,
524 .destroy_watch = audit_free_parent,
525};
526
527static int __init audit_watch_init(void)
528{
529 audit_ih = inotify_init(&audit_inotify_ops);
530 if (IS_ERR(audit_ih))
531 audit_panic("cannot initialize inotify handle");
532 return 0;
533}
534subsys_initcall(audit_watch_init);