diff options
Diffstat (limited to 'kernel/auditsc.c')
-rw-r--r-- | kernel/auditsc.c | 221 |
1 files changed, 220 insertions, 1 deletions
diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 8a85c203be12..80ecab0942ef 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c | |||
@@ -65,6 +65,7 @@ | |||
65 | #include <linux/binfmts.h> | 65 | #include <linux/binfmts.h> |
66 | #include <linux/highmem.h> | 66 | #include <linux/highmem.h> |
67 | #include <linux/syscalls.h> | 67 | #include <linux/syscalls.h> |
68 | #include <linux/inotify.h> | ||
68 | 69 | ||
69 | #include "audit.h" | 70 | #include "audit.h" |
70 | 71 | ||
@@ -179,6 +180,11 @@ struct audit_aux_data_pids { | |||
179 | int pid_count; | 180 | int pid_count; |
180 | }; | 181 | }; |
181 | 182 | ||
183 | struct audit_tree_refs { | ||
184 | struct audit_tree_refs *next; | ||
185 | struct audit_chunk *c[31]; | ||
186 | }; | ||
187 | |||
182 | /* The per-task audit context. */ | 188 | /* The per-task audit context. */ |
183 | struct audit_context { | 189 | struct audit_context { |
184 | int dummy; /* must be the first element */ | 190 | int dummy; /* must be the first element */ |
@@ -211,6 +217,9 @@ struct audit_context { | |||
211 | pid_t target_pid; | 217 | pid_t target_pid; |
212 | u32 target_sid; | 218 | u32 target_sid; |
213 | 219 | ||
220 | struct audit_tree_refs *trees, *first_trees; | ||
221 | int tree_count; | ||
222 | |||
214 | #if AUDIT_DEBUG | 223 | #if AUDIT_DEBUG |
215 | int put_count; | 224 | int put_count; |
216 | int ino_count; | 225 | int ino_count; |
@@ -265,6 +274,117 @@ static int audit_match_perm(struct audit_context *ctx, int mask) | |||
265 | } | 274 | } |
266 | } | 275 | } |
267 | 276 | ||
277 | /* | ||
278 | * We keep a linked list of fixed-sized (31 pointer) arrays of audit_chunk *; | ||
279 | * ->first_trees points to its beginning, ->trees - to the current end of data. | ||
280 | * ->tree_count is the number of free entries in array pointed to by ->trees. | ||
281 | * Original condition is (NULL, NULL, 0); as soon as it grows we never revert to NULL, | ||
282 | * "empty" becomes (p, p, 31) afterwards. We don't shrink the list (and seriously, | ||
283 | * it's going to remain 1-element for almost any setup) until we free context itself. | ||
284 | * References in it _are_ dropped - at the same time we free/drop aux stuff. | ||
285 | */ | ||
286 | |||
287 | #ifdef CONFIG_AUDIT_TREE | ||
288 | static int put_tree_ref(struct audit_context *ctx, struct audit_chunk *chunk) | ||
289 | { | ||
290 | struct audit_tree_refs *p = ctx->trees; | ||
291 | int left = ctx->tree_count; | ||
292 | if (likely(left)) { | ||
293 | p->c[--left] = chunk; | ||
294 | ctx->tree_count = left; | ||
295 | return 1; | ||
296 | } | ||
297 | if (!p) | ||
298 | return 0; | ||
299 | p = p->next; | ||
300 | if (p) { | ||
301 | p->c[30] = chunk; | ||
302 | ctx->trees = p; | ||
303 | ctx->tree_count = 30; | ||
304 | return 1; | ||
305 | } | ||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static int grow_tree_refs(struct audit_context *ctx) | ||
310 | { | ||
311 | struct audit_tree_refs *p = ctx->trees; | ||
312 | ctx->trees = kzalloc(sizeof(struct audit_tree_refs), GFP_KERNEL); | ||
313 | if (!ctx->trees) { | ||
314 | ctx->trees = p; | ||
315 | return 0; | ||
316 | } | ||
317 | if (p) | ||
318 | p->next = ctx->trees; | ||
319 | else | ||
320 | ctx->first_trees = ctx->trees; | ||
321 | ctx->tree_count = 31; | ||
322 | return 1; | ||
323 | } | ||
324 | #endif | ||
325 | |||
326 | static void unroll_tree_refs(struct audit_context *ctx, | ||
327 | struct audit_tree_refs *p, int count) | ||
328 | { | ||
329 | #ifdef CONFIG_AUDIT_TREE | ||
330 | struct audit_tree_refs *q; | ||
331 | int n; | ||
332 | if (!p) { | ||
333 | /* we started with empty chain */ | ||
334 | p = ctx->first_trees; | ||
335 | count = 31; | ||
336 | /* if the very first allocation has failed, nothing to do */ | ||
337 | if (!p) | ||
338 | return; | ||
339 | } | ||
340 | n = count; | ||
341 | for (q = p; q != ctx->trees; q = q->next, n = 31) { | ||
342 | while (n--) { | ||
343 | audit_put_chunk(q->c[n]); | ||
344 | q->c[n] = NULL; | ||
345 | } | ||
346 | } | ||
347 | while (n-- > ctx->tree_count) { | ||
348 | audit_put_chunk(q->c[n]); | ||
349 | q->c[n] = NULL; | ||
350 | } | ||
351 | ctx->trees = p; | ||
352 | ctx->tree_count = count; | ||
353 | #endif | ||
354 | } | ||
355 | |||
356 | static void free_tree_refs(struct audit_context *ctx) | ||
357 | { | ||
358 | struct audit_tree_refs *p, *q; | ||
359 | for (p = ctx->first_trees; p; p = q) { | ||
360 | q = p->next; | ||
361 | kfree(p); | ||
362 | } | ||
363 | } | ||
364 | |||
365 | static int match_tree_refs(struct audit_context *ctx, struct audit_tree *tree) | ||
366 | { | ||
367 | #ifdef CONFIG_AUDIT_TREE | ||
368 | struct audit_tree_refs *p; | ||
369 | int n; | ||
370 | if (!tree) | ||
371 | return 0; | ||
372 | /* full ones */ | ||
373 | for (p = ctx->first_trees; p != ctx->trees; p = p->next) { | ||
374 | for (n = 0; n < 31; n++) | ||
375 | if (audit_tree_match(p->c[n], tree)) | ||
376 | return 1; | ||
377 | } | ||
378 | /* partial */ | ||
379 | if (p) { | ||
380 | for (n = ctx->tree_count; n < 31; n++) | ||
381 | if (audit_tree_match(p->c[n], tree)) | ||
382 | return 1; | ||
383 | } | ||
384 | #endif | ||
385 | return 0; | ||
386 | } | ||
387 | |||
268 | /* Determine if any context name data matches a rule's watch data */ | 388 | /* Determine if any context name data matches a rule's watch data */ |
269 | /* Compare a task_struct with an audit_rule. Return 1 on match, 0 | 389 | /* Compare a task_struct with an audit_rule. Return 1 on match, 0 |
270 | * otherwise. */ | 390 | * otherwise. */ |
@@ -379,6 +499,10 @@ static int audit_filter_rules(struct task_struct *tsk, | |||
379 | result = (name->dev == rule->watch->dev && | 499 | result = (name->dev == rule->watch->dev && |
380 | name->ino == rule->watch->ino); | 500 | name->ino == rule->watch->ino); |
381 | break; | 501 | break; |
502 | case AUDIT_DIR: | ||
503 | if (ctx) | ||
504 | result = match_tree_refs(ctx, rule->tree); | ||
505 | break; | ||
382 | case AUDIT_LOGINUID: | 506 | case AUDIT_LOGINUID: |
383 | result = 0; | 507 | result = 0; |
384 | if (ctx) | 508 | if (ctx) |
@@ -727,6 +851,8 @@ static inline void audit_free_context(struct audit_context *context) | |||
727 | context->name_count, count); | 851 | context->name_count, count); |
728 | } | 852 | } |
729 | audit_free_names(context); | 853 | audit_free_names(context); |
854 | unroll_tree_refs(context, NULL, 0); | ||
855 | free_tree_refs(context); | ||
730 | audit_free_aux(context); | 856 | audit_free_aux(context); |
731 | kfree(context->filterkey); | 857 | kfree(context->filterkey); |
732 | kfree(context); | 858 | kfree(context); |
@@ -1270,6 +1396,7 @@ void audit_syscall_exit(int valid, long return_code) | |||
1270 | tsk->audit_context = new_context; | 1396 | tsk->audit_context = new_context; |
1271 | } else { | 1397 | } else { |
1272 | audit_free_names(context); | 1398 | audit_free_names(context); |
1399 | unroll_tree_refs(context, NULL, 0); | ||
1273 | audit_free_aux(context); | 1400 | audit_free_aux(context); |
1274 | context->aux = NULL; | 1401 | context->aux = NULL; |
1275 | context->aux_pids = NULL; | 1402 | context->aux_pids = NULL; |
@@ -1281,6 +1408,95 @@ void audit_syscall_exit(int valid, long return_code) | |||
1281 | } | 1408 | } |
1282 | } | 1409 | } |
1283 | 1410 | ||
1411 | static inline void handle_one(const struct inode *inode) | ||
1412 | { | ||
1413 | #ifdef CONFIG_AUDIT_TREE | ||
1414 | struct audit_context *context; | ||
1415 | struct audit_tree_refs *p; | ||
1416 | struct audit_chunk *chunk; | ||
1417 | int count; | ||
1418 | if (likely(list_empty(&inode->inotify_watches))) | ||
1419 | return; | ||
1420 | context = current->audit_context; | ||
1421 | p = context->trees; | ||
1422 | count = context->tree_count; | ||
1423 | rcu_read_lock(); | ||
1424 | chunk = audit_tree_lookup(inode); | ||
1425 | rcu_read_unlock(); | ||
1426 | if (!chunk) | ||
1427 | return; | ||
1428 | if (likely(put_tree_ref(context, chunk))) | ||
1429 | return; | ||
1430 | if (unlikely(!grow_tree_refs(context))) { | ||
1431 | printk(KERN_WARNING "out of memory, audit has lost a tree reference"); | ||
1432 | audit_set_auditable(context); | ||
1433 | audit_put_chunk(chunk); | ||
1434 | unroll_tree_refs(context, p, count); | ||
1435 | return; | ||
1436 | } | ||
1437 | put_tree_ref(context, chunk); | ||
1438 | #endif | ||
1439 | } | ||
1440 | |||
1441 | static void handle_path(const struct dentry *dentry) | ||
1442 | { | ||
1443 | #ifdef CONFIG_AUDIT_TREE | ||
1444 | struct audit_context *context; | ||
1445 | struct audit_tree_refs *p; | ||
1446 | const struct dentry *d, *parent; | ||
1447 | struct audit_chunk *drop; | ||
1448 | unsigned long seq; | ||
1449 | int count; | ||
1450 | |||
1451 | context = current->audit_context; | ||
1452 | p = context->trees; | ||
1453 | count = context->tree_count; | ||
1454 | retry: | ||
1455 | drop = NULL; | ||
1456 | d = dentry; | ||
1457 | rcu_read_lock(); | ||
1458 | seq = read_seqbegin(&rename_lock); | ||
1459 | for(;;) { | ||
1460 | struct inode *inode = d->d_inode; | ||
1461 | if (inode && unlikely(!list_empty(&inode->inotify_watches))) { | ||
1462 | struct audit_chunk *chunk; | ||
1463 | chunk = audit_tree_lookup(inode); | ||
1464 | if (chunk) { | ||
1465 | if (unlikely(!put_tree_ref(context, chunk))) { | ||
1466 | drop = chunk; | ||
1467 | break; | ||
1468 | } | ||
1469 | } | ||
1470 | } | ||
1471 | parent = d->d_parent; | ||
1472 | if (parent == d) | ||
1473 | break; | ||
1474 | d = parent; | ||
1475 | } | ||
1476 | if (unlikely(read_seqretry(&rename_lock, seq) || drop)) { /* in this order */ | ||
1477 | rcu_read_unlock(); | ||
1478 | if (!drop) { | ||
1479 | /* just a race with rename */ | ||
1480 | unroll_tree_refs(context, p, count); | ||
1481 | goto retry; | ||
1482 | } | ||
1483 | audit_put_chunk(drop); | ||
1484 | if (grow_tree_refs(context)) { | ||
1485 | /* OK, got more space */ | ||
1486 | unroll_tree_refs(context, p, count); | ||
1487 | goto retry; | ||
1488 | } | ||
1489 | /* too bad */ | ||
1490 | printk(KERN_WARNING | ||
1491 | "out of memory, audit has lost a tree reference"); | ||
1492 | unroll_tree_refs(context, p, count); | ||
1493 | audit_set_auditable(context); | ||
1494 | return; | ||
1495 | } | ||
1496 | rcu_read_unlock(); | ||
1497 | #endif | ||
1498 | } | ||
1499 | |||
1284 | /** | 1500 | /** |
1285 | * audit_getname - add a name to the list | 1501 | * audit_getname - add a name to the list |
1286 | * @name: name to add | 1502 | * @name: name to add |
@@ -1407,7 +1623,7 @@ void __audit_inode(const char *name, const struct dentry *dentry) | |||
1407 | { | 1623 | { |
1408 | int idx; | 1624 | int idx; |
1409 | struct audit_context *context = current->audit_context; | 1625 | struct audit_context *context = current->audit_context; |
1410 | const struct inode *inode = inode = dentry->d_inode; | 1626 | const struct inode *inode = dentry->d_inode; |
1411 | 1627 | ||
1412 | if (!context->in_syscall) | 1628 | if (!context->in_syscall) |
1413 | return; | 1629 | return; |
@@ -1427,6 +1643,7 @@ void __audit_inode(const char *name, const struct dentry *dentry) | |||
1427 | idx = context->name_count - 1; | 1643 | idx = context->name_count - 1; |
1428 | context->names[idx].name = NULL; | 1644 | context->names[idx].name = NULL; |
1429 | } | 1645 | } |
1646 | handle_path(dentry); | ||
1430 | audit_copy_inode(&context->names[idx], inode); | 1647 | audit_copy_inode(&context->names[idx], inode); |
1431 | } | 1648 | } |
1432 | 1649 | ||
@@ -1456,6 +1673,8 @@ void __audit_inode_child(const char *dname, const struct dentry *dentry, | |||
1456 | if (!context->in_syscall) | 1673 | if (!context->in_syscall) |
1457 | return; | 1674 | return; |
1458 | 1675 | ||
1676 | if (inode) | ||
1677 | handle_one(inode); | ||
1459 | /* determine matching parent */ | 1678 | /* determine matching parent */ |
1460 | if (!dname) | 1679 | if (!dname) |
1461 | goto add_names; | 1680 | goto add_names; |