Re: [PATCH] [RFC] KEYS: Add invalidation support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, 2011-12-15 at 12:17 +0000, David Howells wrote:
> Add support for invalidating a key - which renders it immediately invisible to
> further searches and causes the garbage collector to immediately wake up,
> remove it from keyrings and then destroy it when it's no longer referenced.
> 
> Also add specific invalidation permission bits that grant possessor, user,
> group or other invalidation permissions without the requiring the grant of
> SETATTR or WRITE permission - either of which might provide too much access.
> 
> WRITE permission, for example, may allow the key's content to be changed and
> SETATTR would allow the permissions mask and ownership to be altered.
> 
> It's better not to do this with keyctl_revoke() as that marks the key to start
> returning -EKEYREVOKED to searches when what is actually desired is to have the
> key refetched.
> 
> The primary use for this is to evict keys that are cached in special keyrings,
> such as the DNS resolver or an ID mapper.
> 
> 
> Questions for consideration:
> 
> Do I actually need to add a new permission bit for this?  Or would it be
> sufficient to pick one or more of the following criteria?
> 
>  (1) The invalidator has SEARCH permission on the key.
> 
>  (2) The invalidator is the owner (uid match).
> 
>  (3) The invalidator has CAP_SYS_ADMIN.
> 
> Should all keys be invalidatable?  Or should it be possible to set a flag on a
> key (or key type) to say whether it can be invalidated?  Should this be
> alterable by userspace?  Should such a flag be set by request_key() but not
> add_key()?
> 
> Invalidation is basically an operation to eject a key from all keyrings - even
> ones that you can't 'write' - and cause the key to be 'refetched'.
> 
> To-be-Signed-off-by: David Howells <dhowells@xxxxxxxxxx>

Not all keys can be 'refetched'.  A trusted key, sealed to a PCR, can
extend the PCR to prevent it from being re-loaded. Removing the trusted
key could prevent the instantiation/update of encrypted keys.

Mimi

> ---
> 
>  include/linux/key.h        |   15 +++++++++++----
>  include/linux/keyctl.h     |    1 +
>  security/keys/compat.c     |    3 +++
>  security/keys/gc.c         |   23 ++++++++++++++---------
>  security/keys/internal.h   |   18 ++++++++++++++++--
>  security/keys/key.c        |   23 +++++++++++++++++++++++
>  security/keys/keyctl.c     |   34 ++++++++++++++++++++++++++++++++++
>  security/keys/keyring.c    |   25 +++++++++++--------------
>  security/keys/permission.c |   15 ++++++++++-----
>  security/keys/proc.c       |    3 ++-
>  10 files changed, 125 insertions(+), 35 deletions(-)
> 
> diff --git a/include/linux/key.h b/include/linux/key.h
> index 3ac4128..e22195f 100644
> --- a/include/linux/key.h
> +++ b/include/linux/key.h
> @@ -43,7 +43,8 @@ struct key;
>  #define KEY_POS_SEARCH	0x08000000	/* possessor can find a key in search / search a keyring */
>  #define KEY_POS_LINK	0x10000000	/* possessor can create a link to a key/keyring */
>  #define KEY_POS_SETATTR	0x20000000	/* possessor can set key attributes */
> -#define KEY_POS_ALL	0x3f000000
> +#define KEY_POS_INVAL	0x40000000	/* possessor can invalidate a key */
> +#define KEY_POS_ALL	0x7f000000
> 
>  #define KEY_USR_VIEW	0x00010000	/* user permissions... */
>  #define KEY_USR_READ	0x00020000
> @@ -51,7 +52,8 @@ struct key;
>  #define KEY_USR_SEARCH	0x00080000
>  #define KEY_USR_LINK	0x00100000
>  #define KEY_USR_SETATTR	0x00200000
> -#define KEY_USR_ALL	0x003f0000
> +#define KEY_USR_INVAL	0x00400000
> +#define KEY_USR_ALL	0x007f0000
> 
>  #define KEY_GRP_VIEW	0x00000100	/* group permissions... */
>  #define KEY_GRP_READ	0x00000200
> @@ -59,7 +61,8 @@ struct key;
>  #define KEY_GRP_SEARCH	0x00000800
>  #define KEY_GRP_LINK	0x00001000
>  #define KEY_GRP_SETATTR	0x00002000
> -#define KEY_GRP_ALL	0x00003f00
> +#define KEY_GRP_INVAL	0x00004000
> +#define KEY_GRP_ALL	0x00007f00
> 
>  #define KEY_OTH_VIEW	0x00000001	/* third party permissions... */
>  #define KEY_OTH_READ	0x00000002
> @@ -67,7 +70,8 @@ struct key;
>  #define KEY_OTH_SEARCH	0x00000008
>  #define KEY_OTH_LINK	0x00000010
>  #define KEY_OTH_SETATTR	0x00000020
> -#define KEY_OTH_ALL	0x0000003f
> +#define KEY_OTH_INVAL	0x00000040
> +#define KEY_OTH_ALL	0x0000007f
> 
>  #define KEY_PERM_UNDEF	0xffffffff
> 
> @@ -156,6 +160,7 @@ struct key {
>  #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 */
> 
>  	/* the description string
>  	 * - this is used to match a key against search criteria
> @@ -199,6 +204,7 @@ extern struct key *key_alloc(struct key_type *type,
>  #define KEY_ALLOC_NOT_IN_QUOTA	0x0002	/* not in quota */
> 
>  extern void key_revoke(struct key *key);
> +extern void key_invalidate(struct key *key);
>  extern void key_put(struct key *key);
> 
>  static inline struct key *key_get(struct key *key)
> @@ -314,6 +320,7 @@ extern void key_init(void);
>  #define key_serial(k)			0
>  #define key_get(k) 			({ NULL; })
>  #define key_revoke(k)			do { } while(0)
> +#define key_invalidate(k)		do { } while(0)
>  #define key_put(k)			do { } while(0)
>  #define key_ref_put(k)			do { } while(0)
>  #define make_key_ref(k, p)		NULL
> diff --git a/include/linux/keyctl.h b/include/linux/keyctl.h
> index 9b0b865..c9b7f4fa 100644
> --- a/include/linux/keyctl.h
> +++ b/include/linux/keyctl.h
> @@ -55,5 +55,6 @@
>  #define KEYCTL_SESSION_TO_PARENT	18	/* apply session keyring to parent process */
>  #define KEYCTL_REJECT			19	/* reject a partially constructed key */
>  #define KEYCTL_INSTANTIATE_IOV		20	/* instantiate a partially constructed key */
> +#define KEYCTL_INVALIDATE		21	/* invalidate a key */
> 
>  #endif /*  _LINUX_KEYCTL_H */
> diff --git a/security/keys/compat.c b/security/keys/compat.c
> index 4c48e13..fab4f8d 100644
> --- a/security/keys/compat.c
> +++ b/security/keys/compat.c
> @@ -135,6 +135,9 @@ asmlinkage long compat_sys_keyctl(u32 option,
>  		return compat_keyctl_instantiate_key_iov(
>  			arg2, compat_ptr(arg3), arg4, arg5);
> 
> +	case KEYCTL_INVALIDATE:
> +		return keyctl_invalidate_key(arg2);
> +
>  	default:
>  		return -EOPNOTSUPP;
>  	}
> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index bf4d8da..4c7c99e 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -72,6 +72,15 @@ void key_schedule_gc(time_t gc_at)
>  }
> 
>  /*
> + * Schedule a dead links collection run.
> + */
> +void key_schedule_gc_links(void)
> +{
> +	set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags);
> +	queue_work(system_nrt_wq, &key_gc_work);
> +}
> +
> +/*
>   * Some key's cleanup time was met after it expired, so we need to get the
>   * reaper to go through a cycle finding expired keys.
>   */
> @@ -79,8 +88,7 @@ static void key_gc_timer_func(unsigned long data)
>  {
>  	kenter("");
>  	key_gc_next_run = LONG_MAX;
> -	set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags);
> -	queue_work(system_nrt_wq, &key_gc_work);
> +	key_schedule_gc_links();
>  }
> 
>  /*
> @@ -131,12 +139,12 @@ void key_gc_keytype(struct key_type *ktype)
>  static void key_gc_keyring(struct key *keyring, time_t limit)
>  {
>  	struct keyring_list *klist;
> -	struct key *key;
>  	int loop;
> 
>  	kenter("%x", key_serial(keyring));
> 
> -	if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
> +	if (keyring->flags & ((1 << KEY_FLAG_INVALIDATED) |
> +			      (1 << KEY_FLAG_REVOKED)))
>  		goto dont_gc;
> 
>  	/* scan the keyring looking for dead keys */
> @@ -145,12 +153,9 @@ static void key_gc_keyring(struct key *keyring, time_t limit)
>  	if (!klist)
>  		goto unlock_dont_gc;
> 
> -	for (loop = klist->nkeys - 1; loop >= 0; loop--) {
> -		key = klist->keys[loop];
> -		if (test_bit(KEY_FLAG_DEAD, &key->flags) ||
> -		    (key->expiry > 0 && key->expiry <= limit))
> +	for (loop = klist->nkeys - 1; loop >= 0; loop--)
> +		if (key_is_dead(klist->keys[loop], limit))
>  			goto do_gc;
> -	}
> 
>  unlock_dont_gc:
>  	rcu_read_unlock();
> diff --git a/security/keys/internal.h b/security/keys/internal.h
> index c7a7cae..9c3c2d2 100644
> --- a/security/keys/internal.h
> +++ b/security/keys/internal.h
> @@ -151,7 +151,8 @@ extern long join_session_keyring(const char *name);
>  extern struct work_struct key_gc_work;
>  extern unsigned key_gc_delay;
>  extern void keyring_gc(struct key *keyring, time_t limit);
> -extern void key_schedule_gc(time_t expiry_at);
> +extern void key_schedule_gc(time_t gc_at);
> +extern void key_schedule_gc_links(void);
>  extern void key_gc_keytype(struct key_type *ktype);
> 
>  extern int key_task_permission(const key_ref_t key_ref,
> @@ -173,7 +174,8 @@ static inline int key_permission(const key_ref_t key_ref, key_perm_t perm)
>  #define	KEY_SEARCH	0x08	/* require permission to search (keyring) or find (key) */
>  #define	KEY_LINK	0x10	/* require permission to link */
>  #define	KEY_SETATTR	0x20	/* require permission to change attributes */
> -#define	KEY_ALL		0x3f	/* all the above permissions */
> +#define	KEY_INVAL	0x40	/* require permission to invalidate a key */
> +#define	KEY_ALL		0x7f	/* all the above permissions */
> 
>  /*
>   * Authorisation record for request_key().
> @@ -196,6 +198,17 @@ extern struct key *request_key_auth_new(struct key *target,
>  extern struct key *key_get_instantiation_authkey(key_serial_t target_id);
> 
>  /*
> + * Determine whether a key is dead.
> + */
> +static inline bool key_is_dead(struct key *key, time_t limit)
> +{
> +	return
> +		key->flags & ((1 << KEY_FLAG_DEAD) |
> +			      (1 << KEY_FLAG_INVALIDATED)) ||
> +		(key->expiry > 0 && key->expiry <= limit);
> +}
> +
> +/*
>   * keyctl() functions
>   */
>  extern long keyctl_get_keyring_ID(key_serial_t, int);
> @@ -224,6 +237,7 @@ extern long keyctl_reject_key(key_serial_t, unsigned, unsigned, key_serial_t);
>  extern long keyctl_instantiate_key_iov(key_serial_t,
>  				       const struct iovec __user *,
>  				       unsigned, key_serial_t);
> +extern long keyctl_invalidate_key(key_serial_t);
> 
>  extern long keyctl_instantiate_key_common(key_serial_t,
>  					  const struct iovec __user *,
> diff --git a/security/keys/key.c b/security/keys/key.c
> index 4f64c72..a57a0a4 100644
> --- a/security/keys/key.c
> +++ b/security/keys/key.c
> @@ -807,6 +807,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
>  	if (perm == KEY_PERM_UNDEF) {
>  		perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
>  		perm |= KEY_USR_VIEW | KEY_USR_SEARCH | KEY_USR_LINK | KEY_USR_SETATTR;
> +		perm |= KEY_USR_INVAL;
> 
>  		if (ktype->read)
>  			perm |= KEY_POS_READ | KEY_USR_READ;
> @@ -935,6 +936,28 @@ void key_revoke(struct key *key)
>  EXPORT_SYMBOL(key_revoke);
> 
>  /**
> + * key_invalidate - Invalidate a key.
> + * @key: The key to be invalidated.
> + *
> + * Mark a key as being invalidated and have it cleaned up immediately.  The key
> + * is ignored by all searches and other operations from this point.
> + */
> +void key_invalidate(struct key *key)
> +{
> +	kenter("%d", key_serial(key));
> +
> +	key_check(key);
> +
> +	if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
> +		down_write_nested(&key->sem, 1);
> +		if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags))
> +			key_schedule_gc_links();
> +		up_write(&key->sem);
> +	}
> +}
> +EXPORT_SYMBOL(key_invalidate);
> +
> +/**
>   * register_key_type - Register a type of key.
>   * @ktype: The new key type.
>   *
> diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
> index 6523599..53c9bce 100644
> --- a/security/keys/keyctl.c
> +++ b/security/keys/keyctl.c
> @@ -374,6 +374,37 @@ error:
>  }
> 
>  /*
> + * Invalidate a key.
> + *
> + * The key must be grant the caller Invalidate permission for this to work.
> + * The key and any links to the key will be automatically garbage collected
> + * immediately.
> + *
> + * If successful, 0 is returned.
> + */
> +long keyctl_invalidate_key(key_serial_t id)
> +{
> +	key_ref_t key_ref;
> +	long ret;
> +
> +	kenter("%d", id);
> +
> +	key_ref = lookup_user_key(id, 0, KEY_INVAL);
> +	if (IS_ERR(key_ref)) {
> +		ret = PTR_ERR(key_ref);
> +		goto error;
> +	}
> +
> +	key_invalidate(key_ref_to_ptr(key_ref));
> +	ret = 0;
> +
> +	key_ref_put(key_ref);
> +error:
> +	kleave(" = %ld", ret);
> +	return ret;
> +}
> +
> +/*
>   * Clear the specified keyring, creating an empty process keyring if one of the
>   * special keyring IDs is used.
>   *
> @@ -1636,6 +1667,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
>  			(unsigned) arg4,
>  			(key_serial_t) arg5);
> 
> +	case KEYCTL_INVALIDATE:
> +		return keyctl_invalidate_key((key_serial_t) arg2);
> +
>  	default:
>  		return -EOPNOTSUPP;
>  	}
> diff --git a/security/keys/keyring.c b/security/keys/keyring.c
> index 37a7f3b..ce02adc 100644
> --- a/security/keys/keyring.c
> +++ b/security/keys/keyring.c
> @@ -366,13 +366,17 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref,
>  	/* otherwise, the top keyring must not be revoked, expired, or
>  	 * negatively instantiated if we are to search it */
>  	key_ref = ERR_PTR(-EAGAIN);
> -	if (kflags & ((1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) ||
> +	if (kflags & ((1 << KEY_FLAG_INVALIDATED) |
> +		      (1 << KEY_FLAG_REVOKED) |
> +		      (1 << KEY_FLAG_NEGATIVE)) ||
>  	    (keyring->expiry && now.tv_sec >= keyring->expiry))
>  		goto error_2;
> 
>  	/* start processing a new keyring */
>  descend:
> -	if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
> +	kflags = keyring->flags;
> +	if (kflags & ((1 << KEY_FLAG_INVALIDATED) |
> +		      (1 << KEY_FLAG_REVOKED)))
>  		goto not_this_keyring;
> 
>  	keylist = rcu_dereference(keyring->payload.subscriptions);
> @@ -388,9 +392,10 @@ descend:
>  		if (key->type != type)
>  			continue;
> 
> -		/* skip revoked keys and expired keys */
> +		/* skip invalidated, revoked and expired keys */
>  		if (!no_state_check) {
> -			if (kflags & (1 << KEY_FLAG_REVOKED))
> +			if (kflags & ((1 << KEY_FLAG_INVALIDATED) |
> +				      (1 << KEY_FLAG_REVOKED)))
>  				continue;
> 
>  			if (key->expiry && now.tv_sec >= key->expiry)
> @@ -532,7 +537,8 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref,
>  			     key->type->match(key, description)) &&
>  			    key_permission(make_key_ref(key, possessed),
>  					   perm) == 0 &&
> -			    !test_bit(KEY_FLAG_REVOKED, &key->flags)
> +			    !(key->flags & ((1 << KEY_FLAG_INVALIDATED) |
> +					    (1 << KEY_FLAG_REVOKED)))
>  			    )
>  				goto found;
>  		}
> @@ -1120,15 +1126,6 @@ static void keyring_revoke(struct key *keyring)
>  }
> 
>  /*
> - * Determine whether a key is dead.
> - */
> -static bool key_is_dead(struct key *key, time_t limit)
> -{
> -	return test_bit(KEY_FLAG_DEAD, &key->flags) ||
> -		(key->expiry > 0 && key->expiry <= limit);
> -}
> -
> -/*
>   * Collect garbage from the contents of a keyring, replacing the old list with
>   * a new one with the pointers all shuffled down.
>   *
> diff --git a/security/keys/permission.c b/security/keys/permission.c
> index c35b522..5f4c00c 100644
> --- a/security/keys/permission.c
> +++ b/security/keys/permission.c
> @@ -87,20 +87,25 @@ EXPORT_SYMBOL(key_task_permission);
>   * key_validate - Validate a key.
>   * @key: The key to be validated.
>   *
> - * Check that a key is valid, returning 0 if the key is okay, -EKEYREVOKED if
> - * the key's type has been removed or if the key has been revoked or
> - * -EKEYEXPIRED if the key has expired.
> + * Check that a key is valid, returning 0 if the key is okay, -ENOKEY if the
> + * key is invalidated, -EKEYREVOKED if the key's type has been removed or if
> + * the key has been revoked or -EKEYEXPIRED if the key has expired.
>   */
>  int key_validate(struct key *key)
>  {
>  	struct timespec now;
> +	unsigned long flags = key->flags;
>  	int ret = 0;
> 
>  	if (key) {
> +		ret = -ENOKEY;
> +		if (flags & (1 << KEY_FLAG_INVALIDATED))
> +			goto error;
> +
>  		/* check it's still accessible */
>  		ret = -EKEYREVOKED;
> -		if (test_bit(KEY_FLAG_REVOKED, &key->flags) ||
> -		    test_bit(KEY_FLAG_DEAD, &key->flags))
> +		if (flags & ((1 << KEY_FLAG_REVOKED) |
> +			     (1 << KEY_FLAG_DEAD)))
>  			goto error;
> 
>  		/* check it hasn't expired */
> diff --git a/security/keys/proc.c b/security/keys/proc.c
> index 49bbc97..30d1ddf 100644
> --- a/security/keys/proc.c
> +++ b/security/keys/proc.c
> @@ -242,7 +242,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  #define showflag(KEY, LETTER, FLAG) \
>  	(test_bit(FLAG,	&(KEY)->flags) ? LETTER : '-')
> 
> -	seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
> +	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),
>  		   showflag(key, 'R', KEY_FLAG_REVOKED),
> @@ -250,6 +250,7 @@ static int proc_keys_show(struct seq_file *m, void *v)
>  		   showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
>  		   showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
>  		   showflag(key, 'N', KEY_FLAG_NEGATIVE),
> +		   showflag(key, 'i', KEY_FLAG_INVALIDATED),
>  		   atomic_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@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 


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


[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux