[PATCH] keys: Add tracepoints for the keyrings facility

Jarkko Sakkinen jarkko at kernel.org
Mon Jul 29 13:40:53 UTC 2024


On Tue Jul 23, 2024 at 2:20 AM EEST, David Howells wrote:
> Add some tracepoints to aid in debuggin the keyrings facility and
> applications that use it.  A number of events and operations are traceable,
> including:
>
>   - Allocation
>   - Refcounting
>   - Instantiation and negative instantiation/rejection
>   - Update
>   - Detection of key being dead
>   - Key quota changes
>   - Key quota failure
>   - Link, unlink and move
>   - Keyring clearance
>   - Revocation and invalidation
>   - Garbage collection
>
> Signed-off-by: David Howells <dhowells at redhat.com>
> cc: Jarkko Sakkinen <jarkko at kernel.org>
> cc: keyrings at vger.kernel.org
> cc: linux-security-module at vger.kernel.org
> ---
>  Documentation/security/keys/core.rst |    1 
>  crypto/asymmetric_keys/restrict.c    |    6 
>  include/keys/key_user.h              |   35 +++
>  include/linux/key.h                  |   16 -
>  include/trace/events/key.h           |  401 +++++++++++++++++++++++++++++++++++
>  security/keys/gc.c                   |    4 
>  security/keys/internal.h             |   41 ---
>  security/keys/key.c                  |   66 +++++
>  security/keys/keyctl.c               |    2 
>  security/keys/keyring.c              |   45 +++
>  security/keys/process_keys.c         |   15 -
>  11 files changed, 562 insertions(+), 70 deletions(-)
>
> diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
> index 326b8a973828..0b179540d885 100644
> --- a/Documentation/security/keys/core.rst
> +++ b/Documentation/security/keys/core.rst
> @@ -1217,7 +1217,6 @@ payload contents" for more information.
>   *  Extra references can be made to a key by calling one of the following
>      functions::
>  
> -	struct key *__key_get(struct key *key);
>  	struct key *key_get(struct key *key);
>  
>      Keys so references will need to be disposed of by calling key_put() when
> diff --git a/crypto/asymmetric_keys/restrict.c b/crypto/asymmetric_keys/restrict.c
> index afcd4d101ac5..1ea7bfd4e5d7 100644
> --- a/crypto/asymmetric_keys/restrict.c
> +++ b/crypto/asymmetric_keys/restrict.c
> @@ -267,20 +267,20 @@ static int key_or_keyring_common(struct key *dest_keyring,
>  			if (!sig->auth_ids[0] && !sig->auth_ids[1]) {
>  				if (asymmetric_key_id_same(signer_ids[2],
>  							   sig->auth_ids[2]))
> -					key = __key_get(trusted);
> +					key = key_get(trusted);
>  
>  			} else if (!sig->auth_ids[0] || !sig->auth_ids[1]) {
>  				const struct asymmetric_key_id *auth_id;
>  
>  				auth_id = sig->auth_ids[0] ?: sig->auth_ids[1];
>  				if (match_either_id(signer_ids, auth_id))
> -					key = __key_get(trusted);
> +					key = key_get(trusted);
>  
>  			} else if (asymmetric_key_id_same(signer_ids[1],
>  							  sig->auth_ids[1]) &&
>  				   match_either_id(signer_ids,
>  						   sig->auth_ids[0])) {
> -				key = __key_get(trusted);
> +				key = key_get(trusted);
>  			}
>  		} else {
>  			return -EOPNOTSUPP;
> diff --git a/include/keys/key_user.h b/include/keys/key_user.h
> new file mode 100644
> index 000000000000..e9c383d8116e
> --- /dev/null
> +++ b/include/keys/key_user.h
> @@ -0,0 +1,35 @@
> +/* User quota tracking for keys.
> + *
> + * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells (dhowells at redhat.com)
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public Licence
> + * as published by the Free Software Foundation; either version
> + * 2 of the Licence, or (at your option) any later version.
> + */
> +
> +#ifndef _KEYS_KEY_USER_H
> +#define _KEYS_KEY_USER_H
> +
> +/*
> + * Keep track of keys for a user.
> + *
> + * This needs to be separate to user_struct to avoid a refcount-loop
> + * (user_struct pins some keyrings which pin this struct).
> + *
> + * We also keep track of keys under request from userspace for this UID here.
> + */
> +struct key_user {
> +	struct rb_node		node;
> +	struct mutex		cons_lock;	/* construction initiation lock */
> +	spinlock_t		lock;
> +	refcount_t		usage;		/* for accessing qnkeys & qnbytes */
> +	atomic_t		nkeys;		/* number of keys */
> +	atomic_t		nikeys;		/* number of instantiated keys */
> +	kuid_t			uid;
> +	int			qnkeys;		/* number of keys allocated to this user */
> +	int			qnbytes;	/* number of bytes allocated to this user */
> +};
> +
> +#endif /* _KEYS_KEY_USER_H */
> diff --git a/include/linux/key.h b/include/linux/key.h
> index 943a432da3ae..4e5baf3e7286 100644
> --- a/include/linux/key.h
> +++ b/include/linux/key.h
> @@ -299,24 +299,20 @@ extern struct key *key_alloc(struct key_type *type,
>  
>  extern void key_revoke(struct key *key);
>  extern void key_invalidate(struct key *key);
> +struct key *key_get(struct key *key);
> +struct key *key_try_get(struct key *key);
>  extern void key_put(struct key *key);
>  extern bool key_put_tag(struct key_tag *tag);
>  extern void key_remove_domain(struct key_tag *domain_tag);
>  
> -static inline struct key *__key_get(struct key *key)
> -{
> -	refcount_inc(&key->usage);
> -	return key;
> -}
> -
> -static inline struct key *key_get(struct key *key)
> +static inline void key_ref_put(key_ref_t key_ref)
>  {
> -	return key ? __key_get(key) : key;
> +	key_put(key_ref_to_ptr(key_ref));
>  }
>  
> -static inline void key_ref_put(key_ref_t key_ref)
> +static inline void key_ref_get(key_ref_t key_ref)
>  {
> -	key_put(key_ref_to_ptr(key_ref));
> +	key_get(key_ref_to_ptr(key_ref));
>  }
>  
>  extern struct key *request_key_tag(struct key_type *type,
> diff --git a/include/trace/events/key.h b/include/trace/events/key.h
> new file mode 100644
> index 000000000000..b3f8c39cc0e8
> --- /dev/null
> +++ b/include/trace/events/key.h
> @@ -0,0 +1,401 @@
> +/* Keyrings tracepoints
> + *
> + * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells (dhowells at redhat.com)
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public Licence
> + * as published by the Free Software Foundation; either version
> + * 2 of the Licence, or (at your option) any later version.
> + */
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM key
> +
> +#if !defined(_TRACE_KEY_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define _TRACE_KEY_H
> +
> +#include <linux/tracepoint.h>
> +
> +struct key;
> +
> +/*
> + * Declare tracing information enums and their string mappings for display.
> + */
> +#define key_ref_traces \
> +	EM(key_trace_ref_alloc,			"ALLOC") \
> +	EM(key_trace_ref_free,			"FREE ") \
> +	EM(key_trace_ref_gc,			"GC   ") \
> +	EM(key_trace_ref_get,			"GET  ") \
> +	EM(key_trace_ref_put,			"PUT  ") \
> +	E_(key_trace_ref_try_get,		"GET  ")
> +
> +#define key_dead_traces \
> +	EM(key_trace_dead_type_removed,		"TYPR") \
> +	EM(key_trace_dead_domain_removed,	"DOMR") \
> +	EM(key_trace_dead_expired,		"EXPD") \
> +	E_(key_trace_dead_invalidated,		"INVL")
> +
> +/*
> + * Generate enums for tracing information.
> + */
> +#ifndef __NETFS_DECLARE_TRACE_ENUMS_ONCE_ONLY
> +#define __NETFS_DECLARE_TRACE_ENUMS_ONCE_ONLY
> +
> +#undef EM
> +#undef E_
> +#define EM(a, b) a,
> +#define E_(a, b) a
> +
> +enum key_dead_trace	{ key_dead_traces } __mode(byte);
> +enum key_ref_trace	{ key_ref_traces } __mode(byte);
> +
> +#endif /* end __RXRPC_DECLARE_TRACE_ENUMS_ONCE_ONLY */
> +
> +/*
> + * Export enum symbols via userspace.
> + */
> +#undef EM
> +#undef E_
> +#define EM(a, b) TRACE_DEFINE_ENUM(a);
> +#define E_(a, b) TRACE_DEFINE_ENUM(a);
> +
> +key_dead_traces;
> +key_ref_traces;
> +
> +/*
> + * Now redefine the EM() and E_() macros to map the enums to the strings that
> + * will be printed in the output.
> + */
> +#undef EM
> +#undef E_
> +#define EM(a, b)	{ a, b },
> +#define E_(a, b)	{ a, b }
> +
> +TRACE_EVENT(key_alloc,
> +	    TP_PROTO(const struct key *key),
> +
> +	    TP_ARGS(key),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,		key)
> +		    __field(uid_t,			uid)
> +		    __array(char,			type, 8)
> +		    __array(char,			desc, 24)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +		    __entry->uid = from_kuid(&init_user_ns, key->uid);
> +		    strncpy(__entry->type, key->type->name, sizeof(__entry->type) - 1);
> +		    strncpy(__entry->desc, key->description ?: "", sizeof(__entry->desc) - 1);
> +		    __entry->type[sizeof(__entry->type) - 1] = 0;
> +		    __entry->desc[sizeof(__entry->desc) - 1] = 0;
> +			   ),
> +
> +	    TP_printk("key=%08x uid=%08x t=%s d=%s",
> +		      __entry->key,
> +		      __entry->uid,
> +		      __entry->type,
> +		      __entry->desc)
> +	    );
> +
> +TRACE_EVENT(key_ref,
> +	    TP_PROTO(key_serial_t key, unsigned int ref, enum key_ref_trace trace,
> +		     const void *where),
> +
> +	    TP_ARGS(key, ref, trace, where),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,		key)
> +		    __field(enum key_ref_trace,		trace)
> +		    __field(unsigned int,		ref)
> +		    __field(const void *,		where)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->key = key;
> +		    __entry->trace = trace;
> +		    __entry->ref = ref;
> +		    __entry->where = where;
> +			   ),
> +
> +	    TP_printk("key=%08x %s r=%d pc=%pSR",
> +		      __entry->key,
> +		      __print_symbolic(__entry->trace, key_ref_traces),
> +		      __entry->ref,
> +		      __entry->where)
> +	    );
> +
> +TRACE_EVENT(key_instantiate,
> +	    TP_PROTO(const struct key *key, const struct key_preparsed_payload *prep),
> +
> +	    TP_ARGS(key, prep),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	key)
> +		    __field(unsigned int,	datalen)
> +		    __field(unsigned int,	quotalen)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +		    __entry->datalen = prep->datalen;
> +		    __entry->quotalen = prep->quotalen;
> +			   ),
> +
> +	    TP_printk("key=%08x dlen=%u qlen=%u",
> +		      __entry->key,
> +		      __entry->datalen,
> +		      __entry->quotalen)
> +	    );
> +
> +TRACE_EVENT(key_reject,
> +	    TP_PROTO(const struct key *key, int error),
> +
> +	    TP_ARGS(key, error),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	key)
> +		    __field(int,		error)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +		    __entry->error = error;
> +			   ),
> +
> +	    TP_printk("key=%08x err=%d",
> +		      __entry->key,
> +		      __entry->error)
> +	    );
> +
> +TRACE_EVENT(key_update,
> +	    TP_PROTO(const struct key *key, const struct key_preparsed_payload *prep),
> +
> +	    TP_ARGS(key, prep),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	key)
> +		    __field(unsigned int,	datalen)
> +		    __field(unsigned int,	quotalen)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +		    __entry->datalen = prep->datalen;
> +		    __entry->quotalen = prep->quotalen;
> +			   ),
> +
> +	    TP_printk("key=%08x dlen=%u qlen=%u",
> +		      __entry->key,
> +		      __entry->datalen,
> +		      __entry->quotalen)
> +	    );
> +
> +TRACE_EVENT(key_dead,
> +	    TP_PROTO(const struct key *key, enum key_dead_trace trace),
> +
> +	    TP_ARGS(key, trace),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,		key)
> +		    __field(enum key_dead_trace,	trace)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +		    __entry->trace = trace;
> +			   ),
> +
> +	    TP_printk("key=%08x %s",
> +		      __entry->key,
> +		      __print_symbolic(__entry->trace, key_dead_traces))
> +	    );
> +
> +TRACE_EVENT(key_quota,
> +	    TP_PROTO(const struct key_user *user, int change_keys, int change_bytes),
> +
> +	    TP_ARGS(user, change_keys, change_bytes),
> +
> +	    TP_STRUCT__entry(
> +		    __field(uid_t,		uid)
> +		    __field(unsigned int,	nkeys)
> +		    __field(unsigned int,	nikeys)
> +		    __field(unsigned int,	qnkeys)
> +		    __field(unsigned int,	qnbytes)
> +		    __field(int,		change_keys)
> +		    __field(int,		change_bytes)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->uid = from_kuid(&init_user_ns, user->uid);
> +		    __entry->nkeys = atomic_read(&user->nkeys);
> +		    __entry->nikeys = atomic_read(&user->nikeys);
> +		    __entry->qnkeys = user->qnkeys;
> +		    __entry->qnbytes = user->qnbytes;
> +		    __entry->change_keys = change_keys;
> +		    __entry->change_bytes = change_bytes;
> +			   ),
> +
> +	    TP_printk("uid=%d nkeys=%u/%u qkeys=%u qpay=%u ckeys=%d cpay=%d",
> +		      __entry->uid,
> +		      __entry->nikeys, __entry->nkeys,
> +		      __entry->qnkeys,
> +		      __entry->qnbytes,
> +		      __entry->change_keys, __entry->change_bytes)
> +	    );
> +
> +TRACE_EVENT(key_edquot,
> +	    TP_PROTO(const struct key_user *user, unsigned int maxkeys,
> +		     unsigned int maxbytes, unsigned int reqbytes),
> +
> +	    TP_ARGS(user, maxkeys, maxbytes, reqbytes),
> +
> +	    TP_STRUCT__entry(
> +		    __field(uid_t,		uid)
> +		    __field(unsigned int,	nkeys)
> +		    __field(unsigned int,	nikeys)
> +		    __field(unsigned int,	qnkeys)
> +		    __field(unsigned int,	qnbytes)
> +		    __field(unsigned int,	maxkeys)
> +		    __field(unsigned int,	maxbytes)
> +		    __field(unsigned int,	reqbytes)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->uid = from_kuid(&init_user_ns, user->uid);
> +		    __entry->nkeys = atomic_read(&user->nkeys);
> +		    __entry->nikeys = atomic_read(&user->nikeys);
> +		    __entry->qnkeys = user->qnkeys;
> +		    __entry->qnbytes = user->qnbytes;
> +		    __entry->maxkeys = maxkeys;
> +		    __entry->maxbytes = maxbytes;
> +		    __entry->reqbytes = reqbytes;
> +			   ),
> +
> +	    TP_printk("u=%d nkeys=%u/%u qkeys=%u/%u qpay=%u/%u cpay=%u",
> +		      __entry->uid,
> +		      __entry->nikeys, __entry->nkeys,
> +		      __entry->qnkeys, __entry->maxkeys,
> +		      __entry->qnbytes, __entry->maxbytes,
> +		      __entry->reqbytes)
> +	    );
> +
> +TRACE_EVENT(key_link,
> +	    TP_PROTO(const struct key *keyring, const struct key *key),
> +
> +	    TP_ARGS(keyring, key),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	keyring)
> +		    __field(key_serial_t,	key)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->keyring = keyring->serial;
> +		    __entry->key = key->serial;
> +			   ),
> +
> +	    TP_printk("key=%08x to=%08x",
> +		      __entry->key, __entry->keyring)
> +	    );
> +
> +TRACE_EVENT(key_unlink,
> +	    TP_PROTO(const struct key *keyring, const struct key *key),
> +
> +	    TP_ARGS(keyring, key),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	keyring)
> +		    __field(key_serial_t,	key)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->keyring = keyring->serial;
> +		    __entry->key = key->serial;
> +			   ),
> +
> +	    TP_printk("key=%08x from=%08x",
> +		      __entry->key, __entry->keyring)
> +	    );
> +
> +TRACE_EVENT(key_move,
> +	    TP_PROTO(const struct key *from, const struct key *to,
> +		     const struct key *key),
> +
> +	    TP_ARGS(from, to, key),
> +
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	from)
> +		    __field(key_serial_t,	to)
> +		    __field(key_serial_t,	key)
> +			     ),
> +
> +	    TP_fast_assign(
> +		    __entry->from = from->serial;
> +		    __entry->to = to->serial;
> +		    __entry->key = key->serial;
> +			   ),
> +
> +	    TP_printk("key=%08x from=%08x to=%08x",
> +		      __entry->key, __entry->from, __entry->to)
> +	    );
> +
> +TRACE_EVENT(key_clear,
> +	    TP_PROTO(const struct key *keyring),
> +	    TP_ARGS(keyring),
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	keyring)
> +			     ),
> +	    TP_fast_assign(
> +		    __entry->keyring = keyring->serial;
> +			   ),
> +	    TP_printk("key=%08x",
> +		      __entry->keyring)
> +	    );
> +
> +TRACE_EVENT(key_revoke,
> +	    TP_PROTO(const struct key *key),
> +	    TP_ARGS(key),
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	key)
> +			     ),
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +			   ),
> +	    TP_printk("key=%08x",
> +		      __entry->key)
> +	    );
> +
> +TRACE_EVENT(key_invalidate,
> +	    TP_PROTO(const struct key *key),
> +	    TP_ARGS(key),
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	key)
> +			     ),
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +			   ),
> +	    TP_printk("key=%08x",
> +		      __entry->key)
> +	    );
> +
> +TRACE_EVENT(key_gc,
> +	    TP_PROTO(const struct key *key),
> +	    TP_ARGS(key),
> +	    TP_STRUCT__entry(
> +		    __field(key_serial_t,	key)
> +			     ),
> +	    TP_fast_assign(
> +		    __entry->key = key->serial;
> +			   ),
> +	    TP_printk("key=%08x",
> +		      __entry->key)
> +	    );
> +
> +#undef EM
> +#undef E_
> +#endif /* _TRACE_KEY_H */
> +
> +/* This part must be outside protection */
> +#include <trace/define_trace.h>
> diff --git a/security/keys/gc.c b/security/keys/gc.c
> index 7d687b0962b1..47f857653854 100644
> --- a/security/keys/gc.c
> +++ b/security/keys/gc.c
> @@ -141,6 +141,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  
>  		list_del(&key->graveyard_link);
>  
> +		trace_key_gc(key);
>  		kdebug("- %u", key->serial);
>  		key_check(key);
>  
> @@ -163,6 +164,8 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
>  		key_put_tag(key->domain_tag);
>  		kfree(key->description);
>  
> +		trace_key_ref(key->serial, refcount_read(&key->usage),
> +			      key_trace_ref_free, 0);
>  		memzero_explicit(key, sizeof(*key));
>  		kmem_cache_free(key_jar, key);
>  	}
> @@ -331,6 +334,7 @@ static void key_garbage_collector(struct work_struct *work)
>  	 */
>  found_unreferenced_key:
>  	kdebug("unrefd key %d", key->serial);
> +	trace_key_ref(key->serial, refcount_read(&key->usage), key_trace_ref_gc, 0);
>  	rb_erase(&key->serial_node, &key_serial_tree);
>  	spin_unlock(&key_serial_lock);
>  
> diff --git a/security/keys/internal.h b/security/keys/internal.h
> index 2cffa6dc8255..87600683a7f5 100644
> --- a/security/keys/internal.h
> +++ b/security/keys/internal.h
> @@ -19,6 +19,8 @@
>  #include <linux/compat.h>
>  #include <linux/mm.h>
>  #include <linux/vmalloc.h>
> +#include <keys/key_user.h>
> +#include <trace/events/key.h>
>  
>  struct iovec;
>  
> @@ -43,25 +45,6 @@ extern struct key_type key_type_user;
>  extern struct key_type key_type_logon;
>  
>  /*****************************************************************************/
> -/*
> - * Keep track of keys for a user.
> - *
> - * This needs to be separate to user_struct to avoid a refcount-loop
> - * (user_struct pins some keyrings which pin this struct).
> - *
> - * We also keep track of keys under request from userspace for this UID here.
> - */
> -struct key_user {
> -	struct rb_node		node;
> -	struct mutex		cons_lock;	/* construction initiation lock */
> -	spinlock_t		lock;
> -	refcount_t		usage;		/* for accessing qnkeys & qnbytes */
> -	atomic_t		nkeys;		/* number of keys */
> -	atomic_t		nikeys;		/* number of instantiated keys */
> -	kuid_t			uid;
> -	int			qnkeys;		/* number of keys allocated to this user */
> -	int			qnbytes;	/* number of bytes allocated to this user */
> -};
>  
>  extern struct rb_root	key_user_tree;
>  extern spinlock_t	key_user_lock;
> @@ -211,26 +194,6 @@ 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(const struct key *key, time64_t limit)
> -{
> -	time64_t expiry = key->expiry;
> -
> -	if (expiry != TIME64_MAX) {
> -		if (!(key->type->flags & KEY_TYPE_INSTANT_REAP))
> -			expiry += key_gc_delay;
> -		if (expiry <= limit)
> -			return true;
> -	}
> -
> -	return
> -		key->flags & ((1 << KEY_FLAG_DEAD) |
> -			      (1 << KEY_FLAG_INVALIDATED)) ||
> -		key->domain_tag->removed;
> -}
> -

I'd split relocations and similar preparations to separate patches, if
by any means possible. Then it would be less stressing to review the
actual change.

BR, Jarkko



More information about the Linux-security-module-archive mailing list