CVE-2017-15951 (C, published 2017-10-28T02:29Z)
Description: value: The KEYS subsystem in the Linux kernel before 4.13.10 does not correctly synchronize the actions of updating versus finding a key in the "negative" state to avoid a race condition, which allows local users to cause a denial of service or possibly have unspecified other impact via crafted system calls.
Severity: HIGH (CVSS v3 base score: 7.8)
Weakness: CWE-20 \u2014 Improper Input Validation
The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Repository: https://github.com/torvalds/linux
Files changed:
include/linux/key.h
net/dns_resolver/dns_key.c
security/keys/big_key.c
security/keys/encrypted-keys/encrypted.c
security/keys/gc.c
security/keys/key.c
security/keys/keyctl.c
security/keys/keyring.c
security/keys/proc.c
security/keys/process_keys.c
security/keys/request_key.c
security/keys/request_key_auth.c
security/keys/trusted.c
security/keys/user_defined.c
Commit message: 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>
Reviewed-by: Eric Biggers <ebiggers@google.com>


Vulnerable code:
#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 */
		int reject_error;
 * key_is_instantiated - Determine if a key has been positively instantiated
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);
	if (key_is_instantiated(key)) {
	if (key_is_instantiated(key) &&
	if (key_is_instantiated(key))
	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) &&
		    !test_bit(KEY_FLAG_NEGATIVE, &key->flags) &&
		    key->type->destroy)
		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
			set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
	if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
		key->reject_error = -error;
		smp_wmb();
		set_bit(KEY_FLAG_NEGATIVE, &key->flags);
		set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
		/* updating a negative key instantiates it */
		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
		/* updating a negative key instantiates it */
		clear_bit(KEY_FLAG_NEGATIVE, &key->flags);
	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
		ret = -ENOKEY;
		goto error2;
	}
		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
	if (key_is_instantiated(keyring)) {
	unsigned long kflags = key->flags;
		if (kflags & (1 << KEY_FLAG_NEGATIVE)) {
			smp_rmb();
			ctx->result = ERR_PTR(key->reject_error);
		   showflag(key, 'I', KEY_FLAG_INSTANTIATED),
		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
	    !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
		smp_rmb();
		return key->reject_error;
	}
	if (key_is_instantiated(key))
	if (test_bit(KEY_FLAG_NEGATIVE, &key->flags))
	if (!test_bit(KEY_FLAG_NEGATIVE, &key->flags))
	if (key_is_instantiated(key))

Fixed code:
enum key_state {
	KEY_IS_UNINSTANTIATED,
	KEY_IS_POSITIVE,		/* Positively instantiated */
};

	short			state;		/* Key state (+) or rejection error (-) */
#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 */
static inline short key_read_state(const struct key *key)
{
	/* Barrier versus mark_key_instantiated(). */
	return smp_load_acquire(&key->state);
}

 * key_is_positive - Determine if a key has been positively instantiated
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 key_read_state(key) < 0;
	if (key_is_positive(key)) {
	if (key_is_positive(key) &&
	if (key_is_positive(key))
	if (key_is_negative(key))
		short state = key->state;

		if (state == KEY_IS_POSITIVE && key->type->destroy)
		if (state != KEY_IS_UNINSTANTIATED)
/*
 * 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);
}

	if (key->state == KEY_IS_UNINSTANTIATED) {
			mark_key_instantiated(key, 0);
	if (key->state == KEY_IS_UNINSTANTIATED) {
		mark_key_instantiated(key, -error);
		/* Updating a negative key positively instantiates it */
		mark_key_instantiated(key, 0);
		/* Updating a negative key positively instantiates it */
		mark_key_instantiated(key, 0);
	ret = key_read_state(key);
	if (ret < 0)
		goto error2; /* Negatively instantiated */
		if (key->state != KEY_IS_UNINSTANTIATED) {
	if (key_is_positive(keyring)) {
	unsigned long kflags = READ_ONCE(key->flags);
	short state = READ_ONCE(key->state);
		if (state < 0) {
			ctx->result = ERR_PTR(state);
	short state;
	state = key_read_state(key);

		   state != KEY_IS_UNINSTANTIATED ? 'I' : '-',
		   state < 0 ? 'N' : '-',
	    key_read_state(key) == KEY_IS_UNINSTANTIATED)
	ret = key_read_state(key);
	if (ret < 0)
		return ret;
	if (key_is_positive(key))
	if (key_is_negative(key))
	if (key_is_positive(key))
	if (key_is_positive(key))