mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	watch_queue: Add a key/keyring notification facility
Add a key/keyring change notification facility whereby notifications about
changes in key and keyring content and attributes can be received.
Firstly, an event queue needs to be created:
	pipe2(fds, O_NOTIFICATION_PIPE);
	ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
then a notification can be set up to report notifications via that queue:
	struct watch_notification_filter filter = {
		.nr_filters = 1,
		.filters = {
			[0] = {
				.type = WATCH_TYPE_KEY_NOTIFY,
				.subtype_filter[0] = UINT_MAX,
			},
		},
	};
	ioctl(fds[1], IOC_WATCH_QUEUE_SET_FILTER, &filter);
	keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);
After that, records will be placed into the queue when events occur in
which keys are changed in some way.  Records are of the following format:
	struct key_notification {
		struct watch_notification watch;
		__u32	key_id;
		__u32	aux;
	} *n;
Where:
	n->watch.type will be WATCH_TYPE_KEY_NOTIFY.
	n->watch.subtype will indicate the type of event, such as
	NOTIFY_KEY_REVOKED.
	n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
	record.
	n->watch.info & WATCH_INFO_ID will be the second argument to
	keyctl_watch_key(), shifted.
	n->key will be the ID of the affected key.
	n->aux will hold subtype-dependent information, such as the key
	being linked into the keyring specified by n->key in the case of
	NOTIFY_KEY_LINKED.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype.  Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: James Morris <jamorris@linux.microsoft.com>
			
			
This commit is contained in:
		
							parent
							
								
									998f50407f
								
							
						
					
					
						commit
						f7e47677e3
					
				
					 12 changed files with 269 additions and 27 deletions
				
			
		| 
						 | 
					@ -1026,6 +1026,63 @@ The keyctl syscall functions are:
 | 
				
			||||||
     written into the output buffer.  Verification returns 0 on success.
 | 
					     written into the output buffer.  Verification returns 0 on success.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  *  Watch a key or keyring for changes::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						long keyctl(KEYCTL_WATCH_KEY, key_serial_t key, int queue_fd,
 | 
				
			||||||
 | 
							    const struct watch_notification_filter *filter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     This will set or remove a watch for changes on the specified key or
 | 
				
			||||||
 | 
					     keyring.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     "key" is the ID of the key to be watched.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     "queue_fd" is a file descriptor referring to an open "/dev/watch_queue"
 | 
				
			||||||
 | 
					     which manages the buffer into which notifications will be delivered.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     "filter" is either NULL to remove a watch or a filter specification to
 | 
				
			||||||
 | 
					     indicate what events are required from the key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     See Documentation/watch_queue.rst for more information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     Note that only one watch may be emplaced for any particular { key,
 | 
				
			||||||
 | 
					     queue_fd } combination.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     Notification records look like::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct key_notification {
 | 
				
			||||||
 | 
							struct watch_notification watch;
 | 
				
			||||||
 | 
							__u32	key_id;
 | 
				
			||||||
 | 
							__u32	aux;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     In this, watch::type will be "WATCH_TYPE_KEY_NOTIFY" and subtype will be
 | 
				
			||||||
 | 
					     one of::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						NOTIFY_KEY_INSTANTIATED
 | 
				
			||||||
 | 
						NOTIFY_KEY_UPDATED
 | 
				
			||||||
 | 
						NOTIFY_KEY_LINKED
 | 
				
			||||||
 | 
						NOTIFY_KEY_UNLINKED
 | 
				
			||||||
 | 
						NOTIFY_KEY_CLEARED
 | 
				
			||||||
 | 
						NOTIFY_KEY_REVOKED
 | 
				
			||||||
 | 
						NOTIFY_KEY_INVALIDATED
 | 
				
			||||||
 | 
						NOTIFY_KEY_SETATTR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     Where these indicate a key being instantiated/rejected, updated, a link
 | 
				
			||||||
 | 
					     being made in a keyring, a link being removed from a keyring, a keyring
 | 
				
			||||||
 | 
					     being cleared, a key being revoked, a key being invalidated or a key
 | 
				
			||||||
 | 
					     having one of its attributes changed (user, group, perm, timeout,
 | 
				
			||||||
 | 
					     restriction).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     If a watched key is deleted, a basic watch_notification will be issued
 | 
				
			||||||
 | 
					     with "type" set to WATCH_TYPE_META and "subtype" set to
 | 
				
			||||||
 | 
					     watch_meta_removal_notification.  The watchpoint ID will be set in the
 | 
				
			||||||
 | 
					     "info" field.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     This needs to be configured by enabling:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"Provide key/keyring change notifications" (KEY_NOTIFICATIONS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Kernel Services
 | 
					Kernel Services
 | 
				
			||||||
===============
 | 
					===============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -176,6 +176,9 @@ struct key {
 | 
				
			||||||
		struct list_head graveyard_link;
 | 
							struct list_head graveyard_link;
 | 
				
			||||||
		struct rb_node	serial_node;
 | 
							struct rb_node	serial_node;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					#ifdef CONFIG_KEY_NOTIFICATIONS
 | 
				
			||||||
 | 
						struct watch_list	*watchers;	/* Entities watching this key for changes */
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
	struct rw_semaphore	sem;		/* change vs change sem */
 | 
						struct rw_semaphore	sem;		/* change vs change sem */
 | 
				
			||||||
	struct key_user		*user;		/* owner of this key */
 | 
						struct key_user		*user;		/* owner of this key */
 | 
				
			||||||
	void			*security;	/* security data for this key */
 | 
						void			*security;	/* security data for this key */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,6 +69,7 @@
 | 
				
			||||||
#define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
 | 
					#define KEYCTL_RESTRICT_KEYRING		29	/* Restrict keys allowed to link to a keyring */
 | 
				
			||||||
#define KEYCTL_MOVE			30	/* Move keys between keyrings */
 | 
					#define KEYCTL_MOVE			30	/* Move keys between keyrings */
 | 
				
			||||||
#define KEYCTL_CAPABILITIES		31	/* Find capabilities of keyrings subsystem */
 | 
					#define KEYCTL_CAPABILITIES		31	/* Find capabilities of keyrings subsystem */
 | 
				
			||||||
 | 
					#define KEYCTL_WATCH_KEY		32	/* Watch a key or ring of keys for changes */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* keyctl structures */
 | 
					/* keyctl structures */
 | 
				
			||||||
struct keyctl_dh_params {
 | 
					struct keyctl_dh_params {
 | 
				
			||||||
| 
						 | 
					@ -130,5 +131,6 @@ struct keyctl_pkey_params {
 | 
				
			||||||
#define KEYCTL_CAPS0_MOVE		0x80 /* KEYCTL_MOVE supported */
 | 
					#define KEYCTL_CAPS0_MOVE		0x80 /* KEYCTL_MOVE supported */
 | 
				
			||||||
#define KEYCTL_CAPS1_NS_KEYRING_NAME	0x01 /* Keyring names are per-user_namespace */
 | 
					#define KEYCTL_CAPS1_NS_KEYRING_NAME	0x01 /* Keyring names are per-user_namespace */
 | 
				
			||||||
#define KEYCTL_CAPS1_NS_KEY_TAG		0x02 /* Key indexing can include a namespace tag */
 | 
					#define KEYCTL_CAPS1_NS_KEY_TAG		0x02 /* Key indexing can include a namespace tag */
 | 
				
			||||||
 | 
					#define KEYCTL_CAPS1_NOTIFICATIONS	0x04 /* Keys generate watchable notifications */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /*  _LINUX_KEYCTL_H */
 | 
					#endif /*  _LINUX_KEYCTL_H */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum watch_notification_type {
 | 
					enum watch_notification_type {
 | 
				
			||||||
	WATCH_TYPE_META		= 0,	/* Special record */
 | 
						WATCH_TYPE_META		= 0,	/* Special record */
 | 
				
			||||||
	WATCH_TYPE__NR		= 1
 | 
						WATCH_TYPE_KEY_NOTIFY	= 1,	/* Key change event notification */
 | 
				
			||||||
 | 
						WATCH_TYPE__NR		= 2
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum watch_meta_notification_subtype {
 | 
					enum watch_meta_notification_subtype {
 | 
				
			||||||
| 
						 | 
					@ -75,4 +76,29 @@ struct watch_notification_removal {
 | 
				
			||||||
	__u64	id;		/* Type-dependent identifier */
 | 
						__u64	id;		/* Type-dependent identifier */
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Type of key/keyring change notification.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					enum key_notification_subtype {
 | 
				
			||||||
 | 
						NOTIFY_KEY_INSTANTIATED	= 0, /* Key was instantiated (aux is error code) */
 | 
				
			||||||
 | 
						NOTIFY_KEY_UPDATED	= 1, /* Key was updated */
 | 
				
			||||||
 | 
						NOTIFY_KEY_LINKED	= 2, /* Key (aux) was added to watched keyring */
 | 
				
			||||||
 | 
						NOTIFY_KEY_UNLINKED	= 3, /* Key (aux) was removed from watched keyring */
 | 
				
			||||||
 | 
						NOTIFY_KEY_CLEARED	= 4, /* Keyring was cleared */
 | 
				
			||||||
 | 
						NOTIFY_KEY_REVOKED	= 5, /* Key was revoked */
 | 
				
			||||||
 | 
						NOTIFY_KEY_INVALIDATED	= 6, /* Key was invalidated */
 | 
				
			||||||
 | 
						NOTIFY_KEY_SETATTR	= 7, /* Key's attributes got changed */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Key/keyring notification record.
 | 
				
			||||||
 | 
					 * - watch.type = WATCH_TYPE_KEY_NOTIFY
 | 
				
			||||||
 | 
					 * - watch.subtype = enum key_notification_type
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct key_notification {
 | 
				
			||||||
 | 
						struct watch_notification watch;
 | 
				
			||||||
 | 
						__u32	key_id;		/* The key/keyring affected */
 | 
				
			||||||
 | 
						__u32	aux;		/* Per-type auxiliary data */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
 | 
					#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,3 +116,12 @@ config KEY_DH_OPERATIONS
 | 
				
			||||||
	 in the kernel.
 | 
						 in the kernel.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	 If you are unsure as to whether this is required, answer N.
 | 
						 If you are unsure as to whether this is required, answer N.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config KEY_NOTIFICATIONS
 | 
				
			||||||
 | 
						bool "Provide key/keyring change notifications"
 | 
				
			||||||
 | 
						depends on KEYS && WATCH_QUEUE
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  This option provides support for getting change notifications on keys
 | 
				
			||||||
 | 
						  and keyrings on which the caller has View permission.  This makes use
 | 
				
			||||||
 | 
						  of the /dev/watch_queue misc device to handle the notification
 | 
				
			||||||
 | 
						  buffer and provides KEYCTL_WATCH_KEY to enable/disable watches.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -156,6 +156,9 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
 | 
				
			||||||
	case KEYCTL_CAPABILITIES:
 | 
						case KEYCTL_CAPABILITIES:
 | 
				
			||||||
		return keyctl_capabilities(compat_ptr(arg2), arg3);
 | 
							return keyctl_capabilities(compat_ptr(arg2), arg3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case KEYCTL_WATCH_KEY:
 | 
				
			||||||
 | 
							return keyctl_watch_key(arg2, arg3, arg4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return -EOPNOTSUPP;
 | 
							return -EOPNOTSUPP;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -131,6 +131,11 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
 | 
				
			||||||
		kdebug("- %u", key->serial);
 | 
							kdebug("- %u", key->serial);
 | 
				
			||||||
		key_check(key);
 | 
							key_check(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_KEY_NOTIFICATIONS
 | 
				
			||||||
 | 
							remove_watch_list(key->watchers, key->serial);
 | 
				
			||||||
 | 
							key->watchers = NULL;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* Throw away the key data if the key is instantiated */
 | 
							/* Throw away the key data if the key is instantiated */
 | 
				
			||||||
		if (state == KEY_IS_POSITIVE && key->type->destroy)
 | 
							if (state == KEY_IS_POSITIVE && key->type->destroy)
 | 
				
			||||||
			key->type->destroy(key);
 | 
								key->type->destroy(key);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@
 | 
				
			||||||
#include <linux/task_work.h>
 | 
					#include <linux/task_work.h>
 | 
				
			||||||
#include <linux/keyctl.h>
 | 
					#include <linux/keyctl.h>
 | 
				
			||||||
#include <linux/refcount.h>
 | 
					#include <linux/refcount.h>
 | 
				
			||||||
 | 
					#include <linux/watch_queue.h>
 | 
				
			||||||
#include <linux/compat.h>
 | 
					#include <linux/compat.h>
 | 
				
			||||||
#include <linux/mm.h>
 | 
					#include <linux/mm.h>
 | 
				
			||||||
#include <linux/vmalloc.h>
 | 
					#include <linux/vmalloc.h>
 | 
				
			||||||
| 
						 | 
					@ -99,7 +100,8 @@ extern int __key_link_begin(struct key *keyring,
 | 
				
			||||||
			    const struct keyring_index_key *index_key,
 | 
								    const struct keyring_index_key *index_key,
 | 
				
			||||||
			    struct assoc_array_edit **_edit);
 | 
								    struct assoc_array_edit **_edit);
 | 
				
			||||||
extern int __key_link_check_live_key(struct key *keyring, struct key *key);
 | 
					extern int __key_link_check_live_key(struct key *keyring, struct key *key);
 | 
				
			||||||
extern void __key_link(struct key *key, struct assoc_array_edit **_edit);
 | 
					extern void __key_link(struct key *keyring, struct key *key,
 | 
				
			||||||
 | 
							       struct assoc_array_edit **_edit);
 | 
				
			||||||
extern void __key_link_end(struct key *keyring,
 | 
					extern void __key_link_end(struct key *keyring,
 | 
				
			||||||
			   const struct keyring_index_key *index_key,
 | 
								   const struct keyring_index_key *index_key,
 | 
				
			||||||
			   struct assoc_array_edit *edit);
 | 
								   struct assoc_array_edit *edit);
 | 
				
			||||||
| 
						 | 
					@ -183,6 +185,23 @@ extern int key_task_permission(const key_ref_t key_ref,
 | 
				
			||||||
			       const struct cred *cred,
 | 
								       const struct cred *cred,
 | 
				
			||||||
			       key_perm_t perm);
 | 
								       key_perm_t perm);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline void notify_key(struct key *key,
 | 
				
			||||||
 | 
								      enum key_notification_subtype subtype, u32 aux)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					#ifdef CONFIG_KEY_NOTIFICATIONS
 | 
				
			||||||
 | 
						struct key_notification n = {
 | 
				
			||||||
 | 
							.watch.type	= WATCH_TYPE_KEY_NOTIFY,
 | 
				
			||||||
 | 
							.watch.subtype	= subtype,
 | 
				
			||||||
 | 
							.watch.info	= watch_sizeof(n),
 | 
				
			||||||
 | 
							.key_id		= key_serial(key),
 | 
				
			||||||
 | 
							.aux		= aux,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						post_watch_notification(key->watchers, &n.watch, current_cred(),
 | 
				
			||||||
 | 
									n.key_id);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Check to see whether permission is granted to use a key in the desired way.
 | 
					 * Check to see whether permission is granted to use a key in the desired way.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -333,6 +352,15 @@ static inline long keyctl_pkey_e_d_s(int op,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern long keyctl_capabilities(unsigned char __user *_buffer, size_t buflen);
 | 
					extern long keyctl_capabilities(unsigned char __user *_buffer, size_t buflen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_KEY_NOTIFICATIONS
 | 
				
			||||||
 | 
					extern long keyctl_watch_key(key_serial_t, int, int);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					static inline long keyctl_watch_key(key_serial_t key_id, int watch_fd, int watch_id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return -EOPNOTSUPP;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Debugging key validation
 | 
					 * Debugging key validation
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -444,6 +444,7 @@ static int __key_instantiate_and_link(struct key *key,
 | 
				
			||||||
			/* mark the key as being instantiated */
 | 
								/* mark the key as being instantiated */
 | 
				
			||||||
			atomic_inc(&key->user->nikeys);
 | 
								atomic_inc(&key->user->nikeys);
 | 
				
			||||||
			mark_key_instantiated(key, 0);
 | 
								mark_key_instantiated(key, 0);
 | 
				
			||||||
 | 
								notify_key(key, NOTIFY_KEY_INSTANTIATED, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 | 
								if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
 | 
				
			||||||
				awaken = 1;
 | 
									awaken = 1;
 | 
				
			||||||
| 
						 | 
					@ -453,7 +454,7 @@ static int __key_instantiate_and_link(struct key *key,
 | 
				
			||||||
				if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 | 
									if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
 | 
				
			||||||
					set_bit(KEY_FLAG_KEEP, &key->flags);
 | 
										set_bit(KEY_FLAG_KEEP, &key->flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				__key_link(key, _edit);
 | 
									__key_link(keyring, key, _edit);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/* disable the authorisation key */
 | 
								/* disable the authorisation key */
 | 
				
			||||||
| 
						 | 
					@ -601,6 +602,7 @@ int key_reject_and_link(struct key *key,
 | 
				
			||||||
		/* mark the key as being negatively instantiated */
 | 
							/* mark the key as being negatively instantiated */
 | 
				
			||||||
		atomic_inc(&key->user->nikeys);
 | 
							atomic_inc(&key->user->nikeys);
 | 
				
			||||||
		mark_key_instantiated(key, -error);
 | 
							mark_key_instantiated(key, -error);
 | 
				
			||||||
 | 
							notify_key(key, NOTIFY_KEY_INSTANTIATED, -error);
 | 
				
			||||||
		key->expiry = ktime_get_real_seconds() + timeout;
 | 
							key->expiry = ktime_get_real_seconds() + timeout;
 | 
				
			||||||
		key_schedule_gc(key->expiry + key_gc_delay);
 | 
							key_schedule_gc(key->expiry + key_gc_delay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -611,7 +613,7 @@ int key_reject_and_link(struct key *key,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* and link it into the destination keyring */
 | 
							/* and link it into the destination keyring */
 | 
				
			||||||
		if (keyring && link_ret == 0)
 | 
							if (keyring && link_ret == 0)
 | 
				
			||||||
			__key_link(key, &edit);
 | 
								__key_link(keyring, key, &edit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* disable the authorisation key */
 | 
							/* disable the authorisation key */
 | 
				
			||||||
		if (authkey)
 | 
							if (authkey)
 | 
				
			||||||
| 
						 | 
					@ -764,9 +766,11 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
 | 
				
			||||||
	down_write(&key->sem);
 | 
						down_write(&key->sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = key->type->update(key, prep);
 | 
						ret = key->type->update(key, prep);
 | 
				
			||||||
	if (ret == 0)
 | 
						if (ret == 0) {
 | 
				
			||||||
		/* Updating a negative key positively instantiates it */
 | 
							/* Updating a negative key positively instantiates it */
 | 
				
			||||||
		mark_key_instantiated(key, 0);
 | 
							mark_key_instantiated(key, 0);
 | 
				
			||||||
 | 
							notify_key(key, NOTIFY_KEY_UPDATED, 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	up_write(&key->sem);
 | 
						up_write(&key->sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1023,9 +1027,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
 | 
				
			||||||
	down_write(&key->sem);
 | 
						down_write(&key->sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = key->type->update(key, &prep);
 | 
						ret = key->type->update(key, &prep);
 | 
				
			||||||
	if (ret == 0)
 | 
						if (ret == 0) {
 | 
				
			||||||
		/* Updating a negative key positively instantiates it */
 | 
							/* Updating a negative key positively instantiates it */
 | 
				
			||||||
		mark_key_instantiated(key, 0);
 | 
							mark_key_instantiated(key, 0);
 | 
				
			||||||
 | 
							notify_key(key, NOTIFY_KEY_UPDATED, 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	up_write(&key->sem);
 | 
						up_write(&key->sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1057,15 +1063,17 @@ void key_revoke(struct key *key)
 | 
				
			||||||
	 *   instantiated
 | 
						 *   instantiated
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	down_write_nested(&key->sem, 1);
 | 
						down_write_nested(&key->sem, 1);
 | 
				
			||||||
	if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
 | 
						if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags)) {
 | 
				
			||||||
	    key->type->revoke)
 | 
							notify_key(key, NOTIFY_KEY_REVOKED, 0);
 | 
				
			||||||
		key->type->revoke(key);
 | 
							if (key->type->revoke)
 | 
				
			||||||
 | 
								key->type->revoke(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* set the death time to no more than the expiry time */
 | 
							/* set the death time to no more than the expiry time */
 | 
				
			||||||
	time = ktime_get_real_seconds();
 | 
							time = ktime_get_real_seconds();
 | 
				
			||||||
	if (key->revoked_at == 0 || key->revoked_at > time) {
 | 
							if (key->revoked_at == 0 || key->revoked_at > time) {
 | 
				
			||||||
		key->revoked_at = time;
 | 
								key->revoked_at = time;
 | 
				
			||||||
		key_schedule_gc(key->revoked_at + key_gc_delay);
 | 
								key_schedule_gc(key->revoked_at + key_gc_delay);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	up_write(&key->sem);
 | 
						up_write(&key->sem);
 | 
				
			||||||
| 
						 | 
					@ -1087,8 +1095,10 @@ void key_invalidate(struct key *key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
 | 
						if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
 | 
				
			||||||
		down_write_nested(&key->sem, 1);
 | 
							down_write_nested(&key->sem, 1);
 | 
				
			||||||
		if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags))
 | 
							if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
 | 
				
			||||||
 | 
								notify_key(key, NOTIFY_KEY_INVALIDATED, 0);
 | 
				
			||||||
			key_schedule_gc_links();
 | 
								key_schedule_gc_links();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		up_write(&key->sem);
 | 
							up_write(&key->sem);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,9 @@ static const unsigned char keyrings_capabilities[2] = {
 | 
				
			||||||
	       KEYCTL_CAPS0_MOVE
 | 
						       KEYCTL_CAPS0_MOVE
 | 
				
			||||||
	       ),
 | 
						       ),
 | 
				
			||||||
	[1] = (KEYCTL_CAPS1_NS_KEYRING_NAME |
 | 
						[1] = (KEYCTL_CAPS1_NS_KEYRING_NAME |
 | 
				
			||||||
	       KEYCTL_CAPS1_NS_KEY_TAG),
 | 
						       KEYCTL_CAPS1_NS_KEY_TAG |
 | 
				
			||||||
 | 
						       (IS_ENABLED(CONFIG_KEY_NOTIFICATIONS)	? KEYCTL_CAPS1_NOTIFICATIONS : 0)
 | 
				
			||||||
 | 
						       ),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int key_get_type_from_user(char *type,
 | 
					static int key_get_type_from_user(char *type,
 | 
				
			||||||
| 
						 | 
					@ -1039,6 +1041,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 | 
				
			||||||
	if (group != (gid_t) -1)
 | 
						if (group != (gid_t) -1)
 | 
				
			||||||
		key->gid = gid;
 | 
							key->gid = gid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						notify_key(key, NOTIFY_KEY_SETATTR, 0);
 | 
				
			||||||
	ret = 0;
 | 
						ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
error_put:
 | 
					error_put:
 | 
				
			||||||
| 
						 | 
					@ -1089,6 +1092,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
 | 
				
			||||||
	/* if we're not the sysadmin, we can only change a key that we own */
 | 
						/* if we're not the sysadmin, we can only change a key that we own */
 | 
				
			||||||
	if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
 | 
						if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
 | 
				
			||||||
		key->perm = perm;
 | 
							key->perm = perm;
 | 
				
			||||||
 | 
							notify_key(key, NOTIFY_KEY_SETATTR, 0);
 | 
				
			||||||
		ret = 0;
 | 
							ret = 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1480,10 +1484,12 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
 | 
				
			||||||
okay:
 | 
					okay:
 | 
				
			||||||
	key = key_ref_to_ptr(key_ref);
 | 
						key = key_ref_to_ptr(key_ref);
 | 
				
			||||||
	ret = 0;
 | 
						ret = 0;
 | 
				
			||||||
	if (test_bit(KEY_FLAG_KEEP, &key->flags))
 | 
						if (test_bit(KEY_FLAG_KEEP, &key->flags)) {
 | 
				
			||||||
		ret = -EPERM;
 | 
							ret = -EPERM;
 | 
				
			||||||
	else
 | 
						} else {
 | 
				
			||||||
		key_set_timeout(key, timeout);
 | 
							key_set_timeout(key, timeout);
 | 
				
			||||||
 | 
							notify_key(key, NOTIFY_KEY_SETATTR, 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	key_put(key);
 | 
						key_put(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
error:
 | 
					error:
 | 
				
			||||||
| 
						 | 
					@ -1757,6 +1763,90 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
 | 
				
			||||||
	return ret;
 | 
						return ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_KEY_NOTIFICATIONS
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Watch for changes to a key.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The caller must have View permission to watch a key or keyring.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct watch_queue *wqueue;
 | 
				
			||||||
 | 
						struct watch_list *wlist = NULL;
 | 
				
			||||||
 | 
						struct watch *watch = NULL;
 | 
				
			||||||
 | 
						struct key *key;
 | 
				
			||||||
 | 
						key_ref_t key_ref;
 | 
				
			||||||
 | 
						long ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (watch_id < -1 || watch_id > 0xff)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_NEED_VIEW);
 | 
				
			||||||
 | 
						if (IS_ERR(key_ref))
 | 
				
			||||||
 | 
							return PTR_ERR(key_ref);
 | 
				
			||||||
 | 
						key = key_ref_to_ptr(key_ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wqueue = get_watch_queue(watch_queue_fd);
 | 
				
			||||||
 | 
						if (IS_ERR(wqueue)) {
 | 
				
			||||||
 | 
							ret = PTR_ERR(wqueue);
 | 
				
			||||||
 | 
							goto err_key;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (watch_id >= 0) {
 | 
				
			||||||
 | 
							ret = -ENOMEM;
 | 
				
			||||||
 | 
							if (!key->watchers) {
 | 
				
			||||||
 | 
								wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
 | 
				
			||||||
 | 
								if (!wlist)
 | 
				
			||||||
 | 
									goto err_wqueue;
 | 
				
			||||||
 | 
								init_watch_list(wlist, NULL);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							watch = kzalloc(sizeof(*watch), GFP_KERNEL);
 | 
				
			||||||
 | 
							if (!watch)
 | 
				
			||||||
 | 
								goto err_wlist;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							init_watch(watch, wqueue);
 | 
				
			||||||
 | 
							watch->id	= key->serial;
 | 
				
			||||||
 | 
							watch->info_id	= (u32)watch_id << WATCH_INFO_ID__SHIFT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ret = security_watch_key(key);
 | 
				
			||||||
 | 
							if (ret < 0)
 | 
				
			||||||
 | 
								goto err_watch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							down_write(&key->sem);
 | 
				
			||||||
 | 
							if (!key->watchers) {
 | 
				
			||||||
 | 
								key->watchers = wlist;
 | 
				
			||||||
 | 
								wlist = NULL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ret = add_watch_to_object(watch, key->watchers);
 | 
				
			||||||
 | 
							up_write(&key->sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (ret == 0)
 | 
				
			||||||
 | 
								watch = NULL;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ret = -EBADSLT;
 | 
				
			||||||
 | 
							if (key->watchers) {
 | 
				
			||||||
 | 
								down_write(&key->sem);
 | 
				
			||||||
 | 
								ret = remove_watch_from_object(key->watchers,
 | 
				
			||||||
 | 
											       wqueue, key_serial(key),
 | 
				
			||||||
 | 
											       false);
 | 
				
			||||||
 | 
								up_write(&key->sem);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					err_watch:
 | 
				
			||||||
 | 
						kfree(watch);
 | 
				
			||||||
 | 
					err_wlist:
 | 
				
			||||||
 | 
						kfree(wlist);
 | 
				
			||||||
 | 
					err_wqueue:
 | 
				
			||||||
 | 
						put_watch_queue(wqueue);
 | 
				
			||||||
 | 
					err_key:
 | 
				
			||||||
 | 
						key_put(key);
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif /* CONFIG_KEY_NOTIFICATIONS */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Get keyrings subsystem capabilities.
 | 
					 * Get keyrings subsystem capabilities.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -1926,6 +2016,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
 | 
				
			||||||
	case KEYCTL_CAPABILITIES:
 | 
						case KEYCTL_CAPABILITIES:
 | 
				
			||||||
		return keyctl_capabilities((unsigned char __user *)arg2, (size_t)arg3);
 | 
							return keyctl_capabilities((unsigned char __user *)arg2, (size_t)arg3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case KEYCTL_WATCH_KEY:
 | 
				
			||||||
 | 
							return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return -EOPNOTSUPP;
 | 
							return -EOPNOTSUPP;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1056,12 +1056,14 @@ int keyring_restrict(key_ref_t keyring_ref, const char *type,
 | 
				
			||||||
	down_write(&keyring->sem);
 | 
						down_write(&keyring->sem);
 | 
				
			||||||
	down_write(&keyring_serialise_restrict_sem);
 | 
						down_write(&keyring_serialise_restrict_sem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (keyring->restrict_link)
 | 
						if (keyring->restrict_link) {
 | 
				
			||||||
		ret = -EEXIST;
 | 
							ret = -EEXIST;
 | 
				
			||||||
	else if (keyring_detect_restriction_cycle(keyring, restrict_link))
 | 
						} else if (keyring_detect_restriction_cycle(keyring, restrict_link)) {
 | 
				
			||||||
		ret = -EDEADLK;
 | 
							ret = -EDEADLK;
 | 
				
			||||||
	else
 | 
						} else {
 | 
				
			||||||
		keyring->restrict_link = restrict_link;
 | 
							keyring->restrict_link = restrict_link;
 | 
				
			||||||
 | 
							notify_key(keyring, NOTIFY_KEY_SETATTR, 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	up_write(&keyring_serialise_restrict_sem);
 | 
						up_write(&keyring_serialise_restrict_sem);
 | 
				
			||||||
	up_write(&keyring->sem);
 | 
						up_write(&keyring->sem);
 | 
				
			||||||
| 
						 | 
					@ -1362,12 +1364,14 @@ int __key_link_check_live_key(struct key *keyring, struct key *key)
 | 
				
			||||||
 * holds at most one link to any given key of a particular type+description
 | 
					 * holds at most one link to any given key of a particular type+description
 | 
				
			||||||
 * combination.
 | 
					 * combination.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void __key_link(struct key *key, struct assoc_array_edit **_edit)
 | 
					void __key_link(struct key *keyring, struct key *key,
 | 
				
			||||||
 | 
							struct assoc_array_edit **_edit)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	__key_get(key);
 | 
						__key_get(key);
 | 
				
			||||||
	assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key));
 | 
						assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key));
 | 
				
			||||||
	assoc_array_apply_edit(*_edit);
 | 
						assoc_array_apply_edit(*_edit);
 | 
				
			||||||
	*_edit = NULL;
 | 
						*_edit = NULL;
 | 
				
			||||||
 | 
						notify_key(keyring, NOTIFY_KEY_LINKED, key_serial(key));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					@ -1451,7 +1455,7 @@ int key_link(struct key *keyring, struct key *key)
 | 
				
			||||||
	if (ret == 0)
 | 
						if (ret == 0)
 | 
				
			||||||
		ret = __key_link_check_live_key(keyring, key);
 | 
							ret = __key_link_check_live_key(keyring, key);
 | 
				
			||||||
	if (ret == 0)
 | 
						if (ret == 0)
 | 
				
			||||||
		__key_link(key, &edit);
 | 
							__key_link(keyring, key, &edit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
error_end:
 | 
					error_end:
 | 
				
			||||||
	__key_link_end(keyring, &key->index_key, edit);
 | 
						__key_link_end(keyring, &key->index_key, edit);
 | 
				
			||||||
| 
						 | 
					@ -1503,6 +1507,7 @@ static void __key_unlink(struct key *keyring, struct key *key,
 | 
				
			||||||
			 struct assoc_array_edit **_edit)
 | 
								 struct assoc_array_edit **_edit)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	assoc_array_apply_edit(*_edit);
 | 
						assoc_array_apply_edit(*_edit);
 | 
				
			||||||
 | 
						notify_key(keyring, NOTIFY_KEY_UNLINKED, key_serial(key));
 | 
				
			||||||
	*_edit = NULL;
 | 
						*_edit = NULL;
 | 
				
			||||||
	key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
 | 
						key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1621,7 +1626,7 @@ int key_move(struct key *key,
 | 
				
			||||||
		goto error;
 | 
							goto error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	__key_unlink(from_keyring, key, &from_edit);
 | 
						__key_unlink(from_keyring, key, &from_edit);
 | 
				
			||||||
	__key_link(key, &to_edit);
 | 
						__key_link(to_keyring, key, &to_edit);
 | 
				
			||||||
error:
 | 
					error:
 | 
				
			||||||
	__key_link_end(to_keyring, &key->index_key, to_edit);
 | 
						__key_link_end(to_keyring, &key->index_key, to_edit);
 | 
				
			||||||
	__key_unlink_end(from_keyring, key, from_edit);
 | 
						__key_unlink_end(from_keyring, key, from_edit);
 | 
				
			||||||
| 
						 | 
					@ -1655,6 +1660,7 @@ int keyring_clear(struct key *keyring)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		if (edit)
 | 
							if (edit)
 | 
				
			||||||
			assoc_array_apply_edit(edit);
 | 
								assoc_array_apply_edit(edit);
 | 
				
			||||||
 | 
							notify_key(keyring, NOTIFY_KEY_CLEARED, 0);
 | 
				
			||||||
		key_payload_reserve(keyring, 0);
 | 
							key_payload_reserve(keyring, 0);
 | 
				
			||||||
		ret = 0;
 | 
							ret = 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -418,7 +418,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 | 
				
			||||||
		goto key_already_present;
 | 
							goto key_already_present;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (dest_keyring)
 | 
						if (dest_keyring)
 | 
				
			||||||
		__key_link(key, &edit);
 | 
							__key_link(dest_keyring, key, &edit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_unlock(&key_construction_mutex);
 | 
						mutex_unlock(&key_construction_mutex);
 | 
				
			||||||
	if (dest_keyring)
 | 
						if (dest_keyring)
 | 
				
			||||||
| 
						 | 
					@ -437,7 +437,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
 | 
				
			||||||
	if (dest_keyring) {
 | 
						if (dest_keyring) {
 | 
				
			||||||
		ret = __key_link_check_live_key(dest_keyring, key);
 | 
							ret = __key_link_check_live_key(dest_keyring, key);
 | 
				
			||||||
		if (ret == 0)
 | 
							if (ret == 0)
 | 
				
			||||||
			__key_link(key, &edit);
 | 
								__key_link(dest_keyring, key, &edit);
 | 
				
			||||||
		__key_link_end(dest_keyring, &ctx->index_key, edit);
 | 
							__key_link_end(dest_keyring, &ctx->index_key, edit);
 | 
				
			||||||
		if (ret < 0)
 | 
							if (ret < 0)
 | 
				
			||||||
			goto link_check_failed;
 | 
								goto link_check_failed;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue