Merge branch 'access-creds'
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 25 Jul 2019 15:36:29 +0000 (08:36 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 25 Jul 2019 15:36:29 +0000 (08:36 -0700)
The access() (and faccessat()) credentials change can cause an
unnecessary load on the RCU machinery because every access() call ends
up freeing the temporary access credential using RCU.

This isn't really noticeable on small machines, but if you have hundreds
of cores you can cause huge slowdowns due to RCU storms.

It's easy to avoid: the temporary access crededntials aren't actually
normally accessed using RCU at all, so we can avoid the whole issue by
just marking them as such.

* access-creds:
  access: avoid the RCU grace period for the temporary subjective credentials

1  2 
kernel/cred.c

diff --combined kernel/cred.c
index f9a0ce66c9c3e125393f011be7d55a322785ed2e,153ae369e0248e5f314a928cf338be1145d3b681..c0a4c12d38b20ed80254c76acb28c6d060b1df86
@@@ -144,7 -144,10 +144,10 @@@ void __put_cred(struct cred *cred
        BUG_ON(cred == current->cred);
        BUG_ON(cred == current->real_cred);
  
-       call_rcu(&cred->rcu, put_cred_rcu);
+       if (cred->non_rcu)
+               put_cred_rcu(&cred->rcu);
+       else
+               call_rcu(&cred->rcu, put_cred_rcu);
  }
  EXPORT_SYMBOL(__put_cred);
  
@@@ -170,11 -173,6 +173,11 @@@ void exit_creds(struct task_struct *tsk
        validate_creds(cred);
        alter_cred_subscribers(cred, -1);
        put_cred(cred);
 +
 +#ifdef CONFIG_KEYS_REQUEST_CACHE
 +      key_put(current->cached_requested_key);
 +      current->cached_requested_key = NULL;
 +#endif
  }
  
  /**
@@@ -261,6 -259,7 +264,7 @@@ struct cred *prepare_creds(void
        old = task->cred;
        memcpy(new, old, sizeof(struct cred));
  
+       new->non_rcu = 0;
        atomic_set(&new->usage, 1);
        set_cred_subscribers(new, 0);
        get_group_info(new->group_info);
@@@ -328,10 -327,6 +332,10 @@@ int copy_creds(struct task_struct *p, u
        struct cred *new;
        int ret;
  
 +#ifdef CONFIG_KEYS_REQUEST_CACHE
 +      p->cached_requested_key = NULL;
 +#endif
 +
        if (
  #ifdef CONFIG_KEYS
                !p->cred->thread_keyring &&
@@@ -469,9 -464,9 +473,9 @@@ int commit_creds(struct cred *new
  
        /* alter the thread keyring */
        if (!uid_eq(new->fsuid, old->fsuid))
 -              key_fsuid_changed(task);
 +              key_fsuid_changed(new);
        if (!gid_eq(new->fsgid, old->fsgid))
 -              key_fsgid_changed(task);
 +              key_fsgid_changed(new);
  
        /* do it
         * RLIMIT_NPROC limits on user->processes have already been checked
@@@ -544,7 -539,19 +548,19 @@@ const struct cred *override_creds(cons
  
        validate_creds(old);
        validate_creds(new);
-       get_cred(new);
+       /*
+        * NOTE! This uses 'get_new_cred()' rather than 'get_cred()'.
+        *
+        * That means that we do not clear the 'non_rcu' flag, since
+        * we are only installing the cred into the thread-synchronous
+        * '->cred' pointer, not the '->real_cred' pointer that is
+        * visible to other threads under RCU.
+        *
+        * Also note that we did validate_creds() manually, not depending
+        * on the validation in 'get_cred()'.
+        */
+       get_new_cred((struct cred *)new);
        alter_cred_subscribers(new, 1);
        rcu_assign_pointer(current->cred, new);
        alter_cred_subscribers(old, -1);
@@@ -681,6 -688,7 +697,7 @@@ struct cred *prepare_kernel_cred(struc
        validate_creds(old);
  
        *new = *old;
+       new->non_rcu = 0;
        atomic_set(&new->usage, 1);
        set_cred_subscribers(new, 0);
        get_uid(new->user);