diff options
author | Amy Griffis <amy.griffis@hp.com> | 2006-04-07 16:55:56 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2006-06-20 05:25:27 -0400 |
commit | f368c07d7214a7c41dfceb76c8db473b850f0229 (patch) | |
tree | e3f1e2d1a6ffbe61bf99ece51b906654728db4c9 /kernel/auditsc.c | |
parent | 20ca73bc792be9625af184cbec36e1372611d1c3 (diff) |
[PATCH] audit: path-based rules
In this implementation, audit registers inotify watches on the parent
directories of paths specified in audit rules. When audit's inotify
event handler is called, it updates any affected rules based on the
filesystem event. If the parent directory is renamed, removed, or its
filesystem is unmounted, audit removes all rules referencing that
inotify watch.
To keep things simple, this implementation limits location-based
auditing to the directory entries in an existing directory. Given
a path-based rule for /foo/bar/passwd, the following table applies:
passwd modified -- audit event logged
passwd replaced -- audit event logged, rules list updated
bar renamed -- rule removed
foo renamed -- untracked, meaning that the rule now applies to
the new location
Audit users typically want to have many rules referencing filesystem
objects, which can significantly impact filtering performance. This
patch also adds an inode-number-based rule hash to mitigate this
situation.
The patch is relative to the audit git tree:
http://kernel.org/git/?p=linux/kernel/git/viro/audit-current.git;a=summary
and uses the inotify kernel API:
http://lkml.org/lkml/2006/6/1/145
Signed-off-by: Amy Griffis <amy.griffis@hp.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'kernel/auditsc.c')
-rw-r--r-- | kernel/auditsc.c | 124 |
1 files changed, 88 insertions, 36 deletions
diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 14e295a4121b..174a3f624892 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c | |||
@@ -200,12 +200,13 @@ struct audit_context { | |||
200 | #endif | 200 | #endif |
201 | }; | 201 | }; |
202 | 202 | ||
203 | 203 | /* Determine if any context name data matches a rule's watch data */ | |
204 | /* Compare a task_struct with an audit_rule. Return 1 on match, 0 | 204 | /* Compare a task_struct with an audit_rule. Return 1 on match, 0 |
205 | * otherwise. */ | 205 | * otherwise. */ |
206 | static int audit_filter_rules(struct task_struct *tsk, | 206 | static int audit_filter_rules(struct task_struct *tsk, |
207 | struct audit_krule *rule, | 207 | struct audit_krule *rule, |
208 | struct audit_context *ctx, | 208 | struct audit_context *ctx, |
209 | struct audit_names *name, | ||
209 | enum audit_state *state) | 210 | enum audit_state *state) |
210 | { | 211 | { |
211 | int i, j, need_sid = 1; | 212 | int i, j, need_sid = 1; |
@@ -268,7 +269,10 @@ static int audit_filter_rules(struct task_struct *tsk, | |||
268 | } | 269 | } |
269 | break; | 270 | break; |
270 | case AUDIT_DEVMAJOR: | 271 | case AUDIT_DEVMAJOR: |
271 | if (ctx) { | 272 | if (name) |
273 | result = audit_comparator(MAJOR(name->dev), | ||
274 | f->op, f->val); | ||
275 | else if (ctx) { | ||
272 | for (j = 0; j < ctx->name_count; j++) { | 276 | for (j = 0; j < ctx->name_count; j++) { |
273 | if (audit_comparator(MAJOR(ctx->names[j].dev), f->op, f->val)) { | 277 | if (audit_comparator(MAJOR(ctx->names[j].dev), f->op, f->val)) { |
274 | ++result; | 278 | ++result; |
@@ -278,7 +282,10 @@ static int audit_filter_rules(struct task_struct *tsk, | |||
278 | } | 282 | } |
279 | break; | 283 | break; |
280 | case AUDIT_DEVMINOR: | 284 | case AUDIT_DEVMINOR: |
281 | if (ctx) { | 285 | if (name) |
286 | result = audit_comparator(MINOR(name->dev), | ||
287 | f->op, f->val); | ||
288 | else if (ctx) { | ||
282 | for (j = 0; j < ctx->name_count; j++) { | 289 | for (j = 0; j < ctx->name_count; j++) { |
283 | if (audit_comparator(MINOR(ctx->names[j].dev), f->op, f->val)) { | 290 | if (audit_comparator(MINOR(ctx->names[j].dev), f->op, f->val)) { |
284 | ++result; | 291 | ++result; |
@@ -288,7 +295,10 @@ static int audit_filter_rules(struct task_struct *tsk, | |||
288 | } | 295 | } |
289 | break; | 296 | break; |
290 | case AUDIT_INODE: | 297 | case AUDIT_INODE: |
291 | if (ctx) { | 298 | if (name) |
299 | result = (name->ino == f->val || | ||
300 | name->pino == f->val); | ||
301 | else if (ctx) { | ||
292 | for (j = 0; j < ctx->name_count; j++) { | 302 | for (j = 0; j < ctx->name_count; j++) { |
293 | if (audit_comparator(ctx->names[j].ino, f->op, f->val) || | 303 | if (audit_comparator(ctx->names[j].ino, f->op, f->val) || |
294 | audit_comparator(ctx->names[j].pino, f->op, f->val)) { | 304 | audit_comparator(ctx->names[j].pino, f->op, f->val)) { |
@@ -298,6 +308,12 @@ static int audit_filter_rules(struct task_struct *tsk, | |||
298 | } | 308 | } |
299 | } | 309 | } |
300 | break; | 310 | break; |
311 | case AUDIT_WATCH: | ||
312 | if (name && rule->watch->ino != (unsigned long)-1) | ||
313 | result = (name->dev == rule->watch->dev && | ||
314 | (name->ino == rule->watch->ino || | ||
315 | name->pino == rule->watch->ino)); | ||
316 | break; | ||
301 | case AUDIT_LOGINUID: | 317 | case AUDIT_LOGINUID: |
302 | result = 0; | 318 | result = 0; |
303 | if (ctx) | 319 | if (ctx) |
@@ -354,7 +370,7 @@ static enum audit_state audit_filter_task(struct task_struct *tsk) | |||
354 | 370 | ||
355 | rcu_read_lock(); | 371 | rcu_read_lock(); |
356 | list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) { | 372 | list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) { |
357 | if (audit_filter_rules(tsk, &e->rule, NULL, &state)) { | 373 | if (audit_filter_rules(tsk, &e->rule, NULL, NULL, &state)) { |
358 | rcu_read_unlock(); | 374 | rcu_read_unlock(); |
359 | return state; | 375 | return state; |
360 | } | 376 | } |
@@ -384,8 +400,9 @@ static enum audit_state audit_filter_syscall(struct task_struct *tsk, | |||
384 | int bit = AUDIT_BIT(ctx->major); | 400 | int bit = AUDIT_BIT(ctx->major); |
385 | 401 | ||
386 | list_for_each_entry_rcu(e, list, list) { | 402 | list_for_each_entry_rcu(e, list, list) { |
387 | if ((e->rule.mask[word] & bit) == bit | 403 | if ((e->rule.mask[word] & bit) == bit && |
388 | && audit_filter_rules(tsk, &e->rule, ctx, &state)) { | 404 | audit_filter_rules(tsk, &e->rule, ctx, NULL, |
405 | &state)) { | ||
389 | rcu_read_unlock(); | 406 | rcu_read_unlock(); |
390 | return state; | 407 | return state; |
391 | } | 408 | } |
@@ -395,6 +412,49 @@ static enum audit_state audit_filter_syscall(struct task_struct *tsk, | |||
395 | return AUDIT_BUILD_CONTEXT; | 412 | return AUDIT_BUILD_CONTEXT; |
396 | } | 413 | } |
397 | 414 | ||
415 | /* At syscall exit time, this filter is called if any audit_names[] have been | ||
416 | * collected during syscall processing. We only check rules in sublists at hash | ||
417 | * buckets applicable to the inode numbers in audit_names[]. | ||
418 | * Regarding audit_state, same rules apply as for audit_filter_syscall(). | ||
419 | */ | ||
420 | enum audit_state audit_filter_inodes(struct task_struct *tsk, | ||
421 | struct audit_context *ctx) | ||
422 | { | ||
423 | int i; | ||
424 | struct audit_entry *e; | ||
425 | enum audit_state state; | ||
426 | |||
427 | if (audit_pid && tsk->tgid == audit_pid) | ||
428 | return AUDIT_DISABLED; | ||
429 | |||
430 | rcu_read_lock(); | ||
431 | for (i = 0; i < ctx->name_count; i++) { | ||
432 | int word = AUDIT_WORD(ctx->major); | ||
433 | int bit = AUDIT_BIT(ctx->major); | ||
434 | struct audit_names *n = &ctx->names[i]; | ||
435 | int h = audit_hash_ino((u32)n->ino); | ||
436 | struct list_head *list = &audit_inode_hash[h]; | ||
437 | |||
438 | if (list_empty(list)) | ||
439 | continue; | ||
440 | |||
441 | list_for_each_entry_rcu(e, list, list) { | ||
442 | if ((e->rule.mask[word] & bit) == bit && | ||
443 | audit_filter_rules(tsk, &e->rule, ctx, n, &state)) { | ||
444 | rcu_read_unlock(); | ||
445 | return state; | ||
446 | } | ||
447 | } | ||
448 | } | ||
449 | rcu_read_unlock(); | ||
450 | return AUDIT_BUILD_CONTEXT; | ||
451 | } | ||
452 | |||
453 | void audit_set_auditable(struct audit_context *ctx) | ||
454 | { | ||
455 | ctx->auditable = 1; | ||
456 | } | ||
457 | |||
398 | static inline struct audit_context *audit_get_context(struct task_struct *tsk, | 458 | static inline struct audit_context *audit_get_context(struct task_struct *tsk, |
399 | int return_valid, | 459 | int return_valid, |
400 | int return_code) | 460 | int return_code) |
@@ -408,11 +468,20 @@ static inline struct audit_context *audit_get_context(struct task_struct *tsk, | |||
408 | 468 | ||
409 | if (context->in_syscall && !context->auditable) { | 469 | if (context->in_syscall && !context->auditable) { |
410 | enum audit_state state; | 470 | enum audit_state state; |
471 | |||
411 | state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]); | 472 | state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]); |
473 | if (state == AUDIT_RECORD_CONTEXT) { | ||
474 | context->auditable = 1; | ||
475 | goto get_context; | ||
476 | } | ||
477 | |||
478 | state = audit_filter_inodes(tsk, context); | ||
412 | if (state == AUDIT_RECORD_CONTEXT) | 479 | if (state == AUDIT_RECORD_CONTEXT) |
413 | context->auditable = 1; | 480 | context->auditable = 1; |
481 | |||
414 | } | 482 | } |
415 | 483 | ||
484 | get_context: | ||
416 | context->pid = tsk->pid; | 485 | context->pid = tsk->pid; |
417 | context->ppid = sys_getppid(); /* sic. tsk == current in all cases */ | 486 | context->ppid = sys_getppid(); /* sic. tsk == current in all cases */ |
418 | context->uid = tsk->uid; | 487 | context->uid = tsk->uid; |
@@ -1142,37 +1211,20 @@ void __audit_inode_child(const char *dname, const struct inode *inode, | |||
1142 | return; | 1211 | return; |
1143 | 1212 | ||
1144 | /* determine matching parent */ | 1213 | /* determine matching parent */ |
1145 | if (dname) | 1214 | if (!dname) |
1146 | for (idx = 0; idx < context->name_count; idx++) | 1215 | goto no_match; |
1147 | if (context->names[idx].pino == pino) { | 1216 | for (idx = 0; idx < context->name_count; idx++) |
1148 | const char *n; | 1217 | if (context->names[idx].pino == pino) { |
1149 | const char *name = context->names[idx].name; | 1218 | const char *name = context->names[idx].name; |
1150 | int dlen = strlen(dname); | ||
1151 | int nlen = name ? strlen(name) : 0; | ||
1152 | |||
1153 | if (nlen < dlen) | ||
1154 | continue; | ||
1155 | |||
1156 | /* disregard trailing slashes */ | ||
1157 | n = name + nlen - 1; | ||
1158 | while ((*n == '/') && (n > name)) | ||
1159 | n--; | ||
1160 | |||
1161 | /* find last path component */ | ||
1162 | n = n - dlen + 1; | ||
1163 | if (n < name) | ||
1164 | continue; | ||
1165 | else if (n > name) { | ||
1166 | if (*--n != '/') | ||
1167 | continue; | ||
1168 | else | ||
1169 | n++; | ||
1170 | } | ||
1171 | 1219 | ||
1172 | if (strncmp(n, dname, dlen) == 0) | 1220 | if (!name) |
1173 | goto update_context; | 1221 | continue; |
1174 | } | 1222 | |
1223 | if (audit_compare_dname_path(dname, name) == 0) | ||
1224 | goto update_context; | ||
1225 | } | ||
1175 | 1226 | ||
1227 | no_match: | ||
1176 | /* catch-all in case match not found */ | 1228 | /* catch-all in case match not found */ |
1177 | idx = context->name_count++; | 1229 | idx = context->name_count++; |
1178 | context->names[idx].name = NULL; | 1230 | context->names[idx].name = NULL; |