Skip to content

Commit

Permalink
watch_queue: Add a key/keyring notification facility
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: James Morris <[email protected]>
  • Loading branch information
dhowells committed May 19, 2020
1 parent 998f504 commit f7e4767
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 28 deletions.
57 changes: 57 additions & 0 deletions Documentation/security/keys/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,63 @@ The keyctl syscall functions are:
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
===============

Expand Down
3 changes: 3 additions & 0 deletions include/linux/key.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ struct key {
struct list_head graveyard_link;
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 key_user *user; /* owner of this key */
void *security; /* security data for this key */
Expand Down
2 changes: 2 additions & 0 deletions include/uapi/linux/keyctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
#define KEYCTL_RESTRICT_KEYRING 29 /* Restrict keys allowed to link to a keyring */
#define KEYCTL_MOVE 30 /* Move keys between keyrings */
#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 */
struct keyctl_dh_params {
Expand Down Expand Up @@ -130,5 +131,6 @@ struct keyctl_pkey_params {
#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_KEY_TAG 0x02 /* Key indexing can include a namespace tag */
#define KEYCTL_CAPS1_NOTIFICATIONS 0x04 /* Keys generate watchable notifications */

#endif /* _LINUX_KEYCTL_H */
28 changes: 27 additions & 1 deletion include/uapi/linux/watch_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

enum watch_notification_type {
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 {
Expand Down Expand Up @@ -75,4 +76,29 @@ struct watch_notification_removal {
__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 */
9 changes: 9 additions & 0 deletions security/keys/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,12 @@ config KEY_DH_OPERATIONS
in the kernel.

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.
3 changes: 3 additions & 0 deletions security/keys/compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_CAPABILITIES:
return keyctl_capabilities(compat_ptr(arg2), arg3);

case KEYCTL_WATCH_KEY:
return keyctl_watch_key(arg2, arg3, arg4);

default:
return -EOPNOTSUPP;
}
Expand Down
5 changes: 5 additions & 0 deletions security/keys/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
kdebug("- %u", key->serial);
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 */
if (state == KEY_IS_POSITIVE && key->type->destroy)
key->type->destroy(key);
Expand Down
30 changes: 29 additions & 1 deletion security/keys/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <linux/task_work.h>
#include <linux/keyctl.h>
#include <linux/refcount.h>
#include <linux/watch_queue.h>
#include <linux/compat.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
Expand Down Expand Up @@ -99,7 +100,8 @@ extern int __key_link_begin(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit **_edit);
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,
const struct keyring_index_key *index_key,
struct assoc_array_edit *edit);
Expand Down Expand Up @@ -183,6 +185,23 @@ extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred,
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.
*/
Expand Down Expand Up @@ -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);

#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
*/
Expand Down
38 changes: 24 additions & 14 deletions security/keys/key.c
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ static int __key_instantiate_and_link(struct key *key,
/* mark the key as being instantiated */
atomic_inc(&key->user->nikeys);
mark_key_instantiated(key, 0);
notify_key(key, NOTIFY_KEY_INSTANTIATED, 0);

if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
awaken = 1;
Expand All @@ -453,7 +454,7 @@ static int __key_instantiate_and_link(struct key *key,
if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
set_bit(KEY_FLAG_KEEP, &key->flags);

__key_link(key, _edit);
__key_link(keyring, key, _edit);
}

/* disable the authorisation key */
Expand Down Expand Up @@ -601,6 +602,7 @@ int key_reject_and_link(struct key *key,
/* mark the key as being negatively instantiated */
atomic_inc(&key->user->nikeys);
mark_key_instantiated(key, -error);
notify_key(key, NOTIFY_KEY_INSTANTIATED, -error);
key->expiry = ktime_get_real_seconds() + timeout;
key_schedule_gc(key->expiry + key_gc_delay);

Expand All @@ -611,7 +613,7 @@ int key_reject_and_link(struct key *key,

/* and link it into the destination keyring */
if (keyring && link_ret == 0)
__key_link(key, &edit);
__key_link(keyring, key, &edit);

/* disable the authorisation key */
if (authkey)
Expand Down Expand Up @@ -764,9 +766,11 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
down_write(&key->sem);

ret = key->type->update(key, prep);
if (ret == 0)
if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
notify_key(key, NOTIFY_KEY_UPDATED, 0);
}

up_write(&key->sem);

Expand Down Expand Up @@ -1023,9 +1027,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
down_write(&key->sem);

ret = key->type->update(key, &prep);
if (ret == 0)
if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
notify_key(key, NOTIFY_KEY_UPDATED, 0);
}

up_write(&key->sem);

Expand Down Expand Up @@ -1057,15 +1063,17 @@ void key_revoke(struct key *key)
* instantiated
*/
down_write_nested(&key->sem, 1);
if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
key->type->revoke)
key->type->revoke(key);

/* set the death time to no more than the expiry time */
time = ktime_get_real_seconds();
if (key->revoked_at == 0 || key->revoked_at > time) {
key->revoked_at = time;
key_schedule_gc(key->revoked_at + key_gc_delay);
if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags)) {
notify_key(key, NOTIFY_KEY_REVOKED, 0);
if (key->type->revoke)
key->type->revoke(key);

/* set the death time to no more than the expiry time */
time = ktime_get_real_seconds();
if (key->revoked_at == 0 || key->revoked_at > time) {
key->revoked_at = time;
key_schedule_gc(key->revoked_at + key_gc_delay);
}
}

up_write(&key->sem);
Expand All @@ -1087,8 +1095,10 @@ void key_invalidate(struct key *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))
if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
notify_key(key, NOTIFY_KEY_INVALIDATED, 0);
key_schedule_gc_links();
}
up_write(&key->sem);
}
}
Expand Down
Loading

0 comments on commit f7e4767

Please sign in to comment.