All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eric Biggers <ebiggers3@gmail.com>
To: linux-security-module@vger.kernel.org
Subject: Re: [PATCH] KEYS: prevent KEYCTL_READ on negative key
Date: Mon, 25 Sep 2017 18:35:38 +0000	[thread overview]
Message-ID: <20170925183538.GB51000@gmail.com> (raw)
In-Reply-To: <28720.1506346196@warthog.procyon.org.uk>

On Mon, Sep 25, 2017 at 02:29:56PM +0100, David Howells wrote:
> Eric Biggers <ebiggers3@gmail.com> wrote:
> 
> > Putting the check in key_validate() would make lookups with
> > KEY_LOOKUP_PARTIAL stop returning negative keys, which would break
> > keyctl_describe(), keyctl_chown(), keyctl_setperm(), keyctl_set_timeout(),
> > keyctl_get_security() on negative keys.  I presume those are supposed to
> > work?
> 
> Lookups with KEY_LOOKUP_PARTIAL should never return a negative key by
> definition.  A negative key is instantiated with an error code, so is no longer
> under construction.

Well, that's not what KEY_LOOKUP_PARTIAL actually does.  KEY_LOOKUP_PARTIAL
allows the returned key to be uninstantiated, negatively instantiated, *or*
positively instantiated; and the callers seem to rely on that.  Perhaps a better
name might have been KEY_LOOKUP_ALLOW_PARTIAL or KEY_LOOKUP_ALLOW_NONPOSITIVE.

On the other hand, without KEY_LOOKUP_PARTIAL, the returned key is required to
be positively instantiated.  However, there is a special case.  Namely, if no
permissions are requested, the returned key is allowed to be negative (as well
as revoked, invalidated, or expired --- though those can happen at any time
anyway until you do down_read(&key->sem)).  I'm questioning whether we need that
special case.

> 
> key_get_instantiation_authkey() must fail if the key has been constructed - but
> I guess there's a potential race in keyctl_describe_key(), keyctl_set_timeout()
> and keyctl_get_security() between getting the auth token and calling
> lookup_user_key() with perm of 0 in which the key could be instantiated,
> revoked, or instantiated elsewhere, or simply expire.  This would allow the
> instantiating process a longer access window - but they do/did have a valid
> token...

Yes, by the time key_get_instantiation_authkey() returns, the key may have
already been instantiated.  But I'm not too concerned about that, since the
caller must still have had a non-revoked authorization key shortly before.

> 
> It should still be possible to describe, chown, setperm and get the security on
> negative keys by the normal access mechanism.  Changing the timeout should
> probably be denied.
> 
> > Another solution would be to remove the special case from lookup_user_key()
> > where it can return a negative/revoked/invalidated/expired key if
> > KEY_LOOKUP_PARTIAL is not specified and the 'perm' mask is 0.
> 
> There are a number of circumstances in which it lookup_user_key() is called
> with perm=0, and in each case, the caller is responsible for handling the
> security:
> 
>  (1) keyctl_invalidate_key() will do so if the caller doesn't have permission,
>      but CAP_SYS_ADMIN is set and the key is marked KEY_FLAG_ROOT_CAN_INVAL.
> 
>  (2) keyctl_keyring_clear() will do so if the caller doesn't have permission,
>      but CAP_SYS_ADMIN is set and the key is marked KEY_FLAG_ROOT_CAN_CLEAR.
> 
>  (3) keyctl_keyring_unlink() will do so on the key-to-be-removed since only the
>      keyring needs a perm check.
> 
>  (4) keyctl_read_key() always does so and then does the READ perm check and the
>      possessor-can-SEARCH can search check itself.
> 
>  (5) keyctl_describe_key(), keyctl_set_timeout() and keyctl_get_security() will
>      do so if the caller doesn't have permission, but does have a valid
>      authorisation token.  The latter requires that the key be under
>      construction.

It's not about the permission checks.  It's about whether a negative key is
allowed to be returned or not.  And I think overloading 'perm' for that is not
really appropriate, and the cause of the bug in keyctl_read_key().  See the
code, it ignores the return value of wait_for_key_construction() if 'perm' is 0:

        if (!(lflags & KEY_LOOKUP_PARTIAL)) {
                ret = wait_for_key_construction(key, true);
                switch (ret) {
                case -ERESTARTSYS:
                        goto invalid_key;
                default:
                        if (perm)
                                goto invalid_key;
                case 0:
                        break;
                }
	}

> 
> Functions that use KEY_LOOKUP_PARTIAL include:
> 
>  keyctl_describe_key()
>  keyctl_chown_key()
>  keyctl_setperm_key()
>  keyctl_set_timeout()
>  keyctl_get_security()
> 
> all of which might need to be called from the upcall program.  None of these
> should look at the payload.
> 
> > The only callers it would affect are the case in question here which is
> > clearly a bug,
> 
> keyctl_read_key() is definitely buggy.  Actually, rather than manually testing
> KEY_FLAG_NEGATIVE there, it should probably use key_validate().

It already does use key_validate().  But key_validate() is also used in
lookup_user_key(), where it is expected to accept a negative key.

The real problem seems to be that the permissions mask rather than the flags
argument is used to tell lookup_user_key() whether it can return a negative key.

> 
> > and the root-only exceptions for keyctl_invalidate() and
> > keyctl_clear().  And I suspect the latter two are unintentional as well.
> 
> I'm not sure what you think is unintentional.
> 

That root is allowed to invalidate or clear a
negative/invalidated/revoked/expired key or keyring but regular users cannot.

Again, the problem seems to be that the 'perm' argument is used for more than
just the permission check.  I think the 'lflags' argument should indicate what
state the key is allowed to be in, not 'perm'.

> > (Is root *supposed* to be able to invalidate a
> > negative/revoked/invalidated/expired key, or clear a
> > revoked/invalidated/expired keyring?)
> 
> You should be able to invalidate or unlink negative, revoked or expired keys if
> you have permission to do so.  If you're using keyrings to cache stuff, you
> need to be able to invalidate negative results as well as positive ones.
> 
> Invalidation of an invalidated key doesn't really make sense, but it shouldn't
> hurt.  I can't immediately automatically remove all links to the invalidated
> key, but have to leave it to the garbage collector to effect.
> 
> As for clearing of revoked/invalidated/expired keyrings, I'm not sure whether
> it makes sense to allow it - however, whilst keyrings are cleared upon
> revocation (since we have a definite point to do that with the key sem
> writelocked), they aren't automatically cleared upon expiry or invalidation, so
> it might make sense to permit it still.
> 

Eric

WARNING: multiple messages have this Message-ID (diff)
From: ebiggers3@gmail.com (Eric Biggers)
To: linux-security-module@vger.kernel.org
Subject: [PATCH] KEYS: prevent KEYCTL_READ on negative key
Date: Mon, 25 Sep 2017 11:35:38 -0700	[thread overview]
Message-ID: <20170925183538.GB51000@gmail.com> (raw)
In-Reply-To: <28720.1506346196@warthog.procyon.org.uk>

On Mon, Sep 25, 2017 at 02:29:56PM +0100, David Howells wrote:
> Eric Biggers <ebiggers3@gmail.com> wrote:
> 
> > Putting the check in key_validate() would make lookups with
> > KEY_LOOKUP_PARTIAL stop returning negative keys, which would break
> > keyctl_describe(), keyctl_chown(), keyctl_setperm(), keyctl_set_timeout(),
> > keyctl_get_security() on negative keys.  I presume those are supposed to
> > work?
> 
> Lookups with KEY_LOOKUP_PARTIAL should never return a negative key by
> definition.  A negative key is instantiated with an error code, so is no longer
> under construction.

Well, that's not what KEY_LOOKUP_PARTIAL actually does.  KEY_LOOKUP_PARTIAL
allows the returned key to be uninstantiated, negatively instantiated, *or*
positively instantiated; and the callers seem to rely on that.  Perhaps a better
name might have been KEY_LOOKUP_ALLOW_PARTIAL or KEY_LOOKUP_ALLOW_NONPOSITIVE.

On the other hand, without KEY_LOOKUP_PARTIAL, the returned key is required to
be positively instantiated.  However, there is a special case.  Namely, if no
permissions are requested, the returned key is allowed to be negative (as well
as revoked, invalidated, or expired --- though those can happen at any time
anyway until you do down_read(&key->sem)).  I'm questioning whether we need that
special case.

> 
> key_get_instantiation_authkey() must fail if the key has been constructed - but
> I guess there's a potential race in keyctl_describe_key(), keyctl_set_timeout()
> and keyctl_get_security() between getting the auth token and calling
> lookup_user_key() with perm of 0 in which the key could be instantiated,
> revoked, or instantiated elsewhere, or simply expire.  This would allow the
> instantiating process a longer access window - but they do/did have a valid
> token...

Yes, by the time key_get_instantiation_authkey() returns, the key may have
already been instantiated.  But I'm not too concerned about that, since the
caller must still have had a non-revoked authorization key shortly before.

> 
> It should still be possible to describe, chown, setperm and get the security on
> negative keys by the normal access mechanism.  Changing the timeout should
> probably be denied.
> 
> > Another solution would be to remove the special case from lookup_user_key()
> > where it can return a negative/revoked/invalidated/expired key if
> > KEY_LOOKUP_PARTIAL is not specified and the 'perm' mask is 0.
> 
> There are a number of circumstances in which it lookup_user_key() is called
> with perm==0, and in each case, the caller is responsible for handling the
> security:
> 
>  (1) keyctl_invalidate_key() will do so if the caller doesn't have permission,
>      but CAP_SYS_ADMIN is set and the key is marked KEY_FLAG_ROOT_CAN_INVAL.
> 
>  (2) keyctl_keyring_clear() will do so if the caller doesn't have permission,
>      but CAP_SYS_ADMIN is set and the key is marked KEY_FLAG_ROOT_CAN_CLEAR.
> 
>  (3) keyctl_keyring_unlink() will do so on the key-to-be-removed since only the
>      keyring needs a perm check.
> 
>  (4) keyctl_read_key() always does so and then does the READ perm check and the
>      possessor-can-SEARCH can search check itself.
> 
>  (5) keyctl_describe_key(), keyctl_set_timeout() and keyctl_get_security() will
>      do so if the caller doesn't have permission, but does have a valid
>      authorisation token.  The latter requires that the key be under
>      construction.

It's not about the permission checks.  It's about whether a negative key is
allowed to be returned or not.  And I think overloading 'perm' for that is not
really appropriate, and the cause of the bug in keyctl_read_key().  See the
code, it ignores the return value of wait_for_key_construction() if 'perm' is 0:

        if (!(lflags & KEY_LOOKUP_PARTIAL)) {
                ret = wait_for_key_construction(key, true);
                switch (ret) {
                case -ERESTARTSYS:
                        goto invalid_key;
                default:
                        if (perm)
                                goto invalid_key;
                case 0:
                        break;
                }
	}

> 
> Functions that use KEY_LOOKUP_PARTIAL include:
> 
>  keyctl_describe_key()
>  keyctl_chown_key()
>  keyctl_setperm_key()
>  keyctl_set_timeout()
>  keyctl_get_security()
> 
> all of which might need to be called from the upcall program.  None of these
> should look at the payload.
> 
> > The only callers it would affect are the case in question here which is
> > clearly a bug,
> 
> keyctl_read_key() is definitely buggy.  Actually, rather than manually testing
> KEY_FLAG_NEGATIVE there, it should probably use key_validate().

It already does use key_validate().  But key_validate() is also used in
lookup_user_key(), where it is expected to accept a negative key.

The real problem seems to be that the permissions mask rather than the flags
argument is used to tell lookup_user_key() whether it can return a negative key.

> 
> > and the root-only exceptions for keyctl_invalidate() and
> > keyctl_clear().  And I suspect the latter two are unintentional as well.
> 
> I'm not sure what you think is unintentional.
> 

That root is allowed to invalidate or clear a
negative/invalidated/revoked/expired key or keyring but regular users cannot.

Again, the problem seems to be that the 'perm' argument is used for more than
just the permission check.  I think the 'lflags' argument should indicate what
state the key is allowed to be in, not 'perm'.

> > (Is root *supposed* to be able to invalidate a
> > negative/revoked/invalidated/expired key, or clear a
> > revoked/invalidated/expired keyring?)
> 
> You should be able to invalidate or unlink negative, revoked or expired keys if
> you have permission to do so.  If you're using keyrings to cache stuff, you
> need to be able to invalidate negative results as well as positive ones.
> 
> Invalidation of an invalidated key doesn't really make sense, but it shouldn't
> hurt.  I can't immediately automatically remove all links to the invalidated
> key, but have to leave it to the garbage collector to effect.
> 
> As for clearing of revoked/invalidated/expired keyrings, I'm not sure whether
> it makes sense to allow it - however, whilst keyrings are cleared upon
> revocation (since we have a definite point to do that with the key sem
> writelocked), they aren't automatically cleared upon expiry or invalidation, so
> it might make sense to permit it still.
> 

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

WARNING: multiple messages have this Message-ID (diff)
From: Eric Biggers <ebiggers3@gmail.com>
To: David Howells <dhowells@redhat.com>
Cc: keyrings@vger.kernel.org, Michael Halcrow <mhalcrow@google.com>,
	linux-security-module@vger.kernel.org,
	linux-kernel@vger.kernel.org, Eric Biggers <ebiggers@google.com>,
	stable@vger.kernel.org
Subject: Re: [PATCH] KEYS: prevent KEYCTL_READ on negative key
Date: Mon, 25 Sep 2017 11:35:38 -0700	[thread overview]
Message-ID: <20170925183538.GB51000@gmail.com> (raw)
In-Reply-To: <28720.1506346196@warthog.procyon.org.uk>

On Mon, Sep 25, 2017 at 02:29:56PM +0100, David Howells wrote:
> Eric Biggers <ebiggers3@gmail.com> wrote:
> 
> > Putting the check in key_validate() would make lookups with
> > KEY_LOOKUP_PARTIAL stop returning negative keys, which would break
> > keyctl_describe(), keyctl_chown(), keyctl_setperm(), keyctl_set_timeout(),
> > keyctl_get_security() on negative keys.  I presume those are supposed to
> > work?
> 
> Lookups with KEY_LOOKUP_PARTIAL should never return a negative key by
> definition.  A negative key is instantiated with an error code, so is no longer
> under construction.

Well, that's not what KEY_LOOKUP_PARTIAL actually does.  KEY_LOOKUP_PARTIAL
allows the returned key to be uninstantiated, negatively instantiated, *or*
positively instantiated; and the callers seem to rely on that.  Perhaps a better
name might have been KEY_LOOKUP_ALLOW_PARTIAL or KEY_LOOKUP_ALLOW_NONPOSITIVE.

On the other hand, without KEY_LOOKUP_PARTIAL, the returned key is required to
be positively instantiated.  However, there is a special case.  Namely, if no
permissions are requested, the returned key is allowed to be negative (as well
as revoked, invalidated, or expired --- though those can happen at any time
anyway until you do down_read(&key->sem)).  I'm questioning whether we need that
special case.

> 
> key_get_instantiation_authkey() must fail if the key has been constructed - but
> I guess there's a potential race in keyctl_describe_key(), keyctl_set_timeout()
> and keyctl_get_security() between getting the auth token and calling
> lookup_user_key() with perm of 0 in which the key could be instantiated,
> revoked, or instantiated elsewhere, or simply expire.  This would allow the
> instantiating process a longer access window - but they do/did have a valid
> token...

Yes, by the time key_get_instantiation_authkey() returns, the key may have
already been instantiated.  But I'm not too concerned about that, since the
caller must still have had a non-revoked authorization key shortly before.

> 
> It should still be possible to describe, chown, setperm and get the security on
> negative keys by the normal access mechanism.  Changing the timeout should
> probably be denied.
> 
> > Another solution would be to remove the special case from lookup_user_key()
> > where it can return a negative/revoked/invalidated/expired key if
> > KEY_LOOKUP_PARTIAL is not specified and the 'perm' mask is 0.
> 
> There are a number of circumstances in which it lookup_user_key() is called
> with perm==0, and in each case, the caller is responsible for handling the
> security:
> 
>  (1) keyctl_invalidate_key() will do so if the caller doesn't have permission,
>      but CAP_SYS_ADMIN is set and the key is marked KEY_FLAG_ROOT_CAN_INVAL.
> 
>  (2) keyctl_keyring_clear() will do so if the caller doesn't have permission,
>      but CAP_SYS_ADMIN is set and the key is marked KEY_FLAG_ROOT_CAN_CLEAR.
> 
>  (3) keyctl_keyring_unlink() will do so on the key-to-be-removed since only the
>      keyring needs a perm check.
> 
>  (4) keyctl_read_key() always does so and then does the READ perm check and the
>      possessor-can-SEARCH can search check itself.
> 
>  (5) keyctl_describe_key(), keyctl_set_timeout() and keyctl_get_security() will
>      do so if the caller doesn't have permission, but does have a valid
>      authorisation token.  The latter requires that the key be under
>      construction.

It's not about the permission checks.  It's about whether a negative key is
allowed to be returned or not.  And I think overloading 'perm' for that is not
really appropriate, and the cause of the bug in keyctl_read_key().  See the
code, it ignores the return value of wait_for_key_construction() if 'perm' is 0:

        if (!(lflags & KEY_LOOKUP_PARTIAL)) {
                ret = wait_for_key_construction(key, true);
                switch (ret) {
                case -ERESTARTSYS:
                        goto invalid_key;
                default:
                        if (perm)
                                goto invalid_key;
                case 0:
                        break;
                }
	}

> 
> Functions that use KEY_LOOKUP_PARTIAL include:
> 
>  keyctl_describe_key()
>  keyctl_chown_key()
>  keyctl_setperm_key()
>  keyctl_set_timeout()
>  keyctl_get_security()
> 
> all of which might need to be called from the upcall program.  None of these
> should look at the payload.
> 
> > The only callers it would affect are the case in question here which is
> > clearly a bug,
> 
> keyctl_read_key() is definitely buggy.  Actually, rather than manually testing
> KEY_FLAG_NEGATIVE there, it should probably use key_validate().

It already does use key_validate().  But key_validate() is also used in
lookup_user_key(), where it is expected to accept a negative key.

The real problem seems to be that the permissions mask rather than the flags
argument is used to tell lookup_user_key() whether it can return a negative key.

> 
> > and the root-only exceptions for keyctl_invalidate() and
> > keyctl_clear().  And I suspect the latter two are unintentional as well.
> 
> I'm not sure what you think is unintentional.
> 

That root is allowed to invalidate or clear a
negative/invalidated/revoked/expired key or keyring but regular users cannot.

Again, the problem seems to be that the 'perm' argument is used for more than
just the permission check.  I think the 'lflags' argument should indicate what
state the key is allowed to be in, not 'perm'.

> > (Is root *supposed* to be able to invalidate a
> > negative/revoked/invalidated/expired key, or clear a
> > revoked/invalidated/expired keyring?)
> 
> You should be able to invalidate or unlink negative, revoked or expired keys if
> you have permission to do so.  If you're using keyrings to cache stuff, you
> need to be able to invalidate negative results as well as positive ones.
> 
> Invalidation of an invalidated key doesn't really make sense, but it shouldn't
> hurt.  I can't immediately automatically remove all links to the invalidated
> key, but have to leave it to the garbage collector to effect.
> 
> As for clearing of revoked/invalidated/expired keyrings, I'm not sure whether
> it makes sense to allow it - however, whilst keyrings are cleared upon
> revocation (since we have a definite point to do that with the key sem
> writelocked), they aren't automatically cleared upon expiry or invalidation, so
> it might make sense to permit it still.
> 

Eric

  reply	other threads:[~2017-09-25 18:35 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-09-18 18:37 [PATCH] KEYS: prevent KEYCTL_READ on negative key Eric Biggers
2017-09-18 18:37 ` Eric Biggers
2017-09-18 18:37 ` Eric Biggers
2017-09-19 16:09 ` David Howells
2017-09-19 16:09   ` David Howells
2017-09-19 16:09   ` David Howells
2017-09-21 23:34   ` Eric Biggers
2017-09-21 23:34     ` Eric Biggers
2017-09-21 23:34     ` Eric Biggers
2017-09-25 13:29     ` David Howells
2017-09-25 13:29       ` David Howells
2017-09-25 13:29       ` David Howells
2017-09-25 18:35       ` Eric Biggers [this message]
2017-09-25 18:35         ` Eric Biggers
2017-09-25 18:35         ` Eric Biggers

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170925183538.GB51000@gmail.com \
    --to=ebiggers3@gmail.com \
    --cc=linux-security-module@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.