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:
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user