audit: acquire creds selectively to reduce atomic op overhead
Tony Jones [Wed, 27 Apr 2011 13:10:49 +0000 (15:10 +0200)]
Commit c69e8d9c01db ("CRED: Use RCU to access another task's creds and to
release a task's own creds") added calls to get_task_cred and put_cred in
audit_filter_rules.  Profiling with a large number of audit rules active
on the exit chain shows that we are spending upto 48% in this routine for
syscall intensive tests, most of which is in the atomic ops.

1. The code should be accessing tsk->cred rather than tsk->real_cred.
2. Since tsk is current (or tsk is being created by copy_process) access to
tsk->cred without rcu read lock is possible.  At the request of the audit
maintainer, a new flag has been added to audit_filter_rules in order to make
this explicit and guide future code.

Signed-off-by: Tony Jones <tonyj@suse.de>
Acked-by: Eric Paris <eparis@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>

kernel/auditsc.c

index b33513a..00d79df 100644 (file)
@@ -443,17 +443,25 @@ static int match_tree_refs(struct audit_context *ctx, struct audit_tree *tree)
 
 /* Determine if any context name data matches a rule's watch data */
 /* Compare a task_struct with an audit_rule.  Return 1 on match, 0
- * otherwise. */
+ * otherwise.
+ *
+ * If task_creation is true, this is an explicit indication that we are
+ * filtering a task rule at task creation time.  This and tsk == current are
+ * the only situations where tsk->cred may be accessed without an rcu read lock.
+ */
 static int audit_filter_rules(struct task_struct *tsk,
                              struct audit_krule *rule,
                              struct audit_context *ctx,
                              struct audit_names *name,
-                             enum audit_state *state)
+                             enum audit_state *state,
+                             bool task_creation)
 {
-       const struct cred *cred = get_task_cred(tsk);
+       const struct cred *cred;
        int i, j, need_sid = 1;
        u32 sid;
 
+       cred = rcu_dereference_check(tsk->cred, tsk == current || task_creation);
+
        for (i = 0; i < rule->field_count; i++) {
                struct audit_field *f = &rule->fields[i];
                int result = 0;
@@ -637,10 +645,8 @@ static int audit_filter_rules(struct task_struct *tsk,
                        break;
                }
 
-               if (!result) {
-                       put_cred(cred);
+               if (!result)
                        return 0;
-               }
        }
 
        if (ctx) {
@@ -656,7 +662,6 @@ static int audit_filter_rules(struct task_struct *tsk,
        case AUDIT_NEVER:    *state = AUDIT_DISABLED;       break;
        case AUDIT_ALWAYS:   *state = AUDIT_RECORD_CONTEXT; break;
        }
-       put_cred(cred);
        return 1;
 }
 
@@ -671,7 +676,8 @@ static enum audit_state audit_filter_task(struct task_struct *tsk, char **key)
 
        rcu_read_lock();
        list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) {
-               if (audit_filter_rules(tsk, &e->rule, NULL, NULL, &state)) {
+               if (audit_filter_rules(tsk, &e->rule, NULL, NULL,
+                                      &state, true)) {
                        if (state == AUDIT_RECORD_CONTEXT)
                                *key = kstrdup(e->rule.filterkey, GFP_ATOMIC);
                        rcu_read_unlock();
@@ -705,7 +711,7 @@ static enum audit_state audit_filter_syscall(struct task_struct *tsk,
                list_for_each_entry_rcu(e, list, list) {
                        if ((e->rule.mask[word] & bit) == bit &&
                            audit_filter_rules(tsk, &e->rule, ctx, NULL,
-                                              &state)) {
+                                              &state, false)) {
                                rcu_read_unlock();
                                ctx->current_state = state;
                                return state;
@@ -743,7 +749,8 @@ void audit_filter_inodes(struct task_struct *tsk, struct audit_context *ctx)
 
                list_for_each_entry_rcu(e, list, list) {
                        if ((e->rule.mask[word] & bit) == bit &&
-                           audit_filter_rules(tsk, &e->rule, ctx, n, &state)) {
+                           audit_filter_rules(tsk, &e->rule, ctx, n,
+                                              &state, false)) {
                                rcu_read_unlock();
                                ctx->current_state = state;
                                return;