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:
David Howells
2020-01-14 17:07:11 +00:00
parent 998f50407f
commit f7e47677e3
12 changed files with 269 additions and 27 deletions

View File

@@ -37,7 +37,9 @@ static const unsigned char keyrings_capabilities[2] = {
KEYCTL_CAPS0_MOVE
),
[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,
@@ -1039,6 +1041,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
if (group != (gid_t) -1)
key->gid = gid;
notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
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 (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
key->perm = perm;
notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
}
@@ -1480,10 +1484,12 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
okay:
key = key_ref_to_ptr(key_ref);
ret = 0;
if (test_bit(KEY_FLAG_KEEP, &key->flags))
if (test_bit(KEY_FLAG_KEEP, &key->flags)) {
ret = -EPERM;
else
} else {
key_set_timeout(key, timeout);
notify_key(key, NOTIFY_KEY_SETATTR, 0);
}
key_put(key);
error:
@@ -1757,6 +1763,90 @@ error:
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.
*/
@@ -1926,6 +2016,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
case KEYCTL_CAPABILITIES:
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:
return -EOPNOTSUPP;
}