All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 16:17 ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 16:17 UTC (permalink / raw)
  To: linux-security-module


Hi Eric,

Are you okay with my changes/substitution of your key instantiation-state
patches?

David
---
Here's a collection of fixes for Linux keyrings, mostly thanks to Eric
Biggers, including:

 (1) Fix a bunch of places where kernel drivers may access revoked user-type
     keys and don't do it correctly.

 (2) Fix some ecryptfs bits.

 (3) Fix big_key to require CONFIG_CRYPTO.

 (4) Fix a couple of bugs in the asymmetric key type.

 (5) Fix a race between updating and finding negative keys.

 (6) Prevent add_key() from updating uninstantiated keys.

 (7) Make loading of key flags and expiry time atomic when not holding locks.

The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-fixes

David
---
Arnd Bergmann (1):
      security/keys: BIG_KEY requires CONFIG_CRYPTO

David Howells (2):
      KEYS: Fix race between updating and finding a negative key
      KEYS: don't let add_key() update an uninstantiated key

Eric Biggers (10):
      KEYS: encrypted: fix dereference of NULL user_key_payload
      FS-Cache: fix dereference of NULL user_key_payload
      lib/digsig: fix dereference of NULL user_key_payload
      fscrypt: fix dereference of NULL user_key_payload
      ecryptfs: fix dereference of NULL user_key_payload
      ecryptfs: fix out-of-bounds read of key payload
      ecryptfs: move key payload accessor functions into keystore.c
      KEYS: load key flags and expiry time atomically in key_validate()
      KEYS: load key flags and expiry time atomically in keyring_search_iterator()
      KEYS: load key flags and expiry time atomically in proc_keys_show()

Lee, Chun-Yi (2):
      KEYS: Fix the wrong index when checking the existence of second id
      KEYS: checking the input id parameters before finding asymmetric key


 crypto/asymmetric_keys/asymmetric_type.c |    4 +-
 fs/crypto/keyinfo.c                      |    5 ++
 fs/ecryptfs/ecryptfs_kernel.h            |   44 ------------------
 fs/ecryptfs/keystore.c                   |   73 ++++++++++++++++++++++++++++++
 fs/fscache/object-list.c                 |    7 +++
 include/linux/key.h                      |   37 +++++++++------
 lib/digsig.c                             |    6 ++
 security/keys/Kconfig                    |    1 
 security/keys/encrypted-keys/encrypted.c |    9 +++-
 security/keys/gc.c                       |    7 ++-
 security/keys/key.c                      |   37 +++++++++++----
 security/keys/keyctl.c                   |    9 ++--
 security/keys/keyring.c                  |   12 +++--
 security/keys/permission.c               |    7 ++-
 security/keys/proc.c                     |   35 +++++++++-----
 security/keys/process_keys.c             |    8 ++-
 security/keys/request_key.c              |    7 +--
 security/keys/trusted.c                  |    2 -
 security/keys/user_defined.c             |    2 -
 19 files changed, 201 insertions(+), 111 deletions(-)


^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 16:17 ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 16:17 UTC (permalink / raw)
  To: linux-security-module


Hi Eric,

Are you okay with my changes/substitution of your key instantiation-state
patches?

David
---
Here's a collection of fixes for Linux keyrings, mostly thanks to Eric
Biggers, including:

 (1) Fix a bunch of places where kernel drivers may access revoked user-type
     keys and don't do it correctly.

 (2) Fix some ecryptfs bits.

 (3) Fix big_key to require CONFIG_CRYPTO.

 (4) Fix a couple of bugs in the asymmetric key type.

 (5) Fix a race between updating and finding negative keys.

 (6) Prevent add_key() from updating uninstantiated keys.

 (7) Make loading of key flags and expiry time atomic when not holding locks.

The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-fixes

David
---
Arnd Bergmann (1):
      security/keys: BIG_KEY requires CONFIG_CRYPTO

David Howells (2):
      KEYS: Fix race between updating and finding a negative key
      KEYS: don't let add_key() update an uninstantiated key

Eric Biggers (10):
      KEYS: encrypted: fix dereference of NULL user_key_payload
      FS-Cache: fix dereference of NULL user_key_payload
      lib/digsig: fix dereference of NULL user_key_payload
      fscrypt: fix dereference of NULL user_key_payload
      ecryptfs: fix dereference of NULL user_key_payload
      ecryptfs: fix out-of-bounds read of key payload
      ecryptfs: move key payload accessor functions into keystore.c
      KEYS: load key flags and expiry time atomically in key_validate()
      KEYS: load key flags and expiry time atomically in keyring_search_iterator()
      KEYS: load key flags and expiry time atomically in proc_keys_show()

Lee, Chun-Yi (2):
      KEYS: Fix the wrong index when checking the existence of second id
      KEYS: checking the input id parameters before finding asymmetric key


 crypto/asymmetric_keys/asymmetric_type.c |    4 +-
 fs/crypto/keyinfo.c                      |    5 ++
 fs/ecryptfs/ecryptfs_kernel.h            |   44 ------------------
 fs/ecryptfs/keystore.c                   |   73 ++++++++++++++++++++++++++++++
 fs/fscache/object-list.c                 |    7 +++
 include/linux/key.h                      |   37 +++++++++------
 lib/digsig.c                             |    6 ++
 security/keys/Kconfig                    |    1 
 security/keys/encrypted-keys/encrypted.c |    9 +++-
 security/keys/gc.c                       |    7 ++-
 security/keys/key.c                      |   37 +++++++++++----
 security/keys/keyctl.c                   |    9 ++--
 security/keys/keyring.c                  |   12 +++--
 security/keys/permission.c               |    7 ++-
 security/keys/proc.c                     |   35 +++++++++-----
 security/keys/process_keys.c             |    8 ++-
 security/keys/request_key.c              |    7 +--
 security/keys/trusted.c                  |    2 -
 security/keys/user_defined.c             |    2 -
 19 files changed, 201 insertions(+), 111 deletions(-)

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 16:17 ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 16:17 UTC (permalink / raw)
  To: ebiggers; +Cc: dhowells, linux-security-module, keyrings, linux-kernel


Hi Eric,

Are you okay with my changes/substitution of your key instantiation-state
patches?

David
---
Here's a collection of fixes for Linux keyrings, mostly thanks to Eric
Biggers, including:

 (1) Fix a bunch of places where kernel drivers may access revoked user-type
     keys and don't do it correctly.

 (2) Fix some ecryptfs bits.

 (3) Fix big_key to require CONFIG_CRYPTO.

 (4) Fix a couple of bugs in the asymmetric key type.

 (5) Fix a race between updating and finding negative keys.

 (6) Prevent add_key() from updating uninstantiated keys.

 (7) Make loading of key flags and expiry time atomic when not holding locks.

The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-fixes

David
---
Arnd Bergmann (1):
      security/keys: BIG_KEY requires CONFIG_CRYPTO

David Howells (2):
      KEYS: Fix race between updating and finding a negative key
      KEYS: don't let add_key() update an uninstantiated key

Eric Biggers (10):
      KEYS: encrypted: fix dereference of NULL user_key_payload
      FS-Cache: fix dereference of NULL user_key_payload
      lib/digsig: fix dereference of NULL user_key_payload
      fscrypt: fix dereference of NULL user_key_payload
      ecryptfs: fix dereference of NULL user_key_payload
      ecryptfs: fix out-of-bounds read of key payload
      ecryptfs: move key payload accessor functions into keystore.c
      KEYS: load key flags and expiry time atomically in key_validate()
      KEYS: load key flags and expiry time atomically in keyring_search_iterator()
      KEYS: load key flags and expiry time atomically in proc_keys_show()

Lee, Chun-Yi (2):
      KEYS: Fix the wrong index when checking the existence of second id
      KEYS: checking the input id parameters before finding asymmetric key


 crypto/asymmetric_keys/asymmetric_type.c |    4 +-
 fs/crypto/keyinfo.c                      |    5 ++
 fs/ecryptfs/ecryptfs_kernel.h            |   44 ------------------
 fs/ecryptfs/keystore.c                   |   73 ++++++++++++++++++++++++++++++
 fs/fscache/object-list.c                 |    7 +++
 include/linux/key.h                      |   37 +++++++++------
 lib/digsig.c                             |    6 ++
 security/keys/Kconfig                    |    1 
 security/keys/encrypted-keys/encrypted.c |    9 +++-
 security/keys/gc.c                       |    7 ++-
 security/keys/key.c                      |   37 +++++++++++----
 security/keys/keyctl.c                   |    9 ++--
 security/keys/keyring.c                  |   12 +++--
 security/keys/permission.c               |    7 ++-
 security/keys/proc.c                     |   35 +++++++++-----
 security/keys/process_keys.c             |    8 ++-
 security/keys/request_key.c              |    7 +--
 security/keys/trusted.c                  |    2 -
 security/keys/user_defined.c             |    2 -
 19 files changed, 201 insertions(+), 111 deletions(-)

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
  2017-10-12 16:17 ` David Howells
  (?)
@ 2017-10-12 16:26   ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 16:26 UTC (permalink / raw)
  To: linux-security-module

Sorry, I didn't manage to include the patches because stgit doesn't consider:

    Cc: <stable@vger.kernel.org>    [v2.6.38+]

to be a valid email :-/

submitting-patches.rst and stable-kernel-rules.rst state this:

    Cc: stable@vger.kernel.org

or this:

    Cc: <stable@vger.kernel.org> # 3.3.x

David

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 16:26   ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 16:26 UTC (permalink / raw)
  To: linux-security-module

Sorry, I didn't manage to include the patches because stgit doesn't consider:

    Cc: <stable@vger.kernel.org>    [v2.6.38+]

to be a valid email :-/

submitting-patches.rst and stable-kernel-rules.rst state this:

    Cc: stable at vger.kernel.org

or this:

    Cc: <stable@vger.kernel.org> # 3.3.x

David
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 16:26   ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 16:26 UTC (permalink / raw)
  To: ebiggers; +Cc: dhowells, linux-security-module, keyrings, linux-kernel

Sorry, I didn't manage to include the patches because stgit doesn't consider:

    Cc: <stable@vger.kernel.org>    [v2.6.38+]

to be a valid email :-/

submitting-patches.rst and stable-kernel-rules.rst state this:

    Cc: stable@vger.kernel.org

or this:

    Cc: <stable@vger.kernel.org> # 3.3.x

David

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
  2017-10-12 16:17 ` David Howells
  (?)
@ 2017-10-12 18:56   ` Eric Biggers
  -1 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-12 18:56 UTC (permalink / raw)
  To: linux-security-module

Hi David, a few comments:

On Thu, Oct 12, 2017 at 05:17:27PM +0100, David Howells wrote:
> ---
> Arnd Bergmann (1):
>       security/keys: BIG_KEY requires CONFIG_CRYPTO

Doesn't this need 'Cc: <stable@vger.kernel.org>	[v4.9+]',
since the commit it fixes is there?

> 
> David Howells (2):
>       KEYS: Fix race between updating and finding a negative key

I guess the 'state' variable is fine, though it makes this patch (which will
need to be backported) a tad larger.  I'd also prefer a bit more information in
the commit message about the problem being solved.  But anyway, there are also
some problems in how READ_ONCE() and memory barriers are used (or not used) in
the new/changed code, and there is one other bug:

>  static inline bool key_is_instantiated(const struct key *key)
>  {
> -       return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
> -               !test_bit(KEY_FLAG_NEGATIVE, &key->flags);
> +       return key->state = KEY_IS_INSTANTIATED;
> +}

This should use 'smp_load_acquire(&key->state) = KEY_IS_INSTANTIATED', since
some ->describe() methods expect to access the payload after this.  Yes, it's no
worse than before, but as long as the line of code is being replaced we might as
well get it right...

> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index 87cb260e4890..1578f671a213 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>        while (!list_empty(keys)) {
>                struct key *key >                        list_entry(keys->next, struct key, graveyard_link);
> +              short state = READ_ONCE(key->state);
> +

Here the key has no more references, so nothing can be changing the state.
Thus, the READ_ONCE() isn't actually needed.

> +/*
> + * Change the key state to being instantiated.
> + */
> +static void mark_key_instantiated(struct key *key, int reject_error)
> +{
> +       smp_wmb(); /* Commit the payload before setting the state */
> +       key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
> +}
> +

smp_store_release() would make this simpler as well as guarantee that the write
is atomic.

> +       ret = key->state;
> +       if (ret < 0)
> +               goto error2; /* Negatively instantiated */

Not too important in practice (as this is constantly gotten wrong all over the
kernel, and compilers play nice enough to make it not a huge deal), but
READ_ONCE(key->state) will guarantee that the read of 'key->state' is atomic and
not e.g. done byte-by-byte.


> -       if (key->type->describe)
> +       if (key->type->describe) {
> +               smp_rmb(); /* Order access to payload after state set. */
>		  key->type->describe(key, m);
> +       }

This is the wrong place for this memory barrier.  The state is checked
separately in ->describe() and it may have changed between when it was shown in
proc_keys_show() vs. when it is checked in ->describe().  We can't actually make
these two access to ->state consistent with respect to each other right now.
The most we can do is use smp_load_acquire() in key_is_instantiated() so that at
least ->describe() isn't broken by itself.  So the change here is pointless.

> -       if (!(lflags & KEY_LOOKUP_PARTIAL) &&
> -           !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> -               goto invalid_key;
> +       if (!(lflags & KEY_LOOKUP_PARTIAL)) {
> +               if (key->state != KEY_IS_INSTANTIATED)
> +                       goto invalid_key;
> +               smp_rmb(); /* Order access to payload after state set. */
> +       }

This should be simply:

	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
		goto invalid_key;

> @@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
>                          intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
>        if (ret)
>                return -ERESTARTSYS;
> -       if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
> -               smp_rmb();
> -               return key->reject_error;
> -       }
> +       ret = READ_ONCE(key->state);
> +       if (ret < 0)
> +               return ret;
>        return key_validate(key);
> }
> EXPORT_SYMBOL(wait_for_key_construction);

smp_load_acquire() rather than READ_ONCE(), in case the caller uses this as an
indication that it is safe to access the payload.

> diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
> index 3d8c68eba516..9afa64817d4f 100644
> --- a/security/keys/user_defined.c
> +++ b/security/keys/user_defined.c
> @@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
> 
>        /* attach the new data, displacing the old */
>        key->expiry = prep->expiry;
>-       if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
>+       if (key->state < 0)
>                zap = dereference_key_locked(key);
>        rcu_assign_keypointer(key, prep->payload.data[0]);
>        prep->payload.data[0] = NULL;

This is backwards; it should be 'if (!key_is_negative(key))'.

>       KEYS: don't let add_key() update an uninstantiated key
> 

>
> +       if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
> +               ret = wait_for_key_construction(key_ref_to_ptr(key_ref), true);
> +               if (ret < 0) {
> +                       key_ref_put(key_ref);
> +                       key_ref = ERR_PTR(ret);
> +                       goto error_free_prep;
> +               }
> +       }
> +

'key' is NULL here.  It should be 'key_ref_to_ptr(key_ref)'.


>       KEYS: load key flags and expiry time atomically in keyring_search_iterator()

The commit message refers to both flags and expiry time, but now it only
actually updates how the expiry time is loaded, as the flags were already done
by "KEYS: Fix race between updating and finding a negative key".

>       KEYS: load key flags and expiry time atomically in proc_keys_show()

The commit message refers to negative ('N') but not instantiated ('I') as the
example, but that is no longer a valid example since this patch comes after
"KEYS: Fix race between updating and finding a negative key".

Eric

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 18:56   ` Eric Biggers
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-12 18:56 UTC (permalink / raw)
  To: linux-security-module

Hi David, a few comments:

On Thu, Oct 12, 2017 at 05:17:27PM +0100, David Howells wrote:
> ---
> Arnd Bergmann (1):
>       security/keys: BIG_KEY requires CONFIG_CRYPTO

Doesn't this need 'Cc: <stable@vger.kernel.org>	[v4.9+]',
since the commit it fixes is there?

> 
> David Howells (2):
>       KEYS: Fix race between updating and finding a negative key

I guess the 'state' variable is fine, though it makes this patch (which will
need to be backported) a tad larger.  I'd also prefer a bit more information in
the commit message about the problem being solved.  But anyway, there are also
some problems in how READ_ONCE() and memory barriers are used (or not used) in
the new/changed code, and there is one other bug:

>  static inline bool key_is_instantiated(const struct key *key)
>  {
> -       return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
> -               !test_bit(KEY_FLAG_NEGATIVE, &key->flags);
> +       return key->state == KEY_IS_INSTANTIATED;
> +}

This should use 'smp_load_acquire(&key->state) == KEY_IS_INSTANTIATED', since
some ->describe() methods expect to access the payload after this.  Yes, it's no
worse than before, but as long as the line of code is being replaced we might as
well get it right...

> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index 87cb260e4890..1578f671a213 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>        while (!list_empty(keys)) {
>                struct key *key =
>                        list_entry(keys->next, struct key, graveyard_link);
> +              short state = READ_ONCE(key->state);
> +

Here the key has no more references, so nothing can be changing the state.
Thus, the READ_ONCE() isn't actually needed.

> +/*
> + * Change the key state to being instantiated.
> + */
> +static void mark_key_instantiated(struct key *key, int reject_error)
> +{
> +       smp_wmb(); /* Commit the payload before setting the state */
> +       key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
> +}
> +

smp_store_release() would make this simpler as well as guarantee that the write
is atomic.

> +       ret = key->state;
> +       if (ret < 0)
> +               goto error2; /* Negatively instantiated */

Not too important in practice (as this is constantly gotten wrong all over the
kernel, and compilers play nice enough to make it not a huge deal), but
READ_ONCE(key->state) will guarantee that the read of 'key->state' is atomic and
not e.g. done byte-by-byte.


> -       if (key->type->describe)
> +       if (key->type->describe) {
> +               smp_rmb(); /* Order access to payload after state set. */
>		  key->type->describe(key, m);
> +       }

This is the wrong place for this memory barrier.  The state is checked
separately in ->describe() and it may have changed between when it was shown in
proc_keys_show() vs. when it is checked in ->describe().  We can't actually make
these two access to ->state consistent with respect to each other right now.
The most we can do is use smp_load_acquire() in key_is_instantiated() so that at
least ->describe() isn't broken by itself.  So the change here is pointless.

> -       if (!(lflags & KEY_LOOKUP_PARTIAL) &&
> -           !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> -               goto invalid_key;
> +       if (!(lflags & KEY_LOOKUP_PARTIAL)) {
> +               if (key->state != KEY_IS_INSTANTIATED)
> +                       goto invalid_key;
> +               smp_rmb(); /* Order access to payload after state set. */
> +       }

This should be simply:

	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
		goto invalid_key;

> @@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
>                          intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
>        if (ret)
>                return -ERESTARTSYS;
> -       if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
> -               smp_rmb();
> -               return key->reject_error;
> -       }
> +       ret = READ_ONCE(key->state);
> +       if (ret < 0)
> +               return ret;
>        return key_validate(key);
> }
> EXPORT_SYMBOL(wait_for_key_construction);

smp_load_acquire() rather than READ_ONCE(), in case the caller uses this as an
indication that it is safe to access the payload.

> diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
> index 3d8c68eba516..9afa64817d4f 100644
> --- a/security/keys/user_defined.c
> +++ b/security/keys/user_defined.c
> @@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
> 
>        /* attach the new data, displacing the old */
>        key->expiry = prep->expiry;
>-       if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
>+       if (key->state < 0)
>                zap = dereference_key_locked(key);
>        rcu_assign_keypointer(key, prep->payload.data[0]);
>        prep->payload.data[0] = NULL;

This is backwards; it should be 'if (!key_is_negative(key))'.

>       KEYS: don't let add_key() update an uninstantiated key
> 

>
> +       if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
> +               ret = wait_for_key_construction(key_ref_to_ptr(key_ref), true);
> +               if (ret < 0) {
> +                       key_ref_put(key_ref);
> +                       key_ref = ERR_PTR(ret);
> +                       goto error_free_prep;
> +               }
> +       }
> +

'key' is NULL here.  It should be 'key_ref_to_ptr(key_ref)'.


>       KEYS: load key flags and expiry time atomically in keyring_search_iterator()

The commit message refers to both flags and expiry time, but now it only
actually updates how the expiry time is loaded, as the flags were already done
by "KEYS: Fix race between updating and finding a negative key".

>       KEYS: load key flags and expiry time atomically in proc_keys_show()

The commit message refers to negative ('N') but not instantiated ('I') as the
example, but that is no longer a valid example since this patch comes after
"KEYS: Fix race between updating and finding a negative key".

Eric
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 18:56   ` Eric Biggers
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-12 18:56 UTC (permalink / raw)
  To: David Howells; +Cc: ebiggers, linux-security-module, keyrings, linux-kernel

Hi David, a few comments:

On Thu, Oct 12, 2017 at 05:17:27PM +0100, David Howells wrote:
> ---
> Arnd Bergmann (1):
>       security/keys: BIG_KEY requires CONFIG_CRYPTO

Doesn't this need 'Cc: <stable@vger.kernel.org>	[v4.9+]',
since the commit it fixes is there?

> 
> David Howells (2):
>       KEYS: Fix race between updating and finding a negative key

I guess the 'state' variable is fine, though it makes this patch (which will
need to be backported) a tad larger.  I'd also prefer a bit more information in
the commit message about the problem being solved.  But anyway, there are also
some problems in how READ_ONCE() and memory barriers are used (or not used) in
the new/changed code, and there is one other bug:

>  static inline bool key_is_instantiated(const struct key *key)
>  {
> -       return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
> -               !test_bit(KEY_FLAG_NEGATIVE, &key->flags);
> +       return key->state == KEY_IS_INSTANTIATED;
> +}

This should use 'smp_load_acquire(&key->state) == KEY_IS_INSTANTIATED', since
some ->describe() methods expect to access the payload after this.  Yes, it's no
worse than before, but as long as the line of code is being replaced we might as
well get it right...

> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index 87cb260e4890..1578f671a213 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>        while (!list_empty(keys)) {
>                struct key *key =
>                        list_entry(keys->next, struct key, graveyard_link);
> +              short state = READ_ONCE(key->state);
> +

Here the key has no more references, so nothing can be changing the state.
Thus, the READ_ONCE() isn't actually needed.

> +/*
> + * Change the key state to being instantiated.
> + */
> +static void mark_key_instantiated(struct key *key, int reject_error)
> +{
> +       smp_wmb(); /* Commit the payload before setting the state */
> +       key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
> +}
> +

smp_store_release() would make this simpler as well as guarantee that the write
is atomic.

> +       ret = key->state;
> +       if (ret < 0)
> +               goto error2; /* Negatively instantiated */

Not too important in practice (as this is constantly gotten wrong all over the
kernel, and compilers play nice enough to make it not a huge deal), but
READ_ONCE(key->state) will guarantee that the read of 'key->state' is atomic and
not e.g. done byte-by-byte.


> -       if (key->type->describe)
> +       if (key->type->describe) {
> +               smp_rmb(); /* Order access to payload after state set. */
>		  key->type->describe(key, m);
> +       }

This is the wrong place for this memory barrier.  The state is checked
separately in ->describe() and it may have changed between when it was shown in
proc_keys_show() vs. when it is checked in ->describe().  We can't actually make
these two access to ->state consistent with respect to each other right now.
The most we can do is use smp_load_acquire() in key_is_instantiated() so that at
least ->describe() isn't broken by itself.  So the change here is pointless.

> -       if (!(lflags & KEY_LOOKUP_PARTIAL) &&
> -           !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> -               goto invalid_key;
> +       if (!(lflags & KEY_LOOKUP_PARTIAL)) {
> +               if (key->state != KEY_IS_INSTANTIATED)
> +                       goto invalid_key;
> +               smp_rmb(); /* Order access to payload after state set. */
> +       }

This should be simply:

	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
		goto invalid_key;

> @@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
>                          intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
>        if (ret)
>                return -ERESTARTSYS;
> -       if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
> -               smp_rmb();
> -               return key->reject_error;
> -       }
> +       ret = READ_ONCE(key->state);
> +       if (ret < 0)
> +               return ret;
>        return key_validate(key);
> }
> EXPORT_SYMBOL(wait_for_key_construction);

smp_load_acquire() rather than READ_ONCE(), in case the caller uses this as an
indication that it is safe to access the payload.

> diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
> index 3d8c68eba516..9afa64817d4f 100644
> --- a/security/keys/user_defined.c
> +++ b/security/keys/user_defined.c
> @@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
> 
>        /* attach the new data, displacing the old */
>        key->expiry = prep->expiry;
>-       if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
>+       if (key->state < 0)
>                zap = dereference_key_locked(key);
>        rcu_assign_keypointer(key, prep->payload.data[0]);
>        prep->payload.data[0] = NULL;

This is backwards; it should be 'if (!key_is_negative(key))'.

>       KEYS: don't let add_key() update an uninstantiated key
> 

>
> +       if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
> +               ret = wait_for_key_construction(key_ref_to_ptr(key_ref), true);
> +               if (ret < 0) {
> +                       key_ref_put(key_ref);
> +                       key_ref = ERR_PTR(ret);
> +                       goto error_free_prep;
> +               }
> +       }
> +

'key' is NULL here.  It should be 'key_ref_to_ptr(key_ref)'.


>       KEYS: load key flags and expiry time atomically in keyring_search_iterator()

The commit message refers to both flags and expiry time, but now it only
actually updates how the expiry time is loaded, as the flags were already done
by "KEYS: Fix race between updating and finding a negative key".

>       KEYS: load key flags and expiry time atomically in proc_keys_show()

The commit message refers to negative ('N') but not instantiated ('I') as the
example, but that is no longer a valid example since this patch comes after
"KEYS: Fix race between updating and finding a negative key".

Eric

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 20:15   ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:15 UTC (permalink / raw)
  To: linux-security-module


Hi Eric,

Are you okay with my changes/substitution of your key instantiation-state
patches?

David
---
Here's a collection of fixes for Linux keyrings, mostly thanks to Eric
Biggers, including:

 (1) Fix a bunch of places where kernel drivers may access revoked user-type
     keys and don't do it correctly.

 (2) Fix some ecryptfs bits.

 (3) Fix big_key to require CONFIG_CRYPTO.

 (4) Fix a couple of bugs in the asymmetric key type.

 (5) Fix a race between updating and finding negative keys.

 (6) Prevent add_key() from updating uninstantiated keys.

 (7) Make loading of key flags and expiry time atomic when not holding locks.

The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-fixes

David
---
Arnd Bergmann (1):
      security/keys: BIG_KEY requires CONFIG_CRYPTO

David Howells (2):
      KEYS: Fix race between updating and finding a negative key
      KEYS: don't let add_key() update an uninstantiated key

Eric Biggers (10):
      KEYS: encrypted: fix dereference of NULL user_key_payload
      FS-Cache: fix dereference of NULL user_key_payload
      lib/digsig: fix dereference of NULL user_key_payload
      fscrypt: fix dereference of NULL user_key_payload
      ecryptfs: fix dereference of NULL user_key_payload
      ecryptfs: fix out-of-bounds read of key payload
      ecryptfs: move key payload accessor functions into keystore.c
      KEYS: load key flags and expiry time atomically in key_validate()
      KEYS: load key flags and expiry time atomically in keyring_search_iterator()
      KEYS: load key flags and expiry time atomically in proc_keys_show()

Lee, Chun-Yi (2):
      KEYS: Fix the wrong index when checking the existence of second id
      KEYS: checking the input id parameters before finding asymmetric key


 crypto/asymmetric_keys/asymmetric_type.c |    4 +-
 fs/crypto/keyinfo.c                      |    5 ++
 fs/ecryptfs/ecryptfs_kernel.h            |   44 ------------------
 fs/ecryptfs/keystore.c                   |   73 ++++++++++++++++++++++++++++++
 fs/fscache/object-list.c                 |    7 +++
 include/linux/key.h                      |   37 +++++++++------
 lib/digsig.c                             |    6 ++
 security/keys/Kconfig                    |    1 
 security/keys/encrypted-keys/encrypted.c |    9 +++-
 security/keys/gc.c                       |    7 ++-
 security/keys/key.c                      |   37 +++++++++++----
 security/keys/keyctl.c                   |    9 ++--
 security/keys/keyring.c                  |   12 +++--
 security/keys/permission.c               |    7 ++-
 security/keys/proc.c                     |   35 +++++++++-----
 security/keys/process_keys.c             |    8 ++-
 security/keys/request_key.c              |    7 +--
 security/keys/trusted.c                  |    2 -
 security/keys/user_defined.c             |    2 -
 19 files changed, 201 insertions(+), 111 deletions(-)


^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 20:15   ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:15 UTC (permalink / raw)
  To: linux-security-module


Hi Eric,

Are you okay with my changes/substitution of your key instantiation-state
patches?

David
---
Here's a collection of fixes for Linux keyrings, mostly thanks to Eric
Biggers, including:

 (1) Fix a bunch of places where kernel drivers may access revoked user-type
     keys and don't do it correctly.

 (2) Fix some ecryptfs bits.

 (3) Fix big_key to require CONFIG_CRYPTO.

 (4) Fix a couple of bugs in the asymmetric key type.

 (5) Fix a race between updating and finding negative keys.

 (6) Prevent add_key() from updating uninstantiated keys.

 (7) Make loading of key flags and expiry time atomic when not holding locks.

The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-fixes

David
---
Arnd Bergmann (1):
      security/keys: BIG_KEY requires CONFIG_CRYPTO

David Howells (2):
      KEYS: Fix race between updating and finding a negative key
      KEYS: don't let add_key() update an uninstantiated key

Eric Biggers (10):
      KEYS: encrypted: fix dereference of NULL user_key_payload
      FS-Cache: fix dereference of NULL user_key_payload
      lib/digsig: fix dereference of NULL user_key_payload
      fscrypt: fix dereference of NULL user_key_payload
      ecryptfs: fix dereference of NULL user_key_payload
      ecryptfs: fix out-of-bounds read of key payload
      ecryptfs: move key payload accessor functions into keystore.c
      KEYS: load key flags and expiry time atomically in key_validate()
      KEYS: load key flags and expiry time atomically in keyring_search_iterator()
      KEYS: load key flags and expiry time atomically in proc_keys_show()

Lee, Chun-Yi (2):
      KEYS: Fix the wrong index when checking the existence of second id
      KEYS: checking the input id parameters before finding asymmetric key


 crypto/asymmetric_keys/asymmetric_type.c |    4 +-
 fs/crypto/keyinfo.c                      |    5 ++
 fs/ecryptfs/ecryptfs_kernel.h            |   44 ------------------
 fs/ecryptfs/keystore.c                   |   73 ++++++++++++++++++++++++++++++
 fs/fscache/object-list.c                 |    7 +++
 include/linux/key.h                      |   37 +++++++++------
 lib/digsig.c                             |    6 ++
 security/keys/Kconfig                    |    1 
 security/keys/encrypted-keys/encrypted.c |    9 +++-
 security/keys/gc.c                       |    7 ++-
 security/keys/key.c                      |   37 +++++++++++----
 security/keys/keyctl.c                   |    9 ++--
 security/keys/keyring.c                  |   12 +++--
 security/keys/permission.c               |    7 ++-
 security/keys/proc.c                     |   35 +++++++++-----
 security/keys/process_keys.c             |    8 ++-
 security/keys/request_key.c              |    7 +--
 security/keys/trusted.c                  |    2 -
 security/keys/user_defined.c             |    2 -
 19 files changed, 201 insertions(+), 111 deletions(-)

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-12 20:15   ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:15 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel


Hi Eric,

Are you okay with my changes/substitution of your key instantiation-state
patches?

David
---
Here's a collection of fixes for Linux keyrings, mostly thanks to Eric
Biggers, including:

 (1) Fix a bunch of places where kernel drivers may access revoked user-type
     keys and don't do it correctly.

 (2) Fix some ecryptfs bits.

 (3) Fix big_key to require CONFIG_CRYPTO.

 (4) Fix a couple of bugs in the asymmetric key type.

 (5) Fix a race between updating and finding negative keys.

 (6) Prevent add_key() from updating uninstantiated keys.

 (7) Make loading of key flags and expiry time atomic when not holding locks.

The patches can be found here also:

	http://git.kernel.org/cgit/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-fixes

David
---
Arnd Bergmann (1):
      security/keys: BIG_KEY requires CONFIG_CRYPTO

David Howells (2):
      KEYS: Fix race between updating and finding a negative key
      KEYS: don't let add_key() update an uninstantiated key

Eric Biggers (10):
      KEYS: encrypted: fix dereference of NULL user_key_payload
      FS-Cache: fix dereference of NULL user_key_payload
      lib/digsig: fix dereference of NULL user_key_payload
      fscrypt: fix dereference of NULL user_key_payload
      ecryptfs: fix dereference of NULL user_key_payload
      ecryptfs: fix out-of-bounds read of key payload
      ecryptfs: move key payload accessor functions into keystore.c
      KEYS: load key flags and expiry time atomically in key_validate()
      KEYS: load key flags and expiry time atomically in keyring_search_iterator()
      KEYS: load key flags and expiry time atomically in proc_keys_show()

Lee, Chun-Yi (2):
      KEYS: Fix the wrong index when checking the existence of second id
      KEYS: checking the input id parameters before finding asymmetric key


 crypto/asymmetric_keys/asymmetric_type.c |    4 +-
 fs/crypto/keyinfo.c                      |    5 ++
 fs/ecryptfs/ecryptfs_kernel.h            |   44 ------------------
 fs/ecryptfs/keystore.c                   |   73 ++++++++++++++++++++++++++++++
 fs/fscache/object-list.c                 |    7 +++
 include/linux/key.h                      |   37 +++++++++------
 lib/digsig.c                             |    6 ++
 security/keys/Kconfig                    |    1 
 security/keys/encrypted-keys/encrypted.c |    9 +++-
 security/keys/gc.c                       |    7 ++-
 security/keys/key.c                      |   37 +++++++++++----
 security/keys/keyctl.c                   |    9 ++--
 security/keys/keyring.c                  |   12 +++--
 security/keys/permission.c               |    7 ++-
 security/keys/proc.c                     |   35 +++++++++-----
 security/keys/process_keys.c             |    8 ++-
 security/keys/request_key.c              |    7 +--
 security/keys/trusted.c                  |    2 -
 security/keys/user_defined.c             |    2 -
 19 files changed, 201 insertions(+), 111 deletions(-)

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [PATCH 01/15] KEYS: encrypted: fix dereference of NULL user_key_payload
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

A key of type "encrypted" references a "master key" which is used to
encrypt and decrypt the encrypted key's payload.  However, when we
accessed the master key's payload, we failed to handle the case where
the master key has been revoked, which sets the payload pointer to NULL.
Note that request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

This was an issue for master keys of type "user" only.  Master keys can
also be of type "trusted", but those cannot be revoked.

Fixes: 7e70cb497850 ("keys: add new key-type encrypted")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.38+]
Cc: Mimi Zohar <zohar@linux.vnet.ibm.com>
Cc: David Safford <safford@us.ibm.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/encrypted-keys/encrypted.c |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..535db141f4da 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -309,6 +309,13 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 
 	down_read(&ukey->sem);
 	upayload = user_key_payload_locked(ukey);
+	if (!upayload) {
+		/* key was revoked before we acquired its semaphore */
+		up_read(&ukey->sem);
+		key_put(ukey);
+		ukey = ERR_PTR(-EKEYREVOKED);
+		goto error;
+	}
 	*master_key = upayload->data;
 	*master_keylen = upayload->datalen;
 error:


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 01/15] KEYS: encrypted: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

A key of type "encrypted" references a "master key" which is used to
encrypt and decrypt the encrypted key's payload.  However, when we
accessed the master key's payload, we failed to handle the case where
the master key has been revoked, which sets the payload pointer to NULL.
Note that request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

This was an issue for master keys of type "user" only.  Master keys can
also be of type "trusted", but those cannot be revoked.

Fixes: 7e70cb497850 ("keys: add new key-type encrypted")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.38+]
Cc: Mimi Zohar <zohar@linux.vnet.ibm.com>
Cc: David Safford <safford@us.ibm.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/encrypted-keys/encrypted.c |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..535db141f4da 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -309,6 +309,13 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 
 	down_read(&ukey->sem);
 	upayload = user_key_payload_locked(ukey);
+	if (!upayload) {
+		/* key was revoked before we acquired its semaphore */
+		up_read(&ukey->sem);
+		key_put(ukey);
+		ukey = ERR_PTR(-EKEYREVOKED);
+		goto error;
+	}
 	*master_key = upayload->data;
 	*master_keylen = upayload->datalen;
 error:

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 01/15] KEYS: encrypted: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable,
	Mimi Zohar, David Safford

From: Eric Biggers <ebiggers@google.com>

A key of type "encrypted" references a "master key" which is used to
encrypt and decrypt the encrypted key's payload.  However, when we
accessed the master key's payload, we failed to handle the case where
the master key has been revoked, which sets the payload pointer to NULL.
Note that request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

This was an issue for master keys of type "user" only.  Master keys can
also be of type "trusted", but those cannot be revoked.

Fixes: 7e70cb497850 ("keys: add new key-type encrypted")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.38+]
Cc: Mimi Zohar <zohar@linux.vnet.ibm.com>
Cc: David Safford <safford@us.ibm.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/encrypted-keys/encrypted.c |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 69855ba0d3b3..535db141f4da 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -309,6 +309,13 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k
 
 	down_read(&ukey->sem);
 	upayload = user_key_payload_locked(ukey);
+	if (!upayload) {
+		/* key was revoked before we acquired its semaphore */
+		up_read(&ukey->sem);
+		key_put(ukey);
+		ukey = ERR_PTR(-EKEYREVOKED);
+		goto error;
+	}
 	*master_key = upayload->data;
 	*master_keylen = upayload->datalen;
 error:

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 02/15] FS-Cache: fix dereference of NULL user_key_payload
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

When the file /proc/fs/fscache/objects (available with
CONFIG_FSCACHE_OBJECT_LIST=y) is opened, we request a user key with
description "fscache:objlist", then access its payload.  However, a
revoked key has a NULL payload, and we failed to check for this.
request_key() *does* skip revoked keys, but there is still a window
where the key can be revoked before we access its payload.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 4fbf4291aa15 ("FS-Cache: Allow the current state of all objects to be dumped")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.32+]
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/fscache/object-list.c |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..0438d4cd91ef 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -331,6 +331,13 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	rcu_read_lock();
 
 	confkey = user_key_payload_rcu(key);
+	if (!confkey) {
+		/* key was revoked */
+		rcu_read_unlock();
+		key_put(key);
+		goto no_config;
+	}
+
 	buf = confkey->data;
 
 	for (len = confkey->datalen - 1; len >= 0; len--) {


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 02/15] FS-Cache: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

When the file /proc/fs/fscache/objects (available with
CONFIG_FSCACHE_OBJECT_LIST=y) is opened, we request a user key with
description "fscache:objlist", then access its payload.  However, a
revoked key has a NULL payload, and we failed to check for this.
request_key() *does* skip revoked keys, but there is still a window
where the key can be revoked before we access its payload.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 4fbf4291aa15 ("FS-Cache: Allow the current state of all objects to be dumped")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.32+]
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/fscache/object-list.c |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..0438d4cd91ef 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -331,6 +331,13 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	rcu_read_lock();
 
 	confkey = user_key_payload_rcu(key);
+	if (!confkey) {
+		/* key was revoked */
+		rcu_read_unlock();
+		key_put(key);
+		goto no_config;
+	}
+
 	buf = confkey->data;
 
 	for (len = confkey->datalen - 1; len >= 0; len--) {

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 02/15] FS-Cache: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable

From: Eric Biggers <ebiggers@google.com>

When the file /proc/fs/fscache/objects (available with
CONFIG_FSCACHE_OBJECT_LIST=y) is opened, we request a user key with
description "fscache:objlist", then access its payload.  However, a
revoked key has a NULL payload, and we failed to check for this.
request_key() *does* skip revoked keys, but there is still a window
where the key can be revoked before we access its payload.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 4fbf4291aa15 ("FS-Cache: Allow the current state of all objects to be dumped")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.32+]
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/fscache/object-list.c |    7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index b5ab06fabc60..0438d4cd91ef 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -331,6 +331,13 @@ static void fscache_objlist_config(struct fscache_objlist_data *data)
 	rcu_read_lock();
 
 	confkey = user_key_payload_rcu(key);
+	if (!confkey) {
+		/* key was revoked */
+		rcu_read_unlock();
+		key_put(key);
+		goto no_config;
+	}
+
 	buf = confkey->data;
 
 	for (len = confkey->datalen - 1; len >= 0; len--) {

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 03/15] lib/digsig: fix dereference of NULL user_key_payload
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

digsig_verify() requests a user key, then accesses its payload.
However, a revoked key has a NULL payload, and we failed to check for
this.  request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 051dbb918c7f ("crypto: digital signature verification support")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v3.3+]
Cc: Dmitry Kasatkin <dmitry.kasatkin@intel.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 lib/digsig.c |    6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lib/digsig.c b/lib/digsig.c
index 03d7c63837ae..6ba6fcd92dd1 100644
--- a/lib/digsig.c
+++ b/lib/digsig.c
@@ -87,6 +87,12 @@ static int digsig_verify_rsa(struct key *key,
 	down_read(&key->sem);
 	ukp = user_key_payload_locked(key);
 
+	if (!ukp) {
+		/* key was revoked before we acquired its semaphore */
+		err = -EKEYREVOKED;
+		goto err1;
+	}
+
 	if (ukp->datalen < sizeof(*pkh))
 		goto err1;
 


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 03/15] lib/digsig: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

digsig_verify() requests a user key, then accesses its payload.
However, a revoked key has a NULL payload, and we failed to check for
this.  request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 051dbb918c7f ("crypto: digital signature verification support")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v3.3+]
Cc: Dmitry Kasatkin <dmitry.kasatkin@intel.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 lib/digsig.c |    6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lib/digsig.c b/lib/digsig.c
index 03d7c63837ae..6ba6fcd92dd1 100644
--- a/lib/digsig.c
+++ b/lib/digsig.c
@@ -87,6 +87,12 @@ static int digsig_verify_rsa(struct key *key,
 	down_read(&key->sem);
 	ukp = user_key_payload_locked(key);
 
+	if (!ukp) {
+		/* key was revoked before we acquired its semaphore */
+		err = -EKEYREVOKED;
+		goto err1;
+	}
+
 	if (ukp->datalen < sizeof(*pkh))
 		goto err1;
 

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info@ http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 03/15] lib/digsig: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable,
	Dmitry Kasatkin

From: Eric Biggers <ebiggers@google.com>

digsig_verify() requests a user key, then accesses its payload.
However, a revoked key has a NULL payload, and we failed to check for
this.  request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 051dbb918c7f ("crypto: digital signature verification support")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v3.3+]
Cc: Dmitry Kasatkin <dmitry.kasatkin@intel.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 lib/digsig.c |    6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lib/digsig.c b/lib/digsig.c
index 03d7c63837ae..6ba6fcd92dd1 100644
--- a/lib/digsig.c
+++ b/lib/digsig.c
@@ -87,6 +87,12 @@ static int digsig_verify_rsa(struct key *key,
 	down_read(&key->sem);
 	ukp = user_key_payload_locked(key);
 
+	if (!ukp) {
+		/* key was revoked before we acquired its semaphore */
+		err = -EKEYREVOKED;
+		goto err1;
+	}
+
 	if (ukp->datalen < sizeof(*pkh))
 		goto err1;
 

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 04/15] fscrypt: fix dereference of NULL user_key_payload
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

When an fscrypt-encrypted file is opened, we request the file's master
key from the keyrings service as a logon key, then access its payload.
However, a revoked key has a NULL payload, and we failed to check for
this.  request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 88bd6ccdcdd6 ("ext4 crypto: add encryption key management facilities")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v4.1+]
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/crypto/keyinfo.c |    5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/crypto/keyinfo.c b/fs/crypto/keyinfo.c
index 018c588c7ac3..8e704d12a1cf 100644
--- a/fs/crypto/keyinfo.c
+++ b/fs/crypto/keyinfo.c
@@ -109,6 +109,11 @@ static int validate_user_key(struct fscrypt_info *crypt_info,
 		goto out;
 	}
 	ukp = user_key_payload_locked(keyring_key);
+	if (!ukp) {
+		/* key was revoked before we acquired its semaphore */
+		res = -EKEYREVOKED;
+		goto out;
+	}
 	if (ukp->datalen != sizeof(struct fscrypt_key)) {
 		res = -EINVAL;
 		goto out;


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 04/15] fscrypt: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

When an fscrypt-encrypted file is opened, we request the file's master
key from the keyrings service as a logon key, then access its payload.
However, a revoked key has a NULL payload, and we failed to check for
this.  request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 88bd6ccdcdd6 ("ext4 crypto: add encryption key management facilities")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v4.1+]
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/crypto/keyinfo.c |    5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/crypto/keyinfo.c b/fs/crypto/keyinfo.c
index 018c588c7ac3..8e704d12a1cf 100644
--- a/fs/crypto/keyinfo.c
+++ b/fs/crypto/keyinfo.c
@@ -109,6 +109,11 @@ static int validate_user_key(struct fscrypt_info *crypt_info,
 		goto out;
 	}
 	ukp = user_key_payload_locked(keyring_key);
+	if (!ukp) {
+		/* key was revoked before we acquired its semaphore */
+		res = -EKEYREVOKED;
+		goto out;
+	}
 	if (ukp->datalen != sizeof(struct fscrypt_key)) {
 		res = -EINVAL;
 		goto out;

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 04/15] fscrypt: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable

From: Eric Biggers <ebiggers@google.com>

When an fscrypt-encrypted file is opened, we request the file's master
key from the keyrings service as a logon key, then access its payload.
However, a revoked key has a NULL payload, and we failed to check for
this.  request_key() *does* skip revoked keys, but there is still a
window where the key can be revoked before we acquire its semaphore.

Fix it by checking for a NULL payload, treating it like a key which was
already revoked at the time it was requested.

Fixes: 88bd6ccdcdd6 ("ext4 crypto: add encryption key management facilities")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v4.1+]
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/crypto/keyinfo.c |    5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/crypto/keyinfo.c b/fs/crypto/keyinfo.c
index 018c588c7ac3..8e704d12a1cf 100644
--- a/fs/crypto/keyinfo.c
+++ b/fs/crypto/keyinfo.c
@@ -109,6 +109,11 @@ static int validate_user_key(struct fscrypt_info *crypt_info,
 		goto out;
 	}
 	ukp = user_key_payload_locked(keyring_key);
+	if (!ukp) {
+		/* key was revoked before we acquired its semaphore */
+		res = -EKEYREVOKED;
+		goto out;
+	}
 	if (ukp->datalen != sizeof(struct fscrypt_key)) {
 		res = -EINVAL;
 		goto out;

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 05/15] ecryptfs: fix dereference of NULL user_key_payload
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

In eCryptfs, we failed to verify that the authentication token keys are
not revoked before dereferencing their payloads, which is problematic
because the payload of a revoked key is NULL.  request_key() *does* skip
revoked keys, but there is still a window where the key can be revoked
before we acquire the key semaphore.

Fix it by updating ecryptfs_get_key_payload_data() to return
-EKEYREVOKED if the key payload is NULL.  For completeness we check this
for "encrypted" keys as well as "user" keys, although encrypted keys
cannot be revoked currently.

Alternatively we could use key_validate(), but since we'll also need to
fix ecryptfs_get_key_payload_data() to validate the payload length, it
seems appropriate to just check the payload pointer.

Fixes: 237fead61998 ("[PATCH] ecryptfs: fs/Makefile and fs/Kconfig")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.19+]
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |   24 +++++++++++++++++-------
 fs/ecryptfs/keystore.c        |    9 ++++++++-
 2 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 9c351bf757b2..3fbc0ff79699 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -84,11 +84,16 @@ struct ecryptfs_page_crypt_context {
 static inline struct ecryptfs_auth_tok *
 ecryptfs_get_encrypted_key_payload_data(struct key *key)
 {
-	if (key->type = &key_type_encrypted)
-		return (struct ecryptfs_auth_tok *)
-			(&((struct encrypted_key_payload *)key->payload.data[0])->payload_data);
-	else
+	struct encrypted_key_payload *payload;
+
+	if (key->type != &key_type_encrypted)
 		return NULL;
+
+	payload = key->payload.data[0];
+	if (!payload)
+		return ERR_PTR(-EKEYREVOKED);
+
+	return (struct ecryptfs_auth_tok *)payload->payload_data;
 }
 
 static inline struct key *ecryptfs_get_encrypted_key(char *sig)
@@ -114,12 +119,17 @@ static inline struct ecryptfs_auth_tok *
 ecryptfs_get_key_payload_data(struct key *key)
 {
 	struct ecryptfs_auth_tok *auth_tok;
+	struct user_key_payload *ukp;
 
 	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
-	if (!auth_tok)
-		return (struct ecryptfs_auth_tok *)user_key_payload_locked(key)->data;
-	else
+	if (auth_tok)
 		return auth_tok;
+
+	ukp = user_key_payload_locked(key);
+	if (!ukp)
+		return ERR_PTR(-EKEYREVOKED);
+
+	return (struct ecryptfs_auth_tok *)ukp->data;
 }
 
 #define ECRYPTFS_MAX_KEYSET_SIZE 1024
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index 3cf1546dca82..fa218cd64f74 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -459,7 +459,8 @@ static int ecryptfs_verify_version(u16 version)
  * @auth_tok_key: key containing the authentication token
  * @auth_tok: authentication token
  *
- * Returns zero on valid auth tok; -EINVAL otherwise
+ * Returns zero on valid auth tok; -EINVAL if the payload is invalid; or
+ * -EKEYREVOKED if the key was revoked before we acquired its semaphore.
  */
 static int
 ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
@@ -468,6 +469,12 @@ ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
 	int rc = 0;
 
 	(*auth_tok) = ecryptfs_get_key_payload_data(auth_tok_key);
+	if (IS_ERR(*auth_tok)) {
+		rc = PTR_ERR(*auth_tok);
+		*auth_tok = NULL;
+		goto out;
+	}
+
 	if (ecryptfs_verify_version((*auth_tok)->version)) {
 		printk(KERN_ERR "Data structure version mismatch. Userspace "
 		       "tools must match eCryptfs kernel module with major "


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 05/15] ecryptfs: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

In eCryptfs, we failed to verify that the authentication token keys are
not revoked before dereferencing their payloads, which is problematic
because the payload of a revoked key is NULL.  request_key() *does* skip
revoked keys, but there is still a window where the key can be revoked
before we acquire the key semaphore.

Fix it by updating ecryptfs_get_key_payload_data() to return
-EKEYREVOKED if the key payload is NULL.  For completeness we check this
for "encrypted" keys as well as "user" keys, although encrypted keys
cannot be revoked currently.

Alternatively we could use key_validate(), but since we'll also need to
fix ecryptfs_get_key_payload_data() to validate the payload length, it
seems appropriate to just check the payload pointer.

Fixes: 237fead61998 ("[PATCH] ecryptfs: fs/Makefile and fs/Kconfig")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.19+]
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |   24 +++++++++++++++++-------
 fs/ecryptfs/keystore.c        |    9 ++++++++-
 2 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 9c351bf757b2..3fbc0ff79699 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -84,11 +84,16 @@ struct ecryptfs_page_crypt_context {
 static inline struct ecryptfs_auth_tok *
 ecryptfs_get_encrypted_key_payload_data(struct key *key)
 {
-	if (key->type == &key_type_encrypted)
-		return (struct ecryptfs_auth_tok *)
-			(&((struct encrypted_key_payload *)key->payload.data[0])->payload_data);
-	else
+	struct encrypted_key_payload *payload;
+
+	if (key->type != &key_type_encrypted)
 		return NULL;
+
+	payload = key->payload.data[0];
+	if (!payload)
+		return ERR_PTR(-EKEYREVOKED);
+
+	return (struct ecryptfs_auth_tok *)payload->payload_data;
 }
 
 static inline struct key *ecryptfs_get_encrypted_key(char *sig)
@@ -114,12 +119,17 @@ static inline struct ecryptfs_auth_tok *
 ecryptfs_get_key_payload_data(struct key *key)
 {
 	struct ecryptfs_auth_tok *auth_tok;
+	struct user_key_payload *ukp;
 
 	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
-	if (!auth_tok)
-		return (struct ecryptfs_auth_tok *)user_key_payload_locked(key)->data;
-	else
+	if (auth_tok)
 		return auth_tok;
+
+	ukp = user_key_payload_locked(key);
+	if (!ukp)
+		return ERR_PTR(-EKEYREVOKED);
+
+	return (struct ecryptfs_auth_tok *)ukp->data;
 }
 
 #define ECRYPTFS_MAX_KEYSET_SIZE 1024
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index 3cf1546dca82..fa218cd64f74 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -459,7 +459,8 @@ static int ecryptfs_verify_version(u16 version)
  * @auth_tok_key: key containing the authentication token
  * @auth_tok: authentication token
  *
- * Returns zero on valid auth tok; -EINVAL otherwise
+ * Returns zero on valid auth tok; -EINVAL if the payload is invalid; or
+ * -EKEYREVOKED if the key was revoked before we acquired its semaphore.
  */
 static int
 ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
@@ -468,6 +469,12 @@ ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
 	int rc = 0;
 
 	(*auth_tok) = ecryptfs_get_key_payload_data(auth_tok_key);
+	if (IS_ERR(*auth_tok)) {
+		rc = PTR_ERR(*auth_tok);
+		*auth_tok = NULL;
+		goto out;
+	}
+
 	if (ecryptfs_verify_version((*auth_tok)->version)) {
 		printk(KERN_ERR "Data structure version mismatch. Userspace "
 		       "tools must match eCryptfs kernel module with major "

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 05/15] ecryptfs: fix dereference of NULL user_key_payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable,
	Michael Halcrow

From: Eric Biggers <ebiggers@google.com>

In eCryptfs, we failed to verify that the authentication token keys are
not revoked before dereferencing their payloads, which is problematic
because the payload of a revoked key is NULL.  request_key() *does* skip
revoked keys, but there is still a window where the key can be revoked
before we acquire the key semaphore.

Fix it by updating ecryptfs_get_key_payload_data() to return
-EKEYREVOKED if the key payload is NULL.  For completeness we check this
for "encrypted" keys as well as "user" keys, although encrypted keys
cannot be revoked currently.

Alternatively we could use key_validate(), but since we'll also need to
fix ecryptfs_get_key_payload_data() to validate the payload length, it
seems appropriate to just check the payload pointer.

Fixes: 237fead61998 ("[PATCH] ecryptfs: fs/Makefile and fs/Kconfig")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.19+]
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |   24 +++++++++++++++++-------
 fs/ecryptfs/keystore.c        |    9 ++++++++-
 2 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 9c351bf757b2..3fbc0ff79699 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -84,11 +84,16 @@ struct ecryptfs_page_crypt_context {
 static inline struct ecryptfs_auth_tok *
 ecryptfs_get_encrypted_key_payload_data(struct key *key)
 {
-	if (key->type == &key_type_encrypted)
-		return (struct ecryptfs_auth_tok *)
-			(&((struct encrypted_key_payload *)key->payload.data[0])->payload_data);
-	else
+	struct encrypted_key_payload *payload;
+
+	if (key->type != &key_type_encrypted)
 		return NULL;
+
+	payload = key->payload.data[0];
+	if (!payload)
+		return ERR_PTR(-EKEYREVOKED);
+
+	return (struct ecryptfs_auth_tok *)payload->payload_data;
 }
 
 static inline struct key *ecryptfs_get_encrypted_key(char *sig)
@@ -114,12 +119,17 @@ static inline struct ecryptfs_auth_tok *
 ecryptfs_get_key_payload_data(struct key *key)
 {
 	struct ecryptfs_auth_tok *auth_tok;
+	struct user_key_payload *ukp;
 
 	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
-	if (!auth_tok)
-		return (struct ecryptfs_auth_tok *)user_key_payload_locked(key)->data;
-	else
+	if (auth_tok)
 		return auth_tok;
+
+	ukp = user_key_payload_locked(key);
+	if (!ukp)
+		return ERR_PTR(-EKEYREVOKED);
+
+	return (struct ecryptfs_auth_tok *)ukp->data;
 }
 
 #define ECRYPTFS_MAX_KEYSET_SIZE 1024
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index 3cf1546dca82..fa218cd64f74 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -459,7 +459,8 @@ static int ecryptfs_verify_version(u16 version)
  * @auth_tok_key: key containing the authentication token
  * @auth_tok: authentication token
  *
- * Returns zero on valid auth tok; -EINVAL otherwise
+ * Returns zero on valid auth tok; -EINVAL if the payload is invalid; or
+ * -EKEYREVOKED if the key was revoked before we acquired its semaphore.
  */
 static int
 ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
@@ -468,6 +469,12 @@ ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
 	int rc = 0;
 
 	(*auth_tok) = ecryptfs_get_key_payload_data(auth_tok_key);
+	if (IS_ERR(*auth_tok)) {
+		rc = PTR_ERR(*auth_tok);
+		*auth_tok = NULL;
+		goto out;
+	}
+
 	if (ecryptfs_verify_version((*auth_tok)->version)) {
 		printk(KERN_ERR "Data structure version mismatch. Userspace "
 		       "tools must match eCryptfs kernel module with major "

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 06/15] ecryptfs: fix out-of-bounds read of key payload
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

eCryptfs blindly casts the user-supplied key payload to a
'struct ecryptfs_auth_tok' without validating that the payload does, in
fact, have the size of a 'struct ecryptfs_auth_tok'.  Fix it.

Fixes: 237fead61998 ("[PATCH] ecryptfs: fs/Makefile and fs/Kconfig")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.19+]
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |    6 ++++++
 fs/ecryptfs/keystore.c        |    4 ++++
 2 files changed, 10 insertions(+)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 3fbc0ff79699..945844d5f0ef 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -93,6 +93,9 @@ ecryptfs_get_encrypted_key_payload_data(struct key *key)
 	if (!payload)
 		return ERR_PTR(-EKEYREVOKED);
 
+	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
 	return (struct ecryptfs_auth_tok *)payload->payload_data;
 }
 
@@ -129,6 +132,9 @@ ecryptfs_get_key_payload_data(struct key *key)
 	if (!ukp)
 		return ERR_PTR(-EKEYREVOKED);
 
+	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
 	return (struct ecryptfs_auth_tok *)ukp->data;
 }
 
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index fa218cd64f74..95e20ab67df3 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -471,6 +471,10 @@ ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
 	(*auth_tok) = ecryptfs_get_key_payload_data(auth_tok_key);
 	if (IS_ERR(*auth_tok)) {
 		rc = PTR_ERR(*auth_tok);
+		if (rc = -EINVAL) {
+			ecryptfs_printk(KERN_ERR,
+					"Authentication token payload has wrong length\n");
+		}
 		*auth_tok = NULL;
 		goto out;
 	}


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 06/15] ecryptfs: fix out-of-bounds read of key payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

eCryptfs blindly casts the user-supplied key payload to a
'struct ecryptfs_auth_tok' without validating that the payload does, in
fact, have the size of a 'struct ecryptfs_auth_tok'.  Fix it.

Fixes: 237fead61998 ("[PATCH] ecryptfs: fs/Makefile and fs/Kconfig")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.19+]
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |    6 ++++++
 fs/ecryptfs/keystore.c        |    4 ++++
 2 files changed, 10 insertions(+)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 3fbc0ff79699..945844d5f0ef 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -93,6 +93,9 @@ ecryptfs_get_encrypted_key_payload_data(struct key *key)
 	if (!payload)
 		return ERR_PTR(-EKEYREVOKED);
 
+	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
 	return (struct ecryptfs_auth_tok *)payload->payload_data;
 }
 
@@ -129,6 +132,9 @@ ecryptfs_get_key_payload_data(struct key *key)
 	if (!ukp)
 		return ERR_PTR(-EKEYREVOKED);
 
+	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
 	return (struct ecryptfs_auth_tok *)ukp->data;
 }
 
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index fa218cd64f74..95e20ab67df3 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -471,6 +471,10 @@ ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
 	(*auth_tok) = ecryptfs_get_key_payload_data(auth_tok_key);
 	if (IS_ERR(*auth_tok)) {
 		rc = PTR_ERR(*auth_tok);
+		if (rc == -EINVAL) {
+			ecryptfs_printk(KERN_ERR,
+					"Authentication token payload has wrong length\n");
+		}
 		*auth_tok = NULL;
 		goto out;
 	}

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 06/15] ecryptfs: fix out-of-bounds read of key payload
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable,
	Michael Halcrow

From: Eric Biggers <ebiggers@google.com>

eCryptfs blindly casts the user-supplied key payload to a
'struct ecryptfs_auth_tok' without validating that the payload does, in
fact, have the size of a 'struct ecryptfs_auth_tok'.  Fix it.

Fixes: 237fead61998 ("[PATCH] ecryptfs: fs/Makefile and fs/Kconfig")
Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: <stable@vger.kernel.org>    [v2.6.19+]
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |    6 ++++++
 fs/ecryptfs/keystore.c        |    4 ++++
 2 files changed, 10 insertions(+)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 3fbc0ff79699..945844d5f0ef 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -93,6 +93,9 @@ ecryptfs_get_encrypted_key_payload_data(struct key *key)
 	if (!payload)
 		return ERR_PTR(-EKEYREVOKED);
 
+	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
 	return (struct ecryptfs_auth_tok *)payload->payload_data;
 }
 
@@ -129,6 +132,9 @@ ecryptfs_get_key_payload_data(struct key *key)
 	if (!ukp)
 		return ERR_PTR(-EKEYREVOKED);
 
+	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
 	return (struct ecryptfs_auth_tok *)ukp->data;
 }
 
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index fa218cd64f74..95e20ab67df3 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -471,6 +471,10 @@ ecryptfs_verify_auth_tok_from_key(struct key *auth_tok_key,
 	(*auth_tok) = ecryptfs_get_key_payload_data(auth_tok_key);
 	if (IS_ERR(*auth_tok)) {
 		rc = PTR_ERR(*auth_tok);
+		if (rc == -EINVAL) {
+			ecryptfs_printk(KERN_ERR,
+					"Authentication token payload has wrong length\n");
+		}
 		*auth_tok = NULL;
 		goto out;
 	}

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 07/15] ecryptfs: move key payload accessor functions into keystore.c
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

As experience has shown, accessing the 'struct key' payload is very
error-prone, since we need to hold the key semaphore and properly
validate everything.  Fortunately eCryptfs only does it from one place,
in ecryptfs_verify_auth_tok_from_key() in keystore.c.  Therefore, move
the payload accessor functions like ecryptfs_get_key_payload_data() out
of ecryptfs_kernel.h and into keystore.c so that people might be less
tempted to use them directly.

Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |   60 -----------------------------------------
 fs/ecryptfs/keystore.c        |   60 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 60 deletions(-)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 945844d5f0ef..f2e339a6f9e9 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -29,8 +29,6 @@
 #define ECRYPTFS_KERNEL_H
 
 #include <crypto/skcipher.h>
-#include <keys/user-type.h>
-#include <keys/encrypted-type.h>
 #include <linux/fs.h>
 #include <linux/fs_stack.h>
 #include <linux/namei.h>
@@ -80,64 +78,6 @@ struct ecryptfs_page_crypt_context {
 	} param;
 };
 
-#if defined(CONFIG_ENCRYPTED_KEYS) || defined(CONFIG_ENCRYPTED_KEYS_MODULE)
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_encrypted_key_payload_data(struct key *key)
-{
-	struct encrypted_key_payload *payload;
-
-	if (key->type != &key_type_encrypted)
-		return NULL;
-
-	payload = key->payload.data[0];
-	if (!payload)
-		return ERR_PTR(-EKEYREVOKED);
-
-	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
-		return ERR_PTR(-EINVAL);
-
-	return (struct ecryptfs_auth_tok *)payload->payload_data;
-}
-
-static inline struct key *ecryptfs_get_encrypted_key(char *sig)
-{
-	return request_key(&key_type_encrypted, sig, NULL);
-}
-
-#else
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_encrypted_key_payload_data(struct key *key)
-{
-	return NULL;
-}
-
-static inline struct key *ecryptfs_get_encrypted_key(char *sig)
-{
-	return ERR_PTR(-ENOKEY);
-}
-
-#endif /* CONFIG_ENCRYPTED_KEYS */
-
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_key_payload_data(struct key *key)
-{
-	struct ecryptfs_auth_tok *auth_tok;
-	struct user_key_payload *ukp;
-
-	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
-	if (auth_tok)
-		return auth_tok;
-
-	ukp = user_key_payload_locked(key);
-	if (!ukp)
-		return ERR_PTR(-EKEYREVOKED);
-
-	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
-		return ERR_PTR(-EINVAL);
-
-	return (struct ecryptfs_auth_tok *)ukp->data;
-}
-
 #define ECRYPTFS_MAX_KEYSET_SIZE 1024
 #define ECRYPTFS_MAX_CIPHER_NAME_SIZE 31
 #define ECRYPTFS_MAX_NUM_ENC_KEYS 64
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index 95e20ab67df3..cb801bdcbae2 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -27,6 +27,8 @@
 
 #include <crypto/hash.h>
 #include <crypto/skcipher.h>
+#include <keys/user-type.h>
+#include <keys/encrypted-type.h>
 #include <linux/string.h>
 #include <linux/pagemap.h>
 #include <linux/key.h>
@@ -454,6 +456,64 @@ static int ecryptfs_verify_version(u16 version)
 	return rc;
 }
 
+#if defined(CONFIG_ENCRYPTED_KEYS) || defined(CONFIG_ENCRYPTED_KEYS_MODULE)
+static inline struct ecryptfs_auth_tok *
+ecryptfs_get_encrypted_key_payload_data(struct key *key)
+{
+	struct encrypted_key_payload *payload;
+
+	if (key->type != &key_type_encrypted)
+		return NULL;
+
+	payload = key->payload.data[0];
+	if (!payload)
+		return ERR_PTR(-EKEYREVOKED);
+
+	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
+	return (struct ecryptfs_auth_tok *)payload->payload_data;
+}
+
+static inline struct key *ecryptfs_get_encrypted_key(char *sig)
+{
+	return request_key(&key_type_encrypted, sig, NULL);
+}
+
+#else
+static inline struct ecryptfs_auth_tok *
+ecryptfs_get_encrypted_key_payload_data(struct key *key)
+{
+	return NULL;
+}
+
+static inline struct key *ecryptfs_get_encrypted_key(char *sig)
+{
+	return ERR_PTR(-ENOKEY);
+}
+
+#endif /* CONFIG_ENCRYPTED_KEYS */
+
+static struct ecryptfs_auth_tok *
+ecryptfs_get_key_payload_data(struct key *key)
+{
+	struct ecryptfs_auth_tok *auth_tok;
+	struct user_key_payload *ukp;
+
+	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
+	if (auth_tok)
+		return auth_tok;
+
+	ukp = user_key_payload_locked(key);
+	if (!ukp)
+		return ERR_PTR(-EKEYREVOKED);
+
+	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
+	return (struct ecryptfs_auth_tok *)ukp->data;
+}
+
 /**
  * ecryptfs_verify_auth_tok_from_key
  * @auth_tok_key: key containing the authentication token


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 07/15] ecryptfs: move key payload accessor functions into keystore.c
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

As experience has shown, accessing the 'struct key' payload is very
error-prone, since we need to hold the key semaphore and properly
validate everything.  Fortunately eCryptfs only does it from one place,
in ecryptfs_verify_auth_tok_from_key() in keystore.c.  Therefore, move
the payload accessor functions like ecryptfs_get_key_payload_data() out
of ecryptfs_kernel.h and into keystore.c so that people might be less
tempted to use them directly.

Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |   60 -----------------------------------------
 fs/ecryptfs/keystore.c        |   60 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 60 deletions(-)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 945844d5f0ef..f2e339a6f9e9 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -29,8 +29,6 @@
 #define ECRYPTFS_KERNEL_H
 
 #include <crypto/skcipher.h>
-#include <keys/user-type.h>
-#include <keys/encrypted-type.h>
 #include <linux/fs.h>
 #include <linux/fs_stack.h>
 #include <linux/namei.h>
@@ -80,64 +78,6 @@ struct ecryptfs_page_crypt_context {
 	} param;
 };
 
-#if defined(CONFIG_ENCRYPTED_KEYS) || defined(CONFIG_ENCRYPTED_KEYS_MODULE)
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_encrypted_key_payload_data(struct key *key)
-{
-	struct encrypted_key_payload *payload;
-
-	if (key->type != &key_type_encrypted)
-		return NULL;
-
-	payload = key->payload.data[0];
-	if (!payload)
-		return ERR_PTR(-EKEYREVOKED);
-
-	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
-		return ERR_PTR(-EINVAL);
-
-	return (struct ecryptfs_auth_tok *)payload->payload_data;
-}
-
-static inline struct key *ecryptfs_get_encrypted_key(char *sig)
-{
-	return request_key(&key_type_encrypted, sig, NULL);
-}
-
-#else
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_encrypted_key_payload_data(struct key *key)
-{
-	return NULL;
-}
-
-static inline struct key *ecryptfs_get_encrypted_key(char *sig)
-{
-	return ERR_PTR(-ENOKEY);
-}
-
-#endif /* CONFIG_ENCRYPTED_KEYS */
-
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_key_payload_data(struct key *key)
-{
-	struct ecryptfs_auth_tok *auth_tok;
-	struct user_key_payload *ukp;
-
-	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
-	if (auth_tok)
-		return auth_tok;
-
-	ukp = user_key_payload_locked(key);
-	if (!ukp)
-		return ERR_PTR(-EKEYREVOKED);
-
-	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
-		return ERR_PTR(-EINVAL);
-
-	return (struct ecryptfs_auth_tok *)ukp->data;
-}
-
 #define ECRYPTFS_MAX_KEYSET_SIZE 1024
 #define ECRYPTFS_MAX_CIPHER_NAME_SIZE 31
 #define ECRYPTFS_MAX_NUM_ENC_KEYS 64
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index 95e20ab67df3..cb801bdcbae2 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -27,6 +27,8 @@
 
 #include <crypto/hash.h>
 #include <crypto/skcipher.h>
+#include <keys/user-type.h>
+#include <keys/encrypted-type.h>
 #include <linux/string.h>
 #include <linux/pagemap.h>
 #include <linux/key.h>
@@ -454,6 +456,64 @@ static int ecryptfs_verify_version(u16 version)
 	return rc;
 }
 
+#if defined(CONFIG_ENCRYPTED_KEYS) || defined(CONFIG_ENCRYPTED_KEYS_MODULE)
+static inline struct ecryptfs_auth_tok *
+ecryptfs_get_encrypted_key_payload_data(struct key *key)
+{
+	struct encrypted_key_payload *payload;
+
+	if (key->type != &key_type_encrypted)
+		return NULL;
+
+	payload = key->payload.data[0];
+	if (!payload)
+		return ERR_PTR(-EKEYREVOKED);
+
+	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
+	return (struct ecryptfs_auth_tok *)payload->payload_data;
+}
+
+static inline struct key *ecryptfs_get_encrypted_key(char *sig)
+{
+	return request_key(&key_type_encrypted, sig, NULL);
+}
+
+#else
+static inline struct ecryptfs_auth_tok *
+ecryptfs_get_encrypted_key_payload_data(struct key *key)
+{
+	return NULL;
+}
+
+static inline struct key *ecryptfs_get_encrypted_key(char *sig)
+{
+	return ERR_PTR(-ENOKEY);
+}
+
+#endif /* CONFIG_ENCRYPTED_KEYS */
+
+static struct ecryptfs_auth_tok *
+ecryptfs_get_key_payload_data(struct key *key)
+{
+	struct ecryptfs_auth_tok *auth_tok;
+	struct user_key_payload *ukp;
+
+	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
+	if (auth_tok)
+		return auth_tok;
+
+	ukp = user_key_payload_locked(key);
+	if (!ukp)
+		return ERR_PTR(-EKEYREVOKED);
+
+	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
+	return (struct ecryptfs_auth_tok *)ukp->data;
+}
+
 /**
  * ecryptfs_verify_auth_tok_from_key
  * @auth_tok_key: key containing the authentication token

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 07/15] ecryptfs: move key payload accessor functions into keystore.c
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel,
	Michael Halcrow

From: Eric Biggers <ebiggers@google.com>

As experience has shown, accessing the 'struct key' payload is very
error-prone, since we need to hold the key semaphore and properly
validate everything.  Fortunately eCryptfs only does it from one place,
in ecryptfs_verify_auth_tok_from_key() in keystore.c.  Therefore, move
the payload accessor functions like ecryptfs_get_key_payload_data() out
of ecryptfs_kernel.h and into keystore.c so that people might be less
tempted to use them directly.

Reviewed-by: James Morris <james.l.morris@oracle.com>
Cc: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/ecryptfs/ecryptfs_kernel.h |   60 -----------------------------------------
 fs/ecryptfs/keystore.c        |   60 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 60 deletions(-)

diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index 945844d5f0ef..f2e339a6f9e9 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -29,8 +29,6 @@
 #define ECRYPTFS_KERNEL_H
 
 #include <crypto/skcipher.h>
-#include <keys/user-type.h>
-#include <keys/encrypted-type.h>
 #include <linux/fs.h>
 #include <linux/fs_stack.h>
 #include <linux/namei.h>
@@ -80,64 +78,6 @@ struct ecryptfs_page_crypt_context {
 	} param;
 };
 
-#if defined(CONFIG_ENCRYPTED_KEYS) || defined(CONFIG_ENCRYPTED_KEYS_MODULE)
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_encrypted_key_payload_data(struct key *key)
-{
-	struct encrypted_key_payload *payload;
-
-	if (key->type != &key_type_encrypted)
-		return NULL;
-
-	payload = key->payload.data[0];
-	if (!payload)
-		return ERR_PTR(-EKEYREVOKED);
-
-	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
-		return ERR_PTR(-EINVAL);
-
-	return (struct ecryptfs_auth_tok *)payload->payload_data;
-}
-
-static inline struct key *ecryptfs_get_encrypted_key(char *sig)
-{
-	return request_key(&key_type_encrypted, sig, NULL);
-}
-
-#else
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_encrypted_key_payload_data(struct key *key)
-{
-	return NULL;
-}
-
-static inline struct key *ecryptfs_get_encrypted_key(char *sig)
-{
-	return ERR_PTR(-ENOKEY);
-}
-
-#endif /* CONFIG_ENCRYPTED_KEYS */
-
-static inline struct ecryptfs_auth_tok *
-ecryptfs_get_key_payload_data(struct key *key)
-{
-	struct ecryptfs_auth_tok *auth_tok;
-	struct user_key_payload *ukp;
-
-	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
-	if (auth_tok)
-		return auth_tok;
-
-	ukp = user_key_payload_locked(key);
-	if (!ukp)
-		return ERR_PTR(-EKEYREVOKED);
-
-	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
-		return ERR_PTR(-EINVAL);
-
-	return (struct ecryptfs_auth_tok *)ukp->data;
-}
-
 #define ECRYPTFS_MAX_KEYSET_SIZE 1024
 #define ECRYPTFS_MAX_CIPHER_NAME_SIZE 31
 #define ECRYPTFS_MAX_NUM_ENC_KEYS 64
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index 95e20ab67df3..cb801bdcbae2 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -27,6 +27,8 @@
 
 #include <crypto/hash.h>
 #include <crypto/skcipher.h>
+#include <keys/user-type.h>
+#include <keys/encrypted-type.h>
 #include <linux/string.h>
 #include <linux/pagemap.h>
 #include <linux/key.h>
@@ -454,6 +456,64 @@ static int ecryptfs_verify_version(u16 version)
 	return rc;
 }
 
+#if defined(CONFIG_ENCRYPTED_KEYS) || defined(CONFIG_ENCRYPTED_KEYS_MODULE)
+static inline struct ecryptfs_auth_tok *
+ecryptfs_get_encrypted_key_payload_data(struct key *key)
+{
+	struct encrypted_key_payload *payload;
+
+	if (key->type != &key_type_encrypted)
+		return NULL;
+
+	payload = key->payload.data[0];
+	if (!payload)
+		return ERR_PTR(-EKEYREVOKED);
+
+	if (payload->payload_datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
+	return (struct ecryptfs_auth_tok *)payload->payload_data;
+}
+
+static inline struct key *ecryptfs_get_encrypted_key(char *sig)
+{
+	return request_key(&key_type_encrypted, sig, NULL);
+}
+
+#else
+static inline struct ecryptfs_auth_tok *
+ecryptfs_get_encrypted_key_payload_data(struct key *key)
+{
+	return NULL;
+}
+
+static inline struct key *ecryptfs_get_encrypted_key(char *sig)
+{
+	return ERR_PTR(-ENOKEY);
+}
+
+#endif /* CONFIG_ENCRYPTED_KEYS */
+
+static struct ecryptfs_auth_tok *
+ecryptfs_get_key_payload_data(struct key *key)
+{
+	struct ecryptfs_auth_tok *auth_tok;
+	struct user_key_payload *ukp;
+
+	auth_tok = ecryptfs_get_encrypted_key_payload_data(key);
+	if (auth_tok)
+		return auth_tok;
+
+	ukp = user_key_payload_locked(key);
+	if (!ukp)
+		return ERR_PTR(-EKEYREVOKED);
+
+	if (ukp->datalen != sizeof(struct ecryptfs_auth_tok))
+		return ERR_PTR(-EINVAL);
+
+	return (struct ecryptfs_auth_tok *)ukp->data;
+}
+
 /**
  * ecryptfs_verify_auth_tok_from_key
  * @auth_tok_key: key containing the authentication token

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 08/15] security/keys: BIG_KEY requires CONFIG_CRYPTO
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:16     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Arnd Bergmann <arnd@arndb.de>

The recent rework introduced a possible randconfig build failure
when CONFIG_CRYPTO configured to only allow modules:

security/keys/big_key.o: In function `big_key_crypt':
big_key.c:(.text+0x29f): undefined reference to `crypto_aead_setkey'
security/keys/big_key.o: In function `big_key_init':
big_key.c:(.init.text+0x1a): undefined reference to `crypto_alloc_aead'
big_key.c:(.init.text+0x45): undefined reference to `crypto_aead_setauthsize'
big_key.c:(.init.text+0x77): undefined reference to `crypto_destroy_tfm'
crypto/gcm.o: In function `gcm_hash_crypt_remain_continue':
gcm.c:(.text+0x167): undefined reference to `crypto_ahash_finup'
crypto/gcm.o: In function `crypto_gcm_exit_tfm':
gcm.c:(.text+0x847): undefined reference to `crypto_destroy_tfm'

When we 'select CRYPTO' like the other users, we always get a
configuration that builds.

Fixes: 428490e38b2e ("security/keys: rewrite all of big_key crypto")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/Kconfig |    1 +
 1 file changed, 1 insertion(+)

diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 91eafada3164..6462e6654ccf 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -45,6 +45,7 @@ config BIG_KEYS
 	bool "Large payload keys"
 	depends on KEYS
 	depends on TMPFS
+	select CRYPTO
 	select CRYPTO_AES
 	select CRYPTO_GCM
 	help


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 08/15] security/keys: BIG_KEY requires CONFIG_CRYPTO
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: linux-security-module

From: Arnd Bergmann <arnd@arndb.de>

The recent rework introduced a possible randconfig build failure
when CONFIG_CRYPTO configured to only allow modules:

security/keys/big_key.o: In function `big_key_crypt':
big_key.c:(.text+0x29f): undefined reference to `crypto_aead_setkey'
security/keys/big_key.o: In function `big_key_init':
big_key.c:(.init.text+0x1a): undefined reference to `crypto_alloc_aead'
big_key.c:(.init.text+0x45): undefined reference to `crypto_aead_setauthsize'
big_key.c:(.init.text+0x77): undefined reference to `crypto_destroy_tfm'
crypto/gcm.o: In function `gcm_hash_crypt_remain_continue':
gcm.c:(.text+0x167): undefined reference to `crypto_ahash_finup'
crypto/gcm.o: In function `crypto_gcm_exit_tfm':
gcm.c:(.text+0x847): undefined reference to `crypto_destroy_tfm'

When we 'select CRYPTO' like the other users, we always get a
configuration that builds.

Fixes: 428490e38b2e ("security/keys: rewrite all of big_key crypto")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/Kconfig |    1 +
 1 file changed, 1 insertion(+)

diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 91eafada3164..6462e6654ccf 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -45,6 +45,7 @@ config BIG_KEYS
 	bool "Large payload keys"
 	depends on KEYS
 	depends on TMPFS
+	select CRYPTO
 	select CRYPTO_AES
 	select CRYPTO_GCM
 	help

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 08/15] security/keys: BIG_KEY requires CONFIG_CRYPTO
@ 2017-10-12 20:16     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:16 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel,
	Arnd Bergmann

From: Arnd Bergmann <arnd@arndb.de>

The recent rework introduced a possible randconfig build failure
when CONFIG_CRYPTO configured to only allow modules:

security/keys/big_key.o: In function `big_key_crypt':
big_key.c:(.text+0x29f): undefined reference to `crypto_aead_setkey'
security/keys/big_key.o: In function `big_key_init':
big_key.c:(.init.text+0x1a): undefined reference to `crypto_alloc_aead'
big_key.c:(.init.text+0x45): undefined reference to `crypto_aead_setauthsize'
big_key.c:(.init.text+0x77): undefined reference to `crypto_destroy_tfm'
crypto/gcm.o: In function `gcm_hash_crypt_remain_continue':
gcm.c:(.text+0x167): undefined reference to `crypto_ahash_finup'
crypto/gcm.o: In function `crypto_gcm_exit_tfm':
gcm.c:(.text+0x847): undefined reference to `crypto_destroy_tfm'

When we 'select CRYPTO' like the other users, we always get a
configuration that builds.

Fixes: 428490e38b2e ("security/keys: rewrite all of big_key crypto")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/Kconfig |    1 +
 1 file changed, 1 insertion(+)

diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 91eafada3164..6462e6654ccf 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -45,6 +45,7 @@ config BIG_KEYS
 	bool "Large payload keys"
 	depends on KEYS
 	depends on TMPFS
+	select CRYPTO
 	select CRYPTO_AES
 	select CRYPTO_GCM
 	help

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 09/15] KEYS: Fix the wrong index when checking the existence of second id
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:17     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Lee, Chun-Yi <joeyli.kernel@gmail.com>

Fix the wrong index number when checking the existence of second
id in function of finding asymmetric key. The id_1 is the second
id that the index in array must be 1 but not 0.

Fix: 9eb029893(KEYS: Generalise x509_request_asymmetric_key())

Cc: David Howells <dhowells@redhat.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: "David S. Miller" <davem@davemloft.net>
Signed-off-by: "Lee, Chun-Yi" <jlee@suse.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 crypto/asymmetric_keys/asymmetric_type.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index e4b0ed386bc8..a597f5c5a222 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -105,7 +105,7 @@ struct key *find_asymmetric_key(struct key *keyring,
 	if (id_0 && id_1) {
 		const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
 
-		if (!kids->id[0]) {
+		if (!kids->id[1]) {
 			pr_debug("First ID matches, but second is missing\n");
 			goto reject;
 		}


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 09/15] KEYS: Fix the wrong index when checking the existence of second id
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Lee, Chun-Yi <joeyli.kernel@gmail.com>

Fix the wrong index number when checking the existence of second
id in function of finding asymmetric key. The id_1 is the second
id that the index in array must be 1 but not 0.

Fix: 9eb029893(KEYS: Generalise x509_request_asymmetric_key())

Cc: David Howells <dhowells@redhat.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: "David S. Miller" <davem@davemloft.net>
Signed-off-by: "Lee, Chun-Yi" <jlee@suse.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 crypto/asymmetric_keys/asymmetric_type.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index e4b0ed386bc8..a597f5c5a222 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -105,7 +105,7 @@ struct key *find_asymmetric_key(struct key *keyring,
 	if (id_0 && id_1) {
 		const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
 
-		if (!kids->id[0]) {
+		if (!kids->id[1]) {
 			pr_debug("First ID matches, but second is missing\n");
 			goto reject;
 		}

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 09/15] KEYS: Fix the wrong index when checking the existence of second id
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel,
	Herbert Xu, David S. Miller, Lee, Chun-Yi

From: Lee, Chun-Yi <joeyli.kernel@gmail.com>

Fix the wrong index number when checking the existence of second
id in function of finding asymmetric key. The id_1 is the second
id that the index in array must be 1 but not 0.

Fix: 9eb029893(KEYS: Generalise x509_request_asymmetric_key())

Cc: David Howells <dhowells@redhat.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: "David S. Miller" <davem@davemloft.net>
Signed-off-by: "Lee, Chun-Yi" <jlee@suse.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 crypto/asymmetric_keys/asymmetric_type.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index e4b0ed386bc8..a597f5c5a222 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -105,7 +105,7 @@ struct key *find_asymmetric_key(struct key *keyring,
 	if (id_0 && id_1) {
 		const struct asymmetric_key_ids *kids = asymmetric_key_ids(key);
 
-		if (!kids->id[0]) {
+		if (!kids->id[1]) {
 			pr_debug("First ID matches, but second is missing\n");
 			goto reject;
 		}

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 10/15] KEYS: checking the input id parameters before finding asymmetric key
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:17     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Lee, Chun-Yi <joeyli.kernel@gmail.com>

For finding asymmetric key, the input id_0 and id_1 parameters can
not be NULL at the same time. This patch adds the BUG_ON checking
for id_0 and id_1.

Cc: David Howells <dhowells@redhat.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: "David S. Miller" <davem@davemloft.net>
Signed-off-by: "Lee, Chun-Yi" <jlee@suse.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 crypto/asymmetric_keys/asymmetric_type.c |    2 ++
 1 file changed, 2 insertions(+)

diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index a597f5c5a222..39aecad286fe 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -57,6 +57,8 @@ struct key *find_asymmetric_key(struct key *keyring,
 	char *req, *p;
 	int len;
 
+	BUG_ON(!id_0 && !id_1);
+
 	if (id_0) {
 		lookup = id_0->data;
 		len = id_0->len;


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 10/15] KEYS: checking the input id parameters before finding asymmetric key
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Lee, Chun-Yi <joeyli.kernel@gmail.com>

For finding asymmetric key, the input id_0 and id_1 parameters can
not be NULL at the same time. This patch adds the BUG_ON checking
for id_0 and id_1.

Cc: David Howells <dhowells@redhat.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: "David S. Miller" <davem@davemloft.net>
Signed-off-by: "Lee, Chun-Yi" <jlee@suse.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 crypto/asymmetric_keys/asymmetric_type.c |    2 ++
 1 file changed, 2 insertions(+)

diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index a597f5c5a222..39aecad286fe 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -57,6 +57,8 @@ struct key *find_asymmetric_key(struct key *keyring,
 	char *req, *p;
 	int len;
 
+	BUG_ON(!id_0 && !id_1);
+
 	if (id_0) {
 		lookup = id_0->data;
 		len = id_0->len;

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 10/15] KEYS: checking the input id parameters before finding asymmetric key
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: ebiggers
  Cc: keyrings, dhowells, linux-security-module, linux-kernel,
	Herbert Xu, David S. Miller, Lee, Chun-Yi

From: Lee, Chun-Yi <joeyli.kernel@gmail.com>

For finding asymmetric key, the input id_0 and id_1 parameters can
not be NULL at the same time. This patch adds the BUG_ON checking
for id_0 and id_1.

Cc: David Howells <dhowells@redhat.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: "David S. Miller" <davem@davemloft.net>
Signed-off-by: "Lee, Chun-Yi" <jlee@suse.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 crypto/asymmetric_keys/asymmetric_type.c |    2 ++
 1 file changed, 2 insertions(+)

diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index a597f5c5a222..39aecad286fe 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -57,6 +57,8 @@ struct key *find_asymmetric_key(struct key *keyring,
 	char *req, *p;
 	int len;
 
+	BUG_ON(!id_0 && !id_1);
+
 	if (id_0) {
 		lookup = id_0->data;
 		len = id_0->len;

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 11/15] KEYS: Fix race between updating and finding a negative key
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:17     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
error into one field such that:

 (1) The instantiation state can be modified/read atomically.

 (2) The error can be accessed atomically with the state.

 (3) The error isn't stored unioned with the payload pointers.

Possibly the revocation flags can also be combined with this also.

Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
Cc: <stable@vger.kernel.org>	[v4.4+]
Reported-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 include/linux/key.h                      |   37 ++++++++++++++++++------------
 security/keys/encrypted-keys/encrypted.c |    2 +-
 security/keys/gc.c                       |    7 +++---
 security/keys/key.c                      |   28 ++++++++++++++---------
 security/keys/keyctl.c                   |    9 +++----
 security/keys/keyring.c                  |    8 +++---
 security/keys/proc.c                     |   11 ++++++---
 security/keys/process_keys.c             |    8 ++++--
 security/keys/request_key.c              |    7 ++----
 security/keys/trusted.c                  |    2 +-
 security/keys/user_defined.c             |    2 +-
 11 files changed, 70 insertions(+), 51 deletions(-)

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..f50e88c52bd0 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_INSTANTIATED,
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -362,8 +365,12 @@ extern void key_set_timeout(struct key *, unsigned);
  */
 static inline bool key_is_instantiated(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key->state = KEY_IS_INSTANTIATED;
+}
+
+static inline bool key_is_negative(const struct key *key)
+{
+	return key->state < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..1578f671a213 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key  			list_entry(keys->next, struct key, graveyard_link);
+		short state = READ_ONCE(key->state);
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
+		if (state = KEY_IS_INSTANTIATED &&
 		    key->type->destroy)
 			key->type->destroy(key);
 
@@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state = KEY_IS_INSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..de1b789ad29f 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,15 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	smp_wmb(); /* Commit the payload before setting the state */
+	key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +433,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state = KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret = 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +586,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state = KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +758,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret = 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +992,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret = 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..5c30c54e01ed 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key->state;
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state = KEY_IS_INSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..816948abff89 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..46fa0f1bfcc3 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = READ_ONCE(key->state);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state = KEY_IS_INSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
@@ -257,8 +260,10 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 #undef showflag
 
-	if (key->type->describe)
+	if (key->type->describe) {
+		smp_rmb(); /* Order access to payload after state set. */
 		key->type->describe(key, m);
+	}
 	seq_putc(m, '\n');
 
 	rcu_read_unlock();
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..492217ff4924 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -729,9 +729,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 	}
 
 	ret = -EIO;
-	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
-		goto invalid_key;
+	if (!(lflags & KEY_LOOKUP_PARTIAL)) {
+		if (key->state != KEY_IS_INSTANTIATED)
+			goto invalid_key;
+		smp_rmb(); /* Order access to payload after state set. */
+	}
 
 	/* check the permissions */
 	ret = key_task_permission(key_ref, ctx.cred, perm);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..a25f8378f704 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = READ_ONCE(key->state);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..9afa64817d4f 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key->state < 0)
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 11/15] KEYS: Fix race between updating and finding a negative key
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
error into one field such that:

 (1) The instantiation state can be modified/read atomically.

 (2) The error can be accessed atomically with the state.

 (3) The error isn't stored unioned with the payload pointers.

Possibly the revocation flags can also be combined with this also.

Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
Cc: <stable@vger.kernel.org>	[v4.4+]
Reported-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 include/linux/key.h                      |   37 ++++++++++++++++++------------
 security/keys/encrypted-keys/encrypted.c |    2 +-
 security/keys/gc.c                       |    7 +++---
 security/keys/key.c                      |   28 ++++++++++++++---------
 security/keys/keyctl.c                   |    9 +++----
 security/keys/keyring.c                  |    8 +++---
 security/keys/proc.c                     |   11 ++++++---
 security/keys/process_keys.c             |    8 ++++--
 security/keys/request_key.c              |    7 ++----
 security/keys/trusted.c                  |    2 +-
 security/keys/user_defined.c             |    2 +-
 11 files changed, 70 insertions(+), 51 deletions(-)

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..f50e88c52bd0 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_INSTANTIATED,
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -362,8 +365,12 @@ extern void key_set_timeout(struct key *, unsigned);
  */
 static inline bool key_is_instantiated(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key->state == KEY_IS_INSTANTIATED;
+}
+
+static inline bool key_is_negative(const struct key *key)
+{
+	return key->state < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..1578f671a213 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key =
 			list_entry(keys->next, struct key, graveyard_link);
+		short state = READ_ONCE(key->state);
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
+		if (state == KEY_IS_INSTANTIATED &&
 		    key->type->destroy)
 			key->type->destroy(key);
 
@@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state == KEY_IS_INSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..de1b789ad29f 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,15 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	smp_wmb(); /* Commit the payload before setting the state */
+	key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +433,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret == 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +586,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +758,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +992,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..5c30c54e01ed 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key->state;
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state == KEY_IS_INSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..816948abff89 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..46fa0f1bfcc3 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = READ_ONCE(key->state);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state == KEY_IS_INSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
@@ -257,8 +260,10 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 #undef showflag
 
-	if (key->type->describe)
+	if (key->type->describe) {
+		smp_rmb(); /* Order access to payload after state set. */
 		key->type->describe(key, m);
+	}
 	seq_putc(m, '\n');
 
 	rcu_read_unlock();
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..492217ff4924 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -729,9 +729,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 	}
 
 	ret = -EIO;
-	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
-		goto invalid_key;
+	if (!(lflags & KEY_LOOKUP_PARTIAL)) {
+		if (key->state != KEY_IS_INSTANTIATED)
+			goto invalid_key;
+		smp_rmb(); /* Order access to payload after state set. */
+	}
 
 	/* check the permissions */
 	ret = key_task_permission(key_ref, ctx.cred, perm);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..a25f8378f704 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = READ_ONCE(key->state);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..9afa64817d4f 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key->state < 0)
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 11/15] KEYS: Fix race between updating and finding a negative key
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable

Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
error into one field such that:

 (1) The instantiation state can be modified/read atomically.

 (2) The error can be accessed atomically with the state.

 (3) The error isn't stored unioned with the payload pointers.

Possibly the revocation flags can also be combined with this also.

Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
Cc: <stable@vger.kernel.org>	[v4.4+]
Reported-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 include/linux/key.h                      |   37 ++++++++++++++++++------------
 security/keys/encrypted-keys/encrypted.c |    2 +-
 security/keys/gc.c                       |    7 +++---
 security/keys/key.c                      |   28 ++++++++++++++---------
 security/keys/keyctl.c                   |    9 +++----
 security/keys/keyring.c                  |    8 +++---
 security/keys/proc.c                     |   11 ++++++---
 security/keys/process_keys.c             |    8 ++++--
 security/keys/request_key.c              |    7 ++----
 security/keys/trusted.c                  |    2 +-
 security/keys/user_defined.c             |    2 +-
 11 files changed, 70 insertions(+), 51 deletions(-)

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..f50e88c52bd0 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_INSTANTIATED,
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -362,8 +365,12 @@ extern void key_set_timeout(struct key *, unsigned);
  */
 static inline bool key_is_instantiated(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key->state == KEY_IS_INSTANTIATED;
+}
+
+static inline bool key_is_negative(const struct key *key)
+{
+	return key->state < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..1578f671a213 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key =
 			list_entry(keys->next, struct key, graveyard_link);
+		short state = READ_ONCE(key->state);
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
+		if (state == KEY_IS_INSTANTIATED &&
 		    key->type->destroy)
 			key->type->destroy(key);
 
@@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state == KEY_IS_INSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..de1b789ad29f 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,15 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	smp_wmb(); /* Commit the payload before setting the state */
+	key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +433,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret == 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +586,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +758,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +992,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..5c30c54e01ed 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key->state;
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state == KEY_IS_INSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..816948abff89 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..46fa0f1bfcc3 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = READ_ONCE(key->state);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state == KEY_IS_INSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
@@ -257,8 +260,10 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 #undef showflag
 
-	if (key->type->describe)
+	if (key->type->describe) {
+		smp_rmb(); /* Order access to payload after state set. */
 		key->type->describe(key, m);
+	}
 	seq_putc(m, '\n');
 
 	rcu_read_unlock();
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..492217ff4924 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -729,9 +729,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 	}
 
 	ret = -EIO;
-	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
-		goto invalid_key;
+	if (!(lflags & KEY_LOOKUP_PARTIAL)) {
+		if (key->state != KEY_IS_INSTANTIATED)
+			goto invalid_key;
+		smp_rmb(); /* Order access to payload after state set. */
+	}
 
 	/* check the permissions */
 	ret = key_task_permission(key_ref, ctx.cred, perm);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..a25f8378f704 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = READ_ONCE(key->state);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..9afa64817d4f 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key->state < 0)
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 12/15] KEYS: don't let add_key() update an uninstantiated key
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:17     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

Currently, when passed a key that already exists, add_key() will call the
key's ->update() method if such exists.  But this is heavily broken in the
case where the key is uninstantiated because it doesn't call
__key_instantiate_and_link().  Consequently, it doesn't do most of the
things that are supposed to happen when the key is instantiated, such as
setting the instantiation state, clearing KEY_FLAG_USER_CONSTRUCT and
awakening tasks waiting on it, and incrementing key->user->nikeys.

It also never takes key_construction_mutex, which means that
->instantiate() can run concurrently with ->update() on the same key.  In
the case of the "user" and "logon" key types this causes a memory leak, at
best.  Maybe even worse, the ->update() methods of the "encrypted" and
"trusted" key types actually just dereference a NULL pointer when passed an
uninstantiated key.

Change key_create_or_update() to wait interruptibly for the key to finish
construction before continuing.

This patch only affects *uninstantiated* keys.  For now we still allow a
negatively instantiated key to be updated (thereby positively
instantiating it), although that's broken too (the next patch fixes it)
and I'm not sure that anyone actually uses that functionality either.

Here is a simple reproducer for the bug using the "encrypted" key type
(requires CONFIG_ENCRYPTED_KEYS=y), though as noted above the bug
pertained to more than just the "encrypted" key type:

    #include <stdlib.h>
    #include <unistd.h>
    #include <keyutils.h>

    int main(void)
    {
        int ringid = keyctl_join_session_keyring(NULL);

        if (fork()) {
            for (;;) {
                const char payload[] = "update user:foo 32";

                usleep(rand() % 10000);
                add_key("encrypted", "desc", payload, sizeof(payload), ringid);
                keyctl_clear(ringid);
            }
        } else {
            for (;;)
                request_key("encrypted", "desc", "callout_info", ringid);
        }
    }

It causes:

    BUG: unable to handle kernel NULL pointer dereference at 0000000000000018
    IP: encrypted_update+0xb0/0x170
    PGD 7a178067 P4D 7a178067 PUD 77269067 PMD 0
    PREEMPT SMP
    CPU: 0 PID: 340 Comm: reproduce Tainted: G      D         4.14.0-rc1-00025-g428490e38b2e #796
    Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
    task: ffff8a467a39a340 task.stack: ffffb15c40770000
    RIP: 0010:encrypted_update+0xb0/0x170
    RSP: 0018:ffffb15c40773de8 EFLAGS: 00010246
    RAX: 0000000000000000 RBX: ffff8a467a275b00 RCX: 0000000000000000
    RDX: 0000000000000005 RSI: ffff8a467a275b14 RDI: ffffffffb742f303
    RBP: ffffb15c40773e20 R08: 0000000000000000 R09: ffff8a467a275b17
    R10: 0000000000000020 R11: 0000000000000000 R12: 0000000000000000
    R13: 0000000000000000 R14: ffff8a4677057180 R15: ffff8a467a275b0f
    FS:  00007f5d7fb08700(0000) GS:ffff8a467f200000(0000) knlGS:0000000000000000
    CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
    CR2: 0000000000000018 CR3: 0000000077262005 CR4: 00000000001606f0
    Call Trace:
     key_create_or_update+0x2bc/0x460
     SyS_add_key+0x10c/0x1d0
     entry_SYSCALL_64_fastpath+0x1f/0xbe
    RIP: 0033:0x7f5d7f211259
    RSP: 002b:00007ffed03904c8 EFLAGS: 00000246 ORIG_RAX: 00000000000000f8
    RAX: ffffffffffffffda RBX: 000000003b2a7955 RCX: 00007f5d7f211259
    RDX: 00000000004009e4 RSI: 00000000004009ff RDI: 0000000000400a04
    RBP: 0000000068db8bad R08: 000000003b2a7955 R09: 0000000000000004
    R10: 000000000000001a R11: 0000000000000246 R12: 0000000000400868
    R13: 00007ffed03905d0 R14: 0000000000000000 R15: 0000000000000000
    Code: 77 28 e8 64 34 1f 00 45 31 c0 31 c9 48 8d 55 c8 48 89 df 48 8d 75 d0 e8 ff f9 ff ff 85 c0 41 89 c4 0f 88 84 00 00 00 4c 8b 7d c8 <49> 8b 75 18 4c 89 ff e8 24 f8 ff ff 85 c0 41 89 c4 78 6d 49 8b
    RIP: encrypted_update+0xb0/0x170 RSP: ffffb15c40773de8
    CR2: 0000000000000018

Cc: <stable@vger.kernel.org>    [v2.6.12+]
Reported-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Eric Biggers <ebiggers@google.com>
---

 security/keys/key.c |    9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/security/keys/key.c b/security/keys/key.c
index de1b789ad29f..ef2a028bd554 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -942,6 +942,15 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 	 */
 	__key_link_end(keyring, &index_key, edit);
 
+	if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
+		ret = wait_for_key_construction(key_ref_to_ptr(key_ref), true);
+		if (ret < 0) {
+			key_ref_put(key_ref);
+			key_ref = ERR_PTR(ret);
+			goto error_free_prep;
+		}
+	}
+
 	key_ref = __key_update(key_ref, &prep);
 	goto error_free_prep;
 }


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 12/15] KEYS: don't let add_key() update an uninstantiated key
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

Currently, when passed a key that already exists, add_key() will call the
key's ->update() method if such exists.  But this is heavily broken in the
case where the key is uninstantiated because it doesn't call
__key_instantiate_and_link().  Consequently, it doesn't do most of the
things that are supposed to happen when the key is instantiated, such as
setting the instantiation state, clearing KEY_FLAG_USER_CONSTRUCT and
awakening tasks waiting on it, and incrementing key->user->nikeys.

It also never takes key_construction_mutex, which means that
->instantiate() can run concurrently with ->update() on the same key.  In
the case of the "user" and "logon" key types this causes a memory leak, at
best.  Maybe even worse, the ->update() methods of the "encrypted" and
"trusted" key types actually just dereference a NULL pointer when passed an
uninstantiated key.

Change key_create_or_update() to wait interruptibly for the key to finish
construction before continuing.

This patch only affects *uninstantiated* keys.  For now we still allow a
negatively instantiated key to be updated (thereby positively
instantiating it), although that's broken too (the next patch fixes it)
and I'm not sure that anyone actually uses that functionality either.

Here is a simple reproducer for the bug using the "encrypted" key type
(requires CONFIG_ENCRYPTED_KEYS=y), though as noted above the bug
pertained to more than just the "encrypted" key type:

    #include <stdlib.h>
    #include <unistd.h>
    #include <keyutils.h>

    int main(void)
    {
        int ringid = keyctl_join_session_keyring(NULL);

        if (fork()) {
            for (;;) {
                const char payload[] = "update user:foo 32";

                usleep(rand() % 10000);
                add_key("encrypted", "desc", payload, sizeof(payload), ringid);
                keyctl_clear(ringid);
            }
        } else {
            for (;;)
                request_key("encrypted", "desc", "callout_info", ringid);
        }
    }

It causes:

    BUG: unable to handle kernel NULL pointer dereference at 0000000000000018
    IP: encrypted_update+0xb0/0x170
    PGD 7a178067 P4D 7a178067 PUD 77269067 PMD 0
    PREEMPT SMP
    CPU: 0 PID: 340 Comm: reproduce Tainted: G      D         4.14.0-rc1-00025-g428490e38b2e #796
    Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
    task: ffff8a467a39a340 task.stack: ffffb15c40770000
    RIP: 0010:encrypted_update+0xb0/0x170
    RSP: 0018:ffffb15c40773de8 EFLAGS: 00010246
    RAX: 0000000000000000 RBX: ffff8a467a275b00 RCX: 0000000000000000
    RDX: 0000000000000005 RSI: ffff8a467a275b14 RDI: ffffffffb742f303
    RBP: ffffb15c40773e20 R08: 0000000000000000 R09: ffff8a467a275b17
    R10: 0000000000000020 R11: 0000000000000000 R12: 0000000000000000
    R13: 0000000000000000 R14: ffff8a4677057180 R15: ffff8a467a275b0f
    FS:  00007f5d7fb08700(0000) GS:ffff8a467f200000(0000) knlGS:0000000000000000
    CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
    CR2: 0000000000000018 CR3: 0000000077262005 CR4: 00000000001606f0
    Call Trace:
     key_create_or_update+0x2bc/0x460
     SyS_add_key+0x10c/0x1d0
     entry_SYSCALL_64_fastpath+0x1f/0xbe
    RIP: 0033:0x7f5d7f211259
    RSP: 002b:00007ffed03904c8 EFLAGS: 00000246 ORIG_RAX: 00000000000000f8
    RAX: ffffffffffffffda RBX: 000000003b2a7955 RCX: 00007f5d7f211259
    RDX: 00000000004009e4 RSI: 00000000004009ff RDI: 0000000000400a04
    RBP: 0000000068db8bad R08: 000000003b2a7955 R09: 0000000000000004
    R10: 000000000000001a R11: 0000000000000246 R12: 0000000000400868
    R13: 00007ffed03905d0 R14: 0000000000000000 R15: 0000000000000000
    Code: 77 28 e8 64 34 1f 00 45 31 c0 31 c9 48 8d 55 c8 48 89 df 48 8d 75 d0 e8 ff f9 ff ff 85 c0 41 89 c4 0f 88 84 00 00 00 4c 8b 7d c8 <49> 8b 75 18 4c 89 ff e8 24 f8 ff ff 85 c0 41 89 c4 78 6d 49 8b
    RIP: encrypted_update+0xb0/0x170 RSP: ffffb15c40773de8
    CR2: 0000000000000018

Cc: <stable@vger.kernel.org>    [v2.6.12+]
Reported-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Eric Biggers <ebiggers@google.com>
---

 security/keys/key.c |    9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/security/keys/key.c b/security/keys/key.c
index de1b789ad29f..ef2a028bd554 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -942,6 +942,15 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 	 */
 	__key_link_end(keyring, &index_key, edit);
 
+	if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
+		ret = wait_for_key_construction(key_ref_to_ptr(key_ref), true);
+		if (ret < 0) {
+			key_ref_put(key_ref);
+			key_ref = ERR_PTR(ret);
+			goto error_free_prep;
+		}
+	}
+
 	key_ref = __key_update(key_ref, &prep);
 	goto error_free_prep;
 }

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info@ http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 12/15] KEYS: don't let add_key() update an uninstantiated key
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel, stable

Currently, when passed a key that already exists, add_key() will call the
key's ->update() method if such exists.  But this is heavily broken in the
case where the key is uninstantiated because it doesn't call
__key_instantiate_and_link().  Consequently, it doesn't do most of the
things that are supposed to happen when the key is instantiated, such as
setting the instantiation state, clearing KEY_FLAG_USER_CONSTRUCT and
awakening tasks waiting on it, and incrementing key->user->nikeys.

It also never takes key_construction_mutex, which means that
->instantiate() can run concurrently with ->update() on the same key.  In
the case of the "user" and "logon" key types this causes a memory leak, at
best.  Maybe even worse, the ->update() methods of the "encrypted" and
"trusted" key types actually just dereference a NULL pointer when passed an
uninstantiated key.

Change key_create_or_update() to wait interruptibly for the key to finish
construction before continuing.

This patch only affects *uninstantiated* keys.  For now we still allow a
negatively instantiated key to be updated (thereby positively
instantiating it), although that's broken too (the next patch fixes it)
and I'm not sure that anyone actually uses that functionality either.

Here is a simple reproducer for the bug using the "encrypted" key type
(requires CONFIG_ENCRYPTED_KEYS=y), though as noted above the bug
pertained to more than just the "encrypted" key type:

    #include <stdlib.h>
    #include <unistd.h>
    #include <keyutils.h>

    int main(void)
    {
        int ringid = keyctl_join_session_keyring(NULL);

        if (fork()) {
            for (;;) {
                const char payload[] = "update user:foo 32";

                usleep(rand() % 10000);
                add_key("encrypted", "desc", payload, sizeof(payload), ringid);
                keyctl_clear(ringid);
            }
        } else {
            for (;;)
                request_key("encrypted", "desc", "callout_info", ringid);
        }
    }

It causes:

    BUG: unable to handle kernel NULL pointer dereference at 0000000000000018
    IP: encrypted_update+0xb0/0x170
    PGD 7a178067 P4D 7a178067 PUD 77269067 PMD 0
    PREEMPT SMP
    CPU: 0 PID: 340 Comm: reproduce Tainted: G      D         4.14.0-rc1-00025-g428490e38b2e #796
    Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
    task: ffff8a467a39a340 task.stack: ffffb15c40770000
    RIP: 0010:encrypted_update+0xb0/0x170
    RSP: 0018:ffffb15c40773de8 EFLAGS: 00010246
    RAX: 0000000000000000 RBX: ffff8a467a275b00 RCX: 0000000000000000
    RDX: 0000000000000005 RSI: ffff8a467a275b14 RDI: ffffffffb742f303
    RBP: ffffb15c40773e20 R08: 0000000000000000 R09: ffff8a467a275b17
    R10: 0000000000000020 R11: 0000000000000000 R12: 0000000000000000
    R13: 0000000000000000 R14: ffff8a4677057180 R15: ffff8a467a275b0f
    FS:  00007f5d7fb08700(0000) GS:ffff8a467f200000(0000) knlGS:0000000000000000
    CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
    CR2: 0000000000000018 CR3: 0000000077262005 CR4: 00000000001606f0
    Call Trace:
     key_create_or_update+0x2bc/0x460
     SyS_add_key+0x10c/0x1d0
     entry_SYSCALL_64_fastpath+0x1f/0xbe
    RIP: 0033:0x7f5d7f211259
    RSP: 002b:00007ffed03904c8 EFLAGS: 00000246 ORIG_RAX: 00000000000000f8
    RAX: ffffffffffffffda RBX: 000000003b2a7955 RCX: 00007f5d7f211259
    RDX: 00000000004009e4 RSI: 00000000004009ff RDI: 0000000000400a04
    RBP: 0000000068db8bad R08: 000000003b2a7955 R09: 0000000000000004
    R10: 000000000000001a R11: 0000000000000246 R12: 0000000000400868
    R13: 00007ffed03905d0 R14: 0000000000000000 R15: 0000000000000000
    Code: 77 28 e8 64 34 1f 00 45 31 c0 31 c9 48 8d 55 c8 48 89 df 48 8d 75 d0 e8 ff f9 ff ff 85 c0 41 89 c4 0f 88 84 00 00 00 4c 8b 7d c8 <49> 8b 75 18 4c 89 ff e8 24 f8 ff ff 85 c0 41 89 c4 78 6d 49 8b
    RIP: encrypted_update+0xb0/0x170 RSP: ffffb15c40773de8
    CR2: 0000000000000018

Cc: <stable@vger.kernel.org>    [v2.6.12+]
Reported-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Eric Biggers <ebiggers@google.com>
---

 security/keys/key.c |    9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/security/keys/key.c b/security/keys/key.c
index de1b789ad29f..ef2a028bd554 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -942,6 +942,15 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
 	 */
 	__key_link_end(keyring, &index_key, edit);
 
+	if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
+		ret = wait_for_key_construction(key_ref_to_ptr(key_ref), true);
+		if (ret < 0) {
+			key_ref_put(key_ref);
+			key_ref = ERR_PTR(ret);
+			goto error_free_prep;
+		}
+	}
+
 	key_ref = __key_update(key_ref, &prep);
 	goto error_free_prep;
 }

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 13/15] KEYS: load key flags and expiry time atomically in key_validate()
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:17     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

In key_validate(), load the flags and expiry time once atomically, since
these can change concurrently if key_validate() is called without the
key semaphore held.  And we don't want to get inconsistent results if a
variable is referenced multiple times.  For example, key->expiry was
referenced in both 'if (key->expiry)' and in 'if (now.tv_sec >key->expiry)', making it theoretically possible to see a spurious
EKEYEXPIRED while the expiration time was being removed, i.e. set to 0.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/permission.c |    7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..a72b4dd70c8a 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -88,7 +88,8 @@ EXPORT_SYMBOL(key_task_permission);
  */
 int key_validate(const struct key *key)
 {
-	unsigned long flags = key->flags;
+	unsigned long flags = READ_ONCE(key->flags);
+	time_t expiry = READ_ONCE(key->expiry);
 
 	if (flags & (1 << KEY_FLAG_INVALIDATED))
 		return -ENOKEY;
@@ -99,9 +100,9 @@ int key_validate(const struct key *key)
 		return -EKEYREVOKED;
 
 	/* check it hasn't expired */
-	if (key->expiry) {
+	if (expiry) {
 		struct timespec now = current_kernel_time();
-		if (now.tv_sec >= key->expiry)
+		if (now.tv_sec >= expiry)
 			return -EKEYEXPIRED;
 	}
 


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 13/15] KEYS: load key flags and expiry time atomically in key_validate()
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

In key_validate(), load the flags and expiry time once atomically, since
these can change concurrently if key_validate() is called without the
key semaphore held.  And we don't want to get inconsistent results if a
variable is referenced multiple times.  For example, key->expiry was
referenced in both 'if (key->expiry)' and in 'if (now.tv_sec >=
key->expiry)', making it theoretically possible to see a spurious
EKEYEXPIRED while the expiration time was being removed, i.e. set to 0.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/permission.c |    7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..a72b4dd70c8a 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -88,7 +88,8 @@ EXPORT_SYMBOL(key_task_permission);
  */
 int key_validate(const struct key *key)
 {
-	unsigned long flags = key->flags;
+	unsigned long flags = READ_ONCE(key->flags);
+	time_t expiry = READ_ONCE(key->expiry);
 
 	if (flags & (1 << KEY_FLAG_INVALIDATED))
 		return -ENOKEY;
@@ -99,9 +100,9 @@ int key_validate(const struct key *key)
 		return -EKEYREVOKED;
 
 	/* check it hasn't expired */
-	if (key->expiry) {
+	if (expiry) {
 		struct timespec now = current_kernel_time();
-		if (now.tv_sec >= key->expiry)
+		if (now.tv_sec >= expiry)
 			return -EKEYEXPIRED;
 	}
 

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 13/15] KEYS: load key flags and expiry time atomically in key_validate()
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel

From: Eric Biggers <ebiggers@google.com>

In key_validate(), load the flags and expiry time once atomically, since
these can change concurrently if key_validate() is called without the
key semaphore held.  And we don't want to get inconsistent results if a
variable is referenced multiple times.  For example, key->expiry was
referenced in both 'if (key->expiry)' and in 'if (now.tv_sec >=
key->expiry)', making it theoretically possible to see a spurious
EKEYEXPIRED while the expiration time was being removed, i.e. set to 0.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/permission.c |    7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/security/keys/permission.c b/security/keys/permission.c
index 732cc0beffdf..a72b4dd70c8a 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -88,7 +88,8 @@ EXPORT_SYMBOL(key_task_permission);
  */
 int key_validate(const struct key *key)
 {
-	unsigned long flags = key->flags;
+	unsigned long flags = READ_ONCE(key->flags);
+	time_t expiry = READ_ONCE(key->expiry);
 
 	if (flags & (1 << KEY_FLAG_INVALIDATED))
 		return -ENOKEY;
@@ -99,9 +100,9 @@ int key_validate(const struct key *key)
 		return -EKEYREVOKED;
 
 	/* check it hasn't expired */
-	if (key->expiry) {
+	if (expiry) {
 		struct timespec now = current_kernel_time();
-		if (now.tv_sec >= key->expiry)
+		if (now.tv_sec >= expiry)
 			return -EKEYEXPIRED;
 	}
 

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 14/15] KEYS: load key flags and expiry time atomically in keyring_search_iterator()
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:17     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

Similar to the case for key_validate(), we should load the key ->flags
and ->expiry once atomically in keyring_search_iterator(), since they
can be changed concurrently whenever the key semaphore isn't held.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/keyring.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 816948abff89..1ef5c7caf15c 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -566,6 +566,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	/* skip invalidated, revoked and expired keys */
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
+		time_t expiry = READ_ONCE(key->expiry);
+
 		if (kflags & ((1 << KEY_FLAG_INVALIDATED) |
 			      (1 << KEY_FLAG_REVOKED))) {
 			ctx->result = ERR_PTR(-EKEYREVOKED);
@@ -573,7 +575,7 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 			goto skipped;
 		}
 
-		if (key->expiry && ctx->now.tv_sec >= key->expiry) {
+		if (expiry && ctx->now.tv_sec >= expiry) {
 			if (!(ctx->flags & KEYRING_SEARCH_SKIP_EXPIRED))
 				ctx->result = ERR_PTR(-EKEYEXPIRED);
 			kleave(" = %d [expire]", ctx->skipped_ret);


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 14/15] KEYS: load key flags and expiry time atomically in keyring_search_iterator()
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

Similar to the case for key_validate(), we should load the key ->flags
and ->expiry once atomically in keyring_search_iterator(), since they
can be changed concurrently whenever the key semaphore isn't held.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/keyring.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 816948abff89..1ef5c7caf15c 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -566,6 +566,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	/* skip invalidated, revoked and expired keys */
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
+		time_t expiry = READ_ONCE(key->expiry);
+
 		if (kflags & ((1 << KEY_FLAG_INVALIDATED) |
 			      (1 << KEY_FLAG_REVOKED))) {
 			ctx->result = ERR_PTR(-EKEYREVOKED);
@@ -573,7 +575,7 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 			goto skipped;
 		}
 
-		if (key->expiry && ctx->now.tv_sec >= key->expiry) {
+		if (expiry && ctx->now.tv_sec >= expiry) {
 			if (!(ctx->flags & KEYRING_SEARCH_SKIP_EXPIRED))
 				ctx->result = ERR_PTR(-EKEYEXPIRED);
 			kleave(" = %d [expire]", ctx->skipped_ret);

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 14/15] KEYS: load key flags and expiry time atomically in keyring_search_iterator()
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel

From: Eric Biggers <ebiggers@google.com>

Similar to the case for key_validate(), we should load the key ->flags
and ->expiry once atomically in keyring_search_iterator(), since they
can be changed concurrently whenever the key semaphore isn't held.

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/keyring.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 816948abff89..1ef5c7caf15c 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -566,6 +566,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	/* skip invalidated, revoked and expired keys */
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
+		time_t expiry = READ_ONCE(key->expiry);
+
 		if (kflags & ((1 << KEY_FLAG_INVALIDATED) |
 			      (1 << KEY_FLAG_REVOKED))) {
 			ctx->result = ERR_PTR(-EKEYREVOKED);
@@ -573,7 +575,7 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 			goto skipped;
 		}
 
-		if (key->expiry && ctx->now.tv_sec >= key->expiry) {
+		if (expiry && ctx->now.tv_sec >= expiry) {
 			if (!(ctx->flags & KEYRING_SEARCH_SKIP_EXPIRED))
 				ctx->result = ERR_PTR(-EKEYEXPIRED);
 			kleave(" = %d [expire]", ctx->skipped_ret);

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 15/15] KEYS: load key flags and expiry time atomically in proc_keys_show()
  2017-10-12 20:15   ` David Howells
  (?)
@ 2017-10-12 20:17     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

In proc_keys_show(), the key semaphore is not held, so the key ->flags
and ->expiry can be changed concurrently.  We therefore should read them
atomically just once.  Otherwise /proc/keys may show an inconsistent
state, such a key that is negative ('N') but not instantiated ('I').

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/proc.c |   24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/security/keys/proc.c b/security/keys/proc.c
index 46fa0f1bfcc3..589328ed736f 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -179,7 +179,9 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
+	time_t expiry;
 	unsigned long timo;
+	unsigned long flags;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
 	short state;
@@ -218,12 +220,13 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	rcu_read_lock();
 
 	/* come up with a suitable timeout value */
-	if (key->expiry = 0) {
+	expiry = READ_ONCE(key->expiry);
+	if (expiry = 0) {
 		memcpy(xbuf, "perm", 5);
-	} else if (now.tv_sec >= key->expiry) {
+	} else if (now.tv_sec >= expiry) {
 		memcpy(xbuf, "expd", 5);
 	} else {
-		timo = key->expiry - now.tv_sec;
+		timo = expiry - now.tv_sec;
 
 		if (timo < 60)
 			sprintf(xbuf, "%lus", timo);
@@ -239,18 +242,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	state = READ_ONCE(key->state);
 
-#define showflag(KEY, LETTER, FLAG) \
-	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
+#define showflag(FLAGS, LETTER, FLAG) \
+	((FLAGS & (1 << FLAG)) ? LETTER : '-')
 
+	flags = READ_ONCE(key->flags);
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
 		   state = KEY_IS_INSTANTIATED ? 'I' : '-',
-		   showflag(key, 'R', KEY_FLAG_REVOKED),
-		   showflag(key, 'D', KEY_FLAG_DEAD),
-		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
-		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
+		   showflag(flags, 'R', KEY_FLAG_REVOKED),
+		   showflag(flags, 'D', KEY_FLAG_DEAD),
+		   showflag(flags, 'Q', KEY_FLAG_IN_QUOTA),
+		   showflag(flags, 'U', KEY_FLAG_USER_CONSTRUCT),
 		   state < 0 ? 'N' : '-',
-		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
+		   showflag(flags, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
 		   key->perm,


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 15/15] KEYS: load key flags and expiry time atomically in proc_keys_show()
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: linux-security-module

From: Eric Biggers <ebiggers@google.com>

In proc_keys_show(), the key semaphore is not held, so the key ->flags
and ->expiry can be changed concurrently.  We therefore should read them
atomically just once.  Otherwise /proc/keys may show an inconsistent
state, such a key that is negative ('N') but not instantiated ('I').

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/proc.c |   24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/security/keys/proc.c b/security/keys/proc.c
index 46fa0f1bfcc3..589328ed736f 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -179,7 +179,9 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
+	time_t expiry;
 	unsigned long timo;
+	unsigned long flags;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
 	short state;
@@ -218,12 +220,13 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	rcu_read_lock();
 
 	/* come up with a suitable timeout value */
-	if (key->expiry == 0) {
+	expiry = READ_ONCE(key->expiry);
+	if (expiry == 0) {
 		memcpy(xbuf, "perm", 5);
-	} else if (now.tv_sec >= key->expiry) {
+	} else if (now.tv_sec >= expiry) {
 		memcpy(xbuf, "expd", 5);
 	} else {
-		timo = key->expiry - now.tv_sec;
+		timo = expiry - now.tv_sec;
 
 		if (timo < 60)
 			sprintf(xbuf, "%lus", timo);
@@ -239,18 +242,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	state = READ_ONCE(key->state);
 
-#define showflag(KEY, LETTER, FLAG) \
-	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
+#define showflag(FLAGS, LETTER, FLAG) \
+	((FLAGS & (1 << FLAG)) ? LETTER : '-')
 
+	flags = READ_ONCE(key->flags);
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
 		   state == KEY_IS_INSTANTIATED ? 'I' : '-',
-		   showflag(key, 'R', KEY_FLAG_REVOKED),
-		   showflag(key, 'D', KEY_FLAG_DEAD),
-		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
-		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
+		   showflag(flags, 'R', KEY_FLAG_REVOKED),
+		   showflag(flags, 'D', KEY_FLAG_DEAD),
+		   showflag(flags, 'Q', KEY_FLAG_IN_QUOTA),
+		   showflag(flags, 'U', KEY_FLAG_USER_CONSTRUCT),
 		   state < 0 ? 'N' : '-',
-		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
+		   showflag(flags, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
 		   key->perm,

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [PATCH 15/15] KEYS: load key flags and expiry time atomically in proc_keys_show()
@ 2017-10-12 20:17     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-12 20:17 UTC (permalink / raw)
  To: ebiggers; +Cc: keyrings, dhowells, linux-security-module, linux-kernel

From: Eric Biggers <ebiggers@google.com>

In proc_keys_show(), the key semaphore is not held, so the key ->flags
and ->expiry can be changed concurrently.  We therefore should read them
atomically just once.  Otherwise /proc/keys may show an inconsistent
state, such a key that is negative ('N') but not instantiated ('I').

Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 security/keys/proc.c |   24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/security/keys/proc.c b/security/keys/proc.c
index 46fa0f1bfcc3..589328ed736f 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -179,7 +179,9 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	struct rb_node *_p = v;
 	struct key *key = rb_entry(_p, struct key, serial_node);
 	struct timespec now;
+	time_t expiry;
 	unsigned long timo;
+	unsigned long flags;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
 	short state;
@@ -218,12 +220,13 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	rcu_read_lock();
 
 	/* come up with a suitable timeout value */
-	if (key->expiry == 0) {
+	expiry = READ_ONCE(key->expiry);
+	if (expiry == 0) {
 		memcpy(xbuf, "perm", 5);
-	} else if (now.tv_sec >= key->expiry) {
+	} else if (now.tv_sec >= expiry) {
 		memcpy(xbuf, "expd", 5);
 	} else {
-		timo = key->expiry - now.tv_sec;
+		timo = expiry - now.tv_sec;
 
 		if (timo < 60)
 			sprintf(xbuf, "%lus", timo);
@@ -239,18 +242,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 
 	state = READ_ONCE(key->state);
 
-#define showflag(KEY, LETTER, FLAG) \
-	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
+#define showflag(FLAGS, LETTER, FLAG) \
+	((FLAGS & (1 << FLAG)) ? LETTER : '-')
 
+	flags = READ_ONCE(key->flags);
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
 		   state == KEY_IS_INSTANTIATED ? 'I' : '-',
-		   showflag(key, 'R', KEY_FLAG_REVOKED),
-		   showflag(key, 'D', KEY_FLAG_DEAD),
-		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
-		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
+		   showflag(flags, 'R', KEY_FLAG_REVOKED),
+		   showflag(flags, 'D', KEY_FLAG_DEAD),
+		   showflag(flags, 'Q', KEY_FLAG_IN_QUOTA),
+		   showflag(flags, 'U', KEY_FLAG_USER_CONSTRUCT),
 		   state < 0 ? 'N' : '-',
-		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
+		   showflag(flags, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
 		   key->perm,

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
  2017-10-12 18:56   ` Eric Biggers
  (?)
@ 2017-10-13 15:39     ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-13 15:39 UTC (permalink / raw)
  To: linux-security-module

Eric Biggers <ebiggers3@gmail.com> wrote:

> >  static inline bool key_is_instantiated(const struct key *key)
> ...
> This should use 'smp_load_acquire(&key->state) = KEY_IS_INSTANTIATED',

I made a key_read_state() wrapper function that does this and then used it in
a bunch of places that read it, including this one.

> > +              short state = READ_ONCE(key->state);
> 
> Here the key has no more references, so nothing can be changing the state.
> Thus, the READ_ONCE() isn't actually needed.

Changed.

> > +static void mark_key_instantiated(struct key *key, int reject_error)
> > +{
> > +       smp_wmb(); /* Commit the payload before setting the state */
> > +       key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
> > +}
> > +
> 
> smp_store_release() would make this simpler as well as guarantee that the
> write is atomic.

Changed.

> > +       ret = key->state;
> > +       if (ret < 0)
> > +               goto error2; /* Negatively instantiated */
> 
> Not too important in practice (as this is constantly gotten wrong all over
> the kernel, and compilers play nice enough to make it not a huge deal), but
> READ_ONCE(key->state) will guarantee that the read of 'key->state' is atomic
> and not e.g. done byte-by-byte.

Changed to use key_read_state().

> > -       if (key->type->describe)
> > +       if (key->type->describe) {
> > +               smp_rmb(); /* Order access to payload after state set. */
> >		  key->type->describe(key, m);
> > +       }
> 
> This is the wrong place for this memory barrier.  The state is checked
> separately in ->describe() and it may have changed between when it was shown
> in proc_keys_show() vs. when it is checked in ->describe().  We can't
> actually make these two access to ->state consistent with respect to each
> other right now.  The most we can do is use smp_load_acquire() in
> key_is_instantiated() so that at least ->describe() isn't broken by itself.
> So the change here is pointless.

Feasibly.  I've changed the preceding:

	state = READ_ONCE(key->state);

into:

	state = key_read_state(key);

and got rid of smp_rmb() anyway, but you're right, ->describe() needs to
recheck the state if it might want to touch it.

I wonder if I should pass the state into ->describe()?

Possibly I shouldn't be calling ->describe() if the key isn't instantiated.

> > +       if (!(lflags & KEY_LOOKUP_PARTIAL)) {
> > +               if (key->state != KEY_IS_INSTANTIATED)
> > +                       goto invalid_key;
> > +               smp_rmb(); /* Order access to payload after state set. */
> > +       }
> 
> This should be simply:
> 
> 	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
> 		goto invalid_key;

Changed.

> > +       ret = READ_ONCE(key->state);
> > +       if (ret < 0)
> > +               return ret;
> >        return key_validate(key);
> > }
> > EXPORT_SYMBOL(wait_for_key_construction);
> 
> smp_load_acquire() rather than READ_ONCE(), in case the caller uses this as an
> indication that it is safe to access the payload.

Changed to use key_read_state().

> >-       if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
> >+       if (key->state < 0)
> >                zap = dereference_key_locked(key);
> >        rcu_assign_keypointer(key, prep->payload.data[0]);
> >        prep->payload.data[0] = NULL;
> 
> This is backwards; it should be 'if (!key_is_negative(key))'.

Actually, I think it should be 'if (key_is_instantiated())'

> > +       if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
> ...
> 'key' is NULL here.  It should be 'key_ref_to_ptr(key_ref)'.

Fixed.

David
---
commit 21fe21748b709710fbc78536a6d68bb4df8297ad
Author: David Howells <dhowells@redhat.com>
Date:   Wed Oct 4 16:43:25 2017 +0100

    KEYS: Fix race between updating and finding a negative key
    
    Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
    error into one field such that:
    
     (1) The instantiation state can be modified/read atomically.
    
     (2) The error can be accessed atomically with the state.
    
     (3) The error isn't stored unioned with the payload pointers.
    
    This deals with the problem that the state is spread over three different
    objects (two bits and a separate variable) and reading or updating them
    atomically isn't practical, given that not only can uninstantiated keys
    change into instantiated or rejected keys, but rejected keys can also turn
    into instantiated keys - and someone accessing the key might not be using
    any locking.
    
    The main side effect of this problem is that what was held in the payload
    may change, depending on the state.  For instance, you might observe the
    key to be in the rejected state.  You then read the cached error, but if
    the key semaphore wasn't locked, the key might've become instantiated
    between the two reads - and you might now have something in hand that isn't
    actually an error code.
    
    Additionally, barriering is included:
    
     (1) Order payload-set before state-set during instantiation.
    
     (2) Order state-read before payload-read when using the key.
    
    Further separate barriering is necessary if RCU is being used to access the
    payload content after reading the payload pointers.
    
    Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
    Cc: <stable@vger.kernel.org>    [v4.4+]
    Reported-by: Eric Biggers <ebiggers@google.com>
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..eae10f302b44 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_INSTANTIATED,
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -353,6 +356,12 @@ extern void key_set_timeout(struct key *, unsigned);
 #define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
 #define	KEY_NEED_ALL	0x3f	/* All the above permissions */
 
+static inline short key_read_state(const struct key *key)
+{
+	/* Barrier versus mark_key_instantiated(). */
+	return smp_load_acquire(&key->state);
+}
+
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
  * @key: The key to check.
@@ -362,8 +371,12 @@ extern void key_set_timeout(struct key *, unsigned);
  */
 static inline bool key_is_instantiated(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key_read_state(key) = KEY_IS_INSTANTIATED;
+}
+
+static inline bool key_is_negative(const struct key *key)
+{
+	return key_read_state(key) < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..e7aeecbf7f19 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key  			list_entry(keys->next, struct key, graveyard_link);
+		short state = key->state;
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
+		if (state = KEY_IS_INSTANTIATED &&
 		    key->type->destroy)
 			key->type->destroy(key);
 
@@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state = KEY_IS_INSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..cdd8fea8ddc4 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,18 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	/* Commit the payload before setting the state; barrier versus
+	 * key_read_state().
+	 */
+	smp_store_release(&key->state,
+			  (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED);
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +436,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state = KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret = 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +589,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state = KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +761,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret = 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +995,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret = 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..698309a8502e 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state = KEY_IS_INSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..816948abff89 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..9510822c4d96 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = key_read_state(key);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state = KEY_IS_INSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..5a8b985d1d5f 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -729,8 +729,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 	}
 
 	ret = -EIO;
-	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
 		goto invalid_key;
 
 	/* check the permissions */
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..e8036cd0ad54 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..d993d030a644 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_instantiated(key))
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-13 15:39     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-13 15:39 UTC (permalink / raw)
  To: linux-security-module

Eric Biggers <ebiggers3@gmail.com> wrote:

> >  static inline bool key_is_instantiated(const struct key *key)
> ...
> This should use 'smp_load_acquire(&key->state) == KEY_IS_INSTANTIATED',

I made a key_read_state() wrapper function that does this and then used it in
a bunch of places that read it, including this one.

> > +              short state = READ_ONCE(key->state);
> 
> Here the key has no more references, so nothing can be changing the state.
> Thus, the READ_ONCE() isn't actually needed.

Changed.

> > +static void mark_key_instantiated(struct key *key, int reject_error)
> > +{
> > +       smp_wmb(); /* Commit the payload before setting the state */
> > +       key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
> > +}
> > +
> 
> smp_store_release() would make this simpler as well as guarantee that the
> write is atomic.

Changed.

> > +       ret = key->state;
> > +       if (ret < 0)
> > +               goto error2; /* Negatively instantiated */
> 
> Not too important in practice (as this is constantly gotten wrong all over
> the kernel, and compilers play nice enough to make it not a huge deal), but
> READ_ONCE(key->state) will guarantee that the read of 'key->state' is atomic
> and not e.g. done byte-by-byte.

Changed to use key_read_state().

> > -       if (key->type->describe)
> > +       if (key->type->describe) {
> > +               smp_rmb(); /* Order access to payload after state set. */
> >		  key->type->describe(key, m);
> > +       }
> 
> This is the wrong place for this memory barrier.  The state is checked
> separately in ->describe() and it may have changed between when it was shown
> in proc_keys_show() vs. when it is checked in ->describe().  We can't
> actually make these two access to ->state consistent with respect to each
> other right now.  The most we can do is use smp_load_acquire() in
> key_is_instantiated() so that at least ->describe() isn't broken by itself.
> So the change here is pointless.

Feasibly.  I've changed the preceding:

	state = READ_ONCE(key->state);

into:

	state = key_read_state(key);

and got rid of smp_rmb() anyway, but you're right, ->describe() needs to
recheck the state if it might want to touch it.

I wonder if I should pass the state into ->describe()?

Possibly I shouldn't be calling ->describe() if the key isn't instantiated.

> > +       if (!(lflags & KEY_LOOKUP_PARTIAL)) {
> > +               if (key->state != KEY_IS_INSTANTIATED)
> > +                       goto invalid_key;
> > +               smp_rmb(); /* Order access to payload after state set. */
> > +       }
> 
> This should be simply:
> 
> 	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
> 		goto invalid_key;

Changed.

> > +       ret = READ_ONCE(key->state);
> > +       if (ret < 0)
> > +               return ret;
> >        return key_validate(key);
> > }
> > EXPORT_SYMBOL(wait_for_key_construction);
> 
> smp_load_acquire() rather than READ_ONCE(), in case the caller uses this as an
> indication that it is safe to access the payload.

Changed to use key_read_state().

> >-       if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
> >+       if (key->state < 0)
> >                zap = dereference_key_locked(key);
> >        rcu_assign_keypointer(key, prep->payload.data[0]);
> >        prep->payload.data[0] = NULL;
> 
> This is backwards; it should be 'if (!key_is_negative(key))'.

Actually, I think it should be 'if (key_is_instantiated())'

> > +       if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
> ...
> 'key' is NULL here.  It should be 'key_ref_to_ptr(key_ref)'.

Fixed.

David
---
commit 21fe21748b709710fbc78536a6d68bb4df8297ad
Author: David Howells <dhowells@redhat.com>
Date:   Wed Oct 4 16:43:25 2017 +0100

    KEYS: Fix race between updating and finding a negative key
    
    Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
    error into one field such that:
    
     (1) The instantiation state can be modified/read atomically.
    
     (2) The error can be accessed atomically with the state.
    
     (3) The error isn't stored unioned with the payload pointers.
    
    This deals with the problem that the state is spread over three different
    objects (two bits and a separate variable) and reading or updating them
    atomically isn't practical, given that not only can uninstantiated keys
    change into instantiated or rejected keys, but rejected keys can also turn
    into instantiated keys - and someone accessing the key might not be using
    any locking.
    
    The main side effect of this problem is that what was held in the payload
    may change, depending on the state.  For instance, you might observe the
    key to be in the rejected state.  You then read the cached error, but if
    the key semaphore wasn't locked, the key might've become instantiated
    between the two reads - and you might now have something in hand that isn't
    actually an error code.
    
    Additionally, barriering is included:
    
     (1) Order payload-set before state-set during instantiation.
    
     (2) Order state-read before payload-read when using the key.
    
    Further separate barriering is necessary if RCU is being used to access the
    payload content after reading the payload pointers.
    
    Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
    Cc: <stable@vger.kernel.org>    [v4.4+]
    Reported-by: Eric Biggers <ebiggers@google.com>
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..eae10f302b44 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_INSTANTIATED,
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -353,6 +356,12 @@ extern void key_set_timeout(struct key *, unsigned);
 #define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
 #define	KEY_NEED_ALL	0x3f	/* All the above permissions */
 
+static inline short key_read_state(const struct key *key)
+{
+	/* Barrier versus mark_key_instantiated(). */
+	return smp_load_acquire(&key->state);
+}
+
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
  * @key: The key to check.
@@ -362,8 +371,12 @@ extern void key_set_timeout(struct key *, unsigned);
  */
 static inline bool key_is_instantiated(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key_read_state(key) == KEY_IS_INSTANTIATED;
+}
+
+static inline bool key_is_negative(const struct key *key)
+{
+	return key_read_state(key) < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..e7aeecbf7f19 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key =
 			list_entry(keys->next, struct key, graveyard_link);
+		short state = key->state;
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
+		if (state == KEY_IS_INSTANTIATED &&
 		    key->type->destroy)
 			key->type->destroy(key);
 
@@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state == KEY_IS_INSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..cdd8fea8ddc4 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,18 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	/* Commit the payload before setting the state; barrier versus
+	 * key_read_state().
+	 */
+	smp_store_release(&key->state,
+			  (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED);
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +436,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret == 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +589,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +761,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +995,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..698309a8502e 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state == KEY_IS_INSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..816948abff89 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..9510822c4d96 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = key_read_state(key);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state == KEY_IS_INSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..5a8b985d1d5f 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -729,8 +729,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 	}
 
 	ret = -EIO;
-	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
 		goto invalid_key;
 
 	/* check the permissions */
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..e8036cd0ad54 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..d993d030a644 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_instantiated(key))
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-13 15:39     ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-13 15:39 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, ebiggers, linux-security-module, keyrings, linux-kernel

Eric Biggers <ebiggers3@gmail.com> wrote:

> >  static inline bool key_is_instantiated(const struct key *key)
> ...
> This should use 'smp_load_acquire(&key->state) == KEY_IS_INSTANTIATED',

I made a key_read_state() wrapper function that does this and then used it in
a bunch of places that read it, including this one.

> > +              short state = READ_ONCE(key->state);
> 
> Here the key has no more references, so nothing can be changing the state.
> Thus, the READ_ONCE() isn't actually needed.

Changed.

> > +static void mark_key_instantiated(struct key *key, int reject_error)
> > +{
> > +       smp_wmb(); /* Commit the payload before setting the state */
> > +       key->state = (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED;
> > +}
> > +
> 
> smp_store_release() would make this simpler as well as guarantee that the
> write is atomic.

Changed.

> > +       ret = key->state;
> > +       if (ret < 0)
> > +               goto error2; /* Negatively instantiated */
> 
> Not too important in practice (as this is constantly gotten wrong all over
> the kernel, and compilers play nice enough to make it not a huge deal), but
> READ_ONCE(key->state) will guarantee that the read of 'key->state' is atomic
> and not e.g. done byte-by-byte.

Changed to use key_read_state().

> > -       if (key->type->describe)
> > +       if (key->type->describe) {
> > +               smp_rmb(); /* Order access to payload after state set. */
> >		  key->type->describe(key, m);
> > +       }
> 
> This is the wrong place for this memory barrier.  The state is checked
> separately in ->describe() and it may have changed between when it was shown
> in proc_keys_show() vs. when it is checked in ->describe().  We can't
> actually make these two access to ->state consistent with respect to each
> other right now.  The most we can do is use smp_load_acquire() in
> key_is_instantiated() so that at least ->describe() isn't broken by itself.
> So the change here is pointless.

Feasibly.  I've changed the preceding:

	state = READ_ONCE(key->state);

into:

	state = key_read_state(key);

and got rid of smp_rmb() anyway, but you're right, ->describe() needs to
recheck the state if it might want to touch it.

I wonder if I should pass the state into ->describe()?

Possibly I shouldn't be calling ->describe() if the key isn't instantiated.

> > +       if (!(lflags & KEY_LOOKUP_PARTIAL)) {
> > +               if (key->state != KEY_IS_INSTANTIATED)
> > +                       goto invalid_key;
> > +               smp_rmb(); /* Order access to payload after state set. */
> > +       }
> 
> This should be simply:
> 
> 	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
> 		goto invalid_key;

Changed.

> > +       ret = READ_ONCE(key->state);
> > +       if (ret < 0)
> > +               return ret;
> >        return key_validate(key);
> > }
> > EXPORT_SYMBOL(wait_for_key_construction);
> 
> smp_load_acquire() rather than READ_ONCE(), in case the caller uses this as an
> indication that it is safe to access the payload.

Changed to use key_read_state().

> >-       if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
> >+       if (key->state < 0)
> >                zap = dereference_key_locked(key);
> >        rcu_assign_keypointer(key, prep->payload.data[0]);
> >        prep->payload.data[0] = NULL;
> 
> This is backwards; it should be 'if (!key_is_negative(key))'.

Actually, I think it should be 'if (key_is_instantiated())'

> > +       if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) {
> ...
> 'key' is NULL here.  It should be 'key_ref_to_ptr(key_ref)'.

Fixed.

David
---
commit 21fe21748b709710fbc78536a6d68bb4df8297ad
Author: David Howells <dhowells@redhat.com>
Date:   Wed Oct 4 16:43:25 2017 +0100

    KEYS: Fix race between updating and finding a negative key
    
    Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
    error into one field such that:
    
     (1) The instantiation state can be modified/read atomically.
    
     (2) The error can be accessed atomically with the state.
    
     (3) The error isn't stored unioned with the payload pointers.
    
    This deals with the problem that the state is spread over three different
    objects (two bits and a separate variable) and reading or updating them
    atomically isn't practical, given that not only can uninstantiated keys
    change into instantiated or rejected keys, but rejected keys can also turn
    into instantiated keys - and someone accessing the key might not be using
    any locking.
    
    The main side effect of this problem is that what was held in the payload
    may change, depending on the state.  For instance, you might observe the
    key to be in the rejected state.  You then read the cached error, but if
    the key semaphore wasn't locked, the key might've become instantiated
    between the two reads - and you might now have something in hand that isn't
    actually an error code.
    
    Additionally, barriering is included:
    
     (1) Order payload-set before state-set during instantiation.
    
     (2) Order state-read before payload-read when using the key.
    
    Further separate barriering is necessary if RCU is being used to access the
    payload content after reading the payload pointers.
    
    Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
    Cc: <stable@vger.kernel.org>    [v4.4+]
    Reported-by: Eric Biggers <ebiggers@google.com>
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..eae10f302b44 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_INSTANTIATED,
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -353,6 +356,12 @@ extern void key_set_timeout(struct key *, unsigned);
 #define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
 #define	KEY_NEED_ALL	0x3f	/* All the above permissions */
 
+static inline short key_read_state(const struct key *key)
+{
+	/* Barrier versus mark_key_instantiated(). */
+	return smp_load_acquire(&key->state);
+}
+
 /**
  * key_is_instantiated - Determine if a key has been positively instantiated
  * @key: The key to check.
@@ -362,8 +371,12 @@ extern void key_set_timeout(struct key *, unsigned);
  */
 static inline bool key_is_instantiated(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key_read_state(key) == KEY_IS_INSTANTIATED;
+}
+
+static inline bool key_is_negative(const struct key *key)
+{
+	return key_read_state(key) < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..e7aeecbf7f19 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key =
 			list_entry(keys->next, struct key, graveyard_link);
+		short state = key->state;
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
+		if (state == KEY_IS_INSTANTIATED &&
 		    key->type->destroy)
 			key->type->destroy(key);
 
@@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state == KEY_IS_INSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..cdd8fea8ddc4 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,18 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	/* Commit the payload before setting the state; barrier versus
+	 * key_read_state().
+	 */
+	smp_store_release(&key->state,
+			  (reject_error < 0) ? reject_error : KEY_IS_INSTANTIATED);
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +436,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret == 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +589,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +761,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +995,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..698309a8502e 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state == KEY_IS_INSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..816948abff89 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..9510822c4d96 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = key_read_state(key);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state == KEY_IS_INSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..5a8b985d1d5f 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -729,8 +729,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 	}
 
 	ret = -EIO;
-	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
 		goto invalid_key;
 
 	/* check the permissions */
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..e8036cd0ad54 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..d993d030a644 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_instantiated(key))
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
  2017-10-13 15:39     ` David Howells
  (?)
@ 2017-10-16 18:31       ` Eric Biggers
  -1 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-16 18:31 UTC (permalink / raw)
  To: linux-security-module

On Fri, Oct 13, 2017 at 04:39:28PM +0100, David Howells wrote:
> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index 87cb260e4890..e7aeecbf7f19 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  	while (!list_empty(keys)) {
>  		struct key *key >  			list_entry(keys->next, struct key, graveyard_link);
> +		short state = key->state;
> +
>  		list_del(&key->graveyard_link);
>  
>  		kdebug("- %u", key->serial);
>  		key_check(key);
>  
>  		/* Throw away the key data if the key is instantiated */
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
> -		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
> +		if (state = KEY_IS_INSTANTIATED &&
>  		    key->type->destroy)
>  			key->type->destroy(key);

Nit: put the two checks on the same line.

>  
> @@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  		}
>  
>  		atomic_dec(&key->user->nkeys);
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> +		if (state = KEY_IS_INSTANTIATED)
>  			atomic_dec(&key->user->nikeys);

This changes the behavior.  Previously ->nikeys counted both negatively and
positively instantiated keys, while this change implies that it now will only
count positively instantiated keys.  I think you meant 'state !KEY_IS_UNINSTANTIATED'?  Renaming KEY_IS_INSTANTIATED to KEY_IS_POSITIVE or
KEY_IS_POSITIVELY_INSTANTIATED also might help reduce this confusion.

> @@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
>  		atomic_dec(&key->user->nkeys);
>  		atomic_inc(&newowner->nkeys);
>  
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
> +		if (key->state = KEY_IS_INSTANTIATED) {
>  			atomic_dec(&key->user->nikeys);
>  			atomic_inc(&newowner->nikeys);
>  		}

Same problem: ->nikeys was previously counting negatively instantiated keys too.
Now it isn't.  Shouldn't it test 'key->state != KEY_IS_UNINSTANTIATED'?

> diff --git a/security/keys/proc.c b/security/keys/proc.c
> index de834309d100..9510822c4d96 100644
> --- a/security/keys/proc.c
> +++ b/security/keys/proc.c
> @@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  	unsigned long timo;
>  	key_ref_t key_ref, skey_ref;
>  	char xbuf[16];
> +	short state;
>  	int rc;
>  
>  	struct keyring_search_context ctx = {
> @@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  			sprintf(xbuf, "%luw", timo / (60*60*24*7));
>  	}
>  
> +	state = key_read_state(key);
> +
>  #define showflag(KEY, LETTER, FLAG) \
>  	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
>  
>  	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
>  		   key->serial,
> -		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
> +		   state = KEY_IS_INSTANTIATED ? 'I' : '-',

Similar problem.  Previously 'I' was shown for negatively instantiated keys; now
it's not.  Shouldn't it test 'state != KEY_IS_UNINSTANTIATED'?

> diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
> index 293d3598153b..5a8b985d1d5f 100644
> --- a/security/keys/process_keys.c
> +++ b/security/keys/process_keys.c
> @@ -729,8 +729,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
>  	}
>  
>  	ret = -EIO;
> -	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
> -	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> +	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
>  		goto invalid_key;

Similar problem again.  Previously this allowed negatively instantiated keys
through whereas now it only allows positively instantiated keys.  Is that
intentional?

Eric

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-16 18:31       ` Eric Biggers
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-16 18:31 UTC (permalink / raw)
  To: linux-security-module

On Fri, Oct 13, 2017 at 04:39:28PM +0100, David Howells wrote:
> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index 87cb260e4890..e7aeecbf7f19 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  	while (!list_empty(keys)) {
>  		struct key *key =
>  			list_entry(keys->next, struct key, graveyard_link);
> +		short state = key->state;
> +
>  		list_del(&key->graveyard_link);
>  
>  		kdebug("- %u", key->serial);
>  		key_check(key);
>  
>  		/* Throw away the key data if the key is instantiated */
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
> -		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
> +		if (state == KEY_IS_INSTANTIATED &&
>  		    key->type->destroy)
>  			key->type->destroy(key);

Nit: put the two checks on the same line.

>  
> @@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  		}
>  
>  		atomic_dec(&key->user->nkeys);
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> +		if (state == KEY_IS_INSTANTIATED)
>  			atomic_dec(&key->user->nikeys);

This changes the behavior.  Previously ->nikeys counted both negatively and
positively instantiated keys, while this change implies that it now will only
count positively instantiated keys.  I think you meant 'state !=
KEY_IS_UNINSTANTIATED'?  Renaming KEY_IS_INSTANTIATED to KEY_IS_POSITIVE or
KEY_IS_POSITIVELY_INSTANTIATED also might help reduce this confusion.

> @@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
>  		atomic_dec(&key->user->nkeys);
>  		atomic_inc(&newowner->nkeys);
>  
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
> +		if (key->state == KEY_IS_INSTANTIATED) {
>  			atomic_dec(&key->user->nikeys);
>  			atomic_inc(&newowner->nikeys);
>  		}

Same problem: ->nikeys was previously counting negatively instantiated keys too.
Now it isn't.  Shouldn't it test 'key->state != KEY_IS_UNINSTANTIATED'?

> diff --git a/security/keys/proc.c b/security/keys/proc.c
> index de834309d100..9510822c4d96 100644
> --- a/security/keys/proc.c
> +++ b/security/keys/proc.c
> @@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  	unsigned long timo;
>  	key_ref_t key_ref, skey_ref;
>  	char xbuf[16];
> +	short state;
>  	int rc;
>  
>  	struct keyring_search_context ctx = {
> @@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  			sprintf(xbuf, "%luw", timo / (60*60*24*7));
>  	}
>  
> +	state = key_read_state(key);
> +
>  #define showflag(KEY, LETTER, FLAG) \
>  	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
>  
>  	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
>  		   key->serial,
> -		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
> +		   state == KEY_IS_INSTANTIATED ? 'I' : '-',

Similar problem.  Previously 'I' was shown for negatively instantiated keys; now
it's not.  Shouldn't it test 'state != KEY_IS_UNINSTANTIATED'?

> diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
> index 293d3598153b..5a8b985d1d5f 100644
> --- a/security/keys/process_keys.c
> +++ b/security/keys/process_keys.c
> @@ -729,8 +729,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
>  	}
>  
>  	ret = -EIO;
> -	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
> -	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> +	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
>  		goto invalid_key;

Similar problem again.  Previously this allowed negatively instantiated keys
through whereas now it only allows positively instantiated keys.  Is that
intentional?

Eric
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-16 18:31       ` Eric Biggers
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-16 18:31 UTC (permalink / raw)
  To: David Howells; +Cc: ebiggers, linux-security-module, keyrings, linux-kernel

On Fri, Oct 13, 2017 at 04:39:28PM +0100, David Howells wrote:
> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index 87cb260e4890..e7aeecbf7f19 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -129,14 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  	while (!list_empty(keys)) {
>  		struct key *key =
>  			list_entry(keys->next, struct key, graveyard_link);
> +		short state = key->state;
> +
>  		list_del(&key->graveyard_link);
>  
>  		kdebug("- %u", key->serial);
>  		key_check(key);
>  
>  		/* Throw away the key data if the key is instantiated */
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
> -		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
> +		if (state == KEY_IS_INSTANTIATED &&
>  		    key->type->destroy)
>  			key->type->destroy(key);

Nit: put the two checks on the same line.

>  
> @@ -151,7 +152,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  		}
>  
>  		atomic_dec(&key->user->nkeys);
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> +		if (state == KEY_IS_INSTANTIATED)
>  			atomic_dec(&key->user->nikeys);

This changes the behavior.  Previously ->nikeys counted both negatively and
positively instantiated keys, while this change implies that it now will only
count positively instantiated keys.  I think you meant 'state !=
KEY_IS_UNINSTANTIATED'?  Renaming KEY_IS_INSTANTIATED to KEY_IS_POSITIVE or
KEY_IS_POSITIVELY_INSTANTIATED also might help reduce this confusion.

> @@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
>  		atomic_dec(&key->user->nkeys);
>  		atomic_inc(&newowner->nkeys);
>  
> -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
> +		if (key->state == KEY_IS_INSTANTIATED) {
>  			atomic_dec(&key->user->nikeys);
>  			atomic_inc(&newowner->nikeys);
>  		}

Same problem: ->nikeys was previously counting negatively instantiated keys too.
Now it isn't.  Shouldn't it test 'key->state != KEY_IS_UNINSTANTIATED'?

> diff --git a/security/keys/proc.c b/security/keys/proc.c
> index de834309d100..9510822c4d96 100644
> --- a/security/keys/proc.c
> +++ b/security/keys/proc.c
> @@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  	unsigned long timo;
>  	key_ref_t key_ref, skey_ref;
>  	char xbuf[16];
> +	short state;
>  	int rc;
>  
>  	struct keyring_search_context ctx = {
> @@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  			sprintf(xbuf, "%luw", timo / (60*60*24*7));
>  	}
>  
> +	state = key_read_state(key);
> +
>  #define showflag(KEY, LETTER, FLAG) \
>  	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
>  
>  	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
>  		   key->serial,
> -		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
> +		   state == KEY_IS_INSTANTIATED ? 'I' : '-',

Similar problem.  Previously 'I' was shown for negatively instantiated keys; now
it's not.  Shouldn't it test 'state != KEY_IS_UNINSTANTIATED'?

> diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
> index 293d3598153b..5a8b985d1d5f 100644
> --- a/security/keys/process_keys.c
> +++ b/security/keys/process_keys.c
> @@ -729,8 +729,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
>  	}
>  
>  	ret = -EIO;
> -	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
> -	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> +	if (!(lflags & KEY_LOOKUP_PARTIAL) && !key_is_instantiated(key))
>  		goto invalid_key;

Similar problem again.  Previously this allowed negatively instantiated keys
through whereas now it only allows positively instantiated keys.  Is that
intentional?

Eric

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
  2017-10-16 18:31       ` Eric Biggers
  (?)
@ 2017-10-16 22:09         ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-16 22:09 UTC (permalink / raw)
  To: linux-security-module

Eric Biggers <ebiggers3@gmail.com> wrote:

> > -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> > +		if (state = KEY_IS_INSTANTIATED)
> >  			atomic_dec(&key->user->nikeys);
> 
> This changes the behavior.  Previously ->nikeys counted both negatively and
> positively instantiated keys, while this change implies that it now will only
> count positively instantiated keys.  I think you meant 'state !> KEY_IS_UNINSTANTIATED'?  Renaming KEY_IS_INSTANTIATED to KEY_IS_POSITIVE or
> KEY_IS_POSITIVELY_INSTANTIATED also might help reduce this confusion.

Yeah - I think I'm confusing myself by overloading 'instantiated' in my mind.

David

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-16 22:09         ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-16 22:09 UTC (permalink / raw)
  To: linux-security-module

Eric Biggers <ebiggers3@gmail.com> wrote:

> > -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> > +		if (state == KEY_IS_INSTANTIATED)
> >  			atomic_dec(&key->user->nikeys);
> 
> This changes the behavior.  Previously ->nikeys counted both negatively and
> positively instantiated keys, while this change implies that it now will only
> count positively instantiated keys.  I think you meant 'state !=
> KEY_IS_UNINSTANTIATED'?  Renaming KEY_IS_INSTANTIATED to KEY_IS_POSITIVE or
> KEY_IS_POSITIVELY_INSTANTIATED also might help reduce this confusion.

Yeah - I think I'm confusing myself by overloading 'instantiated' in my mind.

David
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-16 22:09         ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-16 22:09 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, ebiggers, linux-security-module, keyrings, linux-kernel

Eric Biggers <ebiggers3@gmail.com> wrote:

> > -		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
> > +		if (state == KEY_IS_INSTANTIATED)
> >  			atomic_dec(&key->user->nikeys);
> 
> This changes the behavior.  Previously ->nikeys counted both negatively and
> positively instantiated keys, while this change implies that it now will only
> count positively instantiated keys.  I think you meant 'state !=
> KEY_IS_UNINSTANTIATED'?  Renaming KEY_IS_INSTANTIATED to KEY_IS_POSITIVE or
> KEY_IS_POSITIVELY_INSTANTIATED also might help reduce this confusion.

Yeah - I think I'm confusing myself by overloading 'instantiated' in my mind.

David

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
  2017-10-16 18:31       ` Eric Biggers
  (?)
@ 2017-10-16 22:27         ` David Howells
  -1 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-16 22:27 UTC (permalink / raw)
  To: linux-security-module

Okay, I've fixed those issues, I think.  I've renamed the instantiation labels
to positive.

Thanks,
David
---
commit f23f3bb0ba3be44e775ac74148157136b919e3b0
Author: David Howells <dhowells@redhat.com>
Date:   Wed Oct 4 16:43:25 2017 +0100

    KEYS: Fix race between updating and finding a negative key
    
    Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
    error into one field such that:
    
     (1) The instantiation state can be modified/read atomically.
    
     (2) The error can be accessed atomically with the state.
    
     (3) The error isn't stored unioned with the payload pointers.
    
    This deals with the problem that the state is spread over three different
    objects (two bits and a separate variable) and reading or updating them
    atomically isn't practical, given that not only can uninstantiated keys
    change into instantiated or rejected keys, but rejected keys can also turn
    into instantiated keys - and someone accessing the key might not be using
    any locking.
    
    The main side effect of this problem is that what was held in the payload
    may change, depending on the state.  For instance, you might observe the
    key to be in the rejected state.  You then read the cached error, but if
    the key semaphore wasn't locked, the key might've become instantiated
    between the two reads - and you might now have something in hand that isn't
    actually an error code.
    
    The state is now KEY_IS_UNINSTANTIATED, KEY_IS_POSITIVE or a negative error
    code if the key is negatively instantiated.  The key_is_instantiated()
    function is replaced with key_is_positive() to avoid confusion as negative
    keys are also 'instantiated'.
    
    Additionally, barriering is included:
    
     (1) Order payload-set before state-set during instantiation.
    
     (2) Order state-read before payload-read when using the key.
    
    Further separate barriering is necessary if RCU is being used to access the
    payload content after reading the payload pointers.
    
    Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
    Cc: stable@vger.kernel.org # v4.4+
    Reported-by: Eric Biggers <ebiggers@google.com>
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..8a15cabe928d 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_POSITIVE,		/* Positively instantiated */
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -353,17 +356,27 @@ extern void key_set_timeout(struct key *, unsigned);
 #define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
 #define	KEY_NEED_ALL	0x3f	/* All the above permissions */
 
+static inline short key_read_state(const struct key *key)
+{
+	/* Barrier versus mark_key_instantiated(). */
+	return smp_load_acquire(&key->state);
+}
+
 /**
- * key_is_instantiated - Determine if a key has been positively instantiated
+ * key_is_positive - Determine if a key has been positively instantiated
  * @key: The key to check.
  *
  * Return true if the specified key has been positively instantiated, false
  * otherwise.
  */
-static inline bool key_is_instantiated(const struct key *key)
+static inline bool key_is_positive(const struct key *key)
+{
+	return key_read_state(key) = KEY_IS_POSITIVE;
+}
+
+static inline bool key_is_negative(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key_read_state(key) < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..e1d4d898a007 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -224,7 +224,7 @@ static int dns_resolver_match_preparse(struct key_match_data *match_data)
 static void dns_resolver_describe(const struct key *key, struct seq_file *m)
 {
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key)) {
+	if (key_is_positive(key)) {
 		int err = PTR_ERR(key->payload.data[dns_key_error]);
 
 		if (err)
diff --git a/security/keys/big_key.c b/security/keys/big_key.c
index e607830b6154..929e14978c42 100644
--- a/security/keys/big_key.c
+++ b/security/keys/big_key.c
@@ -247,7 +247,7 @@ void big_key_revoke(struct key *key)
 
 	/* clear the quota */
 	key_payload_reserve(key, 0);
-	if (key_is_instantiated(key) &&
+	if (key_is_positive(key) &&
 	    (size_t)key->payload.data[big_key_len] > BIG_KEY_FILE_THRESHOLD)
 		vfs_truncate(path, 0);
 }
@@ -279,7 +279,7 @@ void big_key_describe(const struct key *key, struct seq_file *m)
 
 	seq_puts(m, key->description);
 
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, ": %zu [%s]",
 			   datalen,
 			   datalen > BIG_KEY_FILE_THRESHOLD ? "file" : "buff");
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..f01d48cb3de1 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,15 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key  			list_entry(keys->next, struct key, graveyard_link);
+		short state = key->state;
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
-		    key->type->destroy)
+		if (state = KEY_IS_POSITIVE && key->type->destroy)
 			key->type->destroy(key);
 
 		security_key_free(key);
@@ -151,7 +151,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state != KEY_IS_UNINSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..9385e7cc710f 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,18 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	/* Commit the payload before setting the state; barrier versus
+	 * key_read_state().
+	 */
+	smp_store_release(&key->state,
+			  (reject_error < 0) ? reject_error : KEY_IS_POSITIVE);
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +436,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state = KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret = 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +589,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state = KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +761,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret = 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +995,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret = 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..76d22f726ae4 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state != KEY_IS_UNINSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..06173b091a74 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -414,7 +414,7 @@ static void keyring_describe(const struct key *keyring, struct seq_file *m)
 	else
 		seq_puts(m, "[anon]");
 
-	if (key_is_instantiated(keyring)) {
+	if (key_is_positive(keyring)) {
 		if (keyring->keys.nr_leaves_on_tree != 0)
 			seq_printf(m, ": %lu", keyring->keys.nr_leaves_on_tree);
 		else
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4089ce1f7757 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = key_read_state(key);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state != KEY_IS_UNINSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..740affd65ee9 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -730,7 +730,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 
 	ret = -EIO;
 	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+	    key_read_state(key) = KEY_IS_UNINSTANTIATED)
 		goto invalid_key;
 
 	/* check the permissions */
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..e8036cd0ad54 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 6ebf1af8fce9..424e1d90412e 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -73,7 +73,7 @@ static void request_key_auth_describe(const struct key *key,
 
 	seq_puts(m, "key:");
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len);
 }
 
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..9f558bedba23 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_positive(key))
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;
@@ -162,7 +162,7 @@ EXPORT_SYMBOL_GPL(user_destroy);
 void user_describe(const struct key *key, struct seq_file *m)
 {
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, ": %u", key->datalen);
 }
 

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-16 22:27         ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-16 22:27 UTC (permalink / raw)
  To: linux-security-module

Okay, I've fixed those issues, I think.  I've renamed the instantiation labels
to positive.

Thanks,
David
---
commit f23f3bb0ba3be44e775ac74148157136b919e3b0
Author: David Howells <dhowells@redhat.com>
Date:   Wed Oct 4 16:43:25 2017 +0100

    KEYS: Fix race between updating and finding a negative key
    
    Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
    error into one field such that:
    
     (1) The instantiation state can be modified/read atomically.
    
     (2) The error can be accessed atomically with the state.
    
     (3) The error isn't stored unioned with the payload pointers.
    
    This deals with the problem that the state is spread over three different
    objects (two bits and a separate variable) and reading or updating them
    atomically isn't practical, given that not only can uninstantiated keys
    change into instantiated or rejected keys, but rejected keys can also turn
    into instantiated keys - and someone accessing the key might not be using
    any locking.
    
    The main side effect of this problem is that what was held in the payload
    may change, depending on the state.  For instance, you might observe the
    key to be in the rejected state.  You then read the cached error, but if
    the key semaphore wasn't locked, the key might've become instantiated
    between the two reads - and you might now have something in hand that isn't
    actually an error code.
    
    The state is now KEY_IS_UNINSTANTIATED, KEY_IS_POSITIVE or a negative error
    code if the key is negatively instantiated.  The key_is_instantiated()
    function is replaced with key_is_positive() to avoid confusion as negative
    keys are also 'instantiated'.
    
    Additionally, barriering is included:
    
     (1) Order payload-set before state-set during instantiation.
    
     (2) Order state-read before payload-read when using the key.
    
    Further separate barriering is necessary if RCU is being used to access the
    payload content after reading the payload pointers.
    
    Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
    Cc: stable at vger.kernel.org # v4.4+
    Reported-by: Eric Biggers <ebiggers@google.com>
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..8a15cabe928d 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_POSITIVE,		/* Positively instantiated */
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -353,17 +356,27 @@ extern void key_set_timeout(struct key *, unsigned);
 #define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
 #define	KEY_NEED_ALL	0x3f	/* All the above permissions */
 
+static inline short key_read_state(const struct key *key)
+{
+	/* Barrier versus mark_key_instantiated(). */
+	return smp_load_acquire(&key->state);
+}
+
 /**
- * key_is_instantiated - Determine if a key has been positively instantiated
+ * key_is_positive - Determine if a key has been positively instantiated
  * @key: The key to check.
  *
  * Return true if the specified key has been positively instantiated, false
  * otherwise.
  */
-static inline bool key_is_instantiated(const struct key *key)
+static inline bool key_is_positive(const struct key *key)
+{
+	return key_read_state(key) == KEY_IS_POSITIVE;
+}
+
+static inline bool key_is_negative(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key_read_state(key) < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..e1d4d898a007 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -224,7 +224,7 @@ static int dns_resolver_match_preparse(struct key_match_data *match_data)
 static void dns_resolver_describe(const struct key *key, struct seq_file *m)
 {
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key)) {
+	if (key_is_positive(key)) {
 		int err = PTR_ERR(key->payload.data[dns_key_error]);
 
 		if (err)
diff --git a/security/keys/big_key.c b/security/keys/big_key.c
index e607830b6154..929e14978c42 100644
--- a/security/keys/big_key.c
+++ b/security/keys/big_key.c
@@ -247,7 +247,7 @@ void big_key_revoke(struct key *key)
 
 	/* clear the quota */
 	key_payload_reserve(key, 0);
-	if (key_is_instantiated(key) &&
+	if (key_is_positive(key) &&
 	    (size_t)key->payload.data[big_key_len] > BIG_KEY_FILE_THRESHOLD)
 		vfs_truncate(path, 0);
 }
@@ -279,7 +279,7 @@ void big_key_describe(const struct key *key, struct seq_file *m)
 
 	seq_puts(m, key->description);
 
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, ": %zu [%s]",
 			   datalen,
 			   datalen > BIG_KEY_FILE_THRESHOLD ? "file" : "buff");
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..f01d48cb3de1 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,15 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key =
 			list_entry(keys->next, struct key, graveyard_link);
+		short state = key->state;
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
-		    key->type->destroy)
+		if (state == KEY_IS_POSITIVE && key->type->destroy)
 			key->type->destroy(key);
 
 		security_key_free(key);
@@ -151,7 +151,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state != KEY_IS_UNINSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..9385e7cc710f 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,18 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	/* Commit the payload before setting the state; barrier versus
+	 * key_read_state().
+	 */
+	smp_store_release(&key->state,
+			  (reject_error < 0) ? reject_error : KEY_IS_POSITIVE);
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +436,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret == 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +589,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +761,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +995,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..76d22f726ae4 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state != KEY_IS_UNINSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..06173b091a74 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -414,7 +414,7 @@ static void keyring_describe(const struct key *keyring, struct seq_file *m)
 	else
 		seq_puts(m, "[anon]");
 
-	if (key_is_instantiated(keyring)) {
+	if (key_is_positive(keyring)) {
 		if (keyring->keys.nr_leaves_on_tree != 0)
 			seq_printf(m, ": %lu", keyring->keys.nr_leaves_on_tree);
 		else
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4089ce1f7757 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = key_read_state(key);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state != KEY_IS_UNINSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..740affd65ee9 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -730,7 +730,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 
 	ret = -EIO;
 	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+	    key_read_state(key) == KEY_IS_UNINSTANTIATED)
 		goto invalid_key;
 
 	/* check the permissions */
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..e8036cd0ad54 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 6ebf1af8fce9..424e1d90412e 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -73,7 +73,7 @@ static void request_key_auth_describe(const struct key *key,
 
 	seq_puts(m, "key:");
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len);
 }
 
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..9f558bedba23 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_positive(key))
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;
@@ -162,7 +162,7 @@ EXPORT_SYMBOL_GPL(user_destroy);
 void user_describe(const struct key *key, struct seq_file *m)
 {
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, ": %u", key->datalen);
 }
 
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-16 22:27         ` David Howells
  0 siblings, 0 replies; 72+ messages in thread
From: David Howells @ 2017-10-16 22:27 UTC (permalink / raw)
  To: Eric Biggers
  Cc: dhowells, ebiggers, linux-security-module, keyrings, linux-kernel

Okay, I've fixed those issues, I think.  I've renamed the instantiation labels
to positive.

Thanks,
David
---
commit f23f3bb0ba3be44e775ac74148157136b919e3b0
Author: David Howells <dhowells@redhat.com>
Date:   Wed Oct 4 16:43:25 2017 +0100

    KEYS: Fix race between updating and finding a negative key
    
    Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
    error into one field such that:
    
     (1) The instantiation state can be modified/read atomically.
    
     (2) The error can be accessed atomically with the state.
    
     (3) The error isn't stored unioned with the payload pointers.
    
    This deals with the problem that the state is spread over three different
    objects (two bits and a separate variable) and reading or updating them
    atomically isn't practical, given that not only can uninstantiated keys
    change into instantiated or rejected keys, but rejected keys can also turn
    into instantiated keys - and someone accessing the key might not be using
    any locking.
    
    The main side effect of this problem is that what was held in the payload
    may change, depending on the state.  For instance, you might observe the
    key to be in the rejected state.  You then read the cached error, but if
    the key semaphore wasn't locked, the key might've become instantiated
    between the two reads - and you might now have something in hand that isn't
    actually an error code.
    
    The state is now KEY_IS_UNINSTANTIATED, KEY_IS_POSITIVE or a negative error
    code if the key is negatively instantiated.  The key_is_instantiated()
    function is replaced with key_is_positive() to avoid confusion as negative
    keys are also 'instantiated'.
    
    Additionally, barriering is included:
    
     (1) Order payload-set before state-set during instantiation.
    
     (2) Order state-read before payload-read when using the key.
    
    Further separate barriering is necessary if RCU is being used to access the
    payload content after reading the payload pointers.
    
    Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
    Cc: stable@vger.kernel.org # v4.4+
    Reported-by: Eric Biggers <ebiggers@google.com>
    Signed-off-by: David Howells <dhowells@redhat.com>

diff --git a/include/linux/key.h b/include/linux/key.h
index e315e16b6ff8..8a15cabe928d 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -138,6 +138,11 @@ struct key_restriction {
 	struct key_type *keytype;
 };
 
+enum key_state {
+	KEY_IS_UNINSTANTIATED,
+	KEY_IS_POSITIVE,		/* Positively instantiated */
+};
+
 /*****************************************************************************/
 /*
  * authentication token / access credential / keyring
@@ -169,6 +174,7 @@ struct key {
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
+	short			state;		/* Key state (+) or rejection error (-) */
 
 #ifdef KEY_DEBUGGING
 	unsigned		magic;
@@ -176,18 +182,16 @@ struct key {
 #endif
 
 	unsigned long		flags;		/* status flags (change with bitops) */
-#define KEY_FLAG_INSTANTIATED	0	/* set if key has been instantiated */
-#define KEY_FLAG_DEAD		1	/* set if key type has been deleted */
-#define KEY_FLAG_REVOKED	2	/* set if key had been revoked */
-#define KEY_FLAG_IN_QUOTA	3	/* set if key consumes quota */
-#define KEY_FLAG_USER_CONSTRUCT	4	/* set if key is being constructed in userspace */
-#define KEY_FLAG_NEGATIVE	5	/* set if key is negative */
-#define KEY_FLAG_ROOT_CAN_CLEAR	6	/* set if key can be cleared by root without permission */
-#define KEY_FLAG_INVALIDATED	7	/* set if key has been invalidated */
-#define KEY_FLAG_BUILTIN	8	/* set if key is built in to the kernel */
-#define KEY_FLAG_ROOT_CAN_INVAL	9	/* set if key can be invalidated by root without permission */
-#define KEY_FLAG_KEEP		10	/* set if key should not be removed */
-#define KEY_FLAG_UID_KEYRING	11	/* set if key is a user or user session keyring */
+#define KEY_FLAG_DEAD		0	/* set if key type has been deleted */
+#define KEY_FLAG_REVOKED	1	/* set if key had been revoked */
+#define KEY_FLAG_IN_QUOTA	2	/* set if key consumes quota */
+#define KEY_FLAG_USER_CONSTRUCT	3	/* set if key is being constructed in userspace */
+#define KEY_FLAG_ROOT_CAN_CLEAR	4	/* set if key can be cleared by root without permission */
+#define KEY_FLAG_INVALIDATED	5	/* set if key has been invalidated */
+#define KEY_FLAG_BUILTIN	6	/* set if key is built in to the kernel */
+#define KEY_FLAG_ROOT_CAN_INVAL	7	/* set if key can be invalidated by root without permission */
+#define KEY_FLAG_KEEP		8	/* set if key should not be removed */
+#define KEY_FLAG_UID_KEYRING	9	/* set if key is a user or user session keyring */
 
 	/* the key type and key description string
 	 * - the desc is used to match a key against search criteria
@@ -213,7 +217,6 @@ struct key {
 			struct list_head name_link;
 			struct assoc_array keys;
 		};
-		int reject_error;
 	};
 
 	/* This is set on a keyring to restrict the addition of a link to a key
@@ -353,17 +356,27 @@ extern void key_set_timeout(struct key *, unsigned);
 #define	KEY_NEED_SETATTR 0x20	/* Require permission to change attributes */
 #define	KEY_NEED_ALL	0x3f	/* All the above permissions */
 
+static inline short key_read_state(const struct key *key)
+{
+	/* Barrier versus mark_key_instantiated(). */
+	return smp_load_acquire(&key->state);
+}
+
 /**
- * key_is_instantiated - Determine if a key has been positively instantiated
+ * key_is_positive - Determine if a key has been positively instantiated
  * @key: The key to check.
  *
  * Return true if the specified key has been positively instantiated, false
  * otherwise.
  */
-static inline bool key_is_instantiated(const struct key *key)
+static inline bool key_is_positive(const struct key *key)
+{
+	return key_read_state(key) == KEY_IS_POSITIVE;
+}
+
+static inline bool key_is_negative(const struct key *key)
 {
-	return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		!test_bit(KEY_FLAG_NEGATIVE, &key->flags);
+	return key_read_state(key) < 0;
 }
 
 #define dereference_key_rcu(KEY)					\
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 8737412c7b27..e1d4d898a007 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -224,7 +224,7 @@ static int dns_resolver_match_preparse(struct key_match_data *match_data)
 static void dns_resolver_describe(const struct key *key, struct seq_file *m)
 {
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key)) {
+	if (key_is_positive(key)) {
 		int err = PTR_ERR(key->payload.data[dns_key_error]);
 
 		if (err)
diff --git a/security/keys/big_key.c b/security/keys/big_key.c
index e607830b6154..929e14978c42 100644
--- a/security/keys/big_key.c
+++ b/security/keys/big_key.c
@@ -247,7 +247,7 @@ void big_key_revoke(struct key *key)
 
 	/* clear the quota */
 	key_payload_reserve(key, 0);
-	if (key_is_instantiated(key) &&
+	if (key_is_positive(key) &&
 	    (size_t)key->payload.data[big_key_len] > BIG_KEY_FILE_THRESHOLD)
 		vfs_truncate(path, 0);
 }
@@ -279,7 +279,7 @@ void big_key_describe(const struct key *key, struct seq_file *m)
 
 	seq_puts(m, key->description);
 
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, ": %zu [%s]",
 			   datalen,
 			   datalen > BIG_KEY_FILE_THRESHOLD ? "file" : "buff");
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 535db141f4da..d92cbf9687c3 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -854,7 +854,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep)
 	size_t datalen = prep->datalen;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	if (datalen <= 0 || datalen > 32767 || !prep->data)
 		return -EINVAL;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 87cb260e4890..f01d48cb3de1 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -129,15 +129,15 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 	while (!list_empty(keys)) {
 		struct key *key =
 			list_entry(keys->next, struct key, graveyard_link);
+		short state = key->state;
+
 		list_del(&key->graveyard_link);
 
 		kdebug("- %u", key->serial);
 		key_check(key);
 
 		/* Throw away the key data if the key is instantiated */
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
-		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
-		    key->type->destroy)
+		if (state == KEY_IS_POSITIVE && key->type->destroy)
 			key->type->destroy(key);
 
 		security_key_free(key);
@@ -151,7 +151,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 		}
 
 		atomic_dec(&key->user->nkeys);
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+		if (state != KEY_IS_UNINSTANTIATED)
 			atomic_dec(&key->user->nikeys);
 
 		key_user_put(key->user);
diff --git a/security/keys/key.c b/security/keys/key.c
index eb914a838840..9385e7cc710f 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -402,6 +402,18 @@ int key_payload_reserve(struct key *key, size_t datalen)
 EXPORT_SYMBOL(key_payload_reserve);
 
 /*
+ * Change the key state to being instantiated.
+ */
+static void mark_key_instantiated(struct key *key, int reject_error)
+{
+	/* Commit the payload before setting the state; barrier versus
+	 * key_read_state().
+	 */
+	smp_store_release(&key->state,
+			  (reject_error < 0) ? reject_error : KEY_IS_POSITIVE);
+}
+
+/*
  * Instantiate a key and link it into the target keyring atomically.  Must be
  * called with the target keyring's semaphore writelocked.  The target key's
  * semaphore need not be locked as instantiation is serialised by
@@ -424,14 +436,14 @@ static int __key_instantiate_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* instantiate the key */
 		ret = key->type->instantiate(key, prep);
 
 		if (ret == 0) {
 			/* mark the key as being instantiated */
 			atomic_inc(&key->user->nikeys);
-			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+			mark_key_instantiated(key, 0);
 
 			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 				awaken = 1;
@@ -577,13 +589,10 @@ int key_reject_and_link(struct key *key,
 	mutex_lock(&key_construction_mutex);
 
 	/* can't instantiate twice */
-	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+	if (key->state == KEY_IS_UNINSTANTIATED) {
 		/* mark the key as being negatively instantiated */
 		atomic_inc(&key->user->nikeys);
-		key->reject_error = -error;
-		smp_wmb();
-		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
-		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
+		mark_key_instantiated(key, -error);
 		now = current_kernel_time();
 		key->expiry = now.tv_sec + timeout;
 		key_schedule_gc(key->expiry + key_gc_delay);
@@ -752,8 +761,8 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 
 	ret = key->type->update(key, prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
@@ -986,8 +995,8 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 
 	ret = key->type->update(key, &prep);
 	if (ret == 0)
-		/* updating a negative key instantiates it */
-		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
+		/* Updating a negative key positively instantiates it */
+		mark_key_instantiated(key, 0);
 
 	up_write(&key->sem);
 
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 365ff85d7e27..76d22f726ae4 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -766,10 +766,9 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
 
 	key = key_ref_to_ptr(key_ref);
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		ret = -ENOKEY;
-		goto error2;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		goto error2; /* Negatively instantiated */
 
 	/* see if we can read it directly */
 	ret = key_permission(key_ref, KEY_NEED_READ);
@@ -901,7 +900,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 		atomic_dec(&key->user->nkeys);
 		atomic_inc(&newowner->nkeys);
 
-		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+		if (key->state != KEY_IS_UNINSTANTIATED) {
 			atomic_dec(&key->user->nikeys);
 			atomic_inc(&newowner->nikeys);
 		}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 4fa82a8a9c0e..06173b091a74 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -414,7 +414,7 @@ static void keyring_describe(const struct key *keyring, struct seq_file *m)
 	else
 		seq_puts(m, "[anon]");
 
-	if (key_is_instantiated(keyring)) {
+	if (key_is_positive(keyring)) {
 		if (keyring->keys.nr_leaves_on_tree != 0)
 			seq_printf(m, ": %lu", keyring->keys.nr_leaves_on_tree);
 		else
@@ -553,7 +553,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 {
 	struct keyring_search_context *ctx = iterator_data;
 	const struct key *key = keyring_ptr_to_key(object);
-	unsigned long kflags = key->flags;
+	unsigned long kflags = READ_ONCE(key->flags);
+	short state = READ_ONCE(key->state);
 
 	kenter("{%d}", key->serial);
 
@@ -597,9 +598,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
 
 	if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) {
 		/* we set a different error code if we pass a negative key */
-		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
-			smp_rmb();
-			ctx->result = ERR_PTR(key->reject_error);
+		if (state < 0) {
+			ctx->result = ERR_PTR(state);
 			kleave(" = %d [neg]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/proc.c b/security/keys/proc.c
index de834309d100..4089ce1f7757 100644
--- a/security/keys/proc.c
+++ b/security/keys/proc.c
@@ -182,6 +182,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
 	unsigned long timo;
 	key_ref_t key_ref, skey_ref;
 	char xbuf[16];
+	short state;
 	int rc;
 
 	struct keyring_search_context ctx = {
@@ -236,17 +237,19 @@ static int proc_keys_show(struct seq_file *m, void *v)
 			sprintf(xbuf, "%luw", timo / (60*60*24*7));
 	}
 
+	state = key_read_state(key);
+
 #define showflag(KEY, LETTER, FLAG) \
 	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
 
 	seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
 		   key->serial,
-		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
+		   state != KEY_IS_UNINSTANTIATED ? 'I' : '-',
 		   showflag(key, 'R', KEY_FLAG_REVOKED),
 		   showflag(key, 'D', KEY_FLAG_DEAD),
 		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
 		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
-		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
+		   state < 0 ? 'N' : '-',
 		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
 		   refcount_read(&key->usage),
 		   xbuf,
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index 293d3598153b..740affd65ee9 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -730,7 +730,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
 
 	ret = -EIO;
 	if (!(lflags & KEY_LOOKUP_PARTIAL) &&
-	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+	    key_read_state(key) == KEY_IS_UNINSTANTIATED)
 		goto invalid_key;
 
 	/* check the permissions */
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 63e63a42db3c..e8036cd0ad54 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -595,10 +595,9 @@ int wait_for_key_construction(struct key *key, bool intr)
 			  intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
 	if (ret)
 		return -ERESTARTSYS;
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
-		smp_rmb();
-		return key->reject_error;
-	}
+	ret = key_read_state(key);
+	if (ret < 0)
+		return ret;
 	return key_validate(key);
 }
 EXPORT_SYMBOL(wait_for_key_construction);
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index 6ebf1af8fce9..424e1d90412e 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -73,7 +73,7 @@ static void request_key_auth_describe(const struct key *key,
 
 	seq_puts(m, "key:");
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len);
 }
 
diff --git a/security/keys/trusted.c b/security/keys/trusted.c
index ddfaebf60fc8..bd85315cbfeb 100644
--- a/security/keys/trusted.c
+++ b/security/keys/trusted.c
@@ -1066,7 +1066,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep)
 	char *datablob;
 	int ret = 0;
 
-	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_negative(key))
 		return -ENOKEY;
 	p = key->payload.data[0];
 	if (!p->migratable)
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 3d8c68eba516..9f558bedba23 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -114,7 +114,7 @@ int user_update(struct key *key, struct key_preparsed_payload *prep)
 
 	/* attach the new data, displacing the old */
 	key->expiry = prep->expiry;
-	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
+	if (key_is_positive(key))
 		zap = dereference_key_locked(key);
 	rcu_assign_keypointer(key, prep->payload.data[0]);
 	prep->payload.data[0] = NULL;
@@ -162,7 +162,7 @@ EXPORT_SYMBOL_GPL(user_destroy);
 void user_describe(const struct key *key, struct seq_file *m)
 {
 	seq_puts(m, key->description);
-	if (key_is_instantiated(key))
+	if (key_is_positive(key))
 		seq_printf(m, ": %u", key->datalen);
 }
 

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
  2017-10-16 22:27         ` David Howells
  (?)
@ 2017-10-17 17:52           ` Eric Biggers
  -1 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-17 17:52 UTC (permalink / raw)
  To: linux-security-module

On Mon, Oct 16, 2017 at 11:27:22PM +0100, David Howells wrote:
> Okay, I've fixed those issues, I think.  I've renamed the instantiation labels
> to positive.
> 
> Thanks,
> David
> ---
> commit f23f3bb0ba3be44e775ac74148157136b919e3b0
> Author: David Howells <dhowells@redhat.com>
> Date:   Wed Oct 4 16:43:25 2017 +0100
> 
>     KEYS: Fix race between updating and finding a negative key
>     
>     Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
>     error into one field such that:
>     
>      (1) The instantiation state can be modified/read atomically.
>     
>      (2) The error can be accessed atomically with the state.
>     
>      (3) The error isn't stored unioned with the payload pointers.
>     
>     This deals with the problem that the state is spread over three different
>     objects (two bits and a separate variable) and reading or updating them
>     atomically isn't practical, given that not only can uninstantiated keys
>     change into instantiated or rejected keys, but rejected keys can also turn
>     into instantiated keys - and someone accessing the key might not be using
>     any locking.
>     
>     The main side effect of this problem is that what was held in the payload
>     may change, depending on the state.  For instance, you might observe the
>     key to be in the rejected state.  You then read the cached error, but if
>     the key semaphore wasn't locked, the key might've become instantiated
>     between the two reads - and you might now have something in hand that isn't
>     actually an error code.
>     
>     The state is now KEY_IS_UNINSTANTIATED, KEY_IS_POSITIVE or a negative error
>     code if the key is negatively instantiated.  The key_is_instantiated()
>     function is replaced with key_is_positive() to avoid confusion as negative
>     keys are also 'instantiated'.
>     
>     Additionally, barriering is included:
>     
>      (1) Order payload-set before state-set during instantiation.
>     
>      (2) Order state-read before payload-read when using the key.
>     
>     Further separate barriering is necessary if RCU is being used to access the
>     payload content after reading the payload pointers.
>     
>     Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
>     Cc: stable@vger.kernel.org # v4.4+
>     Reported-by: Eric Biggers <ebiggers@google.com>
>     Signed-off-by: David Howells <dhowells@redhat.com>
> 

This looks good now; feel free to add

	Reviewed-by: Eric Biggers <ebiggers@google.com>

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-17 17:52           ` Eric Biggers
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-17 17:52 UTC (permalink / raw)
  To: linux-security-module

On Mon, Oct 16, 2017 at 11:27:22PM +0100, David Howells wrote:
> Okay, I've fixed those issues, I think.  I've renamed the instantiation labels
> to positive.
> 
> Thanks,
> David
> ---
> commit f23f3bb0ba3be44e775ac74148157136b919e3b0
> Author: David Howells <dhowells@redhat.com>
> Date:   Wed Oct 4 16:43:25 2017 +0100
> 
>     KEYS: Fix race between updating and finding a negative key
>     
>     Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
>     error into one field such that:
>     
>      (1) The instantiation state can be modified/read atomically.
>     
>      (2) The error can be accessed atomically with the state.
>     
>      (3) The error isn't stored unioned with the payload pointers.
>     
>     This deals with the problem that the state is spread over three different
>     objects (two bits and a separate variable) and reading or updating them
>     atomically isn't practical, given that not only can uninstantiated keys
>     change into instantiated or rejected keys, but rejected keys can also turn
>     into instantiated keys - and someone accessing the key might not be using
>     any locking.
>     
>     The main side effect of this problem is that what was held in the payload
>     may change, depending on the state.  For instance, you might observe the
>     key to be in the rejected state.  You then read the cached error, but if
>     the key semaphore wasn't locked, the key might've become instantiated
>     between the two reads - and you might now have something in hand that isn't
>     actually an error code.
>     
>     The state is now KEY_IS_UNINSTANTIATED, KEY_IS_POSITIVE or a negative error
>     code if the key is negatively instantiated.  The key_is_instantiated()
>     function is replaced with key_is_positive() to avoid confusion as negative
>     keys are also 'instantiated'.
>     
>     Additionally, barriering is included:
>     
>      (1) Order payload-set before state-set during instantiation.
>     
>      (2) Order state-read before payload-read when using the key.
>     
>     Further separate barriering is necessary if RCU is being used to access the
>     payload content after reading the payload pointers.
>     
>     Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
>     Cc: stable at vger.kernel.org # v4.4+
>     Reported-by: Eric Biggers <ebiggers@google.com>
>     Signed-off-by: David Howells <dhowells@redhat.com>
> 

This looks good now; feel free to add

	Reviewed-by: Eric Biggers <ebiggers@google.com>
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [RFC][PATCH 00/15] KEYS: Fixes
@ 2017-10-17 17:52           ` Eric Biggers
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Biggers @ 2017-10-17 17:52 UTC (permalink / raw)
  To: David Howells; +Cc: ebiggers, linux-security-module, keyrings, linux-kernel

On Mon, Oct 16, 2017 at 11:27:22PM +0100, David Howells wrote:
> Okay, I've fixed those issues, I think.  I've renamed the instantiation labels
> to positive.
> 
> Thanks,
> David
> ---
> commit f23f3bb0ba3be44e775ac74148157136b919e3b0
> Author: David Howells <dhowells@redhat.com>
> Date:   Wed Oct 4 16:43:25 2017 +0100
> 
>     KEYS: Fix race between updating and finding a negative key
>     
>     Consolidate KEY_FLAG_INSTANTIATED, KEY_FLAG_NEGATIVE and the rejection
>     error into one field such that:
>     
>      (1) The instantiation state can be modified/read atomically.
>     
>      (2) The error can be accessed atomically with the state.
>     
>      (3) The error isn't stored unioned with the payload pointers.
>     
>     This deals with the problem that the state is spread over three different
>     objects (two bits and a separate variable) and reading or updating them
>     atomically isn't practical, given that not only can uninstantiated keys
>     change into instantiated or rejected keys, but rejected keys can also turn
>     into instantiated keys - and someone accessing the key might not be using
>     any locking.
>     
>     The main side effect of this problem is that what was held in the payload
>     may change, depending on the state.  For instance, you might observe the
>     key to be in the rejected state.  You then read the cached error, but if
>     the key semaphore wasn't locked, the key might've become instantiated
>     between the two reads - and you might now have something in hand that isn't
>     actually an error code.
>     
>     The state is now KEY_IS_UNINSTANTIATED, KEY_IS_POSITIVE or a negative error
>     code if the key is negatively instantiated.  The key_is_instantiated()
>     function is replaced with key_is_positive() to avoid confusion as negative
>     keys are also 'instantiated'.
>     
>     Additionally, barriering is included:
>     
>      (1) Order payload-set before state-set during instantiation.
>     
>      (2) Order state-read before payload-read when using the key.
>     
>     Further separate barriering is necessary if RCU is being used to access the
>     payload content after reading the payload pointers.
>     
>     Fixes: 146aa8b1453b ("KEYS: Merge the type-specific data with the payload data")
>     Cc: stable@vger.kernel.org # v4.4+
>     Reported-by: Eric Biggers <ebiggers@google.com>
>     Signed-off-by: David Howells <dhowells@redhat.com>
> 

This looks good now; feel free to add

	Reviewed-by: Eric Biggers <ebiggers@google.com>

^ permalink raw reply	[flat|nested] 72+ messages in thread

end of thread, other threads:[~2017-10-17 17:52 UTC | newest]

Thread overview: 72+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-10-12 16:17 [RFC][PATCH 00/15] KEYS: Fixes David Howells
2017-10-12 16:17 ` David Howells
2017-10-12 16:17 ` David Howells
2017-10-12 16:26 ` David Howells
2017-10-12 16:26   ` David Howells
2017-10-12 16:26   ` David Howells
2017-10-12 18:56 ` Eric Biggers
2017-10-12 18:56   ` Eric Biggers
2017-10-12 18:56   ` Eric Biggers
2017-10-13 15:39   ` David Howells
2017-10-13 15:39     ` David Howells
2017-10-13 15:39     ` David Howells
2017-10-16 18:31     ` Eric Biggers
2017-10-16 18:31       ` Eric Biggers
2017-10-16 18:31       ` Eric Biggers
2017-10-16 22:09       ` David Howells
2017-10-16 22:09         ` David Howells
2017-10-16 22:09         ` David Howells
2017-10-16 22:27       ` David Howells
2017-10-16 22:27         ` David Howells
2017-10-16 22:27         ` David Howells
2017-10-17 17:52         ` Eric Biggers
2017-10-17 17:52           ` Eric Biggers
2017-10-17 17:52           ` Eric Biggers
2017-10-12 20:15 ` David Howells
2017-10-12 20:15   ` David Howells
2017-10-12 20:15   ` David Howells
2017-10-12 20:16   ` [PATCH 01/15] KEYS: encrypted: fix dereference of NULL user_key_payload David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16   ` [PATCH 02/15] FS-Cache: " David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16   ` [PATCH 03/15] lib/digsig: " David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16   ` [PATCH 04/15] fscrypt: " David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16   ` [PATCH 05/15] ecryptfs: " David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16   ` [PATCH 06/15] ecryptfs: fix out-of-bounds read of key payload David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16   ` [PATCH 07/15] ecryptfs: move key payload accessor functions into keystore.c David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16   ` [PATCH 08/15] security/keys: BIG_KEY requires CONFIG_CRYPTO David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:16     ` David Howells
2017-10-12 20:17   ` [PATCH 09/15] KEYS: Fix the wrong index when checking the existence of second id David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17   ` [PATCH 10/15] KEYS: checking the input id parameters before finding asymmetric key David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17   ` [PATCH 11/15] KEYS: Fix race between updating and finding a negative key David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17   ` [PATCH 12/15] KEYS: don't let add_key() update an uninstantiated key David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17   ` [PATCH 13/15] KEYS: load key flags and expiry time atomically in key_validate() David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17   ` [PATCH 14/15] KEYS: load key flags and expiry time atomically in keyring_search_iterator() David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17   ` [PATCH 15/15] KEYS: load key flags and expiry time atomically in proc_keys_show() David Howells
2017-10-12 20:17     ` David Howells
2017-10-12 20:17     ` David Howells

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.