diff options
author | Mark Brown <broonie@kernel.org> | 2019-09-20 14:30:18 +0100 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2019-09-20 14:30:24 +0100 |
commit | a99598ea9a48f0fcf459bb32a114a9699aa975c8 (patch) | |
tree | a716ac7e55b1c22cffb820124e7493ac2f8d45f8 | |
parent | b37bdda47743adf2b7f084cd9dcb6ef1f043c164 (diff) | |
parent | 1f96e0f129eb2bea15a00c154eee8b85aa181d1a (diff) | |
download | linux-next-a99598ea9a48f0fcf459bb32a114a9699aa975c8.tar.gz |
Merge remote-tracking branch 'keys/keys-next'
97 files changed, 3632 insertions, 366 deletions
diff --git a/Documentation/ioctl/ioctl-number.rst b/Documentation/ioctl/ioctl-number.rst index bef79cd4c6b4..303fe17d871e 100644 --- a/Documentation/ioctl/ioctl-number.rst +++ b/Documentation/ioctl/ioctl-number.rst @@ -202,6 +202,7 @@ Code Seq# Include File Comments 'W' 00-1F linux/wanrouter.h conflict! (pre 3.9) 'W' 00-3F sound/asound.h conflict! 'W' 40-5F drivers/pci/switch/switchtec.c +'W' 60-61 linux/watch_queue.h 'X' all fs/xfs/xfs_fs.h, conflict! fs/xfs/linux-2.6/xfs_ioctl32.h, include/linux/falloc.h, diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst index d6d8b0b756b6..8a76330ac65a 100644 --- a/Documentation/security/keys/core.rst +++ b/Documentation/security/keys/core.rst @@ -57,9 +57,9 @@ Each key has a number of attributes: type provides an operation to perform a match between the description on a key and a criterion string. - * Each key has an owner user ID, a group ID and a permissions mask. These - are used to control what a process may do to a key from userspace, and - whether a kernel service will be able to find the key. + * Each key has an owner user ID, a group ID and an ACL. These are used to + control what a process may do to a key from userspace, and whether a + kernel service will be able to find the key. * Each key can be set to expire at a specific time by the key type's instantiation function. Keys can also be immortal. @@ -198,43 +198,110 @@ The key service provides a number of features besides keys: Key Access Permissions ====================== -Keys have an owner user ID, a group access ID, and a permissions mask. The mask -has up to eight bits each for possessor, user, group and other access. Only -six of each set of eight bits are defined. These permissions granted are: +Keys have an owner user ID, a group ID and an ACL. The ACL is made up of a +sequence of ACEs that each contain three elements: - * View + * The type of subject. + * The subject. - This permits a key or keyring's attributes to be viewed - including key - type and description. + These two together indicate the subject to whom the permits are granted. + The type can be one of: - * Read + * ``KEY_ACE_SUBJ_STANDARD`` - This permits a key's payload to be viewed or a keyring's list of linked - keys. + The subject is a standard 'macro' type. The subject can be one of: + + * ``KEY_ACE_EVERYONE`` + + The permits are granted to everyone. It replaces the old 'other' + type on the assumption that you wouldn't grant a permission to other + that you you wouldn't grant to everyone else. + + * ``KEY_ACE_OWNER`` + + The permits are granted to the owner of the key (key->uid). + + * ``KEY_ACE_GROUP`` + + The permits are granted to the key's group (key->gid). + + * ``KEY_ACE_POSSESSOR`` + + The permits are granted to anyone who possesses the key. + + * The set of permits granted to the subject. These include: + + * ``KEY_ACE_VIEW`` + + This permits a key or keyring's attributes to be viewed - including the + key type and description. - * Write + * ``KEY_ACE_READ`` - This permits a key's payload to be instantiated or updated, or it allows a - link to be added to or removed from a keyring. + This permits a key's payload to be viewed or a keyring's list of linked + keys. - * Search + * ``KEY_ACE_WRITE`` - This permits keyrings to be searched and keys to be found. Searches can - only recurse into nested keyrings that have search permission set. + This permits a key's payload to be instantiated or updated, or it allows + a link to be added to or removed from a keyring. - * Link + * ``KEY_ACE_SEARCH`` - This permits a key or keyring to be linked to. To create a link from a - keyring to a key, a process must have Write permission on the keyring and - Link permission on the key. + This permits keyrings to be searched and keys to be found. Searches can + only recurse into nested keyrings that have search permission set. - * Set Attribute + * ``KEY_ACE_LINK`` - This permits a key's UID, GID and permissions mask to be changed. + This permits a key or keyring to be linked to. To create a link from a + keyring to a key, a process must have Write permission on the keyring + and Link permission on the key. + + * ``KEY_ACE_SET_SECURITY`` + + This permits a key's UID, GID and permissions mask to be changed. + + * ``KEY_ACE_INVAL`` + + This permits a key to be invalidated with KEYCTL_INVALIDATE. + + * ``KEY_ACE_REVOKE`` + + This permits a key to be revoked with KEYCTL_REVOKE. + + * ``KEY_ACE_JOIN`` + + This permits a keyring to be joined as a session by + KEYCTL_JOIN_SESSION_KEYRING or KEYCTL_SESSION_TO_PARENT. + + * ``KEY_ACE_CLEAR`` + + This permits a keyring to be cleared. For changing the ownership, group ID or permissions mask, being the owner of the key or having the sysadmin capability is sufficient. +The legacy KEYCTL_SETPERM and KEYCTL_DESCRIBE functions can only see/generate +View, Read, Write, Search, Link and SetAttr permits, and do this for each of +possessor, user, group and other permission sets as a 32-bit flag mask. These +will be approximated/inferred: + + SETPERM Permit Implied ACE Permit + =============== ======================= + Search Inval, Join + Write Revoke, Clear + Setattr Set Security, Revoke + + ACE Permit Described as + =============== ======================= + Inval Search + Join Search + Revoke Write (unless Setattr) + Clear write + Set Security Setattr + +'Other' will be approximated as/inferred from the 'Everyone' subject. + SELinux Support =============== @@ -833,6 +900,7 @@ The keyctl syscall functions are: A process must have search permission on the key for this function to be successful. + * Compute a Diffie-Hellman shared secret or public key:: long keyctl(KEYCTL_DH_COMPUTE, struct keyctl_dh_params *params, @@ -1026,6 +1094,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 =============== @@ -1084,7 +1209,8 @@ payload contents" for more information. struct key *request_key(const struct key_type *type, const char *description, - const char *callout_info); + const char *callout_info, + struct key_acl *acl); This is used to request a key or keyring with a description that matches the description specified according to the key type's match_preparse() @@ -1099,6 +1225,8 @@ payload contents" for more information. If successful, the key will have been attached to the default keyring for implicitly obtained request-key keys, as set by KEYCTL_SET_REQKEY_KEYRING. + If a key is created, it will be given the specified ACL. + See also Documentation/security/keys/request-key.rst. @@ -1107,7 +1235,8 @@ payload contents" for more information. struct key *request_key_tag(const struct key_type *type, const char *description, struct key_tag *domain_tag, - const char *callout_info); + const char *callout_info, + struct key_acl *acl); This is identical to request_key(), except that a domain tag may be specifies that causes search algorithm to only match keys matching that @@ -1122,7 +1251,8 @@ payload contents" for more information. struct key_tag *domain_tag, const void *callout_info, size_t callout_len, - void *aux); + void *aux, + struct key_acl *acl); This is identical to request_key_tag(), except that the auxiliary data is passed to the key_type->request_key() op if it exists, and the @@ -1195,7 +1325,7 @@ payload contents" for more information. struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, const struct cred *cred, - key_perm_t perm, + struct key_acl *acl, struct key_restriction *restrict_link, unsigned long flags, struct key *dest); diff --git a/Documentation/security/keys/request-key.rst b/Documentation/security/keys/request-key.rst index 35f2296b704a..f356fd06c8d5 100644 --- a/Documentation/security/keys/request-key.rst +++ b/Documentation/security/keys/request-key.rst @@ -11,14 +11,16 @@ The process starts by either the kernel requesting a service by calling struct key *request_key(const struct key_type *type, const char *description, - const char *callout_info); + const char *callout_info, + struct key_acl *acl); or:: struct key *request_key_tag(const struct key_type *type, const char *description, const struct key_tag *domain_tag, - const char *callout_info); + const char *callout_info, + struct key_acl *acl); or:: @@ -27,7 +29,8 @@ or:: const struct key_tag *domain_tag, const char *callout_info, size_t callout_len, - void *aux); + void *aux, + struct key_acl *acl); or:: diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst new file mode 100644 index 000000000000..4087a8e670a8 --- /dev/null +++ b/Documentation/watch_queue.rst @@ -0,0 +1,460 @@ +============================ +Mappable notifications queue +============================ + +This is a misc device that acts as a mapped ring buffer by which userspace can +receive notifications from the kernel. This can be used in conjunction with:: + + * Key/keyring notifications + + * General device event notifications, including:: + + * Block layer event notifications + + * USB subsystem event notifications + + +The notifications buffers can be enabled by: + + "Device Drivers"/"Misc devices"/"Mappable notification queue" + (CONFIG_WATCH_QUEUE) + +This document has the following sections: + +.. contents:: :local: + + +Overview +======== + +This facility appears as a misc device file that is opened and then mapped and +polled. Each time it is opened, it creates a new buffer specific to the +returned file descriptor. Then, when the opening process sets watches, it +indicates the particular buffer it wants notifications from that watch to be +written into. Note that there are no read() and write() methods (except for +debugging). The user is expected to access the ring directly and to use poll +to wait for new data. + +If a watch is in place, notifications are only written into the buffer if the +filter criteria are passed and if there's sufficient space available in the +ring. If neither of those is so, a notification will be discarded. In the +latter case, an overrun indicator will also be set. + +Note that when producing a notification, the kernel does not wait for the +consumers to collect it, but rather just continues on. This means that +notifications can be generated whilst spinlocks are held and also protects the +kernel from being held up indefinitely by a userspace malfunction. + +As far as the ring goes, the head index belongs to the kernel and the tail +index belongs to userspace. The kernel will refuse to write anything if the +tail index becomes invalid. Userspace *must* use appropriate memory barriers +between reading or updating the tail index and reading the ring. + + +Record Structure +================ + +Notification records in the ring may occupy a variable number of slots within +the buffer, beginning with a 1-slot header:: + + struct watch_notification { + __u32 type:24; + __u32 subtype:8; + __u32 info; + } __attribute__((aligned(WATCH_LENGTH_GRANULARITY))); + +"type" indicates the source of the notification record and "subtype" indicates +the type of record from that source (see the Watch Sources section below). The +type may also be "WATCH_TYPE_META". This is a special record type generated +internally by the watch queue driver itself. There are two subtypes, one of +which indicates records that should be just skipped (padding or metadata): + + * WATCH_META_SKIP_NOTIFICATION + * WATCH_META_REMOVAL_NOTIFICATION + +The former indicates a record that should just be skipped and the latter +indicates that an object on which a watch was installed was removed or +destroyed. + +"info" indicates a bunch of things, including: + + * The length of the record in units of buffer slots (mask with + WATCH_INFO_LENGTH and shift by WATCH_INFO_LENGTH__SHIFT). This indicates + the size of the record, which may be between 1 and 63 slots. To turn this + into a number of bytes, multiply by WATCH_LENGTH_GRANULARITY. + + * The watch ID (mask with WATCH_INFO_ID and shift by WATCH_INFO_ID__SHIFT). + This indicates that caller's ID of the watch, which may be between 0 + and 255. Multiple watches may share a queue, and this provides a means to + distinguish them. + + * In the metadata header in slot 0, a flag (WATCH_INFO_NOTIFICATIONS_LOST) + that indicates that some notifications were lost for some reason, including + buffer overrun, insufficient memory and inconsistent tail index. + + * A type-specific field (WATCH_INFO_TYPE_INFO). This is set by the + notification producer to indicate some meaning specific to the type and + subtype. + +Everything in info apart from the length can be used for filtering. + + +Ring Structure +============== + +The ring is divided into slots of size WATCH_LENGTH_GRANULARITY (8 bytes). The +caller uses an ioctl() to set the size of the ring after opening and this must +be a power-of-2 multiple of the system page size (so that the mask can be used +with AND). + +The head and tail indices are stored in the first two slots in the ring, which +are marked out as a skippable entry:: + + struct watch_queue_buffer { + union { + struct { + struct watch_notification watch; + volatile __u32 head; + volatile __u32 tail; + __u32 mask; + } meta; + struct watch_notification slots[0]; + }; + }; + +In "meta.watch", type will be set to WATCH_TYPE_META and subtype to +WATCH_META_SKIP_NOTIFICATION so that anyone processing the buffer will just +skip this record. Also, because this record is here, records cannot wrap round +the end of the buffer, so a skippable padding element will be inserted at the +end of the buffer if needed. Thus the contents of a notification record in the +buffer are always contiguous. + +"meta.mask" is an AND'able mask to turn the index counters into slots array +indices. + +The buffer is empty if "meta.head" == "meta.tail". + +[!] NOTE that the ring indices "meta.head" and "meta.tail" are indices into +"slots[]" not byte offsets into the buffer. + +[!] NOTE that userspace must never change the head pointer. This belongs to +the kernel and will be updated by that. The kernel will never change the tail +pointer. + +[!] NOTE that userspace must never AND-off the tail pointer before updating it, +but should just keep adding to it and letting it wrap naturally. The value +*should* be masked off when used as an index into slots[]. + +[!] NOTE that if the distance between head and tail becomes too great, the +kernel will assume the buffer is full and write no more until the issue is +resolved. + + +Watch List (Notification Source) API +==================================== + +A "watch list" is a list of watchers that are subscribed to a source of +notifications. A list may be attached to an object (say a key or a superblock) +or may be global (say for device events). From a userspace perspective, a +non-global watch list is typically referred to by reference to the object it +belongs to (such as using KEYCTL_NOTIFY and giving it a key serial number to +watch that specific key). + +To manage a watch list, the following functions are provided: + + * ``void init_watch_list(struct watch_list *wlist, + void (*release_watch)(struct watch *wlist));`` + + Initialise a watch list. If ``release_watch`` is not NULL, then this + indicates a function that should be called when the watch_list object is + destroyed to discard any references the watch list holds on the watched + object. + + * ``void remove_watch_list(struct watch_list *wlist);`` + + This removes all of the watches subscribed to a watch_list and frees them + and then destroys the watch_list object itself. + + +Watch Queue (Notification Buffer) API +===================================== + +A "watch queue" is the buffer allocated by or on behalf of the application that +notification records will be written into. The workings of this are hidden +entirely inside of the watch_queue device driver, but it is necessary to gain a +reference to it to place a watch. These can be managed with: + + * ``struct watch_queue *get_watch_queue(int fd);`` + + Since watch queues are indicated to the kernel by the fd of the character + device that implements the buffer, userspace must hand that fd through a + system call. This can be used to look up an opaque pointer to the watch + queue from the system call. + + * ``void put_watch_queue(struct watch_queue *wqueue);`` + + This discards the reference obtained from ``get_watch_queue()``. + + +Watch Subscription API +====================== + +A "watch" is a subscription on a watch list, indicating the watch queue, and +thus the buffer, into which notification records should be written. The watch +queue object may also carry filtering rules for that object, as set by +userspace. Some parts of the watch struct can be set by the driver:: + + struct watch { + union { + u32 info_id; /* ID to be OR'd in to info field */ + ... + }; + void *private; /* Private data for the watched object */ + u64 id; /* Internal identifier */ + ... + }; + +The ``info_id`` value should be an 8-bit number obtained from userspace and +shifted by WATCH_INFO_ID__SHIFT. This is OR'd into the WATCH_INFO_ID field of +struct watch_notification::info when and if the notification is written into +the associated watch queue buffer. + +The ``private`` field is the driver's data associated with the watch_list and +is cleaned up by the ``watch_list::release_watch()`` method. + +The ``id`` field is the source's ID. Notifications that are posted with a +different ID are ignored. + +The following functions are provided to manage watches: + + * ``void init_watch(struct watch *watch, struct watch_queue *wqueue);`` + + Initialise a watch object, setting its pointer to the watch queue, using + appropriate barriering to avoid lockdep complaints. + + * ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);`` + + Subscribe a watch to a watch list (notification source). The + driver-settable fields in the watch struct must have been set before this + is called. + + * ``int remove_watch_from_object(struct watch_list *wlist, + struct watch_queue *wqueue, + u64 id, false);`` + + Remove a watch from a watch list, where the watch must match the specified + watch queue (``wqueue``) and object identifier (``id``). A notification + (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue to + indicate that the watch got removed. + + * ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);`` + + Remove all the watches from a watch list. It is expected that this will be + called preparatory to destruction and that the watch list will be + inaccessible to new watches by this point. A notification + (``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue of each + subscribed watch to indicate that the watch got removed. + + +Notification Posting API +======================== + +To post a notification to watch list so that the subscribed watches can see it, +the following function should be used:: + + void post_watch_notification(struct watch_list *wlist, + struct watch_notification *n, + const struct cred *cred, + u64 id); + +The notification should be preformatted and a pointer to the header (``n``) +should be passed in. The notification may be larger than this and the size in +units of buffer slots is noted in ``n->info & WATCH_INFO_LENGTH``. + +The ``cred`` struct indicates the credentials of the source (subject) and is +passed to the LSMs, such as SELinux, to allow or suppress the recording of the +note in each individual queue according to the credentials of that queue +(object). + +The ``id`` is the ID of the source object (such as the serial number on a key). +Only watches that have the same ID set in them will see this notification. + + +Global Device Watch List +======================== + +There is a global watch list that hardware generated events, such as device +connection, disconnection, failure and error can be posted upon. It must be +enabled using:: + + CONFIG_DEVICE_NOTIFICATIONS + +Watchpoints are set in userspace using the device_notify(2) system call. +Within the kernel events are posted upon it using:: + + void post_device_notification(struct watch_notification *n, u64 id); + +where ``n`` is the formatted notification record to post. ``id`` is an +identifier that can be used to direct to specific watches, but it should be 0 +for general use on this queue. + + +Watch Sources +============= + +Any particular buffer can be fed from multiple sources. Sources include: + + * WATCH_TYPE_KEY_NOTIFY + + Notifications of this type indicate changes to keys and keyrings, including + the changes of keyring contents or the attributes of keys. + + See Documentation/security/keys/core.rst for more information. + + * WATCH_TYPE_BLOCK_NOTIFY + + Notifications of this type indicate block layer events, such as I/O errors + or temporary link loss. Watches of this type are set on the global device + watch list. + + * WATCH_TYPE_USB_NOTIFY + + Notifications of this type indicate USB subsystem events, such as + attachment, removal, reset and I/O errors. Separate events are generated + for buses and devices. Watchpoints of this type are set on the global + device watch list. + + +Event Filtering +=============== + +Once a watch queue has been created, a set of filters can be applied to limit +the events that are received using:: + + struct watch_notification_filter filter = { + ... + }; + ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) + +The filter description is a variable of type:: + + struct watch_notification_filter { + __u32 nr_filters; + __u32 __reserved; + struct watch_notification_type_filter filters[]; + }; + +Where "nr_filters" is the number of filters in filters[] and "__reserved" +should be 0. The "filters" array has elements of the following type:: + + struct watch_notification_type_filter { + __u32 type; + __u32 info_filter; + __u32 info_mask; + __u32 subtype_filter[8]; + }; + +Where: + + * ``type`` is the event type to filter for and should be something like + "WATCH_TYPE_KEY_NOTIFY" + + * ``info_filter`` and ``info_mask`` act as a filter on the info field of the + notification record. The notification is only written into the buffer if:: + + (watch.info & info_mask) == info_filter + + This could be used, for example, to ignore events that are not exactly on + the watched point in a mount tree. + + * ``subtype_filter`` is a bitmask indicating the subtypes that are of + interest. Bit 0 of subtype_filter[0] corresponds to subtype 0, bit 1 to + subtype 1, and so on. + +If the argument to the ioctl() is NULL, then the filters will be removed and +all events from the watched sources will come through. + + +Waiting For Events +================== + +The file descriptor that holds the buffer may be used with poll() and similar. +POLLIN and POLLRDNORM are set if the buffer indices differ. POLLERR is set if +the buffer indices are further apart than the size of the buffer. Wake-up +events are only generated if the buffer is transitioned from an empty state. + + +Userspace Code Example +====================== + +A buffer is created with something like the following:: + + fd = open("/dev/watch_queue", O_RDWR); + + #define BUF_SIZE 4 + ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE); + + page_size = sysconf(_SC_PAGESIZE); + buf = mmap(NULL, BUF_SIZE * page_size, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + +It can then be set to receive keyring change notifications and device event +notifications:: + + keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fd, 0x01); + + watch_devices(fd, 0x2); + +The notifications can then be consumed by something like the following:: + + extern void saw_key_change(struct watch_notification *n); + extern void saw_block_event(struct watch_notification *n); + extern void saw_usb_event(struct watch_notification *n); + + static int consumer(int fd, struct watch_queue_buffer *buf) + { + struct watch_notification *n; + struct pollfd p[1]; + unsigned int len, head, tail, mask = buf->meta.mask; + + for (;;) { + p[0].fd = fd; + p[0].events = POLLIN | POLLERR; + p[0].revents = 0; + + if (poll(p, 1, -1) == -1 || p[0].revents & POLLERR) + goto went_wrong; + + while (head = _atomic_load_acquire(buf->meta.head), + tail = buf->meta.tail, + tail != head + ) { + n = &buf->slots[tail & mask]; + len = (n->info & WATCH_INFO_LENGTH) >> + WATCH_INFO_LENGTH__SHIFT; + if (len == 0) + goto went_wrong; + + switch (n->type) { + case WATCH_TYPE_KEY_NOTIFY: + saw_key_change(n); + break; + case WATCH_TYPE_BLOCK_NOTIFY: + saw_block_event(n); + break; + case WATCH_TYPE_USB_NOTIFY: + saw_usb_event(n); + break; + } + + tail += len; + _atomic_store_release(buf->meta.tail, tail); + } + } + + went_wrong: + return 0; + } + +Note the memory barriers when loading the head pointer and storing the tail +pointer! diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl index 728fe028c02c..8e841d8e4c22 100644 --- a/arch/alpha/kernel/syscalls/syscall.tbl +++ b/arch/alpha/kernel/syscalls/syscall.tbl @@ -475,3 +475,4 @@ 543 common fspick sys_fspick 544 common pidfd_open sys_pidfd_open # 545 reserved for clone3 +546 common watch_devices sys_watch_devices diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl index 6da7dc4d79cc..0f080cf44cc9 100644 --- a/arch/arm/tools/syscall.tbl +++ b/arch/arm/tools/syscall.tbl @@ -449,3 +449,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open 435 common clone3 sys_clone3 +436 common watch_devices sys_watch_devices diff --git a/arch/arm64/include/asm/unistd.h b/arch/arm64/include/asm/unistd.h index 2629a68b8724..368761302768 100644 --- a/arch/arm64/include/asm/unistd.h +++ b/arch/arm64/include/asm/unistd.h @@ -38,7 +38,7 @@ #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5) #define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800) -#define __NR_compat_syscalls 436 +#define __NR_compat_syscalls 437 #endif #define __ARCH_WANT_SYS_CLONE diff --git a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h index 94ab29cf4f00..b5310789ce7a 100644 --- a/arch/arm64/include/asm/unistd32.h +++ b/arch/arm64/include/asm/unistd32.h @@ -879,6 +879,8 @@ __SYSCALL(__NR_fspick, sys_fspick) __SYSCALL(__NR_pidfd_open, sys_pidfd_open) #define __NR_clone3 435 __SYSCALL(__NR_clone3, sys_clone3) +#define __NR_watch_devices 436 +__SYSCALL(__NR_watch_devices, sys_watch_devices) /* * Please add new compat syscalls above this comment and update diff --git a/arch/ia64/kernel/syscalls/syscall.tbl b/arch/ia64/kernel/syscalls/syscall.tbl index 36d5faf4c86c..2f33f5db2fed 100644 --- a/arch/ia64/kernel/syscalls/syscall.tbl +++ b/arch/ia64/kernel/syscalls/syscall.tbl @@ -356,3 +356,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open # 435 reserved for clone3 +436 common watch_devices sys_watch_devices diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl index a88a285a0e5f..83e4e8784b88 100644 --- a/arch/m68k/kernel/syscalls/syscall.tbl +++ b/arch/m68k/kernel/syscalls/syscall.tbl @@ -435,3 +435,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open # 435 reserved for clone3 +436 common watch_devices sys_watch_devices diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl index 09b0cd7dab0a..9a70a3be3b7b 100644 --- a/arch/microblaze/kernel/syscalls/syscall.tbl +++ b/arch/microblaze/kernel/syscalls/syscall.tbl @@ -441,3 +441,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open 435 common clone3 sys_clone3 +436 common watch_devices sys_watch_devices diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl index c9c879ec9b6d..2ba5b649f0ab 100644 --- a/arch/mips/kernel/syscalls/syscall_n32.tbl +++ b/arch/mips/kernel/syscalls/syscall_n32.tbl @@ -374,3 +374,4 @@ 433 n32 fspick sys_fspick 434 n32 pidfd_open sys_pidfd_open # 435 reserved for clone3 +436 n32 watch_devices sys_watch_devices diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl index bbce9159caa1..ff350988584d 100644 --- a/arch/mips/kernel/syscalls/syscall_n64.tbl +++ b/arch/mips/kernel/syscalls/syscall_n64.tbl @@ -350,3 +350,4 @@ 433 n64 fspick sys_fspick 434 n64 pidfd_open sys_pidfd_open # 435 reserved for clone3 +436 n64 watch_devices sys_watch_devices diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl index 9653591428ec..7b26bd39900e 100644 --- a/arch/mips/kernel/syscalls/syscall_o32.tbl +++ b/arch/mips/kernel/syscalls/syscall_o32.tbl @@ -423,3 +423,4 @@ 433 o32 fspick sys_fspick 434 o32 pidfd_open sys_pidfd_open # 435 reserved for clone3 +436 o32 watch_devices sys_watch_devices diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl index 285ff516150c..b64bbafa5919 100644 --- a/arch/parisc/kernel/syscalls/syscall.tbl +++ b/arch/parisc/kernel/syscalls/syscall.tbl @@ -433,3 +433,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open 435 common clone3 sys_clone3_wrapper +436 common watch_devices sys_watch_devices diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl index 43f736ed47f2..0a503239ab5c 100644 --- a/arch/powerpc/kernel/syscalls/syscall.tbl +++ b/arch/powerpc/kernel/syscalls/syscall.tbl @@ -517,3 +517,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open 435 nospu clone3 ppc_clone3 +436 common watch_devices sys_watch_devices diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl index 3054e9c035a3..19b43c0d928a 100644 --- a/arch/s390/kernel/syscalls/syscall.tbl +++ b/arch/s390/kernel/syscalls/syscall.tbl @@ -438,3 +438,4 @@ 433 common fspick sys_fspick sys_fspick 434 common pidfd_open sys_pidfd_open sys_pidfd_open 435 common clone3 sys_clone3 sys_clone3 +436 common watch_devices sys_watch_devices sys_watch_devices diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl index b5ed26c4c005..b454e07c9372 100644 --- a/arch/sh/kernel/syscalls/syscall.tbl +++ b/arch/sh/kernel/syscalls/syscall.tbl @@ -438,3 +438,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open # 435 reserved for clone3 +436 common watch_devices sys_watch_devices diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl index 8c8cc7537fb2..8ef43c27457e 100644 --- a/arch/sparc/kernel/syscalls/syscall.tbl +++ b/arch/sparc/kernel/syscalls/syscall.tbl @@ -481,3 +481,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open # 435 reserved for clone3 +436 common watch_devices sys_watch_devices diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl index 3fe02546aed3..9b225c0d5240 100644 --- a/arch/x86/entry/syscalls/syscall_32.tbl +++ b/arch/x86/entry/syscalls/syscall_32.tbl @@ -440,3 +440,4 @@ 433 i386 fspick sys_fspick __ia32_sys_fspick 434 i386 pidfd_open sys_pidfd_open __ia32_sys_pidfd_open 435 i386 clone3 sys_clone3 __ia32_sys_clone3 +436 i386 watch_devices sys_watch_devices __ia32_sys_watch_devices diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl index c29976eca4a8..29293d103829 100644 --- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl @@ -357,6 +357,7 @@ 433 common fspick __x64_sys_fspick 434 common pidfd_open __x64_sys_pidfd_open 435 common clone3 __x64_sys_clone3/ptregs +436 common watch_devices __x64_sys_watch_devices # # x32-specific system call numbers start at 512 to avoid cache impact diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl index 25f4de729a6d..243fa18b8d1e 100644 --- a/arch/xtensa/kernel/syscalls/syscall.tbl +++ b/arch/xtensa/kernel/syscalls/syscall.tbl @@ -406,3 +406,4 @@ 433 common fspick sys_fspick 434 common pidfd_open sys_pidfd_open 435 common clone3 sys_clone3 +436 common watch_devices sys_watch_devices diff --git a/block/Kconfig b/block/Kconfig index 41c0917ce622..0906227a9431 100644 --- a/block/Kconfig +++ b/block/Kconfig @@ -177,6 +177,15 @@ config BLK_SED_OPAL Enabling this option enables users to setup/unlock/lock Locking ranges for SED devices using the Opal protocol. +config BLK_NOTIFICATIONS + bool "Block layer event notifications" + depends on DEVICE_NOTIFICATIONS + help + This option provides support for getting block layer event + notifications. This makes use of the /dev/watch_queue misc device to + handle the notification buffer and provides the device_notify() system + call to enable/disable watches. + menu "Partition Types" source "block/partitions/Kconfig" diff --git a/block/blk-core.c b/block/blk-core.c index d5e668ec751b..08e9b12ff5a5 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -184,6 +184,22 @@ static const struct { [BLK_STS_IOERR] = { -EIO, "I/O" }, }; +#ifdef CONFIG_BLK_NOTIFICATIONS +static const +enum block_notification_type blk_notifications[ARRAY_SIZE(blk_errors)] = { + [BLK_STS_TIMEOUT] = NOTIFY_BLOCK_ERROR_TIMEOUT, + [BLK_STS_NOSPC] = NOTIFY_BLOCK_ERROR_NO_SPACE, + [BLK_STS_TRANSPORT] = NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT, + [BLK_STS_TARGET] = NOTIFY_BLOCK_ERROR_CRITICAL_TARGET, + [BLK_STS_NEXUS] = NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS, + [BLK_STS_MEDIUM] = NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM, + [BLK_STS_PROTECTION] = NOTIFY_BLOCK_ERROR_PROTECTION, + [BLK_STS_RESOURCE] = NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE, + [BLK_STS_DEV_RESOURCE] = NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE, + [BLK_STS_IOERR] = NOTIFY_BLOCK_ERROR_IO, +}; +#endif + blk_status_t errno_to_blk_status(int errno) { int i; @@ -224,6 +240,19 @@ static void print_req_error(struct request *req, blk_status_t status, req->cmd_flags & ~REQ_OP_MASK, req->nr_phys_segments, IOPRIO_PRIO_CLASS(req->ioprio)); + +#ifdef CONFIG_BLK_NOTIFICATIONS + if (blk_notifications[idx]) { + struct block_notification n = { + .watch.type = WATCH_TYPE_BLOCK_NOTIFY, + .watch.subtype = blk_notifications[idx], + .watch.info = watch_sizeof(n), + .dev = req->rq_disk ? disk_devt(req->rq_disk) : 0, + .sector = blk_rq_pos(req), + }; + post_block_notification(&n); + } +#endif } static void req_bio_endio(struct request *rq, struct bio *bio, diff --git a/certs/blacklist.c b/certs/blacklist.c index ec00bf337eb6..93d70b885f8e 100644 --- a/certs/blacklist.c +++ b/certs/blacklist.c @@ -89,8 +89,7 @@ int mark_hash_blacklisted(const char *hash) hash, NULL, 0, - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW), + &internal_key_acl, KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN); if (IS_ERR(key)) { @@ -149,9 +148,7 @@ static int __init blacklist_init(void) keyring_alloc(".blacklist", KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ | - KEY_USR_SEARCH, + &internal_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA | KEY_FLAG_KEEP, NULL, NULL); diff --git a/certs/system_keyring.c b/certs/system_keyring.c index 798291177186..73c23de6f593 100644 --- a/certs/system_keyring.c +++ b/certs/system_keyring.c @@ -99,9 +99,7 @@ static __init int system_trusted_keyring_init(void) builtin_trusted_keys = keyring_alloc(".builtin_trusted_keys", KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH), - KEY_ALLOC_NOT_IN_QUOTA, + &internal_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(builtin_trusted_keys)) panic("Can't allocate builtin trusted keyring\n"); @@ -110,10 +108,7 @@ static __init int system_trusted_keyring_init(void) secondary_trusted_keys = keyring_alloc(".secondary_trusted_keys", KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH | - KEY_USR_WRITE), - KEY_ALLOC_NOT_IN_QUOTA, + &internal_writable_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, get_builtin_and_secondary_restriction(), NULL); if (IS_ERR(secondary_trusted_keys)) @@ -163,8 +158,7 @@ static __init int load_system_certificate_list(void) NULL, p, plen, - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ), + &internal_key_acl, KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN | KEY_ALLOC_BYPASS_RESTRICTION); diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 28b92e3cc570..e37d37684132 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -1,6 +1,15 @@ # SPDX-License-Identifier: GPL-2.0 menu "Generic Driver Options" +config DEVICE_NOTIFICATIONS + bool "Provide device event notifications" + depends on WATCH_QUEUE + help + This option provides support for getting hardware event notifications + on devices, buses and interfaces. This makes use of the + /dev/watch_queue misc device to handle the notification buffer. + device_notify(2) is used to set/remove watches. + config UEVENT_HELPER bool "Support for uevent helper" help diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 157452080f3d..4db2e8f1a1f4 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -7,6 +7,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ attribute_container.o transport_class.o \ topology.o container.o property.o cacheinfo.o \ devcon.o swnode.o +obj-$(CONFIG_DEVICE_NOTIFICATIONS) += watch.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-y += power/ obj-$(CONFIG_ISA_BUS_API) += isa.o diff --git a/drivers/base/watch.c b/drivers/base/watch.c new file mode 100644 index 000000000000..725aaa24275b --- /dev/null +++ b/drivers/base/watch.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Event notifications. + * + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include <linux/device.h> +#include <linux/watch_queue.h> +#include <linux/syscalls.h> +#include <linux/init_task.h> +#include <linux/security.h> + +/* + * Global queue for watching for device layer events. + */ +static struct watch_list device_watchers = { + .watchers = HLIST_HEAD_INIT, + .lock = __SPIN_LOCK_UNLOCKED(&device_watchers.lock), +}; + +static DEFINE_SPINLOCK(device_watchers_lock); + +/** + * post_device_notification - Post notification of a device event + * @n - The notification to post + * @id - The device ID + * + * Note that there's only a global queue to which all events are posted. Might + * want to provide per-dev queues also. + */ +void post_device_notification(struct watch_notification *n, u64 id) +{ + post_watch_notification(&device_watchers, n, &init_cred, id); +} +EXPORT_SYMBOL(post_device_notification); + +/** + * sys_watch_devices - Watch for device events. + * @watch_fd: The watch queue to send notifications to. + * @watch_id: The watch ID to be placed in the notification (-1 to remove watch) + * @flags: Flags (reserved for future) + */ +SYSCALL_DEFINE3(watch_devices, int, watch_fd, int, watch_id, unsigned int, flags) +{ + struct watch_queue *wqueue; + struct watch *watch = NULL; + long ret = -ENOMEM; + + if (watch_id < -1 || watch_id > 0xff || flags) + return -EINVAL; + + wqueue = get_watch_queue(watch_fd); + if (IS_ERR(wqueue)) { + ret = PTR_ERR(wqueue); + goto err; + } + + if (watch_id >= 0) { + watch = kzalloc(sizeof(*watch), GFP_KERNEL); + if (!watch) + goto err_wqueue; + + init_watch(watch, wqueue); + watch->info_id = (u32)watch_id << WATCH_INFO_ID__SHIFT; + + ret = security_watch_devices(); + if (ret < 0) + goto err_watch; + + spin_lock(&device_watchers_lock); + ret = add_watch_to_object(watch, &device_watchers); + spin_unlock(&device_watchers_lock); + if (ret == 0) + watch = NULL; + } else { + spin_lock(&device_watchers_lock); + ret = remove_watch_from_object(&device_watchers, wqueue, 0, + false); + spin_unlock(&device_watchers_lock); + } + +err_watch: + kfree(watch); +err_wqueue: + put_watch_queue(wqueue); +err: + return ret; +} diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index f87f6495652f..390cb935bd41 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -1929,7 +1929,7 @@ static int crypt_set_keyring_key(struct crypt_config *cc, const char *key_string return -ENOMEM; key = request_key(key_string[0] == 'l' ? &key_type_logon : &key_type_user, - key_desc + 1, NULL); + key_desc + 1, NULL, NULL); if (IS_ERR(key)) { kzfree(new_key_string); return PTR_ERR(key); diff --git a/drivers/md/dm-verity-verify-sig.c b/drivers/md/dm-verity-verify-sig.c index 614e43db93aa..2ca162d43fe6 100644 --- a/drivers/md/dm-verity-verify-sig.c +++ b/drivers/md/dm-verity-verify-sig.c @@ -36,7 +36,7 @@ static int verity_verify_get_sig_from_key(const char *key_desc, int ret = 0; key = request_key(&key_type_user, - key_desc, NULL); + key_desc, NULL, NULL); if (IS_ERR(key)) return PTR_ERR(key); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c55b63750757..3ce00bf5cfe3 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -5,6 +5,19 @@ menu "Misc devices" +config WATCH_QUEUE + bool "Mappable notification queue" + default n + depends on MMU + help + This is a general notification queue for the kernel to pass events to + userspace through a mmap()'able ring buffer. It can be used in + conjunction with watches for key/keyring change notifications and device + notifications. + + Note that in theory this should work fine with NOMMU, but I'm not + sure how to make that work. + config SENSORS_LIS3LV02D tristate depends on INPUT diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c1860d35dc7e..028b9b6af7ac 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -3,6 +3,7 @@ # Makefile for misc devices that really don't fit anywhere else. # +obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_IBMVMC) += ibmvmc.o obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o diff --git a/drivers/misc/watch_queue.c b/drivers/misc/watch_queue.c new file mode 100644 index 000000000000..b3fc59b4ef6c --- /dev/null +++ b/drivers/misc/watch_queue.c @@ -0,0 +1,898 @@ +// SPDX-License-Identifier: GPL-2.0 +/* User-mappable watch queue + * + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * See Documentation/watch_queue.rst + */ + +#define pr_fmt(fmt) "watchq: " fmt +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/printk.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/poll.h> +#include <linux/uaccess.h> +#include <linux/vmalloc.h> +#include <linux/file.h> +#include <linux/security.h> +#include <linux/cred.h> +#include <linux/sched/signal.h> +#include <linux/watch_queue.h> + +MODULE_DESCRIPTION("Watch queue"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +struct watch_type_filter { + enum watch_notification_type type; + __u32 subtype_filter[1]; /* Bitmask of subtypes to filter on */ + __u32 info_filter; /* Filter on watch_notification::info */ + __u32 info_mask; /* Mask of relevant bits in info_filter */ +}; + +struct watch_filter { + union { + struct rcu_head rcu; + unsigned long type_filter[2]; /* Bitmask of accepted types */ + }; + u32 nr_filters; /* Number of filters */ + struct watch_type_filter filters[]; +}; + +struct watch_queue { + struct rcu_head rcu; + struct address_space mapping; + struct user_struct *owner; /* Owner of the queue for rlimit purposes */ + struct watch_filter __rcu *filter; + wait_queue_head_t waiters; + struct hlist_head watches; /* Contributory watches */ + struct kref usage; /* Object usage count */ + spinlock_t lock; + bool defunct; /* T when queues closed */ + u8 nr_pages; /* Size of pages[] */ + u8 flag_next; /* Flag to apply to next item */ + u32 size; + struct watch_queue_buffer *buffer; /* Pointer to first record */ + + /* The mappable pages. The zeroth page holds the ring pointers. */ + struct page **pages; +}; + +/* + * Write a notification of an event into an mmap'd queue and let the user know. + * Returns true if successful and false on failure (eg. buffer overrun or + * userspace mucked up the ring indices). + */ +static bool write_one_notification(struct watch_queue *wqueue, + struct watch_notification *n) +{ + struct watch_queue_buffer *buf = wqueue->buffer; + struct watch_notification *p; + unsigned int gran = WATCH_LENGTH_GRANULARITY; + unsigned int metalen = sizeof(buf->meta) / gran; + unsigned int size = wqueue->size, mask = size - 1; + unsigned int len; + unsigned int ring_tail, tail, head, used, gap, h; + + /* Barrier against userspace, ordering data read before tail read */ + ring_tail = READ_ONCE(buf->meta.tail); + + head = READ_ONCE(buf->meta.head); + used = head - ring_tail; + + /* Check to see if userspace mucked up the pointers */ + if (used >= size) + goto lost_event; /* Inconsistent */ + tail = ring_tail & mask; + if (tail > 0 && tail < metalen) + goto lost_event; /* Inconsistent */ + + len = (n->info & WATCH_INFO_LENGTH) >> WATCH_INFO_LENGTH__SHIFT; + h = head & mask; + if (h >= tail) { + /* Head is at or after tail in the buffer. There may then be + * two gaps: one to the end of buffer and one at the beginning + * of the buffer between the metadata block and the tail + * pointer. + */ + gap = size - h; + if (len > gap) { + /* Not enough space in the post-head gap; we need to + * wrap. When wrapping, we will have to skip the + * metadata at the beginning of the buffer. + */ + if (len > tail - metalen) + goto lost_event; /* Overrun */ + + /* Fill the space at the end of the page */ + p = &buf->slots[h]; + p->type = WATCH_TYPE_META; + p->subtype = WATCH_META_SKIP_NOTIFICATION; + p->info = gap << WATCH_INFO_LENGTH__SHIFT; + head += gap; + h = 0; + if (h >= tail) + goto lost_event; /* Overrun */ + } + } + + if (h == 0) { + /* Reset and skip the header metadata */ + p = &buf->meta.watch; + p->type = WATCH_TYPE_META; + p->subtype = WATCH_META_SKIP_NOTIFICATION; + p->info = metalen << WATCH_INFO_LENGTH__SHIFT; + head += metalen; + h = metalen; + if (h == tail) + goto lost_event; /* Overrun */ + } + + if (h < tail) { + /* Head is before tail in the buffer. */ + gap = tail - h; + if (len > gap) + goto lost_event; /* Overrun */ + } + + n->info |= wqueue->flag_next; + wqueue->flag_next = 0; + p = &buf->slots[h]; + memcpy(p, n, len * gran); + head += len; + + /* Barrier against userspace, ordering head update after data write. */ + smp_store_release(&buf->meta.head, head); + if (used == 0) + wake_up(&wqueue->waiters); + return true; + +lost_event: + WRITE_ONCE(buf->meta.watch.info, + buf->meta.watch.info | WATCH_INFO_NOTIFICATIONS_LOST); + return false; +} + +/* + * Post a notification to a watch queue. + */ +static bool post_one_notification(struct watch_queue *wqueue, + struct watch_notification *n) +{ + bool done = false; + + if (!wqueue->buffer) + return false; + + spin_lock_bh(&wqueue->lock); /* Protect head pointer */ + + if (!wqueue->defunct) + done = write_one_notification(wqueue, n); + spin_unlock_bh(&wqueue->lock); + return done; +} + +/* + * Apply filter rules to a notification. + */ +static bool filter_watch_notification(const struct watch_filter *wf, + const struct watch_notification *n) +{ + const struct watch_type_filter *wt; + unsigned int st_bits = sizeof(wt->subtype_filter[0]) * 8; + unsigned int st_index = n->subtype / st_bits; + unsigned int st_bit = 1U << (n->subtype % st_bits); + int i; + + if (!test_bit(n->type, wf->type_filter)) + return false; + + for (i = 0; i < wf->nr_filters; i++) { + wt = &wf->filters[i]; + if (n->type == wt->type && + (wt->subtype_filter[st_index] & st_bit) && + (n->info & wt->info_mask) == wt->info_filter) + return true; + } + + return false; /* If there is a filter, the default is to reject. */ +} + +/** + * __post_watch_notification - Post an event notification + * @wlist: The watch list to post the event to. + * @n: The notification record to post. + * @cred: The creds of the process that triggered the notification. + * @id: The ID to match on the watch. + * + * Post a notification of an event into a set of watch queues and let the users + * know. + * + * The size of the notification should be set in n->info & WATCH_INFO_LENGTH and + * should be in units of sizeof(*n). + */ +void __post_watch_notification(struct watch_list *wlist, + struct watch_notification *n, + const struct cred *cred, + u64 id) +{ + const struct watch_filter *wf; + struct watch_queue *wqueue; + struct watch *watch; + + if (((n->info & WATCH_INFO_LENGTH) >> WATCH_INFO_LENGTH__SHIFT) == 0) { + WARN_ON(1); + return; + } + + rcu_read_lock(); + + hlist_for_each_entry_rcu(watch, &wlist->watchers, list_node) { + if (watch->id != id) + continue; + n->info &= ~WATCH_INFO_ID; + n->info |= watch->info_id; + + wqueue = rcu_dereference(watch->queue); + wf = rcu_dereference(wqueue->filter); + if (wf && !filter_watch_notification(wf, n)) + continue; + + if (security_post_notification(watch->cred, cred, n) < 0) + continue; + + post_one_notification(wqueue, n); + } + + rcu_read_unlock(); +} +EXPORT_SYMBOL(__post_watch_notification); + +/* + * Allow the queue to be polled. + */ +static __poll_t watch_queue_poll(struct file *file, poll_table *wait) +{ + struct watch_queue *wqueue = file->private_data; + struct watch_queue_buffer *buf = wqueue->buffer; + unsigned int head, tail; + __poll_t mask = 0; + + if (!buf) + return EPOLLERR; + + poll_wait(file, &wqueue->waiters, wait); + + head = READ_ONCE(buf->meta.head); + tail = READ_ONCE(buf->meta.tail); + if (head != tail) + mask |= EPOLLIN | EPOLLRDNORM; + if (head - tail > wqueue->size) + mask |= EPOLLERR; + return mask; +} + +static int watch_queue_set_page_dirty(struct page *page) +{ + SetPageDirty(page); + return 0; +} + +static const struct address_space_operations watch_queue_aops = { + .set_page_dirty = watch_queue_set_page_dirty, +}; + +static vm_fault_t watch_queue_fault(struct vm_fault *vmf) +{ + struct watch_queue *wqueue = vmf->vma->vm_file->private_data; + struct page *page; + + page = wqueue->pages[vmf->pgoff]; + get_page(page); + if (!lock_page_or_retry(page, vmf->vma->vm_mm, vmf->flags)) { + put_page(page); + return VM_FAULT_RETRY; + } + vmf->page = page; + return VM_FAULT_LOCKED; +} + +static int watch_queue_account_mem(struct watch_queue *wqueue, + unsigned long nr_pages) +{ + struct user_struct *user = wqueue->owner; + unsigned long page_limit, cur_pages, new_pages; + + /* Don't allow more pages than we can safely lock */ + page_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; + cur_pages = atomic_long_read(&user->locked_vm); + + do { + new_pages = cur_pages + nr_pages; + if (new_pages > page_limit && !capable(CAP_IPC_LOCK)) + return -ENOMEM; + } while (atomic_long_try_cmpxchg_relaxed(&user->locked_vm, &cur_pages, + new_pages)); + + wqueue->nr_pages = nr_pages; + return 0; +} + +static void watch_queue_unaccount_mem(struct watch_queue *wqueue) +{ + struct user_struct *user = wqueue->owner; + + if (wqueue->nr_pages) { + atomic_long_sub(wqueue->nr_pages, &user->locked_vm); + wqueue->nr_pages = 0; + } +} + +static void watch_queue_map_pages(struct vm_fault *vmf, + pgoff_t start_pgoff, pgoff_t end_pgoff) +{ + struct watch_queue *wqueue = vmf->vma->vm_file->private_data; + struct page *page; + + rcu_read_lock(); + + do { + page = wqueue->pages[start_pgoff]; + if (trylock_page(page)) { + vm_fault_t ret; + get_page(page); + ret = alloc_set_pte(vmf, NULL, page); + if (ret != 0) + put_page(page); + + unlock_page(page); + } + } while (++start_pgoff < end_pgoff); + + rcu_read_unlock(); +} + +static const struct vm_operations_struct watch_queue_vm_ops = { + .fault = watch_queue_fault, + .map_pages = watch_queue_map_pages, +}; + +/* + * Map the buffer. + */ +static int watch_queue_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct watch_queue *wqueue = file->private_data; + struct inode *inode = file_inode(file); + u8 nr_pages; + + inode_lock(inode); + nr_pages = wqueue->nr_pages; + inode_unlock(inode); + + if (nr_pages == 0 || + vma->vm_pgoff != 0 || + vma->vm_end - vma->vm_start > nr_pages * PAGE_SIZE || + !(pgprot_val(vma->vm_page_prot) & pgprot_val(PAGE_SHARED))) + return -EINVAL; + + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_ops = &watch_queue_vm_ops; + return 0; +} + +/* + * Allocate the required number of pages. + */ +static long watch_queue_set_size(struct watch_queue *wqueue, unsigned long nr_pages) +{ + struct watch_queue_buffer *buf; + unsigned int gran = WATCH_LENGTH_GRANULARITY; + unsigned int metalen = sizeof(buf->meta) / gran; + int i; + + BUILD_BUG_ON(gran != sizeof(__u64)); + + if (wqueue->buffer) + return -EBUSY; + + if (nr_pages == 0 || + nr_pages > 16 || /* TODO: choose a better hard limit */ + !is_power_of_2(nr_pages)) + return -EINVAL; + + if (watch_queue_account_mem(wqueue, nr_pages) < 0) + goto err; + + wqueue->pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL); + if (!wqueue->pages) + goto err_unaccount; + + for (i = 0; i < nr_pages; i++) { + wqueue->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!wqueue->pages[i]) + goto err_some_pages; + wqueue->pages[i]->mapping = &wqueue->mapping; + SetPageUptodate(wqueue->pages[i]); + } + + buf = vmap(wqueue->pages, nr_pages, VM_MAP, PAGE_SHARED); + if (!buf) + goto err_some_pages; + + wqueue->buffer = buf; + wqueue->size = ((nr_pages * PAGE_SIZE) / sizeof(struct watch_notification)); + + /* The first four slots in the buffer contain metadata about the ring, + * including the head and tail indices and mask. + */ + buf->meta.watch.info = metalen << WATCH_INFO_LENGTH__SHIFT; + buf->meta.watch.type = WATCH_TYPE_META; + buf->meta.watch.subtype = WATCH_META_SKIP_NOTIFICATION; + buf->meta.mask = wqueue->size - 1; + buf->meta.head = metalen; + buf->meta.tail = metalen; + return 0; + +err_some_pages: + for (i--; i >= 0; i--) { + ClearPageUptodate(wqueue->pages[i]); + wqueue->pages[i]->mapping = NULL; + put_page(wqueue->pages[i]); + } + + kfree(wqueue->pages); + wqueue->pages = NULL; +err_unaccount: + watch_queue_unaccount_mem(wqueue); +err: + return -ENOMEM; +} + +/* + * Set the filter on a watch queue. + */ +static long watch_queue_set_filter(struct inode *inode, + struct watch_queue *wqueue, + struct watch_notification_filter __user *_filter) +{ + struct watch_notification_type_filter *tf; + struct watch_notification_filter filter; + struct watch_type_filter *q; + struct watch_filter *wfilter; + int ret, nr_filter = 0, i; + + if (!_filter) { + /* Remove the old filter */ + wfilter = NULL; + goto set; + } + + /* Grab the user's filter specification */ + if (copy_from_user(&filter, _filter, sizeof(filter)) != 0) + return -EFAULT; + if (filter.nr_filters == 0 || + filter.nr_filters > 16 || + filter.__reserved != 0) + return -EINVAL; + + tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf)); + if (IS_ERR(tf)) + return PTR_ERR(tf); + + ret = -EINVAL; + for (i = 0; i < filter.nr_filters; i++) { + if ((tf[i].info_filter & ~tf[i].info_mask) || + tf[i].info_mask & WATCH_INFO_LENGTH) + goto err_filter; + /* Ignore any unknown types */ + if (tf[i].type >= sizeof(wfilter->type_filter) * 8) + continue; + nr_filter++; + } + + /* Now we need to build the internal filter from only the relevant + * user-specified filters. + */ + ret = -ENOMEM; + wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL); + if (!wfilter) + goto err_filter; + wfilter->nr_filters = nr_filter; + + q = wfilter->filters; + for (i = 0; i < filter.nr_filters; i++) { + if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG) + continue; + + q->type = tf[i].type; + q->info_filter = tf[i].info_filter; + q->info_mask = tf[i].info_mask; + q->subtype_filter[0] = tf[i].subtype_filter[0]; + __set_bit(q->type, wfilter->type_filter); + q++; + } + + kfree(tf); +set: + inode_lock(inode); + rcu_swap_protected(wqueue->filter, wfilter, + lockdep_is_held(&inode->i_rwsem)); + inode_unlock(inode); + if (wfilter) + kfree_rcu(wfilter, rcu); + return 0; + +err_filter: + kfree(tf); + return ret; +} + +/* + * Set parameters. + */ +static long watch_queue_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct watch_queue *wqueue = file->private_data; + struct inode *inode = file_inode(file); + long ret; + + switch (cmd) { + case IOC_WATCH_QUEUE_SET_SIZE: + inode_lock(inode); + ret = watch_queue_set_size(wqueue, arg); + inode_unlock(inode); + return ret; + + case IOC_WATCH_QUEUE_SET_FILTER: + ret = watch_queue_set_filter( + inode, wqueue, + (struct watch_notification_filter __user *)arg); + return ret; + + default: + return -ENOTTY; + } +} + +/* + * Open the file. + */ +static int watch_queue_open(struct inode *inode, struct file *file) +{ + struct watch_queue *wqueue; + + wqueue = kzalloc(sizeof(*wqueue), GFP_KERNEL); + if (!wqueue) + return -ENOMEM; + + wqueue->mapping.a_ops = &watch_queue_aops; + wqueue->mapping.i_mmap = RB_ROOT_CACHED; + init_rwsem(&wqueue->mapping.i_mmap_rwsem); + spin_lock_init(&wqueue->mapping.private_lock); + + kref_init(&wqueue->usage); + spin_lock_init(&wqueue->lock); + init_waitqueue_head(&wqueue->waiters); + wqueue->owner = get_uid(file->f_cred->user); + + file->private_data = wqueue; + return 0; +} + +static void __put_watch_queue(struct kref *kref) +{ + struct watch_queue *wqueue = + container_of(kref, struct watch_queue, usage); + struct watch_filter *wfilter; + + wfilter = rcu_access_pointer(wqueue->filter); + if (wfilter) + kfree_rcu(wfilter, rcu); + free_uid(wqueue->owner); + kfree_rcu(wqueue, rcu); +} + +/** + * put_watch_queue - Dispose of a ref on a watchqueue. + * @wqueue: The watch queue to unref. + */ +void put_watch_queue(struct watch_queue *wqueue) +{ + kref_put(&wqueue->usage, __put_watch_queue); +} +EXPORT_SYMBOL(put_watch_queue); + +static void free_watch(struct rcu_head *rcu) +{ + struct watch *watch = container_of(rcu, struct watch, rcu); + + put_watch_queue(rcu_access_pointer(watch->queue)); + put_cred(watch->cred); +} + +static void __put_watch(struct kref *kref) +{ + struct watch *watch = container_of(kref, struct watch, usage); + + call_rcu(&watch->rcu, free_watch); +} + +/* + * Discard a watch. + */ +static void put_watch(struct watch *watch) +{ + kref_put(&watch->usage, __put_watch); +} + +/** + * init_watch_queue - Initialise a watch + * @watch: The watch to initialise. + * @wqueue: The queue to assign. + * + * Initialise a watch and set the watch queue. + */ +void init_watch(struct watch *watch, struct watch_queue *wqueue) +{ + kref_init(&watch->usage); + INIT_HLIST_NODE(&watch->list_node); + INIT_HLIST_NODE(&watch->queue_node); + rcu_assign_pointer(watch->queue, wqueue); +} + +/** + * add_watch_to_object - Add a watch on an object to a watch list + * @watch: The watch to add + * @wlist: The watch list to add to + * + * @watch->queue must have been set to point to the queue to post notifications + * to and the watch list of the object to be watched. @watch->cred must also + * have been set to the appropriate credentials and a ref taken on them. + * + * The caller must pin the queue and the list both and must hold the list + * locked against racing watch additions/removals. + */ +int add_watch_to_object(struct watch *watch, struct watch_list *wlist) +{ + struct watch_queue *wqueue = rcu_access_pointer(watch->queue); + struct watch *w; + + hlist_for_each_entry(w, &wlist->watchers, list_node) { + struct watch_queue *wq = rcu_access_pointer(w->queue); + if (wqueue == wq && watch->id == w->id) + return -EBUSY; + } + + watch->cred = get_current_cred(); + rcu_assign_pointer(watch->watch_list, wlist); + + spin_lock_bh(&wqueue->lock); + kref_get(&wqueue->usage); + hlist_add_head(&watch->queue_node, &wqueue->watches); + spin_unlock_bh(&wqueue->lock); + + hlist_add_head(&watch->list_node, &wlist->watchers); + return 0; +} +EXPORT_SYMBOL(add_watch_to_object); + +/** + * remove_watch_from_object - Remove a watch or all watches from an object. + * @wlist: The watch list to remove from + * @wq: The watch queue of interest (ignored if @all is true) + * @id: The ID of the watch to remove (ignored if @all is true) + * @all: True to remove all objects + * + * Remove a specific watch or all watches from an object. A notification is + * sent to the watcher to tell them that this happened. + */ +int remove_watch_from_object(struct watch_list *wlist, struct watch_queue *wq, + u64 id, bool all) +{ + struct watch_notification_removal n; + struct watch_queue *wqueue; + struct watch *watch; + int ret = -EBADSLT; + + rcu_read_lock(); + +again: + spin_lock(&wlist->lock); + hlist_for_each_entry(watch, &wlist->watchers, list_node) { + if (all || + (watch->id == id && rcu_access_pointer(watch->queue) == wq)) + goto found; + } + spin_unlock(&wlist->lock); + goto out; + +found: + ret = 0; + hlist_del_init_rcu(&watch->list_node); + rcu_assign_pointer(watch->watch_list, NULL); + spin_unlock(&wlist->lock); + + /* We now own the reference on watch that used to belong to wlist. */ + + n.watch.type = WATCH_TYPE_META; + n.watch.subtype = WATCH_META_REMOVAL_NOTIFICATION; + n.watch.info = watch->info_id | watch_sizeof(n.watch); + n.id = id; + if (id != 0) + n.watch.info = watch->info_id | watch_sizeof(n); + + wqueue = rcu_dereference(watch->queue); + + /* We don't need the watch list lock for the next bit as RCU is + * protecting *wqueue from deallocation. + */ + if (wqueue) { + post_one_notification(wqueue, &n.watch); + + spin_lock_bh(&wqueue->lock); + + if (!hlist_unhashed(&watch->queue_node)) { + hlist_del_init_rcu(&watch->queue_node); + put_watch(watch); + } + + spin_unlock_bh(&wqueue->lock); + } + + if (wlist->release_watch) { + void (*release_watch)(struct watch *); + + release_watch = wlist->release_watch; + rcu_read_unlock(); + (*release_watch)(watch); + rcu_read_lock(); + } + put_watch(watch); + + if (all && !hlist_empty(&wlist->watchers)) + goto again; +out: + rcu_read_unlock(); + return ret; +} +EXPORT_SYMBOL(remove_watch_from_object); + +/* + * Remove all the watches that are contributory to a queue. This has the + * potential to race with removal of the watches by the destruction of the + * objects being watched or with the distribution of notifications. + */ +static void watch_queue_clear(struct watch_queue *wqueue) +{ + struct watch_list *wlist; + struct watch *watch; + bool release; + + rcu_read_lock(); + spin_lock_bh(&wqueue->lock); + + /* Prevent new additions and prevent notifications from happening */ + wqueue->defunct = true; + + while (!hlist_empty(&wqueue->watches)) { + watch = hlist_entry(wqueue->watches.first, struct watch, queue_node); + hlist_del_init_rcu(&watch->queue_node); + /* We now own a ref on the watch. */ + spin_unlock_bh(&wqueue->lock); + + /* We can't do the next bit under the queue lock as we need to + * get the list lock - which would cause a deadlock if someone + * was removing from the opposite direction at the same time or + * posting a notification. + */ + wlist = rcu_dereference(watch->watch_list); + if (wlist) { + void (*release_watch)(struct watch *); + + spin_lock(&wlist->lock); + + release = !hlist_unhashed(&watch->list_node); + if (release) { + hlist_del_init_rcu(&watch->list_node); + rcu_assign_pointer(watch->watch_list, NULL); + + /* We now own a second ref on the watch. */ + } + + release_watch = wlist->release_watch; + spin_unlock(&wlist->lock); + + if (release) { + if (release_watch) { + rcu_read_unlock(); + /* This might need to call dput(), so + * we have to drop all the locks. + */ + (*release_watch)(watch); + rcu_read_lock(); + } + put_watch(watch); + } + } + + put_watch(watch); + spin_lock_bh(&wqueue->lock); + } + + spin_unlock_bh(&wqueue->lock); + rcu_read_unlock(); +} + +/* + * Release the file. + */ +static int watch_queue_release(struct inode *inode, struct file *file) +{ + struct watch_queue *wqueue = file->private_data; + int i; + + watch_queue_clear(wqueue); + + if (wqueue->buffer) + vunmap(wqueue->buffer); + + for (i = 0; i < wqueue->nr_pages; i++) { + ClearPageUptodate(wqueue->pages[i]); + wqueue->pages[i]->mapping = NULL; + __free_page(wqueue->pages[i]); + } + + kfree(wqueue->pages); + watch_queue_unaccount_mem(wqueue); + put_watch_queue(wqueue); + return 0; +} + +static const struct file_operations watch_queue_fops = { + .owner = THIS_MODULE, + .open = watch_queue_open, + .release = watch_queue_release, + .unlocked_ioctl = watch_queue_ioctl, + .poll = watch_queue_poll, + .mmap = watch_queue_mmap, + .llseek = no_llseek, +}; + +/** + * get_watch_queue - Get a watch queue from its file descriptor. + * @fd: The fd to query. + */ +struct watch_queue *get_watch_queue(int fd) +{ + struct watch_queue *wqueue = ERR_PTR(-EBADF); + struct fd f; + + f = fdget(fd); + if (f.file) { + wqueue = ERR_PTR(-EINVAL); + if (f.file->f_op == &watch_queue_fops) { + wqueue = f.file->private_data; + kref_get(&wqueue->usage); + } + fdput(f); + } + + return wqueue; +} +EXPORT_SYMBOL(get_watch_queue); + +static struct miscdevice watch_queue_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "watch_queue", + .fops = &watch_queue_fops, + .mode = 0666, +}; +builtin_misc_device(watch_queue_dev); diff --git a/drivers/nvdimm/security.c b/drivers/nvdimm/security.c index a570f2263a42..99a5708b37e3 100644 --- a/drivers/nvdimm/security.c +++ b/drivers/nvdimm/security.c @@ -55,7 +55,7 @@ static struct key *nvdimm_request_key(struct nvdimm *nvdimm) struct device *dev = &nvdimm->dev; sprintf(desc, "%s%s", NVDIMM_PREFIX, nvdimm->dimm_id); - key = request_key(&key_type_encrypted, desc, ""); + key = request_key(&key_type_encrypted, desc, "", NULL); if (IS_ERR(key)) { if (PTR_ERR(key) == -ENOKEY) dev_dbg(dev, "request_key() found no key\n"); diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index ecaacc8ed311..57e7b649e48b 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -102,3 +102,12 @@ config USB_AUTOSUSPEND_DELAY The default value Linux has always had is 2 seconds. Change this value if you want a different delay and cannot modify the command line or module parameter. + +config USB_NOTIFICATIONS + bool "Provide USB hardware event notifications" + depends on USB && DEVICE_NOTIFICATIONS + help + This option provides support for getting hardware event notifications + on USB devices and interfaces. This makes use of the + /dev/watch_queue misc device to handle the notification buffer. + device_notify(2) is used to set/remove watches. diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 3f899552f6e3..85cfc473401f 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -41,6 +41,7 @@ #include <linux/dma-mapping.h> #include <asm/byteorder.h> #include <linux/moduleparam.h> +#include <linux/watch_queue.h> #include "usb.h" @@ -2748,13 +2749,61 @@ static void usbdev_remove(struct usb_device *udev) mutex_unlock(&usbfs_mutex); } +#ifdef CONFIG_USB_NOTIFICATIONS +static noinline void post_usb_notification(const char *devname, + enum usb_notification_type subtype, + u32 error) +{ + unsigned int gran = WATCH_LENGTH_GRANULARITY; + unsigned int name_len, n_len; + u64 id = 0; /* Might want to put a dev# here. */ + + struct { + struct usb_notification n; + char more_name[USB_NOTIFICATION_MAX_NAME_LEN - + (sizeof(struct usb_notification) - + offsetof(struct usb_notification, name))]; + } n; + + name_len = strlen(devname); + name_len = min_t(size_t, name_len, USB_NOTIFICATION_MAX_NAME_LEN); + n_len = round_up(offsetof(struct usb_notification, name) + name_len, + gran) / gran; + + memset(&n, 0, sizeof(n)); + memcpy(n.n.name, devname, n_len); + + n.n.watch.type = WATCH_TYPE_USB_NOTIFY; + n.n.watch.subtype = subtype; + n.n.watch.info = n_len; + n.n.error = error; + n.n.name_len = name_len; + + post_device_notification(&n.n.watch, id); +} + +void post_usb_device_notification(const struct usb_device *udev, + enum usb_notification_type subtype, u32 error) +{ + post_usb_notification(dev_name(&udev->dev), subtype, error); +} + +void post_usb_bus_notification(const struct usb_bus *ubus, + enum usb_notification_type subtype, u32 error) +{ + post_usb_notification(ubus->bus_name, subtype, error); +} +#endif + static int usbdev_notify(struct notifier_block *self, unsigned long action, void *dev) { switch (action) { case USB_DEVICE_ADD: + post_usb_device_notification(dev, NOTIFY_USB_DEVICE_ADD, 0); break; case USB_DEVICE_REMOVE: + post_usb_device_notification(dev, NOTIFY_USB_DEVICE_REMOVE, 0); usbdev_remove(dev); break; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 236313f41f4a..e8ebacc15a32 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -29,6 +29,7 @@ #include <linux/random.h> #include <linux/pm_qos.h> #include <linux/kobject.h> +#include <linux/watch_queue.h> #include <linux/uaccess.h> #include <asm/byteorder.h> @@ -4605,6 +4606,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, (udev->config) ? "reset" : "new", speed, devnum, driver_name); + if (udev->config) + post_usb_device_notification(udev, NOTIFY_USB_DEVICE_RESET, 0); + /* Set up TT records, if needed */ if (hdev->tt) { udev->tt = hdev->tt; diff --git a/fs/afs/security.c b/fs/afs/security.c index ce9de1e6742b..e41310a1ab13 100644 --- a/fs/afs/security.c +++ b/fs/afs/security.c @@ -28,7 +28,7 @@ struct key *afs_request_key(struct afs_cell *cell) _debug("key %s", cell->anonymous_key->description); key = request_key_net(&key_type_rxrpc, cell->anonymous_key->description, - cell->net->net, NULL); + cell->net->net, NULL, NULL); if (IS_ERR(key)) { if (PTR_ERR(key) != -ENOKEY) { _leave(" = %ld", PTR_ERR(key)); diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c index 7b9b876b513b..6c164eab5b27 100644 --- a/fs/cifs/cifs_spnego.c +++ b/fs/cifs/cifs_spnego.c @@ -32,6 +32,25 @@ #include "cifsproto.h" static const struct cred *spnego_cred; +static struct key_acl cifs_spnego_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH | KEY_ACE_READ), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; + +static struct key_acl cifs_spnego_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_CLEAR), + } +}; + /* create a new cifs key */ static int cifs_spnego_key_instantiate(struct key *key, struct key_preparsed_payload *prep) @@ -170,7 +189,8 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo) cifs_dbg(FYI, "key description = %s\n", description); saved_cred = override_creds(spnego_cred); - spnego_key = request_key(&cifs_spnego_key_type, description, ""); + spnego_key = request_key(&cifs_spnego_key_type, description, "", + &cifs_spnego_key_acl); revert_creds(saved_cred); #ifdef CONFIG_CIFS_DEBUG2 @@ -207,8 +227,7 @@ init_cifs_spnego(void) keyring = keyring_alloc(".cifs_spnego", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ, + &cifs_spnego_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(keyring)) { ret = PTR_ERR(keyring); diff --git a/fs/cifs/cifsacl.c b/fs/cifs/cifsacl.c index f842944a5c76..5afcc6c88e8e 100644 --- a/fs/cifs/cifsacl.c +++ b/fs/cifs/cifsacl.c @@ -33,6 +33,25 @@ #include "cifsproto.h" #include "cifs_debug.h" +static struct key_acl cifs_idmap_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH | KEY_ACE_READ), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; + +static struct key_acl cifs_idmap_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ), + } +}; + /* security id for everyone/world system group */ static const struct cifs_sid sid_everyone = { 1, 1, {0, 0, 0, 0, 0, 1}, {0} }; @@ -298,7 +317,8 @@ id_to_sid(unsigned int cid, uint sidtype, struct cifs_sid *ssid) rc = 0; saved_cred = override_creds(root_cred); - sidkey = request_key(&cifs_idmap_key_type, desc, ""); + sidkey = request_key(&cifs_idmap_key_type, desc, "", + &cifs_idmap_key_acl); if (IS_ERR(sidkey)) { rc = -EINVAL; cifs_dbg(FYI, "%s: Can't map %cid %u to a SID\n", @@ -403,7 +423,8 @@ try_upcall_to_get_id: return -ENOMEM; saved_cred = override_creds(root_cred); - sidkey = request_key(&cifs_idmap_key_type, sidstr, ""); + sidkey = request_key(&cifs_idmap_key_type, sidstr, "", + &cifs_idmap_key_acl); if (IS_ERR(sidkey)) { rc = -EINVAL; cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n", @@ -481,8 +502,7 @@ init_cifs_idmap(void) keyring = keyring_alloc(".cifs_idmap", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ, + &cifs_idmap_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(keyring)) { ret = PTR_ERR(keyring); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 9d6805062841..bb49f4ea13a6 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3102,7 +3102,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses) } cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc); - key = request_key(&key_type_logon, desc, ""); + key = request_key(&key_type_logon, desc, "", NULL); if (IS_ERR(key)) { if (!ses->domainName) { cifs_dbg(FYI, "domainName is NULL\n"); @@ -3113,7 +3113,7 @@ cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses) /* didn't work, try to find a domain key */ sprintf(desc, "cifs:d:%s", ses->domainName); cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc); - key = request_key(&key_type_logon, desc, ""); + key = request_key(&key_type_logon, desc, "", NULL); if (IS_ERR(key)) { rc = PTR_ERR(key); goto out_err; diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c index c34fa7c61b43..fb4f6a44ffcd 100644 --- a/fs/crypto/keyring.c +++ b/fs/crypto/keyring.c @@ -127,6 +127,35 @@ static struct key_type key_type_fscrypt_user = { .describe = fscrypt_user_key_describe, }; +static struct key_acl fscrypt_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_INVAL | + KEY_ACE_JOIN), + KEY_OWNER_ACE(KEY_ACE_SEARCH | KEY_ACE_INVAL | KEY_ACE_JOIN | + KEY_ACE_READ | KEY_ACE_VIEW), + } +}; + +static struct key_acl fscrypt_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_INVAL), + KEY_OWNER_ACE(KEY_ACE_SEARCH | KEY_ACE_INVAL | KEY_ACE_VIEW), + } +}; + +static struct key_acl fscrypt_user_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_INVAL), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; + /* Search ->s_master_keys or ->mk_users */ static struct key *search_fscrypt_keyring(struct key *keyring, struct key_type *type, @@ -203,8 +232,7 @@ static int allocate_filesystem_keyring(struct super_block *sb) format_fs_keyring_description(description, sb); keyring = keyring_alloc(description, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, - current_cred(), KEY_POS_SEARCH | - KEY_USR_SEARCH | KEY_USR_READ | KEY_USR_VIEW, + current_cred(), &fscrypt_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(keyring)) return PTR_ERR(keyring); @@ -247,8 +275,7 @@ static int allocate_master_key_users_keyring(struct fscrypt_master_key *mk) format_mk_users_keyring_description(description, mk->mk_spec.u.identifier); keyring = keyring_alloc(description, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, - current_cred(), KEY_POS_SEARCH | - KEY_USR_SEARCH | KEY_USR_READ | KEY_USR_VIEW, + current_cred(), &fscrypt_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(keyring)) return PTR_ERR(keyring); @@ -285,7 +312,7 @@ static int add_master_key_user(struct fscrypt_master_key *mk) format_mk_user_description(description, mk->mk_spec.u.identifier); mk_user = key_alloc(&key_type_fscrypt_user, description, current_fsuid(), current_gid(), current_cred(), - KEY_POS_SEARCH | KEY_USR_VIEW, 0, NULL); + &fscrypt_user_key_acl, 0, NULL); if (IS_ERR(mk_user)) return PTR_ERR(mk_user); @@ -357,8 +384,7 @@ static int add_new_master_key(struct fscrypt_master_key_secret *secret, format_mk_description(description, mk_spec); key = key_alloc(&key_type_fscrypt, description, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, current_cred(), - KEY_POS_SEARCH | KEY_USR_SEARCH | KEY_USR_VIEW, - KEY_ALLOC_NOT_IN_QUOTA, NULL); + &fscrypt_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL); if (IS_ERR(key)) { err = PTR_ERR(key); goto out_free_mk; diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c index ad1a36c370c3..0727251be865 100644 --- a/fs/crypto/keysetup_v1.c +++ b/fs/crypto/keysetup_v1.c @@ -104,7 +104,7 @@ find_and_lock_process_key(const char *prefix, if (!description) return ERR_PTR(-ENOMEM); - key = request_key(&key_type_logon, description, NULL); + key = request_key(&key_type_logon, description, NULL, NULL); kfree(description); if (IS_ERR(key)) return key; diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h index 1c1a56be7ea2..67844fe41a61 100644 --- a/fs/ecryptfs/ecryptfs_kernel.h +++ b/fs/ecryptfs/ecryptfs_kernel.h @@ -91,7 +91,7 @@ ecryptfs_get_encrypted_key_payload_data(struct key *key) static inline struct key *ecryptfs_get_encrypted_key(char *sig) { - return request_key(&key_type_encrypted, sig, NULL); + return request_key(&key_type_encrypted, sig, NULL, NULL); } #else diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c index 4dc09638de8f..455b7c642bc9 100644 --- a/fs/ecryptfs/keystore.c +++ b/fs/ecryptfs/keystore.c @@ -1611,7 +1611,7 @@ int ecryptfs_keyring_auth_tok_for_sig(struct key **auth_tok_key, { int rc = 0; - (*auth_tok_key) = request_key(&key_type_user, sig, NULL); + (*auth_tok_key) = request_key(&key_type_user, sig, NULL, NULL); if (IS_ERR(*auth_tok_key)) { (*auth_tok_key) = ecryptfs_get_encrypted_key(sig); if (IS_ERR(*auth_tok_key)) { diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c index 72ebfe578f40..67b7bda5647a 100644 --- a/fs/fscache/object-list.c +++ b/fs/fscache/object-list.c @@ -317,7 +317,7 @@ static void fscache_objlist_config(struct fscache_objlist_data *data) const char *buf; int len; - key = request_key(&key_type_user, "fscache:objlist", NULL); + key = request_key(&key_type_user, "fscache:objlist", NULL, NULL); if (IS_ERR(key)) goto no_config; diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c index 1e7296395d71..69679f4f2e6c 100644 --- a/fs/nfs/nfs4idmap.c +++ b/fs/nfs/nfs4idmap.c @@ -72,6 +72,25 @@ struct idmap { const struct cred *cred; }; +static struct key_acl nfs_idmap_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH | KEY_ACE_READ), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; + +static struct key_acl nfs_idmap_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ), + } +}; + static struct user_namespace *idmap_userns(const struct idmap *idmap) { if (idmap && idmap->cred) @@ -208,8 +227,7 @@ int nfs_idmap_init(void) keyring = keyring_alloc(".id_resolver", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ, + &nfs_idmap_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(keyring)) { ret = PTR_ERR(keyring); @@ -287,11 +305,13 @@ static struct key *nfs_idmap_request_key(const char *name, size_t namelen, return ERR_PTR(ret); if (!idmap->cred || idmap->cred->user_ns == &init_user_ns) - rkey = request_key(&key_type_id_resolver, desc, ""); + rkey = request_key(&key_type_id_resolver, desc, "", + &nfs_idmap_key_acl); if (IS_ERR(rkey)) { mutex_lock(&idmap->idmap_mutex); rkey = request_key_with_auxdata(&key_type_id_resolver_legacy, - desc, NULL, "", 0, idmap); + desc, NULL, "", 0, idmap, + &nfs_idmap_key_acl); mutex_unlock(&idmap->idmap_mutex); } if (!IS_ERR(rkey)) @@ -320,8 +340,6 @@ static ssize_t nfs_idmap_get_key(const char *name, size_t namelen, } rcu_read_lock(); - rkey->perm |= KEY_USR_VIEW; - ret = key_validate(rkey); if (ret < 0) goto out_up; diff --git a/fs/ubifs/auth.c b/fs/ubifs/auth.c index 8cdbd53d780c..53513d8879d2 100644 --- a/fs/ubifs/auth.c +++ b/fs/ubifs/auth.c @@ -300,7 +300,7 @@ int ubifs_init_authentication(struct ubifs_info *c) snprintf(hmac_name, CRYPTO_MAX_ALG_NAME, "hmac(%s)", c->auth_hash_name); - keyring_key = request_key(&key_type_logon, c->auth_key_name, NULL); + keyring_key = request_key(&key_type_logon, c->auth_key_name, NULL, NULL); if (IS_ERR(keyring_key)) { ubifs_err(c, "Failed to request key: %ld", diff --git a/fs/verity/signature.c b/fs/verity/signature.c index c8b255232de5..5cb6eb14d684 100644 --- a/fs/verity/signature.c +++ b/fs/verity/signature.c @@ -131,15 +131,26 @@ static inline int __init fsverity_sysctl_init(void) } #endif /* !CONFIG_SYSCTL */ +static struct key_acl fsverity_acl = { + .usage = REFCOUNT_INIT(1), + .possessor_viewable = false, + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_JOIN | + KEY_ACE_INVAL), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | + KEY_ACE_CLEAR | KEY_ACE_SEARCH | KEY_ACE_INVAL | KEY_ACE_JOIN | + KEY_ACE_SET_SECURITY | KEY_ACE_REVOKE), + } +}; + int __init fsverity_init_signature(void) { struct key *ring; int err; ring = keyring_alloc(".fs-verity", KUIDT_INIT(0), KGIDT_INIT(0), - current_cred(), KEY_POS_SEARCH | - KEY_USR_VIEW | KEY_USR_READ | KEY_USR_WRITE | - KEY_USR_SEARCH | KEY_USR_SETATTR, + current_cred(), &fsverity_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(ring)) return PTR_ERR(ring); diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index f3ea78b0c91c..477472c11815 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -27,6 +27,7 @@ #include <linux/percpu-refcount.h> #include <linux/scatterlist.h> #include <linux/blkzoned.h> +#include <linux/watch_queue.h> struct module; struct scsi_ioctl_command; @@ -1773,6 +1774,20 @@ static inline bool blk_req_can_dispatch_to_zone(struct request *rq) } #endif /* CONFIG_BLK_DEV_ZONED */ +#ifdef CONFIG_BLK_NOTIFICATIONS +static inline void post_block_notification(struct block_notification *n) +{ + u64 id = 0; /* Might want to allow dev# here. */ + + post_device_notification(&n->watch, id); +} +#else +static inline void post_block_notification(struct block_notification *n) +{ +} +#endif + + #else /* CONFIG_BLOCK */ struct block_device; diff --git a/include/linux/device.h b/include/linux/device.h index 297239a08bb7..f30e80185825 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -43,6 +43,7 @@ struct iommu_group; struct iommu_fwspec; struct dev_pin_info; struct iommu_param; +struct watch_notification; struct bus_attribute { struct attribute attr; @@ -1654,6 +1655,12 @@ struct device_link *device_link_add(struct device *consumer, void device_link_del(struct device_link *link); void device_link_remove(void *consumer, struct device *supplier); +#ifdef CONFIG_DEVICE_NOTIFICATIONS +extern void post_device_notification(struct watch_notification *n, u64 id); +#else +static inline void post_device_notification(struct watch_notification *n, u64 id) {} +#endif + #ifndef dev_fmt #define dev_fmt(fmt) fmt #endif diff --git a/include/linux/key.h b/include/linux/key.h index 6cf8e71cf8b7..c1528d7e8f6b 100644 --- a/include/linux/key.h +++ b/include/linux/key.h @@ -27,49 +27,14 @@ /* key handle serial number */ typedef int32_t key_serial_t; -/* key handle permissions mask */ -typedef uint32_t key_perm_t; - struct key; struct net; #ifdef CONFIG_KEYS -#undef KEY_DEBUGGING +#include <linux/keyctl.h> -#define KEY_POS_VIEW 0x01000000 /* possessor can view a key's attributes */ -#define KEY_POS_READ 0x02000000 /* possessor can read key payload / view keyring */ -#define KEY_POS_WRITE 0x04000000 /* possessor can update key payload / add link to keyring */ -#define KEY_POS_SEARCH 0x08000000 /* possessor can find a key in search / search a keyring */ -#define KEY_POS_LINK 0x10000000 /* possessor can create a link to a key/keyring */ -#define KEY_POS_SETATTR 0x20000000 /* possessor can set key attributes */ -#define KEY_POS_ALL 0x3f000000 - -#define KEY_USR_VIEW 0x00010000 /* user permissions... */ -#define KEY_USR_READ 0x00020000 -#define KEY_USR_WRITE 0x00040000 -#define KEY_USR_SEARCH 0x00080000 -#define KEY_USR_LINK 0x00100000 -#define KEY_USR_SETATTR 0x00200000 -#define KEY_USR_ALL 0x003f0000 - -#define KEY_GRP_VIEW 0x00000100 /* group permissions... */ -#define KEY_GRP_READ 0x00000200 -#define KEY_GRP_WRITE 0x00000400 -#define KEY_GRP_SEARCH 0x00000800 -#define KEY_GRP_LINK 0x00001000 -#define KEY_GRP_SETATTR 0x00002000 -#define KEY_GRP_ALL 0x00003f00 - -#define KEY_OTH_VIEW 0x00000001 /* third party permissions... */ -#define KEY_OTH_READ 0x00000002 -#define KEY_OTH_WRITE 0x00000004 -#define KEY_OTH_SEARCH 0x00000008 -#define KEY_OTH_LINK 0x00000010 -#define KEY_OTH_SETATTR 0x00000020 -#define KEY_OTH_ALL 0x0000003f - -#define KEY_PERM_UNDEF 0xffffffff +#undef KEY_DEBUGGING struct seq_file; struct user_struct; @@ -113,6 +78,36 @@ union key_payload { void *data[4]; }; +struct key_ace { + unsigned int type; + unsigned int perm; + union { + kuid_t uid; + kgid_t gid; + unsigned int subject_id; + }; +}; + +struct key_acl { + refcount_t usage; + unsigned short nr_ace; + bool possessor_viewable; + struct rcu_head rcu; + struct key_ace aces[]; +}; + +#define KEY_POSSESSOR_ACE(perms) { \ + .type = KEY_ACE_SUBJ_STANDARD, \ + .perm = perms, \ + .subject_id = KEY_ACE_POSSESSOR \ + } + +#define KEY_OWNER_ACE(perms) { \ + .type = KEY_ACE_SUBJ_STANDARD, \ + .perm = perms, \ + .subject_id = KEY_ACE_OWNER \ + } + /*****************************************************************************/ /* * key reference with possession attribute handling @@ -176,9 +171,13 @@ 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 */ + struct key_acl __rcu *acl; union { time64_t expiry; /* time at which key expires (or 0) */ time64_t revoked_at; /* time at which key was revoked */ @@ -186,7 +185,6 @@ struct key { time64_t last_used_at; /* last time used for LRU keyring discard */ kuid_t uid; kgid_t gid; - key_perm_t perm; /* access permissions */ unsigned short quotalen; /* length added to quota */ unsigned short datalen; /* payload data length * - may not match RCU dereferenced payload @@ -210,6 +208,7 @@ struct key { #define KEY_FLAG_ROOT_CAN_INVAL 7 /* set if key can be invalidated by root without permission */ #define KEY_FLAG_KEEP 8 /* set if key should not be removed */ #define KEY_FLAG_UID_KEYRING 9 /* set if key is a user or user session keyring */ +#define KEY_FLAG_HAS_ACL 10 /* Set if KEYCTL_SETACL called on key */ /* the key type and key description string * - the desc is used to match a key against search criteria @@ -258,7 +257,7 @@ extern struct key *key_alloc(struct key_type *type, const char *desc, kuid_t uid, kgid_t gid, const struct cred *cred, - key_perm_t perm, + struct key_acl *acl, unsigned long flags, struct key_restriction *restrict_link); @@ -295,7 +294,8 @@ static inline void key_ref_put(key_ref_t key_ref) extern struct key *request_key_tag(struct key_type *type, const char *description, struct key_tag *domain_tag, - const char *callout_info); + const char *callout_info, + struct key_acl *acl); extern struct key *request_key_rcu(struct key_type *type, const char *description, @@ -306,21 +306,24 @@ extern struct key *request_key_with_auxdata(struct key_type *type, struct key_tag *domain_tag, const void *callout_info, size_t callout_len, - void *aux); + void *aux, + struct key_acl *acl); /** * request_key - Request a key and wait for construction * @type: Type of key. * @description: The searchable description of the key. * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @acl: The ACL to attach to a new key (or NULL). * * As for request_key_tag(), but with the default global domain tag. */ static inline struct key *request_key(struct key_type *type, const char *description, - const char *callout_info) + const char *callout_info, + struct key_acl *acl) { - return request_key_tag(type, description, NULL, callout_info); + return request_key_tag(type, description, NULL, callout_info, acl); } #ifdef CONFIG_NET @@ -330,6 +333,7 @@ static inline struct key *request_key(struct key_type *type, * @description: The searchable description of the key. * @net: The network namespace that is the key's domain of operation. * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @acl: The ACL to attach to a new key (or NULL). * * As for request_key() except that it does not add the returned key to a * keyring if found, new keys are always allocated in the user's quota, the @@ -339,8 +343,8 @@ static inline struct key *request_key(struct key_type *type, * Furthermore, it then works as wait_for_key_construction() to wait for the * completion of keys undergoing construction with a non-interruptible wait. */ -#define request_key_net(type, description, net, callout_info) \ - request_key_tag(type, description, net->key_domain, callout_info); +#define request_key_net(type, description, net, callout_info, acl) \ + request_key_tag(type, description, net->key_domain, callout_info, acl); /** * request_key_net_rcu - Request a key for a net namespace under RCU conditions @@ -364,7 +368,7 @@ extern key_ref_t key_create_or_update(key_ref_t keyring, const char *description, const void *payload, size_t plen, - key_perm_t perm, + struct key_acl *acl, unsigned long flags); extern int key_update(key_ref_t key, @@ -384,7 +388,7 @@ extern int key_unlink(struct key *keyring, extern struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid, const struct cred *cred, - key_perm_t perm, + struct key_acl *acl, unsigned long flags, struct key_restriction *restrict_link, struct key *dest); @@ -417,19 +421,29 @@ static inline key_serial_t key_serial(const struct key *key) extern void key_set_timeout(struct key *, unsigned); extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags, - key_perm_t perm); + u32 desired_perm); extern void key_free_user_ns(struct user_namespace *); /* * The permissions required on a key that we're looking up. */ -#define KEY_NEED_VIEW 0x01 /* Require permission to view attributes */ -#define KEY_NEED_READ 0x02 /* Require permission to read content */ -#define KEY_NEED_WRITE 0x04 /* Require permission to update / modify */ -#define KEY_NEED_SEARCH 0x08 /* Require permission to search (keyring) or find (key) */ -#define KEY_NEED_LINK 0x10 /* Require permission to link */ -#define KEY_NEED_SETATTR 0x20 /* Require permission to change attributes */ -#define KEY_NEED_ALL 0x3f /* All the above permissions */ +#define KEY_NEED_VIEW 0x001 /* Require permission to view attributes */ +#define KEY_NEED_READ 0x002 /* Require permission to read content */ +#define KEY_NEED_WRITE 0x004 /* Require permission to update / modify */ +#define KEY_NEED_SEARCH 0x008 /* Require permission to search (keyring) or find (key) */ +#define KEY_NEED_LINK 0x010 /* Require permission to link */ +#define KEY_NEED_SETSEC 0x020 /* Require permission to set owner, group, ACL */ +#define KEY_NEED_INVAL 0x040 /* Require permission to invalidate key */ +#define KEY_NEED_REVOKE 0x080 /* Require permission to revoke key */ +#define KEY_NEED_JOIN 0x100 /* Require permission to join keyring as session */ +#define KEY_NEED_CLEAR 0x200 /* Require permission to clear a keyring */ +#define KEY_NEED_ALL 0x3ff + +#define OLD_KEY_NEED_SETATTR 0x20 /* Used to be Require permission to change attributes */ + +extern struct key_acl internal_key_acl; +extern struct key_acl internal_keyring_acl; +extern struct key_acl internal_writable_keyring_acl; static inline short key_read_state(const struct key *key) { diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 915330abf6e5..734d67889826 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -74,6 +74,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_FILE 12 #define LSM_AUDIT_DATA_IBPKEY 13 #define LSM_AUDIT_DATA_IBENDPORT 14 +#define LSM_AUDIT_DATA_NOTIFICATION 15 union { struct path path; struct dentry *dentry; diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 3e5542e8df1c..24ea89f386e2 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1413,6 +1413,24 @@ * @ctx is a pointer in which to place the allocated security context. * @ctxlen points to the place to put the length of @ctx. * + * Security hooks for the general notification queue: + * + * @watch_key: + * Check to see if a process is allowed to watch for event notifications + * from a key or keyring. + * @key: The key to watch. + * + * @watch_devices: + * Check to see if a process is allowed to watch for event notifications + * from devices (as a global set). + * + * @post_notification: + * Check to see if a watch notification can be posted to a particular + * queue. + * @w_cred: The credentials of the whoever set the watch. + * @cred: The event-triggerer's credentials + * @n: The notification being posted + * * Security hooks for using the eBPF maps and programs functionalities through * eBPF syscalls. * @@ -1693,6 +1711,17 @@ union security_list_options { int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen); int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen); int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen); +#ifdef CONFIG_KEY_NOTIFICATIONS + int (*watch_key)(struct key *key); +#endif +#ifdef CONFIG_DEVICE_NOTIFICATIONS + int (*watch_devices)(void); +#endif +#ifdef CONFIG_WATCH_QUEUE + int (*post_notification)(const struct cred *w_cred, + const struct cred *cred, + struct watch_notification *n); +#endif #ifdef CONFIG_SECURITY_NETWORK int (*unix_stream_connect)(struct sock *sock, struct sock *other, @@ -1970,6 +1999,15 @@ struct security_hook_heads { struct hlist_head inode_notifysecctx; struct hlist_head inode_setsecctx; struct hlist_head inode_getsecctx; +#ifdef CONFIG_KEY_NOTIFICATIONS + struct hlist_head watch_key; +#endif +#ifdef CONFIG_DEVICE_NOTIFICATIONS + struct hlist_head watch_devices; +#endif +#ifdef CONFIG_WATCH_QUEUE + struct hlist_head post_notification; +#endif /* CONFIG_WATCH_QUEUE */ #ifdef CONFIG_SECURITY_NETWORK struct hlist_head unix_stream_connect; struct hlist_head unix_may_send; diff --git a/include/linux/security.h b/include/linux/security.h index 23e1c3f17d48..85b7307f26a1 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -57,6 +57,8 @@ struct mm_struct; struct fs_context; struct fs_parameter; enum fs_value_type; +struct watch; +struct watch_notification; /* Default (no) options for the capable function */ #define CAP_OPT_NONE 0x0 @@ -1265,6 +1267,35 @@ static inline int security_locked_down(enum lockdown_reason what) } #endif /* CONFIG_SECURITY */ +#if defined(CONFIG_SECURITY) && defined(CONFIG_KEY_NOTIFICATIONS) +int security_watch_key(struct key *key); +#else +static inline int security_watch_key(struct key *key) +{ + return 0; +} +#endif +#if defined(CONFIG_SECURITY) && defined(CONFIG_DEVICE_NOTIFICATIONS) +int security_watch_devices(void); +#else +static inline int security_watch_devices(void) +{ + return 0; +} +#endif +#if defined(CONFIG_SECURITY) && defined(CONFIG_WATCH_QUEUE) +int security_post_notification(const struct cred *w_cred, + const struct cred *cred, + struct watch_notification *n); +#else +static inline int security_post_notification(const struct cred *w_cred, + const struct cred *cred, + struct watch_notification *n) +{ + return 0; +} +#endif + #ifdef CONFIG_SECURITY_NETWORK int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk); @@ -1889,4 +1920,3 @@ static inline void security_bpf_prog_free(struct bpf_prog_aux *aux) #endif /* CONFIG_BPF_SYSCALL */ #endif /* ! __LINUX_SECURITY_H */ - diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index f7c561c4dcdd..565f033a61bc 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -1000,6 +1000,7 @@ asmlinkage long sys_fspick(int dfd, const char __user *path, unsigned int flags) asmlinkage long sys_pidfd_send_signal(int pidfd, int sig, siginfo_t __user *info, unsigned int flags); +asmlinkage long sys_watch_devices(int watch_fd, int watch_id, unsigned int flags); /* * Architecture-specific system calls diff --git a/include/linux/usb.h b/include/linux/usb.h index e656e7b4b1e4..93fa0666f95a 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -26,6 +26,7 @@ struct usb_device; struct usb_driver; struct wusb_dev; +enum usb_notification_type; /*-------------------------------------------------------------------------*/ @@ -2015,6 +2016,23 @@ extern void usb_led_activity(enum usb_led_event ev); static inline void usb_led_activity(enum usb_led_event ev) {} #endif +/* + * Notification functions. + */ +#ifdef CONFIG_USB_NOTIFICATIONS +extern void post_usb_device_notification(const struct usb_device *udev, + enum usb_notification_type subtype, + u32 error); +extern void post_usb_bus_notification(const struct usb_bus *ubus, + enum usb_notification_type subtype, + u32 error); +#else +static inline void post_usb_device_notification(const struct usb_device *udev, + unsigned int subtype, u32 error) {} +static inline void post_usb_bus_notification(const struct usb_bus *ubus, + unsigned int subtype, u32 error) {} +#endif + #endif /* __KERNEL__ */ #endif diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h new file mode 100644 index 000000000000..34d7915cc5b3 --- /dev/null +++ b/include/linux/watch_queue.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0 +/* User-mappable watch queue + * + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * See Documentation/watch_queue.rst + */ + +#ifndef _LINUX_WATCH_QUEUE_H +#define _LINUX_WATCH_QUEUE_H + +#include <uapi/linux/watch_queue.h> +#include <linux/kref.h> +#include <linux/rcupdate.h> + +#ifdef CONFIG_WATCH_QUEUE + +struct watch_queue; +struct cred; + +/* + * Representation of a watch on an object. + */ +struct watch { + union { + struct rcu_head rcu; + u32 info_id; /* ID to be OR'd in to info field */ + }; + struct watch_queue __rcu *queue; /* Queue to post events to */ + struct hlist_node queue_node; /* Link in queue->watches */ + struct watch_list __rcu *watch_list; + struct hlist_node list_node; /* Link in watch_list->watchers */ + const struct cred *cred; /* Creds of the owner of the watch */ + void *private; /* Private data for the watched object */ + u64 id; /* Internal identifier */ + struct kref usage; /* Object usage count */ +}; + +/* + * List of watches on an object. + */ +struct watch_list { + struct rcu_head rcu; + struct hlist_head watchers; + void (*release_watch)(struct watch *); + spinlock_t lock; +}; + +extern void __post_watch_notification(struct watch_list *, + struct watch_notification *, + const struct cred *, + u64); +extern struct watch_queue *get_watch_queue(int); +extern void put_watch_queue(struct watch_queue *); +extern void init_watch(struct watch *, struct watch_queue *); +extern int add_watch_to_object(struct watch *, struct watch_list *); +extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool); + +static inline void init_watch_list(struct watch_list *wlist, + void (*release_watch)(struct watch *)) +{ + INIT_HLIST_HEAD(&wlist->watchers); + spin_lock_init(&wlist->lock); + wlist->release_watch = release_watch; +} + +static inline void post_watch_notification(struct watch_list *wlist, + struct watch_notification *n, + const struct cred *cred, + u64 id) +{ + if (unlikely(wlist)) + __post_watch_notification(wlist, n, cred, id); +} + +static inline void remove_watch_list(struct watch_list *wlist, u64 id) +{ + if (wlist) { + remove_watch_from_object(wlist, NULL, id, true); + kfree_rcu(wlist, rcu); + } +} + +/** + * watch_sizeof - Calculate the information part of the size of a watch record, + * given the structure size. + */ +#define watch_sizeof(STRUCT) \ + ((sizeof(STRUCT) / WATCH_LENGTH_GRANULARITY) << WATCH_INFO_LENGTH__SHIFT) + +#endif + +#endif /* _LINUX_WATCH_QUEUE_H */ diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h index 1fc8faa6e973..4794d3c2afd7 100644 --- a/include/uapi/asm-generic/unistd.h +++ b/include/uapi/asm-generic/unistd.h @@ -850,9 +850,11 @@ __SYSCALL(__NR_pidfd_open, sys_pidfd_open) #define __NR_clone3 435 __SYSCALL(__NR_clone3, sys_clone3) #endif +#define __NR_watch_devices 436 +__SYSCALL(__NR_watch_devices, sys_watch_devices) #undef __NR_syscalls -#define __NR_syscalls 436 +#define __NR_syscalls 437 /* * 32 bit systems traditionally used different diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h index ed3d5893830d..830e4e9fb498 100644 --- a/include/uapi/linux/keyctl.h +++ b/include/uapi/linux/keyctl.h @@ -15,6 +15,69 @@ #include <linux/types.h> +/* + * Keyring permission grant definitions + */ +enum key_ace_subject_type { + KEY_ACE_SUBJ_STANDARD = 0, /* subject is one of key_ace_standard_subject */ + nr__key_ace_subject_type +}; + +enum key_ace_standard_subject { + KEY_ACE_EVERYONE = 0, /* Everyone, including owner and group */ + KEY_ACE_GROUP = 1, /* The key's group */ + KEY_ACE_OWNER = 2, /* The owner of the key */ + KEY_ACE_POSSESSOR = 3, /* Any process that possesses of the key */ + nr__key_ace_standard_subject +}; + +#define KEY_ACE_VIEW 0x00000001 /* Can describe the key */ +#define KEY_ACE_READ 0x00000002 /* Can read the key content */ +#define KEY_ACE_WRITE 0x00000004 /* Can update/modify the key content */ +#define KEY_ACE_SEARCH 0x00000008 /* Can find the key by search */ +#define KEY_ACE_LINK 0x00000010 /* Can make a link to the key */ +#define KEY_ACE_SET_SECURITY 0x00000020 /* Can set owner, group, ACL */ +#define KEY_ACE_INVAL 0x00000040 /* Can invalidate the key */ +#define KEY_ACE_REVOKE 0x00000080 /* Can revoke the key */ +#define KEY_ACE_JOIN 0x00000100 /* Can join keyring */ +#define KEY_ACE_CLEAR 0x00000200 /* Can clear keyring */ +#define KEY_ACE__PERMS 0xffffffff + +/* + * Old-style permissions mask, deprecated in favour of ACL. + */ +#define KEY_POS_VIEW 0x01000000 /* possessor can view a key's attributes */ +#define KEY_POS_READ 0x02000000 /* possessor can read key payload / view keyring */ +#define KEY_POS_WRITE 0x04000000 /* possessor can update key payload / add link to keyring */ +#define KEY_POS_SEARCH 0x08000000 /* possessor can find a key in search / search a keyring */ +#define KEY_POS_LINK 0x10000000 /* possessor can create a link to a key/keyring */ +#define KEY_POS_SETATTR 0x20000000 /* possessor can set key attributes */ +#define KEY_POS_ALL 0x3f000000 + +#define KEY_USR_VIEW 0x00010000 /* user permissions... */ +#define KEY_USR_READ 0x00020000 +#define KEY_USR_WRITE 0x00040000 +#define KEY_USR_SEARCH 0x00080000 +#define KEY_USR_LINK 0x00100000 +#define KEY_USR_SETATTR 0x00200000 +#define KEY_USR_ALL 0x003f0000 + +#define KEY_GRP_VIEW 0x00000100 /* group permissions... */ +#define KEY_GRP_READ 0x00000200 +#define KEY_GRP_WRITE 0x00000400 +#define KEY_GRP_SEARCH 0x00000800 +#define KEY_GRP_LINK 0x00001000 +#define KEY_GRP_SETATTR 0x00002000 +#define KEY_GRP_ALL 0x00003f00 + +#define KEY_OTH_VIEW 0x00000001 /* third party permissions... */ +#define KEY_OTH_READ 0x00000002 +#define KEY_OTH_WRITE 0x00000004 +#define KEY_OTH_SEARCH 0x00000008 +#define KEY_OTH_LINK 0x00000010 +#define KEY_OTH_SETATTR 0x00000020 +#define KEY_OTH_ALL 0x0000003f + /* special process keyring shortcut IDs */ #define KEY_SPEC_THREAD_KEYRING -1 /* - key ID for thread-specific keyring */ #define KEY_SPEC_PROCESS_KEYRING -2 /* - key ID for process-specific keyring */ @@ -69,6 +132,8 @@ #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 */ +#define KEYCTL_GRANT_PERMISSION 33 /* Grant a permit to a key */ /* keyctl structures */ struct keyctl_dh_params { @@ -130,5 +195,8 @@ 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 */ +#define KEYCTL_CAPS1_ACL 0x08 /* Keys have ACLs rather than a p-u-g-o bitmask */ +#define KEYCTL_CAPS1_GRANT_PERMISSION 0x10 /* KEYCTL_GRANT_PERMISSION is supported */ #endif /* _LINUX_KEYCTL_H */ diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h new file mode 100644 index 000000000000..baa4b3ead006 --- /dev/null +++ b/include/uapi/linux/watch_queue.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_WATCH_QUEUE_H +#define _UAPI_LINUX_WATCH_QUEUE_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +#define IOC_WATCH_QUEUE_SET_SIZE _IO('W', 0x60) /* Set the size in pages */ +#define IOC_WATCH_QUEUE_SET_FILTER _IO('W', 0x61) /* Set the filter */ + +enum watch_notification_type { + WATCH_TYPE_META = 0, /* Special record */ + WATCH_TYPE_KEY_NOTIFY = 1, /* Key change event notification */ + WATCH_TYPE_BLOCK_NOTIFY = 2, /* Block layer event notification */ + WATCH_TYPE_USB_NOTIFY = 3, /* USB subsystem event notification */ + WATCH_TYPE___NR = 4 +}; + +enum watch_meta_notification_subtype { + WATCH_META_SKIP_NOTIFICATION = 0, /* Just skip this record */ + WATCH_META_REMOVAL_NOTIFICATION = 1, /* Watched object was removed */ +}; + +#define WATCH_LENGTH_GRANULARITY sizeof(__u64) + +/* + * Notification record header. This is aligned to 64-bits so that subclasses + * can contain __u64 fields. + */ +struct watch_notification { + __u32 type:24; /* enum watch_notification_type */ + __u32 subtype:8; /* Type-specific subtype (filterable) */ + __u32 info; +#define WATCH_INFO_LENGTH 0x0000003f /* Length of record / sizeof(watch_notification) */ +#define WATCH_INFO_LENGTH__SHIFT 0 +#define WATCH_INFO_ID 0x0000ff00 /* ID of watchpoint, if type-appropriate */ +#define WATCH_INFO_ID__SHIFT 8 +#define WATCH_INFO_TYPE_INFO 0xffff0000 /* Type-specific info */ +#define WATCH_INFO_TYPE_INFO__SHIFT 16 +#define WATCH_INFO_FLAG_0 0x00010000 /* Type-specific info, flag bit 0 */ +#define WATCH_INFO_FLAG_1 0x00020000 /* ... */ +#define WATCH_INFO_FLAG_2 0x00040000 +#define WATCH_INFO_FLAG_3 0x00080000 +#define WATCH_INFO_FLAG_4 0x00100000 +#define WATCH_INFO_FLAG_5 0x00200000 +#define WATCH_INFO_FLAG_6 0x00400000 +#define WATCH_INFO_FLAG_7 0x00800000 +} __attribute__((aligned(WATCH_LENGTH_GRANULARITY))); + +struct watch_queue_buffer { + union { + /* The first few entries are special, containing the + * ring management variables. + */ + struct { + struct watch_notification watch; /* WATCH_TYPE_META */ + __u32 head; /* Ring head index */ + __u32 tail; /* Ring tail index */ + __u32 mask; /* Ring index mask */ + __u32 __reserved; + } meta; + struct watch_notification slots[0]; + }; +}; + +/* + * The Metadata pseudo-notification message uses a flag bits in the information + * field to convey the fact that messages have been lost. We can only use a + * single bit in this manner per word as some arches that support SMP + * (eg. parisc) have no kernel<->user atomic bit ops. + */ +#define WATCH_INFO_NOTIFICATIONS_LOST WATCH_INFO_FLAG_0 + +/* + * Notification filtering rules (IOC_WATCH_QUEUE_SET_FILTER). + */ +struct watch_notification_type_filter { + __u32 type; /* Type to apply filter to */ + __u32 info_filter; /* Filter on watch_notification::info */ + __u32 info_mask; /* Mask of relevant bits in info_filter */ + __u32 subtype_filter[8]; /* Bitmask of subtypes to filter on */ +}; + +struct watch_notification_filter { + __u32 nr_filters; /* Number of filters */ + __u32 __reserved; /* Must be 0 */ + struct watch_notification_type_filter filters[]; +}; + +/* + * Extended watch removal notification. This is used optionally if the type + * wants to indicate an identifier for the object being watched, if there is + * such. This can be distinguished by the length. + * + * type -> WATCH_TYPE_META + * subtype -> WATCH_META_REMOVAL_NOTIFICATION + * length -> 2 * gran + */ +struct watch_notification_removal { + struct watch_notification watch; + __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 */ +}; + +/* + * Type of block layer notification. + */ +enum block_notification_type { + NOTIFY_BLOCK_ERROR_TIMEOUT = 1, /* Timeout error */ + NOTIFY_BLOCK_ERROR_NO_SPACE = 2, /* Critical space allocation error */ + NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT = 3, /* Recoverable transport error */ + NOTIFY_BLOCK_ERROR_CRITICAL_TARGET = 4, /* Critical target error */ + NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS = 5, /* Critical nexus error */ + NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM = 6, /* Critical medium error */ + NOTIFY_BLOCK_ERROR_PROTECTION = 7, /* Protection error */ + NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE = 8, /* Kernel resource error */ + NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE = 9, /* Device resource error */ + NOTIFY_BLOCK_ERROR_IO = 10, /* Other I/O error */ +}; + +/* + * Block layer notification record. + * - watch.type = WATCH_TYPE_BLOCK_NOTIFY + * - watch.subtype = enum block_notification_type + */ +struct block_notification { + struct watch_notification watch; /* WATCH_TYPE_BLOCK_NOTIFY */ + __u64 dev; /* Device number */ + __u64 sector; /* Affected sector */ +}; + +/* + * Type of USB layer notification. + */ +enum usb_notification_type { + NOTIFY_USB_DEVICE_ADD = 0, /* USB device added */ + NOTIFY_USB_DEVICE_REMOVE = 1, /* USB device removed */ + NOTIFY_USB_DEVICE_RESET = 2, /* USB device reset */ + NOTIFY_USB_DEVICE_ERROR = 3, /* USB device error */ +}; + +/* + * USB subsystem notification record. + * - watch.type = WATCH_TYPE_USB_NOTIFY + * - watch.subtype = enum usb_notification_type + */ +struct usb_notification { + struct watch_notification watch; /* WATCH_TYPE_USB_NOTIFY */ + __u32 error; + __u32 reserved; + __u8 name_len; /* Length of device name */ + __u8 name[0]; /* Device name (padded to __u64, truncated at 63 chars) */ +}; + +#define USB_NOTIFICATION_MAX_NAME_LEN 63 + +#endif /* _UAPI_LINUX_WATCH_QUEUE_H */ diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c index 34b76895b81e..184ad68c087f 100644 --- a/kernel/sys_ni.c +++ b/kernel/sys_ni.c @@ -51,6 +51,7 @@ COND_SYSCALL_COMPAT(io_pgetevents); COND_SYSCALL(io_uring_setup); COND_SYSCALL(io_uring_enter); COND_SYSCALL(io_uring_register); +COND_SYSCALL(watch_devices); /* fs/xattr.c */ diff --git a/lib/digsig.c b/lib/digsig.c index e0627c3e53b2..ab0800f98eaf 100644 --- a/lib/digsig.c +++ b/lib/digsig.c @@ -224,7 +224,7 @@ int digsig_verify(struct key *keyring, const char *sig, int siglen, else key = key_ref_to_ptr(kref); } else { - key = request_key(&key_type_user, name, NULL); + key = request_key(&key_type_user, name, NULL, NULL); } if (IS_ERR(key)) { pr_err("key not found, id: %s\n", name); diff --git a/net/ceph/ceph_common.c b/net/ceph/ceph_common.c index 2d568246803f..7076254f64e4 100644 --- a/net/ceph/ceph_common.c +++ b/net/ceph/ceph_common.c @@ -323,7 +323,7 @@ static int get_secret(struct ceph_crypto_key *dst, const char *name) { int err = 0; struct ceph_crypto_key *ckey; - ukey = request_key(&key_type_ceph, name, NULL); + ukey = request_key(&key_type_ceph, name, NULL, NULL); if (IS_ERR(ukey)) { /* request_key errors don't map nicely to mount(2) errors; don't even try, but still printk */ diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c index 3e1a90669006..6b201531b165 100644 --- a/net/dns_resolver/dns_key.c +++ b/net/dns_resolver/dns_key.c @@ -46,6 +46,15 @@ const struct cred *dns_resolver_cache; #define DNS_ERRORNO_OPTION "dnserror" +static struct key_acl dns_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_CLEAR), + } +}; + /* * Preparse instantiation data for a dns_resolver key. * @@ -343,8 +352,7 @@ static int __init init_dns_resolver(void) keyring = keyring_alloc(".dns_resolver", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ, + &dns_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(keyring)) { ret = PTR_ERR(keyring); diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c index cab4e0df924f..236baf2bfa4c 100644 --- a/net/dns_resolver/dns_query.c +++ b/net/dns_resolver/dns_query.c @@ -47,6 +47,16 @@ #include "internal.h" +static struct key_acl dns_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_SEARCH | KEY_ACE_READ), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_INVAL), + } +}; + /** * dns_query - Query the DNS * @net: The network namespace to operate in. @@ -125,7 +135,8 @@ int dns_query(struct net *net, * add_key() to preinstall malicious redirections */ saved_cred = override_creds(dns_resolver_cache); - rkey = request_key_net(&key_type_dns_resolver, desc, net, options); + rkey = request_key_net(&key_type_dns_resolver, desc, net, options, + &dns_key_acl); revert_creds(saved_cred); kfree(desc); if (IS_ERR(rkey)) { @@ -135,8 +146,6 @@ int dns_query(struct net *net, down_read(&rkey->sem); set_bit(KEY_FLAG_ROOT_CAN_INVAL, &rkey->flags); - rkey->perm |= KEY_USR_VIEW; - ret = key_validate(rkey); if (ret < 0) goto put; diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c index 6c3f35fac42d..2032f6a8225e 100644 --- a/net/rxrpc/key.c +++ b/net/rxrpc/key.c @@ -23,6 +23,14 @@ #include <keys/user-type.h> #include "ar-internal.h" +static struct key_acl rxrpc_null_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 1, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ), + } +}; + static int rxrpc_vet_description_s(const char *); static int rxrpc_preparse(struct key_preparsed_payload *); static int rxrpc_preparse_s(struct key_preparsed_payload *); @@ -910,7 +918,8 @@ int rxrpc_request_key(struct rxrpc_sock *rx, char __user *optval, int optlen) if (IS_ERR(description)) return PTR_ERR(description); - key = request_key_net(&key_type_rxrpc, description, sock_net(&rx->sk), NULL); + key = request_key_net(&key_type_rxrpc, description, sock_net(&rx->sk), + NULL, NULL); if (IS_ERR(key)) { kfree(description); _leave(" = %ld", PTR_ERR(key)); @@ -941,7 +950,8 @@ int rxrpc_server_keyring(struct rxrpc_sock *rx, char __user *optval, if (IS_ERR(description)) return PTR_ERR(description); - key = request_key_net(&key_type_keyring, description, sock_net(&rx->sk), NULL); + key = request_key_net(&key_type_keyring, description, sock_net(&rx->sk), + NULL, NULL); if (IS_ERR(key)) { kfree(description); _leave(" = %ld", PTR_ERR(key)); @@ -974,7 +984,8 @@ int rxrpc_get_server_data_key(struct rxrpc_connection *conn, _enter(""); key = key_alloc(&key_type_rxrpc, "x", - GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, 0, + GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, + &internal_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL); if (IS_ERR(key)) { _leave(" = -ENOMEM [alloc %ld]", PTR_ERR(key)); @@ -1022,7 +1033,7 @@ struct key *rxrpc_get_null_key(const char *keyname) key = key_alloc(&key_type_rxrpc, keyname, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred, - KEY_POS_SEARCH, KEY_ALLOC_NOT_IN_QUOTA, NULL); + &rxrpc_null_key_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL); if (IS_ERR(key)) return key; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 5311d0ae2454..918cb1157148 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -741,8 +741,7 @@ static void __init load_keys_from_buffer(const u8 *p, unsigned int buflen) key = key_create_or_update(make_key_ref(builtin_regdb_keys, 1), "asymmetric", NULL, p, plen, - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ), + &internal_key_acl, KEY_ALLOC_NOT_IN_QUOTA | KEY_ALLOC_BUILT_IN | KEY_ALLOC_BYPASS_RESTRICTION); @@ -768,8 +767,7 @@ static int __init load_builtin_regdb_keys(void) builtin_regdb_keys = keyring_alloc(".builtin_regdb_keys", KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH), + &internal_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(builtin_regdb_keys)) return PTR_ERR(builtin_regdb_keys); diff --git a/samples/Kconfig b/samples/Kconfig index c8dacb4dda80..2c3e07addd38 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -169,4 +169,10 @@ config SAMPLE_VFS as mount API and statx(). Note that this is restricted to the x86 arch whilst it accesses system calls that aren't yet in all arches. +config SAMPLE_WATCH_QUEUE + bool "Build example /dev/watch_queue notification consumer" + help + Build example userspace program to use the new mount_notify(), + sb_notify() syscalls and the KEYCTL_WATCH_KEY keyctl() function. + endif # SAMPLES diff --git a/samples/Makefile b/samples/Makefile index 7d6e4ca28d69..a61a39047d02 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_SAMPLE_TRACE_PRINTK) += trace_printk/ obj-$(CONFIG_VIDEO_PCI_SKELETON) += v4l/ obj-y += vfio-mdev/ subdir-$(CONFIG_SAMPLE_VFS) += vfs +subdir-$(CONFIG_SAMPLE_WATCH_QUEUE) += watch_queue diff --git a/samples/watch_queue/Makefile b/samples/watch_queue/Makefile new file mode 100644 index 000000000000..6ee61e3ca8d2 --- /dev/null +++ b/samples/watch_queue/Makefile @@ -0,0 +1,8 @@ +# List of programs to build +hostprogs-y := watch_test + +# Tell kbuild to always build the programs +always := $(hostprogs-y) + +HOSTCFLAGS_watch_test.o += -I$(objtree)/usr/include +HOSTLDLIBS_watch_test += -lkeyutils diff --git a/samples/watch_queue/watch_test.c b/samples/watch_queue/watch_test.c new file mode 100644 index 000000000000..ecc191174b7a --- /dev/null +++ b/samples/watch_queue/watch_test.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Use /dev/watch_queue to watch for notifications. + * + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include <stdbool.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <poll.h> +#include <limits.h> +#include <linux/watch_queue.h> +#include <linux/unistd.h> +#include <linux/keyctl.h> + +#ifndef KEYCTL_WATCH_KEY +#define KEYCTL_WATCH_KEY -1 +#endif +#ifndef __NR_watch_devices +#define __NR_watch_devices -1 +#endif + +#define BUF_SIZE 4 + +static long keyctl_watch_key(int key, int watch_fd, int watch_id) +{ + return syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, watch_fd, watch_id); +} + +static const char *key_subtypes[256] = { + [NOTIFY_KEY_INSTANTIATED] = "instantiated", + [NOTIFY_KEY_UPDATED] = "updated", + [NOTIFY_KEY_LINKED] = "linked", + [NOTIFY_KEY_UNLINKED] = "unlinked", + [NOTIFY_KEY_CLEARED] = "cleared", + [NOTIFY_KEY_REVOKED] = "revoked", + [NOTIFY_KEY_INVALIDATED] = "invalidated", + [NOTIFY_KEY_SETATTR] = "setattr", +}; + +static void saw_key_change(struct watch_notification *n) +{ + struct key_notification *k = (struct key_notification *)n; + unsigned int len = (n->info & WATCH_INFO_LENGTH) >> WATCH_INFO_LENGTH__SHIFT; + + if (len != sizeof(struct key_notification) / WATCH_LENGTH_GRANULARITY) + return; + + printf("KEY %08x change=%u[%s] aux=%u\n", + k->key_id, n->subtype, key_subtypes[n->subtype], k->aux); +} + +static const char *block_subtypes[256] = { + [NOTIFY_BLOCK_ERROR_TIMEOUT] = "timeout", + [NOTIFY_BLOCK_ERROR_NO_SPACE] = "critical space allocation", + [NOTIFY_BLOCK_ERROR_RECOVERABLE_TRANSPORT] = "recoverable transport", + [NOTIFY_BLOCK_ERROR_CRITICAL_TARGET] = "critical target", + [NOTIFY_BLOCK_ERROR_CRITICAL_NEXUS] = "critical nexus", + [NOTIFY_BLOCK_ERROR_CRITICAL_MEDIUM] = "critical medium", + [NOTIFY_BLOCK_ERROR_PROTECTION] = "protection", + [NOTIFY_BLOCK_ERROR_KERNEL_RESOURCE] = "kernel resource", + [NOTIFY_BLOCK_ERROR_DEVICE_RESOURCE] = "device resource", + [NOTIFY_BLOCK_ERROR_IO] = "I/O", +}; + +static void saw_block_change(struct watch_notification *n) +{ + struct block_notification *b = (struct block_notification *)n; + unsigned int len = (n->info & WATCH_INFO_LENGTH) >> WATCH_INFO_LENGTH__SHIFT; + + if (len < sizeof(struct block_notification) / WATCH_LENGTH_GRANULARITY) + return; + + printf("BLOCK %08llx e=%u[%s] s=%llx\n", + (unsigned long long)b->dev, + n->subtype, block_subtypes[n->subtype], + (unsigned long long)b->sector); +} + +static const char *usb_subtypes[256] = { + [NOTIFY_USB_DEVICE_ADD] = "dev-add", + [NOTIFY_USB_DEVICE_REMOVE] = "dev-remove", + [NOTIFY_USB_DEVICE_RESET] = "dev-reset", + [NOTIFY_USB_DEVICE_ERROR] = "dev-error", +}; + +static void saw_usb_event(struct watch_notification *n) +{ + struct usb_notification *u = (struct usb_notification *)n; + unsigned int len = (n->info & WATCH_INFO_LENGTH) >> WATCH_INFO_LENGTH__SHIFT; + + if (len < sizeof(struct usb_notification) / WATCH_LENGTH_GRANULARITY) + return; + + printf("USB %*.*s %s e=%x r=%x\n", + u->name_len, u->name_len, u->name, + usb_subtypes[n->subtype], + u->error, u->reserved); +} + +/* + * Consume and display events. + */ +static int consumer(int fd, struct watch_queue_buffer *buf) +{ + struct watch_notification *n; + struct pollfd p[1]; + unsigned int head, tail, mask = buf->meta.mask; + + for (;;) { + p[0].fd = fd; + p[0].events = POLLIN | POLLERR; + p[0].revents = 0; + + if (poll(p, 1, -1) == -1) { + perror("poll"); + break; + } + + printf("ptrs h=%x t=%x m=%x\n", + buf->meta.head, buf->meta.tail, buf->meta.mask); + + while (head = __atomic_load_n(&buf->meta.head, __ATOMIC_ACQUIRE), + tail = buf->meta.tail, + tail != head + ) { + n = &buf->slots[tail & mask]; + printf("NOTIFY[%08x-%08x] ty=%04x sy=%04x i=%08x\n", + head, tail, n->type, n->subtype, n->info); + if ((n->info & WATCH_INFO_LENGTH) == 0) + goto out; + + switch (n->type) { + case WATCH_TYPE_META: + if (n->subtype == WATCH_META_REMOVAL_NOTIFICATION) + printf("REMOVAL of watchpoint %08x\n", + (n->info & WATCH_INFO_ID) >> + WATCH_INFO_ID__SHIFT); + break; + case WATCH_TYPE_KEY_NOTIFY: + saw_key_change(n); + break; + case WATCH_TYPE_BLOCK_NOTIFY: + saw_block_change(n); + break; + case WATCH_TYPE_USB_NOTIFY: + saw_usb_event(n); + break; + } + + tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_INFO_LENGTH__SHIFT; + __atomic_store_n(&buf->meta.tail, tail, __ATOMIC_RELEASE); + } + } + +out: + return 0; +} + +static struct watch_notification_filter filter = { + .nr_filters = 3, + .__reserved = 0, + .filters = { + [0] = { + .type = WATCH_TYPE_KEY_NOTIFY, + .subtype_filter[0] = UINT_MAX, + }, + [1] = { + .type = WATCH_TYPE_BLOCK_NOTIFY, + .subtype_filter[0] = UINT_MAX, + }, + [2] = { + .type = WATCH_TYPE_USB_NOTIFY, + .subtype_filter[0] = UINT_MAX, + }, + }, +}; + +int main(int argc, char **argv) +{ + struct watch_queue_buffer *buf; + size_t page_size; + int fd; + + fd = open("/dev/watch_queue", O_RDWR); + if (fd == -1) { + perror("/dev/watch_queue"); + exit(1); + } + + if (ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE) == -1) { + perror("/dev/watch_queue(size)"); + exit(1); + } + + if (ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) == -1) { + perror("/dev/watch_queue(filter)"); + exit(1); + } + + page_size = sysconf(_SC_PAGESIZE); + buf = mmap(NULL, BUF_SIZE * page_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (buf == MAP_FAILED) { + perror("mmap"); + exit(1); + } + + if (keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01) == -1) { + perror("keyctl"); + exit(1); + } + + if (syscall(__NR_watch_devices, fd, 0x04, 0) == -1) { + perror("watch_devices"); + exit(1); + } + + return consumer(fd, buf); +} diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index ea1aae3d07b3..cce314e557c2 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -46,7 +46,8 @@ static struct key *integrity_keyring_from_id(const unsigned int id) if (!keyring[id]) { keyring[id] = - request_key(&key_type_keyring, keyring_name[id], NULL); + request_key(&key_type_keyring, keyring_name[id], + NULL, NULL); if (IS_ERR(keyring[id])) { int err = PTR_ERR(keyring[id]); pr_err("no %s keyring: %d\n", keyring_name[id], err); @@ -95,14 +96,14 @@ int integrity_modsig_verify(const unsigned int id, const struct modsig *modsig) } static int __init __integrity_init_keyring(const unsigned int id, - key_perm_t perm, + struct key_acl *acl, struct key_restriction *restriction) { const struct cred *cred = current_cred(); int err = 0; keyring[id] = keyring_alloc(keyring_name[id], KUIDT_INIT(0), - KGIDT_INIT(0), cred, perm, + KGIDT_INIT(0), cred, acl, KEY_ALLOC_NOT_IN_QUOTA, restriction, NULL); if (IS_ERR(keyring[id])) { err = PTR_ERR(keyring[id]); @@ -120,10 +121,7 @@ static int __init __integrity_init_keyring(const unsigned int id, int __init integrity_init_keyring(const unsigned int id) { struct key_restriction *restriction; - key_perm_t perm; - - perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW - | KEY_USR_READ | KEY_USR_SEARCH; + struct key_acl *acl = &internal_keyring_acl; if (id == INTEGRITY_KEYRING_PLATFORM) { restriction = NULL; @@ -138,14 +136,14 @@ int __init integrity_init_keyring(const unsigned int id) return -ENOMEM; restriction->check = restrict_link_to_ima; - perm |= KEY_USR_WRITE; + acl = &internal_writable_keyring_acl; out: - return __integrity_init_keyring(id, perm, restriction); + return __integrity_init_keyring(id, acl, restriction); } -int __init integrity_add_key(const unsigned int id, const void *data, - off_t size, key_perm_t perm) +static int __init integrity_add_key(const unsigned int id, const void *data, + off_t size, struct key_acl *acl) { key_ref_t key; int rc = 0; @@ -154,7 +152,7 @@ int __init integrity_add_key(const unsigned int id, const void *data, return -EINVAL; key = key_create_or_update(make_key_ref(keyring[id], 1), "asymmetric", - NULL, data, size, perm, + NULL, data, size, acl ?: &internal_key_acl, KEY_ALLOC_NOT_IN_QUOTA); if (IS_ERR(key)) { rc = PTR_ERR(key); @@ -174,7 +172,6 @@ int __init integrity_load_x509(const unsigned int id, const char *path) void *data; loff_t size; int rc; - key_perm_t perm; rc = kernel_read_file_from_path(path, &data, &size, 0, READING_X509_CERTIFICATE); @@ -183,21 +180,19 @@ int __init integrity_load_x509(const unsigned int id, const char *path) return rc; } - perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ; - pr_info("Loading X.509 certificate: %s\n", path); - rc = integrity_add_key(id, (const void *)data, size, perm); + rc = integrity_add_key(id, data, size, NULL); vfree(data); return rc; } int __init integrity_load_cert(const unsigned int id, const char *source, - const void *data, size_t len, key_perm_t perm) + const void *data, size_t len, struct key_acl *acl) { if (!data) return -EINVAL; pr_info("Loading X.509 certificate: %s\n", source); - return integrity_add_key(id, data, len, perm); + return integrity_add_key(id, data, len, acl); } diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index 55aec161d0e1..a29df775fdd8 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -53,7 +53,7 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid) else key = key_ref_to_ptr(kref); } else { - key = request_key(&key_type_asymmetric, name, NULL); + key = request_key(&key_type_asymmetric, name, NULL, NULL); } if (IS_ERR(key)) { diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index d485f6fc908e..466eebd3b4aa 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -356,7 +356,7 @@ int evm_init_key(void) struct encrypted_key_payload *ekp; int rc; - evm_key = request_key(&key_type_encrypted, EVMKEY, NULL); + evm_key = request_key(&key_type_encrypted, EVMKEY, NULL, NULL); if (IS_ERR(evm_key)) return -ENOENT; diff --git a/security/integrity/ima/ima_mok.c b/security/integrity/ima/ima_mok.c index 36cadadbfba4..b52ae1476ec3 100644 --- a/security/integrity/ima/ima_mok.c +++ b/security/integrity/ima/ima_mok.c @@ -16,6 +16,15 @@ #include <keys/system_keyring.h> +static struct key_acl integrity_blacklist_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | KEY_ACE_SEARCH), + } +}; + struct key *ima_blacklist_keyring; /* @@ -35,9 +44,7 @@ __init int ima_mok_init(void) ima_blacklist_keyring = keyring_alloc(".ima_blacklist", KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), - (KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ | - KEY_USR_WRITE | KEY_USR_SEARCH, + &integrity_blacklist_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, restriction, NULL); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index d9323d31a3a8..0a09702288cf 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -12,6 +12,8 @@ #include <linux/key.h> #include <linux/audit.h> +struct key_acl; + /* iint action cache flags */ #define IMA_MEASURE 0x00000001 #define IMA_MEASURED 0x00000002 @@ -159,7 +161,7 @@ int integrity_modsig_verify(unsigned int id, const struct modsig *modsig); int __init integrity_init_keyring(const unsigned int id); int __init integrity_load_x509(const unsigned int id, const char *path); int __init integrity_load_cert(const unsigned int id, const char *source, - const void *data, size_t len, key_perm_t perm); + const void *data, size_t len, struct key_acl *acl); #else static inline int integrity_digsig_verify(const unsigned int id, @@ -183,7 +185,7 @@ static inline int integrity_init_keyring(const unsigned int id) static inline int __init integrity_load_cert(const unsigned int id, const char *source, const void *data, size_t len, - key_perm_t perm) + struct key_acl *acl) { return 0; } diff --git a/security/integrity/platform_certs/platform_keyring.c b/security/integrity/platform_certs/platform_keyring.c index bcafd7387729..7646e35f2d91 100644 --- a/security/integrity/platform_certs/platform_keyring.c +++ b/security/integrity/platform_certs/platform_keyring.c @@ -14,6 +14,15 @@ #include <linux/slab.h> #include "../integrity.h" +static struct key_acl platform_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_READ), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; + /** * add_to_platform_keyring - Add to platform keyring without validation. * @source: Source of key @@ -26,13 +35,10 @@ void __init add_to_platform_keyring(const char *source, const void *data, size_t len) { - key_perm_t perm; int rc; - perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW; - rc = integrity_load_cert(INTEGRITY_KEYRING_PLATFORM, source, data, len, - perm); + &platform_key_acl); if (rc) pr_info("Error adding keys to platform keyring %s\n", source); } diff --git a/security/keys/Kconfig b/security/keys/Kconfig index dd313438fecf..20791a556b58 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -120,3 +120,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. diff --git a/security/keys/compat.c b/security/keys/compat.c index 9bcc404131aa..27a9c96ffe57 100644 --- a/security/keys/compat.c +++ b/security/keys/compat.c @@ -157,10 +157,15 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option, case KEYCTL_MOVE: return keyctl_keyring_move(arg2, arg3, arg4, arg5); + case KEYCTL_GRANT_PERMISSION: + return keyctl_grant_permission(arg2, arg3, arg4, arg5); case KEYCTL_CAPABILITIES: return keyctl_capabilities(compat_ptr(arg2), arg3); + case KEYCTL_WATCH_KEY: + return keyctl_watch_key(arg2, arg3, arg4); + default: return -EOPNOTSUPP; } diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 60720f58cbe0..9df560e477c2 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -304,7 +304,7 @@ static struct key *request_user_key(const char *master_desc, const u8 **master_k const struct user_key_payload *upayload; struct key *ukey; - ukey = request_key(&key_type_user, master_desc, NULL); + ukey = request_key(&key_type_user, master_desc, NULL, NULL); if (IS_ERR(ukey)) goto error; diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c index c68528aa49c6..d649f2f29475 100644 --- a/security/keys/encrypted-keys/masterkey_trusted.c +++ b/security/keys/encrypted-keys/masterkey_trusted.c @@ -30,7 +30,7 @@ struct key *request_trusted_key(const char *trusted_desc, struct trusted_key_payload *tpayload; struct key *tkey; - tkey = request_key(&key_type_trusted, trusted_desc, NULL); + tkey = request_key(&key_type_trusted, trusted_desc, NULL, NULL); if (IS_ERR(tkey)) goto error; diff --git a/security/keys/gc.c b/security/keys/gc.c index 671dd730ecfc..2b7e5aa8b71f 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -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); @@ -151,6 +156,7 @@ static noinline void key_gc_unused_keys(struct list_head *keys) key_user_put(key->user); key_put_tag(key->domain_tag); + key_put_acl(rcu_access_pointer(key->acl)); kfree(key->description); memzero_explicit(key, sizeof(*key)); @@ -220,7 +226,6 @@ continue_scanning: if (key->type == key_gc_dead_keytype) { gc_state |= KEY_GC_FOUND_DEAD_KEY; set_bit(KEY_FLAG_DEAD, &key->flags); - key->perm = 0; goto skip_dead_key; } else if (key->type == &key_type_keyring && key->restrict_link) { diff --git a/security/keys/internal.h b/security/keys/internal.h index c039373488bd..e73db32d4db9 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -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> struct iovec; @@ -84,8 +85,11 @@ extern struct rb_root key_serial_tree; extern spinlock_t key_serial_lock; extern struct mutex key_construction_mutex; extern wait_queue_head_t request_key_conswq; +extern struct key_acl default_key_acl; +extern struct key_acl joinable_keyring_acl; extern void key_set_index_key(struct keyring_index_key *index_key); + extern struct key_type *key_type_lookup(const char *type); extern void key_type_put(struct key_type *ktype); @@ -97,7 +101,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); @@ -156,6 +161,7 @@ extern struct key *request_key_and_link(struct key_type *type, const void *callout_info, size_t callout_len, void *aux, + struct key_acl *acl, struct key *dest_keyring, unsigned long flags); @@ -179,7 +185,27 @@ extern void key_gc_keytype(struct key_type *ktype); extern int key_task_permission(const key_ref_t key_ref, const struct cred *cred, - key_perm_t perm); + u32 desired_perm); +extern unsigned int key_acl_to_perm(const struct key_acl *acl); +extern long key_set_acl(struct key *key, struct key_acl *acl); +extern void key_put_acl(struct key_acl *acl); + +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. @@ -226,7 +252,7 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *, const char __user *, key_serial_t); extern long keyctl_read_key(key_serial_t, char __user *, size_t); extern long keyctl_chown_key(key_serial_t, uid_t, gid_t); -extern long keyctl_setperm_key(key_serial_t, key_perm_t); +extern long keyctl_setperm_key(key_serial_t, unsigned int); extern long keyctl_instantiate_key(key_serial_t, const void __user *, size_t, key_serial_t); extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t); @@ -331,6 +357,20 @@ 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 + +extern long keyctl_grant_permission(key_serial_t keyid, + enum key_ace_subject_type type, + unsigned int subject, + unsigned int perm); + /* * Debugging key validation */ diff --git a/security/keys/key.c b/security/keys/key.c index 764f4c57913e..9c0800e666b0 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -195,7 +195,7 @@ serial_exists: * @uid: The owner of the new key. * @gid: The group ID for the new key's group permissions. * @cred: The credentials specifying UID namespace. - * @perm: The permissions mask of the new key. + * @acl: The ACL to attach to the new key. * @flags: Flags specifying quota properties. * @restrict_link: Optional link restriction for new keyrings. * @@ -223,7 +223,7 @@ serial_exists: */ struct key *key_alloc(struct key_type *type, const char *desc, kuid_t uid, kgid_t gid, const struct cred *cred, - key_perm_t perm, unsigned long flags, + struct key_acl *acl, unsigned long flags, struct key_restriction *restrict_link) { struct key_user *user = NULL; @@ -246,6 +246,9 @@ struct key *key_alloc(struct key_type *type, const char *desc, desclen = strlen(desc); quotalen = desclen + 1 + type->def_datalen; + if (!acl) + acl = &default_key_acl; + /* get hold of the key tracking for this user */ user = key_user_lookup(uid); if (!user) @@ -292,7 +295,8 @@ struct key *key_alloc(struct key_type *type, const char *desc, key->datalen = type->def_datalen; key->uid = uid; key->gid = gid; - key->perm = perm; + refcount_inc(&acl->usage); + rcu_assign_pointer(key->acl, acl); key->restrict_link = restrict_link; key->last_used_at = ktime_get_real_seconds(); @@ -443,6 +447,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; @@ -452,7 +457,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 */ @@ -600,6 +605,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); @@ -610,7 +616,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) @@ -763,9 +769,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); @@ -787,7 +795,7 @@ error: * @description: The searchable description for the key. * @payload: The data to use to instantiate or update the key. * @plen: The length of @payload. - * @perm: The permissions mask for a new key. + * @acl: The ACL to attach if a key is created. * @flags: The quota flags for a new key. * * Search the destination keyring for a key of the same description and if one @@ -810,7 +818,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, const char *description, const void *payload, size_t plen, - key_perm_t perm, + struct key_acl *acl, unsigned long flags) { struct keyring_index_key index_key = { @@ -907,22 +915,9 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, goto found_matching_key; } - /* if the client doesn't provide, decide on the permissions we want */ - if (perm == KEY_PERM_UNDEF) { - perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; - perm |= KEY_USR_VIEW; - - if (index_key.type->read) - perm |= KEY_POS_READ; - - if (index_key.type == &key_type_keyring || - index_key.type->update) - perm |= KEY_POS_WRITE; - } - /* allocate a new key */ key = key_alloc(index_key.type, index_key.description, - cred->fsuid, cred->fsgid, cred, perm, flags, NULL); + cred->fsuid, cred->fsgid, cred, acl, flags, NULL); if (IS_ERR(key)) { key_ref = ERR_CAST(key); goto error_link_end; @@ -1013,9 +1008,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); @@ -1047,15 +1044,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); @@ -1077,8 +1076,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); } } diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 9b898c969558..5a418021f639 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -37,7 +37,11 @@ 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) | + KEYCTL_CAPS1_ACL | + KEYCTL_CAPS1_GRANT_PERMISSION + ), }; static int key_get_type_from_user(char *type, @@ -130,8 +134,7 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type, /* create or update the requested key and add it to the target * keyring */ key_ref = key_create_or_update(keyring_ref, type, description, - payload, plen, KEY_PERM_UNDEF, - KEY_ALLOC_IN_QUOTA); + payload, plen, NULL, KEY_ALLOC_IN_QUOTA); if (!IS_ERR(key_ref)) { ret = key_ref_to_ptr(key_ref)->serial; key_ref_put(key_ref); @@ -221,7 +224,8 @@ SYSCALL_DEFINE4(request_key, const char __user *, _type, /* do the search */ key = request_key_and_link(ktype, description, NULL, callout_info, - callout_len, NULL, key_ref_to_ptr(dest_ref), + callout_len, NULL, NULL, + key_ref_to_ptr(dest_ref), KEY_ALLOC_IN_QUOTA); if (IS_ERR(key)) { ret = PTR_ERR(key); @@ -383,16 +387,10 @@ long keyctl_revoke_key(key_serial_t id) struct key *key; long ret; - key_ref = lookup_user_key(id, 0, KEY_NEED_WRITE); + key_ref = lookup_user_key(id, 0, KEY_NEED_REVOKE); if (IS_ERR(key_ref)) { ret = PTR_ERR(key_ref); - if (ret != -EACCES) - goto error; - key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR); - if (IS_ERR(key_ref)) { - ret = PTR_ERR(key_ref); - goto error; - } + goto error; } key = key_ref_to_ptr(key_ref); @@ -426,7 +424,7 @@ long keyctl_invalidate_key(key_serial_t id) kenter("%d", id); - key_ref = lookup_user_key(id, 0, KEY_NEED_SEARCH); + key_ref = lookup_user_key(id, 0, KEY_NEED_INVAL); if (IS_ERR(key_ref)) { ret = PTR_ERR(key_ref); @@ -471,7 +469,7 @@ long keyctl_keyring_clear(key_serial_t ringid) struct key *keyring; long ret; - keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE); + keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_NEED_CLEAR); if (IS_ERR(keyring_ref)) { ret = PTR_ERR(keyring_ref); @@ -646,6 +644,7 @@ long keyctl_describe_key(key_serial_t keyid, size_t buflen) { struct key *key, *instkey; + unsigned int perm; key_ref_t key_ref; char *infobuf; long ret; @@ -675,6 +674,10 @@ okay: key = key_ref_to_ptr(key_ref); desclen = strlen(key->description); + rcu_read_lock(); + perm = key_acl_to_perm(rcu_dereference(key->acl)); + rcu_read_unlock(); + /* calculate how much information we're going to return */ ret = -ENOMEM; infobuf = kasprintf(GFP_KERNEL, @@ -682,7 +685,7 @@ okay: key->type->name, from_kuid_munged(current_user_ns(), key->uid), from_kgid_munged(current_user_ns(), key->gid), - key->perm); + perm); if (!infobuf) goto error2; infolen = strlen(infobuf); @@ -899,7 +902,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group) goto error; key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, - KEY_NEED_SETATTR); + KEY_NEED_SETSEC); if (IS_ERR(key_ref)) { ret = PTR_ERR(key_ref); goto error; @@ -970,6 +973,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: @@ -994,18 +998,25 @@ quota_overrun: * the key need not be fully instantiated yet. If the caller does not have * sysadmin capability, it may only change the permission on keys that it owns. */ -long keyctl_setperm_key(key_serial_t id, key_perm_t perm) +long keyctl_setperm_key(key_serial_t id, unsigned int perm) { + struct key_acl *acl; struct key *key; key_ref_t key_ref; long ret; + int nr, i, j; - ret = -EINVAL; if (perm & ~(KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL)) - goto error; + return -EINVAL; + + nr = 0; + if (perm & KEY_POS_ALL) nr++; + if (perm & KEY_USR_ALL) nr++; + if (perm & KEY_GRP_ALL) nr++; + if (perm & KEY_OTH_ALL) nr++; key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, - KEY_NEED_SETATTR); + KEY_NEED_SETSEC); if (IS_ERR(key_ref)) { ret = PTR_ERR(key_ref); goto error; @@ -1013,17 +1024,45 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm) key = key_ref_to_ptr(key_ref); - /* make the changes with the locks held to prevent chown/chmod races */ - ret = -EACCES; - down_write(&key->sem); + ret = -EOPNOTSUPP; + if (test_bit(KEY_FLAG_HAS_ACL, &key->flags)) + goto error_key; - /* 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; - ret = 0; + ret = -ENOMEM; + acl = kzalloc(struct_size(acl, aces, nr), GFP_KERNEL); + if (!acl) + goto error_key; + + refcount_set(&acl->usage, 1); + acl->nr_ace = nr; + j = 0; + for (i = 0; i < 4; i++) { + struct key_ace *ace = &acl->aces[j]; + unsigned int subset = (perm >> (i * 8)) & KEY_OTH_ALL; + + if (!subset) + continue; + ace->type = KEY_ACE_SUBJ_STANDARD; + ace->subject_id = KEY_ACE_EVERYONE + i; + ace->perm = subset; + if (subset & (KEY_OTH_WRITE | KEY_OTH_SETATTR)) + ace->perm |= KEY_ACE_REVOKE; + if (subset & KEY_OTH_SEARCH) + ace->perm |= KEY_ACE_INVAL; + if (key->type == &key_type_keyring) { + if (subset & KEY_OTH_SEARCH) + ace->perm |= KEY_ACE_JOIN; + if (subset & KEY_OTH_WRITE) + ace->perm |= KEY_ACE_CLEAR; + } + j++; } + /* make the changes with the locks held to prevent chown/chmod races */ + down_write(&key->sem); + ret = key_set_acl(key, acl); up_write(&key->sem); +error_key: key_put(key); error: return ret; @@ -1388,7 +1427,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout) long ret; key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, - KEY_NEED_SETATTR); + KEY_NEED_SETSEC); if (IS_ERR(key_ref)) { /* setting the timeout on a key under construction is permitted * if we have the authorisation token handy */ @@ -1411,10 +1450,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: @@ -1539,7 +1580,7 @@ long keyctl_get_security(key_serial_t keyid, * Attempt to install the calling process's session keyring on the process's * parent process. * - * The keyring must exist and must grant the caller LINK permission, and the + * The keyring must exist and must grant the caller JOIN permission, and the * parent process must be single-threaded and must have the same effective * ownership as this process and mustn't be SUID/SGID. * @@ -1556,7 +1597,7 @@ long keyctl_session_to_parent(void) struct cred *cred; int ret; - keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_LINK); + keyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_JOIN); if (IS_ERR(keyring_r)) return PTR_ERR(keyring_r); @@ -1658,7 +1699,7 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type, char *restriction = NULL; long ret; - key_ref = lookup_user_key(id, 0, KEY_NEED_SETATTR); + key_ref = lookup_user_key(id, 0, KEY_NEED_SETSEC); if (IS_ERR(key_ref)) return PTR_ERR(key_ref); @@ -1688,6 +1729,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. */ @@ -1764,7 +1889,7 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, case KEYCTL_SETPERM: return keyctl_setperm_key((key_serial_t) arg2, - (key_perm_t) arg3); + (unsigned int)arg3); case KEYCTL_INSTANTIATE: return keyctl_instantiate_key((key_serial_t) arg2, @@ -1853,10 +1978,18 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, (key_serial_t)arg3, (key_serial_t)arg4, (unsigned int)arg5); + case KEYCTL_GRANT_PERMISSION: + return keyctl_grant_permission((key_serial_t)arg2, + (enum key_ace_subject_type)arg3, + (unsigned int)arg4, + (unsigned int)arg5); 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; } diff --git a/security/keys/keyring.c b/security/keys/keyring.c index febf36c6ddc5..c5eb8f11cd24 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -515,11 +515,19 @@ static long keyring_read(const struct key *keyring, return ret; } -/* - * Allocate a keyring and link into the destination keyring. +/** + * keyring_alloc - Allocate a keyring and link into the destination + * @description: The key description to allow the key to be searched out. + * @uid: The owner of the new key. + * @gid: The group ID for the new key's group permissions. + * @cred: The credentials specifying UID namespace. + * @acl: The ACL to attach to the new key. + * @flags: Flags specifying quota properties. + * @restrict_link: Optional link restriction for new keyrings. + * @dest: Destination keyring. */ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid, - const struct cred *cred, key_perm_t perm, + const struct cred *cred, struct key_acl *acl, unsigned long flags, struct key_restriction *restrict_link, struct key *dest) @@ -528,7 +536,7 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid, int ret; keyring = key_alloc(&key_type_keyring, description, - uid, gid, cred, perm, flags, restrict_link); + uid, gid, cred, acl, flags, restrict_link); if (!IS_ERR(keyring)) { ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL); if (ret < 0) { @@ -1060,12 +1068,14 @@ int keyring_restrict(key_ref_t keyring_ref, const char *type, down_write(&keyring->sem); down_write(&keyring_serialise_restrict_sem); - if (keyring->restrict_link) + if (keyring->restrict_link) { ret = -EEXIST; - else if (keyring_detect_restriction_cycle(keyring, restrict_link)) + } else if (keyring_detect_restriction_cycle(keyring, restrict_link)) { ret = -EDEADLK; - else + } else { keyring->restrict_link = restrict_link; + notify_key(keyring, NOTIFY_KEY_SETATTR, 0); + } up_write(&keyring_serialise_restrict_sem); up_write(&keyring->sem); @@ -1132,10 +1142,11 @@ found: /* * Find a keyring with the specified name. * - * Only keyrings that have nonzero refcount, are not revoked, and are owned by a - * user in the current user namespace are considered. If @uid_keyring is %true, - * the keyring additionally must have been allocated as a user or user session - * keyring; otherwise, it must grant Search permission directly to the caller. + * Only keyrings that have nonzero refcount, are not revoked, and are owned by + * a user in the current user namespace are considered. If @uid_keyring is + * %true, the keyring additionally must have been allocated as a user or user + * session keyring; otherwise, it must grant JOIN permission directly to the + * caller (ie. not through possession). * * Returns a pointer to the keyring with the keyring's refcount having being * incremented on success. -ENOKEY is returned if a key could not be found. @@ -1169,7 +1180,7 @@ struct key *find_keyring_by_name(const char *name, bool uid_keyring) continue; } else { if (key_permission(make_key_ref(keyring, 0), - KEY_NEED_SEARCH) < 0) + KEY_NEED_JOIN) < 0) continue; } @@ -1366,12 +1377,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 * 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); assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key)); assoc_array_apply_edit(*_edit); *_edit = NULL; + notify_key(keyring, NOTIFY_KEY_LINKED, key_serial(key)); } /* @@ -1455,7 +1468,7 @@ int key_link(struct key *keyring, struct key *key) if (ret == 0) ret = __key_link_check_live_key(keyring, key); if (ret == 0) - __key_link(key, &edit); + __key_link(keyring, key, &edit); error_end: __key_link_end(keyring, &key->index_key, edit); @@ -1487,7 +1500,7 @@ static int __key_unlink_begin(struct key *keyring, struct key *key, struct assoc_array_edit *edit; BUG_ON(*_edit != NULL); - + edit = assoc_array_delete(&keyring->keys, &keyring_assoc_array_ops, &key->index_key); if (IS_ERR(edit)) @@ -1507,6 +1520,7 @@ static void __key_unlink(struct key *keyring, struct key *key, struct assoc_array_edit **_edit) { assoc_array_apply_edit(*_edit); + notify_key(keyring, NOTIFY_KEY_UNLINKED, key_serial(key)); *_edit = NULL; key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES); } @@ -1625,7 +1639,7 @@ int key_move(struct key *key, goto error; __key_unlink(from_keyring, key, &from_edit); - __key_link(key, &to_edit); + __key_link(to_keyring, key, &to_edit); error: __key_link_end(to_keyring, &key->index_key, to_edit); __key_unlink_end(from_keyring, key, from_edit); @@ -1659,6 +1673,7 @@ int keyring_clear(struct key *keyring) } else { if (edit) assoc_array_apply_edit(edit); + notify_key(keyring, NOTIFY_KEY_CLEARED, 0); key_payload_reserve(keyring, 0); ret = 0; } diff --git a/security/keys/permission.c b/security/keys/permission.c index 085f907b64ac..082f3c2f1dd6 100644 --- a/security/keys/permission.c +++ b/security/keys/permission.c @@ -7,13 +7,67 @@ #include <linux/export.h> #include <linux/security.h> +#include <linux/user_namespace.h> +#include <linux/uaccess.h> #include "internal.h" +struct key_acl default_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~KEY_ACE_JOIN), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; +EXPORT_SYMBOL(default_key_acl); + +struct key_acl joinable_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~KEY_ACE_JOIN), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_LINK | KEY_ACE_JOIN), + } +}; +EXPORT_SYMBOL(joinable_keyring_acl); + +struct key_acl internal_key_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH), + } +}; +EXPORT_SYMBOL(internal_key_acl); + +struct key_acl internal_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH), + } +}; +EXPORT_SYMBOL(internal_keyring_acl); + +struct key_acl internal_writable_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | KEY_ACE_SEARCH), + } +}; +EXPORT_SYMBOL(internal_writable_keyring_acl); + /** * key_task_permission - Check a key can be used * @key_ref: The key to check. * @cred: The credentials to use. - * @perm: The permissions to check for. + * @desired_perm: The permission to check for. * * Check to see whether permission is granted to use a key in the desired way, * but permit the security modules to override. @@ -24,53 +78,73 @@ * permissions bits or the LSM check. */ int key_task_permission(const key_ref_t key_ref, const struct cred *cred, - unsigned perm) + unsigned int desired_perm) { - struct key *key; - key_perm_t kperm; - int ret; + const struct key_acl *acl; + const struct key *key; + unsigned int allow = 0; + int i; + + BUILD_BUG_ON(KEY_NEED_VIEW != KEY_ACE_VIEW || + KEY_NEED_READ != KEY_ACE_READ || + KEY_NEED_WRITE != KEY_ACE_WRITE || + KEY_NEED_SEARCH != KEY_ACE_SEARCH || + KEY_NEED_LINK != KEY_ACE_LINK || + KEY_NEED_SETSEC != KEY_ACE_SET_SECURITY || + KEY_NEED_INVAL != KEY_ACE_INVAL || + KEY_NEED_REVOKE != KEY_ACE_REVOKE || + KEY_NEED_JOIN != KEY_ACE_JOIN || + KEY_NEED_CLEAR != KEY_ACE_CLEAR); key = key_ref_to_ptr(key_ref); - /* use the second 8-bits of permissions for keys the caller owns */ - if (uid_eq(key->uid, cred->fsuid)) { - kperm = key->perm >> 16; - goto use_these_perms; - } + rcu_read_lock(); - /* use the third 8-bits of permissions for keys the caller has a group - * membership in common with */ - if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) { - if (gid_eq(key->gid, cred->fsgid)) { - kperm = key->perm >> 8; - goto use_these_perms; - } + acl = rcu_dereference(key->acl); + if (!acl || acl->nr_ace == 0) + goto no_access_rcu; - ret = groups_search(cred->group_info, key->gid); - if (ret) { - kperm = key->perm >> 8; - goto use_these_perms; + for (i = 0; i < acl->nr_ace; i++) { + const struct key_ace *ace = &acl->aces[i]; + + switch (ace->type) { + case KEY_ACE_SUBJ_STANDARD: + switch (ace->subject_id) { + case KEY_ACE_POSSESSOR: + if (is_key_possessed(key_ref)) + allow |= ace->perm; + break; + case KEY_ACE_OWNER: + if (uid_eq(key->uid, cred->fsuid)) + allow |= ace->perm; + break; + case KEY_ACE_GROUP: + if (gid_valid(key->gid)) { + if (gid_eq(key->gid, cred->fsgid)) + allow |= ace->perm; + else if (groups_search(cred->group_info, key->gid)) + allow |= ace->perm; + } + break; + case KEY_ACE_EVERYONE: + allow |= ace->perm; + break; + } + break; } } - /* otherwise use the least-significant 8-bits */ - kperm = key->perm; - -use_these_perms: + rcu_read_unlock(); - /* use the top 8-bits of permissions for keys the caller possesses - * - possessor permissions are additive with other permissions - */ - if (is_key_possessed(key_ref)) - kperm |= key->perm >> 24; + if (!(allow & desired_perm)) + goto no_access; - kperm = kperm & perm & KEY_NEED_ALL; + return security_key_permission(key_ref, cred, desired_perm); - if (kperm != perm) - return -EACCES; - - /* let LSM be the final arbiter */ - return security_key_permission(key_ref, cred, perm); +no_access_rcu: + rcu_read_unlock(); +no_access: + return -EACCES; } EXPORT_SYMBOL(key_task_permission); @@ -104,3 +178,220 @@ int key_validate(const struct key *key) return 0; } EXPORT_SYMBOL(key_validate); + +/* + * Roughly render an ACL to an old-style permissions mask. We cannot + * accurately render what the ACL, particularly if it has ACEs that represent + * subjects outside of { poss, user, group, other }. + */ +unsigned int key_acl_to_perm(const struct key_acl *acl) +{ + unsigned int perm = 0, tperm; + int i; + + BUILD_BUG_ON(KEY_OTH_VIEW != KEY_ACE_VIEW || + KEY_OTH_READ != KEY_ACE_READ || + KEY_OTH_WRITE != KEY_ACE_WRITE || + KEY_OTH_SEARCH != KEY_ACE_SEARCH || + KEY_OTH_LINK != KEY_ACE_LINK || + KEY_OTH_SETATTR != KEY_ACE_SET_SECURITY); + + if (!acl || acl->nr_ace == 0) + return 0; + + for (i = 0; i < acl->nr_ace; i++) { + const struct key_ace *ace = &acl->aces[i]; + + switch (ace->type) { + case KEY_ACE_SUBJ_STANDARD: + tperm = ace->perm & KEY_OTH_ALL; + + /* Invalidation and joining were allowed by SEARCH */ + if (ace->perm & (KEY_ACE_INVAL | KEY_ACE_JOIN)) + tperm |= KEY_OTH_SEARCH; + + /* Revocation was allowed by either SETATTR or WRITE */ + if ((ace->perm & KEY_ACE_REVOKE) && !(tperm & KEY_OTH_SETATTR)) + tperm |= KEY_OTH_WRITE; + + /* Clearing was allowed by WRITE */ + if (ace->perm & KEY_ACE_CLEAR) + tperm |= KEY_OTH_WRITE; + + switch (ace->subject_id) { + case KEY_ACE_POSSESSOR: + perm |= tperm << 24; + break; + case KEY_ACE_OWNER: + perm |= tperm << 16; + break; + case KEY_ACE_GROUP: + perm |= tperm << 8; + break; + case KEY_ACE_EVERYONE: + perm |= tperm << 0; + break; + } + } + } + + return perm; +} + +/* + * Destroy a key's ACL. + */ +void key_put_acl(struct key_acl *acl) +{ + if (acl && refcount_dec_and_test(&acl->usage)) + kfree_rcu(acl, rcu); +} + +/* + * Try to set the ACL. This either attaches or discards the proposed ACL. + */ +long key_set_acl(struct key *key, struct key_acl *acl) +{ + int i; + + /* 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_put_acl(acl); + return -EACCES; + } + + for (i = 0; i < acl->nr_ace; i++) { + const struct key_ace *ace = &acl->aces[i]; + if (ace->type == KEY_ACE_SUBJ_STANDARD && + ace->subject_id == KEY_ACE_POSSESSOR) { + if (ace->perm & KEY_ACE_VIEW) + acl->possessor_viewable = true; + break; + } + } + + rcu_swap_protected(key->acl, acl, lockdep_is_held(&key->sem)); + notify_key(key, NOTIFY_KEY_SETATTR, 0); + key_put_acl(acl); + return 0; +} + +/* + * Allocate a new ACL with an extra ACE slot. + */ +static struct key_acl *key_alloc_acl(const struct key_acl *old_acl, int nr, int skip) +{ + struct key_acl *acl; + int nr_ace, i, j = 0; + + nr_ace = old_acl->nr_ace + nr; + if (nr_ace > 16) + return ERR_PTR(-EINVAL); + + acl = kzalloc(struct_size(acl, aces, nr_ace), GFP_KERNEL); + if (!acl) + return ERR_PTR(-ENOMEM); + + refcount_set(&acl->usage, 1); + acl->nr_ace = nr_ace; + for (i = 0; i < old_acl->nr_ace; i++) { + if (i == skip) + continue; + acl->aces[j] = old_acl->aces[i]; + j++; + } + return acl; +} + +/* + * Generate the revised ACL. + */ +static long key_change_acl(struct key *key, struct key_ace *new_ace) +{ + struct key_acl *acl, *old; + int i; + + old = rcu_dereference_protected(key->acl, lockdep_is_held(&key->sem)); + + for (i = 0; i < old->nr_ace; i++) + if (old->aces[i].type == new_ace->type && + old->aces[i].subject_id == new_ace->subject_id) + goto found_match; + + if (new_ace->perm == 0) + return 0; /* No permissions to remove. Add deny record? */ + + acl = key_alloc_acl(old, 1, -1); + if (IS_ERR(acl)) + return PTR_ERR(acl); + acl->aces[i] = *new_ace; + goto change; + +found_match: + if (new_ace->perm == 0) + goto delete_ace; + if (new_ace->perm == old->aces[i].perm) + return 0; + acl = key_alloc_acl(old, 0, -1); + if (IS_ERR(acl)) + return PTR_ERR(acl); + acl->aces[i].perm = new_ace->perm; + goto change; + +delete_ace: + acl = key_alloc_acl(old, -1, i); + if (IS_ERR(acl)) + return PTR_ERR(acl); + goto change; + +change: + return key_set_acl(key, acl); +} + +/* + * Add, alter or remove (if perm == 0) an ACE in a key's ACL. + */ +long keyctl_grant_permission(key_serial_t keyid, + enum key_ace_subject_type type, + unsigned int subject, + unsigned int perm) +{ + struct key_ace new_ace; + struct key *key; + key_ref_t key_ref; + long ret; + + new_ace.type = type; + new_ace.perm = perm; + + switch (type) { + case KEY_ACE_SUBJ_STANDARD: + if (subject >= nr__key_ace_standard_subject) + return -ENOENT; + new_ace.subject_id = subject; + break; + + default: + return -ENOENT; + } + + key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_SETSEC); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key = key_ref_to_ptr(key_ref); + + down_write(&key->sem); + + /* If we're not the sysadmin, we can only change a key that we own */ + ret = -EACCES; + if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) + ret = key_change_acl(key, &new_ace); + + up_write(&key->sem); + key_put(key); +error: + return ret; +} diff --git a/security/keys/persistent.c b/security/keys/persistent.c index 97af230aa4b2..8171c90d4c9a 100644 --- a/security/keys/persistent.c +++ b/security/keys/persistent.c @@ -12,6 +12,27 @@ unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */ +static struct key_acl persistent_register_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_SEARCH | KEY_ACE_WRITE), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ), + } +}; + +static struct key_acl persistent_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | + KEY_ACE_SEARCH | KEY_ACE_LINK | + KEY_ACE_CLEAR | KEY_ACE_INVAL), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ), + } +}; + /* * Create the persistent keyring register for the current user namespace. * @@ -22,8 +43,7 @@ static int key_create_persistent_register(struct user_namespace *ns) struct key *reg = keyring_alloc(".persistent_register", KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ), + &persistent_register_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(reg)) return PTR_ERR(reg); @@ -56,8 +76,7 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, persistent = keyring_alloc(index_key->description, uid, INVALID_GID, current_cred(), - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ), + &persistent_keyring_acl, KEY_ALLOC_NOT_IN_QUOTA, NULL, ns->persistent_keyring_register); if (IS_ERR(persistent)) diff --git a/security/keys/proc.c b/security/keys/proc.c index 415f3f1c2da0..b394ad1e874b 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -110,11 +110,13 @@ static struct key *find_ge_key(struct seq_file *p, key_serial_t id) } static void *proc_keys_start(struct seq_file *p, loff_t *_pos) + __acquires(rcu) __acquires(key_serial_lock) { key_serial_t pos = *_pos; struct key *key; + rcu_read_lock(); spin_lock(&key_serial_lock); if (*_pos > INT_MAX) @@ -144,12 +146,15 @@ static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) static void proc_keys_stop(struct seq_file *p, void *v) __releases(key_serial_lock) + __releases(rcu) { spin_unlock(&key_serial_lock); + rcu_read_unlock(); } static int proc_keys_show(struct seq_file *m, void *v) { + const struct key_acl *acl; struct rb_node *_p = v; struct key *key = rb_entry(_p, struct key, serial_node); unsigned long flags; @@ -157,6 +162,7 @@ static int proc_keys_show(struct seq_file *m, void *v) time64_t now, expiry; char xbuf[16]; short state; + bool check_pos; u64 timo; int rc; @@ -170,15 +176,15 @@ static int proc_keys_show(struct seq_file *m, void *v) KEYRING_SEARCH_RECURSE), }; - key_ref = make_key_ref(key, 0); + acl = rcu_dereference(key->acl); + check_pos = acl->possessor_viewable; /* determine if the key is possessed by this process (a test we can * skip if the key does not indicate the possessor can view it */ - if (key->perm & KEY_POS_VIEW) { - rcu_read_lock(); + key_ref = make_key_ref(key, 0); + if (check_pos) { skey_ref = search_cred_keyrings_rcu(&ctx); - rcu_read_unlock(); if (!IS_ERR(skey_ref)) { key_ref_put(skey_ref); key_ref = make_key_ref(key, 1); @@ -188,12 +194,10 @@ static int proc_keys_show(struct seq_file *m, void *v) /* check whether the current task is allowed to view the key */ rc = key_task_permission(key_ref, ctx.cred, KEY_NEED_VIEW); if (rc < 0) - return 0; + goto out; now = ktime_get_real_seconds(); - rcu_read_lock(); - /* come up with a suitable timeout value */ expiry = READ_ONCE(key->expiry); if (expiry == 0) { @@ -232,7 +236,7 @@ static int proc_keys_show(struct seq_file *m, void *v) showflag(flags, 'i', KEY_FLAG_INVALIDATED), refcount_read(&key->usage), xbuf, - key->perm, + key_acl_to_perm(acl), from_kuid_munged(seq_user_ns(m), key->uid), from_kgid_munged(seq_user_ns(m), key->gid), key->type->name); @@ -243,7 +247,7 @@ static int proc_keys_show(struct seq_file *m, void *v) key->type->describe(key, m); seq_putc(m, '\n'); - rcu_read_unlock(); +out: return 0; } diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 09541de31f2f..519c94f1cc3c 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -32,6 +32,47 @@ struct key_user root_key_user = { .uid = GLOBAL_ROOT_UID, }; +static struct key_acl user_reg_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .possessor_viewable = true, + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_WRITE | KEY_ACE_SEARCH), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ), + } +}; + +static struct key_acl user_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .possessor_viewable = true, + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_WRITE | + KEY_ACE_SEARCH | KEY_ACE_LINK), + KEY_OWNER_ACE(KEY_ACE__PERMS & ~(KEY_ACE_JOIN | KEY_ACE_SET_SECURITY)), + } +}; + +static struct key_acl session_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .possessor_viewable = true, + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE__PERMS), + KEY_OWNER_ACE(KEY_ACE_VIEW | KEY_ACE_READ), + } +}; + +static struct key_acl thread_and_process_keyring_acl = { + .usage = REFCOUNT_INIT(1), + .possessor_viewable = true, + .nr_ace = 2, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE__PERMS & ~(KEY_ACE_JOIN | KEY_ACE_SET_SECURITY)), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; + /* * Get or create a user register keyring. */ @@ -51,11 +92,8 @@ static struct key *get_user_register(struct user_namespace *user_ns) if (!reg_keyring) { reg_keyring = keyring_alloc(".user_reg", user_ns->owner, INVALID_GID, - &init_cred, - KEY_POS_WRITE | KEY_POS_SEARCH | - KEY_USR_VIEW | KEY_USR_READ, - 0, - NULL, NULL); + &init_cred, &user_reg_keyring_acl, + 0, NULL, NULL); if (!IS_ERR(reg_keyring)) smp_store_release(&user_ns->user_keyring_register, reg_keyring); @@ -77,14 +115,11 @@ int look_up_user_keyrings(struct key **_user_keyring, const struct cred *cred = current_cred(); struct user_namespace *user_ns = current_user_ns(); struct key *reg_keyring, *uid_keyring, *session_keyring; - key_perm_t user_keyring_perm; key_ref_t uid_keyring_r, session_keyring_r; uid_t uid = from_kuid(user_ns, cred->user->uid); char buf[20]; int ret; - user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL; - kenter("%u", uid); reg_keyring = get_user_register(user_ns); @@ -104,7 +139,7 @@ int look_up_user_keyrings(struct key **_user_keyring, kdebug("_uid %p", uid_keyring_r); if (uid_keyring_r == ERR_PTR(-EAGAIN)) { uid_keyring = keyring_alloc(buf, cred->user->uid, INVALID_GID, - cred, user_keyring_perm, + cred, &user_keyring_acl, KEY_ALLOC_UID_KEYRING | KEY_ALLOC_IN_QUOTA, NULL, reg_keyring); @@ -126,7 +161,7 @@ int look_up_user_keyrings(struct key **_user_keyring, kdebug("_uid_ses %p", session_keyring_r); if (session_keyring_r == ERR_PTR(-EAGAIN)) { session_keyring = keyring_alloc(buf, cred->user->uid, INVALID_GID, - cred, user_keyring_perm, + cred, &user_keyring_acl, KEY_ALLOC_UID_KEYRING | KEY_ALLOC_IN_QUOTA, NULL, NULL); @@ -226,7 +261,7 @@ int install_thread_keyring_to_cred(struct cred *new) return 0; keyring = keyring_alloc("_tid", new->uid, new->gid, new, - KEY_POS_ALL | KEY_USR_VIEW, + &thread_and_process_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL); if (IS_ERR(keyring)) @@ -273,7 +308,7 @@ int install_process_keyring_to_cred(struct cred *new) return 0; keyring = keyring_alloc("_pid", new->uid, new->gid, new, - KEY_POS_ALL | KEY_USR_VIEW, + &thread_and_process_keyring_acl, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL); if (IS_ERR(keyring)) @@ -328,8 +363,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring) flags = KEY_ALLOC_IN_QUOTA; keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred, - KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ, - flags, NULL, NULL); + &session_keyring_acl, flags, NULL, NULL); if (IS_ERR(keyring)) return PTR_ERR(keyring); } else { @@ -609,7 +643,7 @@ bool lookup_user_key_possessed(const struct key *key, * returned key reference. */ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags, - key_perm_t perm) + unsigned int desired_perm) { struct keyring_search_context ctx = { .match_data.cmp = lookup_user_key_possessed, @@ -784,12 +818,12 @@ try_again: case -ERESTARTSYS: goto invalid_key; default: - if (perm) + if (desired_perm) goto invalid_key; case 0: break; } - } else if (perm) { + } else if (desired_perm) { ret = key_validate(key); if (ret < 0) goto invalid_key; @@ -801,9 +835,11 @@ try_again: goto invalid_key; /* check the permissions */ - ret = key_task_permission(key_ref, ctx.cred, perm); - if (ret < 0) - goto invalid_key; + if (desired_perm) { + ret = key_task_permission(key_ref, ctx.cred, desired_perm); + if (ret < 0) + goto invalid_key; + } key->last_used_at = ktime_get_real_seconds(); @@ -868,13 +904,13 @@ long join_session_keyring(const char *name) if (PTR_ERR(keyring) == -ENOKEY) { /* not found - try and create a new one */ keyring = keyring_alloc( - name, old->uid, old->gid, old, - KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK, + name, old->uid, old->gid, old, &joinable_keyring_acl, KEY_ALLOC_IN_QUOTA, NULL, NULL); if (IS_ERR(keyring)) { ret = PTR_ERR(keyring); goto error2; } + goto no_perm_test; } else if (IS_ERR(keyring)) { ret = PTR_ERR(keyring); goto error2; @@ -883,6 +919,12 @@ long join_session_keyring(const char *name) goto error3; } + ret = key_task_permission(make_key_ref(keyring, false), old, + KEY_NEED_JOIN); + if (ret < 0) + goto error3; + +no_perm_test: /* we've got a keyring - now to install it */ ret = install_session_keyring_to_cred(new, keyring); if (ret < 0) diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 957b9e3e1492..1b675d5e596b 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -135,8 +135,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux) cred = get_current_cred(); keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred, - KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ, - KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL); + NULL, KEY_ALLOC_QUOTA_OVERRUN, NULL, NULL); put_cred(cred); if (IS_ERR(keyring)) { ret = PTR_ERR(keyring); @@ -367,11 +366,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx, struct key *dest_keyring, unsigned long flags, struct key_user *user, + struct key_acl *acl, struct key **_key) { struct assoc_array_edit *edit = NULL; struct key *key; - key_perm_t perm; key_ref_t key_ref; int ret; @@ -381,17 +380,9 @@ static int construct_alloc_key(struct keyring_search_context *ctx, *_key = NULL; mutex_lock(&user->cons_lock); - perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; - perm |= KEY_USR_VIEW; - if (ctx->index_key.type->read) - perm |= KEY_POS_READ; - if (ctx->index_key.type == &key_type_keyring || - ctx->index_key.type->update) - perm |= KEY_POS_WRITE; - key = key_alloc(ctx->index_key.type, ctx->index_key.description, ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred, - perm, flags, NULL); + acl, flags, NULL); if (IS_ERR(key)) goto alloc_failed; @@ -418,7 +409,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx, goto key_already_present; if (dest_keyring) - __key_link(key, &edit); + __key_link(dest_keyring, key, &edit); mutex_unlock(&key_construction_mutex); if (dest_keyring) @@ -437,7 +428,7 @@ key_already_present: if (dest_keyring) { ret = __key_link_check_live_key(dest_keyring, key); if (ret == 0) - __key_link(key, &edit); + __key_link(dest_keyring, key, &edit); __key_link_end(dest_keyring, &ctx->index_key, edit); if (ret < 0) goto link_check_failed; @@ -474,6 +465,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx, const char *callout_info, size_t callout_len, void *aux, + struct key_acl *acl, struct key *dest_keyring, unsigned long flags) { @@ -496,7 +488,7 @@ static struct key *construct_key_and_link(struct keyring_search_context *ctx, goto error_put_dest_keyring; } - ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key); + ret = construct_alloc_key(ctx, dest_keyring, flags, user, acl, &key); key_user_put(user); if (ret == 0) { @@ -534,6 +526,7 @@ error: * @callout_info: The data to pass to the instantiation upcall (or NULL). * @callout_len: The length of callout_info. * @aux: Auxiliary data for the upcall. + * @acl: The ACL to attach if a new key is created. * @dest_keyring: Where to cache the key. * @flags: Flags to key_alloc(). * @@ -561,6 +554,7 @@ struct key *request_key_and_link(struct key_type *type, const void *callout_info, size_t callout_len, void *aux, + struct key_acl *acl, struct key *dest_keyring, unsigned long flags) { @@ -635,7 +629,7 @@ struct key *request_key_and_link(struct key_type *type, goto error_free; key = construct_key_and_link(&ctx, callout_info, callout_len, - aux, dest_keyring, flags); + aux, acl, dest_keyring, flags); } error_free: @@ -678,6 +672,7 @@ EXPORT_SYMBOL(wait_for_key_construction); * @description: The searchable description of the key. * @domain_tag: The domain in which the key operates. * @callout_info: The data to pass to the instantiation upcall (or NULL). + * @acl: The ACL to attach if a new key is created. * * As for request_key_and_link() except that it does not add the returned key * to a keyring if found, new keys are always allocated in the user's quota, @@ -690,7 +685,8 @@ EXPORT_SYMBOL(wait_for_key_construction); struct key *request_key_tag(struct key_type *type, const char *description, struct key_tag *domain_tag, - const char *callout_info) + const char *callout_info, + struct key_acl *acl) { struct key *key; size_t callout_len = 0; @@ -700,7 +696,7 @@ struct key *request_key_tag(struct key_type *type, callout_len = strlen(callout_info); key = request_key_and_link(type, description, domain_tag, callout_info, callout_len, - NULL, NULL, KEY_ALLOC_IN_QUOTA); + NULL, acl, NULL, KEY_ALLOC_IN_QUOTA); if (!IS_ERR(key)) { ret = wait_for_key_construction(key, false); if (ret < 0) { @@ -720,6 +716,7 @@ EXPORT_SYMBOL(request_key_tag); * @callout_info: The data to pass to the instantiation upcall (or NULL). * @callout_len: The length of callout_info. * @aux: Auxiliary data for the upcall. + * @acl: The ACL to attach if a new key is created. * * As for request_key_and_link() except that it does not add the returned key * to a keyring if found and new keys are always allocated in the user's quota. @@ -732,14 +729,15 @@ struct key *request_key_with_auxdata(struct key_type *type, struct key_tag *domain_tag, const void *callout_info, size_t callout_len, - void *aux) + void *aux, + struct key_acl *acl) { struct key *key; int ret; key = request_key_and_link(type, description, domain_tag, callout_info, callout_len, - aux, NULL, KEY_ALLOC_IN_QUOTA); + aux, acl, NULL, KEY_ALLOC_IN_QUOTA); if (!IS_ERR(key)) { ret = wait_for_key_construction(key, false); if (ret < 0) { diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index ecba39c93fd9..441e9fd15726 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -24,6 +24,17 @@ static void request_key_auth_revoke(struct key *); static void request_key_auth_destroy(struct key *); static long request_key_auth_read(const struct key *, char __user *, size_t); +static struct key_acl request_key_auth_acl = { + .usage = REFCOUNT_INIT(1), + .nr_ace = 2, + .possessor_viewable = true, + .aces = { + KEY_POSSESSOR_ACE(KEY_ACE_VIEW | KEY_ACE_READ | KEY_ACE_SEARCH | + KEY_ACE_LINK), + KEY_OWNER_ACE(KEY_ACE_VIEW), + } +}; + /* * The request-key authorisation key type definition. */ @@ -216,8 +227,8 @@ struct key *request_key_auth_new(struct key *target, const char *op, authkey = key_alloc(&key_type_request_key_auth, desc, cred->fsuid, cred->fsgid, cred, - KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH | KEY_POS_LINK | - KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA, NULL); + &request_key_auth_acl, + KEY_ALLOC_NOT_IN_QUOTA, NULL); if (IS_ERR(authkey)) { ret = PTR_ERR(authkey); goto error_free_rka; diff --git a/security/security.c b/security/security.c index ce6c945bf347..f97723aee417 100644 --- a/security/security.c +++ b/security/security.c @@ -1950,6 +1950,29 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) } EXPORT_SYMBOL(security_inode_getsecctx); +#ifdef CONFIG_WATCH_QUEUE +int security_post_notification(const struct cred *w_cred, + const struct cred *cred, + struct watch_notification *n) +{ + return call_int_hook(post_notification, 0, w_cred, cred, n); +} +#endif /* CONFIG_WATCH_QUEUE */ + +#ifdef CONFIG_KEY_NOTIFICATIONS +int security_watch_key(struct key *key) +{ + return call_int_hook(watch_key, 0, key); +} +#endif + +#ifdef CONFIG_DEVICE_NOTIFICATIONS +int security_watch_devices(void) +{ + return call_int_hook(watch_devices, 0); +} +#endif + #ifdef CONFIG_SECURITY_NETWORK int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 74dd46de01b6..901cc052fcad 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6502,6 +6502,7 @@ static int selinux_key_permission(key_ref_t key_ref, { struct key *key; struct key_security_struct *ksec; + unsigned oldstyle_perm; u32 sid; /* if no specific permissions are requested, we skip the @@ -6510,13 +6511,26 @@ static int selinux_key_permission(key_ref_t key_ref, if (perm == 0) return 0; + oldstyle_perm = perm & (KEY_NEED_VIEW | KEY_NEED_READ | KEY_NEED_WRITE | + KEY_NEED_SEARCH | KEY_NEED_LINK); + if (perm & KEY_NEED_SETSEC) + oldstyle_perm |= OLD_KEY_NEED_SETATTR; + if (perm & KEY_NEED_INVAL) + oldstyle_perm |= KEY_NEED_SEARCH; + if (perm & KEY_NEED_REVOKE && !(perm & OLD_KEY_NEED_SETATTR)) + oldstyle_perm |= KEY_NEED_WRITE; + if (perm & KEY_NEED_JOIN) + oldstyle_perm |= KEY_NEED_SEARCH; + if (perm & KEY_NEED_CLEAR) + oldstyle_perm |= KEY_NEED_WRITE; + sid = cred_sid(cred); key = key_ref_to_ptr(key_ref); ksec = key->security; return avc_has_perm(&selinux_state, - sid, ksec->sid, SECCLASS_KEY, perm, NULL); + sid, ksec->sid, SECCLASS_KEY, oldstyle_perm, NULL); } static int selinux_key_getsecurity(struct key *key, char **_buffer) @@ -6533,6 +6547,17 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer) *_buffer = context; return rc; } + +#ifdef CONFIG_KEY_NOTIFICATIONS +static int selinux_watch_key(struct key *key) +{ + struct key_security_struct *ksec = key->security; + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, + sid, ksec->sid, SECCLASS_KEY, KEY_NEED_VIEW, NULL); +} +#endif #endif #ifdef CONFIG_SECURITY_INFINIBAND @@ -6965,6 +6990,9 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(key_free, selinux_key_free), LSM_HOOK_INIT(key_permission, selinux_key_permission), LSM_HOOK_INIT(key_getsecurity, selinux_key_getsecurity), +#ifdef CONFIG_KEY_NOTIFICATIONS + LSM_HOOK_INIT(watch_key, selinux_watch_key), +#endif #endif #ifdef CONFIG_AUDIT diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 4c5e5a438f8b..d957450a30ab 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -4274,7 +4274,7 @@ static int smack_key_permission(key_ref_t key_ref, if (tkp == NULL) return -EACCES; - if (smack_privileged_cred(CAP_MAC_OVERRIDE, cred)) + if (smack_privileged(CAP_MAC_OVERRIDE)) return 0; #ifdef CONFIG_AUDIT @@ -4284,7 +4284,8 @@ static int smack_key_permission(key_ref_t key_ref, #endif if (perm & (KEY_NEED_READ | KEY_NEED_SEARCH | KEY_NEED_VIEW)) request |= MAY_READ; - if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETATTR)) + if (perm & (KEY_NEED_WRITE | KEY_NEED_LINK | KEY_NEED_SETSEC | + KEY_NEED_INVAL | KEY_NEED_REVOKE | KEY_NEED_CLEAR)) request |= MAY_WRITE; rc = smk_access(tkp, keyp->security, request, &ad); rc = smk_bu_note("key access", tkp, keyp->security, request, rc); @@ -4320,8 +4321,81 @@ static int smack_key_getsecurity(struct key *key, char **_buffer) return length; } + +#ifdef CONFIG_KEY_NOTIFICATIONS +/** + * smack_watch_key - Smack access to watch a key for notifications. + * @key: The key to be watched + * + * Return 0 if the @watch->cred has permission to read from the key object and + * an error otherwise. + */ +static int smack_watch_key(struct key *key) +{ + struct smk_audit_info ad; + struct smack_known *tkp = smk_of_current(); + int rc; + + if (key == NULL) + return -EINVAL; + /* + * If the key hasn't been initialized give it access so that + * it may do so. + */ + if (key->security == NULL) + return 0; + /* + * This should not occur + */ + if (tkp == NULL) + return -EACCES; + + if (smack_privileged_cred(CAP_MAC_OVERRIDE, current_cred())) + return 0; + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_KEY); + ad.a.u.key_struct.key = key->serial; + ad.a.u.key_struct.key_desc = key->description; +#endif + rc = smk_access(tkp, key->security, MAY_READ, &ad); + rc = smk_bu_note("key watch", tkp, key->security, MAY_READ, rc); + return rc; +} +#endif /* CONFIG_KEY_NOTIFICATIONS */ #endif /* CONFIG_KEYS */ +#ifdef CONFIG_WATCH_QUEUE +/** + * smack_post_notification - Smack access to post a notification to a queue + * @w_cred: The credentials of the watcher. + * @cred: The credentials of the event source (may be NULL). + * @n: The notification message to be posted. + */ +static int smack_post_notification(const struct cred *w_cred, + const struct cred *cred, + struct watch_notification *n) +{ + struct smk_audit_info ad; + struct smack_known *subj, *obj; + int rc; + + /* Always let maintenance notifications through. */ + if (n->type == WATCH_TYPE_META) + return 0; + + if (!cred) + return 0; + subj = smk_of_task(smack_cred(cred)); + obj = smk_of_task(smack_cred(w_cred)); + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NOTIFICATION); + rc = smk_access(subj, obj, MAY_WRITE, &ad); + rc = smk_bu_note("notification", subj, obj, MAY_WRITE, rc); + return rc; +} +#endif /* CONFIG_WATCH_QUEUE */ + /* * Smack Audit hooks * @@ -4710,8 +4784,15 @@ static struct security_hook_list smack_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(key_free, smack_key_free), LSM_HOOK_INIT(key_permission, smack_key_permission), LSM_HOOK_INIT(key_getsecurity, smack_key_getsecurity), +#ifdef CONFIG_KEY_NOTIFICATIONS + LSM_HOOK_INIT(watch_key, smack_watch_key), +#endif #endif /* CONFIG_KEYS */ +#ifdef CONFIG_WATCH_QUEUE + LSM_HOOK_INIT(post_notification, smack_post_notification), +#endif + /* Audit hooks */ #ifdef CONFIG_AUDIT LSM_HOOK_INIT(audit_rule_init, smack_audit_rule_init), |