diff options
author | Adrian Szyndela <adrian.s@samsung.com> | 2016-09-09 13:35:49 +0200 |
---|---|---|
committer | Seung-Woo Kim <sw0312.kim@samsung.com> | 2019-09-30 15:41:05 +0900 |
commit | 970070c4f68f113284f86cf7b6fbd23d6b35b511 (patch) | |
tree | f12d03ccd850803414b205ca656bd5e73a7c6258 | |
parent | 46247a9428026c731d387344e96d0d14058d3bd8 (diff) | |
download | linux-exynos-970070c4f68f113284f86cf7b6fbd23d6b35b511.tar.gz linux-exynos-970070c4f68f113284f86cf7b6fbd23d6b35b511.tar.bz2 linux-exynos-970070c4f68f113284f86cf7b6fbd23d6b35b511.zip |
kdbus: the driver, original and non-working
[based on commit 216823ac83c0ab89348e2ed6f66179f53626586e]
Introduce the kdbus driver again. This driver worked previously
on kernel 4.1. This is the source code taken from the working driver.
It is non-working and disabled. It is a base for porting.
The documentation is moved from Documentation to ipc/kdbus/Documentation.
The references to kdbus source code are commented out or removed in Makefiles.
Original authors of the files are those from commit 216823ac83c0ab8934.
Change-Id: Id60af5faf794fc4ae7122976621076f1021f6c38
Signed-off-by: Adrian Szyndela <adrian.s@samsung.com>
Signed-off-by: Hyotaek Shim <hyotaek.shim@samsung.com>
91 files changed, 34532 insertions, 1 deletions
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 3e3fdae5f3ed..b1c4cc994f62 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -299,6 +299,7 @@ Code Seq#(hex) Include File Comments 0x92 00-0F drivers/usb/mon/mon_bin.c 0x93 60-7F linux/auto_fs.h 0x94 all fs/btrfs/ioctl.h +0x95 all uapi/linux/kdbus.h kdbus IPC driver 0x97 00-7F fs/ceph/ioctl.h Ceph file system 0x99 00-0F 537-Addinboard driver <mailto:buk@buks.ipn.de> diff --git a/MAINTAINERS b/MAINTAINERS index 6cb70b853323..fae021749f7b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7440,6 +7440,19 @@ S: Maintained F: Documentation/kbuild/kconfig-language.txt F: scripts/kconfig/ +KDBUS +M: Greg Kroah-Hartman <gregkh@linuxfoundation.org> +M: Daniel Mack <daniel@zonque.org> +M: David Herrmann <dh.herrmann@googlemail.com> +M: Djalal Harouni <tixxdz@opendz.org> +L: linux-kernel@vger.kernel.org +S: Maintained +F: ipc/kdbus/* +F: samples/kdbus/* +F: Documentation/kdbus/* +F: include/uapi/linux/kdbus.h +F: tools/testing/selftests/kdbus/ + KDUMP M: Dave Young <dyoung@redhat.com> M: Baoquan He <bhe@redhat.com> diff --git a/include/uapi/linux/kdbus.h b/include/uapi/linux/kdbus.h new file mode 100644 index 000000000000..4fc44cb1d4a8 --- /dev/null +++ b/include/uapi/linux/kdbus.h @@ -0,0 +1,984 @@ +/* + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef _UAPI_KDBUS_H_ +#define _UAPI_KDBUS_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> + +#define KDBUS_IOCTL_MAGIC 0x95 +#define KDBUS_SRC_ID_KERNEL (0) +#define KDBUS_DST_ID_NAME (0) +#define KDBUS_MATCH_ID_ANY (~0ULL) +#define KDBUS_DST_ID_BROADCAST (~0ULL) +#define KDBUS_FLAG_NEGOTIATE (1ULL << 63) + +/** + * struct kdbus_notify_id_change - name registry change message + * @id: New or former owner of the name + * @flags: flags field from KDBUS_HELLO_* + * + * Sent from kernel to userspace when the owner or activator of + * a well-known name changes. + * + * Attached to: + * KDBUS_ITEM_ID_ADD + * KDBUS_ITEM_ID_REMOVE + */ +struct kdbus_notify_id_change { + __u64 id; + __u64 flags; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_notify_name_change - name registry change message + * @old_id: ID and flags of former owner of a name + * @new_id: ID and flags of new owner of a name + * @name: Well-known name + * + * Sent from kernel to userspace when the owner or activator of + * a well-known name changes. + * + * Attached to: + * KDBUS_ITEM_NAME_ADD + * KDBUS_ITEM_NAME_REMOVE + * KDBUS_ITEM_NAME_CHANGE + */ +struct kdbus_notify_name_change { + struct kdbus_notify_id_change old_id; + struct kdbus_notify_id_change new_id; + char name[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_creds - process credentials + * @uid: User ID + * @euid: Effective UID + * @suid: Saved UID + * @fsuid: Filesystem UID + * @gid: Group ID + * @egid: Effective GID + * @sgid: Saved GID + * @fsgid: Filesystem GID + * + * Attached to: + * KDBUS_ITEM_CREDS + */ +struct kdbus_creds { + __u64 uid; + __u64 euid; + __u64 suid; + __u64 fsuid; + __u64 gid; + __u64 egid; + __u64 sgid; + __u64 fsgid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_pids - process identifiers + * @pid: Process ID + * @tid: Thread ID + * @ppid: Parent process ID + * + * The PID and TID of a process. + * + * Attached to: + * KDBUS_ITEM_PIDS + */ +struct kdbus_pids { + __u64 pid; + __u64 tid; + __u64 ppid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_caps - process capabilities + * @last_cap: Highest currently known capability bit + * @caps: Variable number of 32-bit capabilities flags + * + * Contains a variable number of 32-bit capabilities flags. + * + * Attached to: + * KDBUS_ITEM_CAPS + */ +struct kdbus_caps { + __u32 last_cap; + __u32 caps[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_audit - audit information + * @sessionid: The audit session ID + * @loginuid: The audit login uid + * + * Attached to: + * KDBUS_ITEM_AUDIT + */ +struct kdbus_audit { + __u32 sessionid; + __u32 loginuid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_timestamp + * @seqnum: Global per-domain message sequence number + * @monotonic_ns: Monotonic timestamp, in nanoseconds + * @realtime_ns: Realtime timestamp, in nanoseconds + * + * Attached to: + * KDBUS_ITEM_TIMESTAMP + */ +struct kdbus_timestamp { + __u64 seqnum; + __u64 monotonic_ns; + __u64 realtime_ns; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_vec - I/O vector for kdbus payload items + * @size: The size of the vector + * @address: Memory address of data buffer + * @offset: Offset in the in-message payload memory, + * relative to the message head + * + * Attached to: + * KDBUS_ITEM_PAYLOAD_VEC, KDBUS_ITEM_PAYLOAD_OFF + */ +struct kdbus_vec { + __u64 size; + union { + __u64 address; + __u64 offset; + }; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_bloom_parameter - bus-wide bloom parameters + * @size: Size of the bit field in bytes (m / 8) + * @n_hash: Number of hash functions used (k) + */ +struct kdbus_bloom_parameter { + __u64 size; + __u64 n_hash; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_bloom_filter - bloom filter containing n elements + * @generation: Generation of the element set in the filter + * @data: Bit field, multiple of 8 bytes + */ +struct kdbus_bloom_filter { + __u64 generation; + __u64 data[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_memfd - a kdbus memfd + * @start: The offset into the memfd where the segment starts + * @size: The size of the memfd segment + * @fd: The file descriptor number + * @__pad: Padding to ensure proper alignment and size + * + * Attached to: + * KDBUS_ITEM_PAYLOAD_MEMFD + */ +struct kdbus_memfd { + __u64 start; + __u64 size; + int fd; + __u32 __pad; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_name - a registered well-known name with its flags + * @flags: Flags from KDBUS_NAME_* + * @name: Well-known name + * + * Attached to: + * KDBUS_ITEM_OWNED_NAME + */ +struct kdbus_name { + __u64 flags; + char name[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_policy_access_type - permissions of a policy record + * @_KDBUS_POLICY_ACCESS_NULL: Uninitialized/invalid + * @KDBUS_POLICY_ACCESS_USER: Grant access to a uid + * @KDBUS_POLICY_ACCESS_GROUP: Grant access to gid + * @KDBUS_POLICY_ACCESS_WORLD: World-accessible + */ +enum kdbus_policy_access_type { + _KDBUS_POLICY_ACCESS_NULL, + KDBUS_POLICY_ACCESS_USER, + KDBUS_POLICY_ACCESS_GROUP, + KDBUS_POLICY_ACCESS_WORLD, +}; + +/** + * enum kdbus_policy_access_flags - mode flags + * @KDBUS_POLICY_OWN: Allow to own a well-known name + * Implies KDBUS_POLICY_TALK and KDBUS_POLICY_SEE + * @KDBUS_POLICY_TALK: Allow communication to a well-known name + * Implies KDBUS_POLICY_SEE + * @KDBUS_POLICY_SEE: Allow to see a well-known name + */ +enum kdbus_policy_type { + KDBUS_POLICY_SEE = 0, + KDBUS_POLICY_TALK, + KDBUS_POLICY_OWN, +}; + +/** + * struct kdbus_policy_access - policy access item + * @type: One of KDBUS_POLICY_ACCESS_* types + * @access: Access to grant + * @id: For KDBUS_POLICY_ACCESS_USER, the uid + * For KDBUS_POLICY_ACCESS_GROUP, the gid + */ +struct kdbus_policy_access { + __u64 type; /* USER, GROUP, WORLD */ + __u64 access; /* OWN, TALK, SEE */ + __u64 id; /* uid, gid, 0 */ +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_attach_flags - flags for metadata attachments + * @KDBUS_ATTACH_TIMESTAMP: Timestamp + * @KDBUS_ATTACH_CREDS: Credentials + * @KDBUS_ATTACH_PIDS: PIDs + * @KDBUS_ATTACH_AUXGROUPS: Auxiliary groups + * @KDBUS_ATTACH_NAMES: Well-known names + * @KDBUS_ATTACH_TID_COMM: The "comm" process identifier of the TID + * @KDBUS_ATTACH_PID_COMM: The "comm" process identifier of the PID + * @KDBUS_ATTACH_EXE: The path of the executable + * @KDBUS_ATTACH_CMDLINE: The process command line + * @KDBUS_ATTACH_CGROUP: The croup membership + * @KDBUS_ATTACH_CAPS: The process capabilities + * @KDBUS_ATTACH_SECLABEL: The security label + * @KDBUS_ATTACH_AUDIT: The audit IDs + * @KDBUS_ATTACH_CONN_DESCRIPTION: The human-readable connection name + * @_KDBUS_ATTACH_ALL: All of the above + * @_KDBUS_ATTACH_ANY: Wildcard match to enable any kind of + * metatdata. + */ +enum kdbus_attach_flags { + KDBUS_ATTACH_TIMESTAMP = 1ULL << 0, + KDBUS_ATTACH_CREDS = 1ULL << 1, + KDBUS_ATTACH_PIDS = 1ULL << 2, + KDBUS_ATTACH_AUXGROUPS = 1ULL << 3, + KDBUS_ATTACH_NAMES = 1ULL << 4, + KDBUS_ATTACH_TID_COMM = 1ULL << 5, + KDBUS_ATTACH_PID_COMM = 1ULL << 6, + KDBUS_ATTACH_EXE = 1ULL << 7, + KDBUS_ATTACH_CMDLINE = 1ULL << 8, + KDBUS_ATTACH_CGROUP = 1ULL << 9, + KDBUS_ATTACH_CAPS = 1ULL << 10, + KDBUS_ATTACH_SECLABEL = 1ULL << 11, + KDBUS_ATTACH_AUDIT = 1ULL << 12, + KDBUS_ATTACH_CONN_DESCRIPTION = 1ULL << 13, + _KDBUS_ATTACH_ALL = (1ULL << 14) - 1, + _KDBUS_ATTACH_ANY = ~0ULL +}; + +/** + * enum kdbus_item_type - item types to chain data in a list + * @_KDBUS_ITEM_NULL: Uninitialized/invalid + * @_KDBUS_ITEM_USER_BASE: Start of user items + * @KDBUS_ITEM_NEGOTIATE: Negotiate supported items + * @KDBUS_ITEM_PAYLOAD_VEC: Vector to data + * @KDBUS_ITEM_PAYLOAD_OFF: Data at returned offset to message head + * @KDBUS_ITEM_PAYLOAD_MEMFD: Data as sealed memfd + * @KDBUS_ITEM_FDS: Attached file descriptors + * @KDBUS_ITEM_CANCEL_FD: FD used to cancel a synchronous + * operation by writing to it from + * userspace + * @KDBUS_ITEM_BLOOM_PARAMETER: Bus-wide bloom parameters, used with + * KDBUS_CMD_BUS_MAKE, carries a + * struct kdbus_bloom_parameter + * @KDBUS_ITEM_BLOOM_FILTER: Bloom filter carried with a message, + * used to match against a bloom mask of a + * connection, carries a struct + * kdbus_bloom_filter + * @KDBUS_ITEM_BLOOM_MASK: Bloom mask used to match against a + * message'sbloom filter + * @KDBUS_ITEM_DST_NAME: Destination's well-known name + * @KDBUS_ITEM_MAKE_NAME: Name of domain, bus, endpoint + * @KDBUS_ITEM_ATTACH_FLAGS_SEND: Attach-flags, used for updating which + * metadata a connection opts in to send + * @KDBUS_ITEM_ATTACH_FLAGS_RECV: Attach-flags, used for updating which + * metadata a connection requests to + * receive for each reeceived message + * @KDBUS_ITEM_ID: Connection ID + * @KDBUS_ITEM_NAME: Well-know name with flags + * @_KDBUS_ITEM_ATTACH_BASE: Start of metadata attach items + * @KDBUS_ITEM_TIMESTAMP: Timestamp + * @KDBUS_ITEM_CREDS: Process credentials + * @KDBUS_ITEM_PIDS: Process identifiers + * @KDBUS_ITEM_AUXGROUPS: Auxiliary process groups + * @KDBUS_ITEM_OWNED_NAME: A name owned by the associated + * connection + * @KDBUS_ITEM_TID_COMM: Thread ID "comm" identifier + * (Don't trust this, see below.) + * @KDBUS_ITEM_PID_COMM: Process ID "comm" identifier + * (Don't trust this, see below.) + * @KDBUS_ITEM_EXE: The path of the executable + * (Don't trust this, see below.) + * @KDBUS_ITEM_CMDLINE: The process command line + * (Don't trust this, see below.) + * @KDBUS_ITEM_CGROUP: The croup membership + * @KDBUS_ITEM_CAPS: The process capabilities + * @KDBUS_ITEM_SECLABEL: The security label + * @KDBUS_ITEM_AUDIT: The audit IDs + * @KDBUS_ITEM_CONN_DESCRIPTION: The connection's human-readable name + * (debugging) + * @_KDBUS_ITEM_POLICY_BASE: Start of policy items + * @KDBUS_ITEM_POLICY_ACCESS: Policy access block + * @_KDBUS_ITEM_KERNEL_BASE: Start of kernel-generated message items + * @KDBUS_ITEM_NAME_ADD: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_NAME_REMOVE: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_NAME_CHANGE: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_ID_ADD: Notification in kdbus_notify_id_change + * @KDBUS_ITEM_ID_REMOVE: Notification in kdbus_notify_id_change + * @KDBUS_ITEM_REPLY_TIMEOUT: Timeout has been reached + * @KDBUS_ITEM_REPLY_DEAD: Destination died + * + * N.B: The process and thread COMM fields, as well as the CMDLINE and + * EXE fields may be altered by unprivileged processes und should + * hence *not* used for security decisions. Peers should make use of + * these items only for informational purposes, such as generating log + * records. + */ +enum kdbus_item_type { + _KDBUS_ITEM_NULL, + _KDBUS_ITEM_USER_BASE, + KDBUS_ITEM_NEGOTIATE = _KDBUS_ITEM_USER_BASE, + KDBUS_ITEM_PAYLOAD_VEC, + KDBUS_ITEM_PAYLOAD_OFF, + KDBUS_ITEM_PAYLOAD_MEMFD, + KDBUS_ITEM_FDS, + KDBUS_ITEM_CANCEL_FD, + KDBUS_ITEM_BLOOM_PARAMETER, + KDBUS_ITEM_BLOOM_FILTER, + KDBUS_ITEM_BLOOM_MASK, + KDBUS_ITEM_DST_NAME, + KDBUS_ITEM_MAKE_NAME, + KDBUS_ITEM_ATTACH_FLAGS_SEND, + KDBUS_ITEM_ATTACH_FLAGS_RECV, + KDBUS_ITEM_ID, + KDBUS_ITEM_NAME, + KDBUS_ITEM_DST_ID, + + /* keep these item types in sync with KDBUS_ATTACH_* flags */ + _KDBUS_ITEM_ATTACH_BASE = 0x1000, + KDBUS_ITEM_TIMESTAMP = _KDBUS_ITEM_ATTACH_BASE, + KDBUS_ITEM_CREDS, + KDBUS_ITEM_PIDS, + KDBUS_ITEM_AUXGROUPS, + KDBUS_ITEM_OWNED_NAME, + KDBUS_ITEM_TID_COMM, + KDBUS_ITEM_PID_COMM, + KDBUS_ITEM_EXE, + KDBUS_ITEM_CMDLINE, + KDBUS_ITEM_CGROUP, + KDBUS_ITEM_CAPS, + KDBUS_ITEM_SECLABEL, + KDBUS_ITEM_AUDIT, + KDBUS_ITEM_CONN_DESCRIPTION, + + _KDBUS_ITEM_POLICY_BASE = 0x2000, + KDBUS_ITEM_POLICY_ACCESS = _KDBUS_ITEM_POLICY_BASE, + + _KDBUS_ITEM_KERNEL_BASE = 0x8000, + KDBUS_ITEM_NAME_ADD = _KDBUS_ITEM_KERNEL_BASE, + KDBUS_ITEM_NAME_REMOVE, + KDBUS_ITEM_NAME_CHANGE, + KDBUS_ITEM_ID_ADD, + KDBUS_ITEM_ID_REMOVE, + KDBUS_ITEM_REPLY_TIMEOUT, + KDBUS_ITEM_REPLY_DEAD, +}; + +/** + * struct kdbus_item - chain of data blocks + * @size: Overall data record size + * @type: Kdbus_item type of data + * @data: Generic bytes + * @data32: Generic 32 bit array + * @data64: Generic 64 bit array + * @str: Generic string + * @id: Connection ID + * @vec: KDBUS_ITEM_PAYLOAD_VEC + * @creds: KDBUS_ITEM_CREDS + * @audit: KDBUS_ITEM_AUDIT + * @timestamp: KDBUS_ITEM_TIMESTAMP + * @name: KDBUS_ITEM_NAME + * @bloom_parameter: KDBUS_ITEM_BLOOM_PARAMETER + * @bloom_filter: KDBUS_ITEM_BLOOM_FILTER + * @memfd: KDBUS_ITEM_PAYLOAD_MEMFD + * @name_change: KDBUS_ITEM_NAME_ADD + * KDBUS_ITEM_NAME_REMOVE + * KDBUS_ITEM_NAME_CHANGE + * @id_change: KDBUS_ITEM_ID_ADD + * KDBUS_ITEM_ID_REMOVE + * @policy: KDBUS_ITEM_POLICY_ACCESS + */ +struct kdbus_item { + __u64 size; + __u64 type; + union { + __u8 data[0]; + __u32 data32[0]; + __u64 data64[0]; + char str[0]; + + __u64 id; + struct kdbus_vec vec; + struct kdbus_creds creds; + struct kdbus_pids pids; + struct kdbus_audit audit; + struct kdbus_caps caps; + struct kdbus_timestamp timestamp; + struct kdbus_name name; + struct kdbus_bloom_parameter bloom_parameter; + struct kdbus_bloom_filter bloom_filter; + struct kdbus_memfd memfd; + int fds[0]; + struct kdbus_notify_name_change name_change; + struct kdbus_notify_id_change id_change; + struct kdbus_policy_access policy_access; + }; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_msg_flags - type of message + * @KDBUS_MSG_EXPECT_REPLY: Expect a reply message, used for + * method calls. The userspace-supplied + * cookie identifies the message and the + * respective reply carries the cookie + * in cookie_reply + * @KDBUS_MSG_NO_AUTO_START: Do not start a service if the addressed + * name is not currently active. This flag is + * not looked at by the kernel but only + * serves as hint for userspace implementations. + * @KDBUS_MSG_SIGNAL: Treat this message as signal + */ +enum kdbus_msg_flags { + KDBUS_MSG_EXPECT_REPLY = 1ULL << 0, + KDBUS_MSG_NO_AUTO_START = 1ULL << 1, + KDBUS_MSG_SIGNAL = 1ULL << 2, +}; + +/** + * enum kdbus_payload_type - type of payload carried by message + * @KDBUS_PAYLOAD_KERNEL: Kernel-generated simple message + * @KDBUS_PAYLOAD_DBUS: D-Bus marshalling "DBusDBus" + * + * Any payload-type is accepted. Common types will get added here once + * established. + */ +enum kdbus_payload_type { + KDBUS_PAYLOAD_KERNEL, + KDBUS_PAYLOAD_DBUS = 0x4442757344427573ULL, +}; + +/** + * struct kdbus_msg - the representation of a kdbus message + * @size: Total size of the message + * @flags: Message flags (KDBUS_MSG_*), userspace → kernel + * @priority: Message queue priority value + * @dst_id: 64-bit ID of the destination connection + * @src_id: 64-bit ID of the source connection + * @payload_type: Payload type (KDBUS_PAYLOAD_*) + * @cookie: Userspace-supplied cookie, for the connection + * to identify its messages + * @timeout_ns: The time to wait for a message reply from the peer. + * If there is no reply, and the send command is + * executed asynchronously, a kernel-generated message + * with an attached KDBUS_ITEM_REPLY_TIMEOUT item + * is sent to @src_id. For synchronously executed send + * command, the value denotes the maximum time the call + * blocks to wait for a reply. The timeout is expected in + * nanoseconds and as absolute CLOCK_MONOTONIC value. + * @cookie_reply: A reply to the requesting message with the same + * cookie. The requesting connection can match its + * request and the reply with this value + * @items: A list of kdbus_items containing the message payload + */ +struct kdbus_msg { + __u64 size; + __u64 flags; + __s64 priority; + __u64 dst_id; + __u64 src_id; + __u64 payload_type; + __u64 cookie; + union { + __u64 timeout_ns; + __u64 cookie_reply; + }; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_msg_info - returned message container + * @offset: Offset of kdbus_msg slice in pool + * @msg_size: Copy of the kdbus_msg.size field + * @return_flags: Command return flags, kernel → userspace + */ +struct kdbus_msg_info { + __u64 offset; + __u64 msg_size; + __u64 return_flags; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_send_flags - flags for sending messages + * @KDBUS_SEND_SYNC_REPLY: Wait for destination connection to + * reply to this message. The + * KDBUS_CMD_SEND ioctl() will block + * until the reply is received, and + * reply in struct kdbus_cmd_send will + * yield the offset in the sender's pool + * where the reply can be found. + * This flag is only valid if + * @KDBUS_MSG_EXPECT_REPLY is set as well. + */ +enum kdbus_send_flags { + KDBUS_SEND_SYNC_REPLY = 1ULL << 0, +}; + +/** + * struct kdbus_cmd_send - send message + * @size: Overall size of this structure + * @flags: Flags to change send behavior (KDBUS_SEND_*) + * @return_flags: Command return flags, kernel → userspace + * @msg_address: Storage address of the kdbus_msg to send + * @reply: Storage for message reply if KDBUS_SEND_SYNC_REPLY + * was given + * @items: Additional items for this command + */ +struct kdbus_cmd_send { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 msg_address; + struct kdbus_msg_info reply; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_recv_flags - flags for de-queuing messages + * @KDBUS_RECV_PEEK: Return the next queued message without + * actually de-queuing it, and without installing + * any file descriptors or other resources. It is + * usually used to determine the activating + * connection of a bus name. + * @KDBUS_RECV_DROP: Drop and free the next queued message and all + * its resources without actually receiving it. + * @KDBUS_RECV_USE_PRIORITY: Only de-queue messages with the specified or + * higher priority (lowest values); if not set, + * the priority value is ignored. + */ +enum kdbus_recv_flags { + KDBUS_RECV_PEEK = 1ULL << 0, + KDBUS_RECV_DROP = 1ULL << 1, + KDBUS_RECV_USE_PRIORITY = 1ULL << 2, +}; + +/** + * enum kdbus_recv_return_flags - return flags for message receive commands + * @KDBUS_RECV_RETURN_INCOMPLETE_FDS: One or more file descriptors could not + * be installed. These descriptors in + * KDBUS_ITEM_FDS will carry the value -1. + * @KDBUS_RECV_RETURN_DROPPED_MSGS: There have been dropped messages since + * the last time a message was received. + * The 'dropped_msgs' counter contains the + * number of messages dropped pool + * overflows or other missed broadcasts. + */ +enum kdbus_recv_return_flags { + KDBUS_RECV_RETURN_INCOMPLETE_FDS = 1ULL << 0, + KDBUS_RECV_RETURN_DROPPED_MSGS = 1ULL << 1, +}; + +/** + * struct kdbus_cmd_recv - struct to de-queue a buffered message + * @size: Overall size of this object + * @flags: KDBUS_RECV_* flags, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @priority: Minimum priority of the messages to de-queue. Lowest + * values have the highest priority. + * @dropped_msgs: In case there were any dropped messages since the last + * time a message was received, this will be set to the + * number of lost messages and + * KDBUS_RECV_RETURN_DROPPED_MSGS will be set in + * 'return_flags'. This can only happen if the ioctl + * returns 0 or EAGAIN. + * @msg: Return storage for received message. + * @items: Additional items for this command. + * + * This struct is used with the KDBUS_CMD_RECV ioctl. + */ +struct kdbus_cmd_recv { + __u64 size; + __u64 flags; + __u64 return_flags; + __s64 priority; + __u64 dropped_msgs; + struct kdbus_msg_info msg; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_cmd_free - struct to free a slice of memory in the pool + * @size: Overall size of this structure + * @flags: Flags for the free command, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @offset: The offset of the memory slice, as returned by other + * ioctls + * @items: Additional items to modify the behavior + * + * This struct is used with the KDBUS_CMD_FREE ioctl. + */ +struct kdbus_cmd_free { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 offset; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_hello_flags - flags for struct kdbus_cmd_hello + * @KDBUS_HELLO_ACCEPT_FD: The connection allows the reception of + * any passed file descriptors + * @KDBUS_HELLO_ACTIVATOR: Special-purpose connection which registers + * a well-know name for a process to be started + * when traffic arrives + * @KDBUS_HELLO_POLICY_HOLDER: Special-purpose connection which registers + * policy entries for a name. The provided name + * is not activated and not registered with the + * name database, it only allows unprivileged + * connections to acquire a name, talk or discover + * a service + * @KDBUS_HELLO_MONITOR: Special-purpose connection to monitor + * bus traffic + */ +enum kdbus_hello_flags { + KDBUS_HELLO_ACCEPT_FD = 1ULL << 0, + KDBUS_HELLO_ACTIVATOR = 1ULL << 1, + KDBUS_HELLO_POLICY_HOLDER = 1ULL << 2, + KDBUS_HELLO_MONITOR = 1ULL << 3, +}; + +/** + * struct kdbus_cmd_hello - struct to say hello to kdbus + * @size: The total size of the structure + * @flags: Connection flags (KDBUS_HELLO_*), userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @attach_flags_send: Mask of metadata to attach to each message sent + * off by this connection (KDBUS_ATTACH_*) + * @attach_flags_recv: Mask of metadata to attach to each message receieved + * by the new connection (KDBUS_ATTACH_*) + * @bus_flags: The flags field copied verbatim from the original + * KDBUS_CMD_BUS_MAKE ioctl. It's intended to be useful + * to do negotiation of features of the payload that is + * transferred (kernel → userspace) + * @id: The ID of this connection (kernel → userspace) + * @pool_size: Size of the connection's buffer where the received + * messages are placed + * @offset: Pool offset where items are returned to report + * additional information about the bus and the newly + * created connection. + * @items_size: Size of buffer returned in the pool slice at @offset. + * @id128: Unique 128-bit ID of the bus (kernel → userspace) + * @items: A list of items + * + * This struct is used with the KDBUS_CMD_HELLO ioctl. + */ +struct kdbus_cmd_hello { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 attach_flags_send; + __u64 attach_flags_recv; + __u64 bus_flags; + __u64 id; + __u64 pool_size; + __u64 offset; + __u64 items_size; + __u8 id128[16]; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_info - connection information + * @size: total size of the struct + * @id: 64bit object ID + * @flags: object creation flags + * @items: list of items + * + * Note that the user is responsible for freeing the allocated memory with + * the KDBUS_CMD_FREE ioctl. + */ +struct kdbus_info { + __u64 size; + __u64 id; + __u64 flags; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_list_flags - what to include into the returned list + * @KDBUS_LIST_UNIQUE: active connections + * @KDBUS_LIST_ACTIVATORS: activator connections + * @KDBUS_LIST_NAMES: known well-known names + * @KDBUS_LIST_QUEUED: queued-up names + */ +enum kdbus_list_flags { + KDBUS_LIST_UNIQUE = 1ULL << 0, + KDBUS_LIST_NAMES = 1ULL << 1, + KDBUS_LIST_ACTIVATORS = 1ULL << 2, + KDBUS_LIST_QUEUED = 1ULL << 3, +}; + +/** + * struct kdbus_cmd_list - list connections + * @size: overall size of this object + * @flags: flags for the query (KDBUS_LIST_*), userspace → kernel + * @return_flags: command return flags, kernel → userspace + * @offset: Offset in the caller's pool buffer where an array of + * kdbus_info objects is stored. + * The user must use KDBUS_CMD_FREE to free the + * allocated memory. + * @list_size: size of returned list in bytes + * @items: Items for the command. Reserved for future use. + * + * This structure is used with the KDBUS_CMD_LIST ioctl. + */ +struct kdbus_cmd_list { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 offset; + __u64 list_size; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_cmd_info - struct used for KDBUS_CMD_CONN_INFO ioctl + * @size: The total size of the struct + * @flags: Flags for this ioctl, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @id: The 64-bit ID of the connection. If set to zero, passing + * @name is required. kdbus will look up the name to + * determine the ID in this case. + * @attach_flags: Set of attach flags to specify the set of information + * to receive, userspace → kernel + * @offset: Returned offset in the caller's pool buffer where the + * kdbus_info struct result is stored. The user must + * use KDBUS_CMD_FREE to free the allocated memory. + * @info_size: Output buffer to report size of data at @offset. + * @items: The optional item list, containing the + * well-known name to look up as a KDBUS_ITEM_NAME. + * Only needed in case @id is zero. + * + * On success, the KDBUS_CMD_CONN_INFO ioctl will return 0 and @offset will + * tell the user the offset in the connection pool buffer at which to find the + * result in a struct kdbus_info. + */ +struct kdbus_cmd_info { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 id; + __u64 attach_flags; + __u64 offset; + __u64 info_size; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_cmd_match_flags - flags to control the KDBUS_CMD_MATCH_ADD ioctl + * @KDBUS_MATCH_REPLACE: If entries with the supplied cookie already + * exists, remove them before installing the new + * matches. + */ +enum kdbus_cmd_match_flags { + KDBUS_MATCH_REPLACE = 1ULL << 0, +}; + +/** + * struct kdbus_cmd_match - struct to add or remove matches + * @size: The total size of the struct + * @flags: Flags for match command (KDBUS_MATCH_*), + * userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @cookie: Userspace supplied cookie. When removing, the cookie + * identifies the match to remove + * @items: A list of items for additional information + * + * This structure is used with the KDBUS_CMD_MATCH_ADD and + * KDBUS_CMD_MATCH_REMOVE ioctl. + */ +struct kdbus_cmd_match { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 cookie; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_make_flags - Flags for KDBUS_CMD_{BUS,ENDPOINT}_MAKE + * @KDBUS_MAKE_ACCESS_GROUP: Make the bus or endpoint node group-accessible + * @KDBUS_MAKE_ACCESS_WORLD: Make the bus or endpoint node world-accessible + */ +enum kdbus_make_flags { + KDBUS_MAKE_ACCESS_GROUP = 1ULL << 0, + KDBUS_MAKE_ACCESS_WORLD = 1ULL << 1, +}; + +/** + * enum kdbus_name_flags - flags for KDBUS_CMD_NAME_ACQUIRE + * @KDBUS_NAME_REPLACE_EXISTING: Try to replace name of other connections + * @KDBUS_NAME_ALLOW_REPLACEMENT: Allow the replacement of the name + * @KDBUS_NAME_QUEUE: Name should be queued if busy + * @KDBUS_NAME_IN_QUEUE: Name is queued + * @KDBUS_NAME_ACTIVATOR: Name is owned by a activator connection + * @KDBUS_NAME_PRIMARY: Primary owner of the name + * @KDBUS_NAME_ACQUIRED: Name was acquired/queued _now_ + */ +enum kdbus_name_flags { + KDBUS_NAME_REPLACE_EXISTING = 1ULL << 0, + KDBUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, + KDBUS_NAME_QUEUE = 1ULL << 2, + KDBUS_NAME_IN_QUEUE = 1ULL << 3, + KDBUS_NAME_ACTIVATOR = 1ULL << 4, + KDBUS_NAME_PRIMARY = 1ULL << 5, + KDBUS_NAME_ACQUIRED = 1ULL << 6, +}; + +/** + * struct kdbus_cmd - generic ioctl payload + * @size: Overall size of this structure + * @flags: Flags for this ioctl, userspace → kernel + * @return_flags: Ioctl return flags, kernel → userspace + * @items: Additional items to modify the behavior + * + * This is a generic ioctl payload object. It's used by all ioctls that only + * take flags and items as input. + */ +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * Ioctl API + * + * KDBUS_CMD_BUS_MAKE: After opening the "control" node, this command + * creates a new bus with the specified + * name. The bus is immediately shut down and + * cleaned up when the opened file descriptor is + * closed. + * + * KDBUS_CMD_ENDPOINT_MAKE: Creates a new named special endpoint to talk to + * the bus. Such endpoints usually carry a more + * restrictive policy and grant restricted access + * to specific applications. + * KDBUS_CMD_ENDPOINT_UPDATE: Update the properties of a custom enpoint. Used + * to update the policy. + * + * KDBUS_CMD_HELLO: By opening the bus node, a connection is + * created. After a HELLO the opened connection + * becomes an active peer on the bus. + * KDBUS_CMD_UPDATE: Update the properties of a connection. Used to + * update the metadata subscription mask and + * policy. + * KDBUS_CMD_BYEBYE: Disconnect a connection. If there are no + * messages queued up in the connection's pool, + * the call succeeds, and the handle is rendered + * unusable. Otherwise, -EBUSY is returned without + * any further side-effects. + * KDBUS_CMD_FREE: Release the allocated memory in the receiver's + * pool. + * KDBUS_CMD_CONN_INFO: Retrieve credentials and properties of the + * initial creator of the connection. The data was + * stored at registration time and does not + * necessarily represent the connected process or + * the actual state of the process. + * KDBUS_CMD_BUS_CREATOR_INFO: Retrieve information of the creator of the bus + * a connection is attached to. + * + * KDBUS_CMD_SEND: Send a message and pass data from userspace to + * the kernel. + * KDBUS_CMD_RECV: Receive a message from the kernel which is + * placed in the receiver's pool. + * + * KDBUS_CMD_NAME_ACQUIRE: Request a well-known bus name to associate with + * the connection. Well-known names are used to + * address a peer on the bus. + * KDBUS_CMD_NAME_RELEASE: Release a well-known name the connection + * currently owns. + * KDBUS_CMD_LIST: Retrieve the list of all currently registered + * well-known and unique names. + * + * KDBUS_CMD_MATCH_ADD: Install a match which broadcast messages should + * be delivered to the connection. + * KDBUS_CMD_MATCH_REMOVE: Remove a current match for broadcast messages. + */ +enum kdbus_ioctl_type { + /* bus owner (00-0f) */ + KDBUS_CMD_BUS_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x00, + struct kdbus_cmd), + + /* endpoint owner (10-1f) */ + KDBUS_CMD_ENDPOINT_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x10, + struct kdbus_cmd), + KDBUS_CMD_ENDPOINT_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x11, + struct kdbus_cmd), + + /* connection owner (80-ff) */ + KDBUS_CMD_HELLO = _IOWR(KDBUS_IOCTL_MAGIC, 0x80, + struct kdbus_cmd_hello), + KDBUS_CMD_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x81, + struct kdbus_cmd), + KDBUS_CMD_BYEBYE = _IOW(KDBUS_IOCTL_MAGIC, 0x82, + struct kdbus_cmd), + KDBUS_CMD_FREE = _IOW(KDBUS_IOCTL_MAGIC, 0x83, + struct kdbus_cmd_free), + KDBUS_CMD_CONN_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x84, + struct kdbus_cmd_info), + KDBUS_CMD_BUS_CREATOR_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x85, + struct kdbus_cmd_info), + KDBUS_CMD_LIST = _IOR(KDBUS_IOCTL_MAGIC, 0x86, + struct kdbus_cmd_list), + + KDBUS_CMD_SEND = _IOW(KDBUS_IOCTL_MAGIC, 0x90, + struct kdbus_cmd_send), + KDBUS_CMD_RECV = _IOR(KDBUS_IOCTL_MAGIC, 0x91, + struct kdbus_cmd_recv), + + KDBUS_CMD_NAME_ACQUIRE = _IOW(KDBUS_IOCTL_MAGIC, 0xa0, + struct kdbus_cmd), + KDBUS_CMD_NAME_RELEASE = _IOW(KDBUS_IOCTL_MAGIC, 0xa1, + struct kdbus_cmd), + + KDBUS_CMD_MATCH_ADD = _IOW(KDBUS_IOCTL_MAGIC, 0xb0, + struct kdbus_cmd_match), + KDBUS_CMD_MATCH_REMOVE = _IOW(KDBUS_IOCTL_MAGIC, 0xb1, + struct kdbus_cmd_match), +}; + +#endif /* _UAPI_KDBUS_H_ */ diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index aa50113ebe5b..8764b4730c8d 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -89,4 +89,6 @@ #define BALLOON_KVM_MAGIC 0x13661366 #define ZSMALLOC_MAGIC 0x58295829 +#define KDBUS_SUPER_MAGIC 0x44427573 + #endif /* __LINUX_MAGIC_H__ */ diff --git a/init/Kconfig b/init/Kconfig index 46075327c165..b8325a5e7bfb 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -273,6 +273,19 @@ config POSIX_MQUEUE_SYSCTL depends on SYSCTL default y +config KDBUS + tristate "kdbus interprocess communication" + depends on TMPFS + help + D-Bus is a system for low-latency, low-overhead, easy to use + interprocess communication (IPC). + + See the man-pages and HTML files in Documentation/kdbus/ + that are generated by 'make mandocs' and 'make htmldocs'. + + If you have an ordinary machine, select M here. The module + will be called kdbus. + config CROSS_MEMORY_ATTACH bool "Enable process_vm_readv/writev syscalls" depends on MMU diff --git a/ipc/Makefile b/ipc/Makefile index c2558c430f51..227b96f8f618 100644 --- a/ipc/Makefile +++ b/ipc/Makefile @@ -9,4 +9,4 @@ obj-$(CONFIG_SYSVIPC_SYSCTL) += ipc_sysctl.o obj-$(CONFIG_POSIX_MQUEUE) += mqueue.o msgutil.o obj-$(CONFIG_IPC_NS) += namespace.o obj-$(CONFIG_POSIX_MQUEUE_SYSCTL) += mq_sysctl.o - +#obj-$(CONFIG_KDBUS) += kdbus/ diff --git a/ipc/kdbus/Documentation/kdbus.bus.xml b/ipc/kdbus/Documentation/kdbus.bus.xml new file mode 100644 index 000000000000..4b9a0ac1b351 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.bus.xml @@ -0,0 +1,359 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.bus"> + + <refentryinfo> + <title>kdbus.bus</title> + <productname>kdbus.bus</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.bus</refname> + <refpurpose>kdbus bus</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para> + A bus is a resource that is shared between connections in order to + transmit messages (see + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>). + Each bus is independent, and operations on the bus will not have any + effect on other buses. A bus is a management entity that controls the + addresses of its connections, their policies and message transactions + performed via this bus. + </para> + <para> + Each bus is bound to the mount instance it was created on. It has a + custom name that is unique across all buses of a domain. In + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + a bus is presented as a directory. No operations can be performed on + the bus itself; instead you need to perform the operations on an endpoint + associated with the bus. Endpoints are accessible as files underneath the + bus directory. A default endpoint called <constant>bus</constant> is + provided on each bus. + </para> + <para> + Bus names may be chosen freely except for one restriction: the name must + be prefixed with the numeric effective UID of the creator and a dash. This + is required to avoid namespace clashes between different users. When + creating a bus, the name that is passed in must be properly formatted, or + the kernel will refuse creation of the bus. Example: + <literal>1047-foobar</literal> is an acceptable name for a bus + registered by a user with UID 1047. However, + <literal>1024-foobar</literal> is not, and neither is + <literal>foobar</literal>. The UID must be provided in the + user-namespace of the bus owner. + </para> + <para> + To create a new bus, you need to open the control file of a domain and + employ the <constant>KDBUS_CMD_BUS_MAKE</constant> ioctl. The control + file descriptor that was used to issue + <constant>KDBUS_CMD_BUS_MAKE</constant> must not previously have been + used for any other control-ioctl and must be kept open for the entire + life-time of the created bus. Closing it will immediately cleanup the + entire bus and all its associated resources and endpoints. Every control + file descriptor can only be used to create a single new bus; from that + point on, it is not used for any further communication until the final + <citerefentry> + <refentrytitle>close</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + . + </para> + <para> + Each bus will generate a random, 128-bit UUID upon creation. This UUID + will be returned to creators of connections through + <varname>kdbus_cmd_hello.id128</varname> and can be used to uniquely + identify buses, even across different machines or containers. The UUID + will have its variant bits set to <literal>DCE</literal>, and denote + version 4 (random). For more details on UUIDs, see <ulink + url="https://en.wikipedia.org/wiki/Universally_unique_identifier"> + the Wikipedia article on UUIDs</ulink>. + </para> + + </refsect1> + + <refsect1> + <title>Creating buses</title> + <para> + To create a new bus, the <constant>KDBUS_CMD_BUS_MAKE</constant> + command is used. It takes a <type>struct kdbus_cmd</type> argument. + </para> + <programlisting> +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para>The flags for creation.</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_MAKE_ACCESS_GROUP</constant></term> + <listitem> + <para>Make the bus file group-accessible.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_MAKE_ACCESS_WORLD</constant></term> + <listitem> + <para>Make the bus file world-accessible.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Requests a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will return + <errorcode>0</errorcode>, and the <varname>flags</varname> + field will have all bits set that are valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + The following items (see + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>) + are expected for <constant>KDBUS_CMD_BUS_MAKE</constant>. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_MAKE_NAME</constant></term> + <listitem> + <para> + Contains a null-terminated string that identifies the + bus. The name must be unique across the kdbus domain and + must start with the effective UID of the caller, followed by + a '<literal>-</literal>' (dash). This item is mandatory. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_BLOOM_PARAMETER</constant></term> + <listitem> + <para> + Bus-wide bloom parameters passed in a + <type>struct kdbus_bloom_parameter</type>. These settings are + copied back to new connections verbatim. This item is + mandatory. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for a more detailed description of this item. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ATTACH_FLAGS_RECV</constant></term> + <listitem> + <para> + An optional item that contains a set of required attach flags + that connections must allow. This item is used as a + negotiation measure during connection creation. If connections + do not satisfy the bus requirements, they are not allowed on + the bus. If not set, the bus does not require any metadata to + be attached; in this case connections are free to set their + own attach flags. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ATTACH_FLAGS_SEND</constant></term> + <listitem> + <para> + An optional item that contains a set of attach flags that are + returned to connections when they query the bus creator + metadata. If not set, no metadata is returned. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item, programs can <emphasis>probe</emphasis> the + kernel for known item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para></listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + </variablelist> + + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </refsect1> + + <refsect1> + <title>Return value</title> + <para> + On success, all mentioned ioctl commands return <errorcode>0</errorcode>; + on error, <errorcode>-1</errorcode> is returned, and + <varname>errno</varname> is set to indicate the error. + If the issued ioctl is illegal for the file descriptor used, + <varname>errno</varname> will be set to <constant>ENOTTY</constant>. + </para> + + <refsect2> + <title> + <constant>KDBUS_CMD_BUS_MAKE</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EBADMSG</constant></term> + <listitem><para> + A mandatory item is missing. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + The flags supplied in the <constant>struct kdbus_cmd</constant> + are invalid or the supplied name does not start with the current + UID and a '<literal>-</literal>' (dash). + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EEXIST</constant></term> + <listitem><para> + A bus of that name already exists. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ESHUTDOWN</constant></term> + <listitem><para> + The kdbus mount instance for the bus was already shut down. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EMFILE</constant></term> + <listitem><para> + The maximum number of buses for the current user is exhausted. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.connection.xml b/ipc/kdbus/Documentation/kdbus.connection.xml new file mode 100644 index 000000000000..cefb419f1093 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.connection.xml @@ -0,0 +1,1250 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.connection"> + + <refentryinfo> + <title>kdbus.connection</title> + <productname>kdbus.connection</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.connection</refname> + <refpurpose>kdbus connection</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para> + Connections are identified by their <emphasis>connection ID</emphasis>, + internally implemented as a <type>uint64_t</type> counter. + The IDs of every newly created bus start at <constant>1</constant>, and + every new connection will increment the counter by <constant>1</constant>. + The IDs are not reused. + </para> + <para> + In higher level tools, the user visible representation of a connection is + defined by the D-Bus protocol specification as + <constant>":1.<ID>"</constant>. + </para> + <para> + Messages with a specific <type>uint64_t</type> destination ID are + directly delivered to the connection with the corresponding ID. Signal + messages (see + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>) + may be addressed to the special destination ID + <constant>KDBUS_DST_ID_BROADCAST</constant> (~0ULL) and will then + potentially be delivered to all currently active connections on the bus. + However, in order to receive any signal messages, clients must subscribe + to them by installing a match (see + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>). + </para> + <para> + Messages synthesized and sent directly by the kernel will carry the + special source ID <constant>KDBUS_SRC_ID_KERNEL</constant> (0). + </para> + <para> + In addition to the unique <type>uint64_t</type> connection ID, + established connections can request the ownership of + <emphasis>well-known names</emphasis>, under which they can be found and + addressed by other bus clients. A well-known name is associated with one + and only one connection at a time. See + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + on name acquisition, the name registry, and the validity of names. + </para> + <para> + Messages can specify the special destination ID + <constant>KDBUS_DST_ID_NAME</constant> (0) and carry a well-known name + in the message data. Such a message is delivered to the destination + connection which owns that well-known name. + </para> + + <programlisting><![CDATA[ + +-------------------------------------------------------------------------+ + | +---------------+ +---------------------------+ | + | | Connection | | Message | -----------------+ | + | | :1.22 | --> | src: 22 | | | + | | | | dst: 25 | | | + | | | | | | | + | | | | | | | + | | | +---------------------------+ | | + | | | | | + | | | <--------------------------------------+ | | + | +---------------+ | | | + | | | | + | +---------------+ +---------------------------+ | | | + | | Connection | | Message | -----+ | | + | | :1.25 | --> | src: 25 | | | + | | | | dst: 0xffffffffffffffff | -------------+ | | + | | | | (KDBUS_DST_ID_BROADCAST) | | | | + | | | | | ---------+ | | | + | | | +---------------------------+ | | | | + | | | | | | | + | | | <--------------------------------------------------+ | + | +---------------+ | | | + | | | | + | +---------------+ +---------------------------+ | | | + | | Connection | | Message | --+ | | | + | | :1.55 | --> | src: 55 | | | | | + | | | | dst: 0 / org.foo.bar | | | | | + | | | | | | | | | + | | | | | | | | | + | | | +---------------------------+ | | | | + | | | | | | | + | | | <------------------------------------------+ | | + | +---------------+ | | | + | | | | + | +---------------+ | | | + | | Connection | | | | + | | :1.81 | | | | + | | org.foo.bar | | | | + | | | | | | + | | | | | | + | | | <-----------------------------------+ | | + | | | | | + | | | <----------------------------------------------+ | + | +---------------+ | + +-------------------------------------------------------------------------+ + ]]></programlisting> + </refsect1> + + <refsect1> + <title>Privileged connections</title> + <para> + A connection is considered <emphasis>privileged</emphasis> if the user + it was created by is the same that created the bus, or if the creating + task had <constant>CAP_IPC_OWNER</constant> set when it called + <constant>KDBUS_CMD_HELLO</constant> (see below). + </para> + <para> + Privileged connections have permission to employ certain restricted + functions and commands, which are explained below and in other kdbus + man-pages. + </para> + </refsect1> + + <refsect1> + <title>Activator and policy holder connection</title> + <para> + An <emphasis>activator</emphasis> connection is a placeholder for a + <emphasis>well-known name</emphasis>. Messages sent to such a connection + can be used to start an implementer connection, which will then get all + the messages from the activator copied over. An activator connection + cannot be used to send any message. + </para> + <para> + A <emphasis>policy holder</emphasis> connection only installs a policy + for one or more names. These policy entries are kept active as long as + the connection is alive, and are removed once it terminates. Such a + policy connection type can be used to deploy restrictions for names that + are not yet active on the bus. A policy holder connection cannot be used + to send any message. + </para> + <para> + The creation of activator or policy holder connections is restricted to + privileged users on the bus (see above). + </para> + </refsect1> + + <refsect1> + <title>Monitor connections</title> + <para> + Monitors are eavesdropping connections that receive all the traffic on the + bus, but is invisible to other connections. Such connections have all + properties of any other, regular connection, except for the following + details: + </para> + + <itemizedlist> + <listitem><para> + They will get every message sent over the bus, both unicasts and + broadcasts. + </para></listitem> + + <listitem><para> + Installing matches for signal messages is neither necessary + nor allowed. + </para></listitem> + + <listitem><para> + They cannot send messages or be directly addressed as receiver. + </para></listitem> + + <listitem><para> + They cannot own well-known names. Therefore, they also can't operate as + activators. + </para></listitem> + + <listitem><para> + Their creation and destruction will not cause + <constant>KDBUS_ITEM_ID_{ADD,REMOVE}</constant> (see + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>). + </para></listitem> + + <listitem><para> + They are not listed with their unique name in name registry dumps + (see <constant>KDBUS_CMD_NAME_LIST</constant> in + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>), so other connections cannot detect the presence of + a monitor. + </para></listitem> + </itemizedlist> + <para> + The creation of monitor connections is restricted to privileged users on + the bus (see above). + </para> + </refsect1> + + <refsect1> + <title>Creating connections</title> + <para> + A connection to a bus is created by opening an endpoint file (see + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>) + of a bus and becoming an active client with the + <constant>KDBUS_CMD_HELLO</constant> ioctl. Every connection has a unique + identifier on the bus and can address messages to every other connection + on the same bus by using the peer's connection ID as the destination. + </para> + <para> + The <constant>KDBUS_CMD_HELLO</constant> ioctl takes a <type>struct + kdbus_cmd_hello</type> as argument. + </para> + + <programlisting> +struct kdbus_cmd_hello { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 attach_flags_send; + __u64 attach_flags_recv; + __u64 bus_flags; + __u64 id; + __u64 pool_size; + __u64 offset; + __u8 id128[16]; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem> + <para>Flags to apply to this connection</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_HELLO_ACCEPT_FD</constant></term> + <listitem> + <para> + When this flag is set, the connection can be sent file + descriptors as message payload of unicast messages. If it's + not set, an attempt to send file descriptors will result in + <constant>-ECOMM</constant> on the sender's side. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_HELLO_ACTIVATOR</constant></term> + <listitem> + <para> + Make this connection an activator (see above). With this bit + set, an item of type <constant>KDBUS_ITEM_NAME</constant> has + to be attached. This item describes the well-known name this + connection should be an activator for. + A connection can not be an activator and a policy holder at + the same time time, so this bit is not allowed together with + <constant>KDBUS_HELLO_POLICY_HOLDER</constant>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_HELLO_POLICY_HOLDER</constant></term> + <listitem> + <para> + Make this connection a policy holder (see above). With this + bit set, an item of type <constant>KDBUS_ITEM_NAME</constant> + has to be attached. This item describes the well-known name + this connection should hold a policy for. + A connection can not be an activator and a policy holder at + the same time time, so this bit is not allowed together with + <constant>KDBUS_HELLO_ACTIVATOR</constant>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_HELLO_MONITOR</constant></term> + <listitem> + <para> + Make this connection a monitor connection (see above). + </para> + <para> + This flag can only be set by privileged bus connections. See + below for more information. + A connection can not be monitor and an activator or a policy + holder at the same time time, so this bit is not allowed + together with <constant>KDBUS_HELLO_ACTIVATOR</constant> or + <constant>KDBUS_HELLO_POLICY_HOLDER</constant>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Requests a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will return + <errorcode>0</errorcode>, and the <varname>flags</varname> + field will have all bits set that are valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>attach_flags_send</varname></term> + <listitem><para> + Set the bits for metadata this connection permits to be sent to the + receiving peer. Only metadata items that are both allowed to be sent + by the sender and that are requested by the receiver will be attached + to the message. Note, however, that the bus may optionally require + some of those bits to be set. If the match fails, the ioctl will fail + with <varname>errno</varname> set to + <constant>ECONNREFUSED</constant>. In either case, when returning the + field will be set to the mask of metadata items that are enforced by + the bus with the <constant>KDBUS_FLAGS_KERNEL</constant> bit set as + well. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>attach_flags_recv</varname></term> + <listitem><para> + Request the attachment of metadata for each message received by this + connection. See + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for information about metadata, and + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + regarding items in general. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>bus_flags</varname></term> + <listitem><para> + Upon successful completion of the ioctl, this member will contain the + flags of the bus it connected to. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>id</varname></term> + <listitem><para> + Upon successful completion of the command, this member will contain + the numerical ID of the new connection. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>pool_size</varname></term> + <listitem><para> + The size of the communication pool, in bytes. The pool can be + accessed by calling + <citerefentry> + <refentrytitle>mmap</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + on the file descriptor that was used to issue the + <constant>KDBUS_CMD_HELLO</constant> ioctl. + The pool size of a connection must be greater than + <constant>0</constant> and a multiple of + <constant>PAGE_SIZE</constant>. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>offset</varname></term> + <listitem><para> + The kernel will return the offset in the pool where returned details + will be stored. See below. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>id128</varname></term> + <listitem><para> + Upon successful completion of the ioctl, this member will contain the + <emphasis>128-bit UUID</emphasis> of the connected bus. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Variable list of items containing optional additional information. + The following items are currently expected/valid: + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_CONN_DESCRIPTION</constant></term> + <listitem> + <para> + Contains a string that describes this connection, so it can + be identified later. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NAME</constant></term> + <term><constant>KDBUS_ITEM_POLICY_ACCESS</constant></term> + <listitem> + <para> + For activators and policy holders only, combinations of + these two items describe policy access entries. See + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further details. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_CREDS</constant></term> + <term><constant>KDBUS_ITEM_PIDS</constant></term> + <term><constant>KDBUS_ITEM_SECLABEL</constant></term> + <listitem> + <para> + Privileged bus users may submit these types in order to + create connections with faked credentials. This information + will be returned when peer information is queried by + <constant>KDBUS_CMD_CONN_INFO</constant>. See below for more + information on retrieving information on connections. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item, programs can <emphasis>probe</emphasis> the + kernel for known item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + At the offset returned in the <varname>offset</varname> field of + <type>struct kdbus_cmd_hello</type>, the kernel will store items + of the following types: + </para> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_BLOOM_PARAMETER</constant></term> + <listitem> + <para> + Bloom filter parameter as defined by the bus creator. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + The offset in the pool has to be freed with the + <constant>KDBUS_CMD_FREE</constant> ioctl. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further information. + </para> + </refsect1> + + <refsect1> + <title>Retrieving information on a connection</title> + <para> + The <constant>KDBUS_CMD_CONN_INFO</constant> ioctl can be used to + retrieve credentials and properties of the initial creator of a + connection. This ioctl uses the following struct. + </para> + + <programlisting> +struct kdbus_cmd_info { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 id; + __u64 attach_flags; + __u64 offset; + __u64 info_size; + struct kdbus_item items[0]; +}; + </programlisting> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + Currently, no flags are supported. + <constant>KDBUS_FLAG_NEGOTIATE</constant> is accepted to probe for + valid flags. If set, the ioctl will return <errorcode>0</errorcode>, + and the <varname>flags</varname> field is set to + <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>id</varname></term> + <listitem><para> + The numerical ID of the connection for which information is to be + retrieved. If set to a non-zero value, the + <constant>KDBUS_ITEM_OWNED_NAME</constant> item is ignored. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>attach_flags</varname></term> + <listitem><para> + Specifies which metadata items should be attached to the answer. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>offset</varname></term> + <listitem><para> + When the ioctl returns, this field will contain the offset of the + connection information inside the caller's pool. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further information. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>info_size</varname></term> + <listitem><para> + The kernel will return the size of the returned information, so + applications can optionally + <citerefentry> + <refentrytitle>mmap</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + specific parts of the pool. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further information. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + The following items are expected for + <constant>KDBUS_CMD_CONN_INFO</constant>. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_OWNED_NAME</constant></term> + <listitem> + <para> + Contains the well-known name of the connection to look up as. + This item is mandatory if the <varname>id</varname> field is + set to 0. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item, programs can <emphasis>probe</emphasis> the + kernel for known item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para></listitem> + </varlistentry> + </variablelist> + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + When the ioctl returns, the following struct will be stored in the + caller's pool at <varname>offset</varname>. The fields in this struct + are described below. + </para> + + <programlisting> +struct kdbus_info { + __u64 size; + __u64 id; + __u64 flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>id</varname></term> + <listitem><para> + The connection's unique ID. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + The connection's flags as specified when it was created. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Depending on the <varname>flags</varname> field in + <type>struct kdbus_cmd_info</type>, items of types + <constant>KDBUS_ITEM_OWNED_NAME</constant> and + <constant>KDBUS_ITEM_CONN_DESCRIPTION</constant> may follow here. + <constant>KDBUS_ITEM_NEGOTIATE</constant> is also allowed. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + Once the caller is finished with parsing the return buffer, it needs to + employ the <constant>KDBUS_CMD_FREE</constant> command for the offset, in + order to free the buffer part. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further information. + </para> + </refsect1> + + <refsect1> + <title>Getting information about a connection's bus creator</title> + <para> + The <constant>KDBUS_CMD_BUS_CREATOR_INFO</constant> ioctl takes the same + struct as <constant>KDBUS_CMD_CONN_INFO</constant>, but is used to + retrieve information about the creator of the bus the connection is + attached to. The metadata returned by this call is collected during the + creation of the bus and is never altered afterwards, so it provides + pristine information on the task that created the bus, at the moment when + it did so. + </para> + <para> + In response to this call, a slice in the connection's pool is allocated + and filled with an object of type <type>struct kdbus_info</type>, + pointed to by the ioctl's <varname>offset</varname> field. + </para> + + <programlisting> +struct kdbus_info { + __u64 size; + __u64 id; + __u64 flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>id</varname></term> + <listitem><para> + The bus ID. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + The bus flags as specified when it was created. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Metadata information is stored in items here. The item list + contains a <constant>KDBUS_ITEM_MAKE_NAME</constant> item that + indicates the bus name of the calling connection. + <constant>KDBUS_ITEM_NEGOTIATE</constant> is allowed to probe + for known item types. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + Once the caller is finished with parsing the return buffer, it needs to + employ the <constant>KDBUS_CMD_FREE</constant> command for the offset, in + order to free the buffer part. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further information. + </para> + </refsect1> + + <refsect1> + <title>Updating connection details</title> + <para> + Some of a connection's details can be updated with the + <constant>KDBUS_CMD_CONN_UPDATE</constant> ioctl, using the file + descriptor that was used to create the connection. The update command + uses the following struct. + </para> + + <programlisting> +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + Currently, no flags are supported. + <constant>KDBUS_FLAG_NEGOTIATE</constant> is accepted to probe for + valid flags. If set, the ioctl will return <errorcode>0</errorcode>, + and the <varname>flags</varname> field is set to + <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Items to describe the connection details to be updated. The + following item types are supported. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_ATTACH_FLAGS_SEND</constant></term> + <listitem> + <para> + Supply a new set of metadata items that this connection + permits to be sent along with messages. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ATTACH_FLAGS_RECV</constant></term> + <listitem> + <para> + Supply a new set of metadata items that this connection + requests to be attached to each message. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NAME</constant></term> + <term><constant>KDBUS_ITEM_POLICY_ACCESS</constant></term> + <listitem> + <para> + Policy holder connections may supply a new set of policy + information with these items. For other connection types, + <constant>EOPNOTSUPP</constant> is returned in + <varname>errno</varname>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item, programs can <emphasis>probe</emphasis> the + kernel for known item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Termination of connections</title> + <para> + A connection can be terminated by simply calling + <citerefentry> + <refentrytitle>close</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + on its file descriptor. All pending incoming messages will be discarded, + and the memory allocated by the pool will be freed. + </para> + + <para> + An alternative way of closing down a connection is via the + <constant>KDBUS_CMD_BYEBYE</constant> ioctl. This ioctl will succeed only + if the message queue of the connection is empty at the time of closing; + otherwise, the ioctl will fail with <varname>errno</varname> set to + <constant>EBUSY</constant>. When this ioctl returns + successfully, the connection has been terminated and won't accept any new + messages from remote peers. This way, a connection can be terminated + race-free, without losing any messages. The ioctl takes an argument of + type <type>struct kdbus_cmd</type>. + </para> + + <programlisting> +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + Currently, no flags are supported. + <constant>KDBUS_FLAG_NEGOTIATE</constant> is accepted to probe for + valid flags. If set, the ioctl will fail with + <varname>errno</varname> set to <constant>EPROTO</constant>, and + the <varname>flags</varname> field is set to <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + The following item types are supported. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item, programs can <emphasis>probe</emphasis> the + kernel for known item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Return value</title> + <para> + On success, all mentioned ioctl commands return <errorcode>0</errorcode>; + on error, <errorcode>-1</errorcode> is returned, and + <varname>errno</varname> is set to indicate the error. + If the issued ioctl is illegal for the file descriptor used, + <varname>errno</varname> will be set to <constant>ENOTTY</constant>. + </para> + + <refsect2> + <title> + <constant>KDBUS_CMD_HELLO</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EFAULT</constant></term> + <listitem><para> + The supplied pool size was 0 or not a multiple of the page size. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + The flags supplied in <type>struct kdbus_cmd_hello</type> + are invalid. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + An illegal combination of + <constant>KDBUS_HELLO_MONITOR</constant>, + <constant>KDBUS_HELLO_ACTIVATOR</constant> and + <constant>KDBUS_HELLO_POLICY_HOLDER</constant> was passed in + <varname>flags</varname>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + An invalid set of items was supplied. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ECONNREFUSED</constant></term> + <listitem><para> + The attach_flags_send field did not satisfy the requirements of + the bus. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EPERM</constant></term> + <listitem><para> + A <constant>KDBUS_ITEM_CREDS</constant> items was supplied, but the + current user is not privileged. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ESHUTDOWN</constant></term> + <listitem><para> + The bus you were trying to connect to has already been shut down. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EMFILE</constant></term> + <listitem><para> + The maximum number of connections on the bus has been reached. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EOPNOTSUPP</constant></term> + <listitem><para> + The endpoint does not support the connection flags supplied in + <type>struct kdbus_cmd_hello</type>. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_BYEBYE</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EALREADY</constant></term> + <listitem><para> + The connection has already been shut down. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EBUSY</constant></term> + <listitem><para> + There are still messages queued up in the connection's pool. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_CONN_INFO</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Invalid flags, or neither an ID nor a name was provided, or the + name is invalid. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ESRCH</constant></term> + <listitem><para> + Connection lookup by name failed. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ENXIO</constant></term> + <listitem><para> + No connection with the provided connection ID found. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_CONN_UPDATE</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Illegal flags or items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Wildcards submitted in policy entries, or illegal sequence + of policy items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EOPNOTSUPP</constant></term> + <listitem><para> + Operation not supported by connection. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>E2BIG</constant></term> + <listitem><para> + Too many policy items attached. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.endpoint.xml b/ipc/kdbus/Documentation/kdbus.endpoint.xml new file mode 100644 index 000000000000..6632485f3e84 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.endpoint.xml @@ -0,0 +1,429 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.endpoint"> + + <refentryinfo> + <title>kdbus.endpoint</title> + <productname>kdbus.endpoint</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.endpoint</refname> + <refpurpose>kdbus endpoint</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para> + Endpoints are entry points to a bus (see + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>). + By default, each bus has a default + endpoint called 'bus'. The bus owner has the ability to create custom + endpoints with specific names, permissions, and policy databases + (see below). An endpoint is presented as file underneath the directory + of the parent bus. + </para> + <para> + To create a custom endpoint, open the default endpoint + (<literal>bus</literal>) and use the + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant> ioctl with + <type>struct kdbus_cmd</type>. Custom endpoints always have a policy + database that, by default, forbids any operation. You have to explicitly + install policy entries to allow any operation on this endpoint. + </para> + <para> + Once <constant>KDBUS_CMD_ENDPOINT_MAKE</constant> succeeded, the new + endpoint will appear in the filesystem + (<citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>), and the used file descriptor will manage the + newly created endpoint resource. It cannot be used to manage further + resources and must be kept open as long as the endpoint is needed. The + endpoint will be terminated as soon as the file descriptor is closed. + </para> + <para> + Endpoint names may be chosen freely except for one restriction: the name + must be prefixed with the numeric effective UID of the creator and a dash. + This is required to avoid namespace clashes between different users. When + creating an endpoint, the name that is passed in must be properly + formatted or the kernel will refuse creation of the endpoint. Example: + <literal>1047-my-endpoint</literal> is an acceptable name for an + endpoint registered by a user with UID 1047. However, + <literal>1024-my-endpoint</literal> is not, and neither is + <literal>my-endpoint</literal>. The UID must be provided in the + user-namespace of the bus. + </para> + <para> + To create connections to a bus, use <constant>KDBUS_CMD_HELLO</constant> + on a file descriptor returned by <function>open()</function> on an + endpoint node. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further details. + </para> + </refsect1> + + <refsect1> + <title>Creating custom endpoints</title> + <para> + To create a new endpoint, the + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant> command is used. Along with + the endpoint's name, which will be used to expose the endpoint in the + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>, + the command also optionally takes items to set up the endpoint's + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant> takes a + <type>struct kdbus_cmd</type> argument. + </para> + <programlisting> +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para>The flags for creation.</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_MAKE_ACCESS_GROUP</constant></term> + <listitem> + <para>Make the endpoint file group-accessible.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_MAKE_ACCESS_WORLD</constant></term> + <listitem> + <para>Make the endpoint file world-accessible.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Requests a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will return + <errorcode>0</errorcode>, and the <varname>flags</varname> + field will have all bits set that are valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + The following items are expected for + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant>. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_MAKE_NAME</constant></term> + <listitem> + <para>Contains a string to identify the endpoint name.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NAME</constant></term> + <term><constant>KDBUS_ITEM_POLICY_ACCESS</constant></term> + <listitem> + <para> + These items are used to set the policy attached to the + endpoint. For more details on bus and endpoint policies, see + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para> + </listitem> + </varlistentry> + </variablelist> + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <varname>EINVAL</varname>. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Updating endpoints</title> + <para> + To update an existing endpoint, the + <constant>KDBUS_CMD_ENDPOINT_UPDATE</constant> command is used on the file + descriptor that was used to create the endpoint, using + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant>. The only relevant detail of + the endpoint that can be updated is the policy. When the command is + employed, the policy of the endpoint is <emphasis>replaced</emphasis> + atomically with the new set of rules. + The command takes a <type>struct kdbus_cmd</type> argument. + </para> + <programlisting> +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + Unused for this command. + <constant>KDBUS_FLAG_NEGOTIATE</constant> is accepted to probe for + valid flags. If set, the ioctl will return <errorcode>0</errorcode>, + and the <varname>flags</varname> field is set to + <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + The following items are expected for + <constant>KDBUS_CMD_ENDPOINT_UPDATE</constant>. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_NAME</constant></term> + <term><constant>KDBUS_ITEM_POLICY_ACCESS</constant></term> + <listitem> + <para> + These items are used to set the policy attached to the + endpoint. For more details on bus and endpoint policies, see + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + Existing policy is atomically replaced with the new rules + provided. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item, programs can <emphasis>probe</emphasis> the + kernel for known item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para></listitem> + </varlistentry> + </variablelist> + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Return value</title> + <para> + On success, all mentioned ioctl commands return <errorcode>0</errorcode>; + on error, <errorcode>-1</errorcode> is returned, and + <varname>errno</varname> is set to indicate the error. + If the issued ioctl is illegal for the file descriptor used, + <varname>errno</varname> will be set to <constant>ENOTTY</constant>. + </para> + + <refsect2> + <title> + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant> may fail with the + following errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + The flags supplied in the <type>struct kdbus_cmd</type> + are invalid. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Illegal combination of <constant>KDBUS_ITEM_NAME</constant> and + <constant>KDBUS_ITEM_POLICY_ACCESS</constant> was provided. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EEXIST</constant></term> + <listitem><para> + An endpoint of that name already exists. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EPERM</constant></term> + <listitem><para> + The calling user is not privileged. See + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for information about privileged users. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_ENDPOINT_UPDATE</constant> may fail with the + following errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + The flags supplied in <type>struct kdbus_cmd</type> + are invalid. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Illegal combination of <constant>KDBUS_ITEM_NAME</constant> and + <constant>KDBUS_ITEM_POLICY_ACCESS</constant> was provided. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.fs.xml b/ipc/kdbus/Documentation/kdbus.fs.xml new file mode 100644 index 000000000000..8c2a90e10b66 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.fs.xml @@ -0,0 +1,124 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus_fs"> + + <refentryinfo> + <title>kdbus.fs</title> + <productname>kdbus.fs</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.fs</refname> + <refpurpose>kdbus file system</refpurpose> + </refnamediv> + + <refsect1> + <title>File-system Layout</title> + + <para> + The <emphasis>kdbusfs</emphasis> pseudo filesystem provides access to + kdbus entities, such as <emphasis>buses</emphasis> and + <emphasis>endpoints</emphasis>. Each time the filesystem is mounted, + a new, isolated kdbus instance is created, which is independent from the + other instances. + </para> + <para> + The system-wide standard mount point for <emphasis>kdbusfs</emphasis> is + <constant>/sys/fs/kdbus</constant>. + </para> + + <para> + Buses are represented as directories in the file system layout, whereas + endpoints are exposed as files inside these directories. At the top-level, + a <emphasis>control</emphasis> node is present, which can be opened to + create new buses via the <constant>KDBUS_CMD_BUS_MAKE</constant> ioctl. + Each <emphasis>bus</emphasis> shows a default endpoint called + <varname>bus</varname>, which can be opened to either create a connection + with the <constant>KDBUS_CMD_HELLO</constant> ioctl, or to create new + custom endpoints for the bus with + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant>. See + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>, + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> and + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + + <para>Following, you can see an example layout of the + <emphasis>kdbusfs</emphasis> filesystem:</para> + +<programlisting> + /sys/fs/kdbus/ ; mount-point + |-- 0-system ; bus directory + | |-- bus ; default endpoint + | `-- 1017-custom ; custom endpoint + |-- 1000-user ; bus directory + | |-- bus ; default endpoint + | |-- 1000-service-A ; custom endpoint + | `-- 1000-service-B ; custom endpoint + `-- control ; control file +</programlisting> + </refsect1> + + <refsect1> + <title>Mounting instances</title> + <para> + In order to get a new and separate kdbus environment, a new instance + of <emphasis>kdbusfs</emphasis> can be mounted like this: + </para> +<programlisting> + # mount -t kdbusfs kdbusfs /tmp/new_kdbus/ +</programlisting> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>mount</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.item.xml b/ipc/kdbus/Documentation/kdbus.item.xml new file mode 100644 index 000000000000..09f8b903116f --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.item.xml @@ -0,0 +1,839 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus"> + + <refentryinfo> + <title>kdbus.item</title> + <productname>kdbus item</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.item</refname> + <refpurpose>kdbus item structure, layout and usage</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para> + To flexibly augment transport structures, data blobs of type + <type>struct kdbus_item</type> can be attached to the structs passed + into the ioctls. Some ioctls make items of certain types mandatory, + others are optional. Items that are unsupported by ioctls they are + attached to will cause the ioctl to fail with <varname>errno</varname> + set to <constant>EINVAL</constant>. + Items are also used for information stored in a connection's + <emphasis>pool</emphasis>, such as received messages, name lists or + requested connection or bus owner information. Depending on the type of + an item, its total size is either fixed or variable. + </para> + + <refsect2> + <title>Chaining items</title> + <para> + Whenever items are used as part of the kdbus kernel API, they are + embedded in structs that are embedded inside structs that themselves + include a size field containing the overall size of the structure. + This allows multiple items to be chained up, and an item iterator + (see below) is capable of detecting the end of an item chain. + </para> + </refsect2> + + <refsect2> + <title>Alignment</title> + <para> + The kernel expects all items to be aligned to 8-byte boundaries. + Unaligned items will cause the ioctl they are used with to fail + with <varname>errno</varname> set to <constant>EINVAL</constant>. + An item that has an unaligned size itself hence needs to be padded + if it is followed by another item. + </para> + </refsect2> + + <refsect2> + <title>Iterating items</title> + <para> + A simple iterator would iterate over the items until the items have + reached the embedding structure's overall size. An example + implementation is shown below. + </para> + + <programlisting><![CDATA[ +#define KDBUS_ALIGN8(val) (((val) + 7) & ~7) + +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))(((uint8_t *)item) + KDBUS_ALIGN8((item)->size)) + +#define KDBUS_ITEM_FOREACH(item, head, first) \ + for (item = (head)->first; \ + ((uint8_t *)(item) < (uint8_t *)(head) + (head)->size) && \ + ((uint8_t *)(item) >= (uint8_t *)(head)); \ + item = KDBUS_ITEM_NEXT(item)) + ]]></programlisting> + </refsect2> + </refsect1> + + <refsect1> + <title>Item layout</title> + <para> + A <type>struct kdbus_item</type> consists of a + <varname>size</varname> field, describing its overall size, and a + <varname>type</varname> field, both 64 bit wide. They are followed by + a union to store information that is specific to the item's type. + The struct layout is shown below. + </para> + + <programlisting> +struct kdbus_item { + __u64 size; + __u64 type; + /* item payload - see below */ + union { + __u8 data[0]; + __u32 data32[0]; + __u64 data64[0]; + char str[0]; + + __u64 id; + struct kdbus_vec vec; + struct kdbus_creds creds; + struct kdbus_pids pids; + struct kdbus_audit audit; + struct kdbus_caps caps; + struct kdbus_timestamp timestamp; + struct kdbus_name name; + struct kdbus_bloom_parameter bloom_parameter; + struct kdbus_bloom_filter bloom_filter; + struct kdbus_memfd memfd; + int fds[0]; + struct kdbus_notify_name_change name_change; + struct kdbus_notify_id_change id_change; + struct kdbus_policy_access policy_access; + }; +}; + </programlisting> + + <para> + <type>struct kdbus_item</type> should never be used to allocate + an item instance, as its size may grow in future releases of the API. + Instead, it should be manually assembled by storing the + <varname>size</varname>, <varname>type</varname> and payload to a + struct of its own. + </para> + </refsect1> + + <refsect1> + <title>Item types</title> + + <refsect2> + <title>Negotiation item</title> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item is attached to any ioctl, programs can + <emphasis>probe</emphasis> the kernel for known item types. + The item carries an array of <type>uint64_t</type> values in + <varname>item.data64</varname>, each set to an item type to + probe. The kernel will reset each member of this array that is + not recognized as valid item type to <constant>0</constant>. + This way, users can negotiate kernel features at start-up to + keep newer userspace compatible with older kernels. This item + is never attached by the kernel in response to any command. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title>Command specific items</title> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_PAYLOAD_VEC</constant></term> + <term><constant>KDBUS_ITEM_PAYLOAD_OFF</constant></term> + <listitem><para> + Messages are directly copied by the sending process into the + receiver's + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + This way, two peers can exchange data by effectively doing a + single-copy from one process to another; the kernel will not buffer + the data anywhere else. <constant>KDBUS_ITEM_PAYLOAD_VEC</constant> + is used when <emphasis>sending</emphasis> message. The item + references a memory address when the payload data can be found. + <constant>KDBUS_ITEM_PAYLOAD_OFF</constant> is used when messages + are <emphasis>received</emphasis>, and the + <constant>offset</constant> value describes the offset inside the + receiving connection's + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + where the message payload can be found. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on passing of payload data along with a + message. + <programlisting> +struct kdbus_vec { + __u64 size; + union { + __u64 address; + __u64 offset; + }; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_PAYLOAD_MEMFD</constant></term> + <listitem><para> + Transports a file descriptor of a <emphasis>memfd</emphasis> in + <type>struct kdbus_memfd</type> in <varname>item.memfd</varname>. + The <varname>size</varname> field has to match the actual size of + the memfd that was specified when it was created. The + <varname>start</varname> parameter denotes the offset inside the + memfd at which the referenced payload starts. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on passing of payload data along with a + message. + <programlisting> +struct kdbus_memfd { + __u64 start; + __u64 size; + int fd; + __u32 __pad; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_FDS</constant></term> + <listitem><para> + Contains an array of <emphasis>file descriptors</emphasis>. + When used with <constant>KDBUS_CMD_SEND</constant>, the values of + this array must be filled with valid file descriptor numbers. + When received as item attached to a message, the array will + contain the numbers of the installed file descriptors, or + <constant>-1</constant> in case an error occurred. + In either case, the number of entries in the array is derived from + the item's total size. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title>Items specific to some commands</title> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_CANCEL_FD</constant></term> + <listitem><para> + Transports a file descriptor that can be used to cancel a + synchronous <constant>KDBUS_CMD_SEND</constant> operation by + writing to it. The file descriptor is stored in + <varname>item.fd[0]</varname>. The item may only contain one + file descriptor. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on this item and how to use it. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_BLOOM_PARAMETER</constant></term> + <listitem><para> + Contains a set of <emphasis>bloom parameters</emphasis> as + <type>struct kdbus_bloom_parameter</type> in + <varname>item.bloom_parameter</varname>. + The item is passed from userspace to kernel during the + <constant>KDBUS_CMD_BUS_MAKE</constant> ioctl, and returned + verbatim when <constant>KDBUS_CMD_HELLO</constant> is called. + The kernel does not use the bloom parameters, but they need to + be known by each connection on the bus in order to define the + bloom filter hash details. See + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on matching and bloom filters. + <programlisting> +struct kdbus_bloom_parameter { + __u64 size; + __u64 n_hash; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_BLOOM_FILTER</constant></term> + <listitem><para> + Carries a <emphasis>bloom filter</emphasis> as + <type>struct kdbus_bloom_filter</type> in + <varname>item.bloom_filter</varname>. It is mandatory to send this + item attached to a <type>struct kdbus_msg</type>, in case the + message is a signal. This item is never transported from kernel to + userspace. See + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on matching and bloom filters. + <programlisting> +struct kdbus_bloom_filter { + __u64 generation; + __u64 data[0]; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_BLOOM_MASK</constant></term> + <listitem><para> + Transports a <emphasis>bloom mask</emphasis> as binary data blob + stored in <varname>item.data</varname>. This item is used to + describe a match into a connection's match database. See + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on matching and bloom filters. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_DST_NAME</constant></term> + <listitem><para> + Contains a <emphasis>well-known name</emphasis> to send a + message to, as null-terminated string in + <varname>item.str</varname>. This item is used with + <constant>KDBUS_CMD_SEND</constant>. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on how to send a message. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_MAKE_NAME</constant></term> + <listitem><para> + Contains a <emphasis>bus name</emphasis> or + <emphasis>endpoint name</emphasis>, stored as null-terminated + string in <varname>item.str</varname>. This item is sent from + userspace to kernel when buses or endpoints are created, and + returned back to userspace when the bus creator information is + queried. See + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + and + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ATTACH_FLAGS_SEND</constant></term> + <term><constant>KDBUS_ITEM_ATTACH_FLAGS_RECV</constant></term> + <listitem><para> + Contains a set of <emphasis>attach flags</emphasis> at + <emphasis>send</emphasis> or <emphasis>receive</emphasis> time. See + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>, + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> and + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on attach flags. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ID</constant></term> + <listitem><para> + Transports a connection's <emphasis>numerical ID</emphasis> of + a connection as <type>uint64_t</type> value in + <varname>item.id</varname>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NAME</constant></term> + <listitem><para> + Transports a name associated with the + <emphasis>name registry</emphasis> as null-terminated string as + <type>struct kdbus_name</type> in + <varname>item.name</varname>. The <varname>flags</varname> + contains the flags of the name. See + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on how to access the name registry of a bus. + <programlisting> +struct kdbus_name { + __u64 flags; + char name[0]; +}; + </programlisting> + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title>Items attached by the kernel as metadata</title> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_TIMESTAMP</constant></term> + <listitem><para> + Contains both the <emphasis>monotonic</emphasis> and the + <emphasis>realtime</emphasis> timestamp, taken when the message + was processed on the kernel side. + Stored as <type>struct kdbus_timestamp</type> in + <varname>item.timestamp</varname>. + <programlisting> +struct kdbus_timestamp { + __u64 seqnum; + __u64 monotonic_ns; + __u64 realtime_ns; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_CREDS</constant></term> + <listitem><para> + Contains a set of <emphasis>user</emphasis> and + <emphasis>group</emphasis> information as 32-bit values, in the + usual four flavors: real, effective, saved and filesystem related. + Stored as <type>struct kdbus_creds</type> in + <varname>item.creds</varname>. + <programlisting> +struct kdbus_creds { + __u32 uid; + __u32 euid; + __u32 suid; + __u32 fsuid; + __u32 gid; + __u32 egid; + __u32 sgid; + __u32 fsgid; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_PIDS</constant></term> + <listitem><para> + Contains the <emphasis>PID</emphasis>, <emphasis>TID</emphasis> + and <emphasis>parent PID (PPID)</emphasis> of a remote peer. + Stored as <type>struct kdbus_pids</type> in + <varname>item.pids</varname>. + <programlisting> +struct kdbus_pids { + __u64 pid; + __u64 tid; + __u64 ppid; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_AUXGROUPS</constant></term> + <listitem><para> + Contains the <emphasis>auxiliary (supplementary) groups</emphasis> + a remote peer is a member of, stored as array of + <type>uint32_t</type> values in <varname>item.data32</varname>. + The array length can be determined by looking at the item's total + size, subtracting the size of the header and dividing the + remainder by <constant>sizeof(uint32_t)</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_OWNED_NAME</constant></term> + <listitem><para> + Contains a <emphasis>well-known name</emphasis> currently owned + by a connection. The name is stored as null-terminated string in + <varname>item.str</varname>. Its length can also be derived from + the item's total size. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_TID_COMM</constant> [*]</term> + <listitem><para> + Contains the <emphasis>comm</emphasis> string of a task's + <emphasis>TID</emphasis> (thread ID), stored as null-terminated + string in <varname>item.str</varname>. Its length can also be + derived from the item's total size. Receivers of this item should + not use its contents for any kind of security measures. See below. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_PID_COMM</constant> [*]</term> + <listitem><para> + Contains the <emphasis>comm</emphasis> string of a task's + <emphasis>PID</emphasis> (process ID), stored as null-terminated + string in <varname>item.str</varname>. Its length can also be + derived from the item's total size. Receivers of this item should + not use its contents for any kind of security measures. See below. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_EXE</constant> [*]</term> + <listitem><para> + Contains the <emphasis>path to the executable</emphasis> of a task, + stored as null-terminated string in <varname>item.str</varname>. Its + length can also be derived from the item's total size. Receivers of + this item should not use its contents for any kind of security + measures. See below. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_CMDLINE</constant> [*]</term> + <listitem><para> + Contains the <emphasis>command line arguments</emphasis> of a + task, stored as an <emphasis>array</emphasis> of null-terminated + strings in <varname>item.str</varname>. The total length of all + strings in the array can be derived from the item's total size. + Receivers of this item should not use its contents for any kind + of security measures. See below. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_CGROUP</constant></term> + <listitem><para> + Contains the <emphasis>cgroup path</emphasis> of a task, stored + as null-terminated string in <varname>item.str</varname>. Its + length can also be derived from the item's total size. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_CAPS</constant></term> + <listitem><para> + Contains sets of <emphasis>capabilities</emphasis>, stored as + <type>struct kdbus_caps</type> in <varname>item.caps</varname>. + As the item size may increase in the future, programs should be + written in a way that it takes + <varname>item.caps.last_cap</varname> into account, and derive + the number of sets and rows from the item size and the reported + number of valid capability bits. + <programlisting> +struct kdbus_caps { + __u32 last_cap; + __u32 caps[0]; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_SECLABEL</constant></term> + <listitem><para> + Contains the <emphasis>LSM label</emphasis> of a task, stored as + null-terminated string in <varname>item.str</varname>. Its length + can also be derived from the item's total size. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_AUDIT</constant></term> + <listitem><para> + Contains the audit <emphasis>sessionid</emphasis> and + <emphasis>loginuid</emphasis> of a task, stored as + <type>struct kdbus_audit</type> in + <varname>item.audit</varname>. + <programlisting> +struct kdbus_audit { + __u32 sessionid; + __u32 loginuid; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_CONN_DESCRIPTION</constant></term> + <listitem><para> + Contains the <emphasis>connection description</emphasis>, as set + by <constant>KDBUS_CMD_HELLO</constant> or + <constant>KDBUS_CMD_CONN_UPDATE</constant>, stored as + null-terminated string in <varname>item.str</varname>. Its length + can also be derived from the item's total size. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + All metadata is automatically translated into the + <emphasis>namespaces</emphasis> of the task that receives them. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information. + </para> + + <para> + [*] Note that the content stored in metadata items of type + <constant>KDBUS_ITEM_TID_COMM</constant>, + <constant>KDBUS_ITEM_PID_COMM</constant>, + <constant>KDBUS_ITEM_EXE</constant> and + <constant>KDBUS_ITEM_CMDLINE</constant> + can easily be tampered by the sending tasks. Therefore, they should + <emphasis>not</emphasis> be used for any sort of security relevant + assumptions. The only reason they are transmitted is to let + receivers know about details that were set when metadata was + collected, even though the task they were collected from is not + active any longer when the items are received. + </para> + </refsect2> + + <refsect2> + <title>Items used for policy entries, matches and notifications</title> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_POLICY_ACCESS</constant></term> + <listitem><para> + This item describes a <emphasis>policy access</emphasis> entry to + access the policy database of a + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> or + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + Please refer to + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on the policy database and how to access it. + <programlisting> +struct kdbus_policy_access { + __u64 type; + __u64 access; + __u64 id; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ID_ADD</constant></term> + <term><constant>KDBUS_ITEM_ID_REMOVE</constant></term> + <listitem><para> + This item is sent as attachment to a + <emphasis>kernel notification</emphasis> and indicates that a + new connection was created on the bus, or that a connection was + disconnected, respectively. It stores a + <type>struct kdbus_notify_id_change</type> in + <varname>item.id_change</varname>. + The <varname>id</varname> field contains the numeric ID of the + connection that was added or removed, and <varname>flags</varname> + is set to the connection flags, as passed by + <constant>KDBUS_CMD_HELLO</constant>. See + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + and + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on matches and notification messages. + <programlisting> +struct kdbus_notify_id_change { + __u64 id; + __u64 flags; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NAME_ADD</constant></term> + <term><constant>KDBUS_ITEM_NAME_REMOVE</constant></term> + <term><constant>KDBUS_ITEM_NAME_CHANGE</constant></term> + <listitem><para> + This item is sent as attachment to a + <emphasis>kernel notification</emphasis> and indicates that a + <emphasis>well-known name</emphasis> appeared, disappeared or + transferred to another owner on the bus. It stores a + <type>struct kdbus_notify_name_change</type> in + <varname>item.name_change</varname>. + <varname>old_id</varname> describes the former owner of the name + and is set to <constant>0</constant> values in case of + <constant>KDBUS_ITEM_NAME_ADD</constant>. + <varname>new_id</varname> describes the new owner of the name and + is set to <constant>0</constant> values in case of + <constant>KDBUS_ITEM_NAME_REMOVE</constant>. + The <varname>name</varname> field contains the well-known name the + notification is about, as null-terminated string. See + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + and + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on matches and notification messages. + <programlisting> +struct kdbus_notify_name_change { + struct kdbus_notify_id_change old_id; + struct kdbus_notify_id_change new_id; + char name[0]; +}; + </programlisting> + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_REPLY_TIMEOUT</constant></term> + <listitem><para> + This item is sent as attachment to a + <emphasis>kernel notification</emphasis>. It informs the receiver + that an expected reply to a message was not received in time. + The remote peer ID and the message cookie are stored in the message + header. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information about messages, timeouts and notifications. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_REPLY_DEAD</constant></term> + <listitem><para> + This item is sent as attachment to a + <emphasis>kernel notification</emphasis>. It informs the receiver + that a remote connection a reply is expected from was disconnected + before that reply was sent. The remote peer ID and the message + cookie are stored in the message header. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information about messages, timeouts and notifications. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>memfd_create</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> + +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.match.xml b/ipc/kdbus/Documentation/kdbus.match.xml new file mode 100644 index 000000000000..ae38e04ab4d6 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.match.xml @@ -0,0 +1,555 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.match"> + + <refentryinfo> + <title>kdbus.match</title> + <productname>kdbus.match</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.match</refname> + <refpurpose>kdbus match</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para> + kdbus connections can install matches in order to subscribe to signal + messages sent on the bus. Such signal messages can be either directed + to a single connection (by setting a specific connection ID in + <varname>struct kdbus_msg.dst_id</varname> or by sending it to a + well-known name), or to potentially <emphasis>all</emphasis> currently + active connections on the bus (by setting + <varname>struct kdbus_msg.dst_id</varname> to + <constant>KDBUS_DST_ID_BROADCAST</constant>). + A signal message always has the <constant>KDBUS_MSG_SIGNAL</constant> + bit set in the <varname>flags</varname> bitfield. + Also, signal messages can originate from either the kernel (called + <emphasis>notifications</emphasis>), or from other bus connections. + In either case, a bus connection needs to have a suitable + <emphasis>match</emphasis> installed in order to receive any signal + message. Without any rules installed in the connection, no signal message + will be received. + </para> + </refsect1> + + <refsect1> + <title>Matches for signal messages from other connections</title> + <para> + Matches for messages from other connections (not kernel notifications) + are implemented as bloom filters (see below). The sender adds certain + properties of the message as elements to a bloom filter bit field, and + sends that along with the signal message. + + The receiving connection adds the message properties it is interested in + as elements to a bloom mask bit field, and uploads the mask as match rule, + possibly along with some other rules to further limit the match. + + The kernel will match the signal message's bloom filter against the + connection's bloom mask (simply by &-ing it), and will decide whether + the message should be delivered to a connection. + </para> + <para> + The kernel has no notion of any specific properties of the signal message, + all it sees are the bit fields of the bloom filter and the mask to match + against. The use of bloom filters allows simple and efficient matching, + without exposing any message properties or internals to the kernel side. + Clients need to deal with the fact that they might receive signal messages + which they did not subscribe to, as the bloom filter might allow + false-positives to pass the filter. + + To allow the future extension of the set of elements in the bloom filter, + the filter specifies a <emphasis>generation</emphasis> number. A later + generation must always contain all elements of the set of the previous + generation, but can add new elements to the set. The match rules mask can + carry an array with all previous generations of masks individually stored. + When the filter and mask are matched by the kernel, the mask with the + closest matching generation is selected as the index into the mask array. + </para> + </refsect1> + + <refsect1> + <title>Bloom filters</title> + <para> + Bloom filters allow checking whether a given word is present in a + dictionary. This allows connections to set up a mask for information it + is interested in, and will be delivered signal messages that have a + matching filter. + + For general information, see + <ulink url="https://en.wikipedia.org/wiki/Bloom_filter">the Wikipedia + article on bloom filters</ulink>. + </para> + <para> + The size of the bloom filter is defined per bus when it is created, in + <varname>kdbus_bloom_parameter.size</varname>. All bloom filters attached + to signal messages on the bus must match this size, and all bloom filter + matches uploaded by connections must also match the size, or a multiple + thereof (see below). + + The calculation of the mask has to be done in userspace applications. The + kernel just checks the bitmasks to decide whether or not to let the + message pass. All bits in the mask must match the filter in and bit-wise + <emphasis>AND</emphasis> logic, but the mask may have more bits set than + the filter. Consequently, false positive matches are expected to happen, + and programs must deal with that fact by checking the contents of the + payload again at receive time. + </para> + <para> + Masks are entities that are always passed to the kernel as part of a + match (with an item of type <constant>KDBUS_ITEM_BLOOM_MASK</constant>), + and filters can be attached to signals, with an item of type + <constant>KDBUS_ITEM_BLOOM_FILTER</constant>. For a filter to match, all + its bits have to be set in the match mask as well. + </para> + <para> + For example, consider a bus that has a bloom size of 8 bytes, and the + following mask/filter combinations: + </para> + <programlisting><![CDATA[ + filter 0x0101010101010101 + mask 0x0101010101010101 + -> matches + + filter 0x0303030303030303 + mask 0x0101010101010101 + -> doesn't match + + filter 0x0101010101010101 + mask 0x0303030303030303 + -> matches + ]]></programlisting> + + <para> + Hence, in order to catch all messages, a mask filled with + <constant>0xff</constant> bytes can be installed as a wildcard match rule. + </para> + + <refsect2> + <title>Generations</title> + + <para> + Uploaded matches may contain multiple masks, which have to be as large + as the bloom filter size defined by the bus. Each block of a mask is + called a <emphasis>generation</emphasis>, starting at index 0. + + At match time, when a signal is about to be delivered, a bloom mask + generation is passed, which denotes which of the bloom masks the filter + should be matched against. This allows programs to provide backward + compatible masks at upload time, while older clients can still match + against older versions of filters. + </para> + </refsect2> + </refsect1> + + <refsect1> + <title>Matches for kernel notifications</title> + <para> + To receive kernel generated notifications (see + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>), + a connection must install match rules that are different from + the bloom filter matches described in the section above. They can be + filtered by the connection ID that caused the notification to be sent, by + one of the names it currently owns, or by the type of the notification + (ID/name add/remove/change). + </para> + </refsect1> + + <refsect1> + <title>Adding a match</title> + <para> + To add a match, the <constant>KDBUS_CMD_MATCH_ADD</constant> ioctl is + used, which takes a <type>struct kdbus_cmd_match</type> as an argument + described below. + + Note that each of the items attached to this command will internally + create one match <emphasis>rule</emphasis>, and the collection of them, + which is submitted as one block via the ioctl, is called a + <emphasis>match</emphasis>. To allow a message to pass, all rules of a + match have to be satisfied. Hence, adding more items to the command will + only narrow the possibility of a match to effectively let the message + pass, and will decrease the chance that the connection's process will be + woken up needlessly. + + Multiple matches can be installed per connection. As long as one of it has + a set of rules which allows the message to pass, this one will be + decisive. + </para> + + <programlisting> +struct kdbus_cmd_match { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 cookie; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para>Flags to control the behavior of the ioctl.</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_MATCH_REPLACE</constant></term> + <listitem> + <para>Make the endpoint file group-accessible</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Requests a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will return + <errorcode>0</errorcode>, and the <varname>flags</varname> + field will have all bits set that are valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>cookie</varname></term> + <listitem><para> + A cookie which identifies the match, so it can be referred to when + removing it. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Items to define the actual rules of the matches. The following item + types are expected. Each item will create one new match rule. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_BLOOM_MASK</constant></term> + <listitem> + <para> + An item that carries the bloom filter mask to match against + in its data field. The payload size must match the bloom + filter size that was specified when the bus was created. + See the "Bloom filters" section above for more information on + bloom filters. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NAME</constant></term> + <listitem> + <para> + When used as part of kernel notifications, this item specifies + a name that is acquired, lost or that changed its owner (see + below). When used as part of a match for user-generated signal + messages, it specifies a name that the sending connection must + own at the time of sending the signal. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ID</constant></term> + <listitem> + <para> + Specify a sender connection's ID that will match this rule. + For kernel notifications, this specifies the ID of a + connection that was added to or removed from the bus. + For used-generated signals, it specifies the ID of the + connection that sent the signal message. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NAME_ADD</constant></term> + <term><constant>KDBUS_ITEM_NAME_REMOVE</constant></term> + <term><constant>KDBUS_ITEM_NAME_CHANGE</constant></term> + <listitem> + <para> + These items request delivery of kernel notifications that + describe a name acquisition, loss, or change. The details + are stored in the item's + <varname>kdbus_notify_name_change</varname> member. + All information specified must be matched in order to make + the message pass. Use + <constant>KDBUS_MATCH_ID_ANY</constant> to + match against any unique connection ID. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_ID_ADD</constant></term> + <term><constant>KDBUS_ITEM_ID_REMOVE</constant></term> + <listitem> + <para> + These items request delivery of kernel notifications that are + generated when a connection is created or terminated. + <type>struct kdbus_notify_id_change</type> is used to + store the actual match information. This item can be used to + monitor one particular connection ID, or, when the ID field + is set to <constant>KDBUS_MATCH_ID_ANY</constant>, + all of them. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_NEGOTIATE</constant></term> + <listitem><para> + With this item, programs can <emphasis>probe</emphasis> the + kernel for known item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + Refer to + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on message types. + </para> + </refsect1> + + <refsect1> + <title>Removing a match</title> + <para> + Matches can be removed with the + <constant>KDBUS_CMD_MATCH_REMOVE</constant> ioctl, which takes + <type>struct kdbus_cmd_match</type> as argument, but its fields + usage slightly differs compared to that of + <constant>KDBUS_CMD_MATCH_ADD</constant>. + </para> + + <programlisting> +struct kdbus_cmd_match { + __u64 size; + __u64 cookie; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>cookie</varname></term> + <listitem><para> + The cookie of the match, as it was passed when the match was added. + All matches that have this cookie will be removed. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + No flags are supported for this use case. + <constant>KDBUS_FLAG_NEGOTIATE</constant> is accepted to probe for + valid flags. If set, the ioctl will fail with + <errorcode>-1</errorcode>, <varname>errno</varname> is set to + <constant>EPROTO</constant>, and the <varname>flags</varname> field + is set to <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + No items are supported for this use case, but + <constant>KDBUS_ITEM_NEGOTIATE</constant> is allowed nevertheless. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Return value</title> + <para> + On success, all mentioned ioctl commands return <errorcode>0</errorcode>; + on error, <errorcode>-1</errorcode> is returned, and + <varname>errno</varname> is set to indicate the error. + If the issued ioctl is illegal for the file descriptor used, + <varname>errno</varname> will be set to <constant>ENOTTY</constant>. + </para> + + <refsect2> + <title> + <constant>KDBUS_CMD_MATCH_ADD</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Illegal flags or items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EDOM</constant></term> + <listitem><para> + Illegal bloom filter size. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EMFILE</constant></term> + <listitem><para> + Too many matches for this connection. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_MATCH_REMOVE</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Illegal flags. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EBADSLT</constant></term> + <listitem><para> + A match entry with the given cookie could not be found. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.message.xml b/ipc/kdbus/Documentation/kdbus.message.xml new file mode 100644 index 000000000000..0115d9d50db3 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.message.xml @@ -0,0 +1,1276 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.message"> + + <refentryinfo> + <title>kdbus.message</title> + <productname>kdbus.message</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.message</refname> + <refpurpose>kdbus message</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para> + A kdbus message is used to exchange information between two connections + on a bus, or to transport notifications from the kernel to one or many + connections. This document describes the layout of messages, how payload + is added to them and how they are sent and received. + </para> + </refsect1> + + <refsect1> + <title>Message layout</title> + + <para>The layout of a message is shown below.</para> + + <programlisting> + +-------------------------------------------------------------------------+ + | Message | + | +---------------------------------------------------------------------+ | + | | Header | | + | | size: overall message size, including the data records | | + | | destination: connection ID of the receiver | | + | | source: connection ID of the sender (set by kernel) | | + | | payload_type: "DBusDBus" textual identifier stored as uint64_t | | + | +---------------------------------------------------------------------+ | + | +---------------------------------------------------------------------+ | + | | Data Record | | + | | size: overall record size (without padding) | | + | | type: type of data | | + | | data: reference to data (address or file descriptor) | | + | +---------------------------------------------------------------------+ | + | +---------------------------------------------------------------------+ | + | | padding bytes to the next 8 byte alignment | | + | +---------------------------------------------------------------------+ | + | +---------------------------------------------------------------------+ | + | | Data Record | | + | | size: overall record size (without padding) | | + | | ... | | + | +---------------------------------------------------------------------+ | + | +---------------------------------------------------------------------+ | + | | padding bytes to the next 8 byte alignment | | + | +---------------------------------------------------------------------+ | + | +---------------------------------------------------------------------+ | + | | Data Record | | + | | size: overall record size | | + | | ... | | + | +---------------------------------------------------------------------+ | + | ... further data records ... | + +-------------------------------------------------------------------------+ + </programlisting> + </refsect1> + + <refsect1> + <title>Message payload</title> + + <para> + When connecting to the bus, receivers request a memory pool of a given + size, large enough to carry all backlog of data enqueued for the + connection. The pool is internally backed by a shared memory file which + can be <function>mmap()</function>ed by the receiver. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information. + </para> + + <para> + Message payload must be described in items attached to a message when + it is sent. A receiver can access the payload by looking at the items + that are attached to a message in its pool. The following items are used. + </para> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_PAYLOAD_VEC</constant></term> + <listitem> + <para> + This item references a piece of memory on the sender side which is + directly copied into the receiver's pool. This way, two peers can + exchange data by effectively doing a single-copy from one process + to another; the kernel will not buffer the data anywhere else. + This item is never found in a message received by a connection. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_PAYLOAD_OFF</constant></term> + <listitem> + <para> + This item is attached to messages on the receiving side and points + to a memory area inside the receiver's pool. The + <varname>offset</varname> variable in the item denotes the memory + location relative to the message itself. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_PAYLOAD_MEMFD</constant></term> + <listitem> + <para> + Messages can reference <emphasis>memfd</emphasis> files which + contain the data. memfd files are tmpfs-backed files that allow + sealing of the content of the file, which prevents all writable + access to the file content. + </para> + <para> + Only memfds that have + <constant>(F_SEAL_SHRINK|F_SEAL_GROW|F_SEAL_WRITE|F_SEAL_SEAL) + </constant> + set are accepted as payload data, which enforces reliable passing of + data. The receiver can assume that neither the sender nor anyone + else can alter the content after the message is sent. If those + seals are not set on the memfd, the ioctl will fail with + <errorcode>-1</errorcode>, and <varname>errno</varname> will be + set to <constant>ETXTBUSY</constant>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_FDS</constant></term> + <listitem> + <para> + Messages can transport regular file descriptors via + <constant>KDBUS_ITEM_FDS</constant>. This item carries an array + of <type>int</type> values in <varname>item.fd</varname>. The + maximum number of file descriptors in the item is + <constant>253</constant>, and only one item of this type is + accepted per message. All passed values must be valid file + descriptors; the open count of each file descriptors is increased + by installing it to the receiver's task. This item can only be + used for directed messages, not for broadcasts, and only to + remote peers that have opted-in for receiving file descriptors + at connection time (<constant>KDBUS_HELLO_ACCEPT_FD</constant>). + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + The sender must not make any assumptions on the type in which data is + received by the remote peer. The kernel is free to re-pack multiple + <constant>KDBUS_ITEM_PAYLOAD_VEC</constant> and + <constant>KDBUS_ITEM_PAYLOAD_MEMFD</constant> payloads. For instance, the + kernel may decide to merge multiple <constant>VECs</constant> into a + single <constant>VEC</constant>, inline <constant>MEMFD</constant> + payloads into memory, or merge all passed <constant>VECs</constant> into a + single <constant>MEMFD</constant>. However, the kernel preserves the order + of passed data. This means that the order of all <constant>VEC</constant> + and <constant>MEMFD</constant> items is not changed in respect to each + other. In other words: All passed <constant>VEC</constant> and + <constant>MEMFD</constant> data payloads are treated as a single stream + of data that may be received by the remote peer in a different set of + chunks than it was sent as. + </para> + </refsect1> + + <refsect1> + <title>Sending messages</title> + + <para> + Messages are passed to the kernel with the + <constant>KDBUS_CMD_SEND</constant> ioctl. Depending on the destination + address of the message, the kernel delivers the message to the specific + destination connection, or to some subset of all connections on the same + bus. Sending messages across buses is not possible. Messages are always + queued in the memory pool of the destination connection (see above). + </para> + + <para> + The <constant>KDBUS_CMD_SEND</constant> ioctl uses a + <type>struct kdbus_cmd_send</type> to describe the message + transfer. + </para> + <programlisting> +struct kdbus_cmd_send { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 msg_address; + struct kdbus_msg_info reply; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para>Flags for message delivery</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_SEND_SYNC_REPLY</constant></term> + <listitem> + <para> + By default, all calls to kdbus are considered asynchronous, + non-blocking. However, as there are many use cases that need + to wait for a remote peer to answer a method call, there's a + way to send a message and wait for a reply in a synchronous + fashion. This is what the + <constant>KDBUS_SEND_SYNC_REPLY</constant> controls. The + <constant>KDBUS_CMD_SEND</constant> ioctl will block until the + reply has arrived, the timeout limit is reached, in case the + remote connection was shut down, or if interrupted by a signal + before any reply; see + <citerefentry> + <refentrytitle>signal</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + + The offset of the reply message in the sender's pool is stored + in <varname>reply</varname> when the ioctl has returned without + error. Hence, there is no need for another + <constant>KDBUS_CMD_RECV</constant> ioctl or anything else to + receive the reply. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Request a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will fail with + <errorcode>-1</errorcode>, <varname>errno</varname> + is set to <constant>EPROTO</constant>. + Once the ioctl returned, the <varname>flags</varname> + field will have all bits set that the kernel recognizes as + valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>msg_address</varname></term> + <listitem><para> + In this field, users have to provide a pointer to a message + (<type>struct kdbus_msg</type>) to send. See below for a + detailed description. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>reply</varname></term> + <listitem><para> + Only used for synchronous replies. See description of + <type>struct kdbus_cmd_recv</type> for more details. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + The following items are currently recognized. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_CANCEL_FD</constant></term> + <listitem> + <para> + When this optional item is passed in, and the call is + executed as SYNC call, the passed in file descriptor can be + used as alternative cancellation point. The kernel will call + <citerefentry> + <refentrytitle>poll</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + on this file descriptor, and once it reports any incoming + bytes, the blocking send operation will be canceled; the + blocking, synchronous ioctl call will return + <errorcode>-1</errorcode>, and <varname>errno</varname> will + be set to <errorname>ECANCELED</errorname>. + Any type of file descriptor on which + <citerefentry> + <refentrytitle>poll</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + can be called on can be used as payload to this item; for + example, an eventfd can be used for this purpose, see + <citerefentry> + <refentrytitle>eventfd</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry>. + For asynchronous message sending, this item is allowed but + ignored. + </para> + </listitem> + </varlistentry> + </variablelist> + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + The message referenced by the <varname>msg_address</varname> above has + the following layout. + </para> + + <programlisting> +struct kdbus_msg { + __u64 size; + __u64 flags; + __s64 priority; + __u64 dst_id; + __u64 src_id; + __u64 payload_type; + __u64 cookie; + __u64 timeout_ns; + __u64 cookie_reply; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para>Flags to describe message details.</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_MSG_EXPECT_REPLY</constant></term> + <listitem> + <para> + Expect a reply to this message from the remote peer. With + this bit set, the timeout_ns field must be set to a non-zero + number of nanoseconds in which the receiving peer is expected + to reply. If such a reply is not received in time, the sender + will be notified with a timeout message (see below). The + value must be an absolute value, in nanoseconds and based on + <constant>CLOCK_MONOTONIC</constant>. + </para><para> + For a message to be accepted as reply, it must be a direct + message to the original sender (not a broadcast and not a + signal message), and its + <varname>kdbus_msg.cookie_reply</varname> must match the + previous message's <varname>kdbus_msg.cookie</varname>. + </para><para> + Expected replies also temporarily open the policy of the + sending connection, so the other peer is allowed to respond + within the given time window. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_MSG_NO_AUTO_START</constant></term> + <listitem> + <para> + By default, when a message is sent to an activator + connection, the activator is notified and will start an + implementer. This flag inhibits that behavior. With this bit + set, and the remote being an activator, the ioctl will fail + with <varname>errno</varname> set to + <constant>EADDRNOTAVAIL</constant>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Requests a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will return + <errorcode>0</errorcode>, and the <varname>flags</varname> + field will have all bits set that are valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>priority</varname></term> + <listitem><para> + The priority of this message. Receiving messages (see below) may + optionally be constrained to messages of a minimal priority. This + allows for use cases where timing critical data is interleaved with + control data on the same connection. If unused, the priority field + should be set to <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>dst_id</varname></term> + <listitem><para> + The numeric ID of the destination connection, or + <constant>KDBUS_DST_ID_BROADCAST</constant> + (~0ULL) to address every peer on the bus, or + <constant>KDBUS_DST_ID_NAME</constant> (0) to look + it up dynamically from the bus' name registry. + In the latter case, an item of type + <constant>KDBUS_ITEM_DST_NAME</constant> is mandatory. + Also see + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + . + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>src_id</varname></term> + <listitem><para> + Upon return of the ioctl, this member will contain the sending + connection's numerical ID. Should be 0 at send time. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>payload_type</varname></term> + <listitem><para> + Type of the payload in the actual data records. Currently, only + <constant>KDBUS_PAYLOAD_DBUS</constant> is accepted as input value + of this field. When receiving messages that are generated by the + kernel (notifications), this field will contain + <constant>KDBUS_PAYLOAD_KERNEL</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>cookie</varname></term> + <listitem><para> + Cookie of this message, for later recognition. Also, when replying + to a message (see above), the <varname>cookie_reply</varname> + field must match this value. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>timeout_ns</varname></term> + <listitem><para> + If the message sent requires a reply from the remote peer (see above), + this field contains the timeout in absolute nanoseconds based on + <constant>CLOCK_MONOTONIC</constant>. Also see + <citerefentry> + <refentrytitle>clock_gettime</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>cookie_reply</varname></term> + <listitem><para> + If the message sent is a reply to another message, this field must + match the cookie of the formerly received message. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + A dynamically sized list of items to contain additional information. + The following items are expected/valid: + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_ITEM_PAYLOAD_VEC</constant></term> + <term><constant>KDBUS_ITEM_PAYLOAD_MEMFD</constant></term> + <term><constant>KDBUS_ITEM_FDS</constant></term> + <listitem> + <para> + Actual data records containing the payload. See section + "Message payload". + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_BLOOM_FILTER</constant></term> + <listitem> + <para> + Bloom filter for matches (see below). + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ITEM_DST_NAME</constant></term> + <listitem> + <para> + Well-known name to send this message to. Required if + <varname>dst_id</varname> is set to + <constant>KDBUS_DST_ID_NAME</constant>. + If a connection holding the given name can't be found, + the ioctl will fail with <varname>errno</varname> set to + <constant>ESRCH</constant> is returned. + </para> + <para> + For messages to a unique name (ID), this item is optional. If + present, the kernel will make sure the name owner matches the + given unique name. This allows programs to tie the message + sending to the condition that a name is currently owned by a + certain unique name. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + </variablelist> + + <para> + The message will be augmented by the requested metadata items when + queued into the receiver's pool. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + and + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on metadata. + </para> + </refsect1> + + <refsect1> + <title>Receiving messages</title> + + <para> + Messages are received by the client with the + <constant>KDBUS_CMD_RECV</constant> ioctl. The endpoint file of the bus + supports <function>poll()/epoll()/select()</function>; when new messages + are available on the connection's file descriptor, + <constant>POLLIN</constant> is reported. For compatibility reasons, + <constant>POLLOUT</constant> is always reported as well. Note, however, + that the latter does not guarantee that a message can in fact be sent, as + this depends on how many pending messages the receiver has in its pool. + </para> + + <para> + With the <constant>KDBUS_CMD_RECV</constant> ioctl, a + <type>struct kdbus_cmd_recv</type> is used. + </para> + + <programlisting> +struct kdbus_cmd_recv { + __u64 size; + __u64 flags; + __u64 return_flags; + __s64 priority; + __u64 dropped_msgs; + struct kdbus_msg_info msg; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para>Flags to control the receive command.</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_RECV_PEEK</constant></term> + <listitem> + <para> + Just return the location of the next message. Do not install + file descriptors or anything else. This is usually used to + determine the sender of the next queued message. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_RECV_DROP</constant></term> + <listitem> + <para> + Drop the next message without doing anything else with it, + and free the pool slice. This a short-cut for + <constant>KDBUS_RECV_PEEK</constant> and + <constant>KDBUS_CMD_FREE</constant>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_RECV_USE_PRIORITY</constant></term> + <listitem> + <para> + Dequeue the messages ordered by their priority, and filtering + them with the priority field (see below). + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Request a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will fail with + <errorcode>-1</errorcode>, <varname>errno</varname> + is set to <constant>EPROTO</constant>. + Once the ioctl returned, the <varname>flags</varname> + field will have all bits set that the kernel recognizes as + valid for this command. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. If the <varname>dropped_msgs</varname> + field is non-zero, <constant>KDBUS_RECV_RETURN_DROPPED_MSGS</constant> + is set. If a file descriptor could not be installed, the + <constant>KDBUS_RECV_RETURN_INCOMPLETE_FDS</constant> flag is set. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>priority</varname></term> + <listitem><para> + With <constant>KDBUS_RECV_USE_PRIORITY</constant> set in + <varname>flags</varname>, messages will be dequeued ordered by their + priority, starting with the highest value. Also, messages will be + filtered by the value given in this field, so the returned message + will at least have the requested priority. If no such message is + waiting in the queue, the ioctl will fail, and + <varname>errno</varname> will be set to <constant>EAGAIN</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>dropped_msgs</varname></term> + <listitem><para> + Whenever a message with <constant>KDBUS_MSG_SIGNAL</constant> is sent + but cannot be queued on a peer (e.g., as it contains FDs but the peer + does not support FDs, or there is no space left in the peer's pool) + the 'dropped_msgs' counter of the peer is incremented. On the next + RECV ioctl, the 'dropped_msgs' field is copied into the ioctl struct + and cleared on the peer. If it was non-zero, the + <constant>KDBUS_RECV_RETURN_DROPPED_MSGS</constant> flag will be set + in <varname>return_flags</varname>. Note that this will only happen + if the ioctl succeeded or failed with <constant>EAGAIN</constant>. In + other error cases, the 'dropped_msgs' field of the peer is left + untouched. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>msg</varname></term> + <listitem><para> + Embedded struct containing information on the received message when + this command succeeded (see below). + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem><para> + Items to specify further details for the receive command. + Currently unused, and all items will be rejected with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + Both <type>struct kdbus_cmd_recv</type> and + <type>struct kdbus_cmd_send</type> embed + <type>struct kdbus_msg_info</type>. + For the <constant>KDBUS_CMD_SEND</constant> ioctl, it is used to catch + synchronous replies, if one was requested, and is unused otherwise. + </para> + + <programlisting> +struct kdbus_msg_info { + __u64 offset; + __u64 msg_size; + __u64 return_flags; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>offset</varname></term> + <listitem><para> + Upon return of the ioctl, this field contains the offset in the + receiver's memory pool. The memory must be freed with + <constant>KDBUS_CMD_FREE</constant>. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further details. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>msg_size</varname></term> + <listitem><para> + Upon successful return of the ioctl, this field contains the size of + the allocated slice at offset <varname>offset</varname>. + It is the combination of the size of the stored + <type>struct kdbus_msg</type> object plus all appended VECs. + You can use it in combination with <varname>offset</varname> to map + a single message, instead of mapping the entire pool. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for further details. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem> + <para> + Kernel-provided return flags. Currently, the following flags are + defined. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_RECV_RETURN_INCOMPLETE_FDS</constant></term> + <listitem> + <para> + The message contained memfds or file descriptors, and the + kernel failed to install one or more of them at receive time. + Most probably that happened because the maximum number of + file descriptors for the receiver's task were exceeded. + In such cases, the message is still delivered, so this is not + a fatal condition. File descriptors numbers inside the + <constant>KDBUS_ITEM_FDS</constant> item or memfd files + referenced by <constant>KDBUS_ITEM_PAYLOAD_MEMFD</constant> + items which could not be installed will be set to + <constant>-1</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + </variablelist> + + <para> + Unless <constant>KDBUS_RECV_DROP</constant> was passed, the + <varname>offset</varname> field contains the location of the new message + inside the receiver's pool after the <constant>KDBUS_CMD_RECV</constant> + ioctl was employed. The message is stored as <type>struct kdbus_msg</type> + at this offset, and can be interpreted with the semantics described above. + </para> + <para> + Also, if the connection allowed for file descriptor to be passed + (<constant>KDBUS_HELLO_ACCEPT_FD</constant>), and if the message contained + any, they will be installed into the receiving process when the + <constant>KDBUS_CMD_RECV</constant> ioctl is called. + <emphasis>memfds</emphasis> may always be part of the message payload. + The receiving task is obliged to close all file descriptors appropriately + once no longer needed. If <constant>KDBUS_RECV_PEEK</constant> is set, no + file descriptors are installed. This allows for peeking at a message, + looking at its metadata only and dropping it via + <constant>KDBUS_RECV_DROP</constant>, without installing any of the file + descriptors into the receiving process. + </para> + <para> + The caller is obliged to call the <constant>KDBUS_CMD_FREE</constant> + ioctl with the returned offset when the memory is no longer needed. + </para> + </refsect1> + + <refsect1> + <title>Notifications</title> + <para> + A kernel notification is a regular kdbus message with the following + details. + </para> + + <itemizedlist> + <listitem><para> + kdbus_msg.src_id == <constant>KDBUS_SRC_ID_KERNEL</constant> + </para></listitem> + <listitem><para> + kdbus_msg.dst_id == <constant>KDBUS_DST_ID_BROADCAST</constant> + </para></listitem> + <listitem><para> + kdbus_msg.payload_type == <constant>KDBUS_PAYLOAD_KERNEL</constant> + </para></listitem> + <listitem><para> + Has exactly one of the items attached that are described below. + </para></listitem> + <listitem><para> + Always has a timestamp item (<constant>KDBUS_ITEM_TIMESTAMP</constant>) + attached. + </para></listitem> + </itemizedlist> + + <para> + The kernel will notify its users of the following events. + </para> + + <itemizedlist> + <listitem><para> + When connection <emphasis>A</emphasis> is terminated while connection + <emphasis>B</emphasis> is waiting for a reply from it, connection + <emphasis>B</emphasis> is notified with a message with an item of + type <constant>KDBUS_ITEM_REPLY_DEAD</constant>. + </para></listitem> + + <listitem><para> + When connection <emphasis>A</emphasis> does not receive a reply from + connection <emphasis>B</emphasis> within the specified timeout window, + connection <emphasis>A</emphasis> will receive a message with an + item of type <constant>KDBUS_ITEM_REPLY_TIMEOUT</constant>. + </para></listitem> + + <listitem><para> + When an ordinary connection (not a monitor) is created on or removed + from a bus, messages with an item of type + <constant>KDBUS_ITEM_ID_ADD</constant> or + <constant>KDBUS_ITEM_ID_REMOVE</constant>, respectively, are delivered + to all bus members that match these messages through their match + database. Eavesdroppers (monitor connections) do not cause such + notifications to be sent. They are invisible on the bus. + </para></listitem> + + <listitem><para> + When a connection gains or loses ownership of a name, messages with an + item of type <constant>KDBUS_ITEM_NAME_ADD</constant>, + <constant>KDBUS_ITEM_NAME_REMOVE</constant> or + <constant>KDBUS_ITEM_NAME_CHANGE</constant> are delivered to all bus + members that match these messages through their match database. + </para></listitem> + </itemizedlist> + </refsect1> + + <refsect1> + <title>Return value</title> + <para> + On success, all mentioned ioctl commands return <errorcode>0</errorcode>; + on error, <errorcode>-1</errorcode> is returned, and + <varname>errno</varname> is set to indicate the error. + If the issued ioctl is illegal for the file descriptor used, + <varname>errno</varname> will be set to <constant>ENOTTY</constant>. + </para> + + <refsect2> + <title> + <constant>KDBUS_CMD_SEND</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EOPNOTSUPP</constant></term> + <listitem><para> + The connection is not an ordinary connection, or the passed + file descriptors in <constant>KDBUS_ITEM_FDS</constant> item are + either kdbus handles or unix domain sockets. Both are currently + unsupported. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + The submitted payload type is + <constant>KDBUS_PAYLOAD_KERNEL</constant>, + <constant>KDBUS_MSG_EXPECT_REPLY</constant> was set without timeout + or cookie values, <constant>KDBUS_SEND_SYNC_REPLY</constant> was + set without <constant>KDBUS_MSG_EXPECT_REPLY</constant>, an invalid + item was supplied, <constant>src_id</constant> was non-zero and was + different from the current connection's ID, a supplied memfd had a + size of 0, or a string was not properly null-terminated. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ENOTUNIQ</constant></term> + <listitem><para> + The supplied destination is + <constant>KDBUS_DST_ID_BROADCAST</constant> and either + file descriptors were passed, or + <constant>KDBUS_MSG_EXPECT_REPLY</constant> was set, + or a timeout was given. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>E2BIG</constant></term> + <listitem><para> + Too many items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EMSGSIZE</constant></term> + <listitem><para> + The size of the message header and items or the payload vector + is excessive. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EEXIST</constant></term> + <listitem><para> + Multiple <constant>KDBUS_ITEM_FDS</constant>, + <constant>KDBUS_ITEM_BLOOM_FILTER</constant> or + <constant>KDBUS_ITEM_DST_NAME</constant> items were supplied. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EBADF</constant></term> + <listitem><para> + The supplied <constant>KDBUS_ITEM_FDS</constant> or + <constant>KDBUS_ITEM_PAYLOAD_MEMFD</constant> items + contained an illegal file descriptor. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EMEDIUMTYPE</constant></term> + <listitem><para> + The supplied memfd is not a sealed kdbus memfd. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EMFILE</constant></term> + <listitem><para> + Too many file descriptors inside a + <constant>KDBUS_ITEM_FDS</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EBADMSG</constant></term> + <listitem><para> + An item had illegal size, both a <constant>dst_id</constant> and a + <constant>KDBUS_ITEM_DST_NAME</constant> was given, or both a name + and a bloom filter was given. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ETXTBSY</constant></term> + <listitem><para> + The supplied kdbus memfd file cannot be sealed or the seal + was removed, because it is shared with other processes or + still mapped with + <citerefentry> + <refentrytitle>mmap</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ECOMM</constant></term> + <listitem><para> + A peer does not accept the file descriptors addressed to it. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EFAULT</constant></term> + <listitem><para> + The supplied bloom filter size was not 64-bit aligned, or supplied + memory could not be accessed by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EDOM</constant></term> + <listitem><para> + The supplied bloom filter size did not match the bloom filter + size of the bus. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EDESTADDRREQ</constant></term> + <listitem><para> + <constant>dst_id</constant> was set to + <constant>KDBUS_DST_ID_NAME</constant>, but no + <constant>KDBUS_ITEM_DST_NAME</constant> was attached. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ESRCH</constant></term> + <listitem><para> + The name to look up was not found in the name registry. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EADDRNOTAVAIL</constant></term> + <listitem><para> + <constant>KDBUS_MSG_NO_AUTO_START</constant> was given but the + destination connection is an activator. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ENXIO</constant></term> + <listitem><para> + The passed numeric destination connection ID couldn't be found, + or is not connected. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ECONNRESET</constant></term> + <listitem><para> + The destination connection is no longer active. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ETIMEDOUT</constant></term> + <listitem><para> + Timeout while synchronously waiting for a reply. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINTR</constant></term> + <listitem><para> + Interrupted system call while synchronously waiting for a reply. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EPIPE</constant></term> + <listitem><para> + When sending a message, a synchronous reply from the receiving + connection was expected but the connection died before answering. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ENOBUFS</constant></term> + <listitem><para> + Too many pending messages on the receiver side. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EREMCHG</constant></term> + <listitem><para> + Both a well-known name and a unique name (ID) was given, but + the name is not currently owned by that connection. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EXFULL</constant></term> + <listitem><para> + The memory pool of the receiver is full. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EREMOTEIO</constant></term> + <listitem><para> + While synchronously waiting for a reply, the remote peer + failed with an I/O error. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_RECV</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EOPNOTSUPP</constant></term> + <listitem><para> + The connection is not an ordinary connection, or the passed + file descriptors are either kdbus handles or unix domain + sockets. Both are currently unsupported. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Invalid flags or offset. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EAGAIN</constant></term> + <listitem><para> + No message found in the queue. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>clock_gettime</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>ioctl</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>poll</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>select</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>epoll</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>eventfd</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>memfd_create</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.name.xml b/ipc/kdbus/Documentation/kdbus.name.xml new file mode 100644 index 000000000000..3f5f6a6c5ed6 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.name.xml @@ -0,0 +1,711 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.name"> + + <refentryinfo> + <title>kdbus.name</title> + <productname>kdbus.name</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.name</refname> + <refpurpose>kdbus.name</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + <para> + Each + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + instantiates a name registry to resolve well-known names into unique + connection IDs for message delivery. The registry will be queried when a + message is sent with <varname>kdbus_msg.dst_id</varname> set to + <constant>KDBUS_DST_ID_NAME</constant>, or when a registry dump is + requested with <constant>KDBUS_CMD_NAME_LIST</constant>. + </para> + + <para> + All of the below is subject to policy rules for <emphasis>SEE</emphasis> + and <emphasis>OWN</emphasis> permissions. See + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information. + </para> + </refsect1> + + <refsect1> + <title>Name validity</title> + <para> + A name has to comply with the following rules in order to be considered + valid. + </para> + + <itemizedlist> + <listitem> + <para> + The name has two or more elements separated by a + '<literal>.</literal>' (period) character. + </para> + </listitem> + <listitem> + <para> + All elements must contain at least one character. + </para> + </listitem> + <listitem> + <para> + Each element must only contain the ASCII characters + <literal>[A-Z][a-z][0-9]_</literal> and must not begin with a + digit. + </para> + </listitem> + <listitem> + <para> + The name must contain at least one '<literal>.</literal>' (period) + character (and thus at least two elements). + </para> + </listitem> + <listitem> + <para> + The name must not begin with a '<literal>.</literal>' (period) + character. + </para> + </listitem> + <listitem> + <para> + The name must not exceed <constant>255</constant> characters in + length. + </para> + </listitem> + </itemizedlist> + </refsect1> + + <refsect1> + <title>Acquiring a name</title> + <para> + To acquire a name, a client uses the + <constant>KDBUS_CMD_NAME_ACQUIRE</constant> ioctl with + <type>struct kdbus_cmd</type> as argument. + </para> + + <programlisting> +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para>Flags to control details in the name acquisition.</para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_NAME_REPLACE_EXISTING</constant></term> + <listitem> + <para> + Acquiring a name that is already present usually fails, + unless this flag is set in the call, and + <constant>KDBUS_NAME_ALLOW_REPLACEMENT</constant> (see below) + was set when the current owner of the name acquired it, or + if the current owner is an activator connection (see + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>). + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_NAME_ALLOW_REPLACEMENT</constant></term> + <listitem> + <para> + Allow other connections to take over this name. When this + happens, the former owner of the connection will be notified + of the name loss. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_NAME_QUEUE</constant></term> + <listitem> + <para> + A name that is already acquired by a connection can not be + acquired again (unless the + <constant>KDBUS_NAME_ALLOW_REPLACEMENT</constant> flag was + set during acquisition; see above). + However, a connection can put itself in a queue of + connections waiting for the name to be released. Once that + happens, the first connection in that queue becomes the new + owner and is notified accordingly. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Request a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will fail with + <errorcode>-1</errorcode>, and <varname>errno</varname> + is set to <constant>EPROTO</constant>. + Once the ioctl returned, the <varname>flags</varname> + field will have all bits set that the kernel recognizes as + valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem> + <para> + Flags returned by the kernel. Currently, the following may be + returned by the kernel. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_NAME_IN_QUEUE</constant></term> + <listitem> + <para> + The name was not acquired yet, but the connection was + placed in the queue of peers waiting for the name. + This can only happen if <constant>KDBUS_NAME_QUEUE</constant> + was set in the <varname>flags</varname> member (see above). + The connection will receive a name owner change notification + once the current owner has given up the name and its + ownership was transferred. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Items to submit the name. Currently, one item of type + <constant>KDBUS_ITEM_NAME</constant> is expected and allowed, and + the contained string must be a valid bus name. + <constant>KDBUS_ITEM_NEGOTIATE</constant> may be used to probe for + valid item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for a detailed description of how this item is used. + </para> + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <errorname>>EINVAL</errorname>. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Releasing a name</title> + <para> + A connection may release a name explicitly with the + <constant>KDBUS_CMD_NAME_RELEASE</constant> ioctl. If the connection was + an implementer of an activatable name, its pending messages are moved + back to the activator. If there are any connections queued up as waiters + for the name, the first one in the queue (the oldest entry) will become + the new owner. The same happens implicitly for all names once a + connection terminates. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on connections. + </para> + <para> + The <constant>KDBUS_CMD_NAME_RELEASE</constant> ioctl uses the same data + structure as the acquisition call + (<constant>KDBUS_CMD_NAME_ACQUIRE</constant>), + but with slightly different field usage. + </para> + + <programlisting> +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + Flags to the command. Currently unused. + <constant>KDBUS_FLAG_NEGOTIATE</constant> is accepted to probe for + valid flags. If set, the ioctl will return <errorcode>0</errorcode>, + and the <varname>flags</varname> field is set to + <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Items to submit the name. Currently, one item of type + <constant>KDBUS_ITEM_NAME</constant> is expected and allowed, and + the contained string must be a valid bus name. + <constant>KDBUS_ITEM_NEGOTIATE</constant> may be used to probe for + valid item types. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for a detailed description of how this item is used. + </para> + <para> + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Dumping the name registry</title> + <para> + A connection may request a complete or filtered dump of currently active + bus names with the <constant>KDBUS_CMD_LIST</constant> ioctl, which + takes a <type>struct kdbus_cmd_list</type> as argument. + </para> + + <programlisting> +struct kdbus_cmd_list { + __u64 flags; + __u64 return_flags; + __u64 offset; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>flags</varname></term> + <listitem> + <para> + Any combination of flags to specify which names should be dumped. + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_LIST_UNIQUE</constant></term> + <listitem> + <para> + List the unique (numeric) IDs of the connection, whether it + owns a name or not. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_LIST_NAMES</constant></term> + <listitem> + <para> + List well-known names stored in the database which are + actively owned by a real connection (not an activator). + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_LIST_ACTIVATORS</constant></term> + <listitem> + <para> + List names that are owned by an activator. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_LIST_QUEUED</constant></term> + <listitem> + <para> + List connections that are not yet owning a name but are + waiting for it to become available. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Request a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will fail with + <errorcode>-1</errorcode>, and <varname>errno</varname> + is set to <constant>EPROTO</constant>. + Once the ioctl returned, the <varname>flags</varname> + field will have all bits set that the kernel recognizes as + valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>offset</varname></term> + <listitem><para> + When the ioctl returns successfully, the offset to the name registry + dump inside the connection's pool will be stored in this field. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + The returned list of names is stored in a <type>struct kdbus_list</type> + that in turn contains an array of type <type>struct kdbus_info</type>, + The array-size in bytes is given as <varname>list_size</varname>. + The fields inside <type>struct kdbus_info</type> is described next. + </para> + + <programlisting> +struct kdbus_info { + __u64 size; + __u64 id; + __u64 flags; + struct kdbus_item items[0]; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>id</varname></term> + <listitem><para> + The owning connection's unique ID. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + The flags of the owning connection. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem> + <para> + Items containing the actual name. Currently, one item of type + <constant>KDBUS_ITEM_OWNED_NAME</constant> will be attached, + including the name's flags. In that item, the flags field of the + name may carry the following bits: + </para> + <variablelist> + <varlistentry> + <term><constant>KDBUS_NAME_ALLOW_REPLACEMENT</constant></term> + <listitem> + <para> + Other connections are allowed to take over this name from the + connection that owns it. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_NAME_IN_QUEUE</constant></term> + <listitem> + <para> + When retrieving a list of currently acquired names in the + registry, this flag indicates whether the connection + actually owns the name or is currently waiting for it to + become available. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_NAME_ACTIVATOR</constant></term> + <listitem> + <para> + An activator connection owns a name as a placeholder for an + implementer, which is started on demand by programs as soon + as the first message arrives. There's some more information + on this topic in + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + . + </para> + <para> + In contrast to + <constant>KDBUS_NAME_REPLACE_EXISTING</constant>, + when a name is taken over from an activator connection, all + the messages that have been queued in the activator + connection will be moved over to the new owner. The activator + connection will still be tracked for the name and will take + control again if the implementer connection terminates. + </para> + <para> + This flag can not be used when acquiring a name, but is + implicitly set through <constant>KDBUS_CMD_HELLO</constant> + with <constant>KDBUS_HELLO_ACTIVATOR</constant> set in + <varname>kdbus_cmd_hello.conn_flags</varname>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_FLAG_NEGOTIATE</constant></term> + <listitem> + <para> + Requests a set of valid flags for this ioctl. When this bit is + set, no action is taken; the ioctl will return + <errorcode>0</errorcode>, and the <varname>flags</varname> + field will have all bits set that are valid for this command. + The <constant>KDBUS_FLAG_NEGOTIATE</constant> bit will be + cleared by the operation. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + </variablelist> + + <para> + The returned buffer must be freed with the + <constant>KDBUS_CMD_FREE</constant> ioctl when the user is finished with + it. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information. + </para> + </refsect1> + + <refsect1> + <title>Return value</title> + <para> + On success, all mentioned ioctl commands return <errorcode>0</errorcode>; + on error, <errorcode>-1</errorcode> is returned, and + <varname>errno</varname> is set to indicate the error. + If the issued ioctl is illegal for the file descriptor used, + <varname>errno</varname> will be set to <constant>ENOTTY</constant>. + </para> + + <refsect2> + <title> + <constant>KDBUS_CMD_NAME_ACQUIRE</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Illegal command flags, illegal name provided, or an activator + tried to acquire a second name. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EPERM</constant></term> + <listitem><para> + Policy prohibited name ownership. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EALREADY</constant></term> + <listitem><para> + Connection already owns that name. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EEXIST</constant></term> + <listitem><para> + The name already exists and can not be taken over. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>E2BIG</constant></term> + <listitem><para> + The maximum number of well-known names per connection is exhausted. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_NAME_RELEASE</constant> + may fail with the following errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Invalid command flags, or invalid name provided. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ESRCH</constant></term> + <listitem><para> + Name is not found in the registry. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EADDRINUSE</constant></term> + <listitem><para> + Name is owned by a different connection and can't be released. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title> + <constant>KDBUS_CMD_LIST</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Invalid command flags + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>ENOBUFS</constant></term> + <listitem><para> + No available memory in the connection's pool. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.policy.xml b/ipc/kdbus/Documentation/kdbus.policy.xml new file mode 100644 index 000000000000..67324163880a --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.policy.xml @@ -0,0 +1,406 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.policy"> + + <refentryinfo> + <title>kdbus.policy</title> + <productname>kdbus.policy</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.policy</refname> + <refpurpose>kdbus policy</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + + <para> + A kdbus policy restricts the possibilities of connections to own, see and + talk to well-known names. A policy can be associated with a bus (through a + policy holder connection) or a custom endpoint. kdbus stores its policy + information in a database that can be accessed through the following + ioctl commands: + </para> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_CMD_HELLO</constant></term> + <listitem><para> + When creating, or updating, a policy holder connection. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_CMD_ENDPOINT_MAKE</constant></term> + <term><constant>KDBUS_CMD_ENDPOINT_UPDATE</constant></term> + <listitem><para> + When creating, or updating, a bus custom endpoint. See + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + In all cases, the name and policy access information is stored in items + of type <constant>KDBUS_ITEM_NAME</constant> and + <constant>KDBUS_ITEM_POLICY_ACCESS</constant>. For this transport, the + following rules apply. + </para> + + <itemizedlist> + <listitem> + <para> + An item of type <constant>KDBUS_ITEM_NAME</constant> must be followed + by at least one <constant>KDBUS_ITEM_POLICY_ACCESS</constant> item. + </para> + </listitem> + + <listitem> + <para> + An item of type <constant>KDBUS_ITEM_NAME</constant> can be followed + by an arbitrary number of + <constant>KDBUS_ITEM_POLICY_ACCESS</constant> items. + </para> + </listitem> + + <listitem> + <para> + An arbitrary number of groups of names and access levels can be given. + </para> + </listitem> + </itemizedlist> + + <para> + Names passed in items of type <constant>KDBUS_ITEM_NAME</constant> must + comply to the rules of valid kdbus.name. See + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information. + + The payload of an item of type + <constant>KDBUS_ITEM_POLICY_ACCESS</constant> is defined by the following + struct. For more information on the layout of items, please refer to + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para> + + <programlisting> +struct kdbus_policy_access { + __u64 type; + __u64 access; + __u64 id; +}; + </programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>type</varname></term> + <listitem> + <para> + One of the following. + </para> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_POLICY_ACCESS_USER</constant></term> + <listitem><para> + Grant access to a user with the UID stored in the + <varname>id</varname> field. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_POLICY_ACCESS_GROUP</constant></term> + <listitem><para> + Grant access to a user with the GID stored in the + <varname>id</varname> field. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_POLICY_ACCESS_WORLD</constant></term> + <listitem><para> + Grant access to everyone. The <varname>id</varname> field + is ignored. + </para></listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>access</varname></term> + <listitem> + <para> + The access to grant. One of the following. + </para> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_POLICY_SEE</constant></term> + <listitem><para> + Allow the name to be seen. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_POLICY_TALK</constant></term> + <listitem><para> + Allow the name to be talked to. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_POLICY_OWN</constant></term> + <listitem><para> + Allow the name to be owned. + </para></listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>id</varname></term> + <listitem><para> + For <constant>KDBUS_POLICY_ACCESS_USER</constant>, stores the UID. + For <constant>KDBUS_POLICY_ACCESS_GROUP</constant>, stores the GID. + </para></listitem> + </varlistentry> + + </variablelist> + + <para> + All endpoints of buses have an empty policy database by default. + Therefore, unless policy rules are added, all operations will also be + denied by default. Also see + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para> + </refsect1> + + <refsect1> + <title>Wildcard names</title> + <para> + Policy holder connections may upload names that contain the wildcard + suffix (<literal>".*"</literal>). Such a policy entry is effective for + every well-known name that extends the provided name by exactly one more + level. + + For example, the name <literal>foo.bar.*</literal> matches both + <literal>"foo.bar.baz"</literal> and + <literal>"foo.bar.bazbaz"</literal> are, but not + <literal>"foo.bar.baz.baz"</literal>. + + This allows connections to take control over multiple names that the + policy holder doesn't need to know about when uploading the policy. + + Such wildcard entries are not allowed for custom endpoints. + </para> + </refsect1> + + <refsect1> + <title>Privileged connections</title> + <para> + The policy database is overruled when action is taken by a privileged + connection. Please refer to + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information on what makes a connection privileged. + </para> + </refsect1> + + <refsect1> + <title>Examples</title> + <para> + For instance, a set of policy rules may look like this: + </para> + + <programlisting> +KDBUS_ITEM_NAME: str='org.foo.bar' +KDBUS_ITEM_POLICY_ACCESS: type=USER, access=OWN, ID=1000 +KDBUS_ITEM_POLICY_ACCESS: type=USER, access=TALK, ID=1001 +KDBUS_ITEM_POLICY_ACCESS: type=WORLD, access=SEE + +KDBUS_ITEM_NAME: str='org.blah.baz' +KDBUS_ITEM_POLICY_ACCESS: type=USER, access=OWN, ID=0 +KDBUS_ITEM_POLICY_ACCESS: type=WORLD, access=TALK + </programlisting> + + <para> + That means that 'org.foo.bar' may only be owned by UID 1000, but every + user on the bus is allowed to see the name. However, only UID 1001 may + actually send a message to the connection and receive a reply from it. + + The second rule allows 'org.blah.baz' to be owned by UID 0 only, but + every user may talk to it. + </para> + </refsect1> + + <refsect1> + <title>TALK access and multiple well-known names per connection</title> + <para> + Note that TALK access is checked against all names of a connection. For + example, if a connection owns both <constant>'org.foo.bar'</constant> and + <constant>'org.blah.baz'</constant>, and the policy database allows + <constant>'org.blah.baz'</constant> to be talked to by WORLD, then this + permission is also granted to <constant>'org.foo.bar'</constant>. That + might sound illogical, but after all, we allow messages to be directed to + either the ID or a well-known name, and policy is applied to the + connection, not the name. In other words, the effective TALK policy for a + connection is the most permissive of all names the connection owns. + + For broadcast messages, the receiver needs TALK permissions to the sender + to receive the broadcast. + </para> + <para> + Both the endpoint and the bus policy databases are consulted to allow + name registry listing, owning a well-known name and message delivery. + If either one fails, the operation is failed with + <varname>errno</varname> set to <constant>EPERM</constant>. + + For best practices, connections that own names with a restricted TALK + access should not install matches. This avoids cases where the sent + message may pass the bloom filter due to false-positives and may also + satisfy the policy rules. + + Also see + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para> + </refsect1> + + <refsect1> + <title>Implicit policies</title> + <para> + Depending on the type of the endpoint, a set of implicit rules that + override installed policies might be enforced. + + On default endpoints, the following set is enforced and checked before + any user-supplied policy is checked. + </para> + + <itemizedlist> + <listitem> + <para> + Privileged connections always override any installed policy. Those + connections could easily install their own policies, so there is no + reason to enforce installed policies. + </para> + </listitem> + <listitem> + <para> + Connections can always talk to connections of the same user. This + includes broadcast messages. + </para> + </listitem> + </itemizedlist> + + <para> + Custom endpoints have stricter policies. The following rules apply: + </para> + + <itemizedlist> + <listitem> + <para> + Policy rules are always enforced, even if the connection is a + privileged connection. + </para> + </listitem> + <listitem> + <para> + Policy rules are always enforced for <constant>TALK</constant> access, + even if both ends are running under the same user. This includes + broadcast messages. + </para> + </listitem> + <listitem> + <para> + To restrict the set of names that can be seen, endpoint policies can + install <constant>SEE</constant> policies. + </para> + </listitem> + </itemizedlist> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.pool.xml b/ipc/kdbus/Documentation/kdbus.pool.xml new file mode 100644 index 000000000000..a9e16f196d39 --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.pool.xml @@ -0,0 +1,326 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus.pool"> + + <refentryinfo> + <title>kdbus.pool</title> + <productname>kdbus.pool</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus.pool</refname> + <refpurpose>kdbus pool</refpurpose> + </refnamediv> + + <refsect1> + <title>Description</title> + <para> + A pool for data received from the kernel is installed for every + <emphasis>connection</emphasis> of the <emphasis>bus</emphasis>, and + is sized according to the information stored in the + <varname>pool_size</varname> member of <type>struct kdbus_cmd_hello</type> + when <constant>KDBUS_CMD_HELLO</constant> is employed. Internally, the + pool is segmented into <emphasis>slices</emphasis>, each referenced by its + <emphasis>offset</emphasis> in the pool, expressed in <type>bytes</type>. + See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more information about <constant>KDBUS_CMD_HELLO</constant>. + </para> + + <para> + The pool is written to by the kernel when one of the following + <emphasis>ioctls</emphasis> is issued: + + <variablelist> + <varlistentry> + <term><constant>KDBUS_CMD_HELLO</constant></term> + <listitem><para> + ... to receive details about the bus the connection was made to + </para></listitem> + </varlistentry> + <varlistentry> + <term><constant>KDBUS_CMD_RECV</constant></term> + <listitem><para> + ... to receive a message + </para></listitem> + </varlistentry> + <varlistentry> + <term><constant>KDBUS_CMD_LIST</constant></term> + <listitem><para> + ... to dump the name registry + </para></listitem> + </varlistentry> + <varlistentry> + <term><constant>KDBUS_CMD_CONN_INFO</constant></term> + <listitem><para> + ... to retrieve information on a connection + </para></listitem> + </varlistentry> + <varlistentry> + <term><constant>KDBUS_CMD_BUS_CREATOR_INFO</constant></term> + <listitem><para> + ... to retrieve information about a connection's bus creator + </para></listitem> + </varlistentry> + </variablelist> + + </para> + <para> + The <varname>offset</varname> fields returned by either one of the + aforementioned ioctls describe offsets inside the pool. In order to make + the slice available for subsequent calls, + <constant>KDBUS_CMD_FREE</constant> has to be called on that offset + (see below). Otherwise, the pool will fill up, and the connection won't + be able to receive any more information through its pool. + </para> + </refsect1> + + <refsect1> + <title>Pool slice allocation</title> + <para> + Pool slices are allocated by the kernel in order to report information + back to a task, such as messages, returned name list etc. + Allocation of pool slices cannot be initiated by userspace. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + and + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for examples of commands that use the <emphasis>pool</emphasis> to + return data. + </para> + </refsect1> + + <refsect1> + <title>Accessing the pool memory</title> + <para> + Memory in the pool is read-only for userspace and may only be written + to by the kernel. To read from the pool memory, the caller is expected to + <citerefentry> + <refentrytitle>mmap</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + the buffer into its task, like this: + </para> + <programlisting> +uint8_t *buf = mmap(NULL, size, PROT_READ, MAP_SHARED, conn_fd, 0); + </programlisting> + + <para> + In order to map the entire pool, the <varname>size</varname> parameter in + the example above should be set to the value of the + <varname>pool_size</varname> member of + <type>struct kdbus_cmd_hello</type> when + <constant>KDBUS_CMD_HELLO</constant> was employed to create the + connection (see above). + </para> + + <para> + The <emphasis>file descriptor</emphasis> used to map the memory must be + the one that was used to create the <emphasis>connection</emphasis>. + In other words, the one that was used to call + <constant>KDBUS_CMD_HELLO</constant>. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + + <para> + Alternatively, instead of mapping the entire pool buffer, only parts + of it can be mapped. Every kdbus command that returns an + <emphasis>offset</emphasis> (see above) also reports a + <emphasis>size</emphasis> along with it, so programs can be written + in a way that it only maps portions of the pool to access a specific + <emphasis>slice</emphasis>. + </para> + + <para> + When access to the pool memory is no longer needed, programs should + call <function>munmap()</function> on the pointer returned by + <function>mmap()</function>. + </para> + </refsect1> + + <refsect1> + <title>Freeing pool slices</title> + <para> + The <constant>KDBUS_CMD_FREE</constant> ioctl is used to free a slice + inside the pool, describing an offset that was returned in an + <varname>offset</varname> field of another ioctl struct. + The <constant>KDBUS_CMD_FREE</constant> command takes a + <type>struct kdbus_cmd_free</type> as argument. + </para> + +<programlisting> +struct kdbus_cmd_free { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 offset; + struct kdbus_item items[0]; +}; +</programlisting> + + <para>The fields in this struct are described below.</para> + + <variablelist> + <varlistentry> + <term><varname>size</varname></term> + <listitem><para> + The overall size of the struct, including its items. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>flags</varname></term> + <listitem><para> + Currently unused. + <constant>KDBUS_FLAG_NEGOTIATE</constant> is accepted to probe for + valid flags. If set, the ioctl will return <errorcode>0</errorcode>, + and the <varname>flags</varname> field is set to + <constant>0</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>return_flags</varname></term> + <listitem><para> + Flags returned by the kernel. Currently unused and always set to + <constant>0</constant> by the kernel. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>offset</varname></term> + <listitem><para> + The offset to free, as returned by other ioctls that allocated + memory for returned information. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>items</varname></term> + <listitem><para> + Items to specify further details for the receive command. + Currently unused. + Unrecognized items are rejected, and the ioctl will fail with + <varname>errno</varname> set to <constant>EINVAL</constant>. + All items except for + <constant>KDBUS_ITEM_NEGOTIATE</constant> (see + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + ) will be rejected. + </para></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Return value</title> + <para> + On success, all mentioned ioctl commands return <errorcode>0</errorcode>; + on error, <errorcode>-1</errorcode> is returned, and + <varname>errno</varname> is set to indicate the error. + If the issued ioctl is illegal for the file descriptor used, + <varname>errno</varname> will be set to <constant>ENOTTY</constant>. + </para> + + <refsect2> + <title> + <constant>KDBUS_CMD_FREE</constant> may fail with the following + errors + </title> + + <variablelist> + <varlistentry> + <term><constant>ENXIO</constant></term> + <listitem><para> + No pool slice found at given offset. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + Invalid flags provided. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>EINVAL</constant></term> + <listitem><para> + The offset is valid, but the user is not allowed to free the slice. + This happens, for example, if the offset was retrieved with + <constant>KDBUS_RECV_PEEK</constant>. + </para></listitem> + </varlistentry> + </variablelist> + </refsect2> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>mmap</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>munmap</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + </simplelist> + </refsect1> +</refentry> diff --git a/ipc/kdbus/Documentation/kdbus.xml b/ipc/kdbus/Documentation/kdbus.xml new file mode 100644 index 000000000000..d8e7400df2af --- /dev/null +++ b/ipc/kdbus/Documentation/kdbus.xml @@ -0,0 +1,1012 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<refentry id="kdbus"> + + <refentryinfo> + <title>kdbus</title> + <productname>kdbus</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>kdbus</refentrytitle> + <manvolnum>7</manvolnum> + </refmeta> + + <refnamediv> + <refname>kdbus</refname> + <refpurpose>Kernel Message Bus</refpurpose> + </refnamediv> + + <refsect1> + <title>Synopsis</title> + <para> + kdbus is an inter-process communication bus system controlled by the + kernel. It provides user-space with an API to create buses and send + unicast and multicast messages to one, or many, peers connected to the + same bus. It does not enforce any layout on the transmitted data, but + only provides the transport layer used for message interchange between + peers. + </para> + <para> + This set of man-pages gives a comprehensive overview of the kernel-level + API, with all ioctl commands, associated structs and bit masks. However, + most people will not use this API level directly, but rather let one of + the high-level abstraction libraries help them integrate D-Bus + functionality into their applications. + </para> + </refsect1> + + <refsect1> + <title>Description</title> + <para> + kdbus provides a pseudo filesystem called <emphasis>kdbusfs</emphasis>, + which is usually mounted on <filename>/sys/fs/kdbus</filename>. Bus + primitives can be accessed as files and sub-directories underneath this + mount-point. Any advanced operations are done via + <function>ioctl()</function> on files created by + <emphasis>kdbusfs</emphasis>. Multiple mount-points of + <emphasis>kdbusfs</emphasis> are independent of each other. This allows + namespacing of kdbus by mounting a new instance of + <emphasis>kdbusfs</emphasis> in a new mount-namespace. kdbus calls these + mount instances domains and each bus belongs to exactly one domain. + </para> + + <para> + kdbus was designed as a transport layer for D-Bus, but is in no way + limited, nor controlled by the D-Bus protocol specification. The D-Bus + protocol is one possible application layer on top of kdbus. + </para> + + <para> + For the general D-Bus protocol specification, its payload format, its + marshaling, and its communication semantics, please refer to the + <ulink url="http://dbus.freedesktop.org/doc/dbus-specification.html"> + D-Bus specification</ulink>. + </para> + + </refsect1> + + <refsect1> + <title>Terminology</title> + + <refsect2> + <title>Domain</title> + <para> + A domain is a <emphasis>kdbusfs</emphasis> mount-point containing all + the bus primitives. Each domain is independent, and separate domains + do not affect each other. + </para> + </refsect2> + + <refsect2> + <title>Bus</title> + <para> + A bus is a named object inside a domain. Clients exchange messages + over a bus. Multiple buses themselves have no connection to each other; + messages can only be exchanged on the same bus. The default endpoint of + a bus, to which clients establish connections, is the "bus" file + /sys/fs/kdbus/<bus name>/bus. + Common operating system setups create one "system bus" per system, + and one "user bus" for every logged-in user. Applications or services + may create their own private buses. The kernel driver does not + distinguish between different bus types, they are all handled the same + way. See + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Endpoint</title> + <para> + An endpoint provides a file to talk to a bus. Opening an endpoint + creates a new connection to the bus to which the endpoint belongs. All + endpoints have unique names and are accessible as files underneath the + directory of a bus, e.g., /sys/fs/kdbus/<bus>/<endpoint> + Every bus has a default endpoint called "bus". + A bus can optionally offer additional endpoints with custom names + to provide restricted access to the bus. Custom endpoints carry + additional policy which can be used to create sandboxes with + locked-down, limited, filtered access to a bus. See + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Connection</title> + <para> + A connection to a bus is created by opening an endpoint file of a + bus. Every ordinary client connection has a unique identifier on the + bus and can address messages to every other connection on the same + bus by using the peer's connection ID as the destination. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Pool</title> + <para> + Each connection allocates a piece of shmem-backed memory that is + used to receive messages and answers to ioctl commands from the kernel. + It is never used to send anything to the kernel. In order to access that + memory, an application must mmap() it into its address space. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Well-known Name</title> + <para> + A connection can, in addition to its implicit unique connection ID, + request the ownership of a textual well-known name. Well-known names are + noted in reverse-domain notation, such as com.example.service1. A + connection that offers a service on a bus is usually reached by its + well-known name. An analogy of connection ID and well-known name is an + IP address and a DNS name associated with that address. See + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Message</title> + <para> + Connections can exchange messages with other connections by addressing + the peers with their connection ID or well-known name. A message + consists of a message header with information on how to route the + message, and the message payload, which is a logical byte stream of + arbitrary size. Messages can carry additional file descriptors to be + passed from one connection to another, just like passing file + descriptors over UNIX domain sockets. Every connection can specify which + set of metadata the kernel should attach to the message when it is + delivered to the receiving connection. Metadata contains information + like: system time stamps, UID, GID, TID, proc-starttime, well-known + names, process comm, process exe, process argv, cgroup, capabilities, + seclabel, audit session, loginuid and the connection's human-readable + name. See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Item</title> + <para> + The API of kdbus implements the notion of items, submitted through and + returned by most ioctls, and stored inside data structures in the + connection's pool. See + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Broadcast, signal, filter, match</title> + <para> + Signals are messages that a receiver opts in for by installing a blob of + bytes, called a 'match'. Signal messages must always carry a + counter-part blob, called a 'filter', and signals are only delivered to + peers which have a match that white-lists the message's filter. Senders + of signal messages can use either a single connection ID as receiver, + or the special connection ID + <constant>KDBUS_DST_ID_BROADCAST</constant> to potentially send it to + all connections of a bus, following the logic described above. See + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + and + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Policy</title> + <para> + A policy is a set of rules that define which connections can see, talk + to, or register a well-known name on the bus. A policy is attached to + buses and custom endpoints, and modified by policy holder connections or + owners of custom endpoints. See + <citerefentry> + <refentrytitle>kdbus.policy</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + + <refsect2> + <title>Privileged bus users</title> + <para> + A user connecting to the bus is considered privileged if it is either + the creator of the bus, or if it has the CAP_IPC_OWNER capability flag + set. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </refsect2> + </refsect1> + + <refsect1> + <title>Bus Layout</title> + + <para> + A <emphasis>bus</emphasis> provides and defines an environment that peers + can connect to for message interchange. A bus is created via the kdbus + control interface and can be modified by the bus creator. It applies the + policy that control all bus operations. The bus creator itself does not + participate as a peer. To establish a peer + <emphasis>connection</emphasis>, you have to open one of the + <emphasis>endpoints</emphasis> of a bus. Each bus provides a default + endpoint, but further endpoints can be created on-demand. Endpoints are + used to apply additional policies for all connections on this endpoint. + Thus, they provide additional filters to further restrict access of + specific connections to the bus. + </para> + + <para> + Following, you can see an example bus layout: + </para> + + <programlisting><![CDATA[ + Bus Creator + | + | + +-----+ + | Bus | + +-----+ + | + __________________/ \__________________ + / \ + | | + +----------+ +----------+ + | Endpoint | | Endpoint | + +----------+ +----------+ + _________/|\_________ _________/|\_________ + / | \ / | \ + | | | | | | + | | | | | | + Connection Connection Connection Connection Connection Connection + ]]></programlisting> + + </refsect1> + + <refsect1> + <title>Data structures and interconnections</title> + <programlisting><![CDATA[ + +--------------------------------------------------------------------------+ + | Domain (Mount Point) | + | /sys/fs/kdbus/control | + | +----------------------------------------------------------------------+ | + | | Bus (System Bus) | | + | | /sys/fs/kdbus/0-system/ | | + | | +-------------------------------+ +--------------------------------+ | | + | | | Endpoint | | Endpoint | | | + | | | /sys/fs/kdbus/0-system/bus | | /sys/fs/kdbus/0-system/ep.app | | | + | | +-------------------------------+ +--------------------------------+ | | + | | +--------------+ +--------------+ +--------------+ +---------------+ | | + | | | Connection | | Connection | | Connection | | Connection | | | + | | | :1.22 | | :1.25 | | :1.55 | | :1.81 | | | + | | +--------------+ +--------------+ +--------------+ +---------------+ | | + | +----------------------------------------------------------------------+ | + | | + | +----------------------------------------------------------------------+ | + | | Bus (User Bus for UID 2702) | | + | | /sys/fs/kdbus/2702-user/ | | + | | +-------------------------------+ +--------------------------------+ | | + | | | Endpoint | | Endpoint | | | + | | | /sys/fs/kdbus/2702-user/bus | | /sys/fs/kdbus/2702-user/ep.app | | | + | | +-------------------------------+ +--------------------------------+ | | + | | +--------------+ +--------------+ +--------------+ +---------------+ | | + | | | Connection | | Connection | | Connection | | Connection | | | + | | | :1.22 | | :1.25 | | :1.55 | | :1.81 | | | + | | +--------------+ +--------------+ +--------------------------------+ | | + | +----------------------------------------------------------------------+ | + +--------------------------------------------------------------------------+ + ]]></programlisting> + </refsect1> + + <refsect1> + <title>Metadata</title> + + <refsect2> + <title>When metadata is collected</title> + <para> + kdbus records data about the system in certain situations. Such metadata + can refer to the currently active process (creds, PIDs, current user + groups, process names and its executable path, cgroup membership, + capabilities, security label and audit information), connection + information (description string, currently owned names) and time stamps. + </para> + <para> + Metadata is collected at the following times. + </para> + + <itemizedlist> + <listitem><para> + When a bus is created (<constant>KDBUS_CMD_MAKE</constant>), + information about the calling task is collected. This data is returned + by the kernel via the <constant>KDBUS_CMD_BUS_CREATOR_INFO</constant> + call. + </para></listitem> + + <listitem> + <para> + When a connection is created (<constant>KDBUS_CMD_HELLO</constant>), + information about the calling task is collected. Alternatively, a + privileged connection may provide 'faked' information about + credentials, PIDs and security labels which will be stored instead. + This data is returned by the kernel as information on a connection + (<constant>KDBUS_CMD_CONN_INFO</constant>). Only metadata that a + connection allowed to be sent (by setting its bit in + <varname>attach_flags_send</varname>) will be exported in this way. + </para> + </listitem> + + <listitem> + <para> + When a message is sent (<constant>KDBUS_CMD_SEND</constant>), + information about the sending task and the sending connection is + collected. This metadata will be attached to the message when it + arrives in the receiver's pool. If the connection sending the + message installed faked credentials (see + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>), + the message will not be augmented by any information about the + currently sending task. Note that only metadata that was requested + by the receiving connection will be collected and attached to + messages. + </para> + </listitem> + </itemizedlist> + + <para> + Which metadata items are actually delivered depends on the following + sets and masks: + </para> + + <itemizedlist> + <listitem><para> + (a) the system-wide kmod creds mask + (module parameter <varname>attach_flags_mask</varname>) + </para></listitem> + + <listitem><para> + (b) the per-connection send creds mask, set by the connecting client + </para></listitem> + + <listitem><para> + (c) the per-connection receive creds mask, set by the connecting + client + </para></listitem> + + <listitem><para> + (d) the per-bus minimal creds mask, set by the bus creator + </para></listitem> + + <listitem><para> + (e) the per-bus owner creds mask, set by the bus creator + </para></listitem> + + <listitem><para> + (f) the mask specified when querying creds of a bus peer + </para></listitem> + + <listitem><para> + (g) the mask specified when querying creds of a bus owner + </para></listitem> + </itemizedlist> + + <para> + With the following rules: + </para> + + <itemizedlist> + <listitem> + <para> + [1] The creds attached to messages are determined as + <constant>a & b & c</constant>. + </para> + </listitem> + + <listitem> + <para> + [2] When connecting to a bus (<constant>KDBUS_CMD_HELLO</constant>), + and <constant>~b & d != 0</constant>, the call will fail with, + <errorcode>-1</errorcode>, and <varname>errno</varname> is set to + <constant>ECONNREFUSED</constant>. + </para> + </listitem> + + <listitem> + <para> + [3] When querying creds of a bus peer, the creds returned are + <constant>a & b & f</constant>. + </para> + </listitem> + + <listitem> + <para> + [4] When querying creds of a bus owner, the creds returned are + <constant>a & e & g</constant>. + </para> + </listitem> + </itemizedlist> + + <para> + Hence, programs might not always get all requested metadata items that + it requested. Code must be written so that it can cope with this fact. + </para> + </refsect2> + + <refsect2> + <title>Benefits and heads-up</title> + <para> + Attaching metadata to messages has two major benefits. + + <itemizedlist> + <listitem> + <para> + Metadata attached to messages is gathered at the moment when the + other side calls <constant>KDBUS_CMD_SEND</constant>, or, + respectively, then the kernel notification is generated. There is + no need for the receiving peer to retrieve information about the + task in a second step. This closes a race gap that would otherwise + be inherent. + </para> + </listitem> + <listitem> + <para> + As metadata is delivered along with messages in the same data + blob, no extra calls to kernel functions etc. are needed to gather + them. + </para> + </listitem> + </itemizedlist> + + Note, however, that collecting metadata does come at a price for + performance, so developers should carefully assess which metadata to + really opt-in for. For best practice, data that is not needed as part + of a message should not be requested by the connection in the first + place (see <varname>attach_flags_recv</varname> in + <constant>KDBUS_CMD_HELLO</constant>). + </para> + </refsect2> + + <refsect2> + <title>Attach flags for metadata items</title> + <para> + To let the kernel know which metadata information to attach as items + to the aforementioned commands, it uses a bitmask. In those, the + following <emphasis>attach flags</emphasis> are currently supported. + Both the <varname>attach_flags_recv</varname> and + <varname>attach_flags_send</varname> fields of + <type>struct kdbus_cmd_hello</type>, as well as the payload of the + <constant>KDBUS_ITEM_ATTACH_FLAGS_SEND</constant> and + <constant>KDBUS_ITEM_ATTACH_FLAGS_RECV</constant> items follow this + scheme. + </para> + + <variablelist> + <varlistentry> + <term><constant>KDBUS_ATTACH_TIMESTAMP</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_TIMESTAMP</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_CREDS</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_CREDS</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_PIDS</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_PIDS</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_AUXGROUPS</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_AUXGROUPS</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_NAMES</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_OWNED_NAME</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_TID_COMM</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_TID_COMM</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_PID_COMM</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_PID_COMM</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_EXE</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_EXE</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_CMDLINE</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_CMDLINE</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_CGROUP</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_CGROUP</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_CAPS</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_CAPS</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_SECLABEL</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_SECLABEL</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_AUDIT</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_AUDIT</constant>. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><constant>KDBUS_ATTACH_CONN_DESCRIPTION</constant></term> + <listitem><para> + Requests the attachment of an item of type + <constant>KDBUS_ITEM_CONN_DESCRIPTION</constant>. + </para></listitem> + </varlistentry> + </variablelist> + + <para> + Please refer to + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for detailed information about the layout and payload of items and + what metadata should be used to. + </para> + </refsect2> + </refsect1> + + <refsect1> + <title>The ioctl interface</title> + + <para> + As stated in the 'synopsis' section above, application developers are + strongly encouraged to use kdbus through one of the high-level D-Bus + abstraction libraries, rather than using the low-level API directly. + </para> + + <para> + kdbus on the kernel level exposes its functions exclusively through + <citerefentry> + <refentrytitle>ioctl</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry>, + employed on file descriptors returned by + <citerefentry> + <refentrytitle>open</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + on pseudo files exposed by + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para> + <para> + Following is a list of all the ioctls, along with the command structs + they must be used with. + </para> + + <informaltable frame="none"> + <tgroup cols="3" colsep="1"> + <thead> + <row> + <entry>ioctl signature</entry> + <entry>command</entry> + <entry>transported struct</entry> + </row> + </thead> + <tbody> + <row> + <entry><constant>0x40189500</constant></entry> + <entry><constant>KDBUS_CMD_BUS_MAKE</constant></entry> + <entry><type>struct kdbus_cmd *</type></entry> + </row><row> + <entry><constant>0x40189510</constant></entry> + <entry><constant>KDBUS_CMD_ENDPOINT_MAKE</constant></entry> + <entry><type>struct kdbus_cmd *</type></entry> + </row><row> + <entry><constant>0xc0609580</constant></entry> + <entry><constant>KDBUS_CMD_HELLO</constant></entry> + <entry><type>struct kdbus_cmd_hello *</type></entry> + </row><row> + <entry><constant>0x40189582</constant></entry> + <entry><constant>KDBUS_CMD_BYEBYE</constant></entry> + <entry><type>struct kdbus_cmd *</type></entry> + </row><row> + <entry><constant>0x40389590</constant></entry> + <entry><constant>KDBUS_CMD_SEND</constant></entry> + <entry><type>struct kdbus_cmd_send *</type></entry> + </row><row> + <entry><constant>0x80409591</constant></entry> + <entry><constant>KDBUS_CMD_RECV</constant></entry> + <entry><type>struct kdbus_cmd_recv *</type></entry> + </row><row> + <entry><constant>0x40209583</constant></entry> + <entry><constant>KDBUS_CMD_FREE</constant></entry> + <entry><type>struct kdbus_cmd_free *</type></entry> + </row><row> + <entry><constant>0x401895a0</constant></entry> + <entry><constant>KDBUS_CMD_NAME_ACQUIRE</constant></entry> + <entry><type>struct kdbus_cmd *</type></entry> + </row><row> + <entry><constant>0x401895a1</constant></entry> + <entry><constant>KDBUS_CMD_NAME_RELEASE</constant></entry> + <entry><type>struct kdbus_cmd *</type></entry> + </row><row> + <entry><constant>0x80289586</constant></entry> + <entry><constant>KDBUS_CMD_LIST</constant></entry> + <entry><type>struct kdbus_cmd_list *</type></entry> + </row><row> + <entry><constant>0x80309584</constant></entry> + <entry><constant>KDBUS_CMD_CONN_INFO</constant></entry> + <entry><type>struct kdbus_cmd_info *</type></entry> + </row><row> + <entry><constant>0x40209551</constant></entry> + <entry><constant>KDBUS_CMD_UPDATE</constant></entry> + <entry><type>struct kdbus_cmd *</type></entry> + </row><row> + <entry><constant>0x80309585</constant></entry> + <entry><constant>KDBUS_CMD_BUS_CREATOR_INFO</constant></entry> + <entry><type>struct kdbus_cmd_info *</type></entry> + </row><row> + <entry><constant>0x40189511</constant></entry> + <entry><constant>KDBUS_CMD_ENDPOINT_UPDATE</constant></entry> + <entry><type>struct kdbus_cmd *</type></entry> + </row><row> + <entry><constant>0x402095b0</constant></entry> + <entry><constant>KDBUS_CMD_MATCH_ADD</constant></entry> + <entry><type>struct kdbus_cmd_match *</type></entry> + </row><row> + <entry><constant>0x402095b1</constant></entry> + <entry><constant>KDBUS_CMD_MATCH_REMOVE</constant></entry> + <entry><type>struct kdbus_cmd_match *</type></entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para> + Depending on the type of <emphasis>kdbusfs</emphasis> node that was + opened and what ioctls have been executed on a file descriptor before, + a different sub-set of ioctl commands is allowed. + </para> + + <itemizedlist> + <listitem> + <para> + On a file descriptor resulting from opening a + <emphasis>control node</emphasis>, only the + <constant>KDBUS_CMD_BUS_MAKE</constant> ioctl may be executed. + </para> + </listitem> + <listitem> + <para> + On a file descriptor resulting from opening a + <emphasis>bus endpoint node</emphasis>, only the + <constant>KDBUS_CMD_ENDPOINT_MAKE</constant> and + <constant>KDBUS_CMD_HELLO</constant> ioctls may be executed. + </para> + </listitem> + <listitem> + <para> + A file descriptor that was used to create a bus + (via <constant>KDBUS_CMD_BUS_MAKE</constant>) is called a + <emphasis>bus owner</emphasis> file descriptor. The bus will be + active as long as the file descriptor is kept open. + A bus owner file descriptor can not be used to + employ any further ioctls. As soon as + <citerefentry> + <refentrytitle>close</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + is called on it, the bus will be shut down, along will all associated + endpoints and connections. See + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </listitem> + <listitem> + <para> + A file descriptor that was used to create an endpoint + (via <constant>KDBUS_CMD_ENDPOINT_MAKE</constant>) is called an + <emphasis>endpoint owner</emphasis> file descriptor. The endpoint + will be active as long as the file descriptor is kept open. + An endpoint owner file descriptor can only be used + to update details of an endpoint through the + <constant>KDBUS_CMD_ENDPOINT_UPDATE</constant> ioctl. As soon as + <citerefentry> + <refentrytitle>close</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + is called on it, the endpoint will be removed from the bus, and all + connections that are connected to the bus through it are shut down. + See + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + for more details. + </para> + </listitem> + <listitem> + <para> + A file descriptor that was used to create a connection + (via <constant>KDBUS_CMD_HELLO</constant>) is called a + <emphasis>connection owner</emphasis> file descriptor. The connection + will be active as long as the file descriptor is kept open. + A connection owner file descriptor may be used to + issue any of the following ioctls. + </para> + + <itemizedlist> + <listitem><para> + <constant>KDBUS_CMD_UPDATE</constant> to tweak details of the + connection. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_BYEBYE</constant> to shut down a connection + without losing messages. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_FREE</constant> to free a slice of memory in + the pool. See + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_CONN_INFO</constant> to retrieve information + on other connections on the bus. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_BUS_CREATOR_INFO</constant> to retrieve + information on the bus creator. See + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_LIST</constant> to retrieve a list of + currently active well-known names and unique IDs on the bus. See + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_SEND</constant> and + <constant>KDBUS_CMD_RECV</constant> to send or receive a message. + See + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_NAME_ACQUIRE</constant> and + <constant>KDBUS_CMD_NAME_RELEASE</constant> to acquire or release + a well-known name on the bus. See + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + + <listitem><para> + <constant>KDBUS_CMD_MATCH_ADD</constant> and + <constant>KDBUS_CMD_MATCH_REMOVE</constant> to add or remove + a match for signal messages. See + <citerefentry> + <refentrytitle>kdbus.match</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry>. + </para></listitem> + </itemizedlist> + </listitem> + </itemizedlist> + + <para> + These ioctls, along with the structs they transport, are explained in + detail in the other documents linked to in the "See Also" section below. + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + <simplelist type="inline"> + <member> + <citerefentry> + <refentrytitle>kdbus.bus</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.connection</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.endpoint</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.fs</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.item</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.message</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.name</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>kdbus.pool</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>ioctl</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>mmap</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>open</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <citerefentry> + <refentrytitle>close</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry> + </member> + <member> + <ulink url="http://freedesktop.org/wiki/Software/dbus">D-Bus</ulink> + </member> + </simplelist> + </refsect1> + +</refentry> diff --git a/ipc/kdbus/Documentation/stylesheet.xsl b/ipc/kdbus/Documentation/stylesheet.xsl new file mode 100644 index 000000000000..52565eac7d0d --- /dev/null +++ b/ipc/kdbus/Documentation/stylesheet.xsl @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <param name="chunk.quietly">1</param> + <param name="funcsynopsis.style">ansi</param> + <param name="funcsynopsis.tabular.threshold">80</param> + <param name="callout.graphics">0</param> + <param name="paper.type">A4</param> + <param name="generate.section.toc.level">2</param> + <param name="use.id.as.filename">1</param> + <param name="citerefentry.link">1</param> + <strip-space elements="*"/> + <template name="generate.citerefentry.link"> + <value-of select="refentrytitle"/> + <text>.html</text> + </template> +</stylesheet> diff --git a/ipc/kdbus/Makefile b/ipc/kdbus/Makefile new file mode 100644 index 000000000000..7ee9271e1449 --- /dev/null +++ b/ipc/kdbus/Makefile @@ -0,0 +1,22 @@ +kdbus-y := \ + bus.o \ + connection.o \ + endpoint.o \ + fs.o \ + handle.o \ + item.o \ + main.o \ + match.o \ + message.o \ + metadata.o \ + names.o \ + node.o \ + notify.o \ + domain.o \ + policy.o \ + pool.o \ + reply.o \ + queue.o \ + util.o + +obj-$(CONFIG_KDBUS) += kdbus.o diff --git a/ipc/kdbus/bus.c b/ipc/kdbus/bus.c new file mode 100644 index 000000000000..a67f825bdeaf --- /dev/null +++ b/ipc/kdbus/bus.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/fs.h> +#include <linux/hashtable.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/random.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/uio.h> + +#include "bus.h" +#include "notify.h" +#include "connection.h" +#include "domain.h" +#include "endpoint.h" +#include "handle.h" +#include "item.h" +#include "match.h" +#include "message.h" +#include "metadata.h" +#include "names.h" +#include "policy.h" +#include "util.h" + +static void kdbus_bus_free(struct kdbus_node *node) +{ + struct kdbus_bus *bus = container_of(node, struct kdbus_bus, node); + + WARN_ON(!list_empty(&bus->monitors_list)); + WARN_ON(!hash_empty(bus->conn_hash)); + + kdbus_notify_free(bus); + + kdbus_user_unref(bus->creator); + kdbus_name_registry_free(bus->name_registry); + kdbus_domain_unref(bus->domain); + kdbus_policy_db_clear(&bus->policy_db); + kdbus_meta_proc_unref(bus->creator_meta); + kfree(bus); +} + +static void kdbus_bus_release(struct kdbus_node *node, bool was_active) +{ + struct kdbus_bus *bus = container_of(node, struct kdbus_bus, node); + + if (was_active) + atomic_dec(&bus->creator->buses); +} + +static struct kdbus_bus *kdbus_bus_new(struct kdbus_domain *domain, + const char *name, + struct kdbus_bloom_parameter *bloom, + const u64 *pattach_owner, + u64 flags, kuid_t uid, kgid_t gid) +{ + struct kdbus_bus *b; + u64 attach_owner; + int ret; + + if (bloom->size < 8 || bloom->size > KDBUS_BUS_BLOOM_MAX_SIZE || + !KDBUS_IS_ALIGNED8(bloom->size) || bloom->n_hash < 1) + return ERR_PTR(-EINVAL); + + ret = kdbus_sanitize_attach_flags(pattach_owner ? *pattach_owner : 0, + &attach_owner); + if (ret < 0) + return ERR_PTR(ret); + + ret = kdbus_verify_uid_prefix(name, domain->user_namespace, uid); + if (ret < 0) + return ERR_PTR(ret); + + b = kzalloc(sizeof(*b), GFP_KERNEL); + if (!b) + return ERR_PTR(-ENOMEM); + + kdbus_node_init(&b->node, KDBUS_NODE_BUS); + + b->node.free_cb = kdbus_bus_free; + b->node.release_cb = kdbus_bus_release; + b->node.uid = uid; + b->node.gid = gid; + b->node.mode = S_IRUSR | S_IXUSR; + + if (flags & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD)) + b->node.mode |= S_IRGRP | S_IXGRP; + if (flags & KDBUS_MAKE_ACCESS_WORLD) + b->node.mode |= S_IROTH | S_IXOTH; + + b->id = atomic64_inc_return(&domain->last_id); + b->bus_flags = flags; + b->attach_flags_owner = attach_owner; + generate_random_uuid(b->id128); + b->bloom = *bloom; + b->domain = kdbus_domain_ref(domain); + + kdbus_policy_db_init(&b->policy_db); + + init_rwsem(&b->conn_rwlock); + hash_init(b->conn_hash); + INIT_LIST_HEAD(&b->monitors_list); + + INIT_LIST_HEAD(&b->notify_list); + spin_lock_init(&b->notify_lock); + mutex_init(&b->notify_flush_lock); + + ret = kdbus_node_link(&b->node, &domain->node, name); + if (ret < 0) + goto exit_unref; + + /* cache the metadata/credentials of the creator */ + b->creator_meta = kdbus_meta_proc_new(); + if (IS_ERR(b->creator_meta)) { + ret = PTR_ERR(b->creator_meta); + b->creator_meta = NULL; + goto exit_unref; + } + + ret = kdbus_meta_proc_collect(b->creator_meta, + KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_AUXGROUPS | + KDBUS_ATTACH_TID_COMM | + KDBUS_ATTACH_PID_COMM | + KDBUS_ATTACH_EXE | + KDBUS_ATTACH_CMDLINE | + KDBUS_ATTACH_CGROUP | + KDBUS_ATTACH_CAPS | + KDBUS_ATTACH_SECLABEL | + KDBUS_ATTACH_AUDIT); + if (ret < 0) + goto exit_unref; + + b->name_registry = kdbus_name_registry_new(); + if (IS_ERR(b->name_registry)) { + ret = PTR_ERR(b->name_registry); + b->name_registry = NULL; + goto exit_unref; + } + + /* + * Bus-limits of the creator are accounted on its real UID, just like + * all other per-user limits. + */ + b->creator = kdbus_user_lookup(domain, current_uid()); + if (IS_ERR(b->creator)) { + ret = PTR_ERR(b->creator); + b->creator = NULL; + goto exit_unref; + } + + return b; + +exit_unref: + kdbus_node_deactivate(&b->node); + kdbus_node_unref(&b->node); + return ERR_PTR(ret); +} + +/** + * kdbus_bus_ref() - increase the reference counter of a kdbus_bus + * @bus: The bus to reference + * + * Every user of a bus, except for its creator, must add a reference to the + * kdbus_bus using this function. + * + * Return: the bus itself + */ +struct kdbus_bus *kdbus_bus_ref(struct kdbus_bus *bus) +{ + if (bus) + kdbus_node_ref(&bus->node); + return bus; +} + +/** + * kdbus_bus_unref() - decrease the reference counter of a kdbus_bus + * @bus: The bus to unref + * + * Release a reference. If the reference count drops to 0, the bus will be + * freed. + * + * Return: NULL + */ +struct kdbus_bus *kdbus_bus_unref(struct kdbus_bus *bus) +{ + if (bus) + kdbus_node_unref(&bus->node); + return NULL; +} + +/** + * kdbus_bus_find_conn_by_id() - find a connection with a given id + * @bus: The bus to look for the connection + * @id: The 64-bit connection id + * + * Looks up a connection with a given id. The returned connection + * is ref'ed, and needs to be unref'ed by the user. Returns NULL if + * the connection can't be found. + */ +struct kdbus_conn *kdbus_bus_find_conn_by_id(struct kdbus_bus *bus, u64 id) +{ + struct kdbus_conn *conn, *found = NULL; + + down_read(&bus->conn_rwlock); + hash_for_each_possible(bus->conn_hash, conn, hentry, id) + if (conn->id == id) { + found = kdbus_conn_ref(conn); + break; + } + up_read(&bus->conn_rwlock); + + return found; +} + +/** + * kdbus_bus_broadcast() - send a message to all subscribed connections + * @bus: The bus the connections are connected to + * @conn_src: The source connection, may be %NULL for kernel notifications + * @staging: Staging object containing the message to send + * + * Send message to all connections that are currently active on the bus. + * Connections must still have matches installed in order to let the message + * pass. + * + * The caller must hold the name-registry lock of @bus. + */ +void kdbus_bus_broadcast(struct kdbus_bus *bus, + struct kdbus_conn *conn_src, + struct kdbus_staging *staging) +{ + struct kdbus_conn *conn_dst; + unsigned int i; + int ret; + + lockdep_assert_held(&bus->name_registry->rwlock); + + /* + * Make sure broadcast are queued on monitors before we send it out to + * anyone else. Otherwise, connections might react to broadcasts before + * the monitor gets the broadcast queued. In the worst case, the + * monitor sees a reaction to the broadcast before the broadcast itself. + * We don't give ordering guarantees across connections (and monitors + * can re-construct order via sequence numbers), but we should at least + * try to avoid re-ordering for monitors. + */ + kdbus_bus_eavesdrop(bus, conn_src, staging); + + down_read(&bus->conn_rwlock); + hash_for_each(bus->conn_hash, i, conn_dst, hentry) { + if (!kdbus_conn_is_ordinary(conn_dst)) + continue; + + /* + * Check if there is a match for the kmsg object in + * the destination connection match db + */ + if (!kdbus_match_db_match_msg(conn_dst->match_db, conn_src, + staging)) + continue; + + if (conn_src) { + /* + * Anyone can send broadcasts, as they have no + * destination. But a receiver needs TALK access to + * the sender in order to receive broadcasts. + */ + if (!kdbus_conn_policy_talk(conn_dst, NULL, conn_src)) + continue; + } else { + /* + * Check if there is a policy db that prevents the + * destination connection from receiving this kernel + * notification + */ + if (!kdbus_conn_policy_see_notification(conn_dst, NULL, + staging->msg)) + continue; + } + + ret = kdbus_conn_entry_insert(conn_src, conn_dst, staging, + NULL, NULL); + if (ret < 0) + kdbus_conn_lost_message(conn_dst); + } + up_read(&bus->conn_rwlock); +} + +/** + * kdbus_bus_eavesdrop() - send a message to all subscribed monitors + * @bus: The bus the monitors are connected to + * @conn_src: The source connection, may be %NULL for kernel notifications + * @staging: Staging object containing the message to send + * + * Send message to all monitors that are currently active on the bus. Monitors + * must still have matches installed in order to let the message pass. + * + * The caller must hold the name-registry lock of @bus. + */ +void kdbus_bus_eavesdrop(struct kdbus_bus *bus, + struct kdbus_conn *conn_src, + struct kdbus_staging *staging) +{ + struct kdbus_conn *conn_dst; + int ret; + + /* + * Monitor connections get all messages; ignore possible errors + * when sending messages to monitor connections. + */ + + lockdep_assert_held(&bus->name_registry->rwlock); + + down_read(&bus->conn_rwlock); + list_for_each_entry(conn_dst, &bus->monitors_list, monitor_entry) { + ret = kdbus_conn_entry_insert(conn_src, conn_dst, staging, + NULL, NULL); + if (ret < 0) + kdbus_conn_lost_message(conn_dst); + } + up_read(&bus->conn_rwlock); +} + +/** + * kdbus_cmd_bus_make() - handle KDBUS_CMD_BUS_MAKE + * @domain: domain to operate on + * @argp: command payload + * + * Return: NULL or newly created bus on success, ERR_PTR on failure. + */ +struct kdbus_bus *kdbus_cmd_bus_make(struct kdbus_domain *domain, + void __user *argp) +{ + struct kdbus_bus *bus = NULL; + struct kdbus_cmd *cmd; + struct kdbus_ep *ep = NULL; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_MAKE_NAME, .mandatory = true }, + { .type = KDBUS_ITEM_BLOOM_PARAMETER, .mandatory = true }, + { .type = KDBUS_ITEM_ATTACH_FLAGS_SEND }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_MAKE_ACCESS_GROUP | + KDBUS_MAKE_ACCESS_WORLD, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret < 0) + return ERR_PTR(ret); + if (ret > 0) + return NULL; + + bus = kdbus_bus_new(domain, + argv[1].item->str, &argv[2].item->bloom_parameter, + argv[3].item ? argv[3].item->data64 : NULL, + cmd->flags, current_euid(), current_egid()); + if (IS_ERR(bus)) { + ret = PTR_ERR(bus); + bus = NULL; + goto exit; + } + + if (atomic_inc_return(&bus->creator->buses) > KDBUS_USER_MAX_BUSES) { + atomic_dec(&bus->creator->buses); + ret = -EMFILE; + goto exit; + } + + if (!kdbus_node_activate(&bus->node)) { + atomic_dec(&bus->creator->buses); + ret = -ESHUTDOWN; + goto exit; + } + + ep = kdbus_ep_new(bus, "bus", cmd->flags, bus->node.uid, bus->node.gid, + false); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + ep = NULL; + goto exit; + } + + if (!kdbus_node_activate(&ep->node)) { + ret = -ESHUTDOWN; + goto exit; + } + + /* + * Drop our own reference, effectively causing the endpoint to be + * deactivated and released when the parent bus is. + */ + ep = kdbus_ep_unref(ep); + +exit: + ret = kdbus_args_clear(&args, ret); + if (ret < 0) { + if (ep) { + kdbus_node_deactivate(&ep->node); + kdbus_ep_unref(ep); + } + if (bus) { + kdbus_node_deactivate(&bus->node); + kdbus_bus_unref(bus); + } + return ERR_PTR(ret); + } + return bus; +} + +/** + * kdbus_cmd_bus_creator_info() - handle KDBUS_CMD_BUS_CREATOR_INFO + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_bus_creator_info(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_cmd_info *cmd; + struct kdbus_bus *bus = conn->ep->bus; + struct kdbus_pool_slice *slice = NULL; + struct kdbus_item *meta_items = NULL; + struct kdbus_item_header item_hdr; + struct kdbus_info info = {}; + size_t meta_size, name_len, cnt = 0; + struct kvec kvec[6]; + u64 attach_flags, size = 0; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + ret = kdbus_sanitize_attach_flags(cmd->attach_flags, &attach_flags); + if (ret < 0) + goto exit; + + attach_flags &= bus->attach_flags_owner; + + ret = kdbus_meta_emit(bus->creator_meta, NULL, NULL, conn, + attach_flags, &meta_items, &meta_size); + if (ret < 0) + goto exit; + + name_len = strlen(bus->node.name) + 1; + info.id = bus->id; + info.flags = bus->bus_flags; + item_hdr.type = KDBUS_ITEM_MAKE_NAME; + item_hdr.size = KDBUS_ITEM_HEADER_SIZE + name_len; + + kdbus_kvec_set(&kvec[cnt++], &info, sizeof(info), &size); + kdbus_kvec_set(&kvec[cnt++], &item_hdr, sizeof(item_hdr), &size); + kdbus_kvec_set(&kvec[cnt++], bus->node.name, name_len, &size); + cnt += !!kdbus_kvec_pad(&kvec[cnt], &size); + if (meta_size > 0) { + kdbus_kvec_set(&kvec[cnt++], meta_items, meta_size, &size); + cnt += !!kdbus_kvec_pad(&kvec[cnt], &size); + } + + info.size = size; + + slice = kdbus_pool_slice_alloc(conn->pool, size, false); + if (IS_ERR(slice)) { + ret = PTR_ERR(slice); + slice = NULL; + goto exit; + } + + ret = kdbus_pool_slice_copy_kvec(slice, 0, kvec, cnt, size); + if (ret < 0) + goto exit; + + kdbus_pool_slice_publish(slice, &cmd->offset, &cmd->info_size); + + if (kdbus_member_set_user(&cmd->offset, argp, typeof(*cmd), offset) || + kdbus_member_set_user(&cmd->info_size, argp, + typeof(*cmd), info_size)) + ret = -EFAULT; + +exit: + kdbus_pool_slice_release(slice); + kfree(meta_items); + return kdbus_args_clear(&args, ret); +} diff --git a/ipc/kdbus/bus.h b/ipc/kdbus/bus.h new file mode 100644 index 000000000000..8c2acaed6258 --- /dev/null +++ b/ipc/kdbus/bus.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_BUS_H +#define __KDBUS_BUS_H + +#include <linux/hashtable.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/rwsem.h> +#include <linux/spinlock.h> +#include <uapi/linux/kdbus.h> + +#include "metadata.h" +#include "names.h" +#include "node.h" +#include "policy.h" + +struct kdbus_conn; +struct kdbus_domain; +struct kdbus_staging; +struct kdbus_user; + +/** + * struct kdbus_bus - bus in a domain + * @node: kdbus_node + * @id: ID of this bus in the domain + * @bus_flags: Simple pass-through flags from userspace to userspace + * @attach_flags_owner: KDBUS_ATTACH_* flags of bus creator that other + * connections can see or query + * @id128: Unique random 128 bit ID of this bus + * @bloom: Bloom parameters + * @domain: Domain of this bus + * @creator: Creator of the bus + * @creator_meta: Meta information about the bus creator + * @last_message_id: Last used message id + * @policy_db: Policy database for this bus + * @name_registry: Name registry of this bus + * @conn_rwlock: Read/Write lock for all lists of child connections + * @conn_hash: Map of connection IDs + * @monitors_list: Connections that monitor this bus + * @notify_list: List of pending kernel-generated messages + * @notify_lock: Notification list lock + * @notify_flush_lock: Notification flushing lock + */ +struct kdbus_bus { + struct kdbus_node node; + + /* static */ + u64 id; + u64 bus_flags; + u64 attach_flags_owner; + u8 id128[16]; + struct kdbus_bloom_parameter bloom; + struct kdbus_domain *domain; + struct kdbus_user *creator; + struct kdbus_meta_proc *creator_meta; + + /* protected by own locks */ + atomic64_t last_message_id; + struct kdbus_policy_db policy_db; + struct kdbus_name_registry *name_registry; + + /* protected by conn_rwlock */ + struct rw_semaphore conn_rwlock; + DECLARE_HASHTABLE(conn_hash, 8); + struct list_head monitors_list; + + /* protected by notify_lock */ + struct list_head notify_list; + spinlock_t notify_lock; + struct mutex notify_flush_lock; +}; + +struct kdbus_bus *kdbus_bus_ref(struct kdbus_bus *bus); +struct kdbus_bus *kdbus_bus_unref(struct kdbus_bus *bus); + +struct kdbus_conn *kdbus_bus_find_conn_by_id(struct kdbus_bus *bus, u64 id); +void kdbus_bus_broadcast(struct kdbus_bus *bus, + struct kdbus_conn *conn_src, + struct kdbus_staging *staging); +void kdbus_bus_eavesdrop(struct kdbus_bus *bus, + struct kdbus_conn *conn_src, + struct kdbus_staging *staging); + +struct kdbus_bus *kdbus_cmd_bus_make(struct kdbus_domain *domain, + void __user *argp); +int kdbus_cmd_bus_creator_info(struct kdbus_conn *conn, void __user *argp); + +#endif diff --git a/ipc/kdbus/connection.c b/ipc/kdbus/connection.c new file mode 100644 index 000000000000..02deba366839 --- /dev/null +++ b/ipc/kdbus/connection.c @@ -0,0 +1,2245 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/audit.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/fs_struct.h> +#include <linux/hashtable.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/math64.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/path.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/shmem_fs.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/uio.h> + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "handle.h" +#include "match.h" +#include "message.h" +#include "metadata.h" +#include "names.h" +#include "domain.h" +#include "item.h" +#include "notify.h" +#include "policy.h" +#include "pool.h" +#include "reply.h" +#include "util.h" +#include "queue.h" + +#define KDBUS_CONN_ACTIVE_BIAS (INT_MIN + 2) +#define KDBUS_CONN_ACTIVE_NEW (INT_MIN + 1) + +/* Disable internal kdbus policy - possibilities of connections to own, see and + * talk to names are restricted by libdbuspolicy library and LSM hooks + */ +#define DISABLE_KDBUS_POLICY + +static struct kdbus_conn *kdbus_conn_new(struct kdbus_ep *ep, + struct file *file, + struct kdbus_cmd_hello *hello, + const char *name, + const struct kdbus_creds *creds, + const struct kdbus_pids *pids, + const char *seclabel, + const char *conn_description) +{ +#ifdef CONFIG_DEBUG_LOCK_ALLOC + static struct lock_class_key __key; +#endif + struct kdbus_pool_slice *slice = NULL; + struct kdbus_bus *bus = ep->bus; + struct kdbus_conn *conn; + u64 attach_flags_send; + u64 attach_flags_recv; + u64 items_size = 0; + bool is_policy_holder; + bool is_activator; + bool is_monitor; + bool privileged; + bool owner; + struct kvec kvec; + int ret; + + struct { + u64 size; + u64 type; + struct kdbus_bloom_parameter bloom; + } bloom_item; + + privileged = kdbus_ep_is_privileged(ep, file); + owner = kdbus_ep_is_owner(ep, file); + + is_monitor = hello->flags & KDBUS_HELLO_MONITOR; + is_activator = hello->flags & KDBUS_HELLO_ACTIVATOR; + is_policy_holder = hello->flags & KDBUS_HELLO_POLICY_HOLDER; + + if (!hello->pool_size || !IS_ALIGNED(hello->pool_size, PAGE_SIZE)) + return ERR_PTR(-EINVAL); + if (is_monitor + is_activator + is_policy_holder > 1) + return ERR_PTR(-EINVAL); + if (name && !is_activator && !is_policy_holder) + return ERR_PTR(-EINVAL); + if (!name && (is_activator || is_policy_holder)) + return ERR_PTR(-EINVAL); + if (name && !kdbus_name_is_valid(name, true)) + return ERR_PTR(-EINVAL); + if (is_monitor && ep->user) + return ERR_PTR(-EOPNOTSUPP); + if (!owner && (is_activator || is_policy_holder || is_monitor)) + return ERR_PTR(-EPERM); + if (!owner && (creds || pids || seclabel)) + return ERR_PTR(-EPERM); + + ret = kdbus_sanitize_attach_flags(hello->attach_flags_send, + &attach_flags_send); + if (ret < 0) + return ERR_PTR(ret); + + ret = kdbus_sanitize_attach_flags(hello->attach_flags_recv, + &attach_flags_recv); + if (ret < 0) + return ERR_PTR(ret); + + conn = kzalloc(sizeof(*conn), GFP_KERNEL); + if (!conn) + return ERR_PTR(-ENOMEM); + + kref_init(&conn->kref); + atomic_set(&conn->active, KDBUS_CONN_ACTIVE_NEW); +#ifdef CONFIG_DEBUG_LOCK_ALLOC + lockdep_init_map(&conn->dep_map, "s_active", &__key, 0); +#endif + mutex_init(&conn->lock); + INIT_LIST_HEAD(&conn->names_list); + INIT_LIST_HEAD(&conn->reply_list); + atomic_set(&conn->request_count, 0); + atomic_set(&conn->lost_count, 0); + INIT_DELAYED_WORK(&conn->work, kdbus_reply_list_scan_work); + conn->cred = get_cred(file->f_cred); + conn->pid = get_pid(task_pid(current)); + get_fs_root(current->fs, &conn->root_path); + init_waitqueue_head(&conn->wait); + kdbus_queue_init(&conn->queue); + conn->privileged = privileged; + conn->owner = owner; + conn->ep = kdbus_ep_ref(ep); + conn->id = atomic64_inc_return(&bus->domain->last_id); + conn->flags = hello->flags; + atomic64_set(&conn->attach_flags_send, attach_flags_send); + atomic64_set(&conn->attach_flags_recv, attach_flags_recv); + INIT_LIST_HEAD(&conn->monitor_entry); + + if (conn_description) { + conn->description = kstrdup(conn_description, GFP_KERNEL); + if (!conn->description) { + ret = -ENOMEM; + goto exit_unref; + } + } + + conn->pool = kdbus_pool_new(conn->description, hello->pool_size); + if (IS_ERR(conn->pool)) { + ret = PTR_ERR(conn->pool); + conn->pool = NULL; + goto exit_unref; + } + + conn->match_db = kdbus_match_db_new(); + if (IS_ERR(conn->match_db)) { + ret = PTR_ERR(conn->match_db); + conn->match_db = NULL; + goto exit_unref; + } + + /* return properties of this connection to the caller */ + hello->bus_flags = bus->bus_flags; + hello->id = conn->id; + + BUILD_BUG_ON(sizeof(bus->id128) != sizeof(hello->id128)); + memcpy(hello->id128, bus->id128, sizeof(hello->id128)); + + /* privileged processes can impersonate somebody else */ + if (creds || pids || seclabel) { + conn->meta_fake = kdbus_meta_fake_new(); + if (IS_ERR(conn->meta_fake)) { + ret = PTR_ERR(conn->meta_fake); + conn->meta_fake = NULL; + goto exit_unref; + } + + ret = kdbus_meta_fake_collect(conn->meta_fake, + creds, pids, seclabel); + if (ret < 0) + goto exit_unref; + } else { + conn->meta_proc = kdbus_meta_proc_new(); + if (IS_ERR(conn->meta_proc)) { + ret = PTR_ERR(conn->meta_proc); + conn->meta_proc = NULL; + goto exit_unref; + } + + ret = kdbus_meta_proc_collect(conn->meta_proc, + KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_AUXGROUPS | + KDBUS_ATTACH_TID_COMM | + KDBUS_ATTACH_PID_COMM | + KDBUS_ATTACH_EXE | + KDBUS_ATTACH_CMDLINE | + KDBUS_ATTACH_CGROUP | + KDBUS_ATTACH_CAPS | + KDBUS_ATTACH_SECLABEL | + KDBUS_ATTACH_AUDIT); + if (ret < 0) + goto exit_unref; + } + + /* + * Account the connection against the current user (UID), or for + * custom endpoints use the anonymous user assigned to the endpoint. + * Note that limits are always accounted against the real UID, not + * the effective UID (cred->user always points to the accounting of + * cred->uid, not cred->euid). + * In case the caller is privileged, we allow changing the accounting + * to the faked user. + */ + if (ep->user) { + conn->user = kdbus_user_ref(ep->user); + } else { + kuid_t uid; + + if (conn->meta_fake && uid_valid(conn->meta_fake->uid) && + conn->privileged) + uid = conn->meta_fake->uid; + else + uid = conn->cred->uid; + + conn->user = kdbus_user_lookup(ep->bus->domain, uid); + if (IS_ERR(conn->user)) { + ret = PTR_ERR(conn->user); + conn->user = NULL; + goto exit_unref; + } + } + + if (atomic_inc_return(&conn->user->connections) > KDBUS_USER_MAX_CONN) { + /* decremented by destructor as conn->user is valid */ + ret = -EMFILE; + goto exit_unref; + } + + bloom_item.size = sizeof(bloom_item); + bloom_item.type = KDBUS_ITEM_BLOOM_PARAMETER; + bloom_item.bloom = bus->bloom; + kdbus_kvec_set(&kvec, &bloom_item, bloom_item.size, &items_size); + + slice = kdbus_pool_slice_alloc(conn->pool, items_size, false); + if (IS_ERR(slice)) { + ret = PTR_ERR(slice); + slice = NULL; + goto exit_unref; + } + + ret = kdbus_pool_slice_copy_kvec(slice, 0, &kvec, 1, items_size); + if (ret < 0) + goto exit_unref; + + kdbus_pool_slice_publish(slice, &hello->offset, &hello->items_size); + kdbus_pool_slice_release(slice); + + return conn; + +exit_unref: + kdbus_pool_slice_release(slice); + kdbus_conn_unref(conn); + return ERR_PTR(ret); +} + +static void __kdbus_conn_free(struct kref *kref) +{ + struct kdbus_conn *conn = container_of(kref, struct kdbus_conn, kref); + + WARN_ON(kdbus_conn_active(conn)); + WARN_ON(delayed_work_pending(&conn->work)); + WARN_ON(!list_empty(&conn->queue.msg_list)); + WARN_ON(!list_empty(&conn->names_list)); + WARN_ON(!list_empty(&conn->reply_list)); + + if (conn->user) { + atomic_dec(&conn->user->connections); + kdbus_user_unref(conn->user); + } + + kdbus_meta_fake_free(conn->meta_fake); + kdbus_meta_proc_unref(conn->meta_proc); + kdbus_match_db_free(conn->match_db); + kdbus_pool_free(conn->pool); + kdbus_ep_unref(conn->ep); + path_put(&conn->root_path); + put_pid(conn->pid); + put_cred(conn->cred); + kfree(conn->description); + kfree(conn->quota); + kfree(conn); +} + +/** + * kdbus_conn_ref() - take a connection reference + * @conn: Connection, may be %NULL + * + * Return: the connection itself + */ +struct kdbus_conn *kdbus_conn_ref(struct kdbus_conn *conn) +{ + if (conn) + kref_get(&conn->kref); + return conn; +} + +/** + * kdbus_conn_unref() - drop a connection reference + * @conn: Connection (may be NULL) + * + * When the last reference is dropped, the connection's internal structure + * is freed. + * + * Return: NULL + */ +struct kdbus_conn *kdbus_conn_unref(struct kdbus_conn *conn) +{ + if (conn) + kref_put(&conn->kref, __kdbus_conn_free); + return NULL; +} + +/** + * kdbus_conn_active() - connection is not disconnected + * @conn: Connection to check + * + * Return true if the connection was not disconnected, yet. Note that a + * connection might be disconnected asynchronously, unless you hold the + * connection lock. If that's not suitable for you, see kdbus_conn_acquire() to + * suppress connection shutdown for a short period. + * + * Return: true if the connection is still active + */ +bool kdbus_conn_active(const struct kdbus_conn *conn) +{ + return atomic_read(&conn->active) >= 0; +} + +/** + * kdbus_conn_acquire() - acquire an active connection reference + * @conn: Connection + * + * Users can close a connection via KDBUS_BYEBYE (or by destroying the + * endpoint/bus/...) at any time. Whenever this happens, we should deny any + * user-visible action on this connection and signal ECONNRESET instead. + * To avoid testing for connection availability everytime you take the + * connection-lock, you can acquire a connection for short periods. + * + * By calling kdbus_conn_acquire(), you gain an "active reference" to the + * connection. You must also hold a regular reference at any time! As long as + * you hold the active-ref, the connection will not be shut down. However, if + * the connection was shut down, you can never acquire an active-ref again. + * + * kdbus_conn_disconnect() disables the connection and then waits for all active + * references to be dropped. It will also wake up any pending operation. + * However, you must not sleep for an indefinite period while holding an + * active-reference. Otherwise, kdbus_conn_disconnect() might stall. If you need + * to sleep for an indefinite period, either release the reference and try to + * acquire it again after waking up, or make kdbus_conn_disconnect() wake up + * your wait-queue. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_conn_acquire(struct kdbus_conn *conn) +{ + if (!atomic_inc_unless_negative(&conn->active)) + return -ECONNRESET; + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + rwsem_acquire_read(&conn->dep_map, 0, 1, _RET_IP_); +#endif + + return 0; +} + +/** + * kdbus_conn_release() - release an active connection reference + * @conn: Connection + * + * This releases an active reference that has been acquired via + * kdbus_conn_acquire(). If the connection was already disabled and this is the + * last active-ref that is dropped, the disconnect-waiter will be woken up and + * properly close the connection. + */ +void kdbus_conn_release(struct kdbus_conn *conn) +{ + int v; + + if (!conn) + return; + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + rwsem_release(&conn->dep_map, 1, _RET_IP_); +#endif + + v = atomic_dec_return(&conn->active); + if (v != KDBUS_CONN_ACTIVE_BIAS) + return; + + wake_up_all(&conn->wait); +} + +static int kdbus_conn_connect(struct kdbus_conn *conn, const char *name) +{ + struct kdbus_ep *ep = conn->ep; + struct kdbus_bus *bus = ep->bus; + int ret; + + if (WARN_ON(atomic_read(&conn->active) != KDBUS_CONN_ACTIVE_NEW)) + return -EALREADY; + + /* make sure the ep-node is active while we add our connection */ + if (!kdbus_node_acquire(&ep->node)) + return -ESHUTDOWN; + + /* lock order: domain -> bus -> ep -> names -> conn */ + mutex_lock(&ep->lock); + down_write(&bus->conn_rwlock); + + /* link into monitor list */ + if (kdbus_conn_is_monitor(conn)) + list_add_tail(&conn->monitor_entry, &bus->monitors_list); + + /* link into bus and endpoint */ + list_add_tail(&conn->ep_entry, &ep->conn_list); + hash_add(bus->conn_hash, &conn->hentry, conn->id); + + /* enable lookups and acquire active ref */ + atomic_set(&conn->active, 1); +#ifdef CONFIG_DEBUG_LOCK_ALLOC + rwsem_acquire_read(&conn->dep_map, 0, 1, _RET_IP_); +#endif + + up_write(&bus->conn_rwlock); + mutex_unlock(&ep->lock); + + kdbus_node_release(&ep->node); + + /* + * Notify subscribers about the new active connection, unless it is + * a monitor. Monitors are invisible on the bus, can't be addressed + * directly, and won't cause any notifications. + */ + if (!kdbus_conn_is_monitor(conn)) { + ret = kdbus_notify_id_change(bus, KDBUS_ITEM_ID_ADD, + conn->id, conn->flags); + if (ret < 0) + goto exit_disconnect; + } + + if (kdbus_conn_is_activator(conn)) { + u64 flags = KDBUS_NAME_ACTIVATOR; + + if (WARN_ON(!name)) { + ret = -EINVAL; + goto exit_disconnect; + } + + ret = kdbus_name_acquire(bus->name_registry, conn, name, + flags, NULL); + if (ret < 0) + goto exit_disconnect; + } + + kdbus_conn_release(conn); + kdbus_notify_flush(bus); + return 0; + +exit_disconnect: + kdbus_conn_release(conn); + kdbus_conn_disconnect(conn, false); + return ret; +} + +/** + * kdbus_conn_disconnect() - disconnect a connection + * @conn: The connection to disconnect + * @ensure_queue_empty: Flag to indicate if the call should fail in + * case the connection's message list is not + * empty + * + * If @ensure_msg_list_empty is true, and the connection has pending messages, + * -EBUSY is returned. + * + * Return: 0 on success, negative errno on failure + */ +int kdbus_conn_disconnect(struct kdbus_conn *conn, bool ensure_queue_empty) +{ + struct kdbus_queue_entry *entry, *tmp; + struct kdbus_bus *bus = conn->ep->bus; + struct kdbus_reply *r, *r_tmp; + struct kdbus_conn *c; + int i, v; + + mutex_lock(&conn->lock); + v = atomic_read(&conn->active); + if (v == KDBUS_CONN_ACTIVE_NEW) { + /* was never connected */ + mutex_unlock(&conn->lock); + return 0; + } + if (v < 0) { + /* already dead */ + mutex_unlock(&conn->lock); + return -ECONNRESET; + } + if (ensure_queue_empty && !list_empty(&conn->queue.msg_list)) { + /* still busy */ + mutex_unlock(&conn->lock); + return -EBUSY; + } + + atomic_add(KDBUS_CONN_ACTIVE_BIAS, &conn->active); + mutex_unlock(&conn->lock); + + wake_up_interruptible(&conn->wait); + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + rwsem_acquire(&conn->dep_map, 0, 0, _RET_IP_); + if (atomic_read(&conn->active) != KDBUS_CONN_ACTIVE_BIAS) + lock_contended(&conn->dep_map, _RET_IP_); +#endif + + wait_event(conn->wait, + atomic_read(&conn->active) == KDBUS_CONN_ACTIVE_BIAS); + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + lock_acquired(&conn->dep_map, _RET_IP_); + rwsem_release(&conn->dep_map, 1, _RET_IP_); +#endif + + cancel_delayed_work_sync(&conn->work); + kdbus_policy_remove_owner(&conn->ep->bus->policy_db, conn); + + /* lock order: domain -> bus -> ep -> names -> conn */ + mutex_lock(&conn->ep->lock); + down_write(&bus->conn_rwlock); + + /* remove from bus and endpoint */ + hash_del(&conn->hentry); + list_del(&conn->monitor_entry); + list_del(&conn->ep_entry); + + up_write(&bus->conn_rwlock); + mutex_unlock(&conn->ep->lock); + + /* + * Remove all names associated with this connection; this possibly + * moves queued messages back to the activator connection. + */ + kdbus_name_release_all(bus->name_registry, conn); + + /* if we die while other connections wait for our reply, notify them */ + mutex_lock(&conn->lock); + list_for_each_entry_safe(entry, tmp, &conn->queue.msg_list, entry) { + if (entry->reply) + kdbus_notify_reply_dead(bus, + entry->reply->reply_dst->id, + entry->reply->cookie); + kdbus_queue_entry_free(entry); + } + + list_for_each_entry_safe(r, r_tmp, &conn->reply_list, entry) + kdbus_reply_unlink(r); + mutex_unlock(&conn->lock); + + /* lock order: domain -> bus -> ep -> names -> conn */ + down_read(&bus->conn_rwlock); + hash_for_each(bus->conn_hash, i, c, hentry) { + mutex_lock(&c->lock); + list_for_each_entry_safe(r, r_tmp, &c->reply_list, entry) { + if (r->reply_src != conn) + continue; + + if (r->sync) + kdbus_sync_reply_wakeup(r, -EPIPE); + else + /* send a 'connection dead' notification */ + kdbus_notify_reply_dead(bus, c->id, r->cookie); + + kdbus_reply_unlink(r); + } + mutex_unlock(&c->lock); + } + up_read(&bus->conn_rwlock); + + if (!kdbus_conn_is_monitor(conn)) + kdbus_notify_id_change(bus, KDBUS_ITEM_ID_REMOVE, + conn->id, conn->flags); + + kdbus_notify_flush(bus); + + return 0; +} + +/** + * kdbus_conn_has_name() - check if a connection owns a name + * @conn: Connection + * @name: Well-know name to check for + * + * The caller must hold the registry lock of conn->ep->bus. + * + * Return: true if the name is currently owned by the connection + */ +bool kdbus_conn_has_name(struct kdbus_conn *conn, const char *name) +{ + struct kdbus_name_owner *owner; + + lockdep_assert_held(&conn->ep->bus->name_registry->rwlock); + + list_for_each_entry(owner, &conn->names_list, conn_entry) + if (!(owner->flags & KDBUS_NAME_IN_QUEUE) && + !strcmp(name, owner->name->name)) + return true; + + return false; +} + +struct kdbus_quota { + u32 memory; + u16 msgs; + u8 fds; +}; + +/** + * kdbus_conn_quota_inc() - increase quota accounting + * @c: connection owning the quota tracking + * @u: user to account for (or NULL for kernel accounting) + * @memory: size of memory to account for + * @fds: number of FDs to account for + * + * This call manages the quotas on resource @c. That is, it's used if other + * users want to use the resources of connection @c, which so far only concerns + * the receive queue of the destination. + * + * This increases the quota-accounting for user @u by @memory bytes and @fds + * file descriptors. If the user has already reached the quota limits, this call + * will not do any accounting but return a negative error code indicating the + * failure. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_conn_quota_inc(struct kdbus_conn *c, struct kdbus_user *u, + size_t memory, size_t fds) +{ + struct kdbus_quota *quota; + size_t available, accounted; + unsigned int id; + + /* + * Pool Layout: + * 50% of a pool is always owned by the connection. It is reserved for + * kernel queries, handling received messages and other tasks that are + * under control of the pool owner. The other 50% of the pool are used + * as incoming queue. + * As we optionally support user-space based policies, we need fair + * allocation schemes. Furthermore, resource utilization should be + * maximized, so only minimal resources stay reserved. However, we need + * to adapt to a dynamic number of users, as we cannot know how many + * users will talk to a connection. Therefore, the current allocation + * works like this: + * We limit the number of bytes in a destination's pool per sending + * user. The space available for a user is 33% of the unused pool space + * (whereas the space used by the user itself is also treated as + * 'unused'). This way, we favor users coming first, but keep enough + * pool space available for any following users. Given that messages are + * dequeued in FIFO order, this should balance nicely if the number of + * users grows. At the same time, this algorithm guarantees that the + * space available to a connection is reduced dynamically, the more + * concurrent users talk to a connection. + */ + + /* per user-accounting is expensive, so we keep state small */ + BUILD_BUG_ON(sizeof(quota->memory) != 4); + BUILD_BUG_ON(sizeof(quota->msgs) != 2); + BUILD_BUG_ON(sizeof(quota->fds) != 1); + BUILD_BUG_ON(KDBUS_CONN_MAX_MSGS > U16_MAX); + BUILD_BUG_ON(KDBUS_CONN_MAX_FDS_PER_USER > U8_MAX); + + id = u ? u->id : KDBUS_USER_KERNEL_ID; + if (id >= c->n_quota) { + unsigned int users; + + users = max(KDBUS_ALIGN8(id) + 8, id); + quota = krealloc(c->quota, users * sizeof(*quota), + GFP_KERNEL | __GFP_ZERO); + if (!quota) + return -ENOMEM; + + c->n_quota = users; + c->quota = quota; + } + + quota = &c->quota[id]; + kdbus_pool_accounted(c->pool, &available, &accounted); + + /* half the pool is _always_ reserved for the pool owner */ + available /= 2; + + /* + * Pool owner slices are un-accounted slices; they can claim more + * than 50% of the queue. However, the slices we're dealing with here + * belong to the incoming queue, hence they are 'accounted' slices + * to which the 50%-limit applies. + */ + if (available < accounted) + return -ENOBUFS; + + /* 1/3 of the remaining space (including your own memory) */ + available = (available - accounted + quota->memory) / 3; + + if (available < quota->memory || + available - quota->memory < memory || + quota->memory + memory > U32_MAX) + return -ENOBUFS; + if (quota->msgs >= KDBUS_CONN_MAX_MSGS) + return -ENOBUFS; + if (quota->fds + fds < quota->fds || + quota->fds + fds > KDBUS_CONN_MAX_FDS_PER_USER) + return -EMFILE; + + quota->memory += memory; + quota->fds += fds; + ++quota->msgs; + return 0; +} + +/** + * kdbus_conn_quota_dec() - decrease quota accounting + * @c: connection owning the quota tracking + * @u: user which was accounted for (or NULL for kernel accounting) + * @memory: size of memory which was accounted for + * @fds: number of FDs which were accounted for + * + * This does the reverse of kdbus_conn_quota_inc(). You have to release any + * accounted resources that you called kdbus_conn_quota_inc() for. However, you + * must not call kdbus_conn_quota_dec() if the accounting failed (that is, + * kdbus_conn_quota_inc() failed). + */ +void kdbus_conn_quota_dec(struct kdbus_conn *c, struct kdbus_user *u, + size_t memory, size_t fds) +{ + struct kdbus_quota *quota; + unsigned int id; + + id = u ? u->id : KDBUS_USER_KERNEL_ID; + if (WARN_ON(id >= c->n_quota)) + return; + + quota = &c->quota[id]; + + if (!WARN_ON(quota->msgs == 0)) + --quota->msgs; + if (!WARN_ON(quota->memory < memory)) + quota->memory -= memory; + if (!WARN_ON(quota->fds < fds)) + quota->fds -= fds; +} + +/** + * kdbus_conn_lost_message() - handle lost messages + * @c: connection that lost a message + * + * kdbus is reliable. That means, we try hard to never lose messages. However, + * memory is limited, so we cannot rely on transmissions to never fail. + * Therefore, we use quota-limits to let callers know if their unicast message + * cannot be transmitted to a peer. This works fine for unicasts, but for + * broadcasts we cannot make the caller handle the transmission failure. + * Instead, we must let the destination know that it couldn't receive a + * broadcast. + * As this is an unlikely scenario, we keep it simple. A single lost-counter + * remembers the number of lost messages since the last call to RECV. The next + * message retrieval will notify the connection that it lost messages since the + * last message retrieval and thus should resync its state. + */ +void kdbus_conn_lost_message(struct kdbus_conn *c) +{ + if (atomic_inc_return(&c->lost_count) == 1) + wake_up_interruptible(&c->wait); +} + +/* Callers should take the conn_dst lock */ +static struct kdbus_queue_entry * +kdbus_conn_entry_make(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst, + struct kdbus_staging *staging) +{ + /* The remote connection was disconnected */ + if (!kdbus_conn_active(conn_dst)) + return ERR_PTR(-ECONNRESET); + + /* + * If the connection does not accept file descriptors but the message + * has some attached, refuse it. + * + * If this is a monitor connection, accept the message. In that + * case, all file descriptors will be set to -1 at receive time. + */ + if (!kdbus_conn_is_monitor(conn_dst) && + !(conn_dst->flags & KDBUS_HELLO_ACCEPT_FD) && + staging->gaps && staging->gaps->n_fds > 0) + return ERR_PTR(-ECOMM); + + return kdbus_queue_entry_new(conn_src, conn_dst, staging); +} + +/* + * Synchronously responding to a message, allocate a queue entry + * and attach it to the reply tracking object. + * The connection's queue will never get to see it. + */ +static int kdbus_conn_entry_sync_attach(struct kdbus_conn *conn_dst, + struct kdbus_staging *staging, + struct kdbus_reply *reply_wake) +{ + struct kdbus_queue_entry *entry; + int remote_ret, ret = 0; + + mutex_lock(&reply_wake->reply_dst->lock); + + /* + * If we are still waiting then proceed, allocate a queue + * entry and attach it to the reply object + */ + if (reply_wake->waiting) { + entry = kdbus_conn_entry_make(reply_wake->reply_src, conn_dst, + staging); + if (IS_ERR(entry)) + ret = PTR_ERR(entry); + else + /* Attach the entry to the reply object */ + reply_wake->queue_entry = entry; + } else { + ret = -ECONNRESET; + } + + /* + * Update the reply object and wake up remote peer only + * on appropriate return codes + * + * * -ECOMM: if the replying connection failed with -ECOMM + * then wakeup remote peer with -EREMOTEIO + * + * We do this to differenciate between -ECOMM errors + * from the original sender perspective: + * -ECOMM error during the sync send and + * -ECOMM error during the sync reply, this last + * one is rewritten to -EREMOTEIO + * + * * Wake up on all other return codes. + */ + remote_ret = ret; + + if (ret == -ECOMM) + remote_ret = -EREMOTEIO; + + kdbus_sync_reply_wakeup(reply_wake, remote_ret); + mutex_unlock(&reply_wake->reply_dst->lock); + + return ret; +} + +/** + * kdbus_conn_entry_insert() - enqueue a message into the receiver's pool + * @conn_src: The sending connection + * @conn_dst: The connection to queue into + * @staging: Message to send + * @reply: The reply tracker to attach to the queue entry + * @name: Destination name this msg is sent to, or NULL + * + * Return: 0 on success. negative error otherwise. + */ +int kdbus_conn_entry_insert(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst, + struct kdbus_staging *staging, + struct kdbus_reply *reply, + const struct kdbus_name_entry *name) +{ + struct kdbus_queue_entry *entry; + int ret; + + kdbus_conn_lock2(conn_src, conn_dst); + + entry = kdbus_conn_entry_make(conn_src, conn_dst, staging); + if (IS_ERR(entry)) { + ret = PTR_ERR(entry); + goto exit_unlock; + } + + if (reply) { + kdbus_reply_link(reply); + if (!reply->sync) + schedule_delayed_work(&conn_src->work, 0); + } + + /* + * Record the sequence number of the registered name; it will + * be remembered by the queue, in case messages addressed to a + * name need to be moved from or to an activator. + */ + if (name) + entry->dst_name_id = name->name_id; + + kdbus_queue_entry_enqueue(entry, reply); + wake_up_interruptible(&conn_dst->wait); + + ret = 0; + +exit_unlock: + kdbus_conn_unlock2(conn_src, conn_dst); + return ret; +} + +static int kdbus_conn_wait_reply(struct kdbus_conn *conn_src, + struct kdbus_cmd_send *cmd_send, + struct file *ioctl_file, + struct file *cancel_fd, + struct kdbus_reply *reply_wait, + ktime_t expire) +{ + struct kdbus_queue_entry *entry; + struct poll_wqueues pwq = {}; + int ret; + + if (WARN_ON(!reply_wait)) + return -EIO; + + /* + * Block until the reply arrives. reply_wait is left untouched + * by the timeout scans that might be conducted for other, + * asynchronous replies of conn_src. + */ + + poll_initwait(&pwq); + poll_wait(ioctl_file, &conn_src->wait, &pwq.pt); + + for (;;) { + /* + * Any of the following conditions will stop our synchronously + * blocking SEND command: + * + * a) The origin sender closed its connection + * b) The remote peer answered, setting reply_wait->waiting = 0 + * c) The cancel FD was written to + * d) A signal was received + * e) The specified timeout was reached, and none of the above + * conditions kicked in. + */ + + /* + * We have already acquired an active reference when + * entering here, but another thread may call + * KDBUS_CMD_BYEBYE which does not acquire an active + * reference, therefore kdbus_conn_disconnect() will + * not wait for us. + */ + if (!kdbus_conn_active(conn_src)) { + ret = -ECONNRESET; + break; + } + + /* + * After the replying peer unset the waiting variable + * it will wake up us. + */ + if (!reply_wait->waiting) { + ret = reply_wait->err; + break; + } + + if (cancel_fd) { + unsigned int r; + + r = cancel_fd->f_op->poll(cancel_fd, &pwq.pt); + if (r & POLLIN) { + ret = -ECANCELED; + break; + } + } + + if (signal_pending(current)) { + ret = -EINTR; + break; + } + + if (!poll_schedule_timeout(&pwq, TASK_INTERRUPTIBLE, + &expire, 0)) { + ret = -ETIMEDOUT; + break; + } + + /* + * Reset the poll worker func, so the waitqueues are not + * added to the poll table again. We just reuse what we've + * collected earlier for further iterations. + */ + init_poll_funcptr(&pwq.pt, NULL); + } + + poll_freewait(&pwq); + + if (ret == -EINTR) { + /* + * Interrupted system call. Unref the reply object, and pass + * the return value down the chain. Mark the reply as + * interrupted, so the cleanup work can remove it, but do not + * unlink it from the list. Once the syscall restarts, we'll + * pick it up and wait on it again. + */ + mutex_lock(&conn_src->lock); + reply_wait->interrupted = true; + schedule_delayed_work(&conn_src->work, 0); + mutex_unlock(&conn_src->lock); + + return -ERESTARTSYS; + } + + mutex_lock(&conn_src->lock); + reply_wait->waiting = false; + entry = reply_wait->queue_entry; + if (entry) { + ret = kdbus_queue_entry_install(entry, + &cmd_send->reply.return_flags, + true); + kdbus_pool_slice_publish(entry->slice, &cmd_send->reply.offset, + &cmd_send->reply.msg_size); + kdbus_queue_entry_free(entry); + } + kdbus_reply_unlink(reply_wait); + mutex_unlock(&conn_src->lock); + + return ret; +} + +static int kdbus_pin_dst(struct kdbus_bus *bus, + struct kdbus_staging *staging, + struct kdbus_name_entry **out_name, + struct kdbus_conn **out_dst) +{ + const struct kdbus_msg *msg = staging->msg; + struct kdbus_name_owner *owner = NULL; + struct kdbus_name_entry *name = NULL; + struct kdbus_conn *dst = NULL; + int ret; + + lockdep_assert_held(&bus->name_registry->rwlock); + + if (!staging->dst_name) { + dst = kdbus_bus_find_conn_by_id(bus, msg->dst_id); + if (!dst) + return -ENXIO; + + if (!kdbus_conn_is_ordinary(dst)) { + ret = -ENXIO; + goto error; + } + } else { + name = kdbus_name_lookup_unlocked(bus->name_registry, + staging->dst_name); + if (name) + owner = kdbus_name_get_owner(name); + if (!owner) + return -ESRCH; + + /* + * If both a name and a connection ID are given as destination + * of a message, check that the currently owning connection of + * the name matches the specified ID. + * This way, we allow userspace to send the message to a + * specific connection by ID only if the connection currently + * owns the given name. + */ + if (msg->dst_id != KDBUS_DST_ID_NAME && + msg->dst_id != owner->conn->id) + return -EREMCHG; + + if ((msg->flags & KDBUS_MSG_NO_AUTO_START) && + kdbus_conn_is_activator(owner->conn)) + return -EADDRNOTAVAIL; + + dst = kdbus_conn_ref(owner->conn); + } + + *out_name = name; + *out_dst = dst; + return 0; + +error: + kdbus_conn_unref(dst); + return ret; +} + +static int kdbus_conn_reply(struct kdbus_conn *src, + struct kdbus_staging *staging) +{ + const struct kdbus_msg *msg = staging->msg; + struct kdbus_name_entry *name = NULL; + struct kdbus_reply *reply, *wake = NULL; + struct kdbus_conn *dst = NULL; + struct kdbus_bus *bus = src->ep->bus; + int ret; + + if (WARN_ON(msg->dst_id == KDBUS_DST_ID_BROADCAST) || + WARN_ON(msg->flags & KDBUS_MSG_EXPECT_REPLY) || + WARN_ON(msg->flags & KDBUS_MSG_SIGNAL)) + return -EINVAL; + + /* name-registry must be locked for lookup *and* collecting data */ + down_read(&bus->name_registry->rwlock); + + /* find and pin destination */ + + ret = kdbus_pin_dst(bus, staging, &name, &dst); + if (ret < 0) + goto exit; + + mutex_lock(&dst->lock); + reply = kdbus_reply_find(src, dst, msg->cookie_reply); + if (reply) { + if (reply->sync) + wake = kdbus_reply_ref(reply); + kdbus_reply_unlink(reply); + } + mutex_unlock(&dst->lock); + + + /* send message */ + + kdbus_bus_eavesdrop(bus, src, staging); + + if (wake) + ret = kdbus_conn_entry_sync_attach(dst, staging, wake); + else + ret = kdbus_conn_entry_insert(src, dst, staging, NULL, name); + +exit: + up_read(&bus->name_registry->rwlock); + kdbus_reply_unref(wake); + kdbus_conn_unref(dst); + return ret; +} + +static struct kdbus_reply *kdbus_conn_call(struct kdbus_conn *src, + struct kdbus_staging *staging, + ktime_t exp) +{ + const struct kdbus_msg *msg = staging->msg; + struct kdbus_name_entry *name = NULL; + struct kdbus_reply *wait = NULL; + struct kdbus_conn *dst = NULL; + struct kdbus_bus *bus = src->ep->bus; + int ret; + + if (WARN_ON(msg->dst_id == KDBUS_DST_ID_BROADCAST) || + WARN_ON(msg->flags & KDBUS_MSG_SIGNAL) || + WARN_ON(!(msg->flags & KDBUS_MSG_EXPECT_REPLY))) + return ERR_PTR(-EINVAL); + + /* resume previous wait-context, if available */ + + mutex_lock(&src->lock); + wait = kdbus_reply_find(NULL, src, msg->cookie); + if (wait) { + if (wait->interrupted) { + kdbus_reply_ref(wait); + wait->interrupted = false; + } else { + wait = NULL; + } + } + mutex_unlock(&src->lock); + + if (wait) + return wait; + + if (ktime_compare(ktime_get(), exp) >= 0) + return ERR_PTR(-ETIMEDOUT); + + /* name-registry must be locked for lookup *and* collecting data */ + down_read(&bus->name_registry->rwlock); + + /* find and pin destination */ + + ret = kdbus_pin_dst(bus, staging, &name, &dst); + if (ret < 0) + goto exit; + + if (!kdbus_conn_policy_talk(src, current_cred(), dst)) { + ret = -EPERM; + goto exit; + } + + wait = kdbus_reply_new(dst, src, msg, name, true); + if (IS_ERR(wait)) { + ret = PTR_ERR(wait); + wait = NULL; + goto exit; + } + + /* send message */ + + kdbus_bus_eavesdrop(bus, src, staging); + + ret = kdbus_conn_entry_insert(src, dst, staging, wait, name); + if (ret < 0) + goto exit; + + ret = 0; + +exit: + up_read(&bus->name_registry->rwlock); + if (ret < 0) { + kdbus_reply_unref(wait); + wait = ERR_PTR(ret); + } + kdbus_conn_unref(dst); + return wait; +} + +static int kdbus_conn_unicast(struct kdbus_conn *src, + struct kdbus_staging *staging) +{ + const struct kdbus_msg *msg = staging->msg; + struct kdbus_name_entry *name = NULL; + struct kdbus_reply *wait = NULL; + struct kdbus_conn *dst = NULL; + struct kdbus_bus *bus = src->ep->bus; + bool is_signal = (msg->flags & KDBUS_MSG_SIGNAL); + int ret = 0; + + if (WARN_ON(msg->dst_id == KDBUS_DST_ID_BROADCAST) || + WARN_ON(!(msg->flags & KDBUS_MSG_EXPECT_REPLY) && + msg->cookie_reply != 0)) + return -EINVAL; + + /* name-registry must be locked for lookup *and* collecting data */ + down_read(&bus->name_registry->rwlock); + + /* find and pin destination */ + + ret = kdbus_pin_dst(bus, staging, &name, &dst); + if (ret < 0) + goto exit; + + if (is_signal) { + /* like broadcasts we eavesdrop even if the msg is dropped */ + kdbus_bus_eavesdrop(bus, src, staging); + + /* drop silently if peer is not interested or not privileged */ + if (!kdbus_match_db_match_msg(dst->match_db, src, staging) || + !kdbus_conn_policy_talk(dst, NULL, src)) + goto exit; + } else if (!kdbus_conn_policy_talk(src, current_cred(), dst)) { + ret = -EPERM; + goto exit; + } else if (msg->flags & KDBUS_MSG_EXPECT_REPLY) { + wait = kdbus_reply_new(dst, src, msg, name, false); + if (IS_ERR(wait)) { + ret = PTR_ERR(wait); + wait = NULL; + goto exit; + } + } + + /* send message */ + + if (!is_signal) + kdbus_bus_eavesdrop(bus, src, staging); + + ret = kdbus_conn_entry_insert(src, dst, staging, wait, name); + if (ret < 0 && !is_signal) + goto exit; + + /* signals are treated like broadcasts, recv-errors are ignored */ + ret = 0; + +exit: + up_read(&bus->name_registry->rwlock); + kdbus_reply_unref(wait); + kdbus_conn_unref(dst); + return ret; +} + +/** + * kdbus_conn_move_messages() - move messages from one connection to another + * @conn_dst: Connection to copy to + * @conn_src: Connection to copy from + * @name_id: Filter for the sequence number of the registered + * name, 0 means no filtering. + * + * Move all messages from one connection to another. This is used when + * an implementer connection is taking over/giving back a well-known name + * from/to an activator connection. + */ +void kdbus_conn_move_messages(struct kdbus_conn *conn_dst, + struct kdbus_conn *conn_src, + u64 name_id) +{ + struct kdbus_queue_entry *e, *e_tmp; + struct kdbus_reply *r, *r_tmp; + struct kdbus_bus *bus; + struct kdbus_conn *c; + LIST_HEAD(msg_list); + int i, ret = 0; + + if (WARN_ON(conn_src == conn_dst)) + return; + + bus = conn_src->ep->bus; + + /* lock order: domain -> bus -> ep -> names -> conn */ + down_read(&bus->conn_rwlock); + hash_for_each(bus->conn_hash, i, c, hentry) { + if (c == conn_src || c == conn_dst) + continue; + + mutex_lock(&c->lock); + list_for_each_entry_safe(r, r_tmp, &c->reply_list, entry) { + if (r->reply_src != conn_src) + continue; + + /* filter messages for a specific name */ + if (name_id > 0 && r->name_id != name_id) + continue; + + kdbus_conn_unref(r->reply_src); + r->reply_src = kdbus_conn_ref(conn_dst); + } + mutex_unlock(&c->lock); + } + up_read(&bus->conn_rwlock); + + kdbus_conn_lock2(conn_src, conn_dst); + list_for_each_entry_safe(e, e_tmp, &conn_src->queue.msg_list, entry) { + /* filter messages for a specific name */ + if (name_id > 0 && e->dst_name_id != name_id) + continue; + + if (!(conn_dst->flags & KDBUS_HELLO_ACCEPT_FD) && + e->gaps && e->gaps->n_fds > 0) { + kdbus_conn_lost_message(conn_dst); + kdbus_queue_entry_free(e); + continue; + } + + ret = kdbus_queue_entry_move(e, conn_dst); + if (ret < 0) { + kdbus_conn_lost_message(conn_dst); + kdbus_queue_entry_free(e); + continue; + } + } + kdbus_conn_unlock2(conn_src, conn_dst); + + /* wake up poll() */ + wake_up_interruptible(&conn_dst->wait); +} + +/* query the policy-database for all names of @whom */ +static bool kdbus_conn_policy_query_all(struct kdbus_conn *conn, + const struct cred *conn_creds, + struct kdbus_policy_db *db, + struct kdbus_conn *whom, + unsigned int access) +{ + struct kdbus_name_owner *owner; + bool pass = false; + int res; + + lockdep_assert_held(&conn->ep->bus->name_registry->rwlock); + + down_read(&db->entries_rwlock); + mutex_lock(&whom->lock); + + list_for_each_entry(owner, &whom->names_list, conn_entry) { + if (owner->flags & KDBUS_NAME_IN_QUEUE) + continue; + + res = kdbus_policy_query_unlocked(db, + conn_creds ? : conn->cred, + owner->name->name, + kdbus_strhash(owner->name->name)); + if (res >= (int)access) { + pass = true; + break; + } + } + + mutex_unlock(&whom->lock); + up_read(&db->entries_rwlock); + + return pass; +} + +/** + * kdbus_conn_policy_own_name() - verify a connection can own the given name + * @conn: Connection + * @conn_creds: Credentials of @conn to use for policy check + * @name: Name + * + * This verifies that @conn is allowed to acquire the well-known name @name. + * + * Return: true if allowed, false if not. + */ +bool kdbus_conn_policy_own_name(struct kdbus_conn *conn, + const struct cred *conn_creds, + const char *name) +{ + unsigned int hash = kdbus_strhash(name); + int res; + +#ifdef DISABLE_KDBUS_POLICY + return true; +#endif + + if (!conn_creds) + conn_creds = conn->cred; + + if (conn->ep->user) { + res = kdbus_policy_query(&conn->ep->policy_db, conn_creds, + name, hash); + if (res < KDBUS_POLICY_OWN) + return false; + } + + if (conn->owner) + return true; + + res = kdbus_policy_query(&conn->ep->bus->policy_db, conn_creds, + name, hash); + return res >= KDBUS_POLICY_OWN; +} + +/** + * kdbus_conn_policy_talk() - verify a connection can talk to a given peer + * @conn: Connection that tries to talk + * @conn_creds: Credentials of @conn to use for policy check + * @to: Connection that is talked to + * + * This verifies that @conn is allowed to talk to @to. + * + * Return: true if allowed, false if not. + */ +bool kdbus_conn_policy_talk(struct kdbus_conn *conn, + const struct cred *conn_creds, + struct kdbus_conn *to) +{ + +#ifdef DISABLE_KDBUS_POLICY + return true; +#endif + + if (!conn_creds) + conn_creds = conn->cred; + + if (conn->ep->user && + !kdbus_conn_policy_query_all(conn, conn_creds, &conn->ep->policy_db, + to, KDBUS_POLICY_TALK)) + return false; + + if (conn->owner) + return true; + if (uid_eq(conn_creds->euid, to->cred->uid)) + return true; + + return kdbus_conn_policy_query_all(conn, conn_creds, + &conn->ep->bus->policy_db, to, + KDBUS_POLICY_TALK); +} + +/** + * kdbus_conn_policy_see_name_unlocked() - verify a connection can see a given + * name + * @conn: Connection + * @conn_creds: Credentials of @conn to use for policy check + * @name: Name + * + * This verifies that @conn is allowed to see the well-known name @name. Caller + * must hold policy-lock. + * + * Return: true if allowed, false if not. + */ +bool kdbus_conn_policy_see_name_unlocked(struct kdbus_conn *conn, + const struct cred *conn_creds, + const char *name) +{ + int res; + +#ifdef DISABLE_KDBUS_POLICY + return true; +#endif + + /* + * By default, all names are visible on a bus. SEE policies can only be + * installed on custom endpoints, where by default no name is visible. + */ + if (!conn->ep->user) + return true; + + res = kdbus_policy_query_unlocked(&conn->ep->policy_db, + conn_creds ? : conn->cred, + name, kdbus_strhash(name)); + return res >= KDBUS_POLICY_SEE; +} + +static bool kdbus_conn_policy_see_name(struct kdbus_conn *conn, + const struct cred *conn_creds, + const char *name) +{ + bool res; + + down_read(&conn->ep->policy_db.entries_rwlock); + res = kdbus_conn_policy_see_name_unlocked(conn, conn_creds, name); + up_read(&conn->ep->policy_db.entries_rwlock); + + return res; +} + +static bool kdbus_conn_policy_see(struct kdbus_conn *conn, + const struct cred *conn_creds, + struct kdbus_conn *whom) +{ + +#ifdef DISABLE_KDBUS_POLICY + return true; +#endif + + /* + * By default, all names are visible on a bus, so a connection can + * always see other connections. SEE policies can only be installed on + * custom endpoints, where by default no name is visible and we hide + * peers from each other, unless you see at least _one_ name of the + * peer. + */ + return !conn->ep->user || + kdbus_conn_policy_query_all(conn, conn_creds, + &conn->ep->policy_db, whom, + KDBUS_POLICY_SEE); +} + +/** + * kdbus_conn_policy_see_notification() - verify a connection is allowed to + * receive a given kernel notification + * @conn: Connection + * @conn_creds: Credentials of @conn to use for policy check + * @msg: Notification message + * + * This checks whether @conn is allowed to see the kernel notification. + * + * Return: true if allowed, false if not. + */ +bool kdbus_conn_policy_see_notification(struct kdbus_conn *conn, + const struct cred *conn_creds, + const struct kdbus_msg *msg) +{ + /* + * Depending on the notification type, broadcasted kernel notifications + * have to be filtered: + * + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE}: This notification is forwarded + * to a peer if, and only if, that peer can see the name this + * notification is for. + * + * KDBUS_ITEM_ID_{ADD,REMOVE}: Notifications for ID changes are + * broadcast to everyone, to allow tracking peers. + */ + + switch (msg->items[0].type) { + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + return kdbus_conn_policy_see_name(conn, conn_creds, + msg->items[0].name_change.name); + + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + return true; + + default: + WARN(1, "Invalid type for notification broadcast: %llu\n", + (unsigned long long)msg->items[0].type); + return false; + } +} + +/** + * kdbus_cmd_hello() - handle KDBUS_CMD_HELLO + * @ep: Endpoint to operate on + * @file: File this connection is opened on + * @argp: Command payload + * + * Return: NULL or newly created connection on success, ERR_PTR on failure. + */ +struct kdbus_conn *kdbus_cmd_hello(struct kdbus_ep *ep, struct file *file, + void __user *argp) +{ + struct kdbus_cmd_hello *cmd; + struct kdbus_conn *c = NULL; + const char *item_name; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_NAME }, + { .type = KDBUS_ITEM_CREDS }, + { .type = KDBUS_ITEM_PIDS }, + { .type = KDBUS_ITEM_SECLABEL }, + { .type = KDBUS_ITEM_CONN_DESCRIPTION }, + { .type = KDBUS_ITEM_POLICY_ACCESS, .multiple = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_HELLO_ACCEPT_FD | + KDBUS_HELLO_ACTIVATOR | + KDBUS_HELLO_POLICY_HOLDER | + KDBUS_HELLO_MONITOR, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret < 0) + return ERR_PTR(ret); + if (ret > 0) + return NULL; + + item_name = argv[1].item ? argv[1].item->str : NULL; + + c = kdbus_conn_new(ep, file, cmd, item_name, + argv[2].item ? &argv[2].item->creds : NULL, + argv[3].item ? &argv[3].item->pids : NULL, + argv[4].item ? argv[4].item->str : NULL, + argv[5].item ? argv[5].item->str : NULL); + if (IS_ERR(c)) { + ret = PTR_ERR(c); + c = NULL; + goto exit; + } + + ret = kdbus_conn_connect(c, item_name); + if (ret < 0) + goto exit; + + if (kdbus_conn_is_activator(c) || kdbus_conn_is_policy_holder(c)) { + ret = kdbus_conn_acquire(c); + if (ret < 0) + goto exit; + + ret = kdbus_policy_set(&c->ep->bus->policy_db, args.items, + args.items_size, 1, + kdbus_conn_is_policy_holder(c), c); + kdbus_conn_release(c); + if (ret < 0) + goto exit; + } + + if (copy_to_user(argp, cmd, sizeof(*cmd))) + ret = -EFAULT; + +exit: + ret = kdbus_args_clear(&args, ret); + if (ret < 0) { + if (c) { + kdbus_conn_disconnect(c, false); + kdbus_conn_unref(c); + } + return ERR_PTR(ret); + } + return c; +} + +/** + * kdbus_cmd_byebye_unlocked() - handle KDBUS_CMD_BYEBYE + * @conn: connection to operate on + * @argp: command payload + * + * The caller must not hold any active reference to @conn or this will deadlock. + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_byebye_unlocked(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_cmd *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + ret = kdbus_conn_disconnect(conn, true); + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_conn_info() - handle KDBUS_CMD_CONN_INFO + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_conn_info(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_meta_conn *conn_meta = NULL; + struct kdbus_pool_slice *slice = NULL; + struct kdbus_name_entry *entry = NULL; + struct kdbus_name_owner *owner = NULL; + struct kdbus_conn *owner_conn = NULL; + struct kdbus_item *meta_items = NULL; + struct kdbus_info info = {}; + struct kdbus_cmd_info *cmd; + struct kdbus_bus *bus = conn->ep->bus; + struct kvec kvec[3]; + size_t meta_size, cnt = 0; + const char *name; + u64 attach_flags, size = 0; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_NAME }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + /* registry must be held throughout lookup *and* collecting data */ + down_read(&bus->name_registry->rwlock); + + ret = kdbus_sanitize_attach_flags(cmd->attach_flags, &attach_flags); + if (ret < 0) + goto exit; + + name = argv[1].item ? argv[1].item->str : NULL; + + if (name) { + entry = kdbus_name_lookup_unlocked(bus->name_registry, name); + if (entry) + owner = kdbus_name_get_owner(entry); + if (!owner || + !kdbus_conn_policy_see_name(conn, current_cred(), name) || + (cmd->id != 0 && owner->conn->id != cmd->id)) { + /* pretend a name doesn't exist if you cannot see it */ + ret = -ESRCH; + goto exit; + } + + owner_conn = kdbus_conn_ref(owner->conn); + } else if (cmd->id > 0) { + owner_conn = kdbus_bus_find_conn_by_id(bus, cmd->id); + if (!owner_conn || !kdbus_conn_policy_see(conn, current_cred(), + owner_conn)) { + /* pretend an id doesn't exist if you cannot see it */ + ret = -ENXIO; + goto exit; + } + } else { + ret = -EINVAL; + goto exit; + } + + attach_flags &= atomic64_read(&owner_conn->attach_flags_send); + + conn_meta = kdbus_meta_conn_new(); + if (IS_ERR(conn_meta)) { + ret = PTR_ERR(conn_meta); + conn_meta = NULL; + goto exit; + } + + ret = kdbus_meta_conn_collect(conn_meta, owner_conn, 0, attach_flags); + if (ret < 0) + goto exit; + + ret = kdbus_meta_emit(owner_conn->meta_proc, owner_conn->meta_fake, + conn_meta, conn, attach_flags, + &meta_items, &meta_size); + if (ret < 0) + goto exit; + + info.id = owner_conn->id; + info.flags = owner_conn->flags; + + kdbus_kvec_set(&kvec[cnt++], &info, sizeof(info), &size); + if (meta_size > 0) { + kdbus_kvec_set(&kvec[cnt++], meta_items, meta_size, &size); + cnt += !!kdbus_kvec_pad(&kvec[cnt], &size); + } + + info.size = size; + + slice = kdbus_pool_slice_alloc(conn->pool, size, false); + if (IS_ERR(slice)) { + ret = PTR_ERR(slice); + slice = NULL; + goto exit; + } + + ret = kdbus_pool_slice_copy_kvec(slice, 0, kvec, cnt, size); + if (ret < 0) + goto exit; + + kdbus_pool_slice_publish(slice, &cmd->offset, &cmd->info_size); + + if (kdbus_member_set_user(&cmd->offset, argp, typeof(*cmd), offset) || + kdbus_member_set_user(&cmd->info_size, argp, + typeof(*cmd), info_size)) { + ret = -EFAULT; + goto exit; + } + + ret = 0; + +exit: + up_read(&bus->name_registry->rwlock); + kdbus_pool_slice_release(slice); + kfree(meta_items); + kdbus_meta_conn_unref(conn_meta); + kdbus_conn_unref(owner_conn); + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_update() - handle KDBUS_CMD_UPDATE + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_update(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_item *item_policy; + u64 *item_attach_send = NULL; + u64 *item_attach_recv = NULL; + struct kdbus_cmd *cmd; + u64 attach_send; + u64 attach_recv; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_ATTACH_FLAGS_SEND }, + { .type = KDBUS_ITEM_ATTACH_FLAGS_RECV }, + { .type = KDBUS_ITEM_NAME, .multiple = true }, + { .type = KDBUS_ITEM_POLICY_ACCESS, .multiple = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + item_attach_send = argv[1].item ? &argv[1].item->data64[0] : NULL; + item_attach_recv = argv[2].item ? &argv[2].item->data64[0] : NULL; + item_policy = argv[3].item ? : argv[4].item; + + if (item_attach_send) { + if (!kdbus_conn_is_ordinary(conn) && + !kdbus_conn_is_monitor(conn)) { + ret = -EOPNOTSUPP; + goto exit; + } + + ret = kdbus_sanitize_attach_flags(*item_attach_send, + &attach_send); + if (ret < 0) + goto exit; + } + + if (item_attach_recv) { + if (!kdbus_conn_is_ordinary(conn) && + !kdbus_conn_is_monitor(conn) && + !kdbus_conn_is_activator(conn)) { + ret = -EOPNOTSUPP; + goto exit; + } + + ret = kdbus_sanitize_attach_flags(*item_attach_recv, + &attach_recv); + if (ret < 0) + goto exit; + } + + if (item_policy && !kdbus_conn_is_policy_holder(conn)) { + ret = -EOPNOTSUPP; + goto exit; + } + + /* now that we verified the input, update the connection */ + + if (item_policy) { + ret = kdbus_policy_set(&conn->ep->bus->policy_db, cmd->items, + KDBUS_ITEMS_SIZE(cmd, items), + 1, true, conn); + if (ret < 0) + goto exit; + } + + if (item_attach_send) + atomic64_set(&conn->attach_flags_send, attach_send); + + if (item_attach_recv) + atomic64_set(&conn->attach_flags_recv, attach_recv); + +exit: + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_send() - handle KDBUS_CMD_SEND + * @conn: connection to operate on + * @f: file this command was called on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_send(struct kdbus_conn *conn, struct file *f, void __user *argp) +{ + struct kdbus_cmd_send *cmd; + struct kdbus_staging *staging = NULL; + struct kdbus_msg *msg = NULL; + struct file *cancel_fd = NULL; + int ret, ret2; + + /* command arguments */ + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_CANCEL_FD }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_SEND_SYNC_REPLY, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + /* message arguments */ + struct kdbus_arg msg_argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_PAYLOAD_VEC, .multiple = true }, + { .type = KDBUS_ITEM_PAYLOAD_MEMFD, .multiple = true }, + { .type = KDBUS_ITEM_FDS }, + { .type = KDBUS_ITEM_BLOOM_FILTER }, + { .type = KDBUS_ITEM_DST_NAME }, + }; + struct kdbus_args msg_args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_MSG_EXPECT_REPLY | + KDBUS_MSG_NO_AUTO_START | + KDBUS_MSG_SIGNAL, + .argv = msg_argv, + .argc = ARRAY_SIZE(msg_argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + /* make sure to parse both, @cmd and @msg on negotiation */ + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret < 0) + goto exit; + else if (ret > 0 && !cmd->msg_address) /* negotiation without msg */ + goto exit; + + ret2 = kdbus_args_parse_msg(&msg_args, KDBUS_PTR(cmd->msg_address), + &msg); + if (ret2 < 0) { /* cannot parse message */ + ret = ret2; + goto exit; + } else if (ret2 > 0 && !ret) { /* msg-negot implies cmd-negot */ + ret = -EINVAL; + goto exit; + } else if (ret > 0) { /* negotiation */ + goto exit; + } + + /* here we parsed both, @cmd and @msg, and neither wants negotiation */ + + cmd->reply.return_flags = 0; + kdbus_pool_publish_empty(conn->pool, &cmd->reply.offset, + &cmd->reply.msg_size); + + if (argv[1].item) { + cancel_fd = fget(argv[1].item->fds[0]); + if (!cancel_fd) { + ret = -EBADF; + goto exit; + } + + if (!cancel_fd->f_op->poll) { + ret = -EINVAL; + goto exit; + } + } + + /* patch-in the source of this message */ + if (msg->src_id > 0 && msg->src_id != conn->id) { + ret = -EINVAL; + goto exit; + } + msg->src_id = conn->id; + + staging = kdbus_staging_new_user(conn->ep->bus, cmd, msg); + if (IS_ERR(staging)) { + ret = PTR_ERR(staging); + staging = NULL; + goto exit; + } + + if (msg->dst_id == KDBUS_DST_ID_BROADCAST) { + down_read(&conn->ep->bus->name_registry->rwlock); + kdbus_bus_broadcast(conn->ep->bus, conn, staging); + up_read(&conn->ep->bus->name_registry->rwlock); + } else if (cmd->flags & KDBUS_SEND_SYNC_REPLY) { + struct kdbus_reply *r; + ktime_t exp; + + exp = ns_to_ktime(msg->timeout_ns); + r = kdbus_conn_call(conn, staging, exp); + if (IS_ERR(r)) { + ret = PTR_ERR(r); + goto exit; + } + + ret = kdbus_conn_wait_reply(conn, cmd, f, cancel_fd, r, exp); + kdbus_reply_unref(r); + if (ret < 0) + goto exit; + } else if ((msg->flags & KDBUS_MSG_EXPECT_REPLY) || + msg->cookie_reply == 0) { + ret = kdbus_conn_unicast(conn, staging); + if (ret < 0) + goto exit; + } else { + ret = kdbus_conn_reply(conn, staging); + if (ret < 0) + goto exit; + } + + if (kdbus_member_set_user(&cmd->reply, argp, typeof(*cmd), reply)) + ret = -EFAULT; + +exit: + if (cancel_fd) + fput(cancel_fd); + kdbus_staging_free(staging); + ret = kdbus_args_clear(&msg_args, ret); + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_recv() - handle KDBUS_CMD_RECV + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_recv(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_queue_entry *entry; + struct kdbus_cmd_recv *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_RECV_PEEK | + KDBUS_RECV_DROP | + KDBUS_RECV_USE_PRIORITY, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn) && + !kdbus_conn_is_monitor(conn) && + !kdbus_conn_is_activator(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + cmd->dropped_msgs = 0; + cmd->msg.return_flags = 0; + kdbus_pool_publish_empty(conn->pool, &cmd->msg.offset, + &cmd->msg.msg_size); + + /* DROP+priority is not realiably, so prevent it */ + if ((cmd->flags & KDBUS_RECV_DROP) && + (cmd->flags & KDBUS_RECV_USE_PRIORITY)) { + ret = -EINVAL; + goto exit; + } + + mutex_lock(&conn->lock); + + entry = kdbus_queue_peek(&conn->queue, cmd->priority, + cmd->flags & KDBUS_RECV_USE_PRIORITY); + if (!entry) { + mutex_unlock(&conn->lock); + ret = -EAGAIN; + } else if (cmd->flags & KDBUS_RECV_DROP) { + struct kdbus_reply *reply = kdbus_reply_ref(entry->reply); + + kdbus_queue_entry_free(entry); + + mutex_unlock(&conn->lock); + + if (reply) { + mutex_lock(&reply->reply_dst->lock); + if (!list_empty(&reply->entry)) { + kdbus_reply_unlink(reply); + if (reply->sync) + kdbus_sync_reply_wakeup(reply, -EPIPE); + else + kdbus_notify_reply_dead(conn->ep->bus, + reply->reply_dst->id, + reply->cookie); + } + mutex_unlock(&reply->reply_dst->lock); + kdbus_notify_flush(conn->ep->bus); + } + + kdbus_reply_unref(reply); + } else { + bool install_fds; + + /* + * PEEK just returns the location of the next message. Do not + * install FDs nor memfds nor anything else. The only + * information of interest should be the message header and + * metadata. Any FD numbers in the payload is undefined for + * PEEK'ed messages. + * Also make sure to never install fds into a connection that + * has refused to receive any. Ordinary connections will not get + * messages with FDs queued (the receiver will get -ECOMM), but + * eavesdroppers might. + */ + install_fds = (conn->flags & KDBUS_HELLO_ACCEPT_FD) && + !(cmd->flags & KDBUS_RECV_PEEK); + + ret = kdbus_queue_entry_install(entry, + &cmd->msg.return_flags, + install_fds); + if (ret < 0) { + mutex_unlock(&conn->lock); + goto exit; + } + + kdbus_pool_slice_publish(entry->slice, &cmd->msg.offset, + &cmd->msg.msg_size); + + if (!(cmd->flags & KDBUS_RECV_PEEK)) + kdbus_queue_entry_free(entry); + + mutex_unlock(&conn->lock); + } + + cmd->dropped_msgs = atomic_xchg(&conn->lost_count, 0); + if (cmd->dropped_msgs > 0) + cmd->return_flags |= KDBUS_RECV_RETURN_DROPPED_MSGS; + + if (kdbus_member_set_user(&cmd->msg, argp, typeof(*cmd), msg) || + kdbus_member_set_user(&cmd->dropped_msgs, argp, typeof(*cmd), + dropped_msgs)) + ret = -EFAULT; + +exit: + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_free() - handle KDBUS_CMD_FREE + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_free(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_cmd_free *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn) && + !kdbus_conn_is_monitor(conn) && + !kdbus_conn_is_activator(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + ret = kdbus_pool_release_offset(conn->pool, cmd->offset); + + return kdbus_args_clear(&args, ret); +} diff --git a/ipc/kdbus/connection.h b/ipc/kdbus/connection.h new file mode 100644 index 000000000000..1ad082014faa --- /dev/null +++ b/ipc/kdbus/connection.h @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_CONNECTION_H +#define __KDBUS_CONNECTION_H + +#include <linux/atomic.h> +#include <linux/kref.h> +#include <linux/lockdep.h> +#include <linux/path.h> + +#include "limits.h" +#include "metadata.h" +#include "pool.h" +#include "queue.h" +#include "util.h" + +#define KDBUS_HELLO_SPECIAL_CONN (KDBUS_HELLO_ACTIVATOR | \ + KDBUS_HELLO_POLICY_HOLDER | \ + KDBUS_HELLO_MONITOR) + +struct kdbus_name_entry; +struct kdbus_quota; +struct kdbus_staging; + +/** + * struct kdbus_conn - connection to a bus + * @kref: Reference count + * @active: Active references to the connection + * @id: Connection ID + * @flags: KDBUS_HELLO_* flags + * @attach_flags_send: KDBUS_ATTACH_* flags for sending + * @attach_flags_recv: KDBUS_ATTACH_* flags for receiving + * @description: Human-readable connection description, used for + * debugging. This field is only set when the + * connection is created. + * @ep: The endpoint this connection belongs to + * @lock: Connection data lock + * @hentry: Entry in ID <-> connection map + * @ep_entry: Entry in endpoint + * @monitor_entry: Entry in monitor, if the connection is a monitor + * @reply_list: List of connections this connection should + * reply to + * @work: Delayed work to handle timeouts + * activator for + * @match_db: Subscription filter to broadcast messages + * @meta_proc: Process metadata of connection creator, or NULL + * @meta_fake: Faked metadata, or NULL + * @pool: The user's buffer to receive messages + * @user: Owner of the connection + * @cred: The credentials of the connection at creation time + * @pid: Pid at creation time + * @root_path: Root path at creation time + * @request_count: Number of pending requests issued by this + * connection that are waiting for replies from + * other peers + * @lost_count: Number of lost broadcast messages + * @wait: Wake up this endpoint + * @queue: The message queue associated with this connection + * @quota: Array of per-user quota indexed by user->id + * @n_quota: Number of elements in quota array + * @names_list: List of well-known names + * @name_count: Number of owned well-known names + * @privileged: Whether this connection is privileged on the domain + * @owner: Owned by the same user as the bus owner + */ +struct kdbus_conn { + struct kref kref; + atomic_t active; +#ifdef CONFIG_DEBUG_LOCK_ALLOC + struct lockdep_map dep_map; +#endif + u64 id; + u64 flags; + atomic64_t attach_flags_send; + atomic64_t attach_flags_recv; + const char *description; + struct kdbus_ep *ep; + struct mutex lock; + struct hlist_node hentry; + struct list_head ep_entry; + struct list_head monitor_entry; + struct list_head reply_list; + struct delayed_work work; + struct kdbus_match_db *match_db; + struct kdbus_meta_proc *meta_proc; + struct kdbus_meta_fake *meta_fake; + struct kdbus_pool *pool; + struct kdbus_user *user; + const struct cred *cred; + struct pid *pid; + struct path root_path; + atomic_t request_count; + atomic_t lost_count; + wait_queue_head_t wait; + struct kdbus_queue queue; + + struct kdbus_quota *quota; + unsigned int n_quota; + + /* protected by registry->rwlock */ + struct list_head names_list; + unsigned int name_count; + + bool privileged:1; + bool owner:1; +}; + +struct kdbus_conn *kdbus_conn_ref(struct kdbus_conn *conn); +struct kdbus_conn *kdbus_conn_unref(struct kdbus_conn *conn); +bool kdbus_conn_active(const struct kdbus_conn *conn); +int kdbus_conn_acquire(struct kdbus_conn *conn); +void kdbus_conn_release(struct kdbus_conn *conn); +int kdbus_conn_disconnect(struct kdbus_conn *conn, bool ensure_queue_empty); +bool kdbus_conn_has_name(struct kdbus_conn *conn, const char *name); +int kdbus_conn_quota_inc(struct kdbus_conn *c, struct kdbus_user *u, + size_t memory, size_t fds); +void kdbus_conn_quota_dec(struct kdbus_conn *c, struct kdbus_user *u, + size_t memory, size_t fds); +void kdbus_conn_lost_message(struct kdbus_conn *c); +int kdbus_conn_entry_insert(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst, + struct kdbus_staging *staging, + struct kdbus_reply *reply, + const struct kdbus_name_entry *name); +void kdbus_conn_move_messages(struct kdbus_conn *conn_dst, + struct kdbus_conn *conn_src, + u64 name_id); + +/* policy */ +bool kdbus_conn_policy_own_name(struct kdbus_conn *conn, + const struct cred *conn_creds, + const char *name); +bool kdbus_conn_policy_talk(struct kdbus_conn *conn, + const struct cred *conn_creds, + struct kdbus_conn *to); +bool kdbus_conn_policy_see_name_unlocked(struct kdbus_conn *conn, + const struct cred *curr_creds, + const char *name); +bool kdbus_conn_policy_see_notification(struct kdbus_conn *conn, + const struct cred *curr_creds, + const struct kdbus_msg *msg); + +/* command dispatcher */ +struct kdbus_conn *kdbus_cmd_hello(struct kdbus_ep *ep, struct file *file, + void __user *argp); +int kdbus_cmd_byebye_unlocked(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_conn_info(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_update(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_send(struct kdbus_conn *conn, struct file *f, void __user *argp); +int kdbus_cmd_recv(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_free(struct kdbus_conn *conn, void __user *argp); + +/** + * kdbus_conn_is_ordinary() - Check if connection is ordinary + * @conn: The connection to check + * + * Return: Non-zero if the connection is an ordinary connection + */ +static inline int kdbus_conn_is_ordinary(const struct kdbus_conn *conn) +{ + return !(conn->flags & KDBUS_HELLO_SPECIAL_CONN); +} + +/** + * kdbus_conn_is_activator() - Check if connection is an activator + * @conn: The connection to check + * + * Return: Non-zero if the connection is an activator + */ +static inline int kdbus_conn_is_activator(const struct kdbus_conn *conn) +{ + return conn->flags & KDBUS_HELLO_ACTIVATOR; +} + +/** + * kdbus_conn_is_policy_holder() - Check if connection is a policy holder + * @conn: The connection to check + * + * Return: Non-zero if the connection is a policy holder + */ +static inline int kdbus_conn_is_policy_holder(const struct kdbus_conn *conn) +{ + return conn->flags & KDBUS_HELLO_POLICY_HOLDER; +} + +/** + * kdbus_conn_is_monitor() - Check if connection is a monitor + * @conn: The connection to check + * + * Return: Non-zero if the connection is a monitor + */ +static inline int kdbus_conn_is_monitor(const struct kdbus_conn *conn) +{ + return conn->flags & KDBUS_HELLO_MONITOR; +} + +/** + * kdbus_conn_lock2() - Lock two connections + * @a: connection A to lock or NULL + * @b: connection B to lock or NULL + * + * Lock two connections at once. As we need to have a stable locking order, we + * always lock the connection with lower memory address first. + */ +static inline void kdbus_conn_lock2(struct kdbus_conn *a, struct kdbus_conn *b) +{ + if (a < b) { + if (a) + mutex_lock(&a->lock); + if (b && b != a) + mutex_lock_nested(&b->lock, !!a); + } else { + if (b) + mutex_lock(&b->lock); + if (a && a != b) + mutex_lock_nested(&a->lock, !!b); + } +} + +/** + * kdbus_conn_unlock2() - Unlock two connections + * @a: connection A to unlock or NULL + * @b: connection B to unlock or NULL + * + * Unlock two connections at once. See kdbus_conn_lock2(). + */ +static inline void kdbus_conn_unlock2(struct kdbus_conn *a, + struct kdbus_conn *b) +{ + if (a) + mutex_unlock(&a->lock); + if (b && b != a) + mutex_unlock(&b->lock); +} + +/** + * kdbus_conn_assert_active() - lockdep assert on active lock + * @conn: connection that shall be active + * + * This verifies via lockdep that the caller holds an active reference to the + * given connection. + */ +static inline void kdbus_conn_assert_active(struct kdbus_conn *conn) +{ + lockdep_assert_held(conn); +} + +#endif diff --git a/ipc/kdbus/domain.c b/ipc/kdbus/domain.c new file mode 100644 index 000000000000..ac9f760c150d --- /dev/null +++ b/ipc/kdbus/domain.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "bus.h" +#include "domain.h" +#include "handle.h" +#include "item.h" +#include "limits.h" +#include "util.h" + +static void kdbus_domain_control_free(struct kdbus_node *node) +{ + kfree(node); +} + +static struct kdbus_node *kdbus_domain_control_new(struct kdbus_domain *domain, + unsigned int access) +{ + struct kdbus_node *node; + int ret; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return ERR_PTR(-ENOMEM); + + kdbus_node_init(node, KDBUS_NODE_CONTROL); + + node->free_cb = kdbus_domain_control_free; + node->mode = domain->node.mode; + node->mode = S_IRUSR | S_IWUSR; + if (access & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD)) + node->mode |= S_IRGRP | S_IWGRP; + if (access & KDBUS_MAKE_ACCESS_WORLD) + node->mode |= S_IROTH | S_IWOTH; + + ret = kdbus_node_link(node, &domain->node, "control"); + if (ret < 0) + goto exit_free; + + return node; + +exit_free: + kdbus_node_deactivate(node); + kdbus_node_unref(node); + return ERR_PTR(ret); +} + +static void kdbus_domain_free(struct kdbus_node *node) +{ + struct kdbus_domain *domain = + container_of(node, struct kdbus_domain, node); + + put_user_ns(domain->user_namespace); + ida_destroy(&domain->user_ida); + idr_destroy(&domain->user_idr); + kfree(domain); +} + +/** + * kdbus_domain_new() - create a new domain + * @access: The access mode for this node (KDBUS_MAKE_ACCESS_*) + * + * Return: a new kdbus_domain on success, ERR_PTR on failure + */ +struct kdbus_domain *kdbus_domain_new(unsigned int access) +{ + struct kdbus_domain *d; + int ret; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return ERR_PTR(-ENOMEM); + + kdbus_node_init(&d->node, KDBUS_NODE_DOMAIN); + + d->node.free_cb = kdbus_domain_free; + d->node.mode = S_IRUSR | S_IXUSR; + if (access & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD)) + d->node.mode |= S_IRGRP | S_IXGRP; + if (access & KDBUS_MAKE_ACCESS_WORLD) + d->node.mode |= S_IROTH | S_IXOTH; + + mutex_init(&d->lock); + idr_init(&d->user_idr); + ida_init(&d->user_ida); + + /* Pin user namespace so we can guarantee domain-unique bus * names. */ + d->user_namespace = get_user_ns(current_user_ns()); + + ret = kdbus_node_link(&d->node, NULL, NULL); + if (ret < 0) + goto exit_unref; + + return d; + +exit_unref: + kdbus_node_deactivate(&d->node); + kdbus_node_unref(&d->node); + return ERR_PTR(ret); +} + +/** + * kdbus_domain_ref() - take a domain reference + * @domain: Domain + * + * Return: the domain itself + */ +struct kdbus_domain *kdbus_domain_ref(struct kdbus_domain *domain) +{ + if (domain) + kdbus_node_ref(&domain->node); + return domain; +} + +/** + * kdbus_domain_unref() - drop a domain reference + * @domain: Domain + * + * When the last reference is dropped, the domain internal structure + * is freed. + * + * Return: NULL + */ +struct kdbus_domain *kdbus_domain_unref(struct kdbus_domain *domain) +{ + if (domain) + kdbus_node_unref(&domain->node); + return NULL; +} + +/** + * kdbus_domain_populate() - populate static domain nodes + * @domain: domain to populate + * @access: KDBUS_MAKE_ACCESS_* access restrictions for new nodes + * + * Allocate and activate static sub-nodes of the given domain. This will fail if + * you call it on a non-active node or if the domain was already populated. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_domain_populate(struct kdbus_domain *domain, unsigned int access) +{ + struct kdbus_node *control; + + /* + * Create a control-node for this domain. We drop our own reference + * immediately, effectively causing the node to be deactivated and + * released when the parent domain is. + */ + control = kdbus_domain_control_new(domain, access); + if (IS_ERR(control)) + return PTR_ERR(control); + + kdbus_node_activate(control); + kdbus_node_unref(control); + return 0; +} + +/** + * kdbus_user_lookup() - lookup a kdbus_user object + * @domain: domain of the user + * @uid: uid of the user; INVALID_UID for an anon user + * + * Lookup the kdbus user accounting object for the given domain. If INVALID_UID + * is passed, a new anonymous user is created which is private to the caller. + * + * Return: The user object is returned, ERR_PTR on failure. + */ +struct kdbus_user *kdbus_user_lookup(struct kdbus_domain *domain, kuid_t uid) +{ + struct kdbus_user *u = NULL, *old = NULL; + int ret; + + mutex_lock(&domain->lock); + + if (uid_valid(uid)) { + old = idr_find(&domain->user_idr, __kuid_val(uid)); + /* + * If the object is about to be destroyed, ignore it and + * replace the slot in the IDR later on. + */ + if (old && kref_get_unless_zero(&old->kref)) { + mutex_unlock(&domain->lock); + return old; + } + } + + u = kzalloc(sizeof(*u), GFP_KERNEL); + if (!u) { + ret = -ENOMEM; + goto exit; + } + + kref_init(&u->kref); + u->domain = kdbus_domain_ref(domain); + u->uid = uid; + atomic_set(&u->buses, 0); + atomic_set(&u->connections, 0); + + if (uid_valid(uid)) { + if (old) { + idr_replace(&domain->user_idr, u, __kuid_val(uid)); + old->uid = INVALID_UID; /* mark old as removed */ + } else { + ret = idr_alloc(&domain->user_idr, u, __kuid_val(uid), + __kuid_val(uid) + 1, GFP_KERNEL); + if (ret < 0) + goto exit; + } + } + + /* + * Allocate the smallest possible index for this user; used + * in arrays for accounting user quota in receiver queues. + */ + ret = ida_simple_get(&domain->user_ida, 1, 0, GFP_KERNEL); + if (ret < 0) + goto exit; + + u->id = ret; + mutex_unlock(&domain->lock); + return u; + +exit: + if (u) { + if (uid_valid(u->uid)) + idr_remove(&domain->user_idr, __kuid_val(u->uid)); + kdbus_domain_unref(u->domain); + kfree(u); + } + mutex_unlock(&domain->lock); + return ERR_PTR(ret); +} + +static void __kdbus_user_free(struct kref *kref) +{ + struct kdbus_user *user = container_of(kref, struct kdbus_user, kref); + + WARN_ON(atomic_read(&user->buses) > 0); + WARN_ON(atomic_read(&user->connections) > 0); + + mutex_lock(&user->domain->lock); + ida_simple_remove(&user->domain->user_ida, user->id); + if (uid_valid(user->uid)) + idr_remove(&user->domain->user_idr, __kuid_val(user->uid)); + mutex_unlock(&user->domain->lock); + + kdbus_domain_unref(user->domain); + kfree(user); +} + +/** + * kdbus_user_ref() - take a user reference + * @u: User + * + * Return: @u is returned + */ +struct kdbus_user *kdbus_user_ref(struct kdbus_user *u) +{ + if (u) + kref_get(&u->kref); + return u; +} + +/** + * kdbus_user_unref() - drop a user reference + * @u: User + * + * Return: NULL + */ +struct kdbus_user *kdbus_user_unref(struct kdbus_user *u) +{ + if (u) + kref_put(&u->kref, __kdbus_user_free); + return NULL; +} diff --git a/ipc/kdbus/domain.h b/ipc/kdbus/domain.h new file mode 100644 index 000000000000..447a2bd4d972 --- /dev/null +++ b/ipc/kdbus/domain.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_DOMAIN_H +#define __KDBUS_DOMAIN_H + +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/kref.h> +#include <linux/user_namespace.h> + +#include "node.h" + +/** + * struct kdbus_domain - domain for buses + * @node: Underlying API node + * @lock: Domain data lock + * @last_id: Last used object id + * @user_idr: Set of all users indexed by UID + * @user_ida: Set of all users to compute small indices + * @user_namespace: User namespace, pinned at creation time + * @dentry: Root dentry of VFS mount (don't use outside of kdbusfs) + */ +struct kdbus_domain { + struct kdbus_node node; + struct mutex lock; + atomic64_t last_id; + struct idr user_idr; + struct ida user_ida; + struct user_namespace *user_namespace; + struct dentry *dentry; +}; + +/** + * struct kdbus_user - resource accounting for users + * @kref: Reference counter + * @domain: Domain of the user + * @id: Index of this user + * @uid: UID of the user + * @buses: Number of buses the user has created + * @connections: Number of connections the user has created + */ +struct kdbus_user { + struct kref kref; + struct kdbus_domain *domain; + unsigned int id; + kuid_t uid; + atomic_t buses; + atomic_t connections; +}; + +#define kdbus_domain_from_node(_node) \ + container_of((_node), struct kdbus_domain, node) + +struct kdbus_domain *kdbus_domain_new(unsigned int access); +struct kdbus_domain *kdbus_domain_ref(struct kdbus_domain *domain); +struct kdbus_domain *kdbus_domain_unref(struct kdbus_domain *domain); +int kdbus_domain_populate(struct kdbus_domain *domain, unsigned int access); + +#define KDBUS_USER_KERNEL_ID 0 /* ID 0 is reserved for kernel accounting */ + +struct kdbus_user *kdbus_user_lookup(struct kdbus_domain *domain, kuid_t uid); +struct kdbus_user *kdbus_user_ref(struct kdbus_user *u); +struct kdbus_user *kdbus_user_unref(struct kdbus_user *u); + +#endif diff --git a/ipc/kdbus/endpoint.c b/ipc/kdbus/endpoint.c new file mode 100644 index 000000000000..44e7a20de9c2 --- /dev/null +++ b/ipc/kdbus/endpoint.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/uio.h> + +#include "bus.h" +#include "connection.h" +#include "domain.h" +#include "endpoint.h" +#include "handle.h" +#include "item.h" +#include "message.h" +#include "policy.h" + +static void kdbus_ep_free(struct kdbus_node *node) +{ + struct kdbus_ep *ep = container_of(node, struct kdbus_ep, node); + + WARN_ON(!list_empty(&ep->conn_list)); + + kdbus_policy_db_clear(&ep->policy_db); + kdbus_bus_unref(ep->bus); + kdbus_user_unref(ep->user); + kfree(ep); +} + +static void kdbus_ep_release(struct kdbus_node *node, bool was_active) +{ + struct kdbus_ep *ep = container_of(node, struct kdbus_ep, node); + + /* disconnect all connections to this endpoint */ + for (;;) { + struct kdbus_conn *conn; + + mutex_lock(&ep->lock); + conn = list_first_entry_or_null(&ep->conn_list, + struct kdbus_conn, + ep_entry); + if (!conn) { + mutex_unlock(&ep->lock); + break; + } + + /* take reference, release lock, disconnect without lock */ + kdbus_conn_ref(conn); + mutex_unlock(&ep->lock); + + kdbus_conn_disconnect(conn, false); + kdbus_conn_unref(conn); + } +} + +/** + * kdbus_ep_new() - create a new endpoint + * @bus: The bus this endpoint will be created for + * @name: The name of the endpoint + * @access: The access flags for this node (KDBUS_MAKE_ACCESS_*) + * @uid: The uid of the node + * @gid: The gid of the node + * @is_custom: Whether this is a custom endpoint + * + * This function will create a new endpoint with the given + * name and properties for a given bus. + * + * Return: a new kdbus_ep on success, ERR_PTR on failure. + */ +struct kdbus_ep *kdbus_ep_new(struct kdbus_bus *bus, const char *name, + unsigned int access, kuid_t uid, kgid_t gid, + bool is_custom) +{ + struct kdbus_ep *e; + int ret; + + /* + * Validate only custom endpoints names, default endpoints + * with a "bus" name are created when the bus is created + */ + if (is_custom) { + ret = kdbus_verify_uid_prefix(name, bus->domain->user_namespace, + uid); + if (ret < 0) + return ERR_PTR(ret); + } + + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) + return ERR_PTR(-ENOMEM); + + kdbus_node_init(&e->node, KDBUS_NODE_ENDPOINT); + + e->node.free_cb = kdbus_ep_free; + e->node.release_cb = kdbus_ep_release; + e->node.uid = uid; + e->node.gid = gid; + e->node.mode = S_IRUSR | S_IWUSR; + if (access & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD)) + e->node.mode |= S_IRGRP | S_IWGRP; + if (access & KDBUS_MAKE_ACCESS_WORLD) + e->node.mode |= S_IROTH | S_IWOTH; + + mutex_init(&e->lock); + INIT_LIST_HEAD(&e->conn_list); + kdbus_policy_db_init(&e->policy_db); + e->bus = kdbus_bus_ref(bus); + + ret = kdbus_node_link(&e->node, &bus->node, name); + if (ret < 0) + goto exit_unref; + + /* + * Transactions on custom endpoints are never accounted on the global + * user limits. Instead, for each custom endpoint, we create a custom, + * unique user, which all transactions are accounted on. Regardless of + * the user using that endpoint, it is always accounted on the same + * user-object. This budget is not shared with ordinary users on + * non-custom endpoints. + */ + if (is_custom) { + e->user = kdbus_user_lookup(bus->domain, INVALID_UID); + if (IS_ERR(e->user)) { + ret = PTR_ERR(e->user); + e->user = NULL; + goto exit_unref; + } + } + + return e; + +exit_unref: + kdbus_node_deactivate(&e->node); + kdbus_node_unref(&e->node); + return ERR_PTR(ret); +} + +/** + * kdbus_ep_ref() - increase the reference counter of a kdbus_ep + * @ep: The endpoint to reference + * + * Every user of an endpoint, except for its creator, must add a reference to + * the kdbus_ep instance using this function. + * + * Return: the ep itself + */ +struct kdbus_ep *kdbus_ep_ref(struct kdbus_ep *ep) +{ + if (ep) + kdbus_node_ref(&ep->node); + return ep; +} + +/** + * kdbus_ep_unref() - decrease the reference counter of a kdbus_ep + * @ep: The ep to unref + * + * Release a reference. If the reference count drops to 0, the ep will be + * freed. + * + * Return: NULL + */ +struct kdbus_ep *kdbus_ep_unref(struct kdbus_ep *ep) +{ + if (ep) + kdbus_node_unref(&ep->node); + return NULL; +} + +/** + * kdbus_ep_is_privileged() - check whether a file is privileged + * @ep: endpoint to operate on + * @file: file to test + * + * Return: True if @file is privileged in the domain of @ep. + */ +bool kdbus_ep_is_privileged(struct kdbus_ep *ep, struct file *file) +{ + return !ep->user && + file_ns_capable(file, ep->bus->domain->user_namespace, + CAP_IPC_OWNER); +} + +/** + * kdbus_ep_is_owner() - check whether a file should be treated as bus owner + * @ep: endpoint to operate on + * @file: file to test + * + * Return: True if @file should be treated as bus owner on @ep + */ +bool kdbus_ep_is_owner(struct kdbus_ep *ep, struct file *file) +{ + return !ep->user && + (uid_eq(file->f_cred->euid, ep->bus->node.uid) || + kdbus_ep_is_privileged(ep, file)); +} + +/** + * kdbus_cmd_ep_make() - handle KDBUS_CMD_ENDPOINT_MAKE + * @bus: bus to operate on + * @argp: command payload + * + * Return: NULL or newly created endpoint on success, ERR_PTR on failure. + */ +struct kdbus_ep *kdbus_cmd_ep_make(struct kdbus_bus *bus, void __user *argp) +{ + const char *item_make_name; + struct kdbus_ep *ep = NULL; + struct kdbus_cmd *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_MAKE_NAME, .mandatory = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_MAKE_ACCESS_GROUP | + KDBUS_MAKE_ACCESS_WORLD, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret < 0) + return ERR_PTR(ret); + if (ret > 0) + return NULL; + + item_make_name = argv[1].item->str; + + ep = kdbus_ep_new(bus, item_make_name, cmd->flags, + current_euid(), current_egid(), true); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + ep = NULL; + goto exit; + } + + if (!kdbus_node_activate(&ep->node)) { + ret = -ESHUTDOWN; + goto exit; + } + +exit: + ret = kdbus_args_clear(&args, ret); + if (ret < 0) { + if (ep) { + kdbus_node_deactivate(&ep->node); + kdbus_ep_unref(ep); + } + return ERR_PTR(ret); + } + return ep; +} + +/** + * kdbus_cmd_ep_update() - handle KDBUS_CMD_ENDPOINT_UPDATE + * @ep: endpoint to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_ep_update(struct kdbus_ep *ep, void __user *argp) +{ + struct kdbus_cmd *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_NAME, .multiple = true }, + { .type = KDBUS_ITEM_POLICY_ACCESS, .multiple = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + ret = kdbus_policy_set(&ep->policy_db, args.items, args.items_size, + 0, true, ep); + return kdbus_args_clear(&args, ret); +} diff --git a/ipc/kdbus/endpoint.h b/ipc/kdbus/endpoint.h new file mode 100644 index 000000000000..e0da59f01a7a --- /dev/null +++ b/ipc/kdbus/endpoint.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_ENDPOINT_H +#define __KDBUS_ENDPOINT_H + +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/uidgid.h> +#include "node.h" +#include "policy.h" + +struct kdbus_bus; +struct kdbus_user; + +/** + * struct kdbus_ep - endpoint to access a bus + * @node: The kdbus node + * @lock: Endpoint data lock + * @bus: Bus behind this endpoint + * @user: Custom enpoints account against an anonymous user + * @policy_db: Uploaded policy + * @conn_list: Connections of this endpoint + * + * An endpoint offers access to a bus; the default endpoint node name is "bus". + * Additional custom endpoints to the same bus can be created and they can + * carry their own policies/filters. + */ +struct kdbus_ep { + struct kdbus_node node; + struct mutex lock; + + /* static */ + struct kdbus_bus *bus; + struct kdbus_user *user; + + /* protected by own locks */ + struct kdbus_policy_db policy_db; + + /* protected by ep->lock */ + struct list_head conn_list; +}; + +#define kdbus_ep_from_node(_node) \ + container_of((_node), struct kdbus_ep, node) + +struct kdbus_ep *kdbus_ep_new(struct kdbus_bus *bus, const char *name, + unsigned int access, kuid_t uid, kgid_t gid, + bool policy); +struct kdbus_ep *kdbus_ep_ref(struct kdbus_ep *ep); +struct kdbus_ep *kdbus_ep_unref(struct kdbus_ep *ep); + +bool kdbus_ep_is_privileged(struct kdbus_ep *ep, struct file *file); +bool kdbus_ep_is_owner(struct kdbus_ep *ep, struct file *file); + +struct kdbus_ep *kdbus_cmd_ep_make(struct kdbus_bus *bus, void __user *argp); +int kdbus_cmd_ep_update(struct kdbus_ep *ep, void __user *argp); + +#endif diff --git a/ipc/kdbus/fs.c b/ipc/kdbus/fs.c new file mode 100644 index 000000000000..09c480924b9e --- /dev/null +++ b/ipc/kdbus/fs.c @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/dcache.h> +#include <linux/fs.h> +#include <linux/fsnotify.h> +#include <linux/init.h> +#include <linux/ipc_namespace.h> +#include <linux/magic.h> +#include <linux/module.h> +#include <linux/mount.h> +#include <linux/mutex.h> +#include <linux/namei.h> +#include <linux/pagemap.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include "bus.h" +#include "domain.h" +#include "endpoint.h" +#include "fs.h" +#include "handle.h" +#include "node.h" + +#define kdbus_node_from_dentry(_dentry) \ + ((struct kdbus_node *)(_dentry)->d_fsdata) + +static struct inode *fs_inode_get(struct super_block *sb, + struct kdbus_node *node); + +/* + * Directory Management + */ + +static inline unsigned char kdbus_dt_type(struct kdbus_node *node) +{ + switch (node->type) { + case KDBUS_NODE_DOMAIN: + case KDBUS_NODE_BUS: + return DT_DIR; + case KDBUS_NODE_CONTROL: + case KDBUS_NODE_ENDPOINT: + return DT_REG; + } + + return DT_UNKNOWN; +} + +static int fs_dir_fop_iterate(struct file *file, struct dir_context *ctx) +{ + struct dentry *dentry = file->f_path.dentry; + struct kdbus_node *parent = kdbus_node_from_dentry(dentry); + struct kdbus_node *old, *next = file->private_data; + + /* + * kdbusfs directory iterator (modelled after sysfs/kernfs) + * When iterating kdbusfs directories, we iterate all children of the + * parent kdbus_node object. We use ctx->pos to store the hash of the + * child and file->private_data to store a reference to the next node + * object. If ctx->pos is not modified via llseek while you iterate a + * directory, then we use the file->private_data node pointer to + * directly access the next node in the tree. + * However, if you directly seek on the directory, we have to find the + * closest node to that position and cannot use our node pointer. This + * means iterating the rb-tree to find the closest match and start over + * from there. + * Note that hash values are not necessarily unique. Therefore, llseek + * is not guaranteed to seek to the same node that you got when you + * retrieved the position. Seeking to 0, 1, 2 and >=INT_MAX is safe, + * though. We could use the inode-number as position, but this would + * require another rb-tree for fast access. Kernfs and others already + * ignore those conflicts, so we should be fine, too. + */ + + if (!dir_emit_dots(file, ctx)) + return 0; + + /* acquire @next; if deactivated, or seek detected, find next node */ + old = next; + if (next && ctx->pos == next->hash) { + if (kdbus_node_acquire(next)) + kdbus_node_ref(next); + else + next = kdbus_node_next_child(parent, next); + } else { + next = kdbus_node_find_closest(parent, ctx->pos); + } + kdbus_node_unref(old); + + while (next) { + /* emit @next */ + file->private_data = next; + ctx->pos = next->hash; + + kdbus_node_release(next); + + if (!dir_emit(ctx, next->name, strlen(next->name), next->id, + kdbus_dt_type(next))) + return 0; + + /* find next node after @next */ + old = next; + next = kdbus_node_next_child(parent, next); + kdbus_node_unref(old); + } + + file->private_data = NULL; + ctx->pos = INT_MAX; + + return 0; +} + +static loff_t fs_dir_fop_llseek(struct file *file, loff_t offset, int whence) +{ + struct inode *inode = file_inode(file); + loff_t ret; + + /* protect f_off against fop_iterate */ + mutex_lock(&inode->i_mutex); + ret = generic_file_llseek(file, offset, whence); + mutex_unlock(&inode->i_mutex); + + return ret; +} + +static int fs_dir_fop_release(struct inode *inode, struct file *file) +{ + kdbus_node_unref(file->private_data); + return 0; +} + +static const struct file_operations fs_dir_fops = { + .read = generic_read_dir, + .iterate = fs_dir_fop_iterate, + .llseek = fs_dir_fop_llseek, + .release = fs_dir_fop_release, +}; + +static struct dentry *fs_dir_iop_lookup(struct inode *dir, + struct dentry *dentry, + unsigned int flags) +{ + struct dentry *dnew = NULL; + struct kdbus_node *parent; + struct kdbus_node *node; + struct inode *inode; + + parent = kdbus_node_from_dentry(dentry->d_parent); + if (!kdbus_node_acquire(parent)) + return NULL; + + /* returns reference to _acquired_ child node */ + node = kdbus_node_find_child(parent, dentry->d_name.name); + if (node) { + dentry->d_fsdata = node; + inode = fs_inode_get(dir->i_sb, node); + if (IS_ERR(inode)) + dnew = ERR_CAST(inode); + else + dnew = d_splice_alias(inode, dentry); + + kdbus_node_release(node); + } + + kdbus_node_release(parent); + return dnew; +} + +static const struct inode_operations fs_dir_iops = { + .permission = generic_permission, + .lookup = fs_dir_iop_lookup, +}; + +/* + * Inode Management + */ + +static const struct inode_operations fs_inode_iops = { + .permission = generic_permission, +}; + +static struct inode *fs_inode_get(struct super_block *sb, + struct kdbus_node *node) +{ + struct inode *inode; + + inode = iget_locked(sb, node->id); + if (!inode) + return ERR_PTR(-ENOMEM); + if (!(inode->i_state & I_NEW)) + return inode; + + inode->i_private = kdbus_node_ref(node); + inode->i_mapping->a_ops = &empty_aops; + inode->i_mode = node->mode & S_IALLUGO; + inode->i_atime = inode->i_ctime = inode->i_mtime = CURRENT_TIME; + inode->i_uid = node->uid; + inode->i_gid = node->gid; + + switch (node->type) { + case KDBUS_NODE_DOMAIN: + case KDBUS_NODE_BUS: + inode->i_mode |= S_IFDIR; + inode->i_op = &fs_dir_iops; + inode->i_fop = &fs_dir_fops; + set_nlink(inode, 2); + break; + case KDBUS_NODE_CONTROL: + case KDBUS_NODE_ENDPOINT: + inode->i_mode |= S_IFREG; + inode->i_op = &fs_inode_iops; + inode->i_fop = &kdbus_handle_ops; + break; + } + + unlock_new_inode(inode); + + return inode; +} + +/* + * Superblock Management + */ + +static int fs_super_dop_revalidate(struct dentry *dentry, unsigned int flags) +{ + struct kdbus_node *node; + + /* Force lookup on negatives */ + if (!dentry->d_inode) + return 0; + + node = kdbus_node_from_dentry(dentry); + + /* see whether the node has been removed */ + if (!kdbus_node_is_active(node)) + return 0; + + return 1; +} + +static void fs_super_dop_release(struct dentry *dentry) +{ + kdbus_node_unref(dentry->d_fsdata); +} + +static const struct dentry_operations fs_super_dops = { + .d_revalidate = fs_super_dop_revalidate, + .d_release = fs_super_dop_release, +}; + +static void fs_super_sop_evict_inode(struct inode *inode) +{ + struct kdbus_node *node = kdbus_node_from_inode(inode); + + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + kdbus_node_unref(node); +} + +static const struct super_operations fs_super_sops = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, + .evict_inode = fs_super_sop_evict_inode, +}; + +static int fs_super_fill(struct super_block *sb) +{ + struct kdbus_domain *domain = sb->s_fs_info; + struct inode *inode; + int ret; + + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = KDBUS_SUPER_MAGIC; + sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_op = &fs_super_sops; + sb->s_time_gran = 1; + + inode = fs_inode_get(sb, &domain->node); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + sb->s_root = d_make_root(inode); + if (!sb->s_root) { + /* d_make_root iput()s the inode on failure */ + return -ENOMEM; + } + + /* sb holds domain reference */ + sb->s_root->d_fsdata = &domain->node; + sb->s_d_op = &fs_super_dops; + + /* sb holds root reference */ + domain->dentry = sb->s_root; + + if (!kdbus_node_activate(&domain->node)) + return -ESHUTDOWN; + + ret = kdbus_domain_populate(domain, KDBUS_MAKE_ACCESS_WORLD); + if (ret < 0) + return ret; + + sb->s_flags |= MS_ACTIVE; + return 0; +} + +static void fs_super_kill(struct super_block *sb) +{ + struct kdbus_domain *domain = sb->s_fs_info; + + if (domain) { + kdbus_node_deactivate(&domain->node); + domain->dentry = NULL; + } + + kill_anon_super(sb); + kdbus_domain_unref(domain); +} + +static int fs_super_set(struct super_block *sb, void *data) +{ + int ret; + + ret = set_anon_super(sb, data); + if (!ret) + sb->s_fs_info = data; + + return ret; +} + +static struct dentry *fs_super_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *data) +{ + struct kdbus_domain *domain; + struct super_block *sb; + int ret; + + domain = kdbus_domain_new(KDBUS_MAKE_ACCESS_WORLD); + if (IS_ERR(domain)) + return ERR_CAST(domain); + + sb = sget(fs_type, NULL, fs_super_set, flags, domain); + if (IS_ERR(sb)) { + kdbus_node_deactivate(&domain->node); + kdbus_domain_unref(domain); + return ERR_CAST(sb); + } + + WARN_ON(sb->s_fs_info != domain); + WARN_ON(sb->s_root); + + ret = fs_super_fill(sb); + if (ret < 0) { + /* calls into ->kill_sb() when done */ + deactivate_locked_super(sb); + return ERR_PTR(ret); + } + + return dget(sb->s_root); +} + +static struct file_system_type fs_type = { + .name = KBUILD_MODNAME "fs", + .owner = THIS_MODULE, + .mount = fs_super_mount, + .kill_sb = fs_super_kill, + .fs_flags = FS_USERNS_MOUNT, +}; + +/** + * kdbus_fs_init() - register kdbus filesystem + * + * This registers a filesystem with the VFS layer. The filesystem is called + * `KBUILD_MODNAME "fs"', which usually resolves to `kdbusfs'. The nameing + * scheme allows to set KBUILD_MODNAME to "kdbus2" and you will get an + * independent filesystem for developers. + * + * Each mount of the kdbusfs filesystem has an kdbus_domain attached. + * Operations on this mount will only affect the attached domain. On each mount + * a new domain is automatically created and used for this mount exclusively. + * If you want to share a domain across multiple mounts, you need to bind-mount + * it. + * + * Mounts of kdbusfs (with a different domain each) are unrelated to each other + * and will never have any effect on any domain but their own. + * + * Return: 0 on success, negative error otherwise. + */ +int kdbus_fs_init(void) +{ + return register_filesystem(&fs_type); +} + +/** + * kdbus_fs_exit() - unregister kdbus filesystem + * + * This does the reverse to kdbus_fs_init(). It unregisters the kdbusfs + * filesystem from VFS and cleans up any allocated resources. + */ +void kdbus_fs_exit(void) +{ + unregister_filesystem(&fs_type); +} + +/* acquire domain of @node, making sure all ancestors are active */ +static struct kdbus_domain *fs_acquire_domain(struct kdbus_node *node) +{ + struct kdbus_domain *domain; + struct kdbus_node *iter; + + /* caller must guarantee that @node is linked */ + for (iter = node; iter->parent; iter = iter->parent) + if (!kdbus_node_is_active(iter->parent)) + return NULL; + + /* root nodes are always domains */ + if (WARN_ON(iter->type != KDBUS_NODE_DOMAIN)) + return NULL; + + domain = kdbus_domain_from_node(iter); + if (!kdbus_node_acquire(&domain->node)) + return NULL; + + return domain; +} + +/** + * kdbus_fs_flush() - flush dcache entries of a node + * @node: Node to flush entries of + * + * This flushes all VFS filesystem cache entries for a node and all its + * children. This should be called whenever a node is destroyed during + * runtime. It will flush the cache entries so the linked objects can be + * deallocated. + * + * This is a no-op if you call it on active nodes (they really should stay in + * cache) or on nodes with deactivated parents (flushing the parent is enough). + * Furthermore, there is no need to call it on nodes whose lifetime is bound to + * their parents'. In those cases, the parent-flush will always also flush the + * children. + */ +void kdbus_fs_flush(struct kdbus_node *node) +{ + struct dentry *dentry, *parent_dentry = NULL; + struct kdbus_domain *domain; + struct qstr name; + + /* active nodes should remain in cache */ + if (!kdbus_node_is_deactivated(node)) + return; + + /* nodes that were never linked were never instantiated */ + if (!node->parent) + return; + + /* acquire domain and verify all ancestors are active */ + domain = fs_acquire_domain(node); + if (!domain) + return; + + switch (node->type) { + case KDBUS_NODE_ENDPOINT: + if (WARN_ON(!node->parent || !node->parent->name)) + goto exit; + + name.name = node->parent->name; + name.len = strlen(node->parent->name); + parent_dentry = d_hash_and_lookup(domain->dentry, &name); + if (IS_ERR_OR_NULL(parent_dentry)) + goto exit; + + /* fallthrough */ + case KDBUS_NODE_BUS: + if (WARN_ON(!node->name)) + goto exit; + + name.name = node->name; + name.len = strlen(node->name); + dentry = d_hash_and_lookup(parent_dentry ? : domain->dentry, + &name); + if (!IS_ERR_OR_NULL(dentry)) { + d_invalidate(dentry); + dput(dentry); + } + + dput(parent_dentry); + break; + + default: + /* all other types are bound to their parent lifetime */ + break; + } + +exit: + kdbus_node_release(&domain->node); +} diff --git a/ipc/kdbus/fs.h b/ipc/kdbus/fs.h new file mode 100644 index 000000000000..62f7d6abf11e --- /dev/null +++ b/ipc/kdbus/fs.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUSFS_H +#define __KDBUSFS_H + +#include <linux/kernel.h> + +struct kdbus_node; + +int kdbus_fs_init(void); +void kdbus_fs_exit(void); +void kdbus_fs_flush(struct kdbus_node *node); + +#define kdbus_node_from_inode(_inode) \ + ((struct kdbus_node *)(_inode)->i_private) + +#endif diff --git a/ipc/kdbus/handle.c b/ipc/kdbus/handle.c new file mode 100644 index 000000000000..fc60932d69c7 --- /dev/null +++ b/ipc/kdbus/handle.c @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/kdev_t.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/syscalls.h> + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "fs.h" +#include "handle.h" +#include "item.h" +#include "match.h" +#include "message.h" +#include "names.h" +#include "domain.h" +#include "policy.h" + +static int kdbus_args_verify(struct kdbus_args *args) +{ + struct kdbus_item *item; + size_t i; + int ret; + + KDBUS_ITEMS_FOREACH(item, args->items, args->items_size) { + struct kdbus_arg *arg = NULL; + + if (!KDBUS_ITEM_VALID(item, args->items, args->items_size)) + return -EINVAL; + + for (i = 0; i < args->argc; ++i) + if (args->argv[i].type == item->type) + break; + if (i >= args->argc) + return -EINVAL; + + arg = &args->argv[i]; + + ret = kdbus_item_validate(item); + if (ret < 0) + return ret; + + if (arg->item && !arg->multiple) + return -EINVAL; + + arg->item = item; + } + + if (!KDBUS_ITEMS_END(item, args->items, args->items_size)) + return -EINVAL; + + return 0; +} + +static int kdbus_args_negotiate(struct kdbus_args *args) +{ + struct kdbus_item __user *user; + struct kdbus_item *negotiation; + size_t i, j, num; + + /* + * If KDBUS_FLAG_NEGOTIATE is set, we overwrite the flags field with + * the set of supported flags. Furthermore, if an KDBUS_ITEM_NEGOTIATE + * item is passed, we iterate its payload (array of u64, each set to an + * item type) and clear all unsupported item-types to 0. + * The caller might do this recursively, if other flags or objects are + * embedded in the payload itself. + */ + + if (args->cmd->flags & KDBUS_FLAG_NEGOTIATE) { + if (put_user(args->allowed_flags & ~KDBUS_FLAG_NEGOTIATE, + &args->user->flags)) + return -EFAULT; + } + + if (args->argc < 1 || args->argv[0].type != KDBUS_ITEM_NEGOTIATE || + !args->argv[0].item) + return 0; + + negotiation = args->argv[0].item; + user = (struct kdbus_item __user *) + ((u8 __user *)args->user + + ((u8 *)negotiation - (u8 *)args->cmd)); + num = KDBUS_ITEM_PAYLOAD_SIZE(negotiation) / sizeof(u64); + + for (i = 0; i < num; ++i) { + for (j = 0; j < args->argc; ++j) + if (negotiation->data64[i] == args->argv[j].type) + break; + + if (j < args->argc) + continue; + + /* this item is not supported, clear it out */ + negotiation->data64[i] = 0; + if (put_user(negotiation->data64[i], &user->data64[i])) + return -EFAULT; + } + + return 0; +} + +/** + * __kdbus_args_parse() - parse payload of kdbus command + * @args: object to parse data into + * @is_cmd: whether this is a command or msg payload + * @argp: user-space location of command payload to parse + * @type_size: overall size of command payload to parse + * @items_offset: offset of items array in command payload + * @out: output variable to store pointer to copied payload + * + * This parses the ioctl payload at user-space location @argp into @args. @args + * must be pre-initialized by the caller to reflect the supported flags and + * items of this command. This parser will then copy the command payload into + * kernel-space, verify correctness and consistency and cache pointers to parsed + * items and other data in @args. + * + * If this function succeeded, you must call kdbus_args_clear() to release + * allocated resources before destroying @args. + * + * This can also be used to import kdbus_msg objects. In that case, @is_cmd must + * be set to 'false' and the 'return_flags' field will not be touched (as it + * doesn't exist on kdbus_msg). + * + * Return: On failure a negative error code is returned. Otherwise, 1 is + * returned if negotiation was requested, 0 if not. + */ +int __kdbus_args_parse(struct kdbus_args *args, bool is_cmd, void __user *argp, + size_t type_size, size_t items_offset, void **out) +{ + u64 user_size; + int ret, i; + + ret = kdbus_copy_from_user(&user_size, argp, sizeof(user_size)); + if (ret < 0) + return ret; + + if (user_size < type_size) + return -EINVAL; + if (user_size > KDBUS_CMD_MAX_SIZE) + return -EMSGSIZE; + + if (user_size <= sizeof(args->cmd_buf)) { + if (copy_from_user(args->cmd_buf, argp, user_size)) + return -EFAULT; + args->cmd = (void*)args->cmd_buf; + } else { + args->cmd = memdup_user(argp, user_size); + if (IS_ERR(args->cmd)) + return PTR_ERR(args->cmd); + } + + if (args->cmd->size != user_size) { + ret = -EINVAL; + goto error; + } + + if (is_cmd) + args->cmd->return_flags = 0; + args->user = argp; + args->items = (void *)((u8 *)args->cmd + items_offset); + args->items_size = args->cmd->size - items_offset; + args->is_cmd = is_cmd; + + if (args->cmd->flags & ~args->allowed_flags) { + ret = -EINVAL; + goto error; + } + + ret = kdbus_args_verify(args); + if (ret < 0) + goto error; + + ret = kdbus_args_negotiate(args); + if (ret < 0) + goto error; + + /* mandatory items must be given (but not on negotiation) */ + if (!(args->cmd->flags & KDBUS_FLAG_NEGOTIATE)) { + for (i = 0; i < args->argc; ++i) + if (args->argv[i].mandatory && !args->argv[i].item) { + ret = -EINVAL; + goto error; + } + } + + *out = args->cmd; + return !!(args->cmd->flags & KDBUS_FLAG_NEGOTIATE); + +error: + return kdbus_args_clear(args, ret); +} + +/** + * kdbus_args_clear() - release allocated command resources + * @args: object to release resources of + * @ret: return value of this command + * + * This frees all allocated resources on @args and copies the command result + * flags into user-space. @ret is usually returned unchanged by this function, + * so it can be used in the final 'return' statement of the command handler. + * + * Return: -EFAULT if return values cannot be copied into user-space, otherwise + * @ret is returned unchanged. + */ +int kdbus_args_clear(struct kdbus_args *args, int ret) +{ + if (!args) + return ret; + + if (!IS_ERR_OR_NULL(args->cmd)) { + if (args->is_cmd && put_user(args->cmd->return_flags, + &args->user->return_flags)) + ret = -EFAULT; + if (args->cmd != (void*)args->cmd_buf) + kfree(args->cmd); + args->cmd = NULL; + } + + return ret; +} + +/** + * enum kdbus_handle_type - type an handle can be of + * @KDBUS_HANDLE_NONE: no type set, yet + * @KDBUS_HANDLE_BUS_OWNER: bus owner + * @KDBUS_HANDLE_EP_OWNER: endpoint owner + * @KDBUS_HANDLE_CONNECTED: endpoint connection after HELLO + */ +enum kdbus_handle_type { + KDBUS_HANDLE_NONE, + KDBUS_HANDLE_BUS_OWNER, + KDBUS_HANDLE_EP_OWNER, + KDBUS_HANDLE_CONNECTED, +}; + +/** + * struct kdbus_handle - handle to the kdbus system + * @lock: handle lock + * @type: type of this handle (KDBUS_HANDLE_*) + * @bus_owner: bus this handle owns + * @ep_owner: endpoint this handle owns + * @conn: connection this handle owns + */ +struct kdbus_handle { + struct mutex lock; + + enum kdbus_handle_type type; + union { + struct kdbus_bus *bus_owner; + struct kdbus_ep *ep_owner; + struct kdbus_conn *conn; + }; +}; + +static int kdbus_handle_open(struct inode *inode, struct file *file) +{ + struct kdbus_handle *handle; + struct kdbus_node *node; + int ret; + + node = kdbus_node_from_inode(inode); + if (!kdbus_node_acquire(node)) + return -ESHUTDOWN; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) { + ret = -ENOMEM; + goto exit; + } + + mutex_init(&handle->lock); + handle->type = KDBUS_HANDLE_NONE; + + file->private_data = handle; + ret = 0; + +exit: + kdbus_node_release(node); + return ret; +} + +static int kdbus_handle_release(struct inode *inode, struct file *file) +{ + struct kdbus_handle *handle = file->private_data; + + switch (handle->type) { + case KDBUS_HANDLE_BUS_OWNER: + if (handle->bus_owner) { + kdbus_node_deactivate(&handle->bus_owner->node); + kdbus_bus_unref(handle->bus_owner); + } + break; + case KDBUS_HANDLE_EP_OWNER: + if (handle->ep_owner) { + kdbus_node_deactivate(&handle->ep_owner->node); + kdbus_ep_unref(handle->ep_owner); + } + break; + case KDBUS_HANDLE_CONNECTED: + kdbus_conn_disconnect(handle->conn, false); + kdbus_conn_unref(handle->conn); + break; + case KDBUS_HANDLE_NONE: + /* nothing to clean up */ + break; + } + + kfree(handle); + + return 0; +} + +static long kdbus_handle_ioctl_control(struct file *file, unsigned int cmd, + void __user *argp) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_node *node = file_inode(file)->i_private; + struct kdbus_domain *domain; + int ret = 0; + + if (!kdbus_node_acquire(node)) + return -ESHUTDOWN; + + /* + * The parent of control-nodes is always a domain, make sure to pin it + * so the parent is actually valid. + */ + domain = kdbus_domain_from_node(node->parent); + if (!kdbus_node_acquire(&domain->node)) { + kdbus_node_release(node); + return -ESHUTDOWN; + } + + switch (cmd) { + case KDBUS_CMD_BUS_MAKE: { + struct kdbus_bus *bus; + + bus = kdbus_cmd_bus_make(domain, argp); + if (IS_ERR_OR_NULL(bus)) { + ret = PTR_ERR_OR_ZERO(bus); + break; + } + + handle->bus_owner = bus; + ret = KDBUS_HANDLE_BUS_OWNER; + break; + } + + default: + ret = -EBADFD; + break; + } + + kdbus_node_release(&domain->node); + kdbus_node_release(node); + return ret; +} + +static long kdbus_handle_ioctl_ep(struct file *file, unsigned int cmd, + void __user *buf) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_node *node = file_inode(file)->i_private; + struct kdbus_ep *ep, *file_ep = kdbus_ep_from_node(node); + struct kdbus_bus *bus = file_ep->bus; + struct kdbus_conn *conn; + int ret = 0; + + if (!kdbus_node_acquire(node)) + return -ESHUTDOWN; + + switch (cmd) { + case KDBUS_CMD_ENDPOINT_MAKE: { + /* creating custom endpoints is a privileged operation */ + if (!kdbus_ep_is_owner(file_ep, file)) { + ret = -EPERM; + break; + } + + ep = kdbus_cmd_ep_make(bus, buf); + if (IS_ERR_OR_NULL(ep)) { + ret = PTR_ERR_OR_ZERO(ep); + break; + } + + handle->ep_owner = ep; + ret = KDBUS_HANDLE_EP_OWNER; + break; + } + + case KDBUS_CMD_HELLO: + conn = kdbus_cmd_hello(file_ep, file, buf); + if (IS_ERR_OR_NULL(conn)) { + ret = PTR_ERR_OR_ZERO(conn); + break; + } + + handle->conn = conn; + ret = KDBUS_HANDLE_CONNECTED; + break; + + default: + ret = -EBADFD; + break; + } + + kdbus_node_release(node); + return ret; +} + +static long kdbus_handle_ioctl_ep_owner(struct file *file, unsigned int command, + void __user *buf) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_ep *ep = handle->ep_owner; + int ret; + + if (!kdbus_node_acquire(&ep->node)) + return -ESHUTDOWN; + + switch (command) { + case KDBUS_CMD_ENDPOINT_UPDATE: + ret = kdbus_cmd_ep_update(ep, buf); + break; + default: + ret = -EBADFD; + break; + } + + kdbus_node_release(&ep->node); + return ret; +} + +static long kdbus_handle_ioctl_connected(struct file *file, + unsigned int command, void __user *buf) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_conn *conn = handle->conn; + struct kdbus_conn *release_conn = NULL; + int ret; + + release_conn = conn; + ret = kdbus_conn_acquire(release_conn); + if (ret < 0) + return ret; + + switch (command) { + case KDBUS_CMD_BYEBYE: + /* + * BYEBYE is special; we must not acquire a connection when + * calling into kdbus_conn_disconnect() or we will deadlock, + * because kdbus_conn_disconnect() will wait for all acquired + * references to be dropped. + */ + kdbus_conn_release(release_conn); + release_conn = NULL; + ret = kdbus_cmd_byebye_unlocked(conn, buf); + break; + case KDBUS_CMD_NAME_ACQUIRE: + ret = kdbus_cmd_name_acquire(conn, buf); + break; + case KDBUS_CMD_NAME_RELEASE: + ret = kdbus_cmd_name_release(conn, buf); + break; + case KDBUS_CMD_LIST: + ret = kdbus_cmd_list(conn, buf); + break; + case KDBUS_CMD_CONN_INFO: + ret = kdbus_cmd_conn_info(conn, buf); + break; + case KDBUS_CMD_BUS_CREATOR_INFO: + ret = kdbus_cmd_bus_creator_info(conn, buf); + break; + case KDBUS_CMD_UPDATE: + ret = kdbus_cmd_update(conn, buf); + break; + case KDBUS_CMD_MATCH_ADD: + ret = kdbus_cmd_match_add(conn, buf); + break; + case KDBUS_CMD_MATCH_REMOVE: + ret = kdbus_cmd_match_remove(conn, buf); + break; + case KDBUS_CMD_SEND: + ret = kdbus_cmd_send(conn, file, buf); + break; + case KDBUS_CMD_RECV: + ret = kdbus_cmd_recv(conn, buf); + break; + case KDBUS_CMD_FREE: + ret = kdbus_cmd_free(conn, buf); + break; + default: + ret = -EBADFD; + break; + } + + kdbus_conn_release(release_conn); + return ret; +} + +static long kdbus_handle_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_node *node = kdbus_node_from_inode(file_inode(file)); + void __user *argp = (void __user *)arg; + long ret = -EBADFD; + + switch (cmd) { + case KDBUS_CMD_BUS_MAKE: + case KDBUS_CMD_ENDPOINT_MAKE: + case KDBUS_CMD_HELLO: + mutex_lock(&handle->lock); + if (handle->type == KDBUS_HANDLE_NONE) { + if (node->type == KDBUS_NODE_CONTROL) + ret = kdbus_handle_ioctl_control(file, cmd, + argp); + else if (node->type == KDBUS_NODE_ENDPOINT) + ret = kdbus_handle_ioctl_ep(file, cmd, argp); + + if (ret > 0) { + /* + * The data given via open() is not sufficient + * to setup a kdbus handle. Hence, we require + * the user to perform a setup ioctl. This setup + * can only be performed once and defines the + * type of the handle. The different setup + * ioctls are locked against each other so they + * cannot race. Once the handle type is set, + * the type-dependent ioctls are enabled. To + * improve performance, we don't lock those via + * handle->lock. Instead, we issue a + * write-barrier before performing the + * type-change, which pairs with smp_rmb() in + * all handlers that access the type field. This + * guarantees the handle is fully setup, if + * handle->type is set. If handle->type is + * unset, you must not make any assumptions + * without taking handle->lock. + * Note that handle->type is only set once. It + * will never change afterwards. + */ + smp_wmb(); + handle->type = ret; + } + } + mutex_unlock(&handle->lock); + break; + + case KDBUS_CMD_ENDPOINT_UPDATE: + case KDBUS_CMD_BYEBYE: + case KDBUS_CMD_NAME_ACQUIRE: + case KDBUS_CMD_NAME_RELEASE: + case KDBUS_CMD_LIST: + case KDBUS_CMD_CONN_INFO: + case KDBUS_CMD_BUS_CREATOR_INFO: + case KDBUS_CMD_UPDATE: + case KDBUS_CMD_MATCH_ADD: + case KDBUS_CMD_MATCH_REMOVE: + case KDBUS_CMD_SEND: + case KDBUS_CMD_RECV: + case KDBUS_CMD_FREE: { + enum kdbus_handle_type type; + + /* + * This read-barrier pairs with smp_wmb() of the handle setup. + * it guarantees the handle is fully written, in case the + * type has been set. It allows us to access the handle without + * taking handle->lock, given the guarantee that the type is + * only ever set once, and stays constant afterwards. + * Furthermore, the handle object itself is not modified in any + * way after the type is set. That is, the type-field is the + * last field that is written on any handle. If it has not been + * set, we must not access the handle here. + */ + type = handle->type; + smp_rmb(); + + if (type == KDBUS_HANDLE_EP_OWNER) + ret = kdbus_handle_ioctl_ep_owner(file, cmd, argp); + else if (type == KDBUS_HANDLE_CONNECTED) + ret = kdbus_handle_ioctl_connected(file, cmd, argp); + + break; + } + default: + ret = -ENOTTY; + break; + } + + return ret < 0 ? ret : 0; +} + +static unsigned int kdbus_handle_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct kdbus_handle *handle = file->private_data; + enum kdbus_handle_type type; + unsigned int mask = POLLOUT | POLLWRNORM; + + /* + * This pairs with smp_wmb() during handle setup. It guarantees that + * _iff_ the handle type is set, handle->conn is valid. Furthermore, + * _iff_ the type is set, the handle object is constant and never + * changed again. If it's not set, we must not access the handle but + * bail out. We also must assume no setup has taken place, yet. + */ + type = handle->type; + smp_rmb(); + + /* Only a connected endpoint can read/write data */ + if (type != KDBUS_HANDLE_CONNECTED) + return POLLERR | POLLHUP; + + poll_wait(file, &handle->conn->wait, wait); + + /* + * Verify the connection hasn't been deactivated _after_ adding the + * wait-queue. This guarantees, that if the connection is deactivated + * after we checked it, the waitqueue is signaled and we're called + * again. + */ + if (!kdbus_conn_active(handle->conn)) + return POLLERR | POLLHUP; + + if (!list_empty(&handle->conn->queue.msg_list) || + atomic_read(&handle->conn->lost_count) > 0) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static int kdbus_handle_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct kdbus_handle *handle = file->private_data; + enum kdbus_handle_type type; + int ret = -EBADFD; + + /* + * This pairs with smp_wmb() during handle setup. It guarantees that + * _iff_ the handle type is set, handle->conn is valid. Furthermore, + * _iff_ the type is set, the handle object is constant and never + * changed again. If it's not set, we must not access the handle but + * bail out. We also must assume no setup has taken place, yet. + */ + type = handle->type; + smp_rmb(); + + /* Only connected handles have a pool we can map */ + if (type == KDBUS_HANDLE_CONNECTED) + ret = kdbus_pool_mmap(handle->conn->pool, vma); + + return ret; +} + +const struct file_operations kdbus_handle_ops = { + .owner = THIS_MODULE, + .open = kdbus_handle_open, + .release = kdbus_handle_release, + .poll = kdbus_handle_poll, + .llseek = noop_llseek, + .unlocked_ioctl = kdbus_handle_ioctl, + .mmap = kdbus_handle_mmap, +#ifdef CONFIG_COMPAT + .compat_ioctl = kdbus_handle_ioctl, +#endif +}; diff --git a/ipc/kdbus/handle.h b/ipc/kdbus/handle.h new file mode 100644 index 000000000000..5dde2c10bed4 --- /dev/null +++ b/ipc/kdbus/handle.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_HANDLE_H +#define __KDBUS_HANDLE_H + +#include <linux/fs.h> +#include <uapi/linux/kdbus.h> + +extern const struct file_operations kdbus_handle_ops; + +/** + * kdbus_arg - information and state of a single ioctl command item + * @type: item type + * @item: set by the parser to the first found item of this type + * @multiple: whether multiple items of this type are allowed + * @mandatory: whether at least one item of this type is required + * + * This structure describes a single item in an ioctl command payload. The + * caller has to pre-fill the type and flags, the parser will then use this + * information to verify the ioctl payload. @item is set by the parser to point + * to the first occurrence of the item. + */ +struct kdbus_arg { + u64 type; + struct kdbus_item *item; + bool multiple : 1; + bool mandatory : 1; +}; + +/** + * kdbus_args - information and state of ioctl command parser + * @allowed_flags: set of flags this command supports + * @argc: number of items in @argv + * @argv: array of items this command supports + * @user: set by parser to user-space location of current command + * @cmd: set by parser to kernel copy of command payload + * @cmd_buf: inline buf to avoid kmalloc() on small cmds + * @items: points to item array in @cmd + * @items_size: size of @items in bytes + * @is_cmd: whether this is a command-payload or msg-payload + * + * This structure is used to parse ioctl command payloads on each invocation. + * The ioctl handler has to pre-fill the flags and allowed items before passing + * the object to kdbus_args_parse(). The parser will copy the command payload + * into kernel-space and verify the correctness of the data. + * + * We use a 256 bytes buffer for small command payloads, to be allocated on + * stack on syscall entrance. + */ +struct kdbus_args { + u64 allowed_flags; + size_t argc; + struct kdbus_arg *argv; + + struct kdbus_cmd __user *user; + struct kdbus_cmd *cmd; + u8 cmd_buf[256]; + + struct kdbus_item *items; + size_t items_size; + bool is_cmd : 1; +}; + +int __kdbus_args_parse(struct kdbus_args *args, bool is_cmd, void __user *argp, + size_t type_size, size_t items_offset, void **out); +int kdbus_args_clear(struct kdbus_args *args, int ret); + +#define kdbus_args_parse(_args, _argp, _v) \ + ({ \ + BUILD_BUG_ON(offsetof(typeof(**(_v)), size) != \ + offsetof(struct kdbus_cmd, size)); \ + BUILD_BUG_ON(offsetof(typeof(**(_v)), flags) != \ + offsetof(struct kdbus_cmd, flags)); \ + BUILD_BUG_ON(offsetof(typeof(**(_v)), return_flags) != \ + offsetof(struct kdbus_cmd, return_flags)); \ + __kdbus_args_parse((_args), 1, (_argp), sizeof(**(_v)), \ + offsetof(typeof(**(_v)), items), \ + (void **)(_v)); \ + }) + +#define kdbus_args_parse_msg(_args, _argp, _v) \ + ({ \ + BUILD_BUG_ON(offsetof(typeof(**(_v)), size) != \ + offsetof(struct kdbus_cmd, size)); \ + BUILD_BUG_ON(offsetof(typeof(**(_v)), flags) != \ + offsetof(struct kdbus_cmd, flags)); \ + __kdbus_args_parse((_args), 0, (_argp), sizeof(**(_v)), \ + offsetof(typeof(**(_v)), items), \ + (void **)(_v)); \ + }) + +#endif diff --git a/ipc/kdbus/item.c b/ipc/kdbus/item.c new file mode 100644 index 000000000000..ce78dba03426 --- /dev/null +++ b/ipc/kdbus/item.c @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/ctype.h> +#include <linux/fs.h> +#include <linux/string.h> + +#include "item.h" +#include "limits.h" +#include "util.h" + +/* + * This verifies the string at position @str with size @size is properly + * zero-terminated and does not contain a 0-byte but at the end. + */ +static bool kdbus_str_valid(const char *str, size_t size) +{ + return size > 0 && memchr(str, '\0', size) == str + size - 1; +} + +/** + * kdbus_item_validate_name() - validate an item containing a name + * @item: Item to validate + * + * Return: zero on success or an negative error code on failure + */ +int kdbus_item_validate_name(const struct kdbus_item *item) +{ + const char *name = item->str; + unsigned int i; + size_t len; + + if (item->size < KDBUS_ITEM_HEADER_SIZE + 2) + return -EINVAL; + + if (item->size > KDBUS_ITEM_HEADER_SIZE + + KDBUS_SYSNAME_MAX_LEN + 1) + return -ENAMETOOLONG; + + if (!kdbus_str_valid(name, KDBUS_ITEM_PAYLOAD_SIZE(item))) + return -EINVAL; + + len = strlen(name); + if (len == 0) + return -EINVAL; + + for (i = 0; i < len; i++) { + if (isalpha(name[i])) + continue; + if (isdigit(name[i])) + continue; + if (name[i] == '_') + continue; + if (i > 0 && i + 1 < len && (name[i] == '-' || name[i] == '.')) + continue; + + return -EINVAL; + } + + return 0; +} + +/** + * kdbus_item_validate() - validate a single item + * @item: item to validate + * + * Return: 0 if item is valid, negative error code if not. + */ +int kdbus_item_validate(const struct kdbus_item *item) +{ + size_t payload_size = KDBUS_ITEM_PAYLOAD_SIZE(item); + size_t l; + int ret; + + BUILD_BUG_ON(KDBUS_ITEM_HEADER_SIZE != + sizeof(struct kdbus_item_header)); + + if (item->size < KDBUS_ITEM_HEADER_SIZE) + return -EINVAL; + + switch (item->type) { + case KDBUS_ITEM_NEGOTIATE: + if (payload_size % sizeof(u64) != 0) + return -EINVAL; + break; + + case KDBUS_ITEM_PAYLOAD_VEC: + case KDBUS_ITEM_PAYLOAD_OFF: + if (payload_size != sizeof(struct kdbus_vec)) + return -EINVAL; + if (item->vec.size == 0 || item->vec.size > SIZE_MAX) + return -EINVAL; + break; + + case KDBUS_ITEM_PAYLOAD_MEMFD: + if (payload_size != sizeof(struct kdbus_memfd)) + return -EINVAL; + if (item->memfd.size == 0 || item->memfd.size > SIZE_MAX) + return -EINVAL; + if (item->memfd.fd < 0) + return -EBADF; + break; + + case KDBUS_ITEM_FDS: + if (payload_size % sizeof(int) != 0) + return -EINVAL; + break; + + case KDBUS_ITEM_CANCEL_FD: + if (payload_size != sizeof(int)) + return -EINVAL; + break; + + case KDBUS_ITEM_BLOOM_PARAMETER: + if (payload_size != sizeof(struct kdbus_bloom_parameter)) + return -EINVAL; + break; + + case KDBUS_ITEM_BLOOM_FILTER: + /* followed by the bloom-mask, depends on the bloom-size */ + if (payload_size < sizeof(struct kdbus_bloom_filter)) + return -EINVAL; + break; + + case KDBUS_ITEM_BLOOM_MASK: + /* size depends on bloom-size of bus */ + break; + + case KDBUS_ITEM_CONN_DESCRIPTION: + case KDBUS_ITEM_MAKE_NAME: + ret = kdbus_item_validate_name(item); + if (ret < 0) + return ret; + break; + + case KDBUS_ITEM_ATTACH_FLAGS_SEND: + case KDBUS_ITEM_ATTACH_FLAGS_RECV: + case KDBUS_ITEM_ID: + case KDBUS_ITEM_DST_ID: + if (payload_size != sizeof(u64)) + return -EINVAL; + break; + + case KDBUS_ITEM_TIMESTAMP: + if (payload_size != sizeof(struct kdbus_timestamp)) + return -EINVAL; + break; + + case KDBUS_ITEM_CREDS: + if (payload_size != sizeof(struct kdbus_creds)) + return -EINVAL; + break; + + case KDBUS_ITEM_AUXGROUPS: + if (payload_size % sizeof(u32) != 0) + return -EINVAL; + break; + + case KDBUS_ITEM_NAME: + case KDBUS_ITEM_DST_NAME: + case KDBUS_ITEM_PID_COMM: + case KDBUS_ITEM_TID_COMM: + case KDBUS_ITEM_EXE: + case KDBUS_ITEM_CMDLINE: + case KDBUS_ITEM_CGROUP: + case KDBUS_ITEM_SECLABEL: + if (!kdbus_str_valid(item->str, payload_size)) + return -EINVAL; + break; + + case KDBUS_ITEM_CAPS: + if (payload_size < sizeof(u32)) + return -EINVAL; + if (payload_size < sizeof(u32) + + 4 * CAP_TO_INDEX(item->caps.last_cap) * sizeof(u32)) + return -EINVAL; + break; + + case KDBUS_ITEM_AUDIT: + if (payload_size != sizeof(struct kdbus_audit)) + return -EINVAL; + break; + + case KDBUS_ITEM_POLICY_ACCESS: + if (payload_size != sizeof(struct kdbus_policy_access)) + return -EINVAL; + break; + + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + if (payload_size < sizeof(struct kdbus_notify_name_change)) + return -EINVAL; + l = payload_size - offsetof(struct kdbus_notify_name_change, + name); + if (l > 0 && !kdbus_str_valid(item->name_change.name, l)) + return -EINVAL; + break; + + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + if (payload_size != sizeof(struct kdbus_notify_id_change)) + return -EINVAL; + break; + + case KDBUS_ITEM_REPLY_TIMEOUT: + case KDBUS_ITEM_REPLY_DEAD: + if (payload_size != 0) + return -EINVAL; + break; + + default: + break; + } + + return 0; +} + +/** + * kdbus_items_validate() - validate items passed by user-space + * @items: items to validate + * @items_size: number of items + * + * This verifies that the passed items pointer is consistent and valid. + * Furthermore, each item is checked for: + * - valid "size" value + * - payload is of expected type + * - payload is fully included in the item + * - string payloads are zero-terminated + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_items_validate(const struct kdbus_item *items, size_t items_size) +{ + const struct kdbus_item *item; + int ret; + + KDBUS_ITEMS_FOREACH(item, items, items_size) { + if (!KDBUS_ITEM_VALID(item, items, items_size)) + return -EINVAL; + + ret = kdbus_item_validate(item); + if (ret < 0) + return ret; + } + + if (!KDBUS_ITEMS_END(item, items, items_size)) + return -EINVAL; + + return 0; +} + +/** + * kdbus_item_set() - Set item content + * @item: The item to modify + * @type: The item type to set (KDBUS_ITEM_*) + * @data: Data to copy to item->data, may be %NULL + * @len: Number of bytes in @data + * + * This sets type, size and data fields of an item. If @data is NULL, the data + * memory is cleared. + * + * Note that you must align your @data memory to 8 bytes. Trailing padding (in + * case @len is not 8byte aligned) is cleared by this call. + * + * Returns: Pointer to the following item. + */ +struct kdbus_item *kdbus_item_set(struct kdbus_item *item, u64 type, + const void *data, size_t len) +{ + item->type = type; + item->size = KDBUS_ITEM_HEADER_SIZE + len; + + if (data) { + memcpy(item->data, data, len); + memset(item->data + len, 0, KDBUS_ALIGN8(len) - len); + } else { + memset(item->data, 0, KDBUS_ALIGN8(len)); + } + + return KDBUS_ITEM_NEXT(item); +} diff --git a/ipc/kdbus/item.h b/ipc/kdbus/item.h new file mode 100644 index 000000000000..3a7e6ccc253c --- /dev/null +++ b/ipc/kdbus/item.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_ITEM_H +#define __KDBUS_ITEM_H + +#include <linux/kernel.h> +#include <uapi/linux/kdbus.h> + +#include "util.h" + +/* generic access and iterators over a stream of items */ +#define KDBUS_ITEM_NEXT(_i) (typeof(_i))((u8 *)(_i) + KDBUS_ALIGN8((_i)->size)) +#define KDBUS_ITEMS_SIZE(_h, _is) ((_h)->size - offsetof(typeof(*(_h)), _is)) +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(_s) KDBUS_ALIGN8(KDBUS_ITEM_HEADER_SIZE + (_s)) +#define KDBUS_ITEM_PAYLOAD_SIZE(_i) ((_i)->size - KDBUS_ITEM_HEADER_SIZE) + +#define KDBUS_ITEMS_FOREACH(_i, _is, _s) \ + for ((_i) = (_is); \ + ((u8 *)(_i) < (u8 *)(_is) + (_s)) && \ + ((u8 *)(_i) >= (u8 *)(_is)); \ + (_i) = KDBUS_ITEM_NEXT(_i)) + +#define KDBUS_ITEM_VALID(_i, _is, _s) \ + ((_i)->size >= KDBUS_ITEM_HEADER_SIZE && \ + (u8 *)(_i) + (_i)->size > (u8 *)(_i) && \ + (u8 *)(_i) + (_i)->size <= (u8 *)(_is) + (_s) && \ + (u8 *)(_i) >= (u8 *)(_is)) + +#define KDBUS_ITEMS_END(_i, _is, _s) \ + ((u8 *)(_i) == ((u8 *)(_is) + KDBUS_ALIGN8(_s))) + +/** + * struct kdbus_item_header - Describes the fix part of an item + * @size: The total size of the item + * @type: The item type, one of KDBUS_ITEM_* + */ +struct kdbus_item_header { + u64 size; + u64 type; +}; + +int kdbus_item_validate_name(const struct kdbus_item *item); +int kdbus_item_validate(const struct kdbus_item *item); +int kdbus_items_validate(const struct kdbus_item *items, size_t items_size); +struct kdbus_item *kdbus_item_set(struct kdbus_item *item, u64 type, + const void *data, size_t len); + +#endif diff --git a/ipc/kdbus/limits.h b/ipc/kdbus/limits.h new file mode 100644 index 000000000000..59e3608ee8c3 --- /dev/null +++ b/ipc/kdbus/limits.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_DEFAULTS_H +#define __KDBUS_DEFAULTS_H + +#include <linux/kernel.h> + +/* maximum size of message header and items */ +#define KDBUS_MSG_MAX_SIZE SZ_8K + +/* maximum number of memfd items per message */ +#define KDBUS_MSG_MAX_MEMFD_ITEMS 16 + +/* max size of ioctl command data */ +#define KDBUS_CMD_MAX_SIZE SZ_32K + +/* maximum number of inflight fds in a target queue per user */ +#define KDBUS_CONN_MAX_FDS_PER_USER 16 + +/* maximum message payload size */ +#define KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE SZ_2M + +/* maximum size of bloom bit field in bytes */ +#define KDBUS_BUS_BLOOM_MAX_SIZE SZ_4K + +/* maximum length of well-known bus name */ +#define KDBUS_NAME_MAX_LEN 255 + +/* maximum length of bus, domain, ep name */ +#define KDBUS_SYSNAME_MAX_LEN 63 + +/* maximum number of matches per connection */ +#define KDBUS_MATCH_MAX 256 + +/* maximum number of queued messages from the same individual user */ +#define KDBUS_CONN_MAX_MSGS 256 + +/* maximum number of well-known names per connection */ +#define KDBUS_CONN_MAX_NAMES 256 + +/* maximum number of queued requests waiting for a reply */ +#define KDBUS_CONN_MAX_REQUESTS_PENDING 1024 + +/* maximum number of connections per user in one domain */ +#define KDBUS_USER_MAX_CONN 1024 + +/* maximum number of buses per user in one domain */ +#define KDBUS_USER_MAX_BUSES 16 + +#endif diff --git a/ipc/kdbus/main.c b/ipc/kdbus/main.c new file mode 100644 index 000000000000..1ad4dc8dafa1 --- /dev/null +++ b/ipc/kdbus/main.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/module.h> + +#include "util.h" +#include "fs.h" +#include "handle.h" +#include "metadata.h" +#include "node.h" + +/* + * This is a simplified outline of the internal kdbus object relations, for + * those interested in the inner life of the driver implementation. + * + * From a mount point's (domain's) perspective: + * + * struct kdbus_domain + * |» struct kdbus_user *user (many, owned) + * '» struct kdbus_node node (embedded) + * |» struct kdbus_node children (many, referenced) + * |» struct kdbus_node *parent (pinned) + * '» struct kdbus_bus (many, pinned) + * |» struct kdbus_node node (embedded) + * '» struct kdbus_ep (many, pinned) + * |» struct kdbus_node node (embedded) + * |» struct kdbus_bus *bus (pinned) + * |» struct kdbus_conn conn_list (many, pinned) + * | |» struct kdbus_ep *ep (pinned) + * | |» struct kdbus_name_entry *activator_of (owned) + * | |» struct kdbus_match_db *match_db (owned) + * | |» struct kdbus_meta *meta (owned) + * | |» struct kdbus_match_db *match_db (owned) + * | | '» struct kdbus_match_entry (many, owned) + * | | + * | |» struct kdbus_pool *pool (owned) + * | | '» struct kdbus_pool_slice *slices (many, owned) + * | | '» struct kdbus_pool *pool (pinned) + * | | + * | |» struct kdbus_user *user (pinned) + * | `» struct kdbus_queue_entry entries (many, embedded) + * | |» struct kdbus_pool_slice *slice (pinned) + * | |» struct kdbus_conn_reply *reply (owned) + * | '» struct kdbus_user *user (pinned) + * | + * '» struct kdbus_user *user (pinned) + * '» struct kdbus_policy_db policy_db (embedded) + * |» struct kdbus_policy_db_entry (many, owned) + * | |» struct kdbus_conn (pinned) + * | '» struct kdbus_ep (pinned) + * | + * '» struct kdbus_policy_db_cache_entry (many, owned) + * '» struct kdbus_conn (pinned) + * + * For the life-time of a file descriptor derived from calling open() on a file + * inside the mount point: + * + * struct kdbus_handle + * |» struct kdbus_meta *meta (owned) + * |» struct kdbus_ep *ep (pinned) + * |» struct kdbus_conn *conn (owned) + * '» struct kdbus_ep *ep (owned) + */ + +/* kdbus mount-point /sys/fs/kdbus */ +static struct kobject *kdbus_dir; + +static int __init kdbus_init(void) +{ + int ret; + + kdbus_dir = kobject_create_and_add(KBUILD_MODNAME, fs_kobj); + if (!kdbus_dir) + return -ENOMEM; + + ret = kdbus_fs_init(); + if (ret < 0) { + pr_err("cannot register filesystem: %d\n", ret); + goto exit_dir; + } + + pr_info("initialized\n"); + return 0; + +exit_dir: + kobject_put(kdbus_dir); + return ret; +} + +static void __exit kdbus_exit(void) +{ + kdbus_fs_exit(); + kobject_put(kdbus_dir); + ida_destroy(&kdbus_node_ida); +} + +module_init(kdbus_init); +module_exit(kdbus_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("D-Bus, powerful, easy to use interprocess communication"); +MODULE_ALIAS_FS(KBUILD_MODNAME "fs"); diff --git a/ipc/kdbus/match.c b/ipc/kdbus/match.c new file mode 100644 index 000000000000..4ee6a1f2e1bc --- /dev/null +++ b/ipc/kdbus/match.c @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/fs.h> +#include <linux/hash.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "handle.h" +#include "item.h" +#include "match.h" +#include "message.h" +#include "names.h" + +/** + * struct kdbus_match_db - message filters + * @entries_list: List of matches + * @mdb_rwlock: Match data lock + * @entries_count: Number of entries in database + */ +struct kdbus_match_db { + struct list_head entries_list; + struct rw_semaphore mdb_rwlock; + unsigned int entries_count; +}; + +/** + * struct kdbus_match_entry - a match database entry + * @cookie: User-supplied cookie to lookup the entry + * @list_entry: The list entry element for the db list + * @rules_list: The list head for tracking rules of this entry + */ +struct kdbus_match_entry { + u64 cookie; + struct list_head list_entry; + struct list_head rules_list; +}; + +/** + * struct kdbus_bloom_mask - mask to match against filter + * @generations: Number of generations carried + * @data: Array of bloom bit fields + */ +struct kdbus_bloom_mask { + u64 generations; + u64 *data; +}; + +/** + * struct kdbus_match_rule - a rule appended to a match entry + * @type: An item type to match against + * @bloom_mask: Bloom mask to match a message's filter against, used + * with KDBUS_ITEM_BLOOM_MASK + * @name: Name to match against, used with KDBUS_ITEM_NAME, + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} + * @old_id: ID to match against, used with + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE}, + * KDBUS_ITEM_ID_REMOVE + * @new_id: ID to match against, used with + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE}, + * KDBUS_ITEM_ID_REMOVE + * @src_id: ID to match against, used with KDBUS_ITEM_ID + * @dst_id: Message destination ID, used with KDBUS_ITEM_DST_ID + * @rules_entry: Entry in the entry's rules list + */ +struct kdbus_match_rule { + u64 type; + union { + struct kdbus_bloom_mask bloom_mask; + struct { + char *name; + u64 old_id; + u64 new_id; + }; + u64 src_id; + u64 dst_id; + }; + struct list_head rules_entry; +}; + +static void kdbus_match_rule_free(struct kdbus_match_rule *rule) +{ + if (!rule) + return; + + switch (rule->type) { + case KDBUS_ITEM_BLOOM_MASK: + kfree(rule->bloom_mask.data); + break; + + case KDBUS_ITEM_NAME: + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + kfree(rule->name); + break; + + case KDBUS_ITEM_ID: + case KDBUS_ITEM_DST_ID: + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + break; + + default: + BUG(); + } + + list_del(&rule->rules_entry); + kfree(rule); +} + +static void kdbus_match_entry_free(struct kdbus_match_entry *entry) +{ + struct kdbus_match_rule *r, *tmp; + + if (!entry) + return; + + list_for_each_entry_safe(r, tmp, &entry->rules_list, rules_entry) + kdbus_match_rule_free(r); + + list_del(&entry->list_entry); + kfree(entry); +} + +/** + * kdbus_match_db_free() - free match db resources + * @mdb: The match database + */ +void kdbus_match_db_free(struct kdbus_match_db *mdb) +{ + struct kdbus_match_entry *entry, *tmp; + + if (!mdb) + return; + + list_for_each_entry_safe(entry, tmp, &mdb->entries_list, list_entry) + kdbus_match_entry_free(entry); + + kfree(mdb); +} + +/** + * kdbus_match_db_new() - create a new match database + * + * Return: a new kdbus_match_db on success, ERR_PTR on failure. + */ +struct kdbus_match_db *kdbus_match_db_new(void) +{ + struct kdbus_match_db *d; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return ERR_PTR(-ENOMEM); + + init_rwsem(&d->mdb_rwlock); + INIT_LIST_HEAD(&d->entries_list); + + return d; +} + +static bool kdbus_match_bloom(const struct kdbus_bloom_filter *filter, + const struct kdbus_bloom_mask *mask, + const struct kdbus_conn *conn) +{ + size_t n = conn->ep->bus->bloom.size / sizeof(u64); + const u64 *m; + size_t i; + + /* + * The message's filter carries a generation identifier, the + * match's mask possibly carries an array of multiple generations + * of the mask. Select the mask with the closest match of the + * filter's generation. + */ + m = mask->data + (min(filter->generation, mask->generations - 1) * n); + + /* + * The message's filter contains the messages properties, + * the match's mask contains the properties to look for in the + * message. Check the mask bit field against the filter bit field, + * if the message possibly carries the properties the connection + * has subscribed to. + */ + for (i = 0; i < n; i++) + if ((filter->data[i] & m[i]) != m[i]) + return false; + + return true; +} + +static bool kdbus_match_rule_conn(const struct kdbus_match_rule *r, + struct kdbus_conn *c, + const struct kdbus_staging *s) +{ + lockdep_assert_held(&c->ep->bus->name_registry->rwlock); + + switch (r->type) { + case KDBUS_ITEM_BLOOM_MASK: + return kdbus_match_bloom(s->bloom_filter, &r->bloom_mask, c); + case KDBUS_ITEM_ID: + return r->src_id == c->id || r->src_id == KDBUS_MATCH_ID_ANY; + case KDBUS_ITEM_DST_ID: + return r->dst_id == s->msg->dst_id || + r->dst_id == KDBUS_MATCH_ID_ANY; + case KDBUS_ITEM_NAME: + return kdbus_conn_has_name(c, r->name); + default: + return false; + } +} + +static bool kdbus_match_rule_kernel(const struct kdbus_match_rule *r, + const struct kdbus_staging *s) +{ + struct kdbus_item *n = s->notify; + + if (WARN_ON(!n) || n->type != r->type) + return false; + + switch (r->type) { + case KDBUS_ITEM_ID_ADD: + return r->new_id == KDBUS_MATCH_ID_ANY || + r->new_id == n->id_change.id; + case KDBUS_ITEM_ID_REMOVE: + return r->old_id == KDBUS_MATCH_ID_ANY || + r->old_id == n->id_change.id; + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_CHANGE: + case KDBUS_ITEM_NAME_REMOVE: + return (r->old_id == KDBUS_MATCH_ID_ANY || + r->old_id == n->name_change.old_id.id) && + (r->new_id == KDBUS_MATCH_ID_ANY || + r->new_id == n->name_change.new_id.id) && + (!r->name || !strcmp(r->name, n->name_change.name)); + default: + return false; + } +} + +static bool kdbus_match_rules(const struct kdbus_match_entry *entry, + struct kdbus_conn *c, + const struct kdbus_staging *s) +{ + struct kdbus_match_rule *r; + + list_for_each_entry(r, &entry->rules_list, rules_entry) + if ((c && !kdbus_match_rule_conn(r, c, s)) || + (!c && !kdbus_match_rule_kernel(r, s))) + return false; + + return true; +} + +/** + * kdbus_match_db_match_msg() - match a msg object agains the database entries + * @mdb: The match database + * @conn_src: The connection object originating the message + * @staging: Staging object containing the message to match against + * + * This function will walk through all the database entries previously uploaded + * with kdbus_match_db_add(). As soon as any of them has an all-satisfied rule + * set, this function will return true. + * + * The caller must hold the registry lock of conn_src->ep->bus, in case conn_src + * is non-NULL. + * + * Return: true if there was a matching database entry, false otherwise. + */ +bool kdbus_match_db_match_msg(struct kdbus_match_db *mdb, + struct kdbus_conn *conn_src, + const struct kdbus_staging *staging) +{ + struct kdbus_match_entry *entry; + bool matched = false; + + down_read(&mdb->mdb_rwlock); + list_for_each_entry(entry, &mdb->entries_list, list_entry) { + matched = kdbus_match_rules(entry, conn_src, staging); + if (matched) + break; + } + up_read(&mdb->mdb_rwlock); + + return matched; +} + +static int kdbus_match_db_remove_unlocked(struct kdbus_match_db *mdb, + u64 cookie) +{ + struct kdbus_match_entry *entry, *tmp; + bool found = false; + + list_for_each_entry_safe(entry, tmp, &mdb->entries_list, list_entry) + if (entry->cookie == cookie) { + kdbus_match_entry_free(entry); + --mdb->entries_count; + found = true; + } + + return found ? 0 : -EBADSLT; +} + +/** + * kdbus_cmd_match_add() - handle KDBUS_CMD_MATCH_ADD + * @conn: connection to operate on + * @argp: command payload + * + * One call to this function (or one ioctl(KDBUS_CMD_MATCH_ADD), respectively, + * adds one new database entry with n rules attached to it. Each rule is + * described with an kdbus_item, and an entry is considered matching if all + * its rules are satisfied. + * + * The items attached to a kdbus_cmd_match struct have the following mapping: + * + * KDBUS_ITEM_BLOOM_MASK: A bloom mask + * KDBUS_ITEM_NAME: A connection's source name + * KDBUS_ITEM_ID: A connection ID + * KDBUS_ITEM_DST_ID: A connection ID + * KDBUS_ITEM_NAME_ADD: + * KDBUS_ITEM_NAME_REMOVE: + * KDBUS_ITEM_NAME_CHANGE: Well-known name changes, carry + * kdbus_notify_name_change + * KDBUS_ITEM_ID_ADD: + * KDBUS_ITEM_ID_REMOVE: Connection ID changes, carry + * kdbus_notify_id_change + * + * For kdbus_notify_{id,name}_change structs, only the ID and name fields + * are looked at when adding an entry. The flags are unused. + * + * Also note that KDBUS_ITEM_BLOOM_MASK, KDBUS_ITEM_NAME, KDBUS_ITEM_ID, + * and KDBUS_ITEM_DST_ID are used to match messages from userspace, while the + * others apply to kernel-generated notifications. + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_match_add(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_match_db *mdb = conn->match_db; + struct kdbus_match_entry *entry = NULL; + struct kdbus_cmd_match *cmd; + struct kdbus_item *item; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_BLOOM_MASK, .multiple = true }, + { .type = KDBUS_ITEM_NAME, .multiple = true }, + { .type = KDBUS_ITEM_ID, .multiple = true }, + { .type = KDBUS_ITEM_DST_ID, .multiple = true }, + { .type = KDBUS_ITEM_NAME_ADD, .multiple = true }, + { .type = KDBUS_ITEM_NAME_REMOVE, .multiple = true }, + { .type = KDBUS_ITEM_NAME_CHANGE, .multiple = true }, + { .type = KDBUS_ITEM_ID_ADD, .multiple = true }, + { .type = KDBUS_ITEM_ID_REMOVE, .multiple = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_MATCH_REPLACE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto exit; + } + + entry->cookie = cmd->cookie; + INIT_LIST_HEAD(&entry->list_entry); + INIT_LIST_HEAD(&entry->rules_list); + + KDBUS_ITEMS_FOREACH(item, cmd->items, KDBUS_ITEMS_SIZE(cmd, items)) { + struct kdbus_match_rule *rule; + size_t size = item->size - offsetof(struct kdbus_item, data); + + rule = kzalloc(sizeof(*rule), GFP_KERNEL); + if (!rule) { + ret = -ENOMEM; + goto exit; + } + + rule->type = item->type; + INIT_LIST_HEAD(&rule->rules_entry); + + switch (item->type) { + case KDBUS_ITEM_BLOOM_MASK: { + u64 bsize = conn->ep->bus->bloom.size; + u64 generations; + u64 remainder; + + generations = div64_u64_rem(size, bsize, &remainder); + if (size < bsize || remainder > 0) { + ret = -EDOM; + break; + } + + rule->bloom_mask.data = kmemdup(item->data, + size, GFP_KERNEL); + if (!rule->bloom_mask.data) { + ret = -ENOMEM; + break; + } + + rule->bloom_mask.generations = generations; + break; + } + + case KDBUS_ITEM_NAME: + if (!kdbus_name_is_valid(item->str, false)) { + ret = -EINVAL; + break; + } + + rule->name = kstrdup(item->str, GFP_KERNEL); + if (!rule->name) + ret = -ENOMEM; + + break; + + case KDBUS_ITEM_ID: + rule->src_id = item->id; + break; + + case KDBUS_ITEM_DST_ID: + rule->dst_id = item->id; + break; + + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + rule->old_id = item->name_change.old_id.id; + rule->new_id = item->name_change.new_id.id; + + if (size > sizeof(struct kdbus_notify_name_change)) { + rule->name = kstrdup(item->name_change.name, + GFP_KERNEL); + if (!rule->name) + ret = -ENOMEM; + } + + break; + + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + if (item->type == KDBUS_ITEM_ID_ADD) + rule->new_id = item->id_change.id; + else + rule->old_id = item->id_change.id; + + break; + } + + if (ret < 0) { + kdbus_match_rule_free(rule); + goto exit; + } + + list_add_tail(&rule->rules_entry, &entry->rules_list); + } + + down_write(&mdb->mdb_rwlock); + + /* Remove any entry that has the same cookie as the current one. */ + if (cmd->flags & KDBUS_MATCH_REPLACE) + kdbus_match_db_remove_unlocked(mdb, entry->cookie); + + /* + * If the above removal caught any entry, there will be room for the + * new one. + */ + if (++mdb->entries_count > KDBUS_MATCH_MAX) { + --mdb->entries_count; + ret = -EMFILE; + } else { + list_add_tail(&entry->list_entry, &mdb->entries_list); + entry = NULL; + } + + up_write(&mdb->mdb_rwlock); + +exit: + kdbus_match_entry_free(entry); + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_match_remove() - handle KDBUS_CMD_MATCH_REMOVE + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_match_remove(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_cmd_match *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + down_write(&conn->match_db->mdb_rwlock); + ret = kdbus_match_db_remove_unlocked(conn->match_db, cmd->cookie); + up_write(&conn->match_db->mdb_rwlock); + + return kdbus_args_clear(&args, ret); +} diff --git a/ipc/kdbus/match.h b/ipc/kdbus/match.h new file mode 100644 index 000000000000..ceb492f8e51c --- /dev/null +++ b/ipc/kdbus/match.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_MATCH_H +#define __KDBUS_MATCH_H + +struct kdbus_conn; +struct kdbus_match_db; +struct kdbus_staging; + +struct kdbus_match_db *kdbus_match_db_new(void); +void kdbus_match_db_free(struct kdbus_match_db *db); +int kdbus_match_db_add(struct kdbus_conn *conn, + struct kdbus_cmd_match *cmd); +int kdbus_match_db_remove(struct kdbus_conn *conn, + struct kdbus_cmd_match *cmd); +bool kdbus_match_db_match_msg(struct kdbus_match_db *db, + struct kdbus_conn *conn_src, + const struct kdbus_staging *staging); + +int kdbus_cmd_match_add(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_match_remove(struct kdbus_conn *conn, void __user *argp); + +#endif diff --git a/ipc/kdbus/message.c b/ipc/kdbus/message.c new file mode 100644 index 000000000000..66b9325f03d0 --- /dev/null +++ b/ipc/kdbus/message.c @@ -0,0 +1,1033 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/capability.h> +#include <linux/cgroup.h> +#include <linux/cred.h> +#include <linux/file.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/shmem_fs.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <net/sock.h> + +#include "bus.h" +#include "connection.h" +#include "domain.h" +#include "endpoint.h" +#include "handle.h" +#include "item.h" +#include "match.h" +#include "message.h" +#include "names.h" +#include "policy.h" + +static const char * const zeros = "\0\0\0\0\0\0\0"; + +static struct kdbus_gaps *kdbus_gaps_new(size_t n_memfds, size_t n_fds) +{ + size_t size_offsets, size_memfds, size_fds, size; + struct kdbus_gaps *gaps; + + size_offsets = n_memfds * sizeof(*gaps->memfd_offsets); + size_memfds = n_memfds * sizeof(*gaps->memfd_files); + size_fds = n_fds * sizeof(*gaps->fd_files); + size = sizeof(*gaps) + size_offsets + size_memfds + size_fds; + + gaps = kzalloc(size, GFP_KERNEL); + if (!gaps) + return ERR_PTR(-ENOMEM); + + kref_init(&gaps->kref); + gaps->n_memfds = 0; /* we reserve n_memfds, but don't enforce them */ + gaps->memfd_offsets = (void *)(gaps + 1); + gaps->memfd_files = (void *)((u8 *)gaps->memfd_offsets + size_offsets); + gaps->n_fds = 0; /* we reserve n_fds, but don't enforce them */ + gaps->fd_files = (void *)((u8 *)gaps->memfd_files + size_memfds); + + return gaps; +} + +static void kdbus_gaps_free(struct kref *kref) +{ + struct kdbus_gaps *gaps = container_of(kref, struct kdbus_gaps, kref); + size_t i; + + for (i = 0; i < gaps->n_fds; ++i) + if (gaps->fd_files[i]) + fput(gaps->fd_files[i]); + for (i = 0; i < gaps->n_memfds; ++i) + if (gaps->memfd_files[i]) + fput(gaps->memfd_files[i]); + + kfree(gaps); +} + +/** + * kdbus_gaps_ref() - gain reference + * @gaps: gaps object + * + * Return: @gaps is returned + */ +struct kdbus_gaps *kdbus_gaps_ref(struct kdbus_gaps *gaps) +{ + if (gaps) + kref_get(&gaps->kref); + return gaps; +} + +/** + * kdbus_gaps_unref() - drop reference + * @gaps: gaps object + * + * Return: NULL + */ +struct kdbus_gaps *kdbus_gaps_unref(struct kdbus_gaps *gaps) +{ + if (gaps) + kref_put(&gaps->kref, kdbus_gaps_free); + return NULL; +} + +/** + * kdbus_gaps_install() - install file-descriptors + * @gaps: gaps object, or NULL + * @slice: pool slice that contains the message + * @out_incomplete output variable to note incomplete fds + * + * This function installs all file-descriptors of @gaps into the current + * process and copies the file-descriptor numbers into the target pool slice. + * + * If the file-descriptors were only partially installed, then @out_incomplete + * will be set to true. Otherwise, it's set to false. + * + * Return: 0 on success, negative error code on failure + */ +int kdbus_gaps_install(struct kdbus_gaps *gaps, struct kdbus_pool_slice *slice, + bool *out_incomplete) +{ + bool incomplete_fds = false; + struct kvec kvec; + size_t i, n_fds; + int ret, *fds; + + if (!gaps) { + /* nothing to do */ + *out_incomplete = incomplete_fds; + return 0; + } + + n_fds = gaps->n_fds + gaps->n_memfds; + if (n_fds < 1) { + /* nothing to do */ + *out_incomplete = incomplete_fds; + return 0; + } + + fds = kmalloc_array(n_fds, sizeof(*fds), GFP_TEMPORARY); + n_fds = 0; + if (!fds) + return -ENOMEM; + + /* 1) allocate fds and copy them over */ + + if (gaps->n_fds > 0) { + for (i = 0; i < gaps->n_fds; ++i) { + int fd; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + incomplete_fds = true; + + WARN_ON(!gaps->fd_files[i]); + + fds[n_fds++] = fd < 0 ? -1 : fd; + } + + /* + * The file-descriptor array can only be present once per + * message. Hence, prepare all fds and then copy them over with + * a single kvec. + */ + + WARN_ON(!gaps->fd_offset); + + kvec.iov_base = fds; + kvec.iov_len = gaps->n_fds * sizeof(*fds); + ret = kdbus_pool_slice_copy_kvec(slice, gaps->fd_offset, + &kvec, 1, kvec.iov_len); + if (ret < 0) + goto exit; + } + + for (i = 0; i < gaps->n_memfds; ++i) { + int memfd; + + memfd = get_unused_fd_flags(O_CLOEXEC); + if (memfd < 0) { + incomplete_fds = true; + /* memfds are initialized to -1, skip copying it */ + continue; + } + + fds[n_fds++] = memfd; + + /* + * memfds have to be copied individually as they each are put + * into a separate item. This should not be an issue, though, + * as usually there is no need to send more than one memfd per + * message. + */ + + WARN_ON(!gaps->memfd_offsets[i]); + WARN_ON(!gaps->memfd_files[i]); + + kvec.iov_base = &memfd; + kvec.iov_len = sizeof(memfd); + ret = kdbus_pool_slice_copy_kvec(slice, gaps->memfd_offsets[i], + &kvec, 1, kvec.iov_len); + if (ret < 0) + goto exit; + } + + /* 2) install fds now that everything was successful */ + + for (i = 0; i < gaps->n_fds; ++i) + if (fds[i] >= 0) + fd_install(fds[i], get_file(gaps->fd_files[i])); + for (i = 0; i < gaps->n_memfds; ++i) + if (fds[gaps->n_fds + i] >= 0) + fd_install(fds[gaps->n_fds + i], + get_file(gaps->memfd_files[i])); + + ret = 0; + +exit: + if (ret < 0) + for (i = 0; i < n_fds; ++i) + put_unused_fd(fds[i]); + kfree(fds); + *out_incomplete = incomplete_fds; + return ret; +} + +static struct file *kdbus_get_fd(int fd) +{ + struct file *f, *ret; + + if (fd < 0) + return ERR_PTR(-EBADF); + + f = fget_raw(fd); + if (!f) + return ERR_PTR(-EBADF); + + if (f->f_mode & FMODE_PATH) + ret = f; /* O_PATH is always allowed */ + else if (f->f_op == &kdbus_handle_ops) + ret = ERR_PTR(-EOPNOTSUPP); /* disallow kdbus-fd over kdbus */ + else + ret = f; /* all other are allowed */ + + if (f != ret) + fput(f); + + return ret; +} + +static struct file *kdbus_get_memfd(const struct kdbus_memfd *memfd) +{ + const int m = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL; + struct file *f, *ret; + int s; + + if (memfd->fd < 0) + return ERR_PTR(-EBADF); + + f = fget(memfd->fd); + if (!f) + return ERR_PTR(-EBADF); + + s = shmem_get_seals(f); + if (s < 0) + ret = ERR_PTR(-EMEDIUMTYPE); + else if ((s & m) != m) + ret = ERR_PTR(-ETXTBSY); + else if (memfd->start + memfd->size > (u64)i_size_read(file_inode(f))) + ret = ERR_PTR(-EFAULT); + else + ret = f; + + if (f != ret) + fput(f); + + return ret; +} + +static int kdbus_msg_examine(struct kdbus_msg *msg, struct kdbus_bus *bus, + struct kdbus_cmd_send *cmd, size_t *out_n_memfds, + size_t *out_n_fds, size_t *out_n_parts) +{ + struct kdbus_item *item, *fds = NULL, *bloom = NULL, *dstname = NULL; + u64 n_parts, n_memfds, n_fds, vec_size; + + /* + * Step 1: + * Validate the message and command parameters. + */ + + /* KDBUS_PAYLOAD_KERNEL is reserved to kernel messages */ + if (msg->payload_type == KDBUS_PAYLOAD_KERNEL) + return -EINVAL; + + if (msg->dst_id == KDBUS_DST_ID_BROADCAST) { + /* broadcasts must be marked as signals */ + if (!(msg->flags & KDBUS_MSG_SIGNAL)) + return -EBADMSG; + /* broadcasts cannot have timeouts */ + if (msg->timeout_ns > 0) + return -ENOTUNIQ; + } + + if (msg->flags & KDBUS_MSG_EXPECT_REPLY) { + /* if you expect a reply, you must specify a timeout */ + if (msg->timeout_ns == 0) + return -EINVAL; + /* signals cannot have replies */ + if (msg->flags & KDBUS_MSG_SIGNAL) + return -ENOTUNIQ; + } else { + /* must expect reply if sent as synchronous call */ + if (cmd->flags & KDBUS_SEND_SYNC_REPLY) + return -EINVAL; + /* cannot mark replies as signal */ + if (msg->cookie_reply && (msg->flags & KDBUS_MSG_SIGNAL)) + return -EINVAL; + } + + /* + * Step 2: + * Validate all passed items. While at it, select some statistics that + * are required to allocate state objects later on. + * + * Generic item validation has already been done via + * kdbus_item_validate(). Furthermore, the number of items is naturally + * limited by the maximum message size. Hence, only non-generic item + * checks are performed here (mainly integer overflow tests). + */ + + n_parts = 0; + n_memfds = 0; + n_fds = 0; + vec_size = 0; + + KDBUS_ITEMS_FOREACH(item, msg->items, KDBUS_ITEMS_SIZE(msg, items)) { + switch (item->type) { + case KDBUS_ITEM_PAYLOAD_VEC: { + void __force __user *ptr = KDBUS_PTR(item->vec.address); + u64 size = item->vec.size; + + if (vec_size + size < vec_size) + return -EMSGSIZE; + if (vec_size + size > KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE) + return -EMSGSIZE; + if (ptr && unlikely(!access_ok(VERIFY_READ, ptr, size))) + return -EFAULT; + + if (ptr || size % 8) /* data or padding */ + ++n_parts; + break; + } + case KDBUS_ITEM_PAYLOAD_MEMFD: { + u64 start = item->memfd.start; + u64 size = item->memfd.size; + + if (start + size < start) + return -EMSGSIZE; + if (n_memfds >= KDBUS_MSG_MAX_MEMFD_ITEMS) + return -E2BIG; + + ++n_memfds; + if (size % 8) /* vec-padding required */ + ++n_parts; + break; + } + case KDBUS_ITEM_FDS: { + if (fds) + return -EEXIST; + + fds = item; + n_fds = KDBUS_ITEM_PAYLOAD_SIZE(item) / sizeof(int); + if (n_fds > KDBUS_CONN_MAX_FDS_PER_USER) + return -EMFILE; + + break; + } + case KDBUS_ITEM_BLOOM_FILTER: { + u64 bloom_size; + + if (bloom) + return -EEXIST; + + bloom = item; + bloom_size = KDBUS_ITEM_PAYLOAD_SIZE(item) - + offsetof(struct kdbus_bloom_filter, data); + if (!KDBUS_IS_ALIGNED8(bloom_size)) + return -EFAULT; + if (bloom_size != bus->bloom.size) + return -EDOM; + + break; + } + case KDBUS_ITEM_DST_NAME: { + if (dstname) + return -EEXIST; + + dstname = item; + if (!kdbus_name_is_valid(item->str, false)) + return -EINVAL; + if (msg->dst_id == KDBUS_DST_ID_BROADCAST) + return -EBADMSG; + + break; + } + default: + return -EINVAL; + } + } + + /* + * Step 3: + * Validate that required items were actually passed, and that no item + * contradicts the message flags. + */ + + /* bloom filters must be attached _iff_ it's a signal */ + if (!(msg->flags & KDBUS_MSG_SIGNAL) != !bloom) + return -EBADMSG; + /* destination name is required if no ID is given */ + if (msg->dst_id == KDBUS_DST_ID_NAME && !dstname) + return -EDESTADDRREQ; + /* cannot send file-descriptors attached to broadcasts */ + if (msg->dst_id == KDBUS_DST_ID_BROADCAST && fds) + return -ENOTUNIQ; + + *out_n_memfds = n_memfds; + *out_n_fds = n_fds; + *out_n_parts = n_parts; + + return 0; +} + +static bool kdbus_staging_merge_vecs(struct kdbus_staging *staging, + struct kdbus_item **prev_item, + struct iovec **prev_vec, + const struct kdbus_item *merge) +{ + void __user *ptr = (void __user *)KDBUS_PTR(merge->vec.address); + u64 padding = merge->vec.size % 8; + struct kdbus_item *prev = *prev_item; + struct iovec *vec = *prev_vec; + + /* XXX: merging is disabled so far */ + if (0 && prev && prev->type == KDBUS_ITEM_PAYLOAD_OFF && + !merge->vec.address == !prev->vec.address) { + /* + * If we merge two VECs, we can always drop the second + * PAYLOAD_VEC item. Hence, include its size in the previous + * one. + */ + prev->vec.size += merge->vec.size; + + if (ptr) { + /* + * If we merge two data VECs, we need two iovecs to copy + * the data. But the items can be easily merged by + * summing their lengths. + */ + vec = &staging->parts[staging->n_parts++]; + vec->iov_len = merge->vec.size; + vec->iov_base = ptr; + staging->n_payload += vec->iov_len; + } else if (padding) { + /* + * If we merge two 0-vecs with the second 0-vec + * requiring padding, we need to insert an iovec to copy + * the 0-padding. We try merging it with the previous + * 0-padding iovec. This might end up with an + * iov_len==0, in which case we simply drop the iovec. + */ + if (vec) { + staging->n_payload -= vec->iov_len; + vec->iov_len = prev->vec.size % 8; + if (!vec->iov_len) { + --staging->n_parts; + vec = NULL; + } else { + staging->n_payload += vec->iov_len; + } + } else { + vec = &staging->parts[staging->n_parts++]; + vec->iov_len = padding; + vec->iov_base = (char __user *)zeros; + staging->n_payload += vec->iov_len; + } + } else { + /* + * If we merge two 0-vecs with the second 0-vec having + * no padding, we know the padding of the first stays + * the same. Hence, @vec needs no adjustment. + */ + } + + /* successfully merged with previous item */ + merge = prev; + } else { + /* + * If we cannot merge the payload item with the previous one, + * we simply insert a new iovec for the data/padding. + */ + if (ptr) { + vec = &staging->parts[staging->n_parts++]; + vec->iov_len = merge->vec.size; + vec->iov_base = ptr; + staging->n_payload += vec->iov_len; + } else if (padding) { + vec = &staging->parts[staging->n_parts++]; + vec->iov_len = padding; + vec->iov_base = (char __user *)zeros; + staging->n_payload += vec->iov_len; + } else { + vec = NULL; + } + } + + *prev_item = (struct kdbus_item *)merge; + *prev_vec = vec; + + return merge == prev; +} + +static int kdbus_staging_import(struct kdbus_staging *staging) +{ + struct kdbus_item *it, *item, *last, *prev_payload; + struct kdbus_gaps *gaps = staging->gaps; + struct kdbus_msg *msg = staging->msg; + struct iovec *part, *prev_part; + bool drop_item; + + drop_item = false; + last = NULL; + prev_payload = NULL; + prev_part = NULL; + + /* + * We modify msg->items along the way; make sure to use @item as offset + * to the next item (instead of the iterator @it). + */ + for (it = item = msg->items; + it >= msg->items && + (u8 *)it < (u8 *)msg + msg->size && + (u8 *)it + it->size <= (u8 *)msg + msg->size; ) { + /* + * If we dropped items along the way, move current item to + * front. We must not access @it afterwards, but use @item + * instead! + */ + if (it != item) + memmove(item, it, it->size); + it = (void *)((u8 *)it + KDBUS_ALIGN8(item->size)); + + switch (item->type) { + case KDBUS_ITEM_PAYLOAD_VEC: { + size_t offset = staging->n_payload; + + if (kdbus_staging_merge_vecs(staging, &prev_payload, + &prev_part, item)) { + drop_item = true; + } else if (item->vec.address) { + /* real offset is patched later on */ + item->type = KDBUS_ITEM_PAYLOAD_OFF; + item->vec.offset = offset; + } else { + item->type = KDBUS_ITEM_PAYLOAD_OFF; + item->vec.offset = ~0ULL; + } + + break; + } + case KDBUS_ITEM_PAYLOAD_MEMFD: { + struct file *f; + + f = kdbus_get_memfd(&item->memfd); + if (IS_ERR(f)) + return PTR_ERR(f); + + gaps->memfd_files[gaps->n_memfds] = f; + gaps->memfd_offsets[gaps->n_memfds] = + (u8 *)&item->memfd.fd - (u8 *)msg; + ++gaps->n_memfds; + + /* memfds cannot be merged */ + prev_payload = item; + prev_part = NULL; + + /* insert padding to make following VECs aligned */ + if (item->memfd.size % 8) { + part = &staging->parts[staging->n_parts++]; + part->iov_len = item->memfd.size % 8; + part->iov_base = (char __user *)zeros; + staging->n_payload += part->iov_len; + } + + break; + } + case KDBUS_ITEM_FDS: { + size_t i, n_fds; + + n_fds = KDBUS_ITEM_PAYLOAD_SIZE(item) / sizeof(int); + for (i = 0; i < n_fds; ++i) { + struct file *f; + + f = kdbus_get_fd(item->fds[i]); + if (IS_ERR(f)) + return PTR_ERR(f); + + gaps->fd_files[gaps->n_fds++] = f; + } + + gaps->fd_offset = (u8 *)item->fds - (u8 *)msg; + + break; + } + case KDBUS_ITEM_BLOOM_FILTER: + staging->bloom_filter = &item->bloom_filter; + break; + case KDBUS_ITEM_DST_NAME: + staging->dst_name = item->str; + break; + } + + /* drop item if we merged it with a previous one */ + if (drop_item) { + drop_item = false; + } else { + last = item; + item = KDBUS_ITEM_NEXT(item); + } + } + + /* adjust message size regarding dropped items */ + msg->size = offsetof(struct kdbus_msg, items); + if (last) + msg->size += ((u8 *)last - (u8 *)msg->items) + last->size; + + return 0; +} + +static void kdbus_staging_reserve(struct kdbus_staging *staging) +{ + struct iovec *part; + + part = &staging->parts[staging->n_parts++]; + part->iov_base = (void __user *)zeros; + part->iov_len = 0; +} + +static struct kdbus_staging *kdbus_staging_new(struct kdbus_bus *bus, + size_t n_parts, + size_t msg_extra_size) +{ + const size_t reserved_parts = 5; /* see below for explanation */ + struct kdbus_staging *staging; + int ret; + + n_parts += reserved_parts; + + staging = kzalloc(sizeof(*staging) + n_parts * sizeof(*staging->parts) + + msg_extra_size, GFP_TEMPORARY); + if (!staging) + return ERR_PTR(-ENOMEM); + + staging->msg_seqnum = atomic64_inc_return(&bus->last_message_id); + staging->n_parts = 0; /* we reserve n_parts, but don't enforce them */ + staging->parts = (void *)(staging + 1); + + if (msg_extra_size) /* if requested, allocate message, too */ + staging->msg = (void *)((u8 *)staging->parts + + n_parts * sizeof(*staging->parts)); + + staging->meta_proc = kdbus_meta_proc_new(); + if (IS_ERR(staging->meta_proc)) { + ret = PTR_ERR(staging->meta_proc); + staging->meta_proc = NULL; + goto error; + } + + staging->meta_conn = kdbus_meta_conn_new(); + if (IS_ERR(staging->meta_conn)) { + ret = PTR_ERR(staging->meta_conn); + staging->meta_conn = NULL; + goto error; + } + + /* + * Prepare iovecs to copy the message into the target pool. We use the + * following iovecs: + * * iovec to copy "kdbus_msg.size" + * * iovec to copy "struct kdbus_msg" (minus size) plus items + * * iovec for possible padding after the items + * * iovec for metadata items + * * iovec for possible padding after the items + * + * Make sure to update @reserved_parts if you add more parts here. + */ + + kdbus_staging_reserve(staging); /* msg.size */ + kdbus_staging_reserve(staging); /* msg (minus msg.size) plus items */ + kdbus_staging_reserve(staging); /* msg padding */ + kdbus_staging_reserve(staging); /* meta */ + kdbus_staging_reserve(staging); /* meta padding */ + + return staging; + +error: + kdbus_staging_free(staging); + return ERR_PTR(ret); +} + +struct kdbus_staging *kdbus_staging_new_kernel(struct kdbus_bus *bus, + u64 dst, u64 cookie_timeout, + size_t it_size, size_t it_type) +{ + struct kdbus_staging *staging; + size_t size; + + size = offsetof(struct kdbus_msg, items) + + KDBUS_ITEM_HEADER_SIZE + it_size; + + staging = kdbus_staging_new(bus, 0, KDBUS_ALIGN8(size)); + if (IS_ERR(staging)) + return ERR_CAST(staging); + + staging->msg->size = size; + staging->msg->flags = (dst == KDBUS_DST_ID_BROADCAST) ? + KDBUS_MSG_SIGNAL : 0; + staging->msg->dst_id = dst; + staging->msg->src_id = KDBUS_SRC_ID_KERNEL; + staging->msg->payload_type = KDBUS_PAYLOAD_KERNEL; + staging->msg->cookie_reply = cookie_timeout; + staging->notify = staging->msg->items; + staging->notify->size = KDBUS_ITEM_HEADER_SIZE + it_size; + staging->notify->type = it_type; + + return staging; +} + +struct kdbus_staging *kdbus_staging_new_user(struct kdbus_bus *bus, + struct kdbus_cmd_send *cmd, + struct kdbus_msg *msg) +{ + const size_t reserved_parts = 1; /* see below for explanation */ + size_t n_memfds, n_fds, n_parts; + struct kdbus_staging *staging; + int ret; + + /* + * Examine user-supplied message and figure out how many resources we + * need to allocate in our staging area. This requires us to iterate + * the message twice, but saves us from re-allocating our resources + * all the time. + */ + + ret = kdbus_msg_examine(msg, bus, cmd, &n_memfds, &n_fds, &n_parts); + if (ret < 0) + return ERR_PTR(ret); + + n_parts += reserved_parts; + + /* + * Allocate staging area with the number of required resources. Make + * sure that we have enough iovecs for all required parts pre-allocated + * so this will hopefully be the only memory allocation for this + * message transaction. + */ + + staging = kdbus_staging_new(bus, n_parts, 0); + if (IS_ERR(staging)) + return ERR_CAST(staging); + + staging->msg = msg; + + /* + * If the message contains memfds or fd items, we need to remember some + * state so we can fill in the requested information at RECV time. + * File-descriptors cannot be passed at SEND time. Hence, allocate a + * gaps-object to remember that state. That gaps object is linked to + * from the staging area, but will also be linked to from the message + * queue of each peer. Hence, each receiver owns a reference to it, and + * it will later be used to fill the 'gaps' in message that couldn't be + * filled at SEND time. + * Note that the 'gaps' object is read-only once the staging-allocator + * returns. There might be connections receiving a queued message while + * the sender still broadcasts the message to other receivers. + */ + + if (n_memfds > 0 || n_fds > 0) { + staging->gaps = kdbus_gaps_new(n_memfds, n_fds); + if (IS_ERR(staging->gaps)) { + ret = PTR_ERR(staging->gaps); + staging->gaps = NULL; + kdbus_staging_free(staging); + return ERR_PTR(ret); + } + } + + /* + * kdbus_staging_new() already reserves parts for message setup. For + * user-supplied messages, we add the following iovecs: + * ... variable number of iovecs for payload ... + * * final iovec for possible padding of payload + * + * Make sure to update @reserved_parts if you add more parts here. + */ + + ret = kdbus_staging_import(staging); /* payload */ + kdbus_staging_reserve(staging); /* payload padding */ + + if (ret < 0) + goto error; + + return staging; + +error: + kdbus_staging_free(staging); + return ERR_PTR(ret); +} + +struct kdbus_staging *kdbus_staging_free(struct kdbus_staging *staging) +{ + if (!staging) + return NULL; + + kdbus_meta_conn_unref(staging->meta_conn); + kdbus_meta_proc_unref(staging->meta_proc); + kdbus_gaps_unref(staging->gaps); + kfree(staging); + + return NULL; +} + +static int kdbus_staging_collect_metadata(struct kdbus_staging *staging, + struct kdbus_conn *src, + struct kdbus_conn *dst, + u64 *out_attach) +{ + u64 attach; + int ret; + + if (src) + attach = kdbus_meta_msg_mask(src, dst); + else + attach = KDBUS_ATTACH_TIMESTAMP; /* metadata for kernel msgs */ + + if (src && !src->meta_fake) { + ret = kdbus_meta_proc_collect(staging->meta_proc, attach); + if (ret < 0) + return ret; + } + + ret = kdbus_meta_conn_collect(staging->meta_conn, src, + staging->msg_seqnum, attach); + if (ret < 0) + return ret; + + *out_attach = attach; + return 0; +} + +/** + * kdbus_staging_emit() - emit linearized message in target pool + * @staging: staging object to create message from + * @src: sender of the message (or NULL) + * @dst: target connection to allocate message for + * + * This allocates a pool-slice for @dst and copies the message provided by + * @staging into it. The new slice is then returned to the caller for further + * processing. It's not linked into any queue, yet. + * + * Return: Newly allocated slice or ERR_PTR on failure. + */ +struct kdbus_pool_slice *kdbus_staging_emit(struct kdbus_staging *staging, + struct kdbus_conn *src, + struct kdbus_conn *dst) +{ + struct kdbus_item *item, *meta_items = NULL; + struct kdbus_pool_slice *slice = NULL; + size_t off, size, meta_size; + struct iovec *v; + u64 attach, msg_size; + int ret; + + /* + * Step 1: + * Collect metadata from @src depending on the attach-flags allowed for + * @dst. Translate it into the namespaces pinned by @dst. + */ + + ret = kdbus_staging_collect_metadata(staging, src, dst, &attach); + if (ret < 0) + goto error; + + ret = kdbus_meta_emit(staging->meta_proc, NULL, staging->meta_conn, + dst, attach, &meta_items, &meta_size); + if (ret < 0) + goto error; + + /* + * Step 2: + * Setup iovecs for the message. See kdbus_staging_new() for allocation + * of those iovecs. All reserved iovecs have been initialized with + * iov_len=0 + iov_base=zeros. Furthermore, the iovecs to copy the + * actual message payload have already been initialized and need not be + * touched. + */ + + v = staging->parts; + msg_size = staging->msg->size; + + /* msg.size */ + v->iov_len = sizeof(msg_size); + v->iov_base = (void __user *)&msg_size; + ++v; + + /* msg (after msg.size) plus items */ + v->iov_len = staging->msg->size - sizeof(staging->msg->size); + v->iov_base = (void __user *)((u8 *)staging->msg + + sizeof(staging->msg->size)); + ++v; + + /* padding after msg */ + v->iov_len = KDBUS_ALIGN8(staging->msg->size) - staging->msg->size; + v->iov_base = (void __user *)zeros; + ++v; + + if (meta_size > 0) { + /* metadata items */ + v->iov_len = meta_size; + v->iov_base = (void __user *)meta_items; + ++v; + + /* padding after metadata */ + v->iov_len = KDBUS_ALIGN8(meta_size) - meta_size; + v->iov_base = (void __user *)zeros; + ++v; + + msg_size = KDBUS_ALIGN8(msg_size) + meta_size; + } else { + /* metadata items */ + v->iov_len = 0; + v->iov_base = (void __user *)zeros; + ++v; + + /* padding after metadata */ + v->iov_len = 0; + v->iov_base = (void __user *)zeros; + ++v; + } + + /* ... payload iovecs are already filled in ... */ + + /* compute overall size and fill in padding after payload */ + size = KDBUS_ALIGN8(msg_size); + + if (staging->n_payload > 0) { + size += staging->n_payload; + + v = &staging->parts[staging->n_parts - 1]; + v->iov_len = KDBUS_ALIGN8(size) - size; + v->iov_base = (void __user *)zeros; + + size = KDBUS_ALIGN8(size); + } + + /* + * Step 3: + * The PAYLOAD_OFF items in the message contain a relative 'offset' + * field that tells the receiver where to find the actual payload. This + * offset is relative to the start of the message, and as such depends + * on the size of the metadata items we inserted. This size is variable + * and changes for each peer we send the message to. Hence, we remember + * the last relative offset that was used to calculate the 'offset' + * fields. For each message, we re-calculate it and patch all items, in + * case it changed. + */ + + off = KDBUS_ALIGN8(msg_size); + + if (off != staging->i_payload) { + KDBUS_ITEMS_FOREACH(item, staging->msg->items, + KDBUS_ITEMS_SIZE(staging->msg, items)) { + if (item->type != KDBUS_ITEM_PAYLOAD_OFF) + continue; + + item->vec.offset -= staging->i_payload; + item->vec.offset += off; + } + + staging->i_payload = off; + } + + /* + * Step 4: + * Allocate pool slice and copy over all data. Make sure to properly + * account on user quota. + */ + + ret = kdbus_conn_quota_inc(dst, src ? src->user : NULL, size, + staging->gaps ? staging->gaps->n_fds : 0); + if (ret < 0) + goto error; + + slice = kdbus_pool_slice_alloc(dst->pool, size, true); + if (IS_ERR(slice)) { + ret = PTR_ERR(slice); + slice = NULL; + goto error; + } + + WARN_ON(kdbus_pool_slice_size(slice) != size); + + ret = kdbus_pool_slice_copy_iovec(slice, 0, staging->parts, + staging->n_parts, size); + if (ret < 0) + goto error; + + /* all done, return slice to caller */ + goto exit; + +error: + if (slice) + kdbus_conn_quota_dec(dst, src ? src->user : NULL, size, + staging->gaps ? staging->gaps->n_fds : 0); + kdbus_pool_slice_release(slice); + slice = ERR_PTR(ret); +exit: + kfree(meta_items); + return slice; +} diff --git a/ipc/kdbus/message.h b/ipc/kdbus/message.h new file mode 100644 index 000000000000..298f9c99dfcf --- /dev/null +++ b/ipc/kdbus/message.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_MESSAGE_H +#define __KDBUS_MESSAGE_H + +#include <linux/fs.h> +#include <linux/kref.h> +#include <uapi/linux/kdbus.h> + +struct kdbus_bus; +struct kdbus_conn; +struct kdbus_meta_conn; +struct kdbus_meta_proc; +struct kdbus_pool_slice; + +/** + * struct kdbus_gaps - gaps in message to be filled later + * @kref: Reference counter + * @n_memfd_offs: Number of memfds + * @memfd_offs: Offsets of kdbus_memfd items in target slice + * @n_fds: Number of fds + * @fds: Array of sent fds + * @fds_offset: Offset of fd-array in target slice + * + * The 'gaps' object is used to track data that is needed to fill gaps in a + * message at RECV time. Usually, we try to compile the whole message at SEND + * time. This has the advantage, that we don't have to cache any information and + * can keep the memory consumption small. Furthermore, all copy operations can + * be combined into a single function call, which speeds up transactions + * considerably. + * However, things like file-descriptors can only be fully installed at RECV + * time. The gaps object tracks this data and pins it until a message is + * received. The gaps object is shared between all receivers of the same + * message. + */ +struct kdbus_gaps { + struct kref kref; + + /* state tracking for KDBUS_ITEM_PAYLOAD_MEMFD entries */ + size_t n_memfds; + u64 *memfd_offsets; + struct file **memfd_files; + + /* state tracking for KDBUS_ITEM_FDS */ + size_t n_fds; + struct file **fd_files; + u64 fd_offset; +}; + +struct kdbus_gaps *kdbus_gaps_ref(struct kdbus_gaps *gaps); +struct kdbus_gaps *kdbus_gaps_unref(struct kdbus_gaps *gaps); +int kdbus_gaps_install(struct kdbus_gaps *gaps, struct kdbus_pool_slice *slice, + bool *out_incomplete); + +/** + * struct kdbus_staging - staging area to import messages + * @msg: User-supplied message + * @gaps: Gaps-object created during import (or NULL if empty) + * @msg_seqnum: Message sequence number + * @notify_entry: Entry into list of kernel-generated notifications + * @i_payload: Current relative index of start of payload + * @n_payload: Total number of bytes needed for payload + * @n_parts: Number of parts + * @parts: Array of iovecs that make up the whole message + * @meta_proc: Process metadata of the sender (or NULL if empty) + * @meta_conn: Connection metadata of the sender (or NULL if empty) + * @bloom_filter: Pointer to the bloom-item in @msg, or NULL + * @dst_name: Pointer to the dst-name-item in @msg, or NULL + * @notify: Pointer to the notification item in @msg, or NULL + * + * The kdbus_staging object is a temporary staging area to import user-supplied + * messages into the kernel. It is only used during SEND and dropped once the + * message is queued. Any data that cannot be collected during SEND, is + * collected in a kdbus_gaps object and attached to the message queue. + */ +struct kdbus_staging { + struct kdbus_msg *msg; + struct kdbus_gaps *gaps; + u64 msg_seqnum; + struct list_head notify_entry; + + /* crafted iovecs to copy the message */ + size_t i_payload; + size_t n_payload; + size_t n_parts; + struct iovec *parts; + + /* metadata state */ + struct kdbus_meta_proc *meta_proc; + struct kdbus_meta_conn *meta_conn; + + /* cached pointers into @msg */ + const struct kdbus_bloom_filter *bloom_filter; + const char *dst_name; + struct kdbus_item *notify; +}; + +struct kdbus_staging *kdbus_staging_new_kernel(struct kdbus_bus *bus, + u64 dst, u64 cookie_timeout, + size_t it_size, size_t it_type); +struct kdbus_staging *kdbus_staging_new_user(struct kdbus_bus *bus, + struct kdbus_cmd_send *cmd, + struct kdbus_msg *msg); +struct kdbus_staging *kdbus_staging_free(struct kdbus_staging *staging); +struct kdbus_pool_slice *kdbus_staging_emit(struct kdbus_staging *staging, + struct kdbus_conn *src, + struct kdbus_conn *dst); + +#endif diff --git a/ipc/kdbus/metadata.c b/ipc/kdbus/metadata.c new file mode 100644 index 000000000000..106ec26f2882 --- /dev/null +++ b/ipc/kdbus/metadata.c @@ -0,0 +1,1347 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/audit.h> +#include <linux/capability.h> +#include <linux/cgroup.h> +#include <linux/cred.h> +#include <linux/file.h> +#include <linux/fs_struct.h> +#include <linux/init.h> +#include <linux/kref.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/security.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/uidgid.h> +#include <linux/uio.h> +#include <linux/user_namespace.h> + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "item.h" +#include "message.h" +#include "metadata.h" +#include "names.h" + +/** + * struct kdbus_meta_proc - Process metadata + * @kref: Reference counting + * @lock: Object lock + * @collected: Bitmask of collected items + * @valid: Bitmask of collected and valid items + * @cred: Credentials + * @pid: PID of process + * @tgid: TGID of process + * @ppid: PPID of process + * @tid_comm: TID comm line + * @pid_comm: PID comm line + * @exe_path: Executable path + * @root_path: Root-FS path + * @cmdline: Command-line + * @cgroup: Full cgroup path + * @seclabel: Seclabel + * @audit_loginuid: Audit login-UID + * @audit_sessionid: Audit session-ID + */ +struct kdbus_meta_proc { + struct kref kref; + struct mutex lock; + u64 collected; + u64 valid; + + /* KDBUS_ITEM_CREDS */ + /* KDBUS_ITEM_AUXGROUPS */ + /* KDBUS_ITEM_CAPS */ + const struct cred *cred; + + /* KDBUS_ITEM_PIDS */ + struct pid *pid; + struct pid *tgid; + struct pid *ppid; + + /* KDBUS_ITEM_TID_COMM */ + char tid_comm[TASK_COMM_LEN]; + /* KDBUS_ITEM_PID_COMM */ + char pid_comm[TASK_COMM_LEN]; + + /* KDBUS_ITEM_EXE */ + struct path exe_path; + struct path root_path; + + /* KDBUS_ITEM_CMDLINE */ + char *cmdline; + + /* KDBUS_ITEM_CGROUP */ + char *cgroup; + + /* KDBUS_ITEM_SECLABEL */ + char *seclabel; + + /* KDBUS_ITEM_AUDIT */ + kuid_t audit_loginuid; + unsigned int audit_sessionid; +}; + +/** + * struct kdbus_meta_conn + * @kref: Reference counting + * @lock: Object lock + * @collected: Bitmask of collected items + * @valid: Bitmask of collected and valid items + * @ts: Timestamp values + * @owned_names_items: Serialized items for owned names + * @owned_names_size: Size of @owned_names_items + * @conn_description: Connection description + */ +struct kdbus_meta_conn { + struct kref kref; + struct mutex lock; + u64 collected; + u64 valid; + + /* KDBUS_ITEM_TIMESTAMP */ + struct kdbus_timestamp ts; + + /* KDBUS_ITEM_OWNED_NAME */ + struct kdbus_item *owned_names_items; + size_t owned_names_size; + + /* KDBUS_ITEM_CONN_DESCRIPTION */ + char *conn_description; +}; + +/* fixed size equivalent of "kdbus_caps" */ +struct kdbus_meta_caps { + u32 last_cap; + struct { + u32 caps[_KERNEL_CAPABILITY_U32S]; + } set[4]; +}; + +/** + * kdbus_meta_proc_new() - Create process metadata object + * + * Return: Pointer to new object on success, ERR_PTR on failure. + */ +struct kdbus_meta_proc *kdbus_meta_proc_new(void) +{ + struct kdbus_meta_proc *mp; + + mp = kzalloc(sizeof(*mp), GFP_KERNEL); + if (!mp) + return ERR_PTR(-ENOMEM); + + kref_init(&mp->kref); + mutex_init(&mp->lock); + + return mp; +} + +static void kdbus_meta_proc_free(struct kref *kref) +{ + struct kdbus_meta_proc *mp = container_of(kref, struct kdbus_meta_proc, + kref); + + path_put(&mp->exe_path); + path_put(&mp->root_path); + if (mp->cred) + put_cred(mp->cred); + put_pid(mp->ppid); + put_pid(mp->tgid); + put_pid(mp->pid); + + kfree(mp->seclabel); + kfree(mp->cmdline); + kfree(mp->cgroup); + kfree(mp); +} + +/** + * kdbus_meta_proc_ref() - Gain reference + * @mp: Process metadata object + * + * Return: @mp is returned + */ +struct kdbus_meta_proc *kdbus_meta_proc_ref(struct kdbus_meta_proc *mp) +{ + if (mp) + kref_get(&mp->kref); + return mp; +} + +/** + * kdbus_meta_proc_unref() - Drop reference + * @mp: Process metadata object + * + * Return: NULL + */ +struct kdbus_meta_proc *kdbus_meta_proc_unref(struct kdbus_meta_proc *mp) +{ + if (mp) + kref_put(&mp->kref, kdbus_meta_proc_free); + return NULL; +} + +static void kdbus_meta_proc_collect_pids(struct kdbus_meta_proc *mp) +{ + struct task_struct *parent; + + mp->pid = get_pid(task_pid(current)); + mp->tgid = get_pid(task_tgid(current)); + + rcu_read_lock(); + parent = rcu_dereference(current->real_parent); + mp->ppid = get_pid(task_tgid(parent)); + rcu_read_unlock(); + + mp->valid |= KDBUS_ATTACH_PIDS; +} + +static void kdbus_meta_proc_collect_tid_comm(struct kdbus_meta_proc *mp) +{ + get_task_comm(mp->tid_comm, current); + mp->valid |= KDBUS_ATTACH_TID_COMM; +} + +static void kdbus_meta_proc_collect_pid_comm(struct kdbus_meta_proc *mp) +{ + get_task_comm(mp->pid_comm, current->group_leader); + mp->valid |= KDBUS_ATTACH_PID_COMM; +} + +static void kdbus_meta_proc_collect_exe(struct kdbus_meta_proc *mp) +{ + struct file *exe_file; + + rcu_read_lock(); + exe_file = rcu_dereference(current->mm->exe_file); + if (exe_file) { + mp->exe_path = exe_file->f_path; + path_get(&mp->exe_path); + get_fs_root(current->fs, &mp->root_path); + mp->valid |= KDBUS_ATTACH_EXE; + } + rcu_read_unlock(); +} + +static int kdbus_meta_proc_collect_cmdline(struct kdbus_meta_proc *mp) +{ + struct mm_struct *mm = current->mm; + char *cmdline; + + if (mm->arg_start >= mm->arg_end) + return 0; + + cmdline = strndup_user((const char __user *)mm->arg_start, + mm->arg_end - mm->arg_start); + if (IS_ERR(cmdline)) + return PTR_ERR(cmdline); + + mp->cmdline = cmdline; + mp->valid |= KDBUS_ATTACH_CMDLINE; + + return 0; +} + +static int kdbus_meta_proc_collect_cgroup(struct kdbus_meta_proc *mp) +{ +#ifdef CONFIG_CGROUPS + void *page; + char *s; + + page = (void *)__get_free_page(GFP_TEMPORARY); + if (!page) + return -ENOMEM; + + s = task_cgroup_path(current, page, PAGE_SIZE); + if (s) { + mp->cgroup = kstrdup(s, GFP_KERNEL); + if (!mp->cgroup) { + free_page((unsigned long)page); + return -ENOMEM; + } + } + + free_page((unsigned long)page); + mp->valid |= KDBUS_ATTACH_CGROUP; +#endif + + return 0; +} + +static int kdbus_meta_proc_collect_seclabel(struct kdbus_meta_proc *mp) +{ +#ifdef CONFIG_SECURITY + char *ctx = NULL; + u32 sid, len; + int ret; + + security_task_getsecid(current, &sid); + ret = security_secid_to_secctx(sid, &ctx, &len); + if (ret < 0) { + /* + * EOPNOTSUPP means no security module is active, + * lets skip adding the seclabel then. This effectively + * drops the SECLABEL item. + */ + return (ret == -EOPNOTSUPP) ? 0 : ret; + } + + mp->seclabel = kstrdup(ctx, GFP_KERNEL); + security_release_secctx(ctx, len); + if (!mp->seclabel) + return -ENOMEM; + + mp->valid |= KDBUS_ATTACH_SECLABEL; +#endif + + return 0; +} + +static void kdbus_meta_proc_collect_audit(struct kdbus_meta_proc *mp) +{ +#ifdef CONFIG_AUDITSYSCALL + mp->audit_loginuid = audit_get_loginuid(current); + mp->audit_sessionid = audit_get_sessionid(current); + mp->valid |= KDBUS_ATTACH_AUDIT; +#endif +} + +/** + * kdbus_meta_proc_collect() - Collect process metadata + * @mp: Process metadata object + * @what: Attach flags to collect + * + * This collects process metadata from current and saves it in @mp. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_meta_proc_collect(struct kdbus_meta_proc *mp, u64 what) +{ + int ret; + + if (!mp || !(what & (KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_AUXGROUPS | + KDBUS_ATTACH_TID_COMM | + KDBUS_ATTACH_PID_COMM | + KDBUS_ATTACH_EXE | + KDBUS_ATTACH_CMDLINE | + KDBUS_ATTACH_CGROUP | + KDBUS_ATTACH_CAPS | + KDBUS_ATTACH_SECLABEL | + KDBUS_ATTACH_AUDIT))) + return 0; + + mutex_lock(&mp->lock); + + /* creds, auxgrps and caps share "struct cred" as context */ + { + const u64 m_cred = KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_AUXGROUPS | + KDBUS_ATTACH_CAPS; + + if ((what & m_cred) && !(mp->collected & m_cred)) { + mp->cred = get_current_cred(); + mp->valid |= m_cred; + mp->collected |= m_cred; + } + } + + if ((what & KDBUS_ATTACH_PIDS) && + !(mp->collected & KDBUS_ATTACH_PIDS)) { + kdbus_meta_proc_collect_pids(mp); + mp->collected |= KDBUS_ATTACH_PIDS; + } + + if ((what & KDBUS_ATTACH_TID_COMM) && + !(mp->collected & KDBUS_ATTACH_TID_COMM)) { + kdbus_meta_proc_collect_tid_comm(mp); + mp->collected |= KDBUS_ATTACH_TID_COMM; + } + + if ((what & KDBUS_ATTACH_PID_COMM) && + !(mp->collected & KDBUS_ATTACH_PID_COMM)) { + kdbus_meta_proc_collect_pid_comm(mp); + mp->collected |= KDBUS_ATTACH_PID_COMM; + } + + if ((what & KDBUS_ATTACH_EXE) && + !(mp->collected & KDBUS_ATTACH_EXE)) { + kdbus_meta_proc_collect_exe(mp); + mp->collected |= KDBUS_ATTACH_EXE; + } + + if ((what & KDBUS_ATTACH_CMDLINE) && + !(mp->collected & KDBUS_ATTACH_CMDLINE)) { + ret = kdbus_meta_proc_collect_cmdline(mp); + if (ret < 0) + goto exit_unlock; + mp->collected |= KDBUS_ATTACH_CMDLINE; + } + + if ((what & KDBUS_ATTACH_CGROUP) && + !(mp->collected & KDBUS_ATTACH_CGROUP)) { + ret = kdbus_meta_proc_collect_cgroup(mp); + if (ret < 0) + goto exit_unlock; + mp->collected |= KDBUS_ATTACH_CGROUP; + } + + if ((what & KDBUS_ATTACH_SECLABEL) && + !(mp->collected & KDBUS_ATTACH_SECLABEL)) { + ret = kdbus_meta_proc_collect_seclabel(mp); + if (ret < 0) + goto exit_unlock; + mp->collected |= KDBUS_ATTACH_SECLABEL; + } + + if ((what & KDBUS_ATTACH_AUDIT) && + !(mp->collected & KDBUS_ATTACH_AUDIT)) { + kdbus_meta_proc_collect_audit(mp); + mp->collected |= KDBUS_ATTACH_AUDIT; + } + + ret = 0; + +exit_unlock: + mutex_unlock(&mp->lock); + return ret; +} + +/** + * kdbus_meta_fake_new() - Create fake metadata object + * + * Return: Pointer to new object on success, ERR_PTR on failure. + */ +struct kdbus_meta_fake *kdbus_meta_fake_new(void) +{ + struct kdbus_meta_fake *mf; + + mf = kzalloc(sizeof(*mf), GFP_KERNEL); + if (!mf) + return ERR_PTR(-ENOMEM); + + return mf; +} + +/** + * kdbus_meta_fake_free() - Free fake metadata object + * @mf: Fake metadata object + * + * Return: NULL + */ +struct kdbus_meta_fake *kdbus_meta_fake_free(struct kdbus_meta_fake *mf) +{ + if (mf) { + put_pid(mf->ppid); + put_pid(mf->tgid); + put_pid(mf->pid); + kfree(mf->seclabel); + kfree(mf); + } + + return NULL; +} + +/** + * kdbus_meta_fake_collect() - Fill fake metadata from faked credentials + * @mf: Fake metadata object + * @creds: Creds to set, may be %NULL + * @pids: PIDs to set, may be %NULL + * @seclabel: Seclabel to set, may be %NULL + * + * This function takes information stored in @creds, @pids and @seclabel and + * resolves them to kernel-representations, if possible. This call uses the + * current task's namespaces to resolve the given information. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_meta_fake_collect(struct kdbus_meta_fake *mf, + const struct kdbus_creds *creds, + const struct kdbus_pids *pids, + const char *seclabel) +{ + if (mf->valid) + return -EALREADY; + + if (creds) { + struct user_namespace *ns = current_user_ns(); + + mf->uid = make_kuid(ns, creds->uid); + mf->euid = make_kuid(ns, creds->euid); + mf->suid = make_kuid(ns, creds->suid); + mf->fsuid = make_kuid(ns, creds->fsuid); + + mf->gid = make_kgid(ns, creds->gid); + mf->egid = make_kgid(ns, creds->egid); + mf->sgid = make_kgid(ns, creds->sgid); + mf->fsgid = make_kgid(ns, creds->fsgid); + + if ((creds->uid != (uid_t)-1 && !uid_valid(mf->uid)) || + (creds->euid != (uid_t)-1 && !uid_valid(mf->euid)) || + (creds->suid != (uid_t)-1 && !uid_valid(mf->suid)) || + (creds->fsuid != (uid_t)-1 && !uid_valid(mf->fsuid)) || + (creds->gid != (gid_t)-1 && !gid_valid(mf->gid)) || + (creds->egid != (gid_t)-1 && !gid_valid(mf->egid)) || + (creds->sgid != (gid_t)-1 && !gid_valid(mf->sgid)) || + (creds->fsgid != (gid_t)-1 && !gid_valid(mf->fsgid))) + return -EINVAL; + + mf->valid |= KDBUS_ATTACH_CREDS; + } + + if (pids) { + mf->pid = get_pid(find_vpid(pids->tid)); + mf->tgid = get_pid(find_vpid(pids->pid)); + mf->ppid = get_pid(find_vpid(pids->ppid)); + + if ((pids->tid != 0 && !mf->pid) || + (pids->pid != 0 && !mf->tgid) || + (pids->ppid != 0 && !mf->ppid)) { + put_pid(mf->pid); + put_pid(mf->tgid); + put_pid(mf->ppid); + mf->pid = NULL; + mf->tgid = NULL; + mf->ppid = NULL; + return -EINVAL; + } + + mf->valid |= KDBUS_ATTACH_PIDS; + } + + if (seclabel) { + mf->seclabel = kstrdup(seclabel, GFP_KERNEL); + if (!mf->seclabel) + return -ENOMEM; + + mf->valid |= KDBUS_ATTACH_SECLABEL; + } + + return 0; +} + +/** + * kdbus_meta_conn_new() - Create connection metadata object + * + * Return: Pointer to new object on success, ERR_PTR on failure. + */ +struct kdbus_meta_conn *kdbus_meta_conn_new(void) +{ + struct kdbus_meta_conn *mc; + + mc = kzalloc(sizeof(*mc), GFP_KERNEL); + if (!mc) + return ERR_PTR(-ENOMEM); + + kref_init(&mc->kref); + mutex_init(&mc->lock); + + return mc; +} + +static void kdbus_meta_conn_free(struct kref *kref) +{ + struct kdbus_meta_conn *mc = + container_of(kref, struct kdbus_meta_conn, kref); + + kfree(mc->conn_description); + kfree(mc->owned_names_items); + kfree(mc); +} + +/** + * kdbus_meta_conn_ref() - Gain reference + * @mc: Connection metadata object + */ +struct kdbus_meta_conn *kdbus_meta_conn_ref(struct kdbus_meta_conn *mc) +{ + if (mc) + kref_get(&mc->kref); + return mc; +} + +/** + * kdbus_meta_conn_unref() - Drop reference + * @mc: Connection metadata object + */ +struct kdbus_meta_conn *kdbus_meta_conn_unref(struct kdbus_meta_conn *mc) +{ + if (mc) + kref_put(&mc->kref, kdbus_meta_conn_free); + return NULL; +} + +static void kdbus_meta_conn_collect_timestamp(struct kdbus_meta_conn *mc, + u64 msg_seqnum) +{ + mc->ts.monotonic_ns = ktime_get_ns(); + mc->ts.realtime_ns = ktime_get_real_ns(); + + if (msg_seqnum) + mc->ts.seqnum = msg_seqnum; + + mc->valid |= KDBUS_ATTACH_TIMESTAMP; +} + +static int kdbus_meta_conn_collect_names(struct kdbus_meta_conn *mc, + struct kdbus_conn *conn) +{ + const struct kdbus_name_owner *owner; + struct kdbus_item *item; + size_t slen, size; + + lockdep_assert_held(&conn->ep->bus->name_registry->rwlock); + + size = 0; + /* open-code length calculation to avoid final padding */ + list_for_each_entry(owner, &conn->names_list, conn_entry) + if (!(owner->flags & KDBUS_NAME_IN_QUEUE)) + size = KDBUS_ALIGN8(size) + KDBUS_ITEM_HEADER_SIZE + + sizeof(struct kdbus_name) + + strlen(owner->name->name) + 1; + + if (!size) + return 0; + + /* make sure we include zeroed padding for convenience helpers */ + item = kmalloc(KDBUS_ALIGN8(size), GFP_KERNEL); + if (!item) + return -ENOMEM; + + mc->owned_names_items = item; + mc->owned_names_size = size; + + list_for_each_entry(owner, &conn->names_list, conn_entry) { + if (owner->flags & KDBUS_NAME_IN_QUEUE) + continue; + + slen = strlen(owner->name->name) + 1; + kdbus_item_set(item, KDBUS_ITEM_OWNED_NAME, NULL, + sizeof(struct kdbus_name) + slen); + item->name.flags = owner->flags; + memcpy(item->name.name, owner->name->name, slen); + item = KDBUS_ITEM_NEXT(item); + } + + /* sanity check: the buffer should be completely written now */ + WARN_ON((u8 *)item != + (u8 *)mc->owned_names_items + KDBUS_ALIGN8(size)); + + mc->valid |= KDBUS_ATTACH_NAMES; + return 0; +} + +static int kdbus_meta_conn_collect_description(struct kdbus_meta_conn *mc, + struct kdbus_conn *conn) +{ + if (!conn->description) + return 0; + + mc->conn_description = kstrdup(conn->description, GFP_KERNEL); + if (!mc->conn_description) + return -ENOMEM; + + mc->valid |= KDBUS_ATTACH_CONN_DESCRIPTION; + return 0; +} + +/** + * kdbus_meta_conn_collect() - Collect connection metadata + * @mc: Message metadata object + * @conn: Connection to collect data from + * @msg_seqnum: Sequence number of the message to send + * @what: Attach flags to collect + * + * This collects connection metadata from @msg_seqnum and @conn and saves it + * in @mc. + * + * If KDBUS_ATTACH_NAMES is set in @what and @conn is non-NULL, the caller must + * hold the name-registry read-lock of conn->ep->bus->registry. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_meta_conn_collect(struct kdbus_meta_conn *mc, + struct kdbus_conn *conn, + u64 msg_seqnum, u64 what) +{ + int ret; + + if (!mc || !(what & (KDBUS_ATTACH_TIMESTAMP | + KDBUS_ATTACH_NAMES | + KDBUS_ATTACH_CONN_DESCRIPTION))) + return 0; + + mutex_lock(&mc->lock); + + if (msg_seqnum && (what & KDBUS_ATTACH_TIMESTAMP) && + !(mc->collected & KDBUS_ATTACH_TIMESTAMP)) { + kdbus_meta_conn_collect_timestamp(mc, msg_seqnum); + mc->collected |= KDBUS_ATTACH_TIMESTAMP; + } + + if (conn && (what & KDBUS_ATTACH_NAMES) && + !(mc->collected & KDBUS_ATTACH_NAMES)) { + ret = kdbus_meta_conn_collect_names(mc, conn); + if (ret < 0) + goto exit_unlock; + mc->collected |= KDBUS_ATTACH_NAMES; + } + + if (conn && (what & KDBUS_ATTACH_CONN_DESCRIPTION) && + !(mc->collected & KDBUS_ATTACH_CONN_DESCRIPTION)) { + ret = kdbus_meta_conn_collect_description(mc, conn); + if (ret < 0) + goto exit_unlock; + mc->collected |= KDBUS_ATTACH_CONN_DESCRIPTION; + } + + ret = 0; + +exit_unlock: + mutex_unlock(&mc->lock); + return ret; +} + +static void kdbus_meta_export_caps(struct kdbus_meta_caps *out, + const struct kdbus_meta_proc *mp, + struct user_namespace *user_ns) +{ + struct user_namespace *iter; + const struct cred *cred = mp->cred; + bool parent = false, owner = false; + int i; + + /* + * This translates the effective capabilities of 'cred' into the given + * user-namespace. If the given user-namespace is a child-namespace of + * the user-namespace of 'cred', the mask can be copied verbatim. If + * not, the mask is cleared. + * There's one exception: If 'cred' is the owner of any user-namespace + * in the path between the given user-namespace and the user-namespace + * of 'cred', then it has all effective capabilities set. This means, + * the user who created a user-namespace always has all effective + * capabilities in any child namespaces. Note that this is based on the + * uid of the namespace creator, not the task hierarchy. + */ + for (iter = user_ns; iter; iter = iter->parent) { + if (iter == cred->user_ns) { + parent = true; + break; + } + + if (iter == &init_user_ns) + break; + + if ((iter->parent == cred->user_ns) && + uid_eq(iter->owner, cred->euid)) { + owner = true; + break; + } + } + + out->last_cap = CAP_LAST_CAP; + + CAP_FOR_EACH_U32(i) { + if (parent) { + out->set[0].caps[i] = cred->cap_inheritable.cap[i]; + out->set[1].caps[i] = cred->cap_permitted.cap[i]; + out->set[2].caps[i] = cred->cap_effective.cap[i]; + out->set[3].caps[i] = cred->cap_bset.cap[i]; + } else if (owner) { + out->set[0].caps[i] = 0U; + out->set[1].caps[i] = ~0U; + out->set[2].caps[i] = ~0U; + out->set[3].caps[i] = ~0U; + } else { + out->set[0].caps[i] = 0U; + out->set[1].caps[i] = 0U; + out->set[2].caps[i] = 0U; + out->set[3].caps[i] = 0U; + } + } + + /* clear unused bits */ + for (i = 0; i < 4; i++) + out->set[i].caps[CAP_TO_INDEX(CAP_LAST_CAP)] &= + CAP_LAST_U32_VALID_MASK; +} + +/* This is equivalent to from_kuid_munged(), but maps INVALID_UID to itself */ +static uid_t kdbus_from_kuid_keep(struct user_namespace *ns, kuid_t uid) +{ + return uid_valid(uid) ? from_kuid_munged(ns, uid) : ((uid_t)-1); +} + +/* This is equivalent to from_kgid_munged(), but maps INVALID_GID to itself */ +static gid_t kdbus_from_kgid_keep(struct user_namespace *ns, kgid_t gid) +{ + return gid_valid(gid) ? from_kgid_munged(ns, gid) : ((gid_t)-1); +} + +struct kdbus_meta_staging { + const struct kdbus_meta_proc *mp; + const struct kdbus_meta_fake *mf; + const struct kdbus_meta_conn *mc; + const struct kdbus_conn *conn; + u64 mask; + + void *exe; + const char *exe_path; +}; + +static size_t kdbus_meta_measure(struct kdbus_meta_staging *staging) +{ + const struct kdbus_meta_proc *mp = staging->mp; + const struct kdbus_meta_fake *mf = staging->mf; + const struct kdbus_meta_conn *mc = staging->mc; + const u64 mask = staging->mask; + size_t size = 0; + + /* process metadata */ + + if (mf && (mask & KDBUS_ATTACH_CREDS)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_creds)); + else if (mp && (mask & KDBUS_ATTACH_CREDS)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_creds)); + + if (mf && (mask & KDBUS_ATTACH_PIDS)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_pids)); + else if (mp && (mask & KDBUS_ATTACH_PIDS)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_pids)); + + if (mp && (mask & KDBUS_ATTACH_AUXGROUPS)) + size += KDBUS_ITEM_SIZE(mp->cred->group_info->ngroups * + sizeof(u64)); + + if (mp && (mask & KDBUS_ATTACH_TID_COMM)) + size += KDBUS_ITEM_SIZE(strlen(mp->tid_comm) + 1); + + if (mp && (mask & KDBUS_ATTACH_PID_COMM)) + size += KDBUS_ITEM_SIZE(strlen(mp->pid_comm) + 1); + + if (staging->exe_path && (mask & KDBUS_ATTACH_EXE)) + size += KDBUS_ITEM_SIZE(strlen(staging->exe_path) + 1); + + if (mp && (mask & KDBUS_ATTACH_CMDLINE)) + size += KDBUS_ITEM_SIZE(strlen(mp->cmdline) + 1); + + if (mp && (mask & KDBUS_ATTACH_CGROUP)) + size += KDBUS_ITEM_SIZE(strlen(mp->cgroup) + 1); + + if (mp && (mask & KDBUS_ATTACH_CAPS)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_meta_caps)); + + if (mf && (mask & KDBUS_ATTACH_SECLABEL)) + size += KDBUS_ITEM_SIZE(strlen(mf->seclabel) + 1); + else if (mp && (mask & KDBUS_ATTACH_SECLABEL)) + size += KDBUS_ITEM_SIZE(strlen(mp->seclabel) + 1); + + if (mp && (mask & KDBUS_ATTACH_AUDIT)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_audit)); + + /* connection metadata */ + + if (mc && (mask & KDBUS_ATTACH_NAMES)) + size += KDBUS_ALIGN8(mc->owned_names_size); + + if (mc && (mask & KDBUS_ATTACH_CONN_DESCRIPTION)) + size += KDBUS_ITEM_SIZE(strlen(mc->conn_description) + 1); + + if (mc && (mask & KDBUS_ATTACH_TIMESTAMP)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_timestamp)); + + return size; +} + +static struct kdbus_item *kdbus_write_head(struct kdbus_item **iter, + u64 type, u64 size) +{ + struct kdbus_item *item = *iter; + size_t padding; + + item->type = type; + item->size = KDBUS_ITEM_HEADER_SIZE + size; + + /* clear padding */ + padding = KDBUS_ALIGN8(item->size) - item->size; + if (padding) + memset(item->data + size, 0, padding); + + *iter = KDBUS_ITEM_NEXT(item); + return item; +} + +static struct kdbus_item *kdbus_write_full(struct kdbus_item **iter, + u64 type, u64 size, const void *data) +{ + struct kdbus_item *item; + + item = kdbus_write_head(iter, type, size); + memcpy(item->data, data, size); + return item; +} + +static size_t kdbus_meta_write(struct kdbus_meta_staging *staging, void *mem, + size_t size) +{ + struct user_namespace *user_ns = staging->conn->cred->user_ns; + struct pid_namespace *pid_ns = ns_of_pid(staging->conn->pid); + struct kdbus_item *item = NULL, *items = mem; + u8 *end, *owned_names_end = NULL; + + /* process metadata */ + + if (staging->mf && (staging->mask & KDBUS_ATTACH_CREDS)) { + const struct kdbus_meta_fake *mf = staging->mf; + + item = kdbus_write_head(&items, KDBUS_ITEM_CREDS, + sizeof(struct kdbus_creds)); + item->creds = (struct kdbus_creds){ + .uid = kdbus_from_kuid_keep(user_ns, mf->uid), + .euid = kdbus_from_kuid_keep(user_ns, mf->euid), + .suid = kdbus_from_kuid_keep(user_ns, mf->suid), + .fsuid = kdbus_from_kuid_keep(user_ns, mf->fsuid), + .gid = kdbus_from_kgid_keep(user_ns, mf->gid), + .egid = kdbus_from_kgid_keep(user_ns, mf->egid), + .sgid = kdbus_from_kgid_keep(user_ns, mf->sgid), + .fsgid = kdbus_from_kgid_keep(user_ns, mf->fsgid), + }; + } else if (staging->mp && (staging->mask & KDBUS_ATTACH_CREDS)) { + const struct cred *c = staging->mp->cred; + + item = kdbus_write_head(&items, KDBUS_ITEM_CREDS, + sizeof(struct kdbus_creds)); + item->creds = (struct kdbus_creds){ + .uid = kdbus_from_kuid_keep(user_ns, c->uid), + .euid = kdbus_from_kuid_keep(user_ns, c->euid), + .suid = kdbus_from_kuid_keep(user_ns, c->suid), + .fsuid = kdbus_from_kuid_keep(user_ns, c->fsuid), + .gid = kdbus_from_kgid_keep(user_ns, c->gid), + .egid = kdbus_from_kgid_keep(user_ns, c->egid), + .sgid = kdbus_from_kgid_keep(user_ns, c->sgid), + .fsgid = kdbus_from_kgid_keep(user_ns, c->fsgid), + }; + } + + if (staging->mf && (staging->mask & KDBUS_ATTACH_PIDS)) { + item = kdbus_write_head(&items, KDBUS_ITEM_PIDS, + sizeof(struct kdbus_pids)); + item->pids = (struct kdbus_pids){ + .pid = pid_nr_ns(staging->mf->tgid, pid_ns), + .tid = pid_nr_ns(staging->mf->pid, pid_ns), + .ppid = pid_nr_ns(staging->mf->ppid, pid_ns), + }; + } else if (staging->mp && (staging->mask & KDBUS_ATTACH_PIDS)) { + item = kdbus_write_head(&items, KDBUS_ITEM_PIDS, + sizeof(struct kdbus_pids)); + item->pids = (struct kdbus_pids){ + .pid = pid_nr_ns(staging->mp->tgid, pid_ns), + .tid = pid_nr_ns(staging->mp->pid, pid_ns), + .ppid = pid_nr_ns(staging->mp->ppid, pid_ns), + }; + } + + if (staging->mp && (staging->mask & KDBUS_ATTACH_AUXGROUPS)) { + const struct group_info *info = staging->mp->cred->group_info; + size_t i; + + item = kdbus_write_head(&items, KDBUS_ITEM_AUXGROUPS, + info->ngroups * sizeof(u64)); + for (i = 0; i < info->ngroups; ++i) + item->data64[i] = from_kgid_munged(user_ns, + GROUP_AT(info, i)); + } + + if (staging->mp && (staging->mask & KDBUS_ATTACH_TID_COMM)) + item = kdbus_write_full(&items, KDBUS_ITEM_TID_COMM, + strlen(staging->mp->tid_comm) + 1, + staging->mp->tid_comm); + + if (staging->mp && (staging->mask & KDBUS_ATTACH_PID_COMM)) + item = kdbus_write_full(&items, KDBUS_ITEM_PID_COMM, + strlen(staging->mp->pid_comm) + 1, + staging->mp->pid_comm); + + if (staging->exe_path && (staging->mask & KDBUS_ATTACH_EXE)) + item = kdbus_write_full(&items, KDBUS_ITEM_EXE, + strlen(staging->exe_path) + 1, + staging->exe_path); + + if (staging->mp && (staging->mask & KDBUS_ATTACH_CMDLINE)) + item = kdbus_write_full(&items, KDBUS_ITEM_CMDLINE, + strlen(staging->mp->cmdline) + 1, + staging->mp->cmdline); + + if (staging->mp && (staging->mask & KDBUS_ATTACH_CGROUP)) + item = kdbus_write_full(&items, KDBUS_ITEM_CGROUP, + strlen(staging->mp->cgroup) + 1, + staging->mp->cgroup); + + if (staging->mp && (staging->mask & KDBUS_ATTACH_CAPS)) { + item = kdbus_write_head(&items, KDBUS_ITEM_CAPS, + sizeof(struct kdbus_meta_caps)); + kdbus_meta_export_caps((void*)&item->caps, staging->mp, + user_ns); + } + + if (staging->mf && (staging->mask & KDBUS_ATTACH_SECLABEL)) + item = kdbus_write_full(&items, KDBUS_ITEM_SECLABEL, + strlen(staging->mf->seclabel) + 1, + staging->mf->seclabel); + else if (staging->mp && (staging->mask & KDBUS_ATTACH_SECLABEL)) + item = kdbus_write_full(&items, KDBUS_ITEM_SECLABEL, + strlen(staging->mp->seclabel) + 1, + staging->mp->seclabel); + + if (staging->mp && (staging->mask & KDBUS_ATTACH_AUDIT)) { + item = kdbus_write_head(&items, KDBUS_ITEM_AUDIT, + sizeof(struct kdbus_audit)); + item->audit = (struct kdbus_audit){ + .loginuid = from_kuid(user_ns, + staging->mp->audit_loginuid), + .sessionid = staging->mp->audit_sessionid, + }; + } + + /* connection metadata */ + + if (staging->mc && (staging->mask & KDBUS_ATTACH_NAMES)) { + memcpy(items, staging->mc->owned_names_items, + KDBUS_ALIGN8(staging->mc->owned_names_size)); + owned_names_end = (u8 *)items + staging->mc->owned_names_size; + items = (void *)KDBUS_ALIGN8((unsigned long)owned_names_end); + } + + if (staging->mc && (staging->mask & KDBUS_ATTACH_CONN_DESCRIPTION)) + item = kdbus_write_full(&items, KDBUS_ITEM_CONN_DESCRIPTION, + strlen(staging->mc->conn_description) + 1, + staging->mc->conn_description); + + if (staging->mc && (staging->mask & KDBUS_ATTACH_TIMESTAMP)) + item = kdbus_write_full(&items, KDBUS_ITEM_TIMESTAMP, + sizeof(staging->mc->ts), + &staging->mc->ts); + + /* + * Return real size (minus trailing padding). In case of 'owned_names' + * we cannot deduce it from item->size, so treat it special. + */ + + if (items == (void *)KDBUS_ALIGN8((unsigned long)owned_names_end)) + end = owned_names_end; + else if (item) + end = (u8 *)item + item->size; + else + end = mem; + + WARN_ON((u8 *)items - (u8 *)mem != size); + WARN_ON((void *)KDBUS_ALIGN8((unsigned long)end) != (void *)items); + + return end - (u8 *)mem; +} + +int kdbus_meta_emit(struct kdbus_meta_proc *mp, + struct kdbus_meta_fake *mf, + struct kdbus_meta_conn *mc, + struct kdbus_conn *conn, + u64 mask, + struct kdbus_item **out_items, + size_t *out_size) +{ + struct kdbus_meta_staging staging = {}; + struct kdbus_item *items = NULL; + size_t size = 0; + int ret; + + if (WARN_ON(mf && mp)) + mp = NULL; + + staging.mp = mp; + staging.mf = mf; + staging.mc = mc; + staging.conn = conn; + + /* get mask of valid items */ + if (mf) + staging.mask |= mf->valid; + if (mp) { + mutex_lock(&mp->lock); + staging.mask |= mp->valid; + mutex_unlock(&mp->lock); + } + if (mc) { + mutex_lock(&mc->lock); + staging.mask |= mc->valid; + mutex_unlock(&mc->lock); + } + + staging.mask &= mask; + + if (!staging.mask) { /* bail out if nothing to do */ + ret = 0; + goto exit; + } + + /* EXE is special as it needs a temporary page to assemble */ + if (mp && (staging.mask & KDBUS_ATTACH_EXE)) { + struct path p; + + /* + * XXX: We need access to __d_path() so we can write the path + * relative to conn->root_path. Once upstream, we need + * EXPORT_SYMBOL(__d_path) or an equivalent of d_path() that + * takes the root path directly. Until then, we drop this item + * if the root-paths differ. + */ + + get_fs_root(current->fs, &p); + if (path_equal(&p, &conn->root_path)) { + staging.exe = (void *)__get_free_page(GFP_TEMPORARY); + if (!staging.exe) { + path_put(&p); + ret = -ENOMEM; + goto exit; + } + + staging.exe_path = d_path(&mp->exe_path, staging.exe, + PAGE_SIZE); + if (IS_ERR(staging.exe_path)) { + path_put(&p); + ret = PTR_ERR(staging.exe_path); + goto exit; + } + } + path_put(&p); + } + + size = kdbus_meta_measure(&staging); + if (!size) { /* bail out if nothing to do */ + ret = 0; + goto exit; + } + + items = kmalloc(size, GFP_KERNEL); + if (!items) { + ret = -ENOMEM; + goto exit; + } + + size = kdbus_meta_write(&staging, items, size); + if (!size) { + kfree(items); + items = NULL; + } + + ret = 0; + +exit: + if (staging.exe) + free_page((unsigned long)staging.exe); + if (ret >= 0) { + *out_items = items; + *out_size = size; + } + return ret; +} + +enum { + KDBUS_META_PROC_NONE, + KDBUS_META_PROC_NORMAL, +}; + +/** + * kdbus_proc_permission() - check /proc permissions on target pid + * @pid_ns: namespace we operate in + * @cred: credentials of requestor + * @target: target process + * + * This checks whether a process with credentials @cred can access information + * of @target in the namespace @pid_ns. This tries to follow /proc permissions, + * but is slightly more restrictive. + * + * Return: The /proc access level (KDBUS_META_PROC_*) is returned. + */ +static unsigned int kdbus_proc_permission(const struct pid_namespace *pid_ns, + const struct cred *cred, + struct pid *target) +{ + if (pid_ns->hide_pid < 1) + return KDBUS_META_PROC_NORMAL; + + /* XXX: we need groups_search() exported for aux-groups */ + if (gid_eq(cred->egid, pid_ns->pid_gid)) + return KDBUS_META_PROC_NORMAL; + + /* + * XXX: If ptrace_may_access(PTRACE_MODE_READ) is granted, you can + * overwrite hide_pid. However, ptrace_may_access() only supports + * checking 'current', hence, we cannot use this here. But we + * simply decide to not support this override, so no need to worry. + */ + + return KDBUS_META_PROC_NONE; +} + +/** + * kdbus_meta_proc_mask() - calculate which metadata would be visible to + * a connection via /proc + * @prv_pid: pid of metadata provider + * @req_pid: pid of metadata requestor + * @req_cred: credentials of metadata reqeuestor + * @wanted: metadata that is requested + * + * This checks which metadata items of @prv_pid can be read via /proc by the + * requestor @req_pid. + * + * Return: Set of metadata flags the requestor can see (limited by @wanted). + */ +static u64 kdbus_meta_proc_mask(struct pid *prv_pid, + struct pid *req_pid, + const struct cred *req_cred, + u64 wanted) +{ + struct pid_namespace *prv_ns, *req_ns; + unsigned int proc; + + prv_ns = ns_of_pid(prv_pid); + req_ns = ns_of_pid(req_pid); + + /* + * If the sender is not visible in the receiver namespace, then the + * receiver cannot access the sender via its own procfs. Hence, we do + * not attach any additional metadata. + */ + if (!pid_nr_ns(prv_pid, req_ns)) + return 0; + + /* + * If the pid-namespace of the receiver has hide_pid set, it cannot see + * any process but its own. We shortcut this /proc permission check if + * provider and requestor are the same. If not, we perform rather + * expensive /proc permission checks. + */ + if (prv_pid == req_pid) + proc = KDBUS_META_PROC_NORMAL; + else + proc = kdbus_proc_permission(req_ns, req_cred, prv_pid); + + /* you need /proc access to read standard process attributes */ + if (proc < KDBUS_META_PROC_NORMAL) + wanted &= ~(KDBUS_ATTACH_TID_COMM | + KDBUS_ATTACH_PID_COMM | + KDBUS_ATTACH_SECLABEL | + KDBUS_ATTACH_CMDLINE | + KDBUS_ATTACH_CGROUP | + KDBUS_ATTACH_AUDIT | + KDBUS_ATTACH_CAPS | + KDBUS_ATTACH_EXE); + + /* clear all non-/proc flags */ + return wanted & (KDBUS_ATTACH_TID_COMM | + KDBUS_ATTACH_PID_COMM | + KDBUS_ATTACH_SECLABEL | + KDBUS_ATTACH_CMDLINE | + KDBUS_ATTACH_CGROUP | + KDBUS_ATTACH_AUDIT | + KDBUS_ATTACH_CAPS | + KDBUS_ATTACH_EXE); +} + +/** + * kdbus_meta_get_mask() - calculate attach flags mask for metadata request + * @prv_pid: pid of metadata provider + * @prv_mask: mask of metadata the provide grants unchecked + * @req_pid: pid of metadata requestor + * @req_cred: credentials of metadata requestor + * @req_mask: mask of metadata that is requested + * + * This calculates the metadata items that the requestor @req_pid can access + * from the metadata provider @prv_pid. This permission check consists of + * several different parts: + * - Providers can grant metadata items unchecked. Regardless of their type, + * they're always granted to the requestor. This mask is passed as @prv_mask. + * - Basic items (credentials and connection metadata) are granted implicitly + * to everyone. They're publicly available to any bus-user that can see the + * provider. + * - Process credentials that are not granted implicitly follow the same + * permission checks as /proc. This means, we always assume a requestor + * process has access to their *own* /proc mount, if they have access to + * kdbusfs. + * + * Return: Mask of metadata that is granted. + */ +static u64 kdbus_meta_get_mask(struct pid *prv_pid, u64 prv_mask, + struct pid *req_pid, + const struct cred *req_cred, u64 req_mask) +{ + u64 missing, impl_mask, proc_mask = 0; + + /* + * Connection metadata and basic unix process credentials are + * transmitted implicitly, and cannot be suppressed. Both are required + * to perform user-space policies on the receiver-side. Furthermore, + * connection metadata is public state, anyway, and unix credentials + * are needed for UDS-compatibility. We extend them slightly by + * auxiliary groups and additional uids/gids/pids. + */ + impl_mask = /* connection metadata */ + KDBUS_ATTACH_CONN_DESCRIPTION | + KDBUS_ATTACH_TIMESTAMP | + KDBUS_ATTACH_NAMES | + /* credentials and pids */ + KDBUS_ATTACH_AUXGROUPS | + KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS; + + /* + * Calculate the set of metadata that is not granted implicitly nor by + * the sender, but still requested by the receiver. If any are left, + * perform rather expensive /proc access checks for them. + */ + missing = req_mask & ~((prv_mask | impl_mask) & req_mask); + if (missing) + proc_mask = kdbus_meta_proc_mask(prv_pid, req_pid, req_cred, + missing); + + return (prv_mask | impl_mask | proc_mask) & req_mask; +} + +/** + */ +u64 kdbus_meta_info_mask(const struct kdbus_conn *conn, u64 mask) +{ + return kdbus_meta_get_mask(conn->pid, + atomic64_read(&conn->attach_flags_send), + task_pid(current), + current_cred(), + mask); +} + +/** + */ +u64 kdbus_meta_msg_mask(const struct kdbus_conn *snd, + const struct kdbus_conn *rcv) +{ + return kdbus_meta_get_mask(task_pid(current), + atomic64_read(&snd->attach_flags_send), + rcv->pid, + rcv->cred, + atomic64_read(&rcv->attach_flags_recv)); +} diff --git a/ipc/kdbus/metadata.h b/ipc/kdbus/metadata.h new file mode 100644 index 000000000000..dba7cc7fdbcb --- /dev/null +++ b/ipc/kdbus/metadata.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_METADATA_H +#define __KDBUS_METADATA_H + +#include <linux/kernel.h> + +struct kdbus_conn; +struct kdbus_pool_slice; + +struct kdbus_meta_proc; +struct kdbus_meta_conn; + +/** + * struct kdbus_meta_fake - Fake metadata + * @valid: Bitmask of collected and valid items + * @uid: UID of process + * @euid: EUID of process + * @suid: SUID of process + * @fsuid: FSUID of process + * @gid: GID of process + * @egid: EGID of process + * @sgid: SGID of process + * @fsgid: FSGID of process + * @pid: PID of process + * @tgid: TGID of process + * @ppid: PPID of process + * @seclabel: Seclabel + */ +struct kdbus_meta_fake { + u64 valid; + + /* KDBUS_ITEM_CREDS */ + kuid_t uid, euid, suid, fsuid; + kgid_t gid, egid, sgid, fsgid; + + /* KDBUS_ITEM_PIDS */ + struct pid *pid, *tgid, *ppid; + + /* KDBUS_ITEM_SECLABEL */ + char *seclabel; +}; + +struct kdbus_meta_proc *kdbus_meta_proc_new(void); +struct kdbus_meta_proc *kdbus_meta_proc_ref(struct kdbus_meta_proc *mp); +struct kdbus_meta_proc *kdbus_meta_proc_unref(struct kdbus_meta_proc *mp); +int kdbus_meta_proc_collect(struct kdbus_meta_proc *mp, u64 what); + +struct kdbus_meta_fake *kdbus_meta_fake_new(void); +struct kdbus_meta_fake *kdbus_meta_fake_free(struct kdbus_meta_fake *mf); +int kdbus_meta_fake_collect(struct kdbus_meta_fake *mf, + const struct kdbus_creds *creds, + const struct kdbus_pids *pids, + const char *seclabel); + +struct kdbus_meta_conn *kdbus_meta_conn_new(void); +struct kdbus_meta_conn *kdbus_meta_conn_ref(struct kdbus_meta_conn *mc); +struct kdbus_meta_conn *kdbus_meta_conn_unref(struct kdbus_meta_conn *mc); +int kdbus_meta_conn_collect(struct kdbus_meta_conn *mc, + struct kdbus_conn *conn, + u64 msg_seqnum, u64 what); + +int kdbus_meta_emit(struct kdbus_meta_proc *mp, + struct kdbus_meta_fake *mf, + struct kdbus_meta_conn *mc, + struct kdbus_conn *conn, + u64 mask, + struct kdbus_item **out_items, + size_t *out_size); +u64 kdbus_meta_info_mask(const struct kdbus_conn *conn, u64 mask); +u64 kdbus_meta_msg_mask(const struct kdbus_conn *snd, + const struct kdbus_conn *rcv); + +#endif diff --git a/ipc/kdbus/names.c b/ipc/kdbus/names.c new file mode 100644 index 000000000000..bf44ca3f12b6 --- /dev/null +++ b/ipc/kdbus/names.c @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/ctype.h> +#include <linux/fs.h> +#include <linux/hash.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/uio.h> + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "handle.h" +#include "item.h" +#include "names.h" +#include "notify.h" +#include "policy.h" + +#define KDBUS_NAME_SAVED_MASK (KDBUS_NAME_ALLOW_REPLACEMENT | \ + KDBUS_NAME_QUEUE) + +static bool kdbus_name_owner_is_used(struct kdbus_name_owner *owner) +{ + return !list_empty(&owner->name_entry) || + owner == owner->name->activator; +} + +static struct kdbus_name_owner * +kdbus_name_owner_new(struct kdbus_conn *conn, struct kdbus_name_entry *name, + u64 flags) +{ + struct kdbus_name_owner *owner; + + kdbus_conn_assert_active(conn); + + if (conn->name_count >= KDBUS_CONN_MAX_NAMES) + return ERR_PTR(-E2BIG); + + owner = kmalloc(sizeof(*owner), GFP_KERNEL); + if (!owner) + return ERR_PTR(-ENOMEM); + + owner->flags = flags & KDBUS_NAME_SAVED_MASK; + owner->conn = conn; + owner->name = name; + list_add_tail(&owner->conn_entry, &conn->names_list); + INIT_LIST_HEAD(&owner->name_entry); + + ++conn->name_count; + return owner; +} + +static void kdbus_name_owner_free(struct kdbus_name_owner *owner) +{ + if (!owner) + return; + + WARN_ON(kdbus_name_owner_is_used(owner)); + --owner->conn->name_count; + list_del(&owner->conn_entry); + kfree(owner); +} + +static struct kdbus_name_owner * +kdbus_name_owner_find(struct kdbus_name_entry *name, struct kdbus_conn *conn) +{ + struct kdbus_name_owner *owner; + + /* + * Use conn->names_list over name->queue to make sure boundaries of + * this linear search are controlled by the connection itself. + * Furthermore, this will find normal owners as well as activators + * without any additional code. + */ + list_for_each_entry(owner, &conn->names_list, conn_entry) + if (owner->name == name) + return owner; + + return NULL; +} + +static bool kdbus_name_entry_is_used(struct kdbus_name_entry *name) +{ + return !list_empty(&name->queue) || name->activator; +} + +static struct kdbus_name_owner * +kdbus_name_entry_first(struct kdbus_name_entry *name) +{ + return list_first_entry_or_null(&name->queue, struct kdbus_name_owner, + name_entry); +} + +static struct kdbus_name_entry * +kdbus_name_entry_new(struct kdbus_name_registry *r, u32 hash, + const char *name_str) +{ + struct kdbus_name_entry *name; + size_t namelen; + + lockdep_assert_held(&r->rwlock); + + namelen = strlen(name_str); + + name = kmalloc(sizeof(*name) + namelen + 1, GFP_KERNEL); + if (!name) + return ERR_PTR(-ENOMEM); + + name->name_id = ++r->name_seq_last; + name->activator = NULL; + INIT_LIST_HEAD(&name->queue); + hash_add(r->entries_hash, &name->hentry, hash); + memcpy(name->name, name_str, namelen + 1); + + return name; +} + +static void kdbus_name_entry_free(struct kdbus_name_entry *name) +{ + if (!name) + return; + + WARN_ON(kdbus_name_entry_is_used(name)); + hash_del(&name->hentry); + kfree(name); +} + +static struct kdbus_name_entry * +kdbus_name_entry_find(struct kdbus_name_registry *r, u32 hash, + const char *name_str) +{ + struct kdbus_name_entry *name; + + lockdep_assert_held(&r->rwlock); + + hash_for_each_possible(r->entries_hash, name, hentry, hash) + if (!strcmp(name->name, name_str)) + return name; + + return NULL; +} + +/** + * kdbus_name_registry_new() - create a new name registry + * + * Return: a new kdbus_name_registry on success, ERR_PTR on failure. + */ +struct kdbus_name_registry *kdbus_name_registry_new(void) +{ + struct kdbus_name_registry *r; + + r = kmalloc(sizeof(*r), GFP_KERNEL); + if (!r) + return ERR_PTR(-ENOMEM); + + hash_init(r->entries_hash); + init_rwsem(&r->rwlock); + r->name_seq_last = 0; + + return r; +} + +/** + * kdbus_name_registry_free() - free name registry + * @r: name registry to free, or NULL + * + * Free a name registry and cleanup all internal objects. This is a no-op if + * you pass NULL as registry. + */ +void kdbus_name_registry_free(struct kdbus_name_registry *r) +{ + if (!r) + return; + + WARN_ON(!hash_empty(r->entries_hash)); + kfree(r); +} + +/** + * kdbus_name_lookup_unlocked() - lookup name in registry + * @reg: name registry + * @name: name to lookup + * + * This looks up @name in the given name-registry and returns the + * kdbus_name_entry object. The caller must hold the registry-lock and must not + * access the returned object after releasing the lock. + * + * Return: Pointer to name-entry, or NULL if not found. + */ +struct kdbus_name_entry * +kdbus_name_lookup_unlocked(struct kdbus_name_registry *reg, const char *name) +{ + return kdbus_name_entry_find(reg, kdbus_strhash(name), name); +} + +static int kdbus_name_become_activator(struct kdbus_name_owner *owner, + u64 *return_flags) +{ + if (kdbus_name_owner_is_used(owner)) + return -EALREADY; + if (owner->name->activator) + return -EEXIST; + + owner->name->activator = owner; + owner->flags |= KDBUS_NAME_ACTIVATOR; + + if (kdbus_name_entry_first(owner->name)) { + owner->flags |= KDBUS_NAME_IN_QUEUE; + } else { + owner->flags |= KDBUS_NAME_PRIMARY; + kdbus_notify_name_change(owner->conn->ep->bus, + KDBUS_ITEM_NAME_ADD, + 0, owner->conn->id, + 0, owner->flags, + owner->name->name); + } + + if (return_flags) + *return_flags = owner->flags | KDBUS_NAME_ACQUIRED; + + return 0; +} + +static int kdbus_name_update(struct kdbus_name_owner *owner, u64 flags, + u64 *return_flags) +{ + struct kdbus_name_owner *primary, *activator; + struct kdbus_name_entry *name; + struct kdbus_bus *bus; + u64 nflags = 0; + int ret = 0; + + name = owner->name; + bus = owner->conn->ep->bus; + primary = kdbus_name_entry_first(name); + activator = name->activator; + + /* cannot be activator and acquire a name */ + if (owner == activator) + return -EUCLEAN; + + /* update saved flags */ + owner->flags = flags & KDBUS_NAME_SAVED_MASK; + + if (!primary) { + /* + * No primary owner (but maybe an activator). Take over the + * name. + */ + + list_add(&owner->name_entry, &name->queue); + owner->flags |= KDBUS_NAME_PRIMARY; + nflags |= KDBUS_NAME_ACQUIRED; + + /* move messages to new owner on activation */ + if (activator) { + kdbus_conn_move_messages(owner->conn, activator->conn, + name->name_id); + kdbus_notify_name_change(bus, KDBUS_ITEM_NAME_CHANGE, + activator->conn->id, owner->conn->id, + activator->flags, owner->flags, + name->name); + activator->flags &= ~KDBUS_NAME_PRIMARY; + activator->flags |= KDBUS_NAME_IN_QUEUE; + } else { + kdbus_notify_name_change(bus, KDBUS_ITEM_NAME_ADD, + 0, owner->conn->id, + 0, owner->flags, + name->name); + } + + } else if (owner == primary) { + /* + * Already the primary owner of the name, flags were already + * updated. Nothing to do. + */ + + owner->flags |= KDBUS_NAME_PRIMARY; + + } else if ((primary->flags & KDBUS_NAME_ALLOW_REPLACEMENT) && + (flags & KDBUS_NAME_REPLACE_EXISTING)) { + /* + * We're not the primary owner but can replace it. Move us + * ahead of the primary owner and acquire the name (possibly + * skipping queued owners ahead of us). + */ + + list_del_init(&owner->name_entry); + list_add(&owner->name_entry, &name->queue); + owner->flags |= KDBUS_NAME_PRIMARY; + nflags |= KDBUS_NAME_ACQUIRED; + + kdbus_notify_name_change(bus, KDBUS_ITEM_NAME_CHANGE, + primary->conn->id, owner->conn->id, + primary->flags, owner->flags, + name->name); + + /* requeue old primary, or drop if queueing not wanted */ + if (primary->flags & KDBUS_NAME_QUEUE) { + primary->flags &= ~KDBUS_NAME_PRIMARY; + primary->flags |= KDBUS_NAME_IN_QUEUE; + } else { + list_del_init(&primary->name_entry); + kdbus_name_owner_free(primary); + } + + } else if (flags & KDBUS_NAME_QUEUE) { + /* + * Name is already occupied and we cannot take it over, but + * queuing is allowed. Put us silently on the queue, if not + * already there. + */ + + owner->flags |= KDBUS_NAME_IN_QUEUE; + if (!kdbus_name_owner_is_used(owner)) { + list_add_tail(&owner->name_entry, &name->queue); + nflags |= KDBUS_NAME_ACQUIRED; + } + } else if (kdbus_name_owner_is_used(owner)) { + /* + * Already queued on name, but re-queueing was not requested. + * Make sure to unlink it from the name, the caller is + * responsible for releasing it. + */ + + list_del_init(&owner->name_entry); + } else { + /* + * Name is already claimed and queueing is not requested. + * Return error to the caller. + */ + + ret = -EEXIST; + } + + if (return_flags) + *return_flags = owner->flags | nflags; + + return ret; +} + +int kdbus_name_acquire(struct kdbus_name_registry *reg, + struct kdbus_conn *conn, const char *name_str, + u64 flags, u64 *return_flags) +{ + struct kdbus_name_entry *name = NULL; + struct kdbus_name_owner *owner = NULL; + u32 hash; + int ret; + + kdbus_conn_assert_active(conn); + + down_write(®->rwlock); + + /* + * Verify the connection has access to the name. Do this before testing + * for double-acquisitions and other errors to make sure we do not leak + * information about this name through possible custom endpoints. + */ + if (!kdbus_conn_policy_own_name(conn, current_cred(), name_str)) { + ret = -EPERM; + goto exit; + } + + /* + * Lookup the name entry. If it already exists, search for an owner + * entry as we might already own that name. If either does not exist, + * we will allocate a fresh one. + */ + hash = kdbus_strhash(name_str); + name = kdbus_name_entry_find(reg, hash, name_str); + if (name) { + owner = kdbus_name_owner_find(name, conn); + } else { + name = kdbus_name_entry_new(reg, hash, name_str); + if (IS_ERR(name)) { + ret = PTR_ERR(name); + name = NULL; + goto exit; + } + } + + /* create name owner object if not already queued */ + if (!owner) { + owner = kdbus_name_owner_new(conn, name, flags); + if (IS_ERR(owner)) { + ret = PTR_ERR(owner); + owner = NULL; + goto exit; + } + } + + if (flags & KDBUS_NAME_ACTIVATOR) + ret = kdbus_name_become_activator(owner, return_flags); + else + ret = kdbus_name_update(owner, flags, return_flags); + if (ret < 0) + goto exit; + +exit: + if (owner && !kdbus_name_owner_is_used(owner)) + kdbus_name_owner_free(owner); + if (name && !kdbus_name_entry_is_used(name)) + kdbus_name_entry_free(name); + up_write(®->rwlock); + kdbus_notify_flush(conn->ep->bus); + return ret; +} + +static void kdbus_name_release_unlocked(struct kdbus_name_owner *owner) +{ + struct kdbus_name_owner *primary, *next; + struct kdbus_name_entry *name; + + name = owner->name; + primary = kdbus_name_entry_first(name); + + list_del_init(&owner->name_entry); + if (owner == name->activator) + name->activator = NULL; + + if (!primary || owner == primary) { + next = kdbus_name_entry_first(name); + if (!next) + next = name->activator; + + if (next) { + /* hand to next in queue */ + next->flags &= ~KDBUS_NAME_IN_QUEUE; + next->flags |= KDBUS_NAME_PRIMARY; + if (next == name->activator) + kdbus_conn_move_messages(next->conn, + owner->conn, + name->name_id); + + kdbus_notify_name_change(owner->conn->ep->bus, + KDBUS_ITEM_NAME_CHANGE, + owner->conn->id, next->conn->id, + owner->flags, next->flags, + name->name); + } else { + kdbus_notify_name_change(owner->conn->ep->bus, + KDBUS_ITEM_NAME_REMOVE, + owner->conn->id, 0, + owner->flags, 0, + name->name); + } + } + + kdbus_name_owner_free(owner); + if (!kdbus_name_entry_is_used(name)) + kdbus_name_entry_free(name); +} + +static int kdbus_name_release(struct kdbus_name_registry *reg, + struct kdbus_conn *conn, + const char *name_str) +{ + struct kdbus_name_owner *owner; + struct kdbus_name_entry *name; + int ret = 0; + + down_write(®->rwlock); + name = kdbus_name_entry_find(reg, kdbus_strhash(name_str), name_str); + if (name) { + owner = kdbus_name_owner_find(name, conn); + if (owner) + kdbus_name_release_unlocked(owner); + else + ret = -EADDRINUSE; + } else { + ret = -ESRCH; + } + up_write(®->rwlock); + + kdbus_notify_flush(conn->ep->bus); + return ret; +} + +/** + * kdbus_name_release_all() - remove all name entries of a given connection + * @reg: name registry + * @conn: connection + */ +void kdbus_name_release_all(struct kdbus_name_registry *reg, + struct kdbus_conn *conn) +{ + struct kdbus_name_owner *owner; + + down_write(®->rwlock); + + while ((owner = list_first_entry_or_null(&conn->names_list, + struct kdbus_name_owner, + conn_entry))) + kdbus_name_release_unlocked(owner); + + up_write(®->rwlock); + + kdbus_notify_flush(conn->ep->bus); +} + +/** + * kdbus_name_is_valid() - check if a name is valid + * @p: The name to check + * @allow_wildcard: Whether or not to allow a wildcard name + * + * A name is valid if all of the following criterias are met: + * + * - The name has two or more elements separated by a period ('.') character. + * - All elements must contain at least one character. + * - Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_-" + * and must not begin with a digit. + * - The name must not exceed KDBUS_NAME_MAX_LEN. + * - If @allow_wildcard is true, the name may end on '.*' + */ +bool kdbus_name_is_valid(const char *p, bool allow_wildcard) +{ + bool dot, found_dot = false; + const char *q; + + for (dot = true, q = p; *q; q++) { + if (*q == '.') { + if (dot) + return false; + + found_dot = true; + dot = true; + } else { + bool good; + + good = isalpha(*q) || (!dot && isdigit(*q)) || + *q == '_' || *q == '-' || + (allow_wildcard && dot && + *q == '*' && *(q + 1) == '\0'); + + if (!good) + return false; + + dot = false; + } + } + + if (q - p > KDBUS_NAME_MAX_LEN) + return false; + + if (dot) + return false; + + if (!found_dot) + return false; + + return true; +} + +/** + * kdbus_cmd_name_acquire() - handle KDBUS_CMD_NAME_ACQUIRE + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_name_acquire(struct kdbus_conn *conn, void __user *argp) +{ + const char *item_name; + struct kdbus_cmd *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_NAME, .mandatory = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_NAME_REPLACE_EXISTING | + KDBUS_NAME_ALLOW_REPLACEMENT | + KDBUS_NAME_QUEUE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + item_name = argv[1].item->str; + if (!kdbus_name_is_valid(item_name, false)) { + ret = -EINVAL; + goto exit; + } + + ret = kdbus_name_acquire(conn->ep->bus->name_registry, conn, item_name, + cmd->flags, &cmd->return_flags); + +exit: + return kdbus_args_clear(&args, ret); +} + +/** + * kdbus_cmd_name_release() - handle KDBUS_CMD_NAME_RELEASE + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_name_release(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_cmd *cmd; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + { .type = KDBUS_ITEM_NAME, .mandatory = true }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + if (!kdbus_conn_is_ordinary(conn)) + return -EOPNOTSUPP; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + ret = kdbus_name_release(conn->ep->bus->name_registry, conn, + argv[1].item->str); + return kdbus_args_clear(&args, ret); +} + +static int kdbus_list_write(struct kdbus_conn *conn, + struct kdbus_conn *c, + struct kdbus_pool_slice *slice, + size_t *pos, + struct kdbus_name_owner *o, + bool write) +{ + struct kvec kvec[4]; + size_t cnt = 0; + int ret; + + /* info header */ + struct kdbus_info info = { + .size = 0, + .id = c->id, + .flags = c->flags, + }; + + /* fake the header of a kdbus_name item */ + struct { + u64 size; + u64 type; + u64 flags; + } h = {}; + + if (o && !kdbus_conn_policy_see_name_unlocked(conn, current_cred(), + o->name->name)) + return 0; + + kdbus_kvec_set(&kvec[cnt++], &info, sizeof(info), &info.size); + + /* append name */ + if (o) { + size_t slen = strlen(o->name->name) + 1; + + h.size = offsetof(struct kdbus_item, name.name) + slen; + h.type = KDBUS_ITEM_OWNED_NAME; + h.flags = o->flags; + + kdbus_kvec_set(&kvec[cnt++], &h, sizeof(h), &info.size); + kdbus_kvec_set(&kvec[cnt++], o->name->name, slen, &info.size); + cnt += !!kdbus_kvec_pad(&kvec[cnt], &info.size); + } + + if (write) { + ret = kdbus_pool_slice_copy_kvec(slice, *pos, kvec, + cnt, info.size); + if (ret < 0) + return ret; + } + + *pos += info.size; + return 0; +} + +static int kdbus_list_all(struct kdbus_conn *conn, u64 flags, + struct kdbus_pool_slice *slice, + size_t *pos, bool write) +{ + struct kdbus_conn *c; + size_t p = *pos; + int ret, i; + + hash_for_each(conn->ep->bus->conn_hash, i, c, hentry) { + bool added = false; + + /* skip monitors */ + if (kdbus_conn_is_monitor(c)) + continue; + + /* all names the connection owns */ + if (flags & (KDBUS_LIST_NAMES | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED)) { + struct kdbus_name_owner *o; + + list_for_each_entry(o, &c->names_list, conn_entry) { + if (o->flags & KDBUS_NAME_ACTIVATOR) { + if (!(flags & KDBUS_LIST_ACTIVATORS)) + continue; + + ret = kdbus_list_write(conn, c, slice, + &p, o, write); + if (ret < 0) { + mutex_unlock(&c->lock); + return ret; + } + + added = true; + } else if (o->flags & KDBUS_NAME_IN_QUEUE) { + if (!(flags & KDBUS_LIST_QUEUED)) + continue; + + ret = kdbus_list_write(conn, c, slice, + &p, o, write); + if (ret < 0) { + mutex_unlock(&c->lock); + return ret; + } + + added = true; + } else if (flags & KDBUS_LIST_NAMES) { + ret = kdbus_list_write(conn, c, slice, + &p, o, write); + if (ret < 0) { + mutex_unlock(&c->lock); + return ret; + } + + added = true; + } + } + } + + /* nothing added so far, just add the unique ID */ + if (!added && (flags & KDBUS_LIST_UNIQUE)) { + ret = kdbus_list_write(conn, c, slice, &p, NULL, write); + if (ret < 0) + return ret; + } + } + + *pos = p; + return 0; +} + +/** + * kdbus_cmd_list() - handle KDBUS_CMD_LIST + * @conn: connection to operate on + * @argp: command payload + * + * Return: >=0 on success, negative error code on failure. + */ +int kdbus_cmd_list(struct kdbus_conn *conn, void __user *argp) +{ + struct kdbus_name_registry *reg = conn->ep->bus->name_registry; + struct kdbus_pool_slice *slice = NULL; + struct kdbus_cmd_list *cmd; + size_t pos, size; + int ret; + + struct kdbus_arg argv[] = { + { .type = KDBUS_ITEM_NEGOTIATE }, + }; + struct kdbus_args args = { + .allowed_flags = KDBUS_FLAG_NEGOTIATE | + KDBUS_LIST_UNIQUE | + KDBUS_LIST_NAMES | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED, + .argv = argv, + .argc = ARRAY_SIZE(argv), + }; + + ret = kdbus_args_parse(&args, argp, &cmd); + if (ret != 0) + return ret; + + /* lock order: domain -> bus -> ep -> names -> conn */ + down_read(®->rwlock); + down_read(&conn->ep->bus->conn_rwlock); + down_read(&conn->ep->policy_db.entries_rwlock); + + /* size of records */ + size = 0; + ret = kdbus_list_all(conn, cmd->flags, NULL, &size, false); + if (ret < 0) + goto exit_unlock; + + if (size == 0) { + kdbus_pool_publish_empty(conn->pool, &cmd->offset, + &cmd->list_size); + } else { + slice = kdbus_pool_slice_alloc(conn->pool, size, false); + if (IS_ERR(slice)) { + ret = PTR_ERR(slice); + slice = NULL; + goto exit_unlock; + } + + /* copy the records */ + pos = 0; + ret = kdbus_list_all(conn, cmd->flags, slice, &pos, true); + if (ret < 0) + goto exit_unlock; + + WARN_ON(pos != size); + kdbus_pool_slice_publish(slice, &cmd->offset, &cmd->list_size); + } + + if (kdbus_member_set_user(&cmd->offset, argp, typeof(*cmd), offset) || + kdbus_member_set_user(&cmd->list_size, argp, + typeof(*cmd), list_size)) + ret = -EFAULT; + +exit_unlock: + up_read(&conn->ep->policy_db.entries_rwlock); + up_read(&conn->ep->bus->conn_rwlock); + up_read(®->rwlock); + kdbus_pool_slice_release(slice); + return kdbus_args_clear(&args, ret); +} diff --git a/ipc/kdbus/names.h b/ipc/kdbus/names.h new file mode 100644 index 000000000000..edac59ddd8ee --- /dev/null +++ b/ipc/kdbus/names.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_NAMES_H +#define __KDBUS_NAMES_H + +#include <linux/hashtable.h> +#include <linux/rwsem.h> + +struct kdbus_name_entry; +struct kdbus_name_owner; +struct kdbus_name_registry; + +/** + * struct kdbus_name_registry - names registered for a bus + * @entries_hash: Map of entries + * @lock: Registry data lock + * @name_seq_last: Last used sequence number to assign to a name entry + */ +struct kdbus_name_registry { + DECLARE_HASHTABLE(entries_hash, 8); + struct rw_semaphore rwlock; + u64 name_seq_last; +}; + +/** + * struct kdbus_name_entry - well-know name entry + * @name_id: sequence number of name entry to be able to uniquely + * identify a name over its registration lifetime + * @activator: activator of this name, or NULL + * @queue: list of queued owners + * @hentry: entry in registry map + * @name: well-known name + */ +struct kdbus_name_entry { + u64 name_id; + struct kdbus_name_owner *activator; + struct list_head queue; + struct hlist_node hentry; + char name[]; +}; + +/** + * struct kdbus_name_owner - owner of a well-known name + * @flags: KDBUS_NAME_* flags of this owner + * @conn: connection owning the name + * @name: name that is owned + * @conn_entry: link into @conn + * @name_entry: link into @name + */ +struct kdbus_name_owner { + u64 flags; + struct kdbus_conn *conn; + struct kdbus_name_entry *name; + struct list_head conn_entry; + struct list_head name_entry; +}; + +bool kdbus_name_is_valid(const char *p, bool allow_wildcard); + +struct kdbus_name_registry *kdbus_name_registry_new(void); +void kdbus_name_registry_free(struct kdbus_name_registry *reg); + +struct kdbus_name_entry * +kdbus_name_lookup_unlocked(struct kdbus_name_registry *reg, const char *name); + +int kdbus_name_acquire(struct kdbus_name_registry *reg, + struct kdbus_conn *conn, const char *name, + u64 flags, u64 *return_flags); +void kdbus_name_release_all(struct kdbus_name_registry *reg, + struct kdbus_conn *conn); + +int kdbus_cmd_name_acquire(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_name_release(struct kdbus_conn *conn, void __user *argp); +int kdbus_cmd_list(struct kdbus_conn *conn, void __user *argp); + +/** + * kdbus_name_get_owner() - get current owner of a name + * @name: name to get current owner of + * + * This returns a pointer to the current owner of a name (or its activator if + * there is no owner). The caller must make sure @name is valid and does not + * vanish. + * + * Return: Pointer to current owner or NULL if there is none. + */ +static inline struct kdbus_name_owner * +kdbus_name_get_owner(struct kdbus_name_entry *name) +{ + return list_first_entry_or_null(&name->queue, struct kdbus_name_owner, + name_entry) ? : name->activator; +} + +#endif diff --git a/ipc/kdbus/node.c b/ipc/kdbus/node.c new file mode 100644 index 000000000000..89f58bc85433 --- /dev/null +++ b/ipc/kdbus/node.c @@ -0,0 +1,897 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/atomic.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/kdev_t.h> +#include <linux/rbtree.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include "bus.h" +#include "domain.h" +#include "endpoint.h" +#include "fs.h" +#include "handle.h" +#include "node.h" +#include "util.h" + +/** + * DOC: kdbus nodes + * + * Nodes unify lifetime management across exposed kdbus objects and provide a + * hierarchy. Each kdbus object, that might be exposed to user-space, has a + * kdbus_node object embedded and is linked into the hierarchy. Each node can + * have any number (0-n) of child nodes linked. Each child retains a reference + * to its parent node. For root-nodes, the parent is NULL. + * + * Each node object goes through a bunch of states during it's lifetime: + * * NEW + * * LINKED (can be skipped by NEW->FREED transition) + * * ACTIVE (can be skipped by LINKED->INACTIVE transition) + * * INACTIVE + * * DRAINED + * * FREED + * + * Each node is allocated by the caller and initialized via kdbus_node_init(). + * This never fails and sets the object into state NEW. From now on, ref-counts + * on the node manage its lifetime. During init, the ref-count is set to 1. Once + * it drops to 0, the node goes to state FREED and the node->free_cb() callback + * is called to deallocate any memory. + * + * After initializing a node, you usually link it into the hierarchy. You need + * to provide a parent node and a name. The node will be linked as child to the + * parent and a globally unique ID is assigned to the child. The name of the + * child must be unique for all children of this parent. Otherwise, linking the + * child will fail with -EEXIST. + * Note that the child is not marked active, yet. Admittedly, it prevents any + * other node from being linked with the same name (thus, it reserves that + * name), but any child-lookup (via name or unique ID) will never return this + * child unless it has been marked active. + * + * Once successfully linked, you can use kdbus_node_activate() to activate a + * child. This will mark the child active. This state can be skipped by directly + * deactivating the child via kdbus_node_deactivate() (see below). + * By activating a child, you enable any lookups on this child to succeed from + * now on. Furthermore, any code that got its hands on a reference to the node, + * can from now on "acquire" the node. + * + * Active References (or: 'acquiring' and 'releasing' a node) + * Additionally to normal object references, nodes support something we call + * "active references". An active reference can be acquired via + * kdbus_node_acquire() and released via kdbus_node_release(). A caller + * _must_ own a normal object reference whenever calling those functions. + * Unlike object references, acquiring an active reference can fail (by + * returning 'false' from kdbus_node_acquire()). An active reference can + * only be acquired if the node is marked active. If it is not marked + * active, yet, or if it was already deactivated, no more active references + * can be acquired, ever! + * Active references are used to track tasks working on a node. Whenever a + * task enters kernel-space to perform an action on a node, it acquires an + * active reference, performs the action and releases the reference again. + * While holding an active reference, the node is guaranteed to stay active. + * If the node is deactivated in parallel, the node is marked as + * deactivated, then we wait for all active references to be dropped, before + * we finally proceed with any cleanups. That is, if you hold an active + * reference to a node, any resources that are bound to the "active" state + * are guaranteed to stay accessible until you release your reference. + * + * Active-references are very similar to rw-locks, where acquiring a node is + * equal to try-read-lock and releasing to read-unlock. Deactivating a node + * means write-lock and never releasing it again. + * Unlike rw-locks, the 'active reference' concept is more versatile and + * avoids unusual rw-lock usage (never releasing a write-lock..). + * + * It is safe to acquire multiple active-references recursively. But you + * need to check the return value of kdbus_node_acquire() on _each_ call. It + * may stop granting references at _any_ time. + * + * You're free to perform any operations you want while holding an active + * reference, except sleeping for an indefinite period. Sleeping for a fixed + * amount of time is fine, but you usually should not wait on wait-queues + * without a timeout. + * For example, if you wait for I/O to happen, you should gather all data + * and schedule the I/O operation, then release your active reference and + * wait for it to complete. Then try to acquire a new reference. If it + * fails, perform any cleanup (the node is now dead). Otherwise, you can + * finish your operation. + * + * All nodes can be deactivated via kdbus_node_deactivate() at any time. You can + * call this multiple times, even in parallel or on nodes that were never + * linked, and it will just work. The only restriction is, you must not hold an + * active reference when calling kdbus_node_deactivate(). + * By deactivating a node, it is immediately marked inactive. Then, we wait for + * all active references to be released (called 'draining' the node). This + * shouldn't take very long as we don't perform long-lasting operations while + * holding an active reference. Note that once the node is marked inactive, no + * new active references can be acquired. + * Once all active references are dropped, the node is considered 'drained'. Now + * kdbus_node_deactivate() is called on each child of the node before we + * continue deactivating our node. That is, once all children are entirely + * deactivated, we call ->release_cb() of our node. ->release_cb() can release + * any resources on that node which are bound to the "active" state of a node. + * When done, we unlink the node from its parent rb-tree, mark it as + * 'released' and return. + * If kdbus_node_deactivate() is called multiple times (even in parallel), all + * but one caller will just wait until the node is fully deactivated. That is, + * one random caller of kdbus_node_deactivate() is selected to call + * ->release_cb() and cleanup the node. Only once all this is done, all other + * callers will return from kdbus_node_deactivate(). That is, it doesn't matter + * whether you're the selected caller or not, it will only return after + * everything is fully done. + * + * When a node is activated, we acquire a normal object reference to the node. + * This reference is dropped after deactivation is fully done (and only iff the + * node really was activated). This allows callers to link+activate a child node + * and then drop all refs. The node will be deactivated together with the + * parent, and then be freed when this reference is dropped. + * + * Currently, nodes provide a bunch of resources that external code can use + * directly. This includes: + * + * * node->waitq: Each node has its own wait-queue that is used to manage + * the 'active' state. When a node is deactivated, we wait on + * this queue until all active refs are dropped. Analogously, + * when you release an active reference on a deactivated + * node, and the active ref-count drops to 0, we wake up a + * single thread on this queue. Furthermore, once the + * ->release_cb() callback finished, we wake up all waiters. + * The node-owner is free to re-use this wait-queue for other + * purposes. As node-management uses this queue only during + * deactivation, it is usually totally fine to re-use the + * queue for other, preferably low-overhead, use-cases. + * + * * node->type: This field defines the type of the owner of this node. It + * must be set during node initialization and must remain + * constant. The node management never looks at this value, + * but external users might use to gain access to the owner + * object of a node. + * It is totally up to the owner of the node to define what + * their type means. Usually it means you can access the + * parent structure via container_of(), as long as you hold an + * active reference to the node. + * + * * node->free_cb: callback after all references are dropped + * node->release_cb: callback during node deactivation + * These fields must be set by the node owner during + * node initialization. They must remain constant. If + * NULL, they're skipped. + * + * * node->mode: filesystem access modes + * node->uid: filesystem owner uid + * node->gid: filesystem owner gid + * These fields must be set by the node owner during node + * initialization. They must remain constant and may be + * accessed by other callers to properly initialize + * filesystem nodes. + * + * * node->id: This is an unsigned 32bit integer allocated by an IDA. It is + * always kept as small as possible during allocation and is + * globally unique across all nodes allocated by this module. 0 + * is reserved as "not assigned" and is the default. + * The ID is assigned during kdbus_node_link() and is kept until + * the object is freed. Thus, the ID surpasses the active + * lifetime of a node. As long as you hold an object reference + * to a node (and the node was linked once), the ID is valid and + * unique. + * + * * node->name: name of this node + * node->hash: 31bit hash-value of @name (range [2..INT_MAX-1]) + * These values follow the same lifetime rules as node->id. + * They're initialized when the node is linked and then remain + * constant until the last object reference is dropped. + * Unlike the id, the name is only unique across all siblings + * and only until the node is deactivated. Currently, the name + * is even unique if linked but not activated, yet. This might + * change in the future, though. Code should not rely on this. + * + * * node->lock: lock to protect node->children, node->rb, node->parent + * * node->parent: Reference to parent node. This is set during LINK time + * and is dropped during destruction. You must not access + * it unless you hold an active reference to the node or if + * you know the node is dead. + * * node->children: rb-tree of all linked children of this node. You must + * not access this directly, but use one of the iterator + * or lookup helpers. + */ + +/* + * Bias values track states of "active references". They're all negative. If a + * node is active, its active-ref-counter is >=0 and tracks all active + * references. Once a node is deactivaed, we subtract NODE_BIAS. This means, the + * counter is now negative but still counts the active references. Once it drops + * to exactly NODE_BIAS, we know all active references were dropped. Exactly one + * thread will change it to NODE_RELEASE now, perform cleanup and then put it + * into NODE_DRAINED. Once drained, all other threads that tried deactivating + * the node will now be woken up (thus, they wait until the node is fully done). + * The initial state during node-setup is NODE_NEW. If a node is directly + * deactivated without having ever been active, it is put into + * NODE_RELEASE_DIRECT instead of NODE_BIAS. This tracks this one-bit state + * across node-deactivation. The task putting it into NODE_RELEASE now knows + * whether the node was active before or not. + * + * Some archs implement atomic_sub(v) with atomic_add(-v), so reserve INT_MIN + * to avoid overflows if multiplied by -1. + */ +#define KDBUS_NODE_BIAS (INT_MIN + 5) +#define KDBUS_NODE_RELEASE_DIRECT (KDBUS_NODE_BIAS - 1) +#define KDBUS_NODE_RELEASE (KDBUS_NODE_BIAS - 2) +#define KDBUS_NODE_DRAINED (KDBUS_NODE_BIAS - 3) +#define KDBUS_NODE_NEW (KDBUS_NODE_BIAS - 4) + +/* global unique ID mapping for kdbus nodes */ +DEFINE_IDA(kdbus_node_ida); + +/** + * kdbus_node_name_hash() - hash a name + * @name: The string to hash + * + * This computes the hash of @name. It is guaranteed to be in the range + * [2..INT_MAX-1]. The values 1, 2 and INT_MAX are unused as they are reserved + * for the filesystem code. + * + * Return: hash value of the passed string + */ +static unsigned int kdbus_node_name_hash(const char *name) +{ + unsigned int hash; + + /* reserve hash numbers 0, 1 and >=INT_MAX for magic directories */ + hash = kdbus_strhash(name) & INT_MAX; + if (hash < 2) + hash += 2; + if (hash >= INT_MAX) + hash = INT_MAX - 1; + + return hash; +} + +/** + * kdbus_node_name_compare() - compare a name with a node's name + * @hash: hash of the string to compare the node with + * @name: name to compare the node with + * @node: node to compare the name with + * + * Return: 0 if @name and @hash exactly match the information in @node, or + * an integer less than or greater than zero if @name is found, respectively, + * to be less than or be greater than the string stored in @node. + */ +static int kdbus_node_name_compare(unsigned int hash, const char *name, + const struct kdbus_node *node) +{ + if (hash != node->hash) + return hash - node->hash; + + return strcmp(name, node->name); +} + +/** + * kdbus_node_init() - initialize a kdbus_node + * @node: Pointer to the node to initialize + * @type: The type the node will have (KDBUS_NODE_*) + * + * The caller is responsible of allocating @node and initializating it to zero. + * Once this call returns, you must use the node_ref() and node_unref() + * functions to manage this node. + */ +void kdbus_node_init(struct kdbus_node *node, unsigned int type) +{ + atomic_set(&node->refcnt, 1); + mutex_init(&node->lock); + node->id = 0; + node->type = type; + RB_CLEAR_NODE(&node->rb); + node->children = RB_ROOT; + init_waitqueue_head(&node->waitq); + atomic_set(&node->active, KDBUS_NODE_NEW); +} + +/** + * kdbus_node_link() - link a node into the nodes system + * @node: Pointer to the node to initialize + * @parent: Pointer to a parent node, may be %NULL + * @name: The name of the node (or NULL if root node) + * + * This links a node into the hierarchy. This must not be called multiple times. + * If @parent is NULL, the node becomes a new root node. + * + * This call will fail if @name is not unique across all its siblings or if no + * ID could be allocated. You must not activate a node if linking failed! It is + * safe to deactivate it, though. + * + * Once you linked a node, you must call kdbus_node_deactivate() before you drop + * the last reference (even if you never activate the node). + * + * Return: 0 on success. negative error otherwise. + */ +int kdbus_node_link(struct kdbus_node *node, struct kdbus_node *parent, + const char *name) +{ + int ret; + + if (WARN_ON(node->type != KDBUS_NODE_DOMAIN && !parent)) + return -EINVAL; + + if (WARN_ON(parent && !name)) + return -EINVAL; + + if (name) { + node->name = kstrdup(name, GFP_KERNEL); + if (!node->name) + return -ENOMEM; + + node->hash = kdbus_node_name_hash(name); + } + + ret = ida_simple_get(&kdbus_node_ida, 1, 0, GFP_KERNEL); + if (ret < 0) + return ret; + + node->id = ret; + ret = 0; + + if (parent) { + struct rb_node **n, *prev; + + if (!kdbus_node_acquire(parent)) + return -ESHUTDOWN; + + mutex_lock(&parent->lock); + + n = &parent->children.rb_node; + prev = NULL; + + while (*n) { + struct kdbus_node *pos; + int result; + + pos = kdbus_node_from_rb(*n); + prev = *n; + result = kdbus_node_name_compare(node->hash, + node->name, + pos); + if (result == 0) { + ret = -EEXIST; + goto exit_unlock; + } + + if (result < 0) + n = &pos->rb.rb_left; + else + n = &pos->rb.rb_right; + } + + /* add new node and rebalance the tree */ + rb_link_node(&node->rb, prev, n); + rb_insert_color(&node->rb, &parent->children); + node->parent = kdbus_node_ref(parent); + +exit_unlock: + mutex_unlock(&parent->lock); + kdbus_node_release(parent); + } + + return ret; +} + +/** + * kdbus_node_ref() - Acquire object reference + * @node: node to acquire reference to (or NULL) + * + * This acquires a new reference to @node. You must already own a reference when + * calling this! + * If @node is NULL, this is a no-op. + * + * Return: @node is returned + */ +struct kdbus_node *kdbus_node_ref(struct kdbus_node *node) +{ + if (node) + atomic_inc(&node->refcnt); + return node; +} + +/** + * kdbus_node_unref() - Drop object reference + * @node: node to drop reference to (or NULL) + * + * This drops an object reference to @node. You must not access the node if you + * no longer own a reference. + * If the ref-count drops to 0, the object will be destroyed (->free_cb will be + * called). + * + * If you linked or activated the node, you must deactivate the node before you + * drop your last reference! If you didn't link or activate the node, you can + * drop any reference you want. + * + * Note that this calls into ->free_cb() and thus _might_ sleep. The ->free_cb() + * callbacks must not acquire any outer locks, though. So you can safely drop + * references while holding locks. + * + * If @node is NULL, this is a no-op. + * + * Return: This always returns NULL + */ +struct kdbus_node *kdbus_node_unref(struct kdbus_node *node) +{ + if (node && atomic_dec_and_test(&node->refcnt)) { + struct kdbus_node safe = *node; + + WARN_ON(atomic_read(&node->active) != KDBUS_NODE_DRAINED); + WARN_ON(!RB_EMPTY_NODE(&node->rb)); + + if (node->free_cb) + node->free_cb(node); + if (safe.id > 0) + ida_simple_remove(&kdbus_node_ida, safe.id); + + kfree(safe.name); + + /* + * kdbusfs relies on the parent to be available even after the + * node was deactivated and unlinked. Therefore, we pin it + * until a node is destroyed. + */ + kdbus_node_unref(safe.parent); + } + + return NULL; +} + +/** + * kdbus_node_is_active() - test whether a node is active + * @node: node to test + * + * This checks whether @node is active. That means, @node was linked and + * activated by the node owner and hasn't been deactivated, yet. If, and only + * if, a node is active, kdbus_node_acquire() will be able to acquire active + * references. + * + * Note that this function does not give any lifetime guarantees. After this + * call returns, the node might be deactivated immediately. Normally, what you + * want is to acquire a real active reference via kdbus_node_acquire(). + * + * Return: true if @node is active, false otherwise + */ +bool kdbus_node_is_active(struct kdbus_node *node) +{ + return atomic_read(&node->active) >= 0; +} + +/** + * kdbus_node_is_deactivated() - test whether a node was already deactivated + * @node: node to test + * + * This checks whether kdbus_node_deactivate() was called on @node. Note that + * this might be true even if you never deactivated the node directly, but only + * one of its ancestors. + * + * Note that even if this returns 'false', the node might get deactivated + * immediately after the call returns. + * + * Return: true if @node was already deactivated, false if not + */ +bool kdbus_node_is_deactivated(struct kdbus_node *node) +{ + int v; + + v = atomic_read(&node->active); + return v != KDBUS_NODE_NEW && v < 0; +} + +/** + * kdbus_node_activate() - activate a node + * @node: node to activate + * + * This marks @node as active if, and only if, the node wasn't activated nor + * deactivated, yet, and the parent is still active. Any but the first call to + * kdbus_node_activate() is a no-op. + * If you called kdbus_node_deactivate() before, then even the first call to + * kdbus_node_activate() will be a no-op. + * + * This call doesn't give any lifetime guarantees. The node might get + * deactivated immediately after this call returns. Or the parent might already + * be deactivated, which will make this call a no-op. + * + * If this call successfully activated a node, it will take an object reference + * to it. This reference is dropped after the node is deactivated. Therefore, + * the object owner can safely drop their reference to @node iff they know that + * its parent node will get deactivated at some point. Once the parent node is + * deactivated, it will deactivate all its child and thus drop this reference + * again. + * + * Return: True if this call successfully activated the node, otherwise false. + * Note that this might return false, even if the node is still active + * (eg., if you called this a second time). + */ +bool kdbus_node_activate(struct kdbus_node *node) +{ + bool res = false; + + mutex_lock(&node->lock); + if (atomic_read(&node->active) == KDBUS_NODE_NEW) { + atomic_sub(KDBUS_NODE_NEW, &node->active); + /* activated nodes have ref +1 */ + kdbus_node_ref(node); + res = true; + } + mutex_unlock(&node->lock); + + return res; +} + +/** + * kdbus_node_deactivate() - deactivate a node + * @node: The node to deactivate. + * + * This function recursively deactivates this node and all its children. It + * returns only once all children and the node itself were recursively disabled + * (even if you call this function multiple times in parallel). + * + * It is safe to call this function on _any_ node that was initialized _any_ + * number of times. + * + * This call may sleep, as it waits for all active references to be dropped. + */ +void kdbus_node_deactivate(struct kdbus_node *node) +{ + struct kdbus_node *pos, *child; + struct rb_node *rb; + int v_pre, v_post; + + pos = node; + + /* + * To avoid recursion, we perform back-tracking while deactivating + * nodes. For each node we enter, we first mark the active-counter as + * deactivated by adding BIAS. If the node as children, we set the first + * child as current position and start over. If the node has no + * children, we drain the node by waiting for all active refs to be + * dropped and then releasing the node. + * + * After the node is released, we set its parent as current position + * and start over. If the current position was the initial node, we're + * done. + * + * Note that this function can be called in parallel by multiple + * callers. We make sure that each node is only released once, and any + * racing caller will wait until the other thread fully released that + * node. + */ + + for (;;) { + /* + * Add BIAS to node->active to mark it as inactive. If it was + * never active before, immediately mark it as RELEASE_INACTIVE + * so we remember this state. + * We cannot remember v_pre as we might iterate into the + * children, overwriting v_pre, before we can release our node. + */ + mutex_lock(&pos->lock); + v_pre = atomic_read(&pos->active); + if (v_pre >= 0) + atomic_add_return(KDBUS_NODE_BIAS, &pos->active); + else if (v_pre == KDBUS_NODE_NEW) + atomic_set(&pos->active, KDBUS_NODE_RELEASE_DIRECT); + mutex_unlock(&pos->lock); + + /* wait until all active references were dropped */ + wait_event(pos->waitq, + atomic_read(&pos->active) <= KDBUS_NODE_BIAS); + + mutex_lock(&pos->lock); + /* recurse into first child if any */ + rb = rb_first(&pos->children); + if (rb) { + child = kdbus_node_ref(kdbus_node_from_rb(rb)); + mutex_unlock(&pos->lock); + pos = child; + continue; + } + + /* mark object as RELEASE */ + v_post = atomic_read(&pos->active); + if (v_post == KDBUS_NODE_BIAS || + v_post == KDBUS_NODE_RELEASE_DIRECT) + atomic_set(&pos->active, KDBUS_NODE_RELEASE); + mutex_unlock(&pos->lock); + + /* + * If this is the thread that marked the object as RELEASE, we + * perform the actual release. Otherwise, we wait until the + * release is done and the node is marked as DRAINED. + */ + if (v_post == KDBUS_NODE_BIAS || + v_post == KDBUS_NODE_RELEASE_DIRECT) { + if (pos->release_cb) + pos->release_cb(pos, v_post == KDBUS_NODE_BIAS); + + if (pos->parent) { + mutex_lock(&pos->parent->lock); + if (!RB_EMPTY_NODE(&pos->rb)) { + rb_erase(&pos->rb, + &pos->parent->children); + RB_CLEAR_NODE(&pos->rb); + } + mutex_unlock(&pos->parent->lock); + } + + /* mark as DRAINED */ + atomic_set(&pos->active, KDBUS_NODE_DRAINED); + wake_up_all(&pos->waitq); + + /* drop VFS cache */ + kdbus_fs_flush(pos); + + /* + * If the node was activated and someone subtracted BIAS + * from it to deactivate it, we, and only us, are + * responsible to release the extra ref-count that was + * taken once in kdbus_node_activate(). + * If the node was never activated, no-one ever + * subtracted BIAS, but instead skipped that state and + * immediately went to NODE_RELEASE_DIRECT. In that case + * we must not drop the reference. + */ + if (v_post == KDBUS_NODE_BIAS) + kdbus_node_unref(pos); + } else { + /* wait until object is DRAINED */ + wait_event(pos->waitq, + atomic_read(&pos->active) == KDBUS_NODE_DRAINED); + } + + /* + * We're done with the current node. Continue on its parent + * again, which will try deactivating its next child, or itself + * if no child is left. + * If we've reached our initial node again, we are done and + * can safely return. + */ + if (pos == node) + break; + + child = pos; + pos = pos->parent; + kdbus_node_unref(child); + } +} + +/** + * kdbus_node_acquire() - Acquire an active ref on a node + * @node: The node + * + * This acquires an active-reference to @node. This will only succeed if the + * node is active. You must release this active reference via + * kdbus_node_release() again. + * + * See the introduction to "active references" for more details. + * + * Return: %true if @node was non-NULL and active + */ +bool kdbus_node_acquire(struct kdbus_node *node) +{ + return node && atomic_inc_unless_negative(&node->active); +} + +/** + * kdbus_node_release() - Release an active ref on a node + * @node: The node + * + * This releases an active reference that was previously acquired via + * kdbus_node_acquire(). See kdbus_node_acquire() for details. + */ +void kdbus_node_release(struct kdbus_node *node) +{ + if (node && atomic_dec_return(&node->active) == KDBUS_NODE_BIAS) + wake_up(&node->waitq); +} + +/** + * kdbus_node_find_child() - Find child by name + * @node: parent node to search through + * @name: name of child node + * + * This searches through all children of @node for a child-node with name @name. + * If not found, or if the child is deactivated, NULL is returned. Otherwise, + * the child is acquired and a new reference is returned. + * + * If you're done with the child, you need to release it and drop your + * reference. + * + * This function does not acquire the parent node. However, if the parent was + * already deactivated, then kdbus_node_deactivate() will, at some point, also + * deactivate the child. Therefore, we can rely on the explicit ordering during + * deactivation. + * + * Return: Reference to acquired child node, or NULL if not found / not active. + */ +struct kdbus_node *kdbus_node_find_child(struct kdbus_node *node, + const char *name) +{ + struct kdbus_node *child; + struct rb_node *rb; + unsigned int hash; + int ret; + + hash = kdbus_node_name_hash(name); + + mutex_lock(&node->lock); + rb = node->children.rb_node; + while (rb) { + child = kdbus_node_from_rb(rb); + ret = kdbus_node_name_compare(hash, name, child); + if (ret < 0) + rb = rb->rb_left; + else if (ret > 0) + rb = rb->rb_right; + else + break; + } + if (rb && kdbus_node_acquire(child)) + kdbus_node_ref(child); + else + child = NULL; + mutex_unlock(&node->lock); + + return child; +} + +static struct kdbus_node *node_find_closest_unlocked(struct kdbus_node *node, + unsigned int hash, + const char *name) +{ + struct kdbus_node *n, *pos = NULL; + struct rb_node *rb; + int res; + + /* + * Find the closest child with ``node->hash >= hash'', or, if @name is + * valid, ``node->name >= name'' (where '>=' is the lex. order). + */ + + rb = node->children.rb_node; + while (rb) { + n = kdbus_node_from_rb(rb); + + if (name) + res = kdbus_node_name_compare(hash, name, n); + else + res = hash - n->hash; + + if (res <= 0) { + rb = rb->rb_left; + pos = n; + } else { /* ``hash > n->hash'', ``name > n->name'' */ + rb = rb->rb_right; + } + } + + return pos; +} + +/** + * kdbus_node_find_closest() - Find closest child-match + * @node: parent node to search through + * @hash: hash value to find closest match for + * + * Find the closest child of @node with a hash greater than or equal to @hash. + * The closest match is the left-most child of @node with this property. Which + * means, it is the first child with that hash returned by + * kdbus_node_next_child(), if you'd iterate the whole parent node. + * + * Return: Reference to acquired child, or NULL if none found. + */ +struct kdbus_node *kdbus_node_find_closest(struct kdbus_node *node, + unsigned int hash) +{ + struct kdbus_node *child; + struct rb_node *rb; + + mutex_lock(&node->lock); + + child = node_find_closest_unlocked(node, hash, NULL); + while (child && !kdbus_node_acquire(child)) { + rb = rb_next(&child->rb); + if (rb) + child = kdbus_node_from_rb(rb); + else + child = NULL; + } + kdbus_node_ref(child); + + mutex_unlock(&node->lock); + + return child; +} + +/** + * kdbus_node_next_child() - Acquire next child + * @node: parent node + * @prev: previous child-node position or NULL + * + * This function returns a reference to the next active child of @node, after + * the passed position @prev. If @prev is NULL, a reference to the first active + * child is returned. If no more active children are found, NULL is returned. + * + * This function acquires the next child it returns. If you're done with the + * returned pointer, you need to release _and_ unref it. + * + * The passed in pointer @prev is not modified by this function, and it does + * *not* have to be active. If @prev was acquired via different means, or if it + * was unlinked from its parent before you pass it in, then this iterator will + * still return the next active child (it will have to search through the + * rb-tree based on the node-name, though). + * However, @prev must not be linked to a different parent than @node! + * + * Return: Reference to next acquired child, or NULL if at the end. + */ +struct kdbus_node *kdbus_node_next_child(struct kdbus_node *node, + struct kdbus_node *prev) +{ + struct kdbus_node *pos = NULL; + struct rb_node *rb; + + mutex_lock(&node->lock); + + if (!prev) { + /* + * New iteration; find first node in rb-tree and try to acquire + * it. If we got it, directly return it as first element. + * Otherwise, the loop below will find the next active node. + */ + rb = rb_first(&node->children); + if (!rb) + goto exit; + pos = kdbus_node_from_rb(rb); + if (kdbus_node_acquire(pos)) + goto exit; + } else if (RB_EMPTY_NODE(&prev->rb)) { + /* + * The current iterator is no longer linked to the rb-tree. Use + * its hash value and name to find the next _higher_ node and + * acquire it. If we got it, return it as next element. + * Otherwise, the loop below will find the next active node. + */ + pos = node_find_closest_unlocked(node, prev->hash, prev->name); + if (!pos) + goto exit; + if (kdbus_node_acquire(pos)) + goto exit; + } else { + /* + * The current iterator is still linked to the parent. Set it + * as current position and use the loop below to find the next + * active element. + */ + pos = prev; + } + + /* @pos was already returned or is inactive; find next active node */ + do { + rb = rb_next(&pos->rb); + if (rb) + pos = kdbus_node_from_rb(rb); + else + pos = NULL; + } while (pos && !kdbus_node_acquire(pos)); + +exit: + /* @pos is NULL or acquired. Take ref if non-NULL and return it */ + kdbus_node_ref(pos); + mutex_unlock(&node->lock); + return pos; +} diff --git a/ipc/kdbus/node.h b/ipc/kdbus/node.h new file mode 100644 index 000000000000..970e02b08e9f --- /dev/null +++ b/ipc/kdbus/node.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_NODE_H +#define __KDBUS_NODE_H + +#include <linux/atomic.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/wait.h> + +struct kdbus_node; + +enum kdbus_node_type { + KDBUS_NODE_DOMAIN, + KDBUS_NODE_CONTROL, + KDBUS_NODE_BUS, + KDBUS_NODE_ENDPOINT, +}; + +typedef void (*kdbus_node_free_t) (struct kdbus_node *node); +typedef void (*kdbus_node_release_t) (struct kdbus_node *node, bool was_active); + +struct kdbus_node { + atomic_t refcnt; + atomic_t active; + wait_queue_head_t waitq; + + /* static members */ + unsigned int type; + kdbus_node_free_t free_cb; + kdbus_node_release_t release_cb; + umode_t mode; + kuid_t uid; + kgid_t gid; + + /* valid once linked */ + char *name; + unsigned int hash; + unsigned int id; + struct kdbus_node *parent; /* may be NULL */ + + /* valid iff active */ + struct mutex lock; + struct rb_node rb; + struct rb_root children; +}; + +#define kdbus_node_from_rb(_node) rb_entry((_node), struct kdbus_node, rb) + +extern struct ida kdbus_node_ida; + +void kdbus_node_init(struct kdbus_node *node, unsigned int type); + +int kdbus_node_link(struct kdbus_node *node, struct kdbus_node *parent, + const char *name); + +struct kdbus_node *kdbus_node_ref(struct kdbus_node *node); +struct kdbus_node *kdbus_node_unref(struct kdbus_node *node); + +bool kdbus_node_is_active(struct kdbus_node *node); +bool kdbus_node_is_deactivated(struct kdbus_node *node); +bool kdbus_node_activate(struct kdbus_node *node); +void kdbus_node_deactivate(struct kdbus_node *node); + +bool kdbus_node_acquire(struct kdbus_node *node); +void kdbus_node_release(struct kdbus_node *node); + +struct kdbus_node *kdbus_node_find_child(struct kdbus_node *node, + const char *name); +struct kdbus_node *kdbus_node_find_closest(struct kdbus_node *node, + unsigned int hash); +struct kdbus_node *kdbus_node_next_child(struct kdbus_node *node, + struct kdbus_node *prev); + +#endif diff --git a/ipc/kdbus/notify.c b/ipc/kdbus/notify.c new file mode 100644 index 000000000000..375758c4896a --- /dev/null +++ b/ipc/kdbus/notify.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include "bus.h" +#include "connection.h" +#include "domain.h" +#include "endpoint.h" +#include "item.h" +#include "message.h" +#include "notify.h" + +static inline void kdbus_notify_add_tail(struct kdbus_staging *staging, + struct kdbus_bus *bus) +{ + spin_lock(&bus->notify_lock); + list_add_tail(&staging->notify_entry, &bus->notify_list); + spin_unlock(&bus->notify_lock); +} + +static int kdbus_notify_reply(struct kdbus_bus *bus, u64 id, + u64 cookie, u64 msg_type) +{ + struct kdbus_staging *s; + + s = kdbus_staging_new_kernel(bus, id, cookie, 0, msg_type); + if (IS_ERR(s)) + return PTR_ERR(s); + + kdbus_notify_add_tail(s, bus); + return 0; +} + +/** + * kdbus_notify_reply_timeout() - queue a timeout reply + * @bus: Bus which queues the messages + * @id: The destination's connection ID + * @cookie: The cookie to set in the reply. + * + * Queues a message that has a KDBUS_ITEM_REPLY_TIMEOUT item attached. + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_reply_timeout(struct kdbus_bus *bus, u64 id, u64 cookie) +{ + return kdbus_notify_reply(bus, id, cookie, KDBUS_ITEM_REPLY_TIMEOUT); +} + +/** + * kdbus_notify_reply_dead() - queue a 'dead' reply + * @bus: Bus which queues the messages + * @id: The destination's connection ID + * @cookie: The cookie to set in the reply. + * + * Queues a message that has a KDBUS_ITEM_REPLY_DEAD item attached. + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_reply_dead(struct kdbus_bus *bus, u64 id, u64 cookie) +{ + return kdbus_notify_reply(bus, id, cookie, KDBUS_ITEM_REPLY_DEAD); +} + +/** + * kdbus_notify_name_change() - queue a notification about a name owner change + * @bus: Bus which queues the messages + * @type: The type if the notification; KDBUS_ITEM_NAME_ADD, + * KDBUS_ITEM_NAME_CHANGE or KDBUS_ITEM_NAME_REMOVE + * @old_id: The id of the connection that used to own the name + * @new_id: The id of the new owner connection + * @old_flags: The flags to pass in the KDBUS_ITEM flags field for + * the old owner + * @new_flags: The flags to pass in the KDBUS_ITEM flags field for + * the new owner + * @name: The name that was removed or assigned to a new owner + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_name_change(struct kdbus_bus *bus, u64 type, + u64 old_id, u64 new_id, + u64 old_flags, u64 new_flags, + const char *name) +{ + size_t name_len, extra_size; + struct kdbus_staging *s; + + name_len = strlen(name) + 1; + extra_size = sizeof(struct kdbus_notify_name_change) + name_len; + + s = kdbus_staging_new_kernel(bus, KDBUS_DST_ID_BROADCAST, 0, + extra_size, type); + if (IS_ERR(s)) + return PTR_ERR(s); + + s->notify->name_change.old_id.id = old_id; + s->notify->name_change.old_id.flags = old_flags; + s->notify->name_change.new_id.id = new_id; + s->notify->name_change.new_id.flags = new_flags; + memcpy(s->notify->name_change.name, name, name_len); + + kdbus_notify_add_tail(s, bus); + return 0; +} + +/** + * kdbus_notify_id_change() - queue a notification about a unique ID change + * @bus: Bus which queues the messages + * @type: The type if the notification; KDBUS_ITEM_ID_ADD or + * KDBUS_ITEM_ID_REMOVE + * @id: The id of the connection that was added or removed + * @flags: The flags to pass in the KDBUS_ITEM flags field + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_notify_id_change(struct kdbus_bus *bus, u64 type, u64 id, u64 flags) +{ + struct kdbus_staging *s; + size_t extra_size; + + extra_size = sizeof(struct kdbus_notify_id_change); + s = kdbus_staging_new_kernel(bus, KDBUS_DST_ID_BROADCAST, 0, + extra_size, type); + if (IS_ERR(s)) + return PTR_ERR(s); + + s->notify->id_change.id = id; + s->notify->id_change.flags = flags; + + kdbus_notify_add_tail(s, bus); + return 0; +} + +/** + * kdbus_notify_flush() - send a list of collected messages + * @bus: Bus which queues the messages + * + * The list is empty after sending the messages. + */ +void kdbus_notify_flush(struct kdbus_bus *bus) +{ + LIST_HEAD(notify_list); + struct kdbus_staging *s, *tmp; + + mutex_lock(&bus->notify_flush_lock); + down_read(&bus->name_registry->rwlock); + + spin_lock(&bus->notify_lock); + list_splice_init(&bus->notify_list, ¬ify_list); + spin_unlock(&bus->notify_lock); + + list_for_each_entry_safe(s, tmp, ¬ify_list, notify_entry) { + if (s->msg->dst_id != KDBUS_DST_ID_BROADCAST) { + struct kdbus_conn *conn; + + conn = kdbus_bus_find_conn_by_id(bus, s->msg->dst_id); + if (conn) { + kdbus_bus_eavesdrop(bus, NULL, s); + kdbus_conn_entry_insert(NULL, conn, s, NULL, + NULL); + kdbus_conn_unref(conn); + } + } else { + kdbus_bus_broadcast(bus, NULL, s); + } + + list_del(&s->notify_entry); + kdbus_staging_free(s); + } + + up_read(&bus->name_registry->rwlock); + mutex_unlock(&bus->notify_flush_lock); +} + +/** + * kdbus_notify_free() - free a list of collected messages + * @bus: Bus which queues the messages + */ +void kdbus_notify_free(struct kdbus_bus *bus) +{ + struct kdbus_staging *s, *tmp; + + list_for_each_entry_safe(s, tmp, &bus->notify_list, notify_entry) { + list_del(&s->notify_entry); + kdbus_staging_free(s); + } +} diff --git a/ipc/kdbus/notify.h b/ipc/kdbus/notify.h new file mode 100644 index 000000000000..03df464cb735 --- /dev/null +++ b/ipc/kdbus/notify.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_NOTIFY_H +#define __KDBUS_NOTIFY_H + +struct kdbus_bus; + +int kdbus_notify_id_change(struct kdbus_bus *bus, u64 type, u64 id, u64 flags); +int kdbus_notify_reply_timeout(struct kdbus_bus *bus, u64 id, u64 cookie); +int kdbus_notify_reply_dead(struct kdbus_bus *bus, u64 id, u64 cookie); +int kdbus_notify_name_change(struct kdbus_bus *bus, u64 type, + u64 old_id, u64 new_id, + u64 old_flags, u64 new_flags, + const char *name); +void kdbus_notify_flush(struct kdbus_bus *bus); +void kdbus_notify_free(struct kdbus_bus *bus); + +#endif diff --git a/ipc/kdbus/policy.c b/ipc/kdbus/policy.c new file mode 100644 index 000000000000..f2618e15e78d --- /dev/null +++ b/ipc/kdbus/policy.c @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/dcache.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "bus.h" +#include "connection.h" +#include "domain.h" +#include "item.h" +#include "names.h" +#include "policy.h" + +#define KDBUS_POLICY_HASH_SIZE 64 + +/** + * struct kdbus_policy_db_entry_access - a database entry access item + * @type: One of KDBUS_POLICY_ACCESS_* types + * @access: Access to grant. One of KDBUS_POLICY_* + * @uid: For KDBUS_POLICY_ACCESS_USER, the global uid + * @gid: For KDBUS_POLICY_ACCESS_GROUP, the global gid + * @list: List entry item for the entry's list + * + * This is the internal version of struct kdbus_policy_db_access. + */ +struct kdbus_policy_db_entry_access { + u8 type; /* USER, GROUP, WORLD */ + u8 access; /* OWN, TALK, SEE */ + union { + kuid_t uid; /* global uid */ + kgid_t gid; /* global gid */ + }; + struct list_head list; +}; + +/** + * struct kdbus_policy_db_entry - a policy database entry + * @name: The name to match the policy entry against + * @hentry: The hash entry for the database's entries_hash + * @access_list: List head for keeping tracks of the entry's + * access items. + * @owner: The owner of this entry. Can be a kdbus_conn or + * a kdbus_ep object. + * @wildcard: The name is a wildcard, such as ending on '.*' + */ +struct kdbus_policy_db_entry { + char *name; + struct hlist_node hentry; + struct list_head access_list; + const void *owner; + bool wildcard:1; +}; + +static void kdbus_policy_entry_free(struct kdbus_policy_db_entry *e) +{ + struct kdbus_policy_db_entry_access *a, *tmp; + + list_for_each_entry_safe(a, tmp, &e->access_list, list) { + list_del(&a->list); + kfree(a); + } + + kfree(e->name); + kfree(e); +} + +static unsigned int kdbus_strnhash(const char *str, size_t len) +{ + unsigned long hash = init_name_hash(); + + while (len--) + hash = partial_name_hash(*str++, hash); + + return end_name_hash(hash); +} + +static const struct kdbus_policy_db_entry * +kdbus_policy_lookup(struct kdbus_policy_db *db, const char *name, u32 hash) +{ + struct kdbus_policy_db_entry *e; + const char *dot; + size_t len; + + /* find exact match */ + hash_for_each_possible(db->entries_hash, e, hentry, hash) + if (strcmp(e->name, name) == 0 && !e->wildcard) + return e; + + /* find wildcard match */ + + dot = strrchr(name, '.'); + if (!dot) + return NULL; + + len = dot - name; + hash = kdbus_strnhash(name, len); + + hash_for_each_possible(db->entries_hash, e, hentry, hash) + if (e->wildcard && !strncmp(e->name, name, len) && + !e->name[len]) + return e; + + return NULL; +} + +/** + * kdbus_policy_db_clear - release all memory from a policy db + * @db: The policy database + */ +void kdbus_policy_db_clear(struct kdbus_policy_db *db) +{ + struct kdbus_policy_db_entry *e; + struct hlist_node *tmp; + unsigned int i; + + /* purge entries */ + down_write(&db->entries_rwlock); + hash_for_each_safe(db->entries_hash, i, tmp, e, hentry) { + hash_del(&e->hentry); + kdbus_policy_entry_free(e); + } + up_write(&db->entries_rwlock); +} + +/** + * kdbus_policy_db_init() - initialize a new policy database + * @db: The location of the database + * + * This initializes a new policy-db. The underlying memory must have been + * cleared to zero by the caller. + */ +void kdbus_policy_db_init(struct kdbus_policy_db *db) +{ + hash_init(db->entries_hash); + init_rwsem(&db->entries_rwlock); +} + +/** + * kdbus_policy_query_unlocked() - Query the policy database + * @db: Policy database + * @cred: Credentials to test against + * @name: Name to query + * @hash: Hash value of @name + * + * Same as kdbus_policy_query() but requires the caller to lock the policy + * database against concurrent writes. + * + * Return: The highest KDBUS_POLICY_* access type found, or -EPERM if none. + */ +int kdbus_policy_query_unlocked(struct kdbus_policy_db *db, + const struct cred *cred, const char *name, + unsigned int hash) +{ + struct kdbus_policy_db_entry_access *a; + const struct kdbus_policy_db_entry *e; + int i, highest = -EPERM; + + e = kdbus_policy_lookup(db, name, hash); + if (!e) + return -EPERM; + + list_for_each_entry(a, &e->access_list, list) { + if ((int)a->access <= highest) + continue; + + switch (a->type) { + case KDBUS_POLICY_ACCESS_USER: + if (uid_eq(cred->euid, a->uid)) + highest = a->access; + break; + case KDBUS_POLICY_ACCESS_GROUP: + if (gid_eq(cred->egid, a->gid)) { + highest = a->access; + break; + } + + for (i = 0; i < cred->group_info->ngroups; i++) { + kgid_t gid = GROUP_AT(cred->group_info, i); + + if (gid_eq(gid, a->gid)) { + highest = a->access; + break; + } + } + + break; + case KDBUS_POLICY_ACCESS_WORLD: + highest = a->access; + break; + } + + /* OWN is the highest possible policy */ + if (highest >= KDBUS_POLICY_OWN) + break; + } + + return highest; +} + +/** + * kdbus_policy_query() - Query the policy database + * @db: Policy database + * @cred: Credentials to test against + * @name: Name to query + * @hash: Hash value of @name + * + * Query the policy database @db for the access rights of @cred to the name + * @name. The access rights of @cred are returned, or -EPERM if no access is + * granted. + * + * This call effectively searches for the highest access-right granted to + * @cred. The caller should really cache those as policy lookups are rather + * expensive. + * + * Return: The highest KDBUS_POLICY_* access type found, or -EPERM if none. + */ +int kdbus_policy_query(struct kdbus_policy_db *db, const struct cred *cred, + const char *name, unsigned int hash) +{ + int ret; + + down_read(&db->entries_rwlock); + ret = kdbus_policy_query_unlocked(db, cred, name, hash); + up_read(&db->entries_rwlock); + + return ret; +} + +static void __kdbus_policy_remove_owner(struct kdbus_policy_db *db, + const void *owner) +{ + struct kdbus_policy_db_entry *e; + struct hlist_node *tmp; + int i; + + hash_for_each_safe(db->entries_hash, i, tmp, e, hentry) + if (e->owner == owner) { + hash_del(&e->hentry); + kdbus_policy_entry_free(e); + } +} + +/** + * kdbus_policy_remove_owner() - remove all entries related to a connection + * @db: The policy database + * @owner: The connection which items to remove + */ +void kdbus_policy_remove_owner(struct kdbus_policy_db *db, + const void *owner) +{ + down_write(&db->entries_rwlock); + __kdbus_policy_remove_owner(db, owner); + up_write(&db->entries_rwlock); +} + +/* + * Convert user provided policy access to internal kdbus policy + * access + */ +static struct kdbus_policy_db_entry_access * +kdbus_policy_make_access(const struct kdbus_policy_access *uaccess) +{ + int ret; + struct kdbus_policy_db_entry_access *a; + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) + return ERR_PTR(-ENOMEM); + + ret = -EINVAL; + switch (uaccess->access) { + case KDBUS_POLICY_SEE: + case KDBUS_POLICY_TALK: + case KDBUS_POLICY_OWN: + a->access = uaccess->access; + break; + default: + goto err; + } + + switch (uaccess->type) { + case KDBUS_POLICY_ACCESS_USER: + a->uid = make_kuid(current_user_ns(), uaccess->id); + if (!uid_valid(a->uid)) + goto err; + + break; + case KDBUS_POLICY_ACCESS_GROUP: + a->gid = make_kgid(current_user_ns(), uaccess->id); + if (!gid_valid(a->gid)) + goto err; + + break; + case KDBUS_POLICY_ACCESS_WORLD: + break; + default: + goto err; + } + + a->type = uaccess->type; + + return a; + +err: + kfree(a); + return ERR_PTR(ret); +} + +/** + * kdbus_policy_set() - set a connection's policy rules + * @db: The policy database + * @items: A list of kdbus_item elements that contain both + * names and access rules to set. + * @items_size: The total size of the items. + * @max_policies: The maximum number of policy entries to allow. + * Pass 0 for no limit. + * @allow_wildcards: Boolean value whether wildcard entries (such + * ending on '.*') should be allowed. + * @owner: The owner of the new policy items. + * + * This function sets a new set of policies for a given owner. The names and + * access rules are gathered by walking the list of items passed in as + * argument. An item of type KDBUS_ITEM_NAME is expected before any number of + * KDBUS_ITEM_POLICY_ACCESS items. If there are more repetitions of this + * pattern than denoted in @max_policies, -EINVAL is returned. + * + * In order to allow atomic replacement of rules, the function first removes + * all entries that have been created for the given owner previously. + * + * Callers to this function must make sure that the owner is a custom + * endpoint, or if the endpoint is a default endpoint, then it must be + * either a policy holder or an activator. + * + * Return: 0 on success, negative errno on failure. + */ +int kdbus_policy_set(struct kdbus_policy_db *db, + const struct kdbus_item *items, + size_t items_size, + size_t max_policies, + bool allow_wildcards, + const void *owner) +{ + struct kdbus_policy_db_entry_access *a; + struct kdbus_policy_db_entry *e, *p; + const struct kdbus_item *item; + struct hlist_node *tmp; + HLIST_HEAD(entries); + HLIST_HEAD(restore); + size_t count = 0; + int i, ret = 0; + u32 hash; + + /* Walk the list of items and look for new policies */ + e = NULL; + KDBUS_ITEMS_FOREACH(item, items, items_size) { + switch (item->type) { + case KDBUS_ITEM_NAME: { + size_t len; + + if (max_policies && ++count > max_policies) { + ret = -E2BIG; + goto exit; + } + + if (!kdbus_name_is_valid(item->str, true)) { + ret = -EINVAL; + goto exit; + } + + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) { + ret = -ENOMEM; + goto exit; + } + + INIT_LIST_HEAD(&e->access_list); + e->owner = owner; + hlist_add_head(&e->hentry, &entries); + + e->name = kstrdup(item->str, GFP_KERNEL); + if (!e->name) { + ret = -ENOMEM; + goto exit; + } + + /* + * If a supplied name ends with an '.*', cut off that + * part, only store anything before it, and mark the + * entry as wildcard. + */ + len = strlen(e->name); + if (len > 2 && + e->name[len - 3] == '.' && + e->name[len - 2] == '*') { + if (!allow_wildcards) { + ret = -EINVAL; + goto exit; + } + + e->name[len - 3] = '\0'; + e->wildcard = true; + } + + break; + } + + case KDBUS_ITEM_POLICY_ACCESS: + if (!e) { + ret = -EINVAL; + goto exit; + } + + a = kdbus_policy_make_access(&item->policy_access); + if (IS_ERR(a)) { + ret = PTR_ERR(a); + goto exit; + } + + list_add_tail(&a->list, &e->access_list); + break; + } + } + + down_write(&db->entries_rwlock); + + /* remember previous entries to restore in case of failure */ + hash_for_each_safe(db->entries_hash, i, tmp, e, hentry) + if (e->owner == owner) { + hash_del(&e->hentry); + hlist_add_head(&e->hentry, &restore); + } + + hlist_for_each_entry_safe(e, tmp, &entries, hentry) { + /* prevent duplicates */ + hash = kdbus_strhash(e->name); + hash_for_each_possible(db->entries_hash, p, hentry, hash) + if (strcmp(e->name, p->name) == 0 && + e->wildcard == p->wildcard) { + ret = -EEXIST; + goto restore; + } + + hlist_del(&e->hentry); + hash_add(db->entries_hash, &e->hentry, hash); + } + +restore: + /* if we failed, flush all entries we added so far */ + if (ret < 0) + __kdbus_policy_remove_owner(db, owner); + + /* if we failed, restore entries, otherwise release them */ + hlist_for_each_entry_safe(e, tmp, &restore, hentry) { + hlist_del(&e->hentry); + if (ret < 0) { + hash = kdbus_strhash(e->name); + hash_add(db->entries_hash, &e->hentry, hash); + } else { + kdbus_policy_entry_free(e); + } + } + + up_write(&db->entries_rwlock); + +exit: + hlist_for_each_entry_safe(e, tmp, &entries, hentry) { + hlist_del(&e->hentry); + kdbus_policy_entry_free(e); + } + + return ret; +} diff --git a/ipc/kdbus/policy.h b/ipc/kdbus/policy.h new file mode 100644 index 000000000000..15dd7bc12068 --- /dev/null +++ b/ipc/kdbus/policy.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_POLICY_H +#define __KDBUS_POLICY_H + +#include <linux/hashtable.h> +#include <linux/rwsem.h> + +struct kdbus_conn; +struct kdbus_item; + +/** + * struct kdbus_policy_db - policy database + * @entries_hash: Hashtable of entries + * @entries_rwlock: Mutex to protect the database's access entries + */ +struct kdbus_policy_db { + DECLARE_HASHTABLE(entries_hash, 6); + struct rw_semaphore entries_rwlock; +}; + +void kdbus_policy_db_init(struct kdbus_policy_db *db); +void kdbus_policy_db_clear(struct kdbus_policy_db *db); + +int kdbus_policy_query_unlocked(struct kdbus_policy_db *db, + const struct cred *cred, const char *name, + unsigned int hash); +int kdbus_policy_query(struct kdbus_policy_db *db, const struct cred *cred, + const char *name, unsigned int hash); + +void kdbus_policy_remove_owner(struct kdbus_policy_db *db, + const void *owner); +int kdbus_policy_set(struct kdbus_policy_db *db, + const struct kdbus_item *items, + size_t items_size, + size_t max_policies, + bool allow_wildcards, + const void *owner); + +#endif diff --git a/ipc/kdbus/pool.c b/ipc/kdbus/pool.c new file mode 100644 index 000000000000..63ccd55713c7 --- /dev/null +++ b/ipc/kdbus/pool.c @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/aio.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/highmem.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pagemap.h> +#include <linux/rbtree.h> +#include <linux/sched.h> +#include <linux/shmem_fs.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/uio.h> + +#include "pool.h" +#include "util.h" + +/** + * struct kdbus_pool - the receiver's buffer + * @f: The backing shmem file + * @size: The size of the file + * @accounted_size: Currently accounted memory in bytes + * @lock: Pool data lock + * @slices: All slices sorted by address + * @slices_busy: Tree of allocated slices + * @slices_free: Tree of free slices + * + * The receiver's buffer, managed as a pool of allocated and free + * slices containing the queued messages. + * + * Messages sent with KDBUS_CMD_SEND are copied directly by the + * sending process into the receiver's pool. + * + * Messages received with KDBUS_CMD_RECV just return the offset + * to the data placed in the pool. + * + * The internally allocated memory needs to be returned by the receiver + * with KDBUS_CMD_FREE. + */ +struct kdbus_pool { + struct file *f; + size_t size; + size_t accounted_size; + struct mutex lock; + + struct list_head slices; + struct rb_root slices_busy; + struct rb_root slices_free; +}; + +/** + * struct kdbus_pool_slice - allocated element in kdbus_pool + * @pool: Pool this slice belongs to + * @off: Offset of slice in the shmem file + * @size: Size of slice + * @entry: Entry in "all slices" list + * @rb_node: Entry in free or busy list + * @free: Unused slice + * @accounted: Accounted as queue slice + * @ref_kernel: Kernel holds a reference + * @ref_user: Userspace holds a reference + * + * The pool has one or more slices, always spanning the entire size of the + * pool. + * + * Every slice is an element in a list sorted by the buffer address, to + * provide access to the next neighbor slice. + * + * Every slice is member in either the busy or the free tree. The free + * tree is organized by slice size, the busy tree organized by buffer + * offset. + */ +struct kdbus_pool_slice { + struct kdbus_pool *pool; + size_t off; + size_t size; + + struct list_head entry; + struct rb_node rb_node; + + bool free:1; + bool accounted:1; + bool ref_kernel:1; + bool ref_user:1; +}; + +static struct kdbus_pool_slice *kdbus_pool_slice_new(struct kdbus_pool *pool, + size_t off, size_t size) +{ + struct kdbus_pool_slice *slice; + + slice = kzalloc(sizeof(*slice), GFP_KERNEL); + if (!slice) + return NULL; + + slice->pool = pool; + slice->off = off; + slice->size = size; + slice->free = true; + return slice; +} + +/* insert a slice into the free tree */ +static void kdbus_pool_add_free_slice(struct kdbus_pool *pool, + struct kdbus_pool_slice *slice) +{ + struct rb_node **n; + struct rb_node *pn = NULL; + + n = &pool->slices_free.rb_node; + while (*n) { + struct kdbus_pool_slice *pslice; + + pn = *n; + pslice = rb_entry(pn, struct kdbus_pool_slice, rb_node); + if (slice->size < pslice->size) + n = &pn->rb_left; + else + n = &pn->rb_right; + } + + rb_link_node(&slice->rb_node, pn, n); + rb_insert_color(&slice->rb_node, &pool->slices_free); +} + +/* insert a slice into the busy tree */ +static void kdbus_pool_add_busy_slice(struct kdbus_pool *pool, + struct kdbus_pool_slice *slice) +{ + struct rb_node **n; + struct rb_node *pn = NULL; + + n = &pool->slices_busy.rb_node; + while (*n) { + struct kdbus_pool_slice *pslice; + + pn = *n; + pslice = rb_entry(pn, struct kdbus_pool_slice, rb_node); + if (slice->off < pslice->off) + n = &pn->rb_left; + else if (slice->off > pslice->off) + n = &pn->rb_right; + else + BUG(); + } + + rb_link_node(&slice->rb_node, pn, n); + rb_insert_color(&slice->rb_node, &pool->slices_busy); +} + +static struct kdbus_pool_slice *kdbus_pool_find_slice(struct kdbus_pool *pool, + size_t off) +{ + struct rb_node *n; + + n = pool->slices_busy.rb_node; + while (n) { + struct kdbus_pool_slice *s; + + s = rb_entry(n, struct kdbus_pool_slice, rb_node); + if (off < s->off) + n = n->rb_left; + else if (off > s->off) + n = n->rb_right; + else + return s; + } + + return NULL; +} + +/** + * kdbus_pool_slice_alloc() - allocate memory from a pool + * @pool: The receiver's pool + * @size: The number of bytes to allocate + * @accounted: Whether this slice should be accounted for + * + * The returned slice is used for kdbus_pool_slice_release() to + * free the allocated memory. If either @kvec or @iovec is non-NULL, the data + * will be copied from kernel or userspace memory into the new slice at + * offset 0. + * + * Return: the allocated slice on success, ERR_PTR on failure. + */ +struct kdbus_pool_slice *kdbus_pool_slice_alloc(struct kdbus_pool *pool, + size_t size, bool accounted) +{ + size_t slice_size = KDBUS_ALIGN8(size); + struct rb_node *n, *found = NULL; + struct kdbus_pool_slice *s; + int ret = 0; + + if (WARN_ON(!size)) + return ERR_PTR(-EINVAL); + + /* search a free slice with the closest matching size */ + mutex_lock(&pool->lock); + n = pool->slices_free.rb_node; + while (n) { + s = rb_entry(n, struct kdbus_pool_slice, rb_node); + if (slice_size < s->size) { + found = n; + n = n->rb_left; + } else if (slice_size > s->size) { + n = n->rb_right; + } else { + found = n; + break; + } + } + + /* no slice with the minimum size found in the pool */ + if (!found) { + ret = -EXFULL; + goto exit_unlock; + } + + /* no exact match, use the closest one */ + if (!n) { + struct kdbus_pool_slice *s_new; + + s = rb_entry(found, struct kdbus_pool_slice, rb_node); + + /* split-off the remainder of the size to its own slice */ + s_new = kdbus_pool_slice_new(pool, s->off + slice_size, + s->size - slice_size); + if (!s_new) { + ret = -ENOMEM; + goto exit_unlock; + } + + list_add(&s_new->entry, &s->entry); + kdbus_pool_add_free_slice(pool, s_new); + + /* adjust our size now that we split-off another slice */ + s->size = slice_size; + } + + /* move slice from free to the busy tree */ + rb_erase(found, &pool->slices_free); + kdbus_pool_add_busy_slice(pool, s); + + WARN_ON(s->ref_kernel || s->ref_user); + + s->ref_kernel = true; + s->free = false; + s->accounted = accounted; + if (accounted) + pool->accounted_size += s->size; + mutex_unlock(&pool->lock); + + return s; + +exit_unlock: + mutex_unlock(&pool->lock); + return ERR_PTR(ret); +} + +static void __kdbus_pool_slice_release(struct kdbus_pool_slice *slice) +{ + struct kdbus_pool *pool = slice->pool; + + /* don't free the slice if either has a reference */ + if (slice->ref_kernel || slice->ref_user) + return; + + if (WARN_ON(slice->free)) + return; + + rb_erase(&slice->rb_node, &pool->slices_busy); + + /* merge with the next free slice */ + if (!list_is_last(&slice->entry, &pool->slices)) { + struct kdbus_pool_slice *s; + + s = list_entry(slice->entry.next, + struct kdbus_pool_slice, entry); + if (s->free) { + rb_erase(&s->rb_node, &pool->slices_free); + list_del(&s->entry); + slice->size += s->size; + kfree(s); + } + } + + /* merge with previous free slice */ + if (pool->slices.next != &slice->entry) { + struct kdbus_pool_slice *s; + + s = list_entry(slice->entry.prev, + struct kdbus_pool_slice, entry); + if (s->free) { + rb_erase(&s->rb_node, &pool->slices_free); + list_del(&slice->entry); + s->size += slice->size; + kfree(slice); + slice = s; + } + } + + slice->free = true; + kdbus_pool_add_free_slice(pool, slice); +} + +/** + * kdbus_pool_slice_release() - drop kernel-reference on allocated slice + * @slice: Slice allocated from the pool + * + * This releases the kernel-reference on the given slice. If the + * kernel-reference and the user-reference on a slice are dropped, the slice is + * returned to the pool. + * + * So far, we do not implement full ref-counting on slices. Each, kernel and + * user-space can have exactly one reference to a slice. If both are dropped at + * the same time, the slice is released. + */ +void kdbus_pool_slice_release(struct kdbus_pool_slice *slice) +{ + struct kdbus_pool *pool; + + if (!slice) + return; + + /* @slice may be freed, so keep local ptr to @pool */ + pool = slice->pool; + + mutex_lock(&pool->lock); + /* kernel must own a ref to @slice to drop it */ + WARN_ON(!slice->ref_kernel); + slice->ref_kernel = false; + /* no longer kernel-owned, de-account slice */ + if (slice->accounted && !WARN_ON(pool->accounted_size < slice->size)) + pool->accounted_size -= slice->size; + __kdbus_pool_slice_release(slice); + mutex_unlock(&pool->lock); +} + +/** + * kdbus_pool_release_offset() - release a public offset + * @pool: pool to operate on + * @off: offset to release + * + * This should be called whenever user-space frees a slice given to them. It + * verifies the slice is available and public, and then drops it. It ensures + * correct locking and barriers against queues. + * + * Return: 0 on success, ENXIO if the offset is invalid or not public. + */ +int kdbus_pool_release_offset(struct kdbus_pool *pool, size_t off) +{ + struct kdbus_pool_slice *slice; + int ret = 0; + + /* 'pool->size' is used as dummy offset for empty slices */ + if (off == pool->size) + return 0; + + mutex_lock(&pool->lock); + slice = kdbus_pool_find_slice(pool, off); + if (slice && slice->ref_user) { + slice->ref_user = false; + __kdbus_pool_slice_release(slice); + } else { + ret = -ENXIO; + } + mutex_unlock(&pool->lock); + + return ret; +} + +/** + * kdbus_pool_publish_empty() - publish empty slice to user-space + * @pool: pool to operate on + * @off: output storage for offset, or NULL + * @size: output storage for size, or NULL + * + * This is the same as kdbus_pool_slice_publish(), but uses a dummy slice with + * size 0. The returned offset points to the end of the pool and is never + * returned on real slices. + */ +void kdbus_pool_publish_empty(struct kdbus_pool *pool, u64 *off, u64 *size) +{ + if (off) + *off = pool->size; + if (size) + *size = 0; +} + +/** + * kdbus_pool_slice_publish() - publish slice to user-space + * @slice: The slice + * @out_offset: Output storage for offset, or NULL + * @out_size: Output storage for size, or NULL + * + * This prepares a slice to be published to user-space. + * + * This call combines the following operations: + * * the memory region is flushed so the user's memory view is consistent + * * the slice is marked as referenced by user-space, so user-space has to + * call KDBUS_CMD_FREE to release it + * * the offset and size of the slice are written to the given output + * arguments, if non-NULL + */ +void kdbus_pool_slice_publish(struct kdbus_pool_slice *slice, + u64 *out_offset, u64 *out_size) +{ + mutex_lock(&slice->pool->lock); + /* kernel must own a ref to @slice to gain a user-space ref */ + WARN_ON(!slice->ref_kernel); + slice->ref_user = true; + mutex_unlock(&slice->pool->lock); + + if (out_offset) + *out_offset = slice->off; + if (out_size) + *out_size = slice->size; +} + +/** + * kdbus_pool_slice_offset() - Get a slice's offset inside the pool + * @slice: Slice to return the offset of + * + * Return: The internal offset @slice inside the pool. + */ +off_t kdbus_pool_slice_offset(const struct kdbus_pool_slice *slice) +{ + return slice->off; +} + +/** + * kdbus_pool_slice_size() - get size of a pool slice + * @slice: slice to query + * + * Return: size of the given slice + */ +size_t kdbus_pool_slice_size(const struct kdbus_pool_slice *slice) +{ + return slice->size; +} + +/** + * kdbus_pool_new() - create a new pool + * @name: Name of the (deleted) file which shows up in + * /proc, used for debugging + * @size: Maximum size of the pool + * + * Return: a new kdbus_pool on success, ERR_PTR on failure. + */ +struct kdbus_pool *kdbus_pool_new(const char *name, size_t size) +{ + struct kdbus_pool_slice *s; + struct kdbus_pool *p; + struct file *f; + char *n = NULL; + int ret; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + if (name) { + n = kasprintf(GFP_KERNEL, KBUILD_MODNAME "-conn:%s", name); + if (!n) { + ret = -ENOMEM; + goto exit_free; + } + } + + f = shmem_file_setup(n ?: KBUILD_MODNAME "-conn", size, 0); + kfree(n); + + if (IS_ERR(f)) { + ret = PTR_ERR(f); + goto exit_free; + } + + ret = get_write_access(file_inode(f)); + if (ret < 0) + goto exit_put_shmem; + + /* allocate first slice spanning the entire pool */ + s = kdbus_pool_slice_new(p, 0, size); + if (!s) { + ret = -ENOMEM; + goto exit_put_write; + } + + p->f = f; + p->size = size; + p->slices_free = RB_ROOT; + p->slices_busy = RB_ROOT; + mutex_init(&p->lock); + + INIT_LIST_HEAD(&p->slices); + list_add(&s->entry, &p->slices); + + kdbus_pool_add_free_slice(p, s); + return p; + +exit_put_write: + put_write_access(file_inode(f)); +exit_put_shmem: + fput(f); +exit_free: + kfree(p); + return ERR_PTR(ret); +} + +/** + * kdbus_pool_free() - destroy pool + * @pool: The receiver's pool + */ +void kdbus_pool_free(struct kdbus_pool *pool) +{ + struct kdbus_pool_slice *s, *tmp; + + if (!pool) + return; + + list_for_each_entry_safe(s, tmp, &pool->slices, entry) { + list_del(&s->entry); + kfree(s); + } + + put_write_access(file_inode(pool->f)); + fput(pool->f); + kfree(pool); +} + +/** + * kdbus_pool_accounted() - retrieve accounting information + * @pool: pool to query + * @size: output for overall pool size + * @acc: output for currently accounted size + * + * This returns accounting information of the pool. Note that the data might + * change after the function returns, as the pool lock is dropped. You need to + * protect the data via other means, if you need reliable accounting. + */ +void kdbus_pool_accounted(struct kdbus_pool *pool, size_t *size, size_t *acc) +{ + mutex_lock(&pool->lock); + if (size) + *size = pool->size; + if (acc) + *acc = pool->accounted_size; + mutex_unlock(&pool->lock); +} + +/** + * kdbus_pool_slice_copy_iovec() - copy user memory to a slice + * @slice: The slice to write to + * @off: Offset in the slice to write to + * @iov: iovec array, pointing to data to copy + * @iov_len: Number of elements in @iov + * @total_len: Total number of bytes described in members of @iov + * + * User memory referenced by @iov will be copied into @slice at offset @off. + * + * Return: the numbers of bytes copied, negative errno on failure. + */ +ssize_t +kdbus_pool_slice_copy_iovec(const struct kdbus_pool_slice *slice, loff_t off, + struct iovec *iov, size_t iov_len, size_t total_len) +{ + struct iov_iter iter; + ssize_t len; + + if (WARN_ON(off + total_len > slice->size)) + return -EFAULT; + + off += slice->off; + iov_iter_init(&iter, WRITE, iov, iov_len, total_len); + len = vfs_iter_write(slice->pool->f, &iter, &off); + + return (len >= 0 && len != total_len) ? -EFAULT : len; +} + +/** + * kdbus_pool_slice_copy_kvec() - copy kernel memory to a slice + * @slice: The slice to write to + * @off: Offset in the slice to write to + * @kvec: kvec array, pointing to data to copy + * @kvec_len: Number of elements in @kvec + * @total_len: Total number of bytes described in members of @kvec + * + * Kernel memory referenced by @kvec will be copied into @slice at offset @off. + * + * Return: the numbers of bytes copied, negative errno on failure. + */ +ssize_t kdbus_pool_slice_copy_kvec(const struct kdbus_pool_slice *slice, + loff_t off, struct kvec *kvec, + size_t kvec_len, size_t total_len) +{ + struct iov_iter iter; + mm_segment_t old_fs; + ssize_t len; + + if (WARN_ON(off + total_len > slice->size)) + return -EFAULT; + + off += slice->off; + iov_iter_kvec(&iter, WRITE | ITER_KVEC, kvec, kvec_len, total_len); + + old_fs = get_fs(); + set_fs(get_ds()); + len = vfs_iter_write(slice->pool->f, &iter, &off); + set_fs(old_fs); + + return (len >= 0 && len != total_len) ? -EFAULT : len; +} + +/** + * kdbus_pool_slice_copy() - copy data from one slice into another + * @slice_dst: destination slice + * @slice_src: source slice + * + * Return: 0 on success, negative error number on failure. + */ +int kdbus_pool_slice_copy(const struct kdbus_pool_slice *slice_dst, + const struct kdbus_pool_slice *slice_src) +{ + struct file *f_src = slice_src->pool->f; + struct file *f_dst = slice_dst->pool->f; + struct inode *i_dst = file_inode(f_dst); + struct address_space *mapping_dst = f_dst->f_mapping; + const struct address_space_operations *aops = mapping_dst->a_ops; + unsigned long len = slice_src->size; + loff_t off_src = slice_src->off; + loff_t off_dst = slice_dst->off; + mm_segment_t old_fs; + int ret = 0; + + if (WARN_ON(slice_src->size != slice_dst->size) || + WARN_ON(slice_src->free || slice_dst->free)) + return -EINVAL; + + mutex_lock(&i_dst->i_mutex); + old_fs = get_fs(); + set_fs(get_ds()); + while (len > 0) { + unsigned long page_off; + unsigned long copy_len; + char __user *kaddr; + struct page *page; + ssize_t n_read; + void *fsdata; + long status; + + page_off = off_dst & (PAGE_CACHE_SIZE - 1); + copy_len = min_t(unsigned long, + PAGE_CACHE_SIZE - page_off, len); + + status = aops->write_begin(f_dst, mapping_dst, off_dst, + copy_len, 0, &page, &fsdata); + if (unlikely(status < 0)) { + ret = status; + break; + } + + kaddr = (char __force __user *)kmap(page) + page_off; + n_read = __vfs_read(f_src, kaddr, copy_len, &off_src); + kunmap(page); + mark_page_accessed(page); + flush_dcache_page(page); + + if (unlikely(n_read != copy_len)) { + ret = -EFAULT; + break; + } + + status = aops->write_end(f_dst, mapping_dst, off_dst, + copy_len, copy_len, page, fsdata); + if (unlikely(status != copy_len)) { + ret = -EFAULT; + break; + } + + off_dst += copy_len; + len -= copy_len; + } + set_fs(old_fs); + mutex_unlock(&i_dst->i_mutex); + + return ret; +} + +/** + * kdbus_pool_mmap() - map the pool into the process + * @pool: The receiver's pool + * @vma: passed by mmap() syscall + * + * Return: the result of the mmap() call, negative errno on failure. + */ +int kdbus_pool_mmap(const struct kdbus_pool *pool, struct vm_area_struct *vma) +{ + /* deny write access to the pool */ + if (vma->vm_flags & VM_WRITE) + return -EPERM; + vma->vm_flags &= ~VM_MAYWRITE; + + /* do not allow to map more than the size of the file */ + if ((vma->vm_end - vma->vm_start) > pool->size) + return -EFAULT; + + /* replace the connection file with our shmem file */ + if (vma->vm_file) + fput(vma->vm_file); + vma->vm_file = get_file(pool->f); + + return pool->f->f_op->mmap(pool->f, vma); +} diff --git a/ipc/kdbus/pool.h b/ipc/kdbus/pool.h new file mode 100644 index 000000000000..a9038213aa4d --- /dev/null +++ b/ipc/kdbus/pool.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_POOL_H +#define __KDBUS_POOL_H + +#include <linux/uio.h> + +struct kdbus_pool; +struct kdbus_pool_slice; + +struct kdbus_pool *kdbus_pool_new(const char *name, size_t size); +void kdbus_pool_free(struct kdbus_pool *pool); +void kdbus_pool_accounted(struct kdbus_pool *pool, size_t *size, size_t *acc); +int kdbus_pool_mmap(const struct kdbus_pool *pool, struct vm_area_struct *vma); +int kdbus_pool_release_offset(struct kdbus_pool *pool, size_t off); +void kdbus_pool_publish_empty(struct kdbus_pool *pool, u64 *off, u64 *size); + +struct kdbus_pool_slice *kdbus_pool_slice_alloc(struct kdbus_pool *pool, + size_t size, bool accounted); +void kdbus_pool_slice_release(struct kdbus_pool_slice *slice); +void kdbus_pool_slice_publish(struct kdbus_pool_slice *slice, + u64 *out_offset, u64 *out_size); +off_t kdbus_pool_slice_offset(const struct kdbus_pool_slice *slice); +size_t kdbus_pool_slice_size(const struct kdbus_pool_slice *slice); +int kdbus_pool_slice_copy(const struct kdbus_pool_slice *slice_dst, + const struct kdbus_pool_slice *slice_src); +ssize_t kdbus_pool_slice_copy_kvec(const struct kdbus_pool_slice *slice, + loff_t off, struct kvec *kvec, + size_t kvec_count, size_t total_len); +ssize_t kdbus_pool_slice_copy_iovec(const struct kdbus_pool_slice *slice, + loff_t off, struct iovec *iov, + size_t iov_count, size_t total_len); + +#endif diff --git a/ipc/kdbus/queue.c b/ipc/kdbus/queue.c new file mode 100644 index 000000000000..f9c44d7bae6d --- /dev/null +++ b/ipc/kdbus/queue.c @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/audit.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/hashtable.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/math64.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/uio.h> + +#include "util.h" +#include "domain.h" +#include "connection.h" +#include "item.h" +#include "message.h" +#include "metadata.h" +#include "queue.h" +#include "reply.h" + +/** + * kdbus_queue_init() - initialize data structure related to a queue + * @queue: The queue to initialize + */ +void kdbus_queue_init(struct kdbus_queue *queue) +{ + INIT_LIST_HEAD(&queue->msg_list); + queue->msg_prio_queue = RB_ROOT; +} + +/** + * kdbus_queue_peek() - Retrieves an entry from a queue + * @queue: The queue + * @priority: The minimum priority of the entry to peek + * @use_priority: Boolean flag whether or not to peek by priority + * + * Look for a entry in a queue, either by priority, or the oldest one (FIFO). + * The entry is not freed, put off the queue's lists or anything else. + * + * Return: the peeked queue entry on success, NULL if no suitable msg is found + */ +struct kdbus_queue_entry *kdbus_queue_peek(struct kdbus_queue *queue, + s64 priority, bool use_priority) +{ + struct kdbus_queue_entry *e; + + if (list_empty(&queue->msg_list)) + return NULL; + + if (use_priority) { + /* get next entry with highest priority */ + e = rb_entry(queue->msg_prio_highest, + struct kdbus_queue_entry, prio_node); + + /* no entry with the requested priority */ + if (e->priority > priority) + return NULL; + } else { + /* ignore the priority, return the next entry in the entry */ + e = list_first_entry(&queue->msg_list, + struct kdbus_queue_entry, entry); + } + + return e; +} + +static void kdbus_queue_entry_link(struct kdbus_queue_entry *entry) +{ + struct kdbus_queue *queue = &entry->conn->queue; + struct rb_node **n, *pn = NULL; + bool highest = true; + + lockdep_assert_held(&entry->conn->lock); + if (WARN_ON(!list_empty(&entry->entry))) + return; + + /* sort into priority entry tree */ + n = &queue->msg_prio_queue.rb_node; + while (*n) { + struct kdbus_queue_entry *e; + + pn = *n; + e = rb_entry(pn, struct kdbus_queue_entry, prio_node); + + /* existing node for this priority, add to its list */ + if (likely(entry->priority == e->priority)) { + list_add_tail(&entry->prio_entry, &e->prio_entry); + goto prio_done; + } + + if (entry->priority < e->priority) { + n = &pn->rb_left; + } else { + n = &pn->rb_right; + highest = false; + } + } + + /* cache highest-priority entry */ + if (highest) + queue->msg_prio_highest = &entry->prio_node; + + /* new node for this priority */ + rb_link_node(&entry->prio_node, pn, n); + rb_insert_color(&entry->prio_node, &queue->msg_prio_queue); + INIT_LIST_HEAD(&entry->prio_entry); + +prio_done: + /* add to unsorted fifo list */ + list_add_tail(&entry->entry, &queue->msg_list); +} + +static void kdbus_queue_entry_unlink(struct kdbus_queue_entry *entry) +{ + struct kdbus_queue *queue = &entry->conn->queue; + + lockdep_assert_held(&entry->conn->lock); + if (list_empty(&entry->entry)) + return; + + list_del_init(&entry->entry); + + if (list_empty(&entry->prio_entry)) { + /* + * Single entry for this priority, update cached + * highest-priority entry, remove the tree node. + */ + if (queue->msg_prio_highest == &entry->prio_node) + queue->msg_prio_highest = rb_next(&entry->prio_node); + + rb_erase(&entry->prio_node, &queue->msg_prio_queue); + } else { + struct kdbus_queue_entry *q; + + /* + * Multiple entries for this priority entry, get next one in + * the list. Update cached highest-priority entry, store the + * new one as the tree node. + */ + q = list_first_entry(&entry->prio_entry, + struct kdbus_queue_entry, prio_entry); + list_del(&entry->prio_entry); + + if (queue->msg_prio_highest == &entry->prio_node) + queue->msg_prio_highest = &q->prio_node; + + rb_replace_node(&entry->prio_node, &q->prio_node, + &queue->msg_prio_queue); + } +} + +/** + * kdbus_queue_entry_new() - allocate a queue entry + * @src: source connection, or NULL + * @dst: destination connection + * @s: staging object carrying the message + * + * Allocates a queue entry based on a given msg and allocate space for + * the message payload and the requested metadata in the connection's pool. + * The entry is not actually added to the queue's lists at this point. + * + * Return: the allocated entry on success, or an ERR_PTR on failures. + */ +struct kdbus_queue_entry *kdbus_queue_entry_new(struct kdbus_conn *src, + struct kdbus_conn *dst, + struct kdbus_staging *s) +{ + struct kdbus_queue_entry *entry; + int ret; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&entry->entry); + entry->priority = s->msg->priority; + entry->conn = kdbus_conn_ref(dst); + entry->gaps = kdbus_gaps_ref(s->gaps); + + entry->slice = kdbus_staging_emit(s, src, dst); + if (IS_ERR(entry->slice)) { + ret = PTR_ERR(entry->slice); + entry->slice = NULL; + goto error; + } + + entry->user = src ? kdbus_user_ref(src->user) : NULL; + return entry; + +error: + kdbus_queue_entry_free(entry); + return ERR_PTR(ret); +} + +/** + * kdbus_queue_entry_free() - free resources of an entry + * @entry: The entry to free + * + * Removes resources allocated by a queue entry, along with the entry itself. + * Note that the entry's slice is not freed at this point. + */ +void kdbus_queue_entry_free(struct kdbus_queue_entry *entry) +{ + if (!entry) + return; + + lockdep_assert_held(&entry->conn->lock); + + kdbus_queue_entry_unlink(entry); + kdbus_reply_unref(entry->reply); + + if (entry->slice) { + kdbus_conn_quota_dec(entry->conn, entry->user, + kdbus_pool_slice_size(entry->slice), + entry->gaps ? entry->gaps->n_fds : 0); + kdbus_pool_slice_release(entry->slice); + } + + kdbus_user_unref(entry->user); + kdbus_gaps_unref(entry->gaps); + kdbus_conn_unref(entry->conn); + kfree(entry); +} + +/** + * kdbus_queue_entry_install() - install message components into the + * receiver's process + * @entry: The queue entry to install + * @return_flags: Pointer to store the return flags for userspace + * @install_fds: Whether or not to install associated file descriptors + * + * Return: 0 on success. + */ +int kdbus_queue_entry_install(struct kdbus_queue_entry *entry, + u64 *return_flags, bool install_fds) +{ + bool incomplete_fds = false; + int ret; + + lockdep_assert_held(&entry->conn->lock); + + ret = kdbus_gaps_install(entry->gaps, entry->slice, &incomplete_fds); + if (ret < 0) + return ret; + + if (incomplete_fds) + *return_flags |= KDBUS_RECV_RETURN_INCOMPLETE_FDS; + return 0; +} + +/** + * kdbus_queue_entry_enqueue() - enqueue an entry + * @entry: entry to enqueue + * @reply: reply to link to this entry (or NULL if none) + * + * This enqueues an unqueued entry into the message queue of the linked + * connection. It also binds a reply object to the entry so we can remember it + * when the message is moved. + * + * Once this call returns (and the connection lock is released), this entry can + * be dequeued by the target connection. Note that the entry will not be removed + * from the queue until it is destroyed. + */ +void kdbus_queue_entry_enqueue(struct kdbus_queue_entry *entry, + struct kdbus_reply *reply) +{ + lockdep_assert_held(&entry->conn->lock); + + if (WARN_ON(entry->reply) || WARN_ON(!list_empty(&entry->entry))) + return; + + entry->reply = kdbus_reply_ref(reply); + kdbus_queue_entry_link(entry); +} + +/** + * kdbus_queue_entry_move() - move queue entry + * @e: queue entry to move + * @dst: destination connection to queue the entry on + * + * This moves a queue entry onto a different connection. It allocates a new + * slice on the target connection and copies the message over. If the copy + * succeeded, we move the entry from @src to @dst. + * + * On failure, the entry is left untouched. + * + * The queue entry must be queued right now, and after the call succeeds it will + * be queued on the destination, but no longer on the source. + * + * The caller must hold the connection lock of the source *and* destination. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_queue_entry_move(struct kdbus_queue_entry *e, + struct kdbus_conn *dst) +{ + struct kdbus_pool_slice *slice = NULL; + struct kdbus_conn *src = e->conn; + size_t size, fds; + int ret; + + lockdep_assert_held(&src->lock); + lockdep_assert_held(&dst->lock); + + if (WARN_ON(list_empty(&e->entry))) + return -EINVAL; + if (src == dst) + return 0; + + size = kdbus_pool_slice_size(e->slice); + fds = e->gaps ? e->gaps->n_fds : 0; + + ret = kdbus_conn_quota_inc(dst, e->user, size, fds); + if (ret < 0) + return ret; + + slice = kdbus_pool_slice_alloc(dst->pool, size, true); + if (IS_ERR(slice)) { + ret = PTR_ERR(slice); + slice = NULL; + goto error; + } + + ret = kdbus_pool_slice_copy(slice, e->slice); + if (ret < 0) + goto error; + + kdbus_queue_entry_unlink(e); + kdbus_conn_quota_dec(src, e->user, size, fds); + kdbus_pool_slice_release(e->slice); + kdbus_conn_unref(e->conn); + + e->slice = slice; + e->conn = kdbus_conn_ref(dst); + kdbus_queue_entry_link(e); + + return 0; + +error: + kdbus_pool_slice_release(slice); + kdbus_conn_quota_dec(dst, e->user, size, fds); + return ret; +} diff --git a/ipc/kdbus/queue.h b/ipc/kdbus/queue.h new file mode 100644 index 000000000000..bf686d182ce1 --- /dev/null +++ b/ipc/kdbus/queue.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_QUEUE_H +#define __KDBUS_QUEUE_H + +#include <linux/list.h> +#include <linux/rbtree.h> + +struct kdbus_conn; +struct kdbus_pool_slice; +struct kdbus_reply; +struct kdbus_staging; +struct kdbus_user; + +/** + * struct kdbus_queue - a connection's message queue + * @msg_list: List head for kdbus_queue_entry objects + * @msg_prio_queue: RB tree root for messages, sorted by priority + * @msg_prio_highest: Link to the RB node referencing the message with the + * highest priority in the tree. + */ +struct kdbus_queue { + struct list_head msg_list; + struct rb_root msg_prio_queue; + struct rb_node *msg_prio_highest; +}; + +/** + * struct kdbus_queue_entry - messages waiting to be read + * @entry: Entry in the connection's list + * @prio_node: Entry in the priority queue tree + * @prio_entry: Queue tree node entry in the list of one priority + * @priority: Message priority + * @dst_name_id: The sequence number of the name this message is + * addressed to, 0 for messages sent to an ID + * @conn: Connection this entry is queued on + * @gaps: Gaps object to fill message gaps at RECV time + * @user: User used for accounting + * @slice: Slice in the receiver's pool for the message + * @reply: The reply block if a reply to this message is expected + */ +struct kdbus_queue_entry { + struct list_head entry; + struct rb_node prio_node; + struct list_head prio_entry; + + s64 priority; + u64 dst_name_id; + + struct kdbus_conn *conn; + struct kdbus_gaps *gaps; + struct kdbus_user *user; + struct kdbus_pool_slice *slice; + struct kdbus_reply *reply; +}; + +void kdbus_queue_init(struct kdbus_queue *queue); +struct kdbus_queue_entry *kdbus_queue_peek(struct kdbus_queue *queue, + s64 priority, bool use_priority); + +struct kdbus_queue_entry *kdbus_queue_entry_new(struct kdbus_conn *src, + struct kdbus_conn *dst, + struct kdbus_staging *s); +void kdbus_queue_entry_free(struct kdbus_queue_entry *entry); +int kdbus_queue_entry_install(struct kdbus_queue_entry *entry, + u64 *return_flags, bool install_fds); +void kdbus_queue_entry_enqueue(struct kdbus_queue_entry *entry, + struct kdbus_reply *reply); +int kdbus_queue_entry_move(struct kdbus_queue_entry *entry, + struct kdbus_conn *dst); + +#endif /* __KDBUS_QUEUE_H */ diff --git a/ipc/kdbus/reply.c b/ipc/kdbus/reply.c new file mode 100644 index 000000000000..07cb133cd8f1 --- /dev/null +++ b/ipc/kdbus/reply.c @@ -0,0 +1,252 @@ +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/uio.h> + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "message.h" +#include "metadata.h" +#include "names.h" +#include "domain.h" +#include "item.h" +#include "notify.h" +#include "policy.h" +#include "reply.h" +#include "util.h" + +/** + * kdbus_reply_new() - Allocate and set up a new kdbus_reply object + * @reply_src: The connection a reply is expected from + * @reply_dst: The connection this reply object belongs to + * @msg: Message associated with the reply + * @name_entry: Name entry used to send the message + * @sync: Whether or not to make this reply synchronous + * + * Allocate and fill a new kdbus_reply object. + * + * Return: New kdbus_conn object on success, ERR_PTR on error. + */ +struct kdbus_reply *kdbus_reply_new(struct kdbus_conn *reply_src, + struct kdbus_conn *reply_dst, + const struct kdbus_msg *msg, + struct kdbus_name_entry *name_entry, + bool sync) +{ + struct kdbus_reply *r; + int ret; + + if (atomic_inc_return(&reply_dst->request_count) > + KDBUS_CONN_MAX_REQUESTS_PENDING) { + ret = -EMLINK; + goto exit_dec_request_count; + } + + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) { + ret = -ENOMEM; + goto exit_dec_request_count; + } + + kref_init(&r->kref); + INIT_LIST_HEAD(&r->entry); + r->reply_src = kdbus_conn_ref(reply_src); + r->reply_dst = kdbus_conn_ref(reply_dst); + r->cookie = msg->cookie; + r->name_id = name_entry ? name_entry->name_id : 0; + r->deadline_ns = msg->timeout_ns; + + if (sync) { + r->sync = true; + r->waiting = true; + } + + return r; + +exit_dec_request_count: + atomic_dec(&reply_dst->request_count); + return ERR_PTR(ret); +} + +static void __kdbus_reply_free(struct kref *kref) +{ + struct kdbus_reply *reply = + container_of(kref, struct kdbus_reply, kref); + + atomic_dec(&reply->reply_dst->request_count); + kdbus_conn_unref(reply->reply_src); + kdbus_conn_unref(reply->reply_dst); + kfree(reply); +} + +/** + * kdbus_reply_ref() - Increase reference on kdbus_reply + * @r: The reply, may be %NULL + * + * Return: The reply object with an extra reference + */ +struct kdbus_reply *kdbus_reply_ref(struct kdbus_reply *r) +{ + if (r) + kref_get(&r->kref); + return r; +} + +/** + * kdbus_reply_unref() - Decrease reference on kdbus_reply + * @r: The reply, may be %NULL + * + * Return: NULL + */ +struct kdbus_reply *kdbus_reply_unref(struct kdbus_reply *r) +{ + if (r) + kref_put(&r->kref, __kdbus_reply_free); + return NULL; +} + +/** + * kdbus_reply_link() - Link reply object into target connection + * @r: Reply to link + */ +void kdbus_reply_link(struct kdbus_reply *r) +{ + if (WARN_ON(!list_empty(&r->entry))) + return; + + list_add_tail(&r->entry, &r->reply_dst->reply_list); + kdbus_reply_ref(r); +} + +/** + * kdbus_reply_unlink() - Unlink reply object from target connection + * @r: Reply to unlink + */ +void kdbus_reply_unlink(struct kdbus_reply *r) +{ + if (!list_empty(&r->entry)) { + list_del_init(&r->entry); + kdbus_reply_unref(r); + } +} + +/** + * kdbus_sync_reply_wakeup() - Wake a synchronously blocking reply + * @reply: The reply object + * @err: Error code to set on the remote side + * + * Wake up remote peer (method origin) with the appropriate synchronous reply + * code. + */ +void kdbus_sync_reply_wakeup(struct kdbus_reply *reply, int err) +{ + if (WARN_ON(!reply->sync)) + return; + + reply->waiting = false; + reply->err = err; + wake_up_interruptible(&reply->reply_dst->wait); +} + +/** + * kdbus_reply_find() - Find the corresponding reply object + * @replying: The replying connection or NULL + * @reply_dst: The connection the reply will be sent to + * (method origin) + * @cookie: The cookie of the requesting message + * + * Lookup a reply object that should be sent as a reply by + * @replying to @reply_dst with the given cookie. + * + * Callers must take the @reply_dst lock. + * + * Return: the corresponding reply object or NULL if not found + */ +struct kdbus_reply *kdbus_reply_find(struct kdbus_conn *replying, + struct kdbus_conn *reply_dst, + u64 cookie) +{ + struct kdbus_reply *r; + + list_for_each_entry(r, &reply_dst->reply_list, entry) { + if (r->cookie == cookie && + (!replying || r->reply_src == replying)) + return r; + } + + return NULL; +} + +/** + * kdbus_reply_list_scan_work() - Worker callback to scan the replies of a + * connection for exceeded timeouts + * @work: Work struct of the connection to scan + * + * Walk the list of replies stored with a connection and look for entries + * that have exceeded their timeout. If such an entry is found, a timeout + * notification is sent to the waiting peer, and the reply is removed from + * the list. + * + * The work is rescheduled to the nearest timeout found during the list + * iteration. + */ +void kdbus_reply_list_scan_work(struct work_struct *work) +{ + struct kdbus_conn *conn = + container_of(work, struct kdbus_conn, work.work); + struct kdbus_reply *reply, *reply_tmp; + u64 deadline = ~0ULL; + u64 now; + + now = ktime_get_ns(); + + mutex_lock(&conn->lock); + if (!kdbus_conn_active(conn)) { + mutex_unlock(&conn->lock); + return; + } + + list_for_each_entry_safe(reply, reply_tmp, &conn->reply_list, entry) { + /* + * If the reply block is waiting for synchronous I/O, + * the timeout is handled by wait_event_*_timeout(), + * so we don't have to care for it here. + */ + if (reply->sync && !reply->interrupted) + continue; + + WARN_ON(reply->reply_dst != conn); + + if (reply->deadline_ns > now) { + /* remember next timeout */ + if (deadline > reply->deadline_ns) + deadline = reply->deadline_ns; + + continue; + } + + /* + * A zero deadline means the connection died, was + * cleaned up already and the notification was sent. + * Don't send notifications for reply trackers that were + * left in an interrupted syscall state. + */ + if (reply->deadline_ns != 0 && !reply->interrupted) + kdbus_notify_reply_timeout(conn->ep->bus, conn->id, + reply->cookie); + + kdbus_reply_unlink(reply); + } + + /* rearm delayed work with next timeout */ + if (deadline != ~0ULL) + schedule_delayed_work(&conn->work, + nsecs_to_jiffies(deadline - now)); + + mutex_unlock(&conn->lock); + + kdbus_notify_flush(conn->ep->bus); +} diff --git a/ipc/kdbus/reply.h b/ipc/kdbus/reply.h new file mode 100644 index 000000000000..68d52321a917 --- /dev/null +++ b/ipc/kdbus/reply.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_REPLY_H +#define __KDBUS_REPLY_H + +/** + * struct kdbus_reply - an entry of kdbus_conn's list of replies + * @kref: Ref-count of this object + * @entry: The entry of the connection's reply_list + * @reply_src: The connection the reply will be sent from + * @reply_dst: The connection the reply will be sent to + * @queue_entry: The queue entry item that is prepared by the replying + * connection + * @deadline_ns: The deadline of the reply, in nanoseconds + * @cookie: The cookie of the requesting message + * @name_id: ID of the well-known name the original msg was sent to + * @sync: The reply block is waiting for synchronous I/O + * @waiting: The condition to synchronously wait for + * @interrupted: The sync reply was left in an interrupted state + * @err: The error code for the synchronous reply + */ +struct kdbus_reply { + struct kref kref; + struct list_head entry; + struct kdbus_conn *reply_src; + struct kdbus_conn *reply_dst; + struct kdbus_queue_entry *queue_entry; + u64 deadline_ns; + u64 cookie; + u64 name_id; + bool sync:1; + bool waiting:1; + bool interrupted:1; + int err; +}; + +struct kdbus_reply *kdbus_reply_new(struct kdbus_conn *reply_src, + struct kdbus_conn *reply_dst, + const struct kdbus_msg *msg, + struct kdbus_name_entry *name_entry, + bool sync); + +struct kdbus_reply *kdbus_reply_ref(struct kdbus_reply *r); +struct kdbus_reply *kdbus_reply_unref(struct kdbus_reply *r); + +void kdbus_reply_link(struct kdbus_reply *r); +void kdbus_reply_unlink(struct kdbus_reply *r); + +struct kdbus_reply *kdbus_reply_find(struct kdbus_conn *replying, + struct kdbus_conn *reply_dst, + u64 cookie); + +void kdbus_sync_reply_wakeup(struct kdbus_reply *reply, int err); +void kdbus_reply_list_scan_work(struct work_struct *work); + +#endif /* __KDBUS_REPLY_H */ diff --git a/ipc/kdbus/util.c b/ipc/kdbus/util.c new file mode 100644 index 000000000000..72b188330896 --- /dev/null +++ b/ipc/kdbus/util.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <linux/capability.h> +#include <linux/cred.h> +#include <linux/ctype.h> +#include <linux/err.h> +#include <linux/file.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/uaccess.h> +#include <linux/uio.h> +#include <linux/user_namespace.h> + +#include "limits.h" +#include "util.h" + +/** + * kdbus_copy_from_user() - copy aligned data from user-space + * @dest: target buffer in kernel memory + * @user_ptr: user-provided source buffer + * @size: memory size to copy from user + * + * This copies @size bytes from @user_ptr into the kernel, just like + * copy_from_user() does. But we enforce an 8-byte alignment and reject any + * unaligned user-space pointers. + * + * Return: 0 on success, negative error code on failure. + */ +int kdbus_copy_from_user(void *dest, void __user *user_ptr, size_t size) +{ + if (!KDBUS_IS_ALIGNED8((uintptr_t)user_ptr)) + return -EFAULT; + + if (copy_from_user(dest, user_ptr, size)) + return -EFAULT; + + return 0; +} + +/** + * kdbus_verify_uid_prefix() - verify UID prefix of a user-supplied name + * @name: user-supplied name to verify + * @user_ns: user-namespace to act in + * @kuid: Kernel internal uid of user + * + * This verifies that the user-supplied name @name has their UID as prefix. This + * is the default name-spacing policy we enforce on user-supplied names for + * public kdbus entities like buses and endpoints. + * + * The user must supply names prefixed with "<UID>-", whereas the UID is + * interpreted in the user-namespace of the domain. If the user fails to supply + * such a prefixed name, we reject it. + * + * Return: 0 on success, negative error code on failure + */ +int kdbus_verify_uid_prefix(const char *name, struct user_namespace *user_ns, + kuid_t kuid) +{ + uid_t uid; + char prefix[16]; + + /* + * The kuid must have a mapping into the userns of the domain + * otherwise do not allow creation of buses nor endpoints. + */ + uid = from_kuid(user_ns, kuid); + if (uid == (uid_t) -1) + return -EINVAL; + + snprintf(prefix, sizeof(prefix), "%u-", uid); + if (strncmp(name, prefix, strlen(prefix)) != 0) + return -EINVAL; + + return 0; +} + +/** + * kdbus_sanitize_attach_flags() - Sanitize attach flags from user-space + * @flags: Attach flags provided by userspace + * @attach_flags: A pointer where to store the valid attach flags + * + * Convert attach-flags provided by user-space into a valid mask. If the mask + * is invalid, an error is returned. The sanitized attach flags are stored in + * the output parameter. + * + * Return: 0 on success, negative error on failure. + */ +int kdbus_sanitize_attach_flags(u64 flags, u64 *attach_flags) +{ + /* 'any' degrades to 'all' for compatibility */ + if (flags == _KDBUS_ATTACH_ANY) + flags = _KDBUS_ATTACH_ALL; + + /* reject unknown attach flags */ + if (flags & ~_KDBUS_ATTACH_ALL) + return -EINVAL; + + *attach_flags = flags; + return 0; +} + +/** + * kdbus_kvec_set - helper utility to assemble kvec arrays + * @kvec: kvec entry to use + * @src: Source address to set in @kvec + * @len: Number of bytes in @src + * @total_len: Pointer to total length variable + * + * Set @src and @len in @kvec, and increase @total_len by @len. + */ +void kdbus_kvec_set(struct kvec *kvec, void *src, size_t len, u64 *total_len) +{ + kvec->iov_base = src; + kvec->iov_len = len; + *total_len += len; +} + +static const char * const zeros = "\0\0\0\0\0\0\0"; + +/** + * kdbus_kvec_pad - conditionally write a padding kvec + * @kvec: kvec entry to use + * @len: Total length used for kvec array + * + * Check if the current total byte length of the array in @len is aligned to + * 8 bytes. If it isn't, fill @kvec with padding information and increase @len + * by the number of bytes stored in @kvec. + * + * Return: the number of added padding bytes. + */ +size_t kdbus_kvec_pad(struct kvec *kvec, u64 *len) +{ + size_t pad = KDBUS_ALIGN8(*len) - *len; + + if (!pad) + return 0; + + kvec->iov_base = (void *)zeros; + kvec->iov_len = pad; + + *len += pad; + + return pad; +} diff --git a/ipc/kdbus/util.h b/ipc/kdbus/util.h new file mode 100644 index 000000000000..529716669fe7 --- /dev/null +++ b/ipc/kdbus/util.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org> + * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org> + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * Copyright (C) 2013-2015 Linux Foundation + * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#ifndef __KDBUS_UTIL_H +#define __KDBUS_UTIL_H + +#include <linux/dcache.h> +#include <linux/ioctl.h> + +#include <uapi/linux/kdbus.h> + +/* all exported addresses are 64 bit */ +#define KDBUS_PTR(addr) ((void __user *)(uintptr_t)(addr)) + +/* all exported sizes are 64 bit and data aligned to 64 bit */ +#define KDBUS_ALIGN8(s) ALIGN((s), 8) +#define KDBUS_IS_ALIGNED8(s) (IS_ALIGNED(s, 8)) + +/** + * kdbus_member_set_user - write a structure member to user memory + * @_s: Variable to copy from + * @_b: Buffer to write to + * @_t: Structure type + * @_m: Member name in the passed structure + * + * Return: the result of copy_to_user() + */ +#define kdbus_member_set_user(_s, _b, _t, _m) \ +({ \ + u64 __user *_sz = \ + (void __user *)((u8 __user *)(_b) + offsetof(_t, _m)); \ + copy_to_user(_sz, _s, FIELD_SIZEOF(_t, _m)); \ +}) + +/** + * kdbus_strhash - calculate a hash + * @str: String + * + * Return: hash value + */ +static inline unsigned int kdbus_strhash(const char *str) +{ + unsigned long hash = init_name_hash(); + + while (*str) + hash = partial_name_hash(*str++, hash); + + return end_name_hash(hash); +} + +int kdbus_verify_uid_prefix(const char *name, struct user_namespace *user_ns, + kuid_t kuid); +int kdbus_sanitize_attach_flags(u64 flags, u64 *attach_flags); + +int kdbus_copy_from_user(void *dest, void __user *user_ptr, size_t size); + +struct kvec; + +void kdbus_kvec_set(struct kvec *kvec, void *src, size_t len, u64 *total_len); +size_t kdbus_kvec_pad(struct kvec *kvec, u64 *len); + +#endif diff --git a/samples/Kconfig b/samples/Kconfig index 9cb63188d3ef..c7d54862a55a 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -62,6 +62,13 @@ config SAMPLE_KDB Build an example of how to dynamically add the hello command to the kdb shell. +config SAMPLE_KDBUS + bool "Build kdbus API example" + depends on KDBUS + help + Build an example of how the kdbus API can be used from + userspace. + config SAMPLE_RPMSG_CLIENT tristate "Build rpmsg client sample -- loadable modules only" depends on RPMSG && m diff --git a/samples/kdbus/.gitignore b/samples/kdbus/.gitignore new file mode 100644 index 000000000000..ee07d9857086 --- /dev/null +++ b/samples/kdbus/.gitignore @@ -0,0 +1 @@ +kdbus-workers diff --git a/samples/kdbus/Makefile b/samples/kdbus/Makefile new file mode 100644 index 000000000000..137f84272099 --- /dev/null +++ b/samples/kdbus/Makefile @@ -0,0 +1,9 @@ +# kbuild trick to avoid linker error. Can be omitted if a module is built. +obj- := dummy.o + +hostprogs-$(CONFIG_SAMPLE_KDBUS) += kdbus-workers + +always := $(hostprogs-y) + +HOSTCFLAGS_kdbus-workers.o += -I$(objtree)/usr/include +HOSTLOADLIBES_kdbus-workers := -lrt diff --git a/samples/kdbus/kdbus-api.h b/samples/kdbus/kdbus-api.h new file mode 100644 index 000000000000..5ed5907c5cb4 --- /dev/null +++ b/samples/kdbus/kdbus-api.h @@ -0,0 +1,114 @@ +#ifndef KDBUS_API_H +#define KDBUS_API_H + +#include <sys/ioctl.h> +#include <linux/kdbus.h> + +#define KDBUS_ALIGN8(l) (((l) + 7) & ~7) +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(s) KDBUS_ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))(((uint8_t *)item) + KDBUS_ALIGN8((item)->size)) +#define KDBUS_FOREACH(iter, first, _size) \ + for (iter = (first); \ + ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) && \ + ((uint8_t *)(iter) >= (uint8_t *)(first)); \ + iter = (void*)(((uint8_t *)iter) + KDBUS_ALIGN8((iter)->size))) + +static inline int kdbus_cmd_bus_make(int control_fd, struct kdbus_cmd *cmd) +{ + int ret = ioctl(control_fd, KDBUS_CMD_BUS_MAKE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_endpoint_make(int bus_fd, struct kdbus_cmd *cmd) +{ + int ret = ioctl(bus_fd, KDBUS_CMD_ENDPOINT_MAKE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_endpoint_update(int ep_fd, struct kdbus_cmd *cmd) +{ + int ret = ioctl(ep_fd, KDBUS_CMD_ENDPOINT_UPDATE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_hello(int bus_fd, struct kdbus_cmd_hello *cmd) +{ + int ret = ioctl(bus_fd, KDBUS_CMD_HELLO, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_update(int fd, struct kdbus_cmd *cmd) +{ + int ret = ioctl(fd, KDBUS_CMD_UPDATE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_byebye(int conn_fd, struct kdbus_cmd *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_BYEBYE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_free(int conn_fd, struct kdbus_cmd_free *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_FREE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_conn_info(int conn_fd, struct kdbus_cmd_info *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_CONN_INFO, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_bus_creator_info(int conn_fd, struct kdbus_cmd_info *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_BUS_CREATOR_INFO, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_list(int fd, struct kdbus_cmd_list *cmd) +{ + int ret = ioctl(fd, KDBUS_CMD_LIST, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_send(int conn_fd, struct kdbus_cmd_send *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_SEND, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_recv(int conn_fd, struct kdbus_cmd_recv *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_RECV, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_name_acquire(int conn_fd, struct kdbus_cmd *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_NAME_ACQUIRE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_name_release(int conn_fd, struct kdbus_cmd *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_NAME_RELEASE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_match_add(int conn_fd, struct kdbus_cmd_match *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_MATCH_ADD, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +static inline int kdbus_cmd_match_remove(int conn_fd, struct kdbus_cmd_match *cmd) +{ + int ret = ioctl(conn_fd, KDBUS_CMD_MATCH_REMOVE, cmd); + return (ret < 0) ? (errno > 0 ? -errno : -EINVAL) : 0; +} + +#endif /* KDBUS_API_H */ diff --git a/samples/kdbus/kdbus-workers.c b/samples/kdbus/kdbus-workers.c new file mode 100644 index 000000000000..d331e0186899 --- /dev/null +++ b/samples/kdbus/kdbus-workers.c @@ -0,0 +1,1326 @@ +/* + * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com> + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +/* + * Example: Workers + * This program computes prime-numbers based on the sieve of Eratosthenes. The + * master sets up a shared memory region and spawns workers which clear out the + * non-primes. The master reacts to keyboard input and to client-requests to + * control what each worker does. Note that this is in no way meant as efficient + * way to compute primes. It should only serve as example how a master/worker + * concept can be implemented with kdbus used as control messages. + * + * The main process is called the 'master'. It creates a new, private bus which + * will be used between the master and its workers to communicate. The master + * then spawns a fixed number of workers. Whenever a worker dies (detected via + * SIGCHLD), the master spawns a new worker. When done, the master waits for all + * workers to exit, prints a status report and exits itself. + * + * The master process does *not* keep track of its workers. Instead, this + * example implements a PULL model. That is, the master acquires a well-known + * name on the bus which each worker uses to request tasks from the master. If + * there are no more tasks, the master will return an empty task-list, which + * casues a worker to exit immediately. + * + * As tasks can be computationally expensive, we support cancellation. Whenever + * the master process is interrupted, it will drop its well-known name on the + * bus. This causes kdbus to broadcast a name-change notification. The workers + * check for broadcast messages regularly and will exit if they receive one. + * + * This example exists of 4 objects: + * * master: The master object contains the context of the master process. This + * process manages the prime-context, spawns workers and assigns + * prime-ranges to each worker to compute. + * The master itself does not do any prime-computations itself. + * * child: The child object contains the context of a worker. It inherits the + * prime context from its parent (the master) and then creates a new + * bus context to request prime-ranges to compute. + * * prime: The "prime" object is used to abstract how we compute primes. When + * allocated, it prepares a memory region to hold 1 bit for each + * natural number up to a fixed maximum ('MAX_PRIMES'). + * The memory region is backed by a memfd which we share between + * processes. Each worker now gets assigned a range of natural + * numbers which it clears multiples of off the memory region. The + * master process is responsible of distributing all natural numbers + * up to the fixed maximum to its workers. + * * bus: The bus object is an abstraction of the kdbus API. It is pretty + * straightfoward and only manages the connection-fd plus the + * memory-mapped pool in a single object. + * + * This example is in reversed order, which should make it easier to read + * top-down, but requires some forward-declarations. Just ignore those. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/memfd.h> +#include <signal.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/signalfd.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include "kdbus-api.h" + +/* FORWARD DECLARATIONS */ + +#define POOL_SIZE (16 * 1024 * 1024) +#define MAX_PRIMES (2UL << 24) +#define WORKER_COUNT (16) +#define PRIME_STEPS (65536 * 4) + +static const char *arg_busname = "example-workers"; +static const char *arg_modname = "kdbus"; +static const char *arg_master = "org.freedesktop.master"; + +static int err_assert(int r_errno, const char *msg, const char *func, int line, + const char *file) +{ + r_errno = (r_errno != 0) ? -abs(r_errno) : -EFAULT; + if (r_errno < 0) { + errno = -r_errno; + fprintf(stderr, "ERR: %s: %m (%s:%d in %s)\n", + msg, func, line, file); + } + return r_errno; +} + +#define err_r(_r, _msg) err_assert((_r), (_msg), __func__, __LINE__, __FILE__) +#define err(_msg) err_r(errno, (_msg)) + +struct prime; +struct bus; +struct master; +struct child; + +struct prime { + int fd; + uint8_t *area; + size_t max; + size_t done; + size_t status; +}; + +static int prime_new(struct prime **out); +static void prime_free(struct prime *p); +static bool prime_done(struct prime *p); +static void prime_consume(struct prime *p, size_t amount); +static int prime_run(struct prime *p, struct bus *cancel, size_t number); +static void prime_print(struct prime *p); + +struct bus { + int fd; + uint8_t *pool; +}; + +static int bus_open_connection(struct bus **out, uid_t uid, const char *name, + uint64_t recv_flags); +static void bus_close_connection(struct bus *b); +static void bus_poool_free_slice(struct bus *b, uint64_t offset); +static int bus_acquire_name(struct bus *b, const char *name); +static int bus_install_name_loss_match(struct bus *b, const char *name); +static int bus_poll(struct bus *b); +static int bus_make(uid_t uid, const char *name); + +struct master { + size_t n_workers; + size_t max_workers; + + int signal_fd; + int control_fd; + + struct prime *prime; + struct bus *bus; +}; + +static int master_new(struct master **out); +static void master_free(struct master *m); +static int master_run(struct master *m); +static int master_poll(struct master *m); +static int master_handle_stdin(struct master *m); +static int master_handle_signal(struct master *m); +static int master_handle_bus(struct master *m); +static int master_reply(struct master *m, const struct kdbus_msg *msg); +static int master_waitpid(struct master *m); +static int master_spawn(struct master *m); + +struct child { + struct bus *bus; + struct prime *prime; +}; + +static int child_new(struct child **out, struct prime *p); +static void child_free(struct child *c); +static int child_run(struct child *c); + +/* END OF FORWARD DECLARATIONS */ + +/* + * This is the main entrypoint of this example. It is pretty straightforward. We + * create a master object, run the computation, print a status report and then + * exit. Nothing particularly interesting here, so lets look into the master + * object... + */ +int main(int argc, char **argv) +{ + struct master *m = NULL; + int r; + + r = master_new(&m); + if (r < 0) + goto out; + + r = master_run(m); + if (r < 0) + goto out; + + if (0) + prime_print(m->prime); + +out: + master_free(m); + if (r < 0 && r != -EINTR) + fprintf(stderr, "failed\n"); + else + fprintf(stderr, "done\n"); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +/* + * ...this will allocate a new master context. It keeps track of the current + * number of children/workers that are running, manages a signalfd to track + * SIGCHLD, and creates a private kdbus bus. Afterwards, it opens its connection + * to the bus and acquires a well known-name (arg_master). + */ +static int master_new(struct master **out) +{ + struct master *m; + sigset_t smask; + int r; + + m = calloc(1, sizeof(*m)); + if (!m) + return err("cannot allocate master"); + + m->max_workers = WORKER_COUNT; + m->signal_fd = -1; + m->control_fd = -1; + + /* Block SIGINT and SIGCHLD signals */ + sigemptyset(&smask); + sigaddset(&smask, SIGINT); + sigaddset(&smask, SIGCHLD); + sigprocmask(SIG_BLOCK, &smask, NULL); + + m->signal_fd = signalfd(-1, &smask, SFD_CLOEXEC); + if (m->signal_fd < 0) { + r = err("cannot create signalfd"); + goto error; + } + + r = prime_new(&m->prime); + if (r < 0) + goto error; + + m->control_fd = bus_make(getuid(), arg_busname); + if (m->control_fd < 0) { + r = m->control_fd; + goto error; + } + + /* + * Open a bus connection for the master, and require each received + * message to have a metadata item of type KDBUS_ITEM_PIDS attached. + * The current UID is needed to compute the name of the bus node to + * connect to. + */ + r = bus_open_connection(&m->bus, getuid(), + arg_busname, KDBUS_ATTACH_PIDS); + if (r < 0) + goto error; + + /* + * Acquire a well-known name on the bus, so children can address + * messages to the master using KDBUS_DST_ID_NAME as destination-ID + * of messages. + */ + r = bus_acquire_name(m->bus, arg_master); + if (r < 0) + goto error; + + *out = m; + return 0; + +error: + master_free(m); + return r; +} + +/* pretty straightforward destructor of a master object */ +static void master_free(struct master *m) +{ + if (!m) + return; + + bus_close_connection(m->bus); + if (m->control_fd >= 0) + close(m->control_fd); + prime_free(m->prime); + if (m->signal_fd >= 0) + close(m->signal_fd); + free(m); +} + +static int master_run(struct master *m) +{ + int res, r = 0; + + while (!prime_done(m->prime)) { + while (m->n_workers < m->max_workers) { + r = master_spawn(m); + if (r < 0) + break; + } + + r = master_poll(m); + if (r < 0) + break; + } + + if (r < 0) { + bus_close_connection(m->bus); + m->bus = NULL; + } + + while (m->n_workers > 0) { + res = master_poll(m); + if (res < 0) { + if (m->bus) { + bus_close_connection(m->bus); + m->bus = NULL; + } + r = res; + } + } + + return r == -EINTR ? 0 : r; +} + +static int master_poll(struct master *m) +{ + struct pollfd fds[3] = {}; + int r = 0, n = 0; + + /* + * Add stdin, the eventfd and the connection owner file descriptor to + * the pollfd table, and handle incoming traffic on the latter in + * master_handle_bus(). + */ + fds[n].fd = STDIN_FILENO; + fds[n++].events = POLLIN; + fds[n].fd = m->signal_fd; + fds[n++].events = POLLIN; + if (m->bus) { + fds[n].fd = m->bus->fd; + fds[n++].events = POLLIN; + } + + r = poll(fds, n, -1); + if (r < 0) + return err("poll() failed"); + + if (fds[0].revents & POLLIN) + r = master_handle_stdin(m); + else if (fds[0].revents) + r = err("ERR/HUP on stdin"); + if (r < 0) + return r; + + if (fds[1].revents & POLLIN) + r = master_handle_signal(m); + else if (fds[1].revents) + r = err("ERR/HUP on signalfd"); + if (r < 0) + return r; + + if (fds[2].revents & POLLIN) + r = master_handle_bus(m); + else if (fds[2].revents) + r = err("ERR/HUP on bus"); + + return r; +} + +static int master_handle_stdin(struct master *m) +{ + char buf[128]; + ssize_t l; + int r = 0; + + l = read(STDIN_FILENO, buf, sizeof(buf)); + if (l < 0) + return err("cannot read stdin"); + if (l == 0) + return err_r(-EINVAL, "EOF on stdin"); + + while (l-- > 0) { + switch (buf[l]) { + case 'q': + /* quit */ + r = -EINTR; + break; + case '\n': + case ' ': + /* ignore */ + break; + default: + if (isgraph(buf[l])) + fprintf(stderr, "invalid input '%c'\n", buf[l]); + else + fprintf(stderr, "invalid input 0x%x\n", buf[l]); + break; + } + } + + return r; +} + +static int master_handle_signal(struct master *m) +{ + struct signalfd_siginfo val; + ssize_t l; + + l = read(m->signal_fd, &val, sizeof(val)); + if (l < 0) + return err("cannot read signalfd"); + if (l != sizeof(val)) + return err_r(-EINVAL, "invalid data from signalfd"); + + switch (val.ssi_signo) { + case SIGCHLD: + return master_waitpid(m); + case SIGINT: + return err_r(-EINTR, "interrupted"); + default: + return err_r(-EINVAL, "caught invalid signal"); + } +} + +static int master_handle_bus(struct master *m) +{ + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + const struct kdbus_msg *msg = NULL; + const struct kdbus_item *item; + const struct kdbus_vec *vec = NULL; + int r = 0; + + /* + * To receive a message, the KDBUS_CMD_RECV ioctl is used. + * It takes an argument of type 'struct kdbus_cmd_recv', which + * will contain information on the received message when the call + * returns. See kdbus.message(7). + */ + r = kdbus_cmd_recv(m->bus->fd, &recv); + /* + * EAGAIN is returned when there is no message waiting on this + * connection. This is not an error - simply bail out. + */ + if (r == -EAGAIN) + return 0; + if (r < 0) + return err_r(r, "cannot receive message"); + + /* + * Messages received by a connection are stored inside the connection's + * pool, at an offset that has been returned in the 'recv' command + * struct above. The value describes the relative offset from the + * start address of the pool. A message is described with + * 'struct kdbus_msg'. See kdbus.message(7). + */ + msg = (void *)(m->bus->pool + recv.msg.offset); + + /* + * A messages describes its actual payload in an array of items. + * KDBUS_FOREACH() is a simple iterator that walks such an array. + * struct kdbus_msg has a field to denote its total size, which is + * needed to determine the number of items in the array. + */ + KDBUS_FOREACH(item, msg->items, + msg->size - offsetof(struct kdbus_msg, items)) { + /* + * An item of type PAYLOAD_OFF describes in-line memory + * stored in the pool at a described offset. That offset is + * relative to the start address of the message header. + * This example program only expects one single item of that + * type, remembers the struct kdbus_vec member of the item + * when it sees it, and bails out if there is more than one + * of them. + */ + if (item->type == KDBUS_ITEM_PAYLOAD_OFF) { + if (vec) { + r = err_r(-EEXIST, + "message with multiple vecs"); + break; + } + vec = &item->vec; + if (vec->size != 1) { + r = err_r(-EINVAL, "invalid message size"); + break; + } + + /* + * MEMFDs are transported as items of type PAYLOAD_MEMFD. + * If such an item is attached, a new file descriptor was + * installed into the task when KDBUS_CMD_RECV was called, and + * its number is stored in item->memfd.fd. + * Implementers *must* handle this item type and close the + * file descriptor when no longer needed in order to prevent + * file descriptor exhaustion. This example program just bails + * out with an error in this case, as memfds are not expected + * in this context. + */ + } else if (item->type == KDBUS_ITEM_PAYLOAD_MEMFD) { + r = err_r(-EINVAL, "message with memfd"); + break; + } + } + if (r < 0) + goto exit; + if (!vec) { + r = err_r(-EINVAL, "empty message"); + goto exit; + } + + switch (*((const uint8_t *)msg + vec->offset)) { + case 'r': { + r = master_reply(m, msg); + break; + } + default: + r = err_r(-EINVAL, "invalid message type"); + break; + } + +exit: + /* + * We are done with the memory slice that was given to us through + * recv.msg.offset. Tell the kernel it can use it for other content + * in the future. See kdbus.pool(7). + */ + bus_poool_free_slice(m->bus, recv.msg.offset); + return r; +} + +static int master_reply(struct master *m, const struct kdbus_msg *msg) +{ + struct kdbus_cmd_send cmd; + struct kdbus_item *item; + struct kdbus_msg *reply; + size_t size, status, p[2]; + int r; + + /* + * This functions sends a message over kdbus. To do this, it uses the + * KDBUS_CMD_SEND ioctl, which takes a command struct argument of type + * 'struct kdbus_cmd_send'. This struct stores a pointer to the actual + * message to send. See kdbus.message(7). + */ + p[0] = m->prime->done; + p[1] = prime_done(m->prime) ? 0 : PRIME_STEPS; + + size = sizeof(*reply); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + /* Prepare the message to send */ + reply = alloca(size); + memset(reply, 0, size); + reply->size = size; + + /* Each message has a cookie that can be used to send replies */ + reply->cookie = 1; + + /* The payload_type is arbitrary, but it must be non-zero */ + reply->payload_type = 0xdeadbeef; + + /* + * We are sending a reply. Let the kernel know the cookie of the + * message we are replying to. + */ + reply->cookie_reply = msg->cookie; + + /* + * Messages can either be directed to a well-known name (stored as + * string) or to a unique name (stored as number). This example does + * the latter. If the message would be directed to a well-known name + * instead, the message's dst_id field would be set to + * KDBUS_DST_ID_NAME, and the name would be attaches in an item of type + * KDBUS_ITEM_DST_NAME. See below for an example, and also refer to + * kdbus.message(7). + */ + reply->dst_id = msg->src_id; + + /* Our message has exactly one item to store its payload */ + item = reply->items; + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)p; + item->vec.size = sizeof(p); + + /* + * Now prepare the command struct, and reference the message we want + * to send. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)reply; + + /* + * Finally, employ the command on the connection owner + * file descriptor. + */ + r = kdbus_cmd_send(m->bus->fd, &cmd); + if (r < 0) + return err_r(r, "cannot send reply"); + + if (p[1]) { + prime_consume(m->prime, p[1]); + status = m->prime->done * 10000 / m->prime->max; + if (status != m->prime->status) { + m->prime->status = status; + fprintf(stderr, "status: %7.3lf%%\n", + (double)status / 100); + } + } + + return 0; +} + +static int master_waitpid(struct master *m) +{ + pid_t pid; + int r; + + while ((pid = waitpid(-1, &r, WNOHANG)) > 0) { + if (m->n_workers > 0) + --m->n_workers; + if (!WIFEXITED(r)) + r = err_r(-EINVAL, "child died unexpectedly"); + else if (WEXITSTATUS(r) != 0) + r = err_r(-WEXITSTATUS(r), "child failed"); + } + + return r; +} + +static int master_spawn(struct master *m) +{ + struct child *c = NULL; + struct prime *p = NULL; + pid_t pid; + int r; + + /* Spawn off one child and call child_run() inside it */ + + pid = fork(); + if (pid < 0) + return err("cannot fork"); + if (pid > 0) { + /* parent */ + ++m->n_workers; + return 0; + } + + /* child */ + + p = m->prime; + m->prime = NULL; + master_free(m); + + r = child_new(&c, p); + if (r < 0) + goto exit; + + r = child_run(c); + +exit: + child_free(c); + exit(abs(r)); +} + +static int child_new(struct child **out, struct prime *p) +{ + struct child *c; + int r; + + c = calloc(1, sizeof(*c)); + if (!c) + return err("cannot allocate child"); + + c->prime = p; + + /* + * Open a connection to the bus and require each received message to + * carry a list of the well-known names the sendind connection currently + * owns. The current UID is needed in order to determine the name of the + * bus node to connect to. + */ + r = bus_open_connection(&c->bus, getuid(), + arg_busname, KDBUS_ATTACH_NAMES); + if (r < 0) + goto error; + + /* + * Install a kdbus match so the child's connection gets notified when + * the master loses its well-known name. + */ + r = bus_install_name_loss_match(c->bus, arg_master); + if (r < 0) + goto error; + + *out = c; + return 0; + +error: + child_free(c); + return r; +} + +static void child_free(struct child *c) +{ + if (!c) + return; + + bus_close_connection(c->bus); + prime_free(c->prime); + free(c); +} + +static int child_run(struct child *c) +{ + struct kdbus_cmd_send cmd; + struct kdbus_item *item; + struct kdbus_vec *vec = NULL; + struct kdbus_msg *msg; + struct timespec spec; + size_t n, steps, size; + int r = 0; + + /* + * Let's send a message to the master and ask for work. To do this, + * we use the KDBUS_CMD_SEND ioctl, which takes an argument of type + * 'struct kdbus_cmd_send'. This struct stores a pointer to the actual + * message to send. See kdbus.message(7). + */ + size = sizeof(*msg); + size += KDBUS_ITEM_SIZE(strlen(arg_master) + 1); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = alloca(size); + memset(msg, 0, size); + msg->size = size; + + /* + * Tell the kernel that we expect a reply to this message. This means + * that + * + * a) The remote peer will gain temporary permission to talk to us + * even if it would not be allowed to normally. + * + * b) A timeout value is required. + * + * For asynchronous send commands, if no reply is received, we will + * get a kernel notification with an item of type + * KDBUS_ITEM_REPLY_TIMEOUT attached. + * + * For synchronous send commands (which this example does), the + * ioctl will block until a reply is received or the timeout is + * exceeded. + */ + msg->flags = KDBUS_MSG_EXPECT_REPLY; + + /* Set our cookie. Replies must use this cookie to send their reply. */ + msg->cookie = 1; + + /* The payload_type is arbitrary, but it must be non-zero */ + msg->payload_type = 0xdeadbeef; + + /* + * We are sending our message to the current owner of a well-known + * name. This makes an item of type KDBUS_ITEM_DST_NAME mandatory. + */ + msg->dst_id = KDBUS_DST_ID_NAME; + + /* + * Set the reply timeout to 5 seconds. Timeouts are always set in + * absolute timestamps, based con CLOCK_MONOTONIC. See kdbus.message(7). + */ + clock_gettime(CLOCK_MONOTONIC_COARSE, &spec); + msg->timeout_ns += (5 + spec.tv_sec) * 1000ULL * 1000ULL * 1000ULL; + msg->timeout_ns += spec.tv_nsec; + + /* + * Fill the appended items. First, set the well-known name of the + * destination we want to talk to. + */ + item = msg->items; + item->type = KDBUS_ITEM_DST_NAME; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(arg_master) + 1; + strcpy(item->str, arg_master); + + /* + * The 2nd item contains a vector to memory we want to send. It + * can be content of any type. In our case, we're sending a one-byte + * string only. The memory referenced by this item will be copied into + * the pool of the receiver connection, and does not need to be valid + * after the command is employed. + */ + item = KDBUS_ITEM_NEXT(item); + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)"r"; + item->vec.size = 1; + + /* Set up the command struct and reference the message we prepared */ + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + /* + * The send commands knows a mode in which it will block until a + * reply to a message is received. This example uses that mode. + * The pool offset to the received reply will be stored in the command + * struct after the send command returned. See below. + */ + cmd.flags = KDBUS_SEND_SYNC_REPLY; + + /* + * Finally, employ the command on the connection owner + * file descriptor. + */ + r = kdbus_cmd_send(c->bus->fd, &cmd); + if (r == -ESRCH || r == -EPIPE || r == -ECONNRESET) + return 0; + if (r < 0) + return err_r(r, "cannot send request to master"); + + /* + * The command was sent with the KDBUS_SEND_SYNC_REPLY flag set, + * and returned successfully, which means that cmd.reply.offset now + * points to a message inside our connection's pool where the reply + * is found. This is equivalent to receiving the reply with + * KDBUS_CMD_RECV, but it doesn't require waiting for the reply with + * poll() and also saves the ioctl to receive the message. + */ + msg = (void *)(c->bus->pool + cmd.reply.offset); + + /* + * A messages describes its actual payload in an array of items. + * KDBUS_FOREACH() is a simple iterator that walks such an array. + * struct kdbus_msg has a field to denote its total size, which is + * needed to determine the number of items in the array. + */ + KDBUS_FOREACH(item, msg->items, + msg->size - offsetof(struct kdbus_msg, items)) { + /* + * An item of type PAYLOAD_OFF describes in-line memory + * stored in the pool at a described offset. That offset is + * relative to the start address of the message header. + * This example program only expects one single item of that + * type, remembers the struct kdbus_vec member of the item + * when it sees it, and bails out if there is more than one + * of them. + */ + if (item->type == KDBUS_ITEM_PAYLOAD_OFF) { + if (vec) { + r = err_r(-EEXIST, + "message with multiple vecs"); + break; + } + vec = &item->vec; + if (vec->size != 2 * sizeof(size_t)) { + r = err_r(-EINVAL, "invalid message size"); + break; + } + /* + * MEMFDs are transported as items of type PAYLOAD_MEMFD. + * If such an item is attached, a new file descriptor was + * installed into the task when KDBUS_CMD_RECV was called, and + * its number is stored in item->memfd.fd. + * Implementers *must* handle this item type close the + * file descriptor when no longer needed in order to prevent + * file descriptor exhaustion. This example program just bails + * out with an error in this case, as memfds are not expected + * in this context. + */ + } else if (item->type == KDBUS_ITEM_PAYLOAD_MEMFD) { + r = err_r(-EINVAL, "message with memfd"); + break; + } + } + if (r < 0) + goto exit; + if (!vec) { + r = err_r(-EINVAL, "empty message"); + goto exit; + } + + n = ((size_t *)((const uint8_t *)msg + vec->offset))[0]; + steps = ((size_t *)((const uint8_t *)msg + vec->offset))[1]; + + while (steps-- > 0) { + ++n; + r = prime_run(c->prime, c->bus, n); + if (r < 0) + break; + r = bus_poll(c->bus); + if (r != 0) { + r = r < 0 ? r : -EINTR; + break; + } + } + +exit: + /* + * We are done with the memory slice that was given to us through + * cmd.reply.offset. Tell the kernel it can use it for other content + * in the future. See kdbus.pool(7). + */ + bus_poool_free_slice(c->bus, cmd.reply.offset); + return r; +} + +/* + * Prime Computation + * + */ + +static int prime_new(struct prime **out) +{ + struct prime *p; + int r; + + p = calloc(1, sizeof(*p)); + if (!p) + return err("cannot allocate prime memory"); + + p->fd = -1; + p->area = MAP_FAILED; + p->max = MAX_PRIMES; + + /* + * Prepare and map a memfd to store the bit-fields for the number + * ranges we want to perform the prime detection on. + */ + p->fd = syscall(__NR_memfd_create, "prime-area", MFD_CLOEXEC); + if (p->fd < 0) { + r = err("cannot create memfd"); + goto error; + } + + r = ftruncate(p->fd, p->max / 8 + 1); + if (r < 0) { + r = err("cannot ftruncate area"); + goto error; + } + + p->area = mmap(NULL, p->max / 8 + 1, PROT_READ | PROT_WRITE, + MAP_SHARED, p->fd, 0); + if (p->area == MAP_FAILED) { + r = err("cannot mmap memfd"); + goto error; + } + + *out = p; + return 0; + +error: + prime_free(p); + return r; +} + +static void prime_free(struct prime *p) +{ + if (!p) + return; + + if (p->area != MAP_FAILED) + munmap(p->area, p->max / 8 + 1); + if (p->fd >= 0) + close(p->fd); + free(p); +} + +static bool prime_done(struct prime *p) +{ + return p->done >= p->max; +} + +static void prime_consume(struct prime *p, size_t amount) +{ + p->done += amount; +} + +static int prime_run(struct prime *p, struct bus *cancel, size_t number) +{ + size_t i, n = 0; + int r; + + if (number < 2 || number > 65535) + return 0; + + for (i = number * number; + i < p->max && i > number; + i += number) { + p->area[i / 8] |= 1 << (i % 8); + + if (!(++n % (1 << 20))) { + r = bus_poll(cancel); + if (r != 0) + return r < 0 ? r : -EINTR; + } + } + + return 0; +} + +static void prime_print(struct prime *p) +{ + size_t i, l = 0; + + fprintf(stderr, "PRIMES:"); + for (i = 0; i < p->max; ++i) { + if (!(p->area[i / 8] & (1 << (i % 8)))) + fprintf(stderr, "%c%7zu", !(l++ % 16) ? '\n' : ' ', i); + } + fprintf(stderr, "\nEND\n"); +} + +static int bus_open_connection(struct bus **out, uid_t uid, const char *name, + uint64_t recv_flags) +{ + struct kdbus_cmd_hello hello; + char path[128]; + struct bus *b; + int r; + + /* + * The 'bus' object is our representation of a kdbus connection which + * stores two details: the connection owner file descriptor, and the + * mmap()ed memory of its associated pool. See kdbus.connection(7) and + * kdbus.pool(7). + */ + b = calloc(1, sizeof(*b)); + if (!b) + return err("cannot allocate bus memory"); + + b->fd = -1; + b->pool = MAP_FAILED; + + /* Compute the name of the bus node to connect to. */ + snprintf(path, sizeof(path), "/sys/fs/%s/%lu-%s/bus", + arg_modname, (unsigned long)uid, name); + b->fd = open(path, O_RDWR | O_CLOEXEC); + if (b->fd < 0) { + r = err("cannot open bus"); + goto error; + } + + /* + * To make a connection to the bus, the KDBUS_CMD_HELLO ioctl is used. + * It takes an argument of type 'struct kdbus_cmd_hello'. + */ + memset(&hello, 0, sizeof(hello)); + hello.size = sizeof(hello); + + /* + * Specify a mask of metadata attach flags, describing metadata items + * that this new connection allows to be sent. + */ + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + + /* + * Specify a mask of metadata attach flags, describing metadata items + * that this new connection wants to be receive along with each message. + */ + hello.attach_flags_recv = recv_flags; + + /* + * A connection may choose the size of its pool, but the number has to + * comply with two rules: a) it must be greater than 0, and b) it must + * be a mulitple of PAGE_SIZE. See kdbus.pool(7). + */ + hello.pool_size = POOL_SIZE; + + /* + * Now employ the command on the file descriptor opened above. + * This command will turn the file descriptor into a connection-owner + * file descriptor that controls the life-time of the connection; once + * it's closed, the connection is shut down. + */ + r = kdbus_cmd_hello(b->fd, &hello); + if (r < 0) { + err_r(r, "HELLO failed"); + goto error; + } + + bus_poool_free_slice(b, hello.offset); + + /* + * Map the pool of the connection. Its size has been set in the + * command struct above. See kdbus.pool(7). + */ + b->pool = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, b->fd, 0); + if (b->pool == MAP_FAILED) { + r = err("cannot mmap pool"); + goto error; + } + + *out = b; + return 0; + +error: + bus_close_connection(b); + return r; +} + +static void bus_close_connection(struct bus *b) +{ + if (!b) + return; + + /* + * A bus connection is closed by simply calling close() on the + * connection owner file descriptor. The unique name and all owned + * well-known names of the conneciton will disappear. + * See kdbus.connection(7). + */ + if (b->pool != MAP_FAILED) + munmap(b->pool, POOL_SIZE); + if (b->fd >= 0) + close(b->fd); + free(b); +} + +static void bus_poool_free_slice(struct bus *b, uint64_t offset) +{ + struct kdbus_cmd_free cmd = { + .size = sizeof(cmd), + .offset = offset, + }; + int r; + + /* + * Once we're done with a piece of pool memory that was returned + * by a command, we have to call the KDBUS_CMD_FREE ioctl on it so it + * can be reused. The command takes an argument of type + * 'struct kdbus_cmd_free', in which the pool offset of the slice to + * free is stored. The ioctl is employed on the connection owner + * file descriptor. See kdbus.pool(7), + */ + r = kdbus_cmd_free(b->fd, &cmd); + if (r < 0) + err_r(r, "cannot free pool slice"); +} + +static int bus_acquire_name(struct bus *b, const char *name) +{ + struct kdbus_item *item; + struct kdbus_cmd *cmd; + size_t size; + int r; + + /* + * This function acquires a well-known name on the bus through the + * KDBUS_CMD_NAME_ACQUIRE ioctl. This ioctl takes an argument of type + * 'struct kdbus_cmd', which is assembled below. See kdbus.name(7). + */ + size = sizeof(*cmd); + size += KDBUS_ITEM_SIZE(strlen(name) + 1); + + cmd = alloca(size); + memset(cmd, 0, size); + cmd->size = size; + + /* + * The command requires an item of type KDBUS_ITEM_NAME, and its + * content must be a valid bus name. + */ + item = cmd->items; + item->type = KDBUS_ITEM_NAME; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + strcpy(item->str, name); + + /* + * Employ the command on the connection owner file descriptor. + */ + r = kdbus_cmd_name_acquire(b->fd, cmd); + if (r < 0) + return err_r(r, "cannot acquire name"); + + return 0; +} + +static int bus_install_name_loss_match(struct bus *b, const char *name) +{ + struct kdbus_cmd_match *match; + struct kdbus_item *item; + size_t size; + int r; + + /* + * In order to install a match for signal messages, we have to + * assemble a 'struct kdbus_cmd_match' and use it along with the + * KDBUS_CMD_MATCH_ADD ioctl. See kdbus.match(7). + */ + size = sizeof(*match); + size += KDBUS_ITEM_SIZE(sizeof(item->name_change) + strlen(name) + 1); + + match = alloca(size); + memset(match, 0, size); + match->size = size; + + /* + * A match is comprised of many 'rules', each of which describes a + * mandatory detail of the message. All rules of a match must be + * satified in order to make a message pass. + */ + item = match->items; + + /* + * In this case, we're interested in notifications that inform us + * about a well-known name being removed from the bus. + */ + item->type = KDBUS_ITEM_NAME_REMOVE; + item->size = KDBUS_ITEM_HEADER_SIZE + + sizeof(item->name_change) + strlen(name) + 1; + + /* + * We could limit the match further and require a specific unique-ID + * to be the new or the old owner of the name. In this case, however, + * we don't, and allow 'any' id. + */ + item->name_change.old_id.id = KDBUS_MATCH_ID_ANY; + item->name_change.new_id.id = KDBUS_MATCH_ID_ANY; + + /* Copy in the well-known name we're interested in */ + strcpy(item->name_change.name, name); + + /* + * Add the match through the KDBUS_CMD_MATCH_ADD ioctl, employed on + * the connection owner fd. + */ + r = kdbus_cmd_match_add(b->fd, match); + if (r < 0) + return err_r(r, "cannot add match"); + + return 0; +} + +static int bus_poll(struct bus *b) +{ + struct pollfd fds[1] = {}; + int r; + + /* + * A connection endpoint supports poll() and will wake-up the + * task with POLLIN set once a message has arrived. + */ + fds[0].fd = b->fd; + fds[0].events = POLLIN; + r = poll(fds, sizeof(fds) / sizeof(*fds), 0); + if (r < 0) + return err("cannot poll bus"); + return !!(fds[0].revents & POLLIN); +} + +static int bus_make(uid_t uid, const char *name) +{ + struct kdbus_item *item; + struct kdbus_cmd *make; + char path[128], busname[128]; + size_t size; + int r, fd; + + /* + * Compute the full path to the 'control' node. 'arg_modname' may be + * set to a different value than 'kdbus' for development purposes. + * The 'control' node is the primary entry point to kdbus that must be + * used in order to create a bus. See kdbus(7) and kdbus.bus(7). + */ + snprintf(path, sizeof(path), "/sys/fs/%s/control", arg_modname); + + /* + * Compute the bus name. A valid bus name must always be prefixed with + * the EUID of the currently running process in order to avoid name + * conflicts. See kdbus.bus(7). + */ + snprintf(busname, sizeof(busname), "%lu-%s", (unsigned long)uid, name); + + fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) + return err("cannot open control file"); + + /* + * The KDBUS_CMD_BUS_MAKE ioctl takes an argument of type + * 'struct kdbus_cmd', and expects at least two items attached to + * it: one to decribe the bloom parameters to be propagated to + * connections of the bus, and the name of the bus that was computed + * above. Assemble this struct now, and fill it with values. + */ + size = sizeof(*make); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_parameter)); + size += KDBUS_ITEM_SIZE(strlen(busname) + 1); + + make = alloca(size); + memset(make, 0, size); + make->size = size; + + /* + * Each item has a 'type' and 'size' field, and must be stored at an + * 8-byte aligned address. The KDBUS_ITEM_NEXT macro is used to advance + * the pointer. See kdbus.item(7) for more details. + */ + item = make->items; + item->type = KDBUS_ITEM_BLOOM_PARAMETER; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(item->bloom_parameter); + item->bloom_parameter.size = 8; + item->bloom_parameter.n_hash = 1; + + /* The name of the new bus is stored in the next item. */ + item = KDBUS_ITEM_NEXT(item); + item->type = KDBUS_ITEM_MAKE_NAME; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(busname) + 1; + strcpy(item->str, busname); + + /* + * Now create the bus via the KDBUS_CMD_BUS_MAKE ioctl and return the + * fd that was used back to the caller of this function. This fd is now + * called a 'bus owner file descriptor', and it controls the life-time + * of the newly created bus; once the file descriptor is closed, the + * bus goes away, and all connections are shut down. See kdbus.bus(7). + */ + r = kdbus_cmd_bus_make(fd, make); + if (r < 0) { + err_r(r, "cannot make bus"); + close(fd); + return r; + } + + return fd; +} diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 10b89f5b9af7..c6e4cb96a2be 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -13,6 +13,7 @@ TARGETS += gpio TARGETS += intel_pstate TARGETS += ipc TARGETS += kcmp +#TARGETS += kdbus TARGETS += lib TARGETS += membarrier TARGETS += memfd diff --git a/tools/testing/selftests/kdbus/Makefile b/tools/testing/selftests/kdbus/Makefile new file mode 100644 index 000000000000..0a5afe3f7b6d --- /dev/null +++ b/tools/testing/selftests/kdbus/Makefile @@ -0,0 +1,49 @@ +CFLAGS += -I../../../../usr/include/ +CFLAGS += -I../../../../samples/kdbus/ +CFLAGS += -I../../../../include/uapi/ +CFLAGS += -std=gnu99 +CFLAGS += -DKBUILD_MODNAME=\"kdbus\" -D_GNU_SOURCE +LDLIBS = -pthread -lcap -lm + +OBJS= \ + kdbus-enum.o \ + kdbus-util.o \ + kdbus-test.o \ + kdbus-test.o \ + test-activator.o \ + test-attach-flags.o \ + test-benchmark.o \ + test-bus.o \ + test-chat.o \ + test-connection.o \ + test-daemon.o \ + test-endpoint.o \ + test-fd.o \ + test-free.o \ + test-match.o \ + test-message.o \ + test-metadata-ns.o \ + test-monitor.o \ + test-names.o \ + test-policy.o \ + test-policy-ns.o \ + test-policy-priv.o \ + test-send.o \ + test-sync.o \ + test-timeout.o + +all: kdbus-test + +include ../lib.mk + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +kdbus-test: $(OBJS) + $(CC) $(CFLAGS) $^ $(LDLIBS) -o $@ + +run_tests: + ./kdbus-test --tap + +clean: + rm -f *.o kdbus-test diff --git a/tools/testing/selftests/kdbus/kdbus-enum.c b/tools/testing/selftests/kdbus/kdbus-enum.c new file mode 100644 index 000000000000..4f1e5797895f --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-enum.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> + +#include "kdbus-util.h" +#include "kdbus-enum.h" + +struct kdbus_enum_table { + long long id; + const char *name; +}; + +#define TABLE(what) static struct kdbus_enum_table kdbus_table_##what[] +#define ENUM(_id) { .id = _id, .name = STRINGIFY(_id) } +#define LOOKUP(what) \ + const char *enum_##what(long long id) \ + { \ + for (size_t i = 0; i < ELEMENTSOF(kdbus_table_##what); i++) \ + if (id == kdbus_table_##what[i].id) \ + return kdbus_table_##what[i].name; \ + return "UNKNOWN"; \ + } + +TABLE(CMD) = { + ENUM(KDBUS_CMD_BUS_MAKE), + ENUM(KDBUS_CMD_ENDPOINT_MAKE), + ENUM(KDBUS_CMD_HELLO), + ENUM(KDBUS_CMD_SEND), + ENUM(KDBUS_CMD_RECV), + ENUM(KDBUS_CMD_LIST), + ENUM(KDBUS_CMD_NAME_RELEASE), + ENUM(KDBUS_CMD_CONN_INFO), + ENUM(KDBUS_CMD_MATCH_ADD), + ENUM(KDBUS_CMD_MATCH_REMOVE), +}; +LOOKUP(CMD); + +TABLE(MSG) = { + ENUM(_KDBUS_ITEM_NULL), + ENUM(KDBUS_ITEM_PAYLOAD_VEC), + ENUM(KDBUS_ITEM_PAYLOAD_OFF), + ENUM(KDBUS_ITEM_PAYLOAD_MEMFD), + ENUM(KDBUS_ITEM_FDS), + ENUM(KDBUS_ITEM_BLOOM_PARAMETER), + ENUM(KDBUS_ITEM_BLOOM_FILTER), + ENUM(KDBUS_ITEM_DST_NAME), + ENUM(KDBUS_ITEM_MAKE_NAME), + ENUM(KDBUS_ITEM_ATTACH_FLAGS_SEND), + ENUM(KDBUS_ITEM_ATTACH_FLAGS_RECV), + ENUM(KDBUS_ITEM_ID), + ENUM(KDBUS_ITEM_NAME), + ENUM(KDBUS_ITEM_TIMESTAMP), + ENUM(KDBUS_ITEM_CREDS), + ENUM(KDBUS_ITEM_PIDS), + ENUM(KDBUS_ITEM_AUXGROUPS), + ENUM(KDBUS_ITEM_OWNED_NAME), + ENUM(KDBUS_ITEM_TID_COMM), + ENUM(KDBUS_ITEM_PID_COMM), + ENUM(KDBUS_ITEM_EXE), + ENUM(KDBUS_ITEM_CMDLINE), + ENUM(KDBUS_ITEM_CGROUP), + ENUM(KDBUS_ITEM_CAPS), + ENUM(KDBUS_ITEM_SECLABEL), + ENUM(KDBUS_ITEM_AUDIT), + ENUM(KDBUS_ITEM_CONN_DESCRIPTION), + ENUM(KDBUS_ITEM_NAME_ADD), + ENUM(KDBUS_ITEM_NAME_REMOVE), + ENUM(KDBUS_ITEM_NAME_CHANGE), + ENUM(KDBUS_ITEM_ID_ADD), + ENUM(KDBUS_ITEM_ID_REMOVE), + ENUM(KDBUS_ITEM_REPLY_TIMEOUT), + ENUM(KDBUS_ITEM_REPLY_DEAD), +}; +LOOKUP(MSG); + +TABLE(PAYLOAD) = { + ENUM(KDBUS_PAYLOAD_KERNEL), + ENUM(KDBUS_PAYLOAD_DBUS), +}; +LOOKUP(PAYLOAD); diff --git a/tools/testing/selftests/kdbus/kdbus-enum.h b/tools/testing/selftests/kdbus/kdbus-enum.h new file mode 100644 index 000000000000..a67cec3512a7 --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-enum.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ +#pragma once + +const char *enum_CMD(long long id); +const char *enum_MSG(long long id); +const char *enum_MATCH(long long id); +const char *enum_PAYLOAD(long long id); diff --git a/tools/testing/selftests/kdbus/kdbus-test.c b/tools/testing/selftests/kdbus/kdbus-test.c new file mode 100644 index 000000000000..356a1c7fdf72 --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-test.c @@ -0,0 +1,956 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <time.h> +#include <unistd.h> +#include <stdint.h> +#include <assert.h> +#include <getopt.h> +#include <stdbool.h> +#include <signal.h> +#include <sys/mount.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <sys/syscall.h> +#include <sys/eventfd.h> +#include <linux/sched.h> + +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +enum { + TEST_CREATE_BUS = 1 << 0, + TEST_CREATE_CONN = 1 << 1, + TEST_CREATE_CAN_FAIL = 1 << 2, +}; + +struct kdbus_test { + const char *name; + const char *desc; + int (*func)(struct kdbus_test_env *env); + unsigned int flags; +}; + +struct kdbus_test_args { + bool mntns; + bool pidns; + bool userns; + char *uid_map; + char *gid_map; + int loop; + int wait; + int fork; + int tap_output; + char *module; + char *root; + char *test; + char *busname; + char *mask_param_path; +}; + +static const struct kdbus_test tests[] = { + { + .name = "bus-make", + .desc = "bus make functions", + .func = kdbus_test_bus_make, + .flags = 0, + }, + { + .name = "hello", + .desc = "the HELLO command", + .func = kdbus_test_hello, + .flags = TEST_CREATE_BUS, + }, + { + .name = "byebye", + .desc = "the BYEBYE command", + .func = kdbus_test_byebye, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "chat", + .desc = "a chat pattern", + .func = kdbus_test_chat, + .flags = TEST_CREATE_BUS, + }, + { + .name = "daemon", + .desc = "a simple daemon", + .func = kdbus_test_daemon, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "fd-passing", + .desc = "file descriptor passing", + .func = kdbus_test_fd_passing, + .flags = TEST_CREATE_BUS, + }, + { + .name = "endpoint", + .desc = "custom endpoint", + .func = kdbus_test_custom_endpoint, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "monitor", + .desc = "monitor functionality", + .func = kdbus_test_monitor, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "name-basics", + .desc = "basic name registry functions", + .func = kdbus_test_name_basic, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "name-conflict", + .desc = "name registry conflict details", + .func = kdbus_test_name_conflict, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "name-queue", + .desc = "queuing of names", + .func = kdbus_test_name_queue, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "message-basic", + .desc = "basic message handling", + .func = kdbus_test_message_basic, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "message-prio", + .desc = "handling of messages with priority", + .func = kdbus_test_message_prio, + .flags = TEST_CREATE_BUS, + }, + { + .name = "message-quota", + .desc = "message quotas are enforced", + .func = kdbus_test_message_quota, + .flags = TEST_CREATE_BUS, + }, + { + .name = "memory-access", + .desc = "memory access", + .func = kdbus_test_memory_access, + .flags = TEST_CREATE_BUS, + }, + { + .name = "timeout", + .desc = "timeout", + .func = kdbus_test_timeout, + .flags = TEST_CREATE_BUS, + }, + { + .name = "send", + .desc = "send", + .func = kdbus_test_send, + .flags = TEST_CREATE_CONN | TEST_CREATE_CAN_FAIL, + }, + { + .name = "sync-byebye", + .desc = "synchronous replies vs. BYEBYE", + .func = kdbus_test_sync_byebye, + .flags = TEST_CREATE_BUS, + }, + { + .name = "sync-reply", + .desc = "synchronous replies", + .func = kdbus_test_sync_reply, + .flags = TEST_CREATE_BUS, + }, + { + .name = "message-free", + .desc = "freeing of memory", + .func = kdbus_test_free, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "connection-info", + .desc = "retrieving connection information", + .func = kdbus_test_conn_info, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "connection-update", + .desc = "updating connection information", + .func = kdbus_test_conn_update, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "writable-pool", + .desc = "verifying pools are never writable", + .func = kdbus_test_writable_pool, + .flags = TEST_CREATE_BUS, + }, + { + .name = "policy", + .desc = "policy", + .func = kdbus_test_policy, + .flags = TEST_CREATE_BUS, + }, + { + .name = "policy-priv", + .desc = "unprivileged bus access", + .func = kdbus_test_policy_priv, + .flags = TEST_CREATE_BUS, + }, + { + .name = "policy-ns", + .desc = "policy in user namespaces", + .func = kdbus_test_policy_ns, + .flags = TEST_CREATE_BUS, + }, + { + .name = "metadata-ns", + .desc = "metadata in different namespaces", + .func = kdbus_test_metadata_ns, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-id-add", + .desc = "adding of matches by id", + .func = kdbus_test_match_id_add, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-id-remove", + .desc = "removing of matches by id", + .func = kdbus_test_match_id_remove, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-replace", + .desc = "replace of matches with the same cookie", + .func = kdbus_test_match_replace, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-name-add", + .desc = "adding of matches by name", + .func = kdbus_test_match_name_add, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-name-remove", + .desc = "removing of matches by name", + .func = kdbus_test_match_name_remove, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-name-change", + .desc = "matching for name changes", + .func = kdbus_test_match_name_change, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "match-bloom", + .desc = "matching with bloom filters", + .func = kdbus_test_match_bloom, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "activator", + .desc = "activator connections", + .func = kdbus_test_activator, + .flags = TEST_CREATE_BUS | TEST_CREATE_CONN, + }, + { + .name = "benchmark", + .desc = "benchmark", + .func = kdbus_test_benchmark, + .flags = TEST_CREATE_BUS, + }, + { + .name = "benchmark-nomemfds", + .desc = "benchmark without using memfds", + .func = kdbus_test_benchmark_nomemfds, + .flags = TEST_CREATE_BUS, + }, + { + .name = "benchmark-uds", + .desc = "benchmark comparison to UDS", + .func = kdbus_test_benchmark_uds, + .flags = TEST_CREATE_BUS, + }, + { + /* Last test */ + .name = "attach-flags", + .desc = "attach flags mask", + .func = kdbus_test_attach_flags, + .flags = 0, + }, +}; + +#define N_TESTS ((int) (sizeof(tests) / sizeof(tests[0]))) + +static int test_prepare_env(const struct kdbus_test *t, + const struct kdbus_test_args *args, + struct kdbus_test_env *env) +{ + if (t->flags & TEST_CREATE_BUS) { + char *s; + char *n = NULL; + int ret; + + asprintf(&s, "%s/control", args->root); + + env->control_fd = open(s, O_RDWR); + free(s); + ASSERT_RETURN(env->control_fd >= 0); + + if (!args->busname) { + n = unique_name("test-bus"); + ASSERT_RETURN(n); + } + + ret = kdbus_create_bus(env->control_fd, + args->busname ?: n, + _KDBUS_ATTACH_ALL, + _KDBUS_ATTACH_ALL, &s); + free(n); + ASSERT_RETURN((ret == 0) || (t->flags & TEST_CREATE_CAN_FAIL)); + + asprintf(&env->buspath, "%s/%s/bus", args->root, s); + free(s); + } + + if (t->flags & TEST_CREATE_CONN) { + if (!env->buspath) { + char *s = NULL; + char *n = NULL; + int ret; + + if (!args->busname) { + n = unique_name("test-bus"); + ASSERT_RETURN(n); + } + + ret = kdbus_create_bus(-1, + args->busname ?: n, + 0, + 0, &s); + free(n); + ASSERT_RETURN(ret == 0); + + asprintf(&env->buspath, "%s/%s/bus", args->root, s); + free(s); + } + ASSERT_RETURN(env->buspath); + env->conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(env->conn || (t->flags & TEST_CREATE_CAN_FAIL)); + } + + env->root = args->root; + env->module = args->module; + env->mask_param_path = args->mask_param_path; + + return 0; +} + +void test_unprepare_env(const struct kdbus_test *t, struct kdbus_test_env *env) +{ + if (env->conn) { + kdbus_conn_free(env->conn); + env->conn = NULL; + } + + if (env->control_fd >= 0) { + close(env->control_fd); + env->control_fd = -1; + } + + if (env->buspath) { + free(env->buspath); + env->buspath = NULL; + } +} + +static int test_run(const struct kdbus_test *t, + const struct kdbus_test_args *kdbus_args, + int wait) +{ + int ret; + struct kdbus_test_env env = {}; + + ret = test_prepare_env(t, kdbus_args, &env); + if (ret != TEST_OK) + return ret; + + if (wait > 0) { + printf("Sleeping %d seconds before running test ...\n", wait); + sleep(wait); + } + + ret = t->func(&env); + test_unprepare_env(t, &env); + return ret; +} + +static int test_run_forked(const struct kdbus_test *t, + const struct kdbus_test_args *kdbus_args, + int wait) +{ + int ret; + pid_t pid; + + pid = fork(); + if (pid < 0) { + return TEST_ERR; + } else if (pid == 0) { + ret = test_run(t, kdbus_args, wait); + _exit(ret); + } + + pid = waitpid(pid, &ret, 0); + if (pid <= 0) + return TEST_ERR; + else if (!WIFEXITED(ret)) + return TEST_ERR; + else + return WEXITSTATUS(ret); +} + +static void print_test_result(int ret) +{ + switch (ret) { + case TEST_OK: + printf("OK"); + break; + case TEST_SKIP: + printf("SKIPPED"); + break; + case TEST_ERR: + printf("ERROR"); + break; + } +} + +static int start_all_tests(struct kdbus_test_args *kdbus_args) +{ + int ret; + unsigned int fail_cnt = 0; + unsigned int skip_cnt = 0; + unsigned int ok_cnt = 0; + unsigned int i; + + if (kdbus_args->tap_output) { + printf("1..%d\n", N_TESTS); + fflush(stdout); + } + + kdbus_util_verbose = false; + + for (i = 0; i < N_TESTS; i++) { + const struct kdbus_test *t = tests + i; + + if (!kdbus_args->tap_output) { + unsigned int n; + + printf("Testing %s (%s) ", t->desc, t->name); + for (n = 0; n < 60 - strlen(t->desc) - strlen(t->name); n++) + printf("."); + printf(" "); + } + + ret = test_run_forked(t, kdbus_args, 0); + switch (ret) { + case TEST_OK: + ok_cnt++; + break; + case TEST_SKIP: + skip_cnt++; + break; + case TEST_ERR: + fail_cnt++; + break; + } + + if (kdbus_args->tap_output) { + printf("%sok %d - %s%s (%s)\n", + (ret == TEST_ERR) ? "not " : "", i + 1, + (ret == TEST_SKIP) ? "# SKIP " : "", + t->desc, t->name); + fflush(stdout); + } else { + print_test_result(ret); + printf("\n"); + } + } + + if (kdbus_args->tap_output) + printf("Failed %d/%d tests, %.2f%% okay\n", fail_cnt, N_TESTS, + 100.0 - (fail_cnt * 100.0) / ((float) N_TESTS)); + else + printf("\nSUMMARY: %u tests passed, %u skipped, %u failed\n", + ok_cnt, skip_cnt, fail_cnt); + + return fail_cnt > 0 ? TEST_ERR : TEST_OK; +} + +static int start_one_test(struct kdbus_test_args *kdbus_args) +{ + int i, ret; + bool test_found = false; + + for (i = 0; i < N_TESTS; i++) { + const struct kdbus_test *t = tests + i; + + if (strcmp(t->name, kdbus_args->test)) + continue; + + do { + test_found = true; + if (kdbus_args->fork) + ret = test_run_forked(t, kdbus_args, + kdbus_args->wait); + else + ret = test_run(t, kdbus_args, + kdbus_args->wait); + + printf("Testing %s: ", t->desc); + print_test_result(ret); + printf("\n"); + + if (ret != TEST_OK) + break; + } while (kdbus_args->loop); + + return ret; + } + + if (!test_found) { + printf("Unknown test-id '%s'\n", kdbus_args->test); + return TEST_ERR; + } + + return TEST_OK; +} + +static void usage(const char *argv0) +{ + unsigned int i, j; + + printf("Usage: %s [options]\n" + "Options:\n" + "\t-a, --tap Output test results in TAP format\n" + "\t-m, --module <module> Kdbus module name\n" + "\t-x, --loop Run in a loop\n" + "\t-f, --fork Fork before running a test\n" + "\t-h, --help Print this help\n" + "\t-r, --root <root> Toplevel of the kdbus hierarchy\n" + "\t-t, --test <test-id> Run one specific test only, in verbose mode\n" + "\t-b, --bus <busname> Instead of generating a random bus name, take <busname>.\n" + "\t-w, --wait <secs> Wait <secs> before actually starting test\n" + "\t --mntns New mount namespace\n" + "\t --pidns New PID namespace\n" + "\t --userns New user namespace\n" + "\t --uidmap uid_map UID map for user namespace\n" + "\t --gidmap gid_map GID map for user namespace\n" + "\n", argv0); + + printf("By default, all test are run once, and a summary is printed.\n" + "Available tests for --test:\n\n"); + + for (i = 0; i < N_TESTS; i++) { + const struct kdbus_test *t = tests + i; + + printf("\t%s", t->name); + + for (j = 0; j < 24 - strlen(t->name); j++) + printf(" "); + + printf("Test %s\n", t->desc); + } + + printf("\n"); + printf("Note that some tests may, if run specifically by --test, " + "behave differently, and not terminate by themselves.\n"); + + exit(EXIT_FAILURE); +} + +void print_kdbus_test_args(struct kdbus_test_args *args) +{ + if (args->userns || args->pidns || args->mntns) + printf("# Starting tests in new %s%s%s namespaces%s\n", + args->mntns ? "MOUNT " : "", + args->pidns ? "PID " : "", + args->userns ? "USER " : "", + args->mntns ? ", kdbusfs will be remounted" : ""); + else + printf("# Starting tests in the same namespaces\n"); +} + +void print_metadata_support(void) +{ + bool no_meta_audit, no_meta_cgroups, no_meta_seclabel; + + /* + * KDBUS_ATTACH_CGROUP, KDBUS_ATTACH_AUDIT and + * KDBUS_ATTACH_SECLABEL + */ + no_meta_audit = !config_auditsyscall_is_enabled(); + no_meta_cgroups = !config_cgroups_is_enabled(); + no_meta_seclabel = !config_security_is_enabled(); + + if (no_meta_audit | no_meta_cgroups | no_meta_seclabel) + printf("# Starting tests without %s%s%s metadata support\n", + no_meta_audit ? "AUDIT " : "", + no_meta_cgroups ? "CGROUP " : "", + no_meta_seclabel ? "SECLABEL " : ""); + else + printf("# Starting tests with full metadata support\n"); +} + +int run_tests(struct kdbus_test_args *kdbus_args) +{ + int ret; + static char control[4096]; + + snprintf(control, sizeof(control), "%s/control", kdbus_args->root); + + if (access(control, W_OK) < 0) { + printf("Unable to locate control node at '%s'.\n", + control); + return TEST_ERR; + } + + if (kdbus_args->test) { + ret = start_one_test(kdbus_args); + } else { + do { + ret = start_all_tests(kdbus_args); + if (ret != TEST_OK) + break; + } while (kdbus_args->loop); + } + + return ret; +} + +static void nop_handler(int sig) {} + +static int test_prepare_mounts(struct kdbus_test_args *kdbus_args) +{ + int ret; + char kdbusfs[64] = {'\0'}; + + snprintf(kdbusfs, sizeof(kdbusfs), "%sfs", kdbus_args->module); + + /* make current mount slave */ + ret = mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL); + if (ret < 0) { + ret = -errno; + printf("error mount() root: %d (%m)\n", ret); + return ret; + } + + /* Remount procfs since we need it in our tests */ + if (kdbus_args->pidns) { + ret = mount("proc", "/proc", "proc", + MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (ret < 0) { + ret = -errno; + printf("error mount() /proc : %d (%m)\n", ret); + return ret; + } + } + + /* Remount kdbusfs */ + ret = mount(kdbusfs, kdbus_args->root, kdbusfs, + MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (ret < 0) { + ret = -errno; + printf("error mount() %s :%d (%m)\n", kdbusfs, ret); + return ret; + } + + return 0; +} + +int run_tests_in_namespaces(struct kdbus_test_args *kdbus_args) +{ + int ret; + int efd = -1; + int status; + pid_t pid, rpid; + struct sigaction oldsa; + struct sigaction sa = { + .sa_handler = nop_handler, + .sa_flags = SA_NOCLDSTOP, + }; + + efd = eventfd(0, EFD_CLOEXEC); + if (efd < 0) { + ret = -errno; + printf("eventfd() failed: %d (%m)\n", ret); + return TEST_ERR; + } + + ret = sigaction(SIGCHLD, &sa, &oldsa); + if (ret < 0) { + ret = -errno; + printf("sigaction() failed: %d (%m)\n", ret); + return TEST_ERR; + } + + /* setup namespaces */ + pid = syscall(__NR_clone, SIGCHLD| + (kdbus_args->userns ? CLONE_NEWUSER : 0) | + (kdbus_args->mntns ? CLONE_NEWNS : 0) | + (kdbus_args->pidns ? CLONE_NEWPID : 0), NULL); + if (pid < 0) { + printf("clone() failed: %d (%m)\n", -errno); + return TEST_ERR; + } + + if (pid == 0) { + eventfd_t event_status = 0; + + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + if (ret < 0) { + ret = -errno; + printf("error prctl(): %d (%m)\n", ret); + _exit(TEST_ERR); + } + + /* reset sighandlers of childs */ + ret = sigaction(SIGCHLD, &oldsa, NULL); + if (ret < 0) { + ret = -errno; + printf("sigaction() failed: %d (%m)\n", ret); + _exit(TEST_ERR); + } + + ret = eventfd_read(efd, &event_status); + if (ret < 0 || event_status != 1) { + printf("error eventfd_read()\n"); + _exit(TEST_ERR); + } + + if (kdbus_args->mntns) { + ret = test_prepare_mounts(kdbus_args); + if (ret < 0) { + printf("error preparing mounts\n"); + _exit(TEST_ERR); + } + } + + ret = run_tests(kdbus_args); + _exit(ret); + } + + /* Setup userns mapping */ + if (kdbus_args->userns) { + ret = userns_map_uid_gid(pid, kdbus_args->uid_map, + kdbus_args->gid_map); + if (ret < 0) { + printf("error mapping uid and gid in userns\n"); + eventfd_write(efd, 2); + return TEST_ERR; + } + } + + ret = eventfd_write(efd, 1); + if (ret < 0) { + ret = -errno; + printf("error eventfd_write(): %d (%m)\n", ret); + return TEST_ERR; + } + + rpid = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(rpid == pid, TEST_ERR); + + close(efd); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return TEST_ERR; + + return TEST_OK; +} + +int start_tests(struct kdbus_test_args *kdbus_args) +{ + int ret; + bool namespaces; + uint64_t kdbus_param_mask; + static char fspath[4096], parampath[4096]; + + namespaces = (kdbus_args->mntns || kdbus_args->pidns || + kdbus_args->userns); + + /* for pidns we need mntns set */ + if (kdbus_args->pidns && !kdbus_args->mntns) { + printf("Failed: please set both pid and mnt namesapces\n"); + return TEST_ERR; + } + + if (kdbus_args->userns) { + if (!config_user_ns_is_enabled()) { + printf("User namespace not supported\n"); + return TEST_ERR; + } + + if (!kdbus_args->uid_map || !kdbus_args->gid_map) { + printf("Failed: please specify uid or gid mapping\n"); + return TEST_ERR; + } + } + + print_kdbus_test_args(kdbus_args); + print_metadata_support(); + + /* setup kdbus paths */ + if (!kdbus_args->module) + kdbus_args->module = "kdbus"; + + if (!kdbus_args->root) { + snprintf(fspath, sizeof(fspath), "/sys/fs/%s", + kdbus_args->module); + kdbus_args->root = fspath; + } + + snprintf(parampath, sizeof(parampath), + "/sys/module/%s/parameters/attach_flags_mask", + kdbus_args->module); + kdbus_args->mask_param_path = parampath; + + ret = kdbus_sysfs_get_parameter_mask(kdbus_args->mask_param_path, + &kdbus_param_mask); + if (ret < 0) + return TEST_ERR; + + printf("# Starting tests with an attach_flags_mask=0x%llx\n", + (unsigned long long)kdbus_param_mask); + + /* Start tests */ + if (namespaces) + ret = run_tests_in_namespaces(kdbus_args); + else + ret = run_tests(kdbus_args); + + return ret; +} + +int main(int argc, char *argv[]) +{ + int t, ret = 0; + struct kdbus_test_args *kdbus_args; + enum { + ARG_MNTNS = 0x100, + ARG_PIDNS, + ARG_USERNS, + ARG_UIDMAP, + ARG_GIDMAP, + }; + char *exec = basename(argv[0]); + + kdbus_args = malloc(sizeof(*kdbus_args)); + if (!kdbus_args) { + printf("unable to malloc() kdbus_args\n"); + return EXIT_FAILURE; + } + + memset(kdbus_args, 0, sizeof(*kdbus_args)); + + static const struct option options[] = { + { "loop", no_argument, NULL, 'x' }, + { "help", no_argument, NULL, 'h' }, + { "root", required_argument, NULL, 'r' }, + { "test", required_argument, NULL, 't' }, + { "bus", required_argument, NULL, 'b' }, + { "wait", required_argument, NULL, 'w' }, + { "fork", no_argument, NULL, 'f' }, + { "module", required_argument, NULL, 'm' }, + { "tap", no_argument, NULL, 'a' }, + { "mntns", no_argument, NULL, ARG_MNTNS }, + { "pidns", no_argument, NULL, ARG_PIDNS }, + { "userns", no_argument, NULL, ARG_USERNS }, + { "uidmap", required_argument, NULL, ARG_UIDMAP }, + { "gidmap", required_argument, NULL, ARG_GIDMAP }, + {} + }; + + srand(time(NULL)); + + if (strcmp(exec, "kdbus-test") != 0) { + kdbus_args->test = exec; + } + + while ((t = getopt_long(argc, argv, "hxfm:r:t:b:w:a", options, NULL)) >= 0) { + switch (t) { + case 'x': + kdbus_args->loop = 1; + break; + + case 'm': + kdbus_args->module = optarg; + break; + + case 'r': + kdbus_args->root = optarg; + break; + + case 't': + kdbus_args->test = optarg; + break; + + case 'b': + kdbus_args->busname = optarg; + break; + + case 'w': + kdbus_args->wait = strtol(optarg, NULL, 10); + break; + + case 'f': + kdbus_args->fork = 1; + break; + + case 'a': + kdbus_args->tap_output = 1; + break; + + case ARG_MNTNS: + kdbus_args->mntns = true; + break; + + case ARG_PIDNS: + kdbus_args->pidns = true; + break; + + case ARG_USERNS: + kdbus_args->userns = true; + break; + + case ARG_UIDMAP: + kdbus_args->uid_map = optarg; + break; + + case ARG_GIDMAP: + kdbus_args->gid_map = optarg; + break; + + default: + case 'h': + usage(argv[0]); + } + } + + ret = start_tests(kdbus_args); + if (ret == TEST_ERR) + return EXIT_FAILURE; + + free(kdbus_args); + + return 0; +} diff --git a/tools/testing/selftests/kdbus/kdbus-test.h b/tools/testing/selftests/kdbus/kdbus-test.h new file mode 100644 index 000000000000..826b9f45bfdd --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-test.h @@ -0,0 +1,86 @@ +#ifndef _TEST_KDBUS_H_ +#define _TEST_KDBUS_H_ + +struct kdbus_test_env { + char *buspath; + const char *root; + const char *module; + const char *mask_param_path; + int control_fd; + struct kdbus_conn *conn; +}; + +enum { + TEST_OK, + TEST_SKIP, + TEST_ERR, +}; + +#define ASSERT_RETURN_VAL(cond, val) \ + if (!(cond)) { \ + fprintf(stderr, "Assertion '%s' failed in %s(), %s:%d\n", \ + #cond, __func__, __FILE__, __LINE__); \ + return val; \ + } + +#define ASSERT_EXIT_VAL(cond, val) \ + if (!(cond)) { \ + fprintf(stderr, "Assertion '%s' failed in %s(), %s:%d\n", \ + #cond, __func__, __FILE__, __LINE__); \ + _exit(val); \ + } + +#define ASSERT_BREAK(cond) \ + if (!(cond)) { \ + fprintf(stderr, "Assertion '%s' failed in %s(), %s:%d\n", \ + #cond, __func__, __FILE__, __LINE__); \ + break; \ + } + +#define ASSERT_RETURN(cond) \ + ASSERT_RETURN_VAL(cond, TEST_ERR) + +#define ASSERT_EXIT(cond) \ + ASSERT_EXIT_VAL(cond, EXIT_FAILURE) + +int kdbus_test_activator(struct kdbus_test_env *env); +int kdbus_test_attach_flags(struct kdbus_test_env *env); +int kdbus_test_benchmark(struct kdbus_test_env *env); +int kdbus_test_benchmark_nomemfds(struct kdbus_test_env *env); +int kdbus_test_benchmark_uds(struct kdbus_test_env *env); +int kdbus_test_bus_make(struct kdbus_test_env *env); +int kdbus_test_byebye(struct kdbus_test_env *env); +int kdbus_test_chat(struct kdbus_test_env *env); +int kdbus_test_conn_info(struct kdbus_test_env *env); +int kdbus_test_conn_update(struct kdbus_test_env *env); +int kdbus_test_daemon(struct kdbus_test_env *env); +int kdbus_test_custom_endpoint(struct kdbus_test_env *env); +int kdbus_test_fd_passing(struct kdbus_test_env *env); +int kdbus_test_free(struct kdbus_test_env *env); +int kdbus_test_hello(struct kdbus_test_env *env); +int kdbus_test_match_bloom(struct kdbus_test_env *env); +int kdbus_test_match_id_add(struct kdbus_test_env *env); +int kdbus_test_match_id_remove(struct kdbus_test_env *env); +int kdbus_test_match_replace(struct kdbus_test_env *env); +int kdbus_test_match_name_add(struct kdbus_test_env *env); +int kdbus_test_match_name_change(struct kdbus_test_env *env); +int kdbus_test_match_name_remove(struct kdbus_test_env *env); +int kdbus_test_message_basic(struct kdbus_test_env *env); +int kdbus_test_message_prio(struct kdbus_test_env *env); +int kdbus_test_message_quota(struct kdbus_test_env *env); +int kdbus_test_memory_access(struct kdbus_test_env *env); +int kdbus_test_metadata_ns(struct kdbus_test_env *env); +int kdbus_test_monitor(struct kdbus_test_env *env); +int kdbus_test_name_basic(struct kdbus_test_env *env); +int kdbus_test_name_conflict(struct kdbus_test_env *env); +int kdbus_test_name_queue(struct kdbus_test_env *env); +int kdbus_test_policy(struct kdbus_test_env *env); +int kdbus_test_policy_ns(struct kdbus_test_env *env); +int kdbus_test_policy_priv(struct kdbus_test_env *env); +int kdbus_test_send(struct kdbus_test_env *env); +int kdbus_test_sync_byebye(struct kdbus_test_env *env); +int kdbus_test_sync_reply(struct kdbus_test_env *env); +int kdbus_test_timeout(struct kdbus_test_env *env); +int kdbus_test_writable_pool(struct kdbus_test_env *env); + +#endif /* _TEST_KDBUS_H_ */ diff --git a/tools/testing/selftests/kdbus/kdbus-util.c b/tools/testing/selftests/kdbus/kdbus-util.c new file mode 100644 index 000000000000..6677dadd1109 --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-util.c @@ -0,0 +1,1636 @@ +/* + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#include <inttypes.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <grp.h> +#include <sys/capability.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <linux/unistd.h> +#include <linux/memfd.h> + +#ifndef __NR_memfd_create + #ifdef __x86_64__ + #define __NR_memfd_create 319 + #elif defined __arm__ + #define __NR_memfd_create 385 + #else + #define __NR_memfd_create 356 + #endif +#endif + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#ifndef F_ADD_SEALS +#define F_LINUX_SPECIFIC_BASE 1024 +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +int kdbus_util_verbose = true; + +int kdbus_sysfs_get_parameter_mask(const char *path, uint64_t *mask) +{ + int ret; + FILE *file; + unsigned long long value; + + file = fopen(path, "r"); + if (!file) { + ret = -errno; + kdbus_printf("--- error fopen(): %d (%m)\n", ret); + return ret; + } + + ret = fscanf(file, "%llu", &value); + if (ret != 1) { + if (ferror(file)) + ret = -errno; + else + ret = -EIO; + + kdbus_printf("--- error fscanf(): %d\n", ret); + fclose(file); + return ret; + } + + *mask = (uint64_t)value; + + fclose(file); + + return 0; +} + +int kdbus_sysfs_set_parameter_mask(const char *path, uint64_t mask) +{ + int ret; + FILE *file; + + file = fopen(path, "w"); + if (!file) { + ret = -errno; + kdbus_printf("--- error open(): %d (%m)\n", ret); + return ret; + } + + ret = fprintf(file, "%llu", (unsigned long long)mask); + if (ret <= 0) { + ret = -EIO; + kdbus_printf("--- error fprintf(): %d\n", ret); + } + + fclose(file); + + return ret > 0 ? 0 : ret; +} + +int kdbus_create_bus(int control_fd, const char *name, + uint64_t req_meta, uint64_t owner_meta, + char **path) +{ + struct { + struct kdbus_cmd cmd; + + /* bloom size item */ + struct { + uint64_t size; + uint64_t type; + struct kdbus_bloom_parameter bloom; + } bp; + + /* required and owner metadata items */ + struct { + uint64_t size; + uint64_t type; + uint64_t flags; + } attach[2]; + + /* name item */ + struct { + uint64_t size; + uint64_t type; + char str[64]; + } name; + } bus_make; + int ret = 0; + + memset(&bus_make, 0, sizeof(bus_make)); + bus_make.bp.size = sizeof(bus_make.bp); + bus_make.bp.type = KDBUS_ITEM_BLOOM_PARAMETER; + bus_make.bp.bloom.size = 64; + bus_make.bp.bloom.n_hash = 1; + + snprintf(bus_make.name.str, sizeof(bus_make.name.str), + "%u-%s", getuid(), name); + + bus_make.attach[0].type = KDBUS_ITEM_ATTACH_FLAGS_RECV; + bus_make.attach[0].size = sizeof(bus_make.attach[0]); + bus_make.attach[0].flags = req_meta; + + bus_make.attach[1].type = KDBUS_ITEM_ATTACH_FLAGS_SEND; + bus_make.attach[1].size = sizeof(bus_make.attach[0]); + bus_make.attach[1].flags = owner_meta; + + bus_make.name.type = KDBUS_ITEM_MAKE_NAME; + bus_make.name.size = KDBUS_ITEM_HEADER_SIZE + + strlen(bus_make.name.str) + 1; + + bus_make.cmd.flags = KDBUS_MAKE_ACCESS_WORLD; + bus_make.cmd.size = sizeof(bus_make.cmd) + + bus_make.bp.size + + bus_make.attach[0].size + + bus_make.attach[1].size + + bus_make.name.size; + + if (control_fd != -1) { + kdbus_printf( + "Creating bus with name >%s< on control fd %d ...\n", + name, control_fd); + + ret = kdbus_cmd_bus_make(control_fd, &bus_make.cmd); + if (ret < 0) { + kdbus_printf("--- error when making bus: %d (%m)\n", + ret); + return ret; + } + } + + if (ret == 0 && path) + *path = strdup(bus_make.name.str); + + return ret; +} + +struct kdbus_conn * +kdbus_hello(const char *path, uint64_t flags, + const struct kdbus_item *item, size_t item_size) +{ + struct kdbus_cmd_free cmd_free = {}; + int fd, ret; + struct { + struct kdbus_cmd_hello hello; + + struct { + uint64_t size; + uint64_t type; + char str[16]; + } conn_name; + + uint8_t extra_items[item_size]; + } h; + struct kdbus_conn *conn; + + memset(&h, 0, sizeof(h)); + + if (item_size > 0) + memcpy(h.extra_items, item, item_size); + + kdbus_printf("-- opening bus connection %s\n", path); + fd = open(path, O_RDWR|O_CLOEXEC); + if (fd < 0) { + kdbus_printf("--- error %d (%m)\n", fd); + return NULL; + } + + h.hello.flags = flags | KDBUS_HELLO_ACCEPT_FD; + h.hello.attach_flags_send = _KDBUS_ATTACH_ALL; + h.hello.attach_flags_recv = _KDBUS_ATTACH_ALL; + h.conn_name.type = KDBUS_ITEM_CONN_DESCRIPTION; + strcpy(h.conn_name.str, "this-is-my-name"); + h.conn_name.size = KDBUS_ITEM_HEADER_SIZE + strlen(h.conn_name.str) + 1; + + h.hello.size = sizeof(h); + h.hello.pool_size = POOL_SIZE; + + ret = kdbus_cmd_hello(fd, (struct kdbus_cmd_hello *) &h.hello); + if (ret < 0) { + kdbus_printf("--- error when saying hello: %d (%m)\n", ret); + return NULL; + } + kdbus_printf("-- Our peer ID for %s: %llu -- bus uuid: '%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x'\n", + path, (unsigned long long)h.hello.id, + h.hello.id128[0], h.hello.id128[1], h.hello.id128[2], + h.hello.id128[3], h.hello.id128[4], h.hello.id128[5], + h.hello.id128[6], h.hello.id128[7], h.hello.id128[8], + h.hello.id128[9], h.hello.id128[10], h.hello.id128[11], + h.hello.id128[12], h.hello.id128[13], h.hello.id128[14], + h.hello.id128[15]); + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = h.hello.offset; + kdbus_cmd_free(fd, &cmd_free); + + conn = malloc(sizeof(*conn)); + if (!conn) { + kdbus_printf("unable to malloc()!?\n"); + return NULL; + } + + conn->buf = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, fd, 0); + if (conn->buf == MAP_FAILED) { + free(conn); + close(fd); + kdbus_printf("--- error mmap (%m)\n"); + return NULL; + } + + conn->fd = fd; + conn->id = h.hello.id; + return conn; +} + +struct kdbus_conn * +kdbus_hello_registrar(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access, uint64_t flags) +{ + struct kdbus_item *item, *items; + size_t i, size; + + size = KDBUS_ITEM_SIZE(strlen(name) + 1) + + num_access * KDBUS_ITEM_SIZE(sizeof(*access)); + + items = alloca(size); + + item = items; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + item->type = KDBUS_ITEM_NAME; + strcpy(item->str, name); + item = KDBUS_ITEM_NEXT(item); + + for (i = 0; i < num_access; i++) { + item->size = KDBUS_ITEM_HEADER_SIZE + + sizeof(struct kdbus_policy_access); + item->type = KDBUS_ITEM_POLICY_ACCESS; + + item->policy_access.type = access[i].type; + item->policy_access.access = access[i].access; + item->policy_access.id = access[i].id; + + item = KDBUS_ITEM_NEXT(item); + } + + return kdbus_hello(path, flags, items, size); +} + +struct kdbus_conn *kdbus_hello_activator(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access) +{ + return kdbus_hello_registrar(path, name, access, num_access, + KDBUS_HELLO_ACTIVATOR); +} + +bool kdbus_item_in_message(struct kdbus_msg *msg, uint64_t type) +{ + const struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, msg, items) + if (item->type == type) + return true; + + return false; +} + +int kdbus_bus_creator_info(struct kdbus_conn *conn, + uint64_t flags, + uint64_t *offset) +{ + struct kdbus_cmd_info *cmd; + size_t size = sizeof(*cmd); + int ret; + + cmd = alloca(size); + memset(cmd, 0, size); + cmd->size = size; + cmd->attach_flags = flags; + + ret = kdbus_cmd_bus_creator_info(conn->fd, cmd); + if (ret < 0) { + kdbus_printf("--- error when requesting info: %d (%m)\n", ret); + return ret; + } + + if (offset) + *offset = cmd->offset; + else + kdbus_free(conn, cmd->offset); + + return 0; +} + +int kdbus_conn_info(struct kdbus_conn *conn, uint64_t id, + const char *name, uint64_t flags, + uint64_t *offset) +{ + struct kdbus_cmd_info *cmd; + size_t size = sizeof(*cmd); + struct kdbus_info *info; + int ret; + + if (name) + size += KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + + cmd = alloca(size); + memset(cmd, 0, size); + cmd->size = size; + cmd->attach_flags = flags; + + if (name) { + cmd->items[0].size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + cmd->items[0].type = KDBUS_ITEM_NAME; + strcpy(cmd->items[0].str, name); + } else { + cmd->id = id; + } + + ret = kdbus_cmd_conn_info(conn->fd, cmd); + if (ret < 0) { + kdbus_printf("--- error when requesting info: %d (%m)\n", ret); + return ret; + } + + info = (struct kdbus_info *) (conn->buf + cmd->offset); + if (info->size != cmd->info_size) { + kdbus_printf("%s(): size mismatch: %d != %d\n", __func__, + (int) info->size, (int) cmd->info_size); + return -EIO; + } + + if (offset) + *offset = cmd->offset; + else + kdbus_free(conn, cmd->offset); + + return 0; +} + +void kdbus_conn_free(struct kdbus_conn *conn) +{ + if (!conn) + return; + + if (conn->buf) + munmap(conn->buf, POOL_SIZE); + + if (conn->fd >= 0) + close(conn->fd); + + free(conn); +} + +int sys_memfd_create(const char *name, __u64 size) +{ + int ret, fd; + + ret = syscall(__NR_memfd_create, name, MFD_ALLOW_SEALING); + if (ret < 0) + return ret; + + fd = ret; + + ret = ftruncate(fd, size); + if (ret < 0) { + close(fd); + return ret; + } + + return fd; +} + +int sys_memfd_seal_set(int fd) +{ + return fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | + F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); +} + +off_t sys_memfd_get_size(int fd, off_t *size) +{ + struct stat stat; + int ret; + + ret = fstat(fd, &stat); + if (ret < 0) { + kdbus_printf("stat() failed: %m\n"); + return ret; + } + + *size = stat.st_size; + return 0; +} + +static int __kdbus_msg_send(const struct kdbus_conn *conn, + const char *name, + uint64_t cookie, + uint64_t flags, + uint64_t timeout, + int64_t priority, + uint64_t dst_id, + uint64_t cmd_flags, + int cancel_fd, + int fds_count, + int fds[]) +{ + struct kdbus_cmd_send *cmd; + struct kdbus_msg *msg; + const char ref1[1024 * 128 + 3] = "0123456789_0"; + const char ref2[] = "0123456789_1"; + struct kdbus_item *item; + struct timespec now; + uint64_t size; + int memfd = -1; + int ret; + + size = sizeof(*msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + size += fds_count > 0 ? KDBUS_ITEM_SIZE(sizeof(int) * fds_count) : 0; + + if (dst_id == KDBUS_DST_ID_BROADCAST) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + else { + memfd = sys_memfd_create("my-name-is-nice", 1024 * 1024); + if (memfd < 0) { + kdbus_printf("failed to create memfd: %m\n"); + return memfd; + } + + if (write(memfd, "kdbus memfd 1234567", 19) != 19) { + ret = -errno; + kdbus_printf("writing to memfd failed: %m\n"); + return ret; + } + + ret = sys_memfd_seal_set(memfd); + if (ret < 0) { + ret = -errno; + kdbus_printf("memfd sealing failed: %m\n"); + return ret; + } + + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + } + + if (name) + size += KDBUS_ITEM_SIZE(strlen(name) + 1); + + msg = malloc(size); + if (!msg) { + ret = -errno; + kdbus_printf("unable to malloc()!?\n"); + return ret; + } + + if (dst_id == KDBUS_DST_ID_BROADCAST) + flags |= KDBUS_MSG_SIGNAL; + + memset(msg, 0, size); + msg->flags = flags; + msg->priority = priority; + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = name ? 0 : dst_id; + msg->cookie = cookie; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + if (timeout) { + ret = clock_gettime(CLOCK_MONOTONIC_COARSE, &now); + if (ret < 0) + return ret; + + msg->timeout_ns = now.tv_sec * 1000000000ULL + + now.tv_nsec + timeout; + } + + item = msg->items; + + if (name) { + item->type = KDBUS_ITEM_DST_NAME; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + strcpy(item->str, name); + item = KDBUS_ITEM_NEXT(item); + } + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)&ref1; + item->vec.size = sizeof(ref1); + item = KDBUS_ITEM_NEXT(item); + + /* data padding for ref1 */ + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)NULL; + item->vec.size = KDBUS_ALIGN8(sizeof(ref1)) - sizeof(ref1); + item = KDBUS_ITEM_NEXT(item); + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)&ref2; + item->vec.size = sizeof(ref2); + item = KDBUS_ITEM_NEXT(item); + + if (dst_id == KDBUS_DST_ID_BROADCAST) { + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + item->bloom_filter.generation = 0; + } else { + item->type = KDBUS_ITEM_PAYLOAD_MEMFD; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_memfd); + item->memfd.size = 16; + item->memfd.fd = memfd; + } + item = KDBUS_ITEM_NEXT(item); + + if (fds_count > 0) { + item->type = KDBUS_ITEM_FDS; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(int) * fds_count; + memcpy(&item->fds, fds, sizeof(int) * fds_count); + item = KDBUS_ITEM_NEXT(item); + } + + size = sizeof(*cmd); + if (cancel_fd != -1) + size += KDBUS_ITEM_SIZE(sizeof(cancel_fd)); + + cmd = malloc(size); + if (!cmd) { + ret = -errno; + kdbus_printf("unable to malloc(%ld)!?\n", (long)size); + return ret; + } + cmd->size = size; + cmd->flags = cmd_flags; + cmd->msg_address = (uintptr_t)msg; + + item = cmd->items; + + if (cancel_fd != -1) { + item->type = KDBUS_ITEM_CANCEL_FD; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(cancel_fd); + item->fds[0] = cancel_fd; + item = KDBUS_ITEM_NEXT(item); + } + + + ret = kdbus_cmd_send(conn->fd, cmd); + if (memfd >= 0) + close(memfd); + + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + if (cmd_flags & KDBUS_SEND_SYNC_REPLY) { + struct kdbus_msg *reply; + + kdbus_printf("SYNC REPLY @offset %llu:\n", cmd->reply.offset); + reply = (struct kdbus_msg *)(conn->buf + cmd->reply.offset); + kdbus_msg_dump(conn, reply); + + kdbus_msg_free(reply); + + ret = kdbus_free(conn, cmd->reply.offset); + if (ret < 0) + return ret; + } + + free(msg); + free(cmd); + + return 0; +} + +int kdbus_msg_send(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id, int fds_count, int fds[]) +{ + return __kdbus_msg_send(conn, name, cookie, flags, timeout, priority, + dst_id, 0, -1, fds_count, fds); +} + +int kdbus_msg_send_sync(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id, int cancel_fd) +{ + return __kdbus_msg_send(conn, name, cookie, flags, timeout, priority, + dst_id, KDBUS_SEND_SYNC_REPLY, cancel_fd, + 0, NULL); +} + +int kdbus_msg_send_reply(const struct kdbus_conn *conn, + uint64_t reply_cookie, + uint64_t dst_id) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_msg *msg; + const char ref1[1024 * 128 + 3] = "0123456789_0"; + struct kdbus_item *item; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = malloc(size); + if (!msg) { + kdbus_printf("unable to malloc()!?\n"); + return -ENOMEM; + } + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = dst_id; + msg->cookie_reply = reply_cookie; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)&ref1; + item->vec.size = sizeof(ref1); + item = KDBUS_ITEM_NEXT(item); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) + kdbus_printf("error sending message: %d (%m)\n", ret); + + free(msg); + + return ret; +} + +static char *msg_id(uint64_t id, char *buf) +{ + if (id == 0) + return "KERNEL"; + if (id == ~0ULL) + return "BROADCAST"; + sprintf(buf, "%llu", (unsigned long long)id); + return buf; +} + +int kdbus_msg_dump(const struct kdbus_conn *conn, const struct kdbus_msg *msg) +{ + const struct kdbus_item *item = msg->items; + char buf_src[32]; + char buf_dst[32]; + uint64_t timeout = 0; + uint64_t cookie_reply = 0; + int ret = 0; + + if (msg->flags & KDBUS_MSG_EXPECT_REPLY) + timeout = msg->timeout_ns; + else + cookie_reply = msg->cookie_reply; + + kdbus_printf("MESSAGE: %s (%llu bytes) flags=0x%08llx, %s → %s, " + "cookie=%llu, timeout=%llu cookie_reply=%llu priority=%lli\n", + enum_PAYLOAD(msg->payload_type), (unsigned long long)msg->size, + (unsigned long long)msg->flags, + msg_id(msg->src_id, buf_src), msg_id(msg->dst_id, buf_dst), + (unsigned long long)msg->cookie, (unsigned long long)timeout, + (unsigned long long)cookie_reply, (long long)msg->priority); + + KDBUS_ITEM_FOREACH(item, msg, items) { + if (item->size < KDBUS_ITEM_HEADER_SIZE) { + kdbus_printf(" +%s (%llu bytes) invalid data record\n", + enum_MSG(item->type), item->size); + ret = -EINVAL; + break; + } + + switch (item->type) { + case KDBUS_ITEM_PAYLOAD_OFF: { + char *s; + + if (item->vec.offset == ~0ULL) + s = "[\\0-bytes]"; + else + s = (char *)msg + item->vec.offset; + + kdbus_printf(" +%s (%llu bytes) off=%llu size=%llu '%s'\n", + enum_MSG(item->type), item->size, + (unsigned long long)item->vec.offset, + (unsigned long long)item->vec.size, s); + break; + } + + case KDBUS_ITEM_FDS: { + int i, n = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + + kdbus_printf(" +%s (%llu bytes, %d fds)\n", + enum_MSG(item->type), item->size, n); + + for (i = 0; i < n; i++) + kdbus_printf(" fd[%d] = %d\n", + i, item->fds[i]); + + break; + } + + case KDBUS_ITEM_PAYLOAD_MEMFD: { + char *buf; + off_t size; + + buf = mmap(NULL, item->memfd.size, PROT_READ, + MAP_PRIVATE, item->memfd.fd, 0); + if (buf == MAP_FAILED) { + kdbus_printf("mmap() fd=%i size=%llu failed: %m\n", + item->memfd.fd, item->memfd.size); + break; + } + + if (sys_memfd_get_size(item->memfd.fd, &size) < 0) { + kdbus_printf("KDBUS_CMD_MEMFD_SIZE_GET failed: %m\n"); + break; + } + + kdbus_printf(" +%s (%llu bytes) fd=%i size=%llu filesize=%llu '%s'\n", + enum_MSG(item->type), item->size, item->memfd.fd, + (unsigned long long)item->memfd.size, + (unsigned long long)size, buf); + munmap(buf, item->memfd.size); + break; + } + + case KDBUS_ITEM_CREDS: + kdbus_printf(" +%s (%llu bytes) uid=%lld, euid=%lld, suid=%lld, fsuid=%lld, " + "gid=%lld, egid=%lld, sgid=%lld, fsgid=%lld\n", + enum_MSG(item->type), item->size, + item->creds.uid, item->creds.euid, + item->creds.suid, item->creds.fsuid, + item->creds.gid, item->creds.egid, + item->creds.sgid, item->creds.fsgid); + break; + + case KDBUS_ITEM_PIDS: + kdbus_printf(" +%s (%llu bytes) pid=%lld, tid=%lld, ppid=%lld\n", + enum_MSG(item->type), item->size, + item->pids.pid, item->pids.tid, + item->pids.ppid); + break; + + case KDBUS_ITEM_AUXGROUPS: { + int i, n; + + kdbus_printf(" +%s (%llu bytes)\n", + enum_MSG(item->type), item->size); + n = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(uint64_t); + + for (i = 0; i < n; i++) + kdbus_printf(" gid[%d] = %lld\n", + i, item->data64[i]); + break; + } + + case KDBUS_ITEM_NAME: + case KDBUS_ITEM_PID_COMM: + case KDBUS_ITEM_TID_COMM: + case KDBUS_ITEM_EXE: + case KDBUS_ITEM_CGROUP: + case KDBUS_ITEM_SECLABEL: + case KDBUS_ITEM_DST_NAME: + case KDBUS_ITEM_CONN_DESCRIPTION: + kdbus_printf(" +%s (%llu bytes) '%s' (%zu)\n", + enum_MSG(item->type), item->size, + item->str, strlen(item->str)); + break; + + case KDBUS_ITEM_OWNED_NAME: { + kdbus_printf(" +%s (%llu bytes) '%s' (%zu) flags=0x%08llx\n", + enum_MSG(item->type), item->size, + item->name.name, strlen(item->name.name), + item->name.flags); + break; + } + + case KDBUS_ITEM_CMDLINE: { + size_t size = item->size - KDBUS_ITEM_HEADER_SIZE; + const char *str = item->str; + int count = 0; + + kdbus_printf(" +%s (%llu bytes) ", + enum_MSG(item->type), item->size); + while (size) { + kdbus_printf("'%s' ", str); + size -= strlen(str) + 1; + str += strlen(str) + 1; + count++; + } + + kdbus_printf("(%d string%s)\n", + count, (count == 1) ? "" : "s"); + break; + } + + case KDBUS_ITEM_AUDIT: + kdbus_printf(" +%s (%llu bytes) loginuid=%u sessionid=%u\n", + enum_MSG(item->type), item->size, + item->audit.loginuid, item->audit.sessionid); + break; + + case KDBUS_ITEM_CAPS: { + const uint32_t *cap; + int n, i; + + kdbus_printf(" +%s (%llu bytes) len=%llu bytes, last_cap %d\n", + enum_MSG(item->type), item->size, + (unsigned long long)item->size - + KDBUS_ITEM_HEADER_SIZE, + (int) item->caps.last_cap); + + cap = item->caps.caps; + n = (item->size - offsetof(struct kdbus_item, caps.caps)) + / 4 / sizeof(uint32_t); + + kdbus_printf(" CapInh="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(0 * n) + (n - i - 1)]); + + kdbus_printf(" CapPrm="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(1 * n) + (n - i - 1)]); + + kdbus_printf(" CapEff="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(2 * n) + (n - i - 1)]); + + kdbus_printf(" CapBnd="); + for (i = 0; i < n; i++) + kdbus_printf("%08x", cap[(3 * n) + (n - i - 1)]); + kdbus_printf("\n"); + break; + } + + case KDBUS_ITEM_TIMESTAMP: + kdbus_printf(" +%s (%llu bytes) seq=%llu realtime=%lluns monotonic=%lluns\n", + enum_MSG(item->type), item->size, + (unsigned long long)item->timestamp.seqnum, + (unsigned long long)item->timestamp.realtime_ns, + (unsigned long long)item->timestamp.monotonic_ns); + break; + + case KDBUS_ITEM_REPLY_TIMEOUT: + kdbus_printf(" +%s (%llu bytes) cookie=%llu\n", + enum_MSG(item->type), item->size, + msg->cookie_reply); + break; + + case KDBUS_ITEM_NAME_ADD: + case KDBUS_ITEM_NAME_REMOVE: + case KDBUS_ITEM_NAME_CHANGE: + kdbus_printf(" +%s (%llu bytes) '%s', old id=%lld, now id=%lld, old_flags=0x%llx new_flags=0x%llx\n", + enum_MSG(item->type), + (unsigned long long) item->size, + item->name_change.name, + item->name_change.old_id.id, + item->name_change.new_id.id, + item->name_change.old_id.flags, + item->name_change.new_id.flags); + break; + + case KDBUS_ITEM_ID_ADD: + case KDBUS_ITEM_ID_REMOVE: + kdbus_printf(" +%s (%llu bytes) id=%llu flags=%llu\n", + enum_MSG(item->type), + (unsigned long long) item->size, + (unsigned long long) item->id_change.id, + (unsigned long long) item->id_change.flags); + break; + + default: + kdbus_printf(" +%s (%llu bytes)\n", + enum_MSG(item->type), item->size); + break; + } + } + + if ((char *)item - ((char *)msg + msg->size) >= 8) { + kdbus_printf("invalid padding at end of message\n"); + ret = -EINVAL; + } + + kdbus_printf("\n"); + + return ret; +} + +void kdbus_msg_free(struct kdbus_msg *msg) +{ + const struct kdbus_item *item; + int nfds, i; + + if (!msg) + return; + + KDBUS_ITEM_FOREACH(item, msg, items) { + switch (item->type) { + /* close all memfds */ + case KDBUS_ITEM_PAYLOAD_MEMFD: + close(item->memfd.fd); + break; + case KDBUS_ITEM_FDS: + nfds = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + + for (i = 0; i < nfds; i++) + close(item->fds[i]); + + break; + } + } +} + +int kdbus_msg_recv(struct kdbus_conn *conn, + struct kdbus_msg **msg_out, + uint64_t *offset) +{ + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *msg; + int ret; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret < 0) + return ret; + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + ret = kdbus_msg_dump(conn, msg); + if (ret < 0) { + kdbus_msg_free(msg); + return ret; + } + + if (msg_out) { + *msg_out = msg; + + if (offset) + *offset = recv.msg.offset; + } else { + kdbus_msg_free(msg); + + ret = kdbus_free(conn, recv.msg.offset); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Returns: 0 on success, negative errno on failure. + * + * We must return -ETIMEDOUT, -ECONNREST, -EAGAIN and other errors. + * We must return the result of kdbus_msg_recv() + */ +int kdbus_msg_recv_poll(struct kdbus_conn *conn, + int timeout_ms, + struct kdbus_msg **msg_out, + uint64_t *offset) +{ + int ret; + + do { + struct timeval before, after, diff; + struct pollfd fd; + + fd.fd = conn->fd; + fd.events = POLLIN | POLLPRI | POLLHUP; + fd.revents = 0; + + gettimeofday(&before, NULL); + ret = poll(&fd, 1, timeout_ms); + gettimeofday(&after, NULL); + + if (ret == 0) { + ret = -ETIMEDOUT; + break; + } + + if (ret > 0) { + if (fd.revents & POLLIN) + ret = kdbus_msg_recv(conn, msg_out, offset); + + if (fd.revents & (POLLHUP | POLLERR)) + ret = -ECONNRESET; + } + + if (ret == 0 || ret != -EAGAIN) + break; + + timersub(&after, &before, &diff); + timeout_ms -= diff.tv_sec * 1000UL + + diff.tv_usec / 1000UL; + } while (timeout_ms > 0); + + return ret; +} + +int kdbus_free(const struct kdbus_conn *conn, uint64_t offset) +{ + struct kdbus_cmd_free cmd_free = {}; + int ret; + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = offset; + cmd_free.flags = 0; + + ret = kdbus_cmd_free(conn->fd, &cmd_free); + if (ret < 0) { + kdbus_printf("KDBUS_CMD_FREE failed: %d (%m)\n", ret); + return ret; + } + + return 0; +} + +int kdbus_name_acquire(struct kdbus_conn *conn, + const char *name, uint64_t *flags) +{ + struct kdbus_cmd *cmd_name; + size_t name_len = strlen(name) + 1; + uint64_t size = sizeof(*cmd_name) + KDBUS_ITEM_SIZE(name_len); + struct kdbus_item *item; + int ret; + + cmd_name = alloca(size); + + memset(cmd_name, 0, size); + + item = cmd_name->items; + item->size = KDBUS_ITEM_HEADER_SIZE + name_len; + item->type = KDBUS_ITEM_NAME; + strcpy(item->str, name); + + cmd_name->size = size; + if (flags) + cmd_name->flags = *flags; + + ret = kdbus_cmd_name_acquire(conn->fd, cmd_name); + if (ret < 0) { + kdbus_printf("error aquiring name: %s\n", strerror(-ret)); + return ret; + } + + kdbus_printf("%s(): flags after call: 0x%llx\n", __func__, + cmd_name->return_flags); + + if (flags) + *flags = cmd_name->return_flags; + + return 0; +} + +int kdbus_name_release(struct kdbus_conn *conn, const char *name) +{ + struct kdbus_cmd *cmd_name; + size_t name_len = strlen(name) + 1; + uint64_t size = sizeof(*cmd_name) + KDBUS_ITEM_SIZE(name_len); + struct kdbus_item *item; + int ret; + + cmd_name = alloca(size); + + memset(cmd_name, 0, size); + + item = cmd_name->items; + item->size = KDBUS_ITEM_HEADER_SIZE + name_len; + item->type = KDBUS_ITEM_NAME; + strcpy(item->str, name); + + cmd_name->size = size; + + kdbus_printf("conn %lld giving up name '%s'\n", + (unsigned long long) conn->id, name); + + ret = kdbus_cmd_name_release(conn->fd, cmd_name); + if (ret < 0) { + kdbus_printf("error releasing name: %s\n", strerror(-ret)); + return ret; + } + + return 0; +} + +int kdbus_list(struct kdbus_conn *conn, uint64_t flags) +{ + struct kdbus_cmd_list cmd_list = {}; + struct kdbus_info *list, *name; + int ret; + + cmd_list.size = sizeof(cmd_list); + cmd_list.flags = flags; + + ret = kdbus_cmd_list(conn->fd, &cmd_list); + if (ret < 0) { + kdbus_printf("error listing names: %d (%m)\n", ret); + return ret; + } + + kdbus_printf("REGISTRY:\n"); + list = (struct kdbus_info *)(conn->buf + cmd_list.offset); + + KDBUS_FOREACH(name, list, cmd_list.list_size) { + uint64_t flags = 0; + struct kdbus_item *item; + const char *n = "MISSING-NAME"; + + if (name->size == sizeof(struct kdbus_cmd)) + continue; + + KDBUS_ITEM_FOREACH(item, name, items) + if (item->type == KDBUS_ITEM_OWNED_NAME) { + n = item->name.name; + flags = item->name.flags; + } + + kdbus_printf("%8llu flags=0x%08llx conn=0x%08llx '%s'\n", + name->id, (unsigned long long) flags, + name->flags, n); + } + kdbus_printf("\n"); + + ret = kdbus_free(conn, cmd_list.offset); + + return ret; +} + +int kdbus_conn_update_attach_flags(struct kdbus_conn *conn, + uint64_t attach_flags_send, + uint64_t attach_flags_recv) +{ + int ret; + size_t size; + struct kdbus_cmd *update; + struct kdbus_item *item; + + size = sizeof(struct kdbus_cmd); + size += KDBUS_ITEM_SIZE(sizeof(uint64_t)) * 2; + + update = malloc(size); + if (!update) { + kdbus_printf("error malloc: %m\n"); + return -ENOMEM; + } + + memset(update, 0, size); + update->size = size; + + item = update->items; + + item->type = KDBUS_ITEM_ATTACH_FLAGS_SEND; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t); + item->data64[0] = attach_flags_send; + item = KDBUS_ITEM_NEXT(item); + + item->type = KDBUS_ITEM_ATTACH_FLAGS_RECV; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(uint64_t); + item->data64[0] = attach_flags_recv; + item = KDBUS_ITEM_NEXT(item); + + ret = kdbus_cmd_update(conn->fd, update); + if (ret < 0) + kdbus_printf("error conn update: %d (%m)\n", ret); + + free(update); + + return ret; +} + +int kdbus_conn_update_policy(struct kdbus_conn *conn, const char *name, + const struct kdbus_policy_access *access, + size_t num_access) +{ + struct kdbus_cmd *update; + struct kdbus_item *item; + size_t i, size; + int ret; + + size = sizeof(struct kdbus_cmd); + size += KDBUS_ITEM_SIZE(strlen(name) + 1); + size += num_access * KDBUS_ITEM_SIZE(sizeof(struct kdbus_policy_access)); + + update = malloc(size); + if (!update) { + kdbus_printf("error malloc: %m\n"); + return -ENOMEM; + } + + memset(update, 0, size); + update->size = size; + + item = update->items; + + item->type = KDBUS_ITEM_NAME; + item->size = KDBUS_ITEM_HEADER_SIZE + strlen(name) + 1; + strcpy(item->str, name); + item = KDBUS_ITEM_NEXT(item); + + for (i = 0; i < num_access; i++) { + item->size = KDBUS_ITEM_HEADER_SIZE + + sizeof(struct kdbus_policy_access); + item->type = KDBUS_ITEM_POLICY_ACCESS; + + item->policy_access.type = access[i].type; + item->policy_access.access = access[i].access; + item->policy_access.id = access[i].id; + + item = KDBUS_ITEM_NEXT(item); + } + + ret = kdbus_cmd_update(conn->fd, update); + if (ret < 0) + kdbus_printf("error conn update: %d (%m)\n", ret); + + free(update); + + return ret; +} + +int kdbus_add_match_id(struct kdbus_conn *conn, uint64_t cookie, + uint64_t type, uint64_t id) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + int ret; + + memset(&buf, 0, sizeof(buf)); + + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = cookie; + buf.item.size = sizeof(buf.item); + buf.item.type = type; + buf.item.chg.id = id; + + ret = kdbus_cmd_match_add(conn->fd, &buf.cmd); + if (ret < 0) + kdbus_printf("--- error adding conn match: %d (%m)\n", ret); + + return ret; +} + +int kdbus_add_match_empty(struct kdbus_conn *conn) +{ + struct { + struct kdbus_cmd_match cmd; + struct kdbus_item item; + } buf; + int ret; + + memset(&buf, 0, sizeof(buf)); + + buf.item.size = sizeof(uint64_t) * 3; + buf.item.type = KDBUS_ITEM_ID; + buf.item.id = KDBUS_MATCH_ID_ANY; + + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(conn->fd, &buf.cmd); + if (ret < 0) + kdbus_printf("--- error adding conn match: %d (%m)\n", ret); + + return ret; +} + +static int all_ids_are_mapped(const char *path) +{ + int ret; + FILE *file; + uint32_t inside_id, length; + + file = fopen(path, "r"); + if (!file) { + ret = -errno; + kdbus_printf("error fopen() %s: %d (%m)\n", + path, ret); + return ret; + } + + ret = fscanf(file, "%u\t%*u\t%u", &inside_id, &length); + if (ret != 2) { + if (ferror(file)) + ret = -errno; + else + ret = -EIO; + + kdbus_printf("--- error fscanf(): %d\n", ret); + fclose(file); + return ret; + } + + fclose(file); + + /* + * If length is 4294967295 which means the invalid uid + * (uid_t) -1 then we are able to map all uid/gids + */ + if (inside_id == 0 && length == (uid_t) -1) + return 1; + + return 0; +} + +int all_uids_gids_are_mapped() +{ + int ret; + + ret = all_ids_are_mapped("/proc/self/uid_map"); + if (ret <= 0) { + kdbus_printf("--- error not all uids are mapped\n"); + return 0; + } + + ret = all_ids_are_mapped("/proc/self/gid_map"); + if (ret <= 0) { + kdbus_printf("--- error not all gids are mapped\n"); + return 0; + } + + return 1; +} + +int drop_privileges(uid_t uid, gid_t gid) +{ + int ret; + + ret = setgroups(0, NULL); + if (ret < 0) { + ret = -errno; + kdbus_printf("error setgroups: %d (%m)\n", ret); + return ret; + } + + ret = setresgid(gid, gid, gid); + if (ret < 0) { + ret = -errno; + kdbus_printf("error setresgid: %d (%m)\n", ret); + return ret; + } + + ret = setresuid(uid, uid, uid); + if (ret < 0) { + ret = -errno; + kdbus_printf("error setresuid: %d (%m)\n", ret); + return ret; + } + + return ret; +} + +uint64_t now(clockid_t clock) +{ + struct timespec spec; + + clock_gettime(clock, &spec); + return spec.tv_sec * 1000ULL * 1000ULL * 1000ULL + spec.tv_nsec; +} + +char *unique_name(const char *prefix) +{ + unsigned int i; + uint64_t u_now; + char n[17]; + char *str; + int r; + + /* + * This returns a random string which is guaranteed to be + * globally unique across all calls to unique_name(). We + * compose the string as: + * <prefix>-<random>-<time> + * With: + * <prefix>: string provided by the caller + * <random>: a random alpha string of 16 characters + * <time>: the current time in micro-seconds since last boot + * + * The <random> part makes the string always look vastly different, + * the <time> part makes sure no two calls return the same string. + */ + + u_now = now(CLOCK_MONOTONIC); + + for (i = 0; i < sizeof(n) - 1; ++i) + n[i] = 'a' + (rand() % ('z' - 'a')); + n[sizeof(n) - 1] = 0; + + r = asprintf(&str, "%s-%s-%" PRIu64, prefix, n, u_now); + if (r < 0) + return NULL; + + return str; +} + +static int do_userns_map_id(pid_t pid, + const char *map_file, + const char *map_id) +{ + int ret; + int fd; + char *map; + unsigned int i; + + map = strndupa(map_id, strlen(map_id)); + if (!map) { + ret = -errno; + kdbus_printf("error strndupa %s: %d (%m)\n", + map_file, ret); + return ret; + } + + for (i = 0; i < strlen(map); i++) + if (map[i] == ',') + map[i] = '\n'; + + fd = open(map_file, O_RDWR); + if (fd < 0) { + ret = -errno; + kdbus_printf("error open %s: %d (%m)\n", + map_file, ret); + return ret; + } + + ret = write(fd, map, strlen(map)); + if (ret < 0) { + ret = -errno; + kdbus_printf("error write to %s: %d (%m)\n", + map_file, ret); + goto out; + } + + ret = 0; + +out: + close(fd); + return ret; +} + +int userns_map_uid_gid(pid_t pid, + const char *map_uid, + const char *map_gid) +{ + int fd, ret; + char file_id[128] = {'\0'}; + + snprintf(file_id, sizeof(file_id), "/proc/%ld/uid_map", + (long) pid); + + ret = do_userns_map_id(pid, file_id, map_uid); + if (ret < 0) + return ret; + + snprintf(file_id, sizeof(file_id), "/proc/%ld/setgroups", + (long) pid); + + fd = open(file_id, O_WRONLY); + if (fd >= 0) { + write(fd, "deny\n", 5); + close(fd); + } + + snprintf(file_id, sizeof(file_id), "/proc/%ld/gid_map", + (long) pid); + + return do_userns_map_id(pid, file_id, map_gid); +} + +static int do_cap_get_flag(cap_t caps, cap_value_t cap) +{ + int ret; + cap_flag_value_t flag_set; + + ret = cap_get_flag(caps, cap, CAP_EFFECTIVE, &flag_set); + if (ret < 0) { + ret = -errno; + kdbus_printf("error cap_get_flag(): %d (%m)\n", ret); + return ret; + } + + return (flag_set == CAP_SET); +} + +/* + * Returns: + * 1 in case all the requested effective capabilities are set. + * 0 in case we do not have the requested capabilities. This value + * will be used to abort tests with TEST_SKIP + * Negative errno on failure. + * + * Terminate args with a negative value. + */ +int test_is_capable(int cap, ...) +{ + int ret; + va_list ap; + cap_t caps; + + caps = cap_get_proc(); + if (!cap) { + ret = -errno; + kdbus_printf("error cap_get_proc(): %d (%m)\n", ret); + return ret; + } + + ret = do_cap_get_flag(caps, (cap_value_t)cap); + if (ret <= 0) + goto out; + + va_start(ap, cap); + while ((cap = va_arg(ap, int)) > 0) { + ret = do_cap_get_flag(caps, (cap_value_t)cap); + if (ret <= 0) + break; + } + va_end(ap); + +out: + cap_free(caps); + return ret; +} + +int config_user_ns_is_enabled(void) +{ + return (access("/proc/self/uid_map", F_OK) == 0); +} + +int config_auditsyscall_is_enabled(void) +{ + return (access("/proc/self/loginuid", F_OK) == 0); +} + +int config_cgroups_is_enabled(void) +{ + return (access("/proc/self/cgroup", F_OK) == 0); +} + +int config_security_is_enabled(void) +{ + int fd; + int ret; + char buf[128]; + + /* CONFIG_SECURITY is disabled */ + if (access("/proc/self/attr/current", F_OK) != 0) + return 0; + + /* + * Now only if read() fails with -EINVAL then we assume + * that SECLABEL and LSM are disabled + */ + fd = open("/proc/self/attr/current", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return 1; + + ret = read(fd, buf, sizeof(buf)); + if (ret == -1 && errno == EINVAL) + ret = 0; + else + ret = 1; + + close(fd); + + return ret; +} diff --git a/tools/testing/selftests/kdbus/kdbus-util.h b/tools/testing/selftests/kdbus/kdbus-util.h new file mode 100644 index 000000000000..652d957eac95 --- /dev/null +++ b/tools/testing/selftests/kdbus/kdbus-util.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Daniel Mack + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ +#pragma once + +#define BIT(X) (1 << (X)) + +#include <time.h> +#include <stdbool.h> +#include <linux/kdbus.h> + +#define _STRINGIFY(x) #x +#define STRINGIFY(x) _STRINGIFY(x) +#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +#define KDBUS_PTR(addr) ((void *)(uintptr_t)(addr)) + +#define KDBUS_ALIGN8(l) (((l) + 7) & ~7) +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(s) KDBUS_ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) + +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))(((uint8_t *)item) + KDBUS_ALIGN8((item)->size)) +#define KDBUS_ITEM_FOREACH(item, head, first) \ + for (item = (head)->first; \ + ((uint8_t *)(item) < (uint8_t *)(head) + (head)->size) && \ + ((uint8_t *)(item) >= (uint8_t *)(head)); \ + item = KDBUS_ITEM_NEXT(item)) +#define KDBUS_FOREACH(iter, first, _size) \ + for (iter = (first); \ + ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) && \ + ((uint8_t *)(iter) >= (uint8_t *)(first)); \ + iter = (void*)(((uint8_t *)iter) + KDBUS_ALIGN8((iter)->size))) + + +#define _KDBUS_ATTACH_BITS_SET_NR (__builtin_popcountll(_KDBUS_ATTACH_ALL)) + +/* Sum of KDBUS_ITEM_* that reflects _KDBUS_ATTACH_ALL */ +#define KDBUS_ATTACH_ITEMS_TYPE_SUM \ + ((((_KDBUS_ATTACH_BITS_SET_NR - 1) * \ + ((_KDBUS_ATTACH_BITS_SET_NR - 1) + 1)) / 2 ) + \ + (_KDBUS_ITEM_ATTACH_BASE * _KDBUS_ATTACH_BITS_SET_NR)) + + +#define POOL_SIZE (16 * 1024LU * 1024LU) + +#define UNPRIV_UID 65534 +#define UNPRIV_GID 65534 + +/* Dump as user of process, useful for user namespace testing */ +#define SUID_DUMP_USER 1 + +extern int kdbus_util_verbose; + +#define kdbus_printf(X...) \ + if (kdbus_util_verbose) \ + printf(X) + +#define RUN_UNPRIVILEGED(child_uid, child_gid, _child_, _parent_) ({ \ + pid_t pid, rpid; \ + int ret; \ + \ + pid = fork(); \ + if (pid == 0) { \ + ret = drop_privileges(child_uid, child_gid); \ + ASSERT_EXIT_VAL(ret == 0, ret); \ + \ + _child_; \ + _exit(0); \ + } else if (pid > 0) { \ + _parent_; \ + rpid = waitpid(pid, &ret, 0); \ + ASSERT_RETURN(rpid == pid); \ + ASSERT_RETURN(WIFEXITED(ret)); \ + ASSERT_RETURN(WEXITSTATUS(ret) == 0); \ + ret = TEST_OK; \ + } else { \ + ret = pid; \ + } \ + \ + ret; \ + }) + +#define RUN_UNPRIVILEGED_CONN(_var_, _bus_, _code_) \ + RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ \ + struct kdbus_conn *_var_; \ + _var_ = kdbus_hello(_bus_, 0, NULL, 0); \ + ASSERT_EXIT(_var_); \ + _code_; \ + kdbus_conn_free(_var_); \ + }), ({ 0; })) + +#define RUN_CLONE_CHILD(clone_ret, flags, _setup_, _child_body_, \ + _parent_setup_, _parent_body_) ({ \ + pid_t pid, rpid; \ + int ret; \ + int efd = -1; \ + \ + _setup_; \ + efd = eventfd(0, EFD_CLOEXEC); \ + ASSERT_RETURN(efd >= 0); \ + *clone_ret = 0; \ + pid = syscall(__NR_clone, flags, NULL); \ + if (pid == 0) { \ + eventfd_t event_status = 0; \ + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); \ + ASSERT_EXIT(ret == 0); \ + ret = eventfd_read(efd, &event_status); \ + if (ret < 0 || event_status != 1) { \ + kdbus_printf("error eventfd_read()\n"); \ + _exit(EXIT_FAILURE); \ + } \ + _child_body_; \ + _exit(0); \ + } else if (pid > 0) { \ + _parent_setup_; \ + ret = eventfd_write(efd, 1); \ + ASSERT_RETURN(ret >= 0); \ + _parent_body_; \ + rpid = waitpid(pid, &ret, 0); \ + ASSERT_RETURN(rpid == pid); \ + ASSERT_RETURN(WIFEXITED(ret)); \ + ASSERT_RETURN(WEXITSTATUS(ret) == 0); \ + ret = TEST_OK; \ + } else { \ + ret = -errno; \ + *clone_ret = -errno; \ + } \ + close(efd); \ + ret; \ +}) + +/* Enums for parent if it should drop privs or not */ +enum kdbus_drop_parent { + DO_NOT_DROP, + DROP_SAME_UNPRIV, + DROP_OTHER_UNPRIV, +}; + +struct kdbus_conn { + int fd; + uint64_t id; + unsigned char *buf; +}; + +int kdbus_sysfs_get_parameter_mask(const char *path, uint64_t *mask); +int kdbus_sysfs_set_parameter_mask(const char *path, uint64_t mask); + +int sys_memfd_create(const char *name, __u64 size); +int sys_memfd_seal_set(int fd); +off_t sys_memfd_get_size(int fd, off_t *size); + +int kdbus_list(struct kdbus_conn *conn, uint64_t flags); +int kdbus_name_release(struct kdbus_conn *conn, const char *name); +int kdbus_name_acquire(struct kdbus_conn *conn, const char *name, + uint64_t *flags); +void kdbus_msg_free(struct kdbus_msg *msg); +int kdbus_msg_recv(struct kdbus_conn *conn, + struct kdbus_msg **msg, uint64_t *offset); +int kdbus_msg_recv_poll(struct kdbus_conn *conn, int timeout_ms, + struct kdbus_msg **msg_out, uint64_t *offset); +int kdbus_free(const struct kdbus_conn *conn, uint64_t offset); +int kdbus_msg_dump(const struct kdbus_conn *conn, + const struct kdbus_msg *msg); +int kdbus_create_bus(int control_fd, const char *name, + uint64_t req_meta, uint64_t owner_meta, + char **path); +int kdbus_msg_send(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id, int fds_count, int fds[]); +int kdbus_msg_send_sync(const struct kdbus_conn *conn, const char *name, + uint64_t cookie, uint64_t flags, uint64_t timeout, + int64_t priority, uint64_t dst_id, int cancel_fd); +int kdbus_msg_send_reply(const struct kdbus_conn *conn, + uint64_t reply_cookie, + uint64_t dst_id); +struct kdbus_conn *kdbus_hello(const char *path, uint64_t hello_flags, + const struct kdbus_item *item, + size_t item_size); +struct kdbus_conn *kdbus_hello_registrar(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access, uint64_t flags); +struct kdbus_conn *kdbus_hello_activator(const char *path, const char *name, + const struct kdbus_policy_access *access, + size_t num_access); +bool kdbus_item_in_message(struct kdbus_msg *msg, uint64_t type); +int kdbus_bus_creator_info(struct kdbus_conn *conn, + uint64_t flags, + uint64_t *offset); +int kdbus_conn_info(struct kdbus_conn *conn, uint64_t id, + const char *name, uint64_t flags, uint64_t *offset); +void kdbus_conn_free(struct kdbus_conn *conn); +int kdbus_conn_update_attach_flags(struct kdbus_conn *conn, + uint64_t attach_flags_send, + uint64_t attach_flags_recv); +int kdbus_conn_update_policy(struct kdbus_conn *conn, const char *name, + const struct kdbus_policy_access *access, + size_t num_access); + +int kdbus_add_match_id(struct kdbus_conn *conn, uint64_t cookie, + uint64_t type, uint64_t id); +int kdbus_add_match_empty(struct kdbus_conn *conn); + +int all_uids_gids_are_mapped(); +int drop_privileges(uid_t uid, gid_t gid); +uint64_t now(clockid_t clock); +char *unique_name(const char *prefix); + +int userns_map_uid_gid(pid_t pid, + const char *map_uid, + const char *map_gid); +int test_is_capable(int cap, ...); +int config_user_ns_is_enabled(void); +int config_auditsyscall_is_enabled(void); +int config_cgroups_is_enabled(void); +int config_security_is_enabled(void); diff --git a/tools/testing/selftests/kdbus/test-activator.c b/tools/testing/selftests/kdbus/test-activator.c new file mode 100644 index 000000000000..c70fafe83759 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-activator.c @@ -0,0 +1,320 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <sys/capability.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static int kdbus_starter_poll(struct kdbus_conn *conn) +{ + int ret; + struct pollfd fd; + + fd.fd = conn->fd; + fd.events = POLLIN | POLLPRI | POLLHUP; + fd.revents = 0; + + ret = poll(&fd, 1, 100); + if (ret == 0) + return -ETIMEDOUT; + else if (ret > 0) { + if (fd.revents & POLLIN) + return 0; + + if (fd.revents & (POLLHUP | POLLERR)) + ret = -ECONNRESET; + } + + return ret; +} + +/* Ensure that kdbus activator logic is safe */ +static int kdbus_priv_activator(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_msg *msg = NULL; + uint64_t cookie = 0xdeadbeef; + uint64_t flags = KDBUS_NAME_REPLACE_EXISTING; + struct kdbus_conn *activator; + struct kdbus_conn *service; + struct kdbus_conn *client; + struct kdbus_conn *holder; + struct kdbus_policy_access *access; + + access = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = getuid(), + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = getuid(), + .access = KDBUS_POLICY_TALK, + }, + }; + + activator = kdbus_hello_activator(env->buspath, "foo.priv.activator", + access, 2); + ASSERT_RETURN(activator); + + service = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(service); + + client = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(client); + + /* + * Make sure that other users can't TALK to the activator + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk using the ID */ + ret = kdbus_msg_send(unpriv, NULL, 0xdeadbeef, 0, 0, + 0, activator->id, 0, NULL); + ASSERT_EXIT(ret == -ENXIO); + + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + 0xdeadbeef, 0, 0, 0, + KDBUS_DST_ID_NAME, 0, NULL); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure that we did not receive anything, so the + * service will not be started automatically + */ + + ret = kdbus_starter_poll(activator); + ASSERT_RETURN(ret == -ETIMEDOUT); + + /* + * Now try to emulate the starter/service logic and + * acquire the name. + */ + + cookie++; + ret = kdbus_msg_send(service, "foo.priv.activator", cookie, + 0, 0, 0, KDBUS_DST_ID_NAME, 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_starter_poll(activator); + ASSERT_RETURN(ret == 0); + + /* Policies are still checked, access denied */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "foo.priv.activator", + &flags); + ASSERT_RETURN(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_name_acquire(service, "foo.priv.activator", + &flags); + ASSERT_RETURN(ret == 0); + + /* We read our previous starter message */ + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + /* Try to talk, we still fail */ + + cookie++; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + cookie, 0, 0, 0, + KDBUS_DST_ID_NAME, 0, NULL); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* Still nothing to read */ + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + /* We receive every thing now */ + + cookie++; + ret = kdbus_msg_send(client, "foo.priv.activator", cookie, + 0, 0, 0, KDBUS_DST_ID_NAME, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_recv_poll(service, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* Policies default to deny TALK now */ + kdbus_conn_free(activator); + + cookie++; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + cookie, 0, 0, 0, + KDBUS_DST_ID_NAME, + 0, NULL); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + /* Same user is able to TALK */ + cookie++; + ret = kdbus_msg_send(client, "foo.priv.activator", cookie, + 0, 0, 0, KDBUS_DST_ID_NAME, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_recv_poll(service, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + access = (struct kdbus_policy_access []){ + { + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = getuid(), + .access = KDBUS_POLICY_TALK, + }, + }; + + holder = kdbus_hello_registrar(env->buspath, "foo.priv.activator", + access, 1, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder); + + /* Now we are able to TALK to the name */ + + cookie++; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + /* Try to talk to the name */ + ret = kdbus_msg_send(unpriv, "foo.priv.activator", + cookie, 0, 0, 0, + KDBUS_DST_ID_NAME, + 0, NULL); + ASSERT_EXIT(ret == 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(service, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "foo.priv.activator", + &flags); + ASSERT_RETURN(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + kdbus_conn_free(service); + kdbus_conn_free(client); + kdbus_conn_free(holder); + + return 0; +} + +int kdbus_test_activator(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_conn *activator; + struct pollfd fds[2]; + bool activator_done = false; + struct kdbus_policy_access access[2]; + + access[0].type = KDBUS_POLICY_ACCESS_USER; + access[0].id = getuid(); + access[0].access = KDBUS_POLICY_OWN; + + access[1].type = KDBUS_POLICY_ACCESS_WORLD; + access[1].access = KDBUS_POLICY_TALK; + + activator = kdbus_hello_activator(env->buspath, "foo.test.activator", + access, 2); + ASSERT_RETURN(activator); + + ret = kdbus_add_match_empty(env->conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_list(env->conn, KDBUS_LIST_NAMES | + KDBUS_LIST_UNIQUE | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_send(env->conn, "foo.test.activator", 0xdeafbeef, + 0, 0, 0, KDBUS_DST_ID_NAME, 0, NULL); + ASSERT_RETURN(ret == 0); + + fds[0].fd = activator->fd; + fds[1].fd = env->conn->fd; + + kdbus_printf("-- entering poll loop ...\n"); + + for (;;) { + int i, nfds = sizeof(fds) / sizeof(fds[0]); + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 3000); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_list(env->conn, KDBUS_LIST_NAMES); + ASSERT_RETURN(ret == 0); + + if ((fds[0].revents & POLLIN) && !activator_done) { + uint64_t flags = KDBUS_NAME_REPLACE_EXISTING; + + kdbus_printf("Starter was called back!\n"); + + ret = kdbus_name_acquire(env->conn, + "foo.test.activator", &flags); + ASSERT_RETURN(ret == 0); + + activator_done = true; + } + + if (fds[1].revents & POLLIN) { + kdbus_msg_recv(env->conn, NULL, NULL); + break; + } + } + + /* Check if all uids/gids are mapped */ + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + /* Check now capabilities, so we run the previous tests */ + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + if (!ret) + return TEST_SKIP; + + ret = kdbus_priv_activator(env); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(activator); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-attach-flags.c b/tools/testing/selftests/kdbus/test-attach-flags.c new file mode 100644 index 000000000000..deee7c332f25 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-attach-flags.c @@ -0,0 +1,750 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <sys/capability.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <linux/unistd.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +/* + * Should be the sum of the currently supported and compiled-in + * KDBUS_ITEMS_* that reflect KDBUS_ATTACH_* flags. + */ +static unsigned int KDBUS_TEST_ITEMS_SUM = KDBUS_ATTACH_ITEMS_TYPE_SUM; + +static struct kdbus_conn *__kdbus_hello(const char *path, uint64_t flags, + uint64_t attach_flags_send, + uint64_t attach_flags_recv) +{ + struct kdbus_cmd_free cmd_free = {}; + int ret, fd; + struct kdbus_conn *conn; + struct { + struct kdbus_cmd_hello hello; + + struct { + uint64_t size; + uint64_t type; + char str[16]; + } conn_name; + + uint8_t extra_items[0]; + } h; + + memset(&h, 0, sizeof(h)); + + kdbus_printf("-- opening bus connection %s\n", path); + fd = open(path, O_RDWR|O_CLOEXEC); + if (fd < 0) { + kdbus_printf("--- error %d (%m)\n", fd); + return NULL; + } + + h.hello.flags = flags | KDBUS_HELLO_ACCEPT_FD; + h.hello.attach_flags_send = attach_flags_send; + h.hello.attach_flags_recv = attach_flags_recv; + h.conn_name.type = KDBUS_ITEM_CONN_DESCRIPTION; + strcpy(h.conn_name.str, "this-is-my-name"); + h.conn_name.size = KDBUS_ITEM_HEADER_SIZE + strlen(h.conn_name.str) + 1; + + h.hello.size = sizeof(h); + h.hello.pool_size = POOL_SIZE; + + ret = kdbus_cmd_hello(fd, (struct kdbus_cmd_hello *) &h.hello); + if (ret < 0) { + kdbus_printf("--- error when saying hello: %d (%m)\n", ret); + return NULL; + } + + kdbus_printf("-- New connection ID : %llu\n", + (unsigned long long)h.hello.id); + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = h.hello.offset; + ret = kdbus_cmd_free(fd, &cmd_free); + if (ret < 0) + return NULL; + + conn = malloc(sizeof(*conn)); + if (!conn) { + kdbus_printf("unable to malloc()!?\n"); + return NULL; + } + + conn->buf = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, fd, 0); + if (conn->buf == MAP_FAILED) { + ret = -errno; + free(conn); + close(fd); + kdbus_printf("--- error mmap: %d (%m)\n", ret); + return NULL; + } + + conn->fd = fd; + conn->id = h.hello.id; + return conn; +} + +static int kdbus_test_peers_creation(struct kdbus_test_env *env) +{ + int ret; + int control_fd; + char *path; + char *busname; + char buspath[2048]; + char control_path[2048]; + uint64_t attach_flags_mask; + struct kdbus_conn *conn; + + snprintf(control_path, sizeof(control_path), + "%s/control", env->root); + + /* + * Set kdbus system-wide mask to 0, this has nothing + * to do with the following tests, bus and connection + * creation nor connection update, but we do it so we are + * sure that everything work as expected + */ + + attach_flags_mask = 0; + ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, + attach_flags_mask); + ASSERT_RETURN(ret == 0); + + + /* + * Create bus with a full set of ATTACH flags + */ + + control_fd = open(control_path, O_RDWR); + ASSERT_RETURN(control_fd >= 0); + + busname = unique_name("test-peers-creation-bus"); + ASSERT_RETURN(busname); + + ret = kdbus_create_bus(control_fd, busname, _KDBUS_ATTACH_ALL, + 0, &path); + ASSERT_RETURN(ret == 0); + + snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); + + /* + * Create a connection with an empty send attach flags, or + * with just KDBUS_ATTACH_CREDS, this should fail + */ + conn = __kdbus_hello(buspath, 0, 0, 0); + ASSERT_RETURN(conn == NULL); + ASSERT_RETURN(errno == ECONNREFUSED); + + conn = __kdbus_hello(buspath, 0, KDBUS_ATTACH_CREDS, + _KDBUS_ATTACH_ALL); + ASSERT_RETURN(conn == NULL); + ASSERT_RETURN(errno == ECONNREFUSED); + + conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); + ASSERT_RETURN(conn); + + /* Try to cut back some send attach flags */ + ret = kdbus_conn_update_attach_flags(conn, + KDBUS_ATTACH_CREDS| + KDBUS_ATTACH_PIDS, + _KDBUS_ATTACH_ALL); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_conn_update_attach_flags(conn, + _KDBUS_ATTACH_ALL, 0); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + free(path); + free(busname); + close(control_fd); + + + /* Test a new bus with KDBUS_ATTACH_PIDS */ + + control_fd = open(control_path, O_RDWR); + ASSERT_RETURN(control_fd >= 0); + + busname = unique_name("test-peer-flags-bus"); + ASSERT_RETURN(busname); + + ret = kdbus_create_bus(control_fd, busname, KDBUS_ATTACH_PIDS, + 0, &path); + ASSERT_RETURN(ret == 0); + + snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); + + /* + * Create a connection with an empty send attach flags, or + * all flags except KDBUS_ATTACH_PIDS + */ + conn = __kdbus_hello(buspath, 0, 0, 0); + ASSERT_RETURN(conn == NULL); + ASSERT_RETURN(errno == ECONNREFUSED); + + conn = __kdbus_hello(buspath, 0, + _KDBUS_ATTACH_ALL & ~KDBUS_ATTACH_PIDS, + _KDBUS_ATTACH_ALL); + ASSERT_RETURN(conn == NULL); + ASSERT_RETURN(errno == ECONNREFUSED); + + /* The following should succeed */ + conn = __kdbus_hello(buspath, 0, KDBUS_ATTACH_PIDS, 0); + ASSERT_RETURN(conn); + kdbus_conn_free(conn); + + conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_conn_update_attach_flags(conn, + _KDBUS_ATTACH_ALL & + ~KDBUS_ATTACH_PIDS, + _KDBUS_ATTACH_ALL); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_conn_update_attach_flags(conn, 0, + _KDBUS_ATTACH_ALL); + ASSERT_RETURN(ret == -EINVAL); + + /* Now we want only KDBUS_ATTACH_PIDS */ + ret = kdbus_conn_update_attach_flags(conn, + KDBUS_ATTACH_PIDS, 0); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + free(path); + free(busname); + close(control_fd); + + + /* + * Create bus with 0 as ATTACH flags, the bus does not + * require any attach flags + */ + + control_fd = open(control_path, O_RDWR); + ASSERT_RETURN(control_fd >= 0); + + busname = unique_name("test-peer-flags-bus"); + ASSERT_RETURN(busname); + + ret = kdbus_create_bus(control_fd, busname, 0, 0, &path); + ASSERT_RETURN(ret == 0); + + snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); + + /* Bus is open it does not require any send attach flags */ + conn = __kdbus_hello(buspath, 0, 0, 0); + ASSERT_RETURN(conn); + kdbus_conn_free(conn); + + conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_conn_update_attach_flags(conn, 0, 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_attach_flags(conn, KDBUS_ATTACH_CREDS, 0); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + free(path); + free(busname); + close(control_fd); + + return 0; +} + +static int kdbus_test_peers_info(struct kdbus_test_env *env) +{ + int ret; + int control_fd; + char *path; + char *busname; + unsigned int i = 0; + uint64_t offset = 0; + char buspath[2048]; + char control_path[2048]; + uint64_t attach_flags_mask; + struct kdbus_item *item; + struct kdbus_info *info; + struct kdbus_conn *conn; + struct kdbus_conn *reader; + unsigned long long attach_count = 0; + + snprintf(control_path, sizeof(control_path), + "%s/control", env->root); + + attach_flags_mask = 0; + ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, + attach_flags_mask); + ASSERT_RETURN(ret == 0); + + control_fd = open(control_path, O_RDWR); + ASSERT_RETURN(control_fd >= 0); + + busname = unique_name("test-peers-info-bus"); + ASSERT_RETURN(busname); + + ret = kdbus_create_bus(control_fd, busname, _KDBUS_ATTACH_ALL, + 0, &path); + ASSERT_RETURN(ret == 0); + + snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); + + /* Create connections with the appropriate flags */ + conn = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); + ASSERT_RETURN(conn); + + reader = __kdbus_hello(buspath, 0, _KDBUS_ATTACH_ALL, 0); + ASSERT_RETURN(reader); + + ret = kdbus_conn_info(reader, conn->id, NULL, + _KDBUS_ATTACH_ALL, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(reader->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + /* all attach flags are masked, no metadata */ + KDBUS_ITEM_FOREACH(item, info, items) + i++; + + ASSERT_RETURN(i == 0); + + kdbus_free(reader, offset); + + /* Set the mask to _KDBUS_ATTACH_ANY */ + attach_flags_mask = _KDBUS_ATTACH_ANY; + ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, + attach_flags_mask); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_info(reader, conn->id, NULL, + _KDBUS_ATTACH_ALL, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(reader->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + attach_count = 0; + KDBUS_ITEM_FOREACH(item, info, items) + attach_count += item->type; + + /* + * All flags have been returned except for: + * KDBUS_ITEM_TIMESTAMP and + * KDBUS_ITEM_OWNED_NAME we do not own any name. + */ + ASSERT_RETURN(attach_count == (KDBUS_TEST_ITEMS_SUM - + KDBUS_ITEM_OWNED_NAME - + KDBUS_ITEM_TIMESTAMP)); + + kdbus_free(reader, offset); + + /* Request only OWNED names */ + ret = kdbus_conn_info(reader, conn->id, NULL, + KDBUS_ATTACH_NAMES, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(reader->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + attach_count = 0; + KDBUS_ITEM_FOREACH(item, info, items) + attach_count += item->type; + + /* we should not get any metadata since we do not own names */ + ASSERT_RETURN(attach_count == 0); + + kdbus_free(reader, offset); + + kdbus_conn_free(conn); + kdbus_conn_free(reader); + + return 0; +} + +/** + * @kdbus_mask_param: kdbus module mask parameter (system-wide) + * @requested_meta: The bus owner metadata that we want + * @expected_items: The returned KDBUS_ITEMS_* sum. Used to + * validate the returned metadata items + */ +static int kdbus_cmp_bus_creator_metadata(struct kdbus_test_env *env, + struct kdbus_conn *conn, + uint64_t kdbus_mask_param, + uint64_t requested_meta, + unsigned long expected_items) +{ + int ret; + uint64_t offset = 0; + struct kdbus_info *info; + struct kdbus_item *item; + unsigned long attach_count = 0; + + ret = kdbus_sysfs_set_parameter_mask(env->mask_param_path, + kdbus_mask_param); + ASSERT_RETURN(ret == 0); + + ret = kdbus_bus_creator_info(conn, requested_meta, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + + KDBUS_ITEM_FOREACH(item, info, items) + attach_count += item->type; + + ASSERT_RETURN(attach_count == expected_items); + + ret = kdbus_free(conn, offset); + ASSERT_RETURN(ret == 0); + + return 0; +} + +static int kdbus_test_bus_creator_info(struct kdbus_test_env *env) +{ + int ret; + int control_fd; + char *path; + char *busname; + char buspath[2048]; + char control_path[2048]; + uint64_t attach_flags_mask; + struct kdbus_conn *conn; + unsigned long expected_items = 0; + + snprintf(control_path, sizeof(control_path), + "%s/control", env->root); + + control_fd = open(control_path, O_RDWR); + ASSERT_RETURN(control_fd >= 0); + + busname = unique_name("test-peers-info-bus"); + ASSERT_RETURN(busname); + + /* + * Now the bus allows us to see all its KDBUS_ATTACH_* + * items + */ + ret = kdbus_create_bus(control_fd, busname, 0, + _KDBUS_ATTACH_ALL, &path); + ASSERT_RETURN(ret == 0); + + snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); + + conn = __kdbus_hello(buspath, 0, 0, 0); + ASSERT_RETURN(conn); + + /* + * Start with a kdbus module mask set to _KDBUS_ATTACH_ANY + */ + attach_flags_mask = _KDBUS_ATTACH_ANY; + + /* + * All flags will be returned except for: + * KDBUS_ITEM_TIMESTAMP + * KDBUS_ITEM_OWNED_NAME + * KDBUS_ITEM_CONN_DESCRIPTION + * + * An extra flags is always returned KDBUS_ITEM_MAKE_NAME + * which contains the bus name + */ + expected_items = KDBUS_TEST_ITEMS_SUM + KDBUS_ITEM_MAKE_NAME; + expected_items -= KDBUS_ITEM_TIMESTAMP + + KDBUS_ITEM_OWNED_NAME + + KDBUS_ITEM_CONN_DESCRIPTION; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + /* + * We should have: + * KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + KDBUS_ITEM_MAKE_NAME + */ + expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + + KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_CREDS, + expected_items); + ASSERT_RETURN(ret == 0); + + /* KDBUS_ITEM_MAKE_NAME is always returned */ + expected_items = KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + 0, expected_items); + ASSERT_RETURN(ret == 0); + + /* + * Restrict kdbus system-wide mask to KDBUS_ATTACH_PIDS + */ + + attach_flags_mask = KDBUS_ATTACH_PIDS; + + /* + * We should have: + * KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME + */ + expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + + /* system-wide mask to 0 */ + attach_flags_mask = 0; + + /* we should only see: KDBUS_ITEM_MAKE_NAME */ + expected_items = KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + free(path); + free(busname); + close(control_fd); + + + /* + * A new bus that hides all its owner metadata + */ + + control_fd = open(control_path, O_RDWR); + ASSERT_RETURN(control_fd >= 0); + + busname = unique_name("test-peers-info-bus"); + ASSERT_RETURN(busname); + + ret = kdbus_create_bus(control_fd, busname, 0, 0, &path); + ASSERT_RETURN(ret == 0); + + snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); + + conn = __kdbus_hello(buspath, 0, 0, 0); + ASSERT_RETURN(conn); + + /* + * Start with a kdbus module mask set to _KDBUS_ATTACH_ANY + */ + attach_flags_mask = _KDBUS_ATTACH_ANY; + + /* + * We only get the KDBUS_ITEM_MAKE_NAME + */ + expected_items = KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + /* + * We still get only kdbus_ITEM_MAKE_NAME + */ + attach_flags_mask = 0; + expected_items = KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + free(path); + free(busname); + close(control_fd); + + + /* + * A new bus that shows only the PID and CREDS metadata + * of the bus owner. + */ + control_fd = open(control_path, O_RDWR); + ASSERT_RETURN(control_fd >= 0); + + busname = unique_name("test-peers-info-bus"); + ASSERT_RETURN(busname); + + ret = kdbus_create_bus(control_fd, busname, 0, + KDBUS_ATTACH_PIDS| + KDBUS_ATTACH_CREDS, &path); + ASSERT_RETURN(ret == 0); + + snprintf(buspath, sizeof(buspath), "%s/%s/bus", env->root, path); + + conn = __kdbus_hello(buspath, 0, 0, 0); + ASSERT_RETURN(conn); + + /* + * Start with a kdbus module mask set to _KDBUS_ATTACH_ANY + */ + attach_flags_mask = _KDBUS_ATTACH_ANY; + + /* + * We should have: + * KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + KDBUS_ITEM_MAKE_NAME + */ + expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_CREDS + + KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + expected_items = KDBUS_ITEM_CREDS + KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + KDBUS_ATTACH_CREDS, + expected_items); + ASSERT_RETURN(ret == 0); + + /* KDBUS_ITEM_MAKE_NAME is always returned */ + expected_items = KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + 0, expected_items); + ASSERT_RETURN(ret == 0); + + /* + * Restrict kdbus system-wide mask to KDBUS_ATTACH_PIDS + */ + + attach_flags_mask = KDBUS_ATTACH_PIDS; + /* + * We should have: + * KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME + */ + expected_items = KDBUS_ITEM_PIDS + KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + /* No KDBUS_ATTACH_CREDS */ + expected_items = KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + KDBUS_ATTACH_CREDS, + expected_items); + ASSERT_RETURN(ret == 0); + + /* system-wide mask to 0 */ + attach_flags_mask = 0; + + /* we should only see: KDBUS_ITEM_MAKE_NAME */ + expected_items = KDBUS_ITEM_MAKE_NAME; + ret = kdbus_cmp_bus_creator_metadata(env, conn, + attach_flags_mask, + _KDBUS_ATTACH_ALL, + expected_items); + ASSERT_RETURN(ret == 0); + + + kdbus_conn_free(conn); + free(path); + free(busname); + close(control_fd); + + return 0; +} + +int kdbus_test_attach_flags(struct kdbus_test_env *env) +{ + int ret; + uint64_t flags_mask; + uint64_t old_kdbus_flags_mask; + + /* We need CAP_DAC_OVERRIDE to overwrite the kdbus mask */ + ret = test_is_capable(CAP_DAC_OVERRIDE, -1); + ASSERT_RETURN(ret >= 0); + + /* no enough privileges, SKIP test */ + if (!ret) + return TEST_SKIP; + + /* + * We need to be able to write to + * "/sys/module/kdbus/parameters/attach_flags_mask" + * perhaps we are unprvileged/privileged in its userns + */ + ret = access(env->mask_param_path, W_OK); + if (ret < 0) { + kdbus_printf("--- access() '%s' failed: %d (%m)\n", + env->mask_param_path, -errno); + return TEST_SKIP; + } + + ret = kdbus_sysfs_get_parameter_mask(env->mask_param_path, + &old_kdbus_flags_mask); + ASSERT_RETURN(ret == 0); + + /* setup the right KDBUS_TEST_ITEMS_SUM */ + if (!config_auditsyscall_is_enabled()) + KDBUS_TEST_ITEMS_SUM -= KDBUS_ITEM_AUDIT; + + if (!config_cgroups_is_enabled()) + KDBUS_TEST_ITEMS_SUM -= KDBUS_ITEM_CGROUP; + + if (!config_security_is_enabled()) + KDBUS_TEST_ITEMS_SUM -= KDBUS_ITEM_SECLABEL; + + /* + * Test the connection creation attach flags + */ + ret = kdbus_test_peers_creation(env); + /* Restore previous kdbus mask */ + kdbus_sysfs_set_parameter_mask(env->mask_param_path, + old_kdbus_flags_mask); + ASSERT_RETURN(ret == 0); + + /* + * Test the CONN_INFO attach flags + */ + ret = kdbus_test_peers_info(env); + /* Restore previous kdbus mask */ + kdbus_sysfs_set_parameter_mask(env->mask_param_path, + old_kdbus_flags_mask); + ASSERT_RETURN(ret == 0); + + /* + * Test the Bus creator info and its attach flags + */ + ret = kdbus_test_bus_creator_info(env); + /* Restore previous kdbus mask */ + kdbus_sysfs_set_parameter_mask(env->mask_param_path, + old_kdbus_flags_mask); + ASSERT_RETURN(ret == 0); + + ret = kdbus_sysfs_get_parameter_mask(env->mask_param_path, + &flags_mask); + ASSERT_RETURN(ret == 0 && old_kdbus_flags_mask == flags_mask); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-benchmark.c b/tools/testing/selftests/kdbus/test-benchmark.c new file mode 100644 index 000000000000..8a9744b00508 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-benchmark.c @@ -0,0 +1,451 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <locale.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <sys/time.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <math.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#define SERVICE_NAME "foo.bar.echo" + +/* + * To have a banchmark comparison with unix socket, set: + * user_memfd = false; + * compare_uds = true; + * attach_none = true; do not attached metadata + */ + +static bool use_memfd = true; /* transmit memfd? */ +static bool compare_uds = false; /* unix-socket comparison? */ +static bool attach_none = false; /* clear attach-flags? */ +static char stress_payload[8192]; + +struct stats { + uint64_t count; + uint64_t latency_acc; + uint64_t latency_low; + uint64_t latency_high; + uint64_t latency_avg; + uint64_t latency_ssquares; +}; + +static struct stats stats; + +static void reset_stats(void) +{ + stats.count = 0; + stats.latency_acc = 0; + stats.latency_low = UINT64_MAX; + stats.latency_high = 0; + stats.latency_avg = 0; + stats.latency_ssquares = 0; +} + +static void dump_stats(bool is_uds) +{ + if (stats.count > 0) { + kdbus_printf("stats %s: %'llu packets processed, latency (nsecs) min/max/avg/dev %'7llu // %'7llu // %'7llu // %'7.f\n", + is_uds ? " (UNIX)" : "(KDBUS)", + (unsigned long long) stats.count, + (unsigned long long) stats.latency_low, + (unsigned long long) stats.latency_high, + (unsigned long long) stats.latency_avg, + sqrt(stats.latency_ssquares / stats.count)); + } else { + kdbus_printf("*** no packets received. bus stuck?\n"); + } +} + +static void add_stats(uint64_t prev) +{ + uint64_t diff, latency_avg_prev; + + diff = now(CLOCK_THREAD_CPUTIME_ID) - prev; + + stats.count++; + stats.latency_acc += diff; + + /* see Welford62 */ + latency_avg_prev = stats.latency_avg; + stats.latency_avg = stats.latency_acc / stats.count; + stats.latency_ssquares += (diff - latency_avg_prev) * (diff - stats.latency_avg); + + if (stats.latency_low > diff) + stats.latency_low = diff; + + if (stats.latency_high < diff) + stats.latency_high = diff; +} + +static int setup_simple_kdbus_msg(struct kdbus_conn *conn, + uint64_t dst_id, + struct kdbus_msg **msg_out) +{ + struct kdbus_msg *msg; + struct kdbus_item *item; + uint64_t size; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t) stress_payload; + item->vec.size = sizeof(stress_payload); + item = KDBUS_ITEM_NEXT(item); + + *msg_out = msg; + + return 0; +} + +static int setup_memfd_kdbus_msg(struct kdbus_conn *conn, + uint64_t dst_id, + off_t *memfd_item_offset, + struct kdbus_msg **msg_out) +{ + struct kdbus_msg *msg; + struct kdbus_item *item; + uint64_t size; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t) stress_payload; + item->vec.size = sizeof(stress_payload); + item = KDBUS_ITEM_NEXT(item); + + item->type = KDBUS_ITEM_PAYLOAD_MEMFD; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_memfd); + item->memfd.size = sizeof(uint64_t); + + *memfd_item_offset = (unsigned char *)item - (unsigned char *)msg; + *msg_out = msg; + + return 0; +} + +static int +send_echo_request(struct kdbus_conn *conn, uint64_t dst_id, + void *kdbus_msg, off_t memfd_item_offset) +{ + struct kdbus_cmd_send cmd = {}; + int memfd = -1; + int ret; + + if (use_memfd) { + uint64_t now_ns = now(CLOCK_THREAD_CPUTIME_ID); + struct kdbus_item *item = memfd_item_offset + kdbus_msg; + memfd = sys_memfd_create("memfd-name", 0); + ASSERT_RETURN_VAL(memfd >= 0, memfd); + + ret = write(memfd, &now_ns, sizeof(now_ns)); + ASSERT_RETURN_VAL(ret == sizeof(now_ns), -EAGAIN); + + ret = sys_memfd_seal_set(memfd); + ASSERT_RETURN_VAL(ret == 0, -errno); + + item->memfd.fd = memfd; + } + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)kdbus_msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + ASSERT_RETURN_VAL(ret == 0, ret); + + close(memfd); + + return 0; +} + +static int +handle_echo_reply(struct kdbus_conn *conn, uint64_t send_ns) +{ + int ret; + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *msg; + const struct kdbus_item *item; + bool has_memfd = false; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret == -EAGAIN) + return ret; + + ASSERT_RETURN_VAL(ret == 0, ret); + + if (!use_memfd) + goto out; + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + + KDBUS_ITEM_FOREACH(item, msg, items) { + switch (item->type) { + case KDBUS_ITEM_PAYLOAD_MEMFD: { + char *buf; + + buf = mmap(NULL, item->memfd.size, PROT_READ, + MAP_PRIVATE, item->memfd.fd, 0); + ASSERT_RETURN_VAL(buf != MAP_FAILED, -EINVAL); + ASSERT_RETURN_VAL(item->memfd.size == sizeof(uint64_t), + -EINVAL); + + add_stats(*(uint64_t*)buf); + munmap(buf, item->memfd.size); + close(item->memfd.fd); + has_memfd = true; + break; + } + + case KDBUS_ITEM_PAYLOAD_OFF: + /* ignore */ + break; + } + } + +out: + if (!has_memfd) + add_stats(send_ns); + + ret = kdbus_free(conn, recv.msg.offset); + ASSERT_RETURN_VAL(ret == 0, -errno); + + return 0; +} + +static int benchmark(struct kdbus_test_env *env) +{ + static char buf[sizeof(stress_payload)]; + struct kdbus_msg *kdbus_msg = NULL; + off_t memfd_cached_offset = 0; + int ret; + struct kdbus_conn *conn_a, *conn_b; + struct pollfd fds[2]; + uint64_t start, send_ns, now_ns, diff; + unsigned int i; + int uds[2]; + + setlocale(LC_ALL, ""); + + for (i = 0; i < sizeof(stress_payload); i++) + stress_payload[i] = i; + + /* setup kdbus pair */ + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + ret = kdbus_add_match_empty(conn_a); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(conn_b); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, SERVICE_NAME, NULL); + ASSERT_RETURN(ret == 0); + + if (attach_none) { + ret = kdbus_conn_update_attach_flags(conn_a, + _KDBUS_ATTACH_ALL, + 0); + ASSERT_RETURN(ret == 0); + } + + /* setup UDS pair */ + + ret = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, uds); + ASSERT_RETURN(ret == 0); + + /* setup a kdbus msg now */ + if (use_memfd) { + ret = setup_memfd_kdbus_msg(conn_b, conn_a->id, + &memfd_cached_offset, + &kdbus_msg); + ASSERT_RETURN(ret == 0); + } else { + ret = setup_simple_kdbus_msg(conn_b, conn_a->id, &kdbus_msg); + ASSERT_RETURN(ret == 0); + } + + /* start benchmark */ + + kdbus_printf("-- entering poll loop ...\n"); + + do { + /* run kdbus benchmark */ + fds[0].fd = conn_a->fd; + fds[1].fd = conn_b->fd; + + /* cancel any pending message */ + handle_echo_reply(conn_a, 0); + + start = now(CLOCK_THREAD_CPUTIME_ID); + reset_stats(); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = send_echo_request(conn_b, conn_a->id, + kdbus_msg, memfd_cached_offset); + ASSERT_RETURN(ret == 0); + + while (1) { + unsigned int nfds = sizeof(fds) / sizeof(fds[0]); + unsigned int i; + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 10); + if (ret < 0) + break; + + if (fds[0].revents & POLLIN) { + ret = handle_echo_reply(conn_a, send_ns); + ASSERT_RETURN(ret == 0); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = send_echo_request(conn_b, conn_a->id, + kdbus_msg, + memfd_cached_offset); + ASSERT_RETURN(ret == 0); + } + + now_ns = now(CLOCK_THREAD_CPUTIME_ID); + diff = now_ns - start; + if (diff > 1000000000ULL) { + start = now_ns; + + dump_stats(false); + break; + } + } + + if (!compare_uds) + continue; + + /* run unix-socket benchmark as comparison */ + + fds[0].fd = uds[0]; + fds[1].fd = uds[1]; + + /* cancel any pendign message */ + read(uds[1], buf, sizeof(buf)); + + start = now(CLOCK_THREAD_CPUTIME_ID); + reset_stats(); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = write(uds[0], stress_payload, sizeof(stress_payload)); + ASSERT_RETURN(ret == sizeof(stress_payload)); + + while (1) { + unsigned int nfds = sizeof(fds) / sizeof(fds[0]); + unsigned int i; + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 10); + if (ret < 0) + break; + + if (fds[1].revents & POLLIN) { + ret = read(uds[1], buf, sizeof(buf)); + ASSERT_RETURN(ret == sizeof(buf)); + + add_stats(send_ns); + + send_ns = now(CLOCK_THREAD_CPUTIME_ID); + ret = write(uds[0], buf, sizeof(buf)); + ASSERT_RETURN(ret == sizeof(buf)); + } + + now_ns = now(CLOCK_THREAD_CPUTIME_ID); + diff = now_ns - start; + if (diff > 1000000000ULL) { + start = now_ns; + + dump_stats(true); + break; + } + } + + } while (kdbus_util_verbose); + + kdbus_printf("-- closing bus connections\n"); + + free(kdbus_msg); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return (stats.count > 1) ? TEST_OK : TEST_ERR; +} + +int kdbus_test_benchmark(struct kdbus_test_env *env) +{ + use_memfd = true; + attach_none = false; + compare_uds = false; + return benchmark(env); +} + +int kdbus_test_benchmark_nomemfds(struct kdbus_test_env *env) +{ + use_memfd = false; + attach_none = false; + compare_uds = false; + return benchmark(env); +} + +int kdbus_test_benchmark_uds(struct kdbus_test_env *env) +{ + use_memfd = false; + attach_none = true; + compare_uds = true; + return benchmark(env); +} diff --git a/tools/testing/selftests/kdbus/test-bus.c b/tools/testing/selftests/kdbus/test-bus.c new file mode 100644 index 000000000000..762fb30397d4 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-bus.c @@ -0,0 +1,175 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <sys/mman.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +static struct kdbus_item *kdbus_get_item(struct kdbus_info *info, + uint64_t type) +{ + struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == type) + return item; + + return NULL; +} + +static int test_bus_creator_info(const char *bus_path) +{ + int ret; + uint64_t offset; + struct kdbus_conn *conn; + struct kdbus_info *info; + struct kdbus_item *item; + char *tmp, *busname; + + /* extract the bus-name from @bus_path */ + tmp = strdup(bus_path); + ASSERT_RETURN(tmp); + busname = strrchr(tmp, '/'); + ASSERT_RETURN(busname); + *busname = 0; + busname = strrchr(tmp, '/'); + ASSERT_RETURN(busname); + ++busname; + + conn = kdbus_hello(bus_path, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_bus_creator_info(conn, _KDBUS_ATTACH_ALL, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + + item = kdbus_get_item(info, KDBUS_ITEM_MAKE_NAME); + ASSERT_RETURN(item); + ASSERT_RETURN(!strcmp(item->str, busname)); + + ret = kdbus_free(conn, offset); + ASSERT_RETURN_VAL(ret == 0, ret); + + free(tmp); + kdbus_conn_free(conn); + return 0; +} + +int kdbus_test_bus_make(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd cmd; + + /* bloom size item */ + struct { + uint64_t size; + uint64_t type; + struct kdbus_bloom_parameter bloom; + } bs; + + /* name item */ + uint64_t n_size; + uint64_t n_type; + char name[64]; + } bus_make; + char s[PATH_MAX], *name; + int ret, control_fd2; + uid_t uid; + + name = unique_name(""); + ASSERT_RETURN(name); + + snprintf(s, sizeof(s), "%s/control", env->root); + env->control_fd = open(s, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(env->control_fd >= 0); + + control_fd2 = open(s, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(control_fd2 >= 0); + + memset(&bus_make, 0, sizeof(bus_make)); + + bus_make.bs.size = sizeof(bus_make.bs); + bus_make.bs.type = KDBUS_ITEM_BLOOM_PARAMETER; + bus_make.bs.bloom.size = 64; + bus_make.bs.bloom.n_hash = 1; + + bus_make.n_type = KDBUS_ITEM_MAKE_NAME; + + uid = getuid(); + + /* missing uid prefix */ + snprintf(bus_make.name, sizeof(bus_make.name), "foo"); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EINVAL); + + /* non alphanumeric character */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-blah@123", uid); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EINVAL); + + /* '-' at the end */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-blah-", uid); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EINVAL); + + /* create a new bus */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-1", uid, name); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == 0); + + ret = kdbus_cmd_bus_make(control_fd2, &bus_make.cmd); + ASSERT_RETURN(ret == -EEXIST); + + snprintf(s, sizeof(s), "%s/%u-%s-1/bus", env->root, uid, name); + ASSERT_RETURN(access(s, F_OK) == 0); + + ret = test_bus_creator_info(s); + ASSERT_RETURN(ret == 0); + + /* can't use the same fd for bus make twice, even though a different + * bus name is used + */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-2", uid, name); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(env->control_fd, &bus_make.cmd); + ASSERT_RETURN(ret == -EBADFD); + + /* create a new bus, with different fd and different bus name */ + snprintf(bus_make.name, sizeof(bus_make.name), "%u-%s-2", uid, name); + bus_make.n_size = KDBUS_ITEM_HEADER_SIZE + strlen(bus_make.name) + 1; + bus_make.cmd.size = sizeof(struct kdbus_cmd) + + sizeof(bus_make.bs) + bus_make.n_size; + ret = kdbus_cmd_bus_make(control_fd2, &bus_make.cmd); + ASSERT_RETURN(ret == 0); + + close(control_fd2); + free(name); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-chat.c b/tools/testing/selftests/kdbus/test-chat.c new file mode 100644 index 000000000000..e19f595283cd --- /dev/null +++ b/tools/testing/selftests/kdbus/test-chat.c @@ -0,0 +1,122 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <stdbool.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int kdbus_test_chat(struct kdbus_test_env *env) +{ + int ret, cookie; + struct kdbus_conn *conn_a, *conn_b; + struct pollfd fds[2]; + uint64_t flags; + int count; + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + flags = KDBUS_NAME_ALLOW_REPLACEMENT; + ret = kdbus_name_acquire(conn_a, "foo.bar.test", &flags); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, "foo.bar.baz", NULL); + ASSERT_RETURN(ret == 0); + + flags = KDBUS_NAME_QUEUE; + ret = kdbus_name_acquire(conn_b, "foo.bar.baz", &flags); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, "foo.bar.double", NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn_a, "foo.bar.double", NULL); + ASSERT_RETURN(ret == -EALREADY); + + ret = kdbus_name_release(conn_a, "foo.bar.double"); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_release(conn_a, "foo.bar.double"); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_list(conn_b, KDBUS_LIST_UNIQUE | + KDBUS_LIST_NAMES | + KDBUS_LIST_QUEUED | + KDBUS_LIST_ACTIVATORS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(conn_a); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(conn_b); + ASSERT_RETURN(ret == 0); + + cookie = 0; + ret = kdbus_msg_send(conn_b, NULL, 0xc0000000 | cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + fds[0].fd = conn_a->fd; + fds[1].fd = conn_b->fd; + + kdbus_printf("-- entering poll loop ...\n"); + + for (count = 0;; count++) { + int i, nfds = sizeof(fds) / sizeof(fds[0]); + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, 3000); + ASSERT_RETURN(ret >= 0); + + if (fds[0].revents & POLLIN) { + if (count > 2) + kdbus_name_release(conn_a, "foo.bar.baz"); + + ret = kdbus_msg_recv(conn_a, NULL, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(conn_a, NULL, + 0xc0000000 | cookie++, + 0, 0, 0, conn_b->id, 0, NULL); + ASSERT_RETURN(ret == 0); + } + + if (fds[1].revents & POLLIN) { + ret = kdbus_msg_recv(conn_b, NULL, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(conn_b, NULL, + 0xc0000000 | cookie++, + 0, 0, 0, conn_a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + } + + ret = kdbus_list(conn_b, KDBUS_LIST_UNIQUE | + KDBUS_LIST_NAMES | + KDBUS_LIST_QUEUED | + KDBUS_LIST_ACTIVATORS); + ASSERT_RETURN(ret == 0); + + if (count > 10) + break; + } + + kdbus_printf("-- closing bus connections\n"); + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-connection.c b/tools/testing/selftests/kdbus/test-connection.c new file mode 100644 index 000000000000..7afb76fdf7da --- /dev/null +++ b/tools/testing/selftests/kdbus/test-connection.c @@ -0,0 +1,618 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/capability.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +int kdbus_test_hello(struct kdbus_test_env *env) +{ + struct kdbus_cmd_free cmd_free = {}; + struct kdbus_cmd_hello hello; + int fd, ret; + + memset(&hello, 0, sizeof(hello)); + + fd = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(fd >= 0); + + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + hello.attach_flags_recv = _KDBUS_ATTACH_ALL; + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + + /* an unaligned hello must result in -EFAULT */ + ret = kdbus_cmd_hello(fd, (struct kdbus_cmd_hello *) ((char *) &hello + 1)); + ASSERT_RETURN(ret == -EFAULT); + + /* a size of 0 must return EMSGSIZE */ + hello.size = 1; + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + hello.size = sizeof(struct kdbus_cmd_hello); + + /* check faulty flags */ + hello.flags = 1ULL << 32; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + /* check for faulty pool sizes */ + hello.pool_size = 0; + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + hello.pool_size = 4097; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + hello.pool_size = POOL_SIZE; + + /* + * The connection created by the core requires ALL meta flags + * to be sent. An attempt to send less than that should result in + * -ECONNREFUSED. + */ + hello.attach_flags_send = _KDBUS_ATTACH_ALL & ~KDBUS_ATTACH_TIMESTAMP; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -ECONNREFUSED); + + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + hello.offset = (__u64)-1; + + /* success test */ + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == 0); + + /* The kernel should have returned some items */ + ASSERT_RETURN(hello.offset != (__u64)-1); + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(fd, &cmd_free); + ASSERT_RETURN(ret >= 0); + + close(fd); + + fd = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(fd >= 0); + + /* no ACTIVATOR flag without a name */ + hello.flags = KDBUS_HELLO_ACTIVATOR; + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == -EINVAL); + + close(fd); + + return TEST_OK; +} + +int kdbus_test_byebye(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + struct kdbus_cmd_recv cmd_recv = { .size = sizeof(cmd_recv) }; + struct kdbus_cmd cmd_byebye = { .size = sizeof(cmd_byebye) }; + int ret; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(env->conn); + ASSERT_RETURN(ret == 0); + + /* send over 1st connection */ + ret = kdbus_msg_send(env->conn, NULL, 0, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* say byebye on the 2nd, which must fail */ + ret = kdbus_cmd_byebye(conn->fd, &cmd_byebye); + ASSERT_RETURN(ret == -EBUSY); + + /* receive the message */ + ret = kdbus_cmd_recv(conn->fd, &cmd_recv); + ASSERT_RETURN(ret == 0); + + ret = kdbus_free(conn, cmd_recv.msg.offset); + ASSERT_RETURN(ret == 0); + + /* and try again */ + ret = kdbus_cmd_byebye(conn->fd, &cmd_byebye); + ASSERT_RETURN(ret == 0); + + /* a 2nd try should result in -ECONNRESET */ + ret = kdbus_cmd_byebye(conn->fd, &cmd_byebye); + ASSERT_RETURN(ret == -ECONNRESET); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +/* Get only the first item */ +static struct kdbus_item *kdbus_get_item(struct kdbus_info *info, + uint64_t type) +{ + struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == type) + return item; + + return NULL; +} + +static unsigned int kdbus_count_item(struct kdbus_info *info, + uint64_t type) +{ + unsigned int i = 0; + const struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == type) + i++; + + return i; +} + +static int kdbus_fuzz_conn_info(struct kdbus_test_env *env, int capable) +{ + int ret; + unsigned int cnt = 0; + uint64_t offset = 0; + uint64_t kdbus_flags_mask; + struct kdbus_info *info; + struct kdbus_conn *conn; + struct kdbus_conn *privileged; + const struct kdbus_item *item; + uint64_t valid_flags_set; + uint64_t invalid_flags_set; + uint64_t valid_flags = KDBUS_ATTACH_NAMES | + KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_CONN_DESCRIPTION; + + uint64_t invalid_flags = KDBUS_ATTACH_NAMES | + KDBUS_ATTACH_CREDS | + KDBUS_ATTACH_PIDS | + KDBUS_ATTACH_CAPS | + KDBUS_ATTACH_CGROUP | + KDBUS_ATTACH_CONN_DESCRIPTION; + + struct kdbus_creds cached_creds; + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + + getresuid(&ruid, &euid, &suid); + getresgid(&rgid, &egid, &sgid); + + cached_creds.uid = ruid; + cached_creds.euid = euid; + cached_creds.suid = suid; + cached_creds.fsuid = ruid; + + cached_creds.gid = rgid; + cached_creds.egid = egid; + cached_creds.sgid = sgid; + cached_creds.fsgid = rgid; + + struct kdbus_pids cached_pids = { + .pid = getpid(), + .tid = syscall(SYS_gettid), + .ppid = getppid(), + }; + + ret = kdbus_sysfs_get_parameter_mask(env->mask_param_path, + &kdbus_flags_mask); + ASSERT_RETURN(ret == 0); + + valid_flags_set = valid_flags & kdbus_flags_mask; + invalid_flags_set = invalid_flags & kdbus_flags_mask; + + ret = kdbus_conn_info(env->conn, env->conn->id, NULL, + valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(env->conn->buf + offset); + ASSERT_RETURN(info->id == env->conn->id); + + /* We do not have any well-known name */ + item = kdbus_get_item(info, KDBUS_ITEM_NAME); + ASSERT_RETURN(item == NULL); + + item = kdbus_get_item(info, KDBUS_ITEM_CONN_DESCRIPTION); + if (valid_flags_set & KDBUS_ATTACH_CONN_DESCRIPTION) { + ASSERT_RETURN(item); + } else { + ASSERT_RETURN(item == NULL); + } + + kdbus_free(env->conn, offset); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + privileged = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(privileged); + + ret = kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + /* We do not have any well-known name */ + item = kdbus_get_item(info, KDBUS_ITEM_NAME); + ASSERT_RETURN(item == NULL); + + cnt = kdbus_count_item(info, KDBUS_ITEM_CREDS); + if (valid_flags_set & KDBUS_ATTACH_CREDS) { + ASSERT_RETURN(cnt == 1); + + item = kdbus_get_item(info, KDBUS_ITEM_CREDS); + ASSERT_RETURN(item); + + /* Compare received items with cached creds */ + ASSERT_RETURN(memcmp(&item->creds, &cached_creds, + sizeof(struct kdbus_creds)) == 0); + } else { + ASSERT_RETURN(cnt == 0); + } + + item = kdbus_get_item(info, KDBUS_ITEM_PIDS); + if (valid_flags_set & KDBUS_ATTACH_PIDS) { + ASSERT_RETURN(item); + + /* Compare item->pids with cached PIDs */ + ASSERT_RETURN(item->pids.pid == cached_pids.pid && + item->pids.tid == cached_pids.tid && + item->pids.ppid == cached_pids.ppid); + } else { + ASSERT_RETURN(item == NULL); + } + + /* We did not request KDBUS_ITEM_CAPS */ + item = kdbus_get_item(info, KDBUS_ITEM_CAPS); + ASSERT_RETURN(item == NULL); + + kdbus_free(conn, offset); + + ret = kdbus_name_acquire(conn, "com.example.a", NULL); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + item = kdbus_get_item(info, KDBUS_ITEM_OWNED_NAME); + if (valid_flags_set & KDBUS_ATTACH_NAMES) { + ASSERT_RETURN(item && !strcmp(item->name.name, "com.example.a")); + } else { + ASSERT_RETURN(item == NULL); + } + + kdbus_free(conn, offset); + + ret = kdbus_conn_info(conn, 0, "com.example.a", valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + kdbus_free(conn, offset); + + /* does not have the necessary caps to drop to unprivileged */ + if (!capable) + goto continue_test; + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + ret = kdbus_conn_info(conn, conn->id, NULL, + valid_flags, &offset); + ASSERT_EXIT(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_EXIT(info->id == conn->id); + + if (valid_flags_set & KDBUS_ATTACH_NAMES) { + item = kdbus_get_item(info, KDBUS_ITEM_OWNED_NAME); + ASSERT_EXIT(item && + strcmp(item->name.name, + "com.example.a") == 0); + } + + if (valid_flags_set & KDBUS_ATTACH_CREDS) { + item = kdbus_get_item(info, KDBUS_ITEM_CREDS); + ASSERT_EXIT(item); + + /* Compare received items with cached creds */ + ASSERT_EXIT(memcmp(&item->creds, &cached_creds, + sizeof(struct kdbus_creds)) == 0); + } + + if (valid_flags_set & KDBUS_ATTACH_PIDS) { + item = kdbus_get_item(info, KDBUS_ITEM_PIDS); + ASSERT_EXIT(item); + + /* + * Compare item->pids with cached pids of + * privileged one. + * + * cmd_info will always return cached pids. + */ + ASSERT_EXIT(item->pids.pid == cached_pids.pid && + item->pids.tid == cached_pids.tid); + } + + kdbus_free(conn, offset); + + /* + * Use invalid_flags and make sure that userspace + * do not play with us. + */ + ret = kdbus_conn_info(conn, conn->id, NULL, + invalid_flags, &offset); + ASSERT_EXIT(ret == 0); + + /* + * Make sure that we return only one creds item and + * it points to the cached creds. + */ + cnt = kdbus_count_item(info, KDBUS_ITEM_CREDS); + if (invalid_flags_set & KDBUS_ATTACH_CREDS) { + ASSERT_EXIT(cnt == 1); + + item = kdbus_get_item(info, KDBUS_ITEM_CREDS); + ASSERT_EXIT(item); + + /* Compare received items with cached creds */ + ASSERT_EXIT(memcmp(&item->creds, &cached_creds, + sizeof(struct kdbus_creds)) == 0); + } else { + ASSERT_EXIT(cnt == 0); + } + + if (invalid_flags_set & KDBUS_ATTACH_PIDS) { + cnt = kdbus_count_item(info, KDBUS_ITEM_PIDS); + ASSERT_EXIT(cnt == 1); + + item = kdbus_get_item(info, KDBUS_ITEM_PIDS); + ASSERT_EXIT(item); + + /* Compare item->pids with cached pids */ + ASSERT_EXIT(item->pids.pid == cached_pids.pid && + item->pids.tid == cached_pids.tid); + } + + cnt = kdbus_count_item(info, KDBUS_ITEM_CGROUP); + if (invalid_flags_set & KDBUS_ATTACH_CGROUP) { + ASSERT_EXIT(cnt == 1); + } else { + ASSERT_EXIT(cnt == 0); + } + + cnt = kdbus_count_item(info, KDBUS_ITEM_CAPS); + if (invalid_flags_set & KDBUS_ATTACH_CAPS) { + ASSERT_EXIT(cnt == 1); + } else { + ASSERT_EXIT(cnt == 0); + } + + kdbus_free(conn, offset); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + +continue_test: + + /* A second name */ + ret = kdbus_name_acquire(conn, "com.example.b", NULL); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_conn_info(conn, conn->id, NULL, valid_flags, &offset); + ASSERT_RETURN(ret == 0); + + info = (struct kdbus_info *)(conn->buf + offset); + ASSERT_RETURN(info->id == conn->id); + + cnt = kdbus_count_item(info, KDBUS_ITEM_OWNED_NAME); + if (valid_flags_set & KDBUS_ATTACH_NAMES) { + ASSERT_RETURN(cnt == 2); + } else { + ASSERT_RETURN(cnt == 0); + } + + kdbus_free(conn, offset); + + ASSERT_RETURN(ret == 0); + + return 0; +} + +int kdbus_test_conn_info(struct kdbus_test_env *env) +{ + int ret; + int have_caps; + struct { + struct kdbus_cmd_info cmd_info; + + struct { + uint64_t size; + uint64_t type; + char str[64]; + } name; + } buf; + + buf.cmd_info.size = sizeof(struct kdbus_cmd_info); + buf.cmd_info.flags = 0; + buf.cmd_info.attach_flags = 0; + buf.cmd_info.id = env->conn->id; + + ret = kdbus_conn_info(env->conn, env->conn->id, NULL, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* try to pass a name that is longer than the buffer's size */ + buf.name.size = KDBUS_ITEM_HEADER_SIZE + 1; + buf.name.type = KDBUS_ITEM_NAME; + strcpy(buf.name.str, "foo.bar.bla"); + + buf.cmd_info.id = 0; + buf.cmd_info.size = sizeof(buf.cmd_info) + buf.name.size; + ret = kdbus_cmd_conn_info(env->conn->fd, (struct kdbus_cmd_info *) &buf); + ASSERT_RETURN(ret == -EINVAL); + + /* Pass a non existent name */ + ret = kdbus_conn_info(env->conn, 0, "non.existent.name", 0, NULL); + ASSERT_RETURN(ret == -ESRCH); + + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + /* Test for caps here, so we run the previous test */ + have_caps = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(have_caps >= 0); + + ret = kdbus_fuzz_conn_info(env, have_caps); + ASSERT_RETURN(ret == 0); + + /* Now if we have skipped some tests then let the user know */ + if (!have_caps) + return TEST_SKIP; + + return TEST_OK; +} + +int kdbus_test_conn_update(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + struct kdbus_msg *msg; + int found = 0; + int ret; + + /* + * kdbus_hello() sets all attach flags. Receive a message by this + * connection, and make sure a timestamp item (just to pick one) is + * present. + */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_msg_send(env->conn, NULL, 0x12345678, 0, 0, 0, conn->id, + 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + found = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(found == 1); + + kdbus_msg_free(msg); + + /* + * Now, modify the attach flags and repeat the action. The item must + * now be missing. + */ + found = 0; + + ret = kdbus_conn_update_attach_flags(conn, + _KDBUS_ATTACH_ALL, + _KDBUS_ATTACH_ALL & + ~KDBUS_ATTACH_TIMESTAMP); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_send(env->conn, NULL, 0x12345678, 0, 0, 0, conn->id, + 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + found = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(found == 0); + + /* Provide a bogus attach_flags value */ + ret = kdbus_conn_update_attach_flags(conn, + _KDBUS_ATTACH_ALL + 1, + _KDBUS_ATTACH_ALL); + ASSERT_RETURN(ret == -EINVAL); + + kdbus_msg_free(msg); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_writable_pool(struct kdbus_test_env *env) +{ + struct kdbus_cmd_free cmd_free = {}; + struct kdbus_cmd_hello hello; + int fd, ret; + void *map; + + fd = open(env->buspath, O_RDWR | O_CLOEXEC); + ASSERT_RETURN(fd >= 0); + + memset(&hello, 0, sizeof(hello)); + hello.flags = KDBUS_HELLO_ACCEPT_FD; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + hello.attach_flags_recv = _KDBUS_ATTACH_ALL; + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + hello.offset = (__u64)-1; + + /* success test */ + ret = kdbus_cmd_hello(fd, &hello); + ASSERT_RETURN(ret == 0); + + /* The kernel should have returned some items */ + ASSERT_RETURN(hello.offset != (__u64)-1); + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(fd, &cmd_free); + ASSERT_RETURN(ret >= 0); + + /* pools cannot be mapped writable */ + map = mmap(NULL, POOL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ASSERT_RETURN(map == MAP_FAILED); + + /* pools can always be mapped readable */ + map = mmap(NULL, POOL_SIZE, PROT_READ, MAP_SHARED, fd, 0); + ASSERT_RETURN(map != MAP_FAILED); + + /* make sure we cannot change protection masks to writable */ + ret = mprotect(map, POOL_SIZE, PROT_READ | PROT_WRITE); + ASSERT_RETURN(ret < 0); + + munmap(map, POOL_SIZE); + close(fd); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-daemon.c b/tools/testing/selftests/kdbus/test-daemon.c new file mode 100644 index 000000000000..8bc2386190d9 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-daemon.c @@ -0,0 +1,65 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <stdbool.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int kdbus_test_daemon(struct kdbus_test_env *env) +{ + struct pollfd fds[2]; + int count; + int ret; + + /* This test doesn't make any sense in non-interactive mode */ + if (!kdbus_util_verbose) + return TEST_OK; + + printf("Created connection %llu on bus '%s'\n", + (unsigned long long) env->conn->id, env->buspath); + + ret = kdbus_name_acquire(env->conn, "com.example.kdbus-test", NULL); + ASSERT_RETURN(ret == 0); + printf(" Aquired name: com.example.kdbus-test\n"); + + fds[0].fd = env->conn->fd; + fds[1].fd = STDIN_FILENO; + + printf("Monitoring connections:\n"); + + for (count = 0;; count++) { + int i, nfds = sizeof(fds) / sizeof(fds[0]); + + for (i = 0; i < nfds; i++) { + fds[i].events = POLLIN | POLLPRI | POLLHUP; + fds[i].revents = 0; + } + + ret = poll(fds, nfds, -1); + if (ret <= 0) + break; + + if (fds[0].revents & POLLIN) { + ret = kdbus_msg_recv(env->conn, NULL, NULL); + ASSERT_RETURN(ret == 0); + } + + /* stdin */ + if (fds[1].revents & POLLIN) + break; + } + + printf("Closing bus connection\n"); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-endpoint.c b/tools/testing/selftests/kdbus/test-endpoint.c new file mode 100644 index 000000000000..dcc6ab91c4e6 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-endpoint.c @@ -0,0 +1,341 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <libgen.h> +#include <sys/capability.h> +#include <sys/wait.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +#define KDBUS_SYSNAME_MAX_LEN 63 + +static int install_name_add_match(struct kdbus_conn *conn, const char *name) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + int ret; + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_ADD; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(conn->fd, &buf.cmd); + if (ret < 0) + return ret; + + return 0; +} + +static int create_endpoint(const char *buspath, uid_t uid, const char *name, + uint64_t flags) +{ + struct { + struct kdbus_cmd cmd; + + /* name item */ + struct { + uint64_t size; + uint64_t type; + /* max should be KDBUS_SYSNAME_MAX_LEN */ + char str[128]; + } name; + } ep_make; + int fd, ret; + + fd = open(buspath, O_RDWR); + if (fd < 0) + return fd; + + memset(&ep_make, 0, sizeof(ep_make)); + + snprintf(ep_make.name.str, + /* Use the KDBUS_SYSNAME_MAX_LEN or sizeof(str) */ + KDBUS_SYSNAME_MAX_LEN > strlen(name) ? + KDBUS_SYSNAME_MAX_LEN : sizeof(ep_make.name.str), + "%u-%s", uid, name); + + ep_make.name.type = KDBUS_ITEM_MAKE_NAME; + ep_make.name.size = KDBUS_ITEM_HEADER_SIZE + + strlen(ep_make.name.str) + 1; + + ep_make.cmd.flags = flags; + ep_make.cmd.size = sizeof(ep_make.cmd) + ep_make.name.size; + + ret = kdbus_cmd_endpoint_make(fd, &ep_make.cmd); + if (ret < 0) { + kdbus_printf("error creating endpoint: %d (%m)\n", ret); + return ret; + } + + return fd; +} + +static int unpriv_test_custom_ep(const char *buspath) +{ + int ret, ep_fd1, ep_fd2; + char *ep1, *ep2, *tmp1, *tmp2; + + tmp1 = strdup(buspath); + tmp2 = strdup(buspath); + ASSERT_RETURN(tmp1 && tmp2); + + ret = asprintf(&ep1, "%s/%u-%s", dirname(tmp1), getuid(), "apps1"); + ASSERT_RETURN(ret >= 0); + + ret = asprintf(&ep2, "%s/%u-%s", dirname(tmp2), getuid(), "apps2"); + ASSERT_RETURN(ret >= 0); + + free(tmp1); + free(tmp2); + + /* endpoint only accessible to current uid */ + ep_fd1 = create_endpoint(buspath, getuid(), "apps1", 0); + ASSERT_RETURN(ep_fd1 >= 0); + + /* endpoint world accessible */ + ep_fd2 = create_endpoint(buspath, getuid(), "apps2", + KDBUS_MAKE_ACCESS_WORLD); + ASSERT_RETURN(ep_fd2 >= 0); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({ + int ep_fd; + struct kdbus_conn *ep_conn; + + /* + * Make sure that we are not able to create custom + * endpoints + */ + ep_fd = create_endpoint(buspath, getuid(), + "unpriv_costum_ep", 0); + ASSERT_EXIT(ep_fd == -EPERM); + + /* + * Endpoint "apps1" only accessible to same users, + * that own the endpoint. Access denied by VFS + */ + ep_conn = kdbus_hello(ep1, 0, NULL, 0); + ASSERT_EXIT(!ep_conn && errno == EACCES); + + /* Endpoint "apps2" world accessible */ + ep_conn = kdbus_hello(ep2, 0, NULL, 0); + ASSERT_EXIT(ep_conn); + + kdbus_conn_free(ep_conn); + + _exit(EXIT_SUCCESS); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + + close(ep_fd1); + close(ep_fd2); + free(ep1); + free(ep2); + + return 0; +} + +static int update_endpoint(int fd, const char *name) +{ + int len = strlen(name) + 1; + struct { + struct kdbus_cmd cmd; + + /* name item */ + struct { + uint64_t size; + uint64_t type; + char str[KDBUS_ALIGN8(len)]; + } name; + + struct { + uint64_t size; + uint64_t type; + struct kdbus_policy_access access; + } access; + } ep_update; + int ret; + + memset(&ep_update, 0, sizeof(ep_update)); + + ep_update.name.size = KDBUS_ITEM_HEADER_SIZE + len; + ep_update.name.type = KDBUS_ITEM_NAME; + strncpy(ep_update.name.str, name, sizeof(ep_update.name.str) - 1); + + ep_update.access.size = sizeof(ep_update.access); + ep_update.access.type = KDBUS_ITEM_POLICY_ACCESS; + ep_update.access.access.type = KDBUS_POLICY_ACCESS_WORLD; + ep_update.access.access.access = KDBUS_POLICY_SEE; + + ep_update.cmd.size = sizeof(ep_update); + + ret = kdbus_cmd_endpoint_update(fd, &ep_update.cmd); + if (ret < 0) { + kdbus_printf("error updating endpoint: %d (%m)\n", ret); + return ret; + } + + return 0; +} + +int kdbus_test_custom_endpoint(struct kdbus_test_env *env) +{ + char *ep, *tmp; + int ret, ep_fd; + struct kdbus_msg *msg; + struct kdbus_conn *ep_conn; + struct kdbus_conn *reader; + const char *name = "foo.bar.baz"; + const char *epname = "foo"; + char fake_ep[KDBUS_SYSNAME_MAX_LEN + 1] = {'\0'}; + + memset(fake_ep, 'X', sizeof(fake_ep) - 1); + + /* Try to create a custom endpoint with a long name */ + ret = create_endpoint(env->buspath, getuid(), fake_ep, 0); + ASSERT_RETURN(ret == -ENAMETOOLONG); + + /* Try to create a custom endpoint with a different uid */ + ret = create_endpoint(env->buspath, getuid() + 1, "foobar", 0); + ASSERT_RETURN(ret == -EINVAL); + + /* create a custom endpoint, and open a connection on it */ + ep_fd = create_endpoint(env->buspath, getuid(), "foo", 0); + ASSERT_RETURN(ep_fd >= 0); + + tmp = strdup(env->buspath); + ASSERT_RETURN(tmp); + + ret = asprintf(&ep, "%s/%u-%s", dirname(tmp), getuid(), epname); + free(tmp); + ASSERT_RETURN(ret >= 0); + + /* Register a connection that listen to broadcasts */ + reader = kdbus_hello(ep, 0, NULL, 0); + ASSERT_RETURN(reader); + + /* Register to kernel signals */ + ret = kdbus_add_match_id(reader, 0x1, KDBUS_ITEM_ID_ADD, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_id(reader, 0x2, KDBUS_ITEM_ID_REMOVE, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = install_name_add_match(reader, name); + ASSERT_RETURN(ret == 0); + + /* Monitor connections are not supported on custom endpoints */ + ep_conn = kdbus_hello(ep, KDBUS_HELLO_MONITOR, NULL, 0); + ASSERT_RETURN(!ep_conn && errno == EOPNOTSUPP); + + ep_conn = kdbus_hello(ep, 0, NULL, 0); + ASSERT_RETURN(ep_conn); + + /* + * Add a name add match on the endpoint connection, acquire name from + * the unfiltered connection, and make sure the filtered connection + * did not get the notification on the name owner change. Also, the + * endpoint connection may not be able to call conn_info, neither on + * the name nor on the ID. + */ + ret = install_name_add_match(ep_conn, name); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(ep_conn, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + ret = kdbus_conn_info(ep_conn, 0, name, 0, NULL); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_conn_info(ep_conn, 0, "random.crappy.name", 0, NULL); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_conn_info(ep_conn, env->conn->id, NULL, 0, NULL); + ASSERT_RETURN(ret == -ENXIO); + + ret = kdbus_conn_info(ep_conn, 0x0fffffffffffffffULL, NULL, 0, NULL); + ASSERT_RETURN(ret == -ENXIO); + + /* Check that the reader did not receive anything */ + ret = kdbus_msg_recv(reader, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Release the name again, update the custom endpoint policy, + * and try again. This time, the connection on the custom endpoint + * should have gotten it. + */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + ret = update_endpoint(ep_fd, name); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(ep_conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_ADD); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == 0); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == env->conn->id); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + kdbus_msg_free(msg); + + ret = kdbus_msg_recv(reader, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + kdbus_msg_free(msg); + + ret = kdbus_conn_info(ep_conn, 0, name, 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_info(ep_conn, env->conn->id, NULL, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* If we have privileges test custom endpoints */ + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + /* + * All uids/gids are mapped and we have the necessary caps + */ + if (ret && all_uids_gids_are_mapped()) { + ret = unpriv_test_custom_ep(env->buspath); + ASSERT_RETURN(ret == 0); + } + + kdbus_conn_free(reader); + kdbus_conn_free(ep_conn); + close(ep_fd); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-fd.c b/tools/testing/selftests/kdbus/test-fd.c new file mode 100644 index 000000000000..bfd8980e0004 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-fd.c @@ -0,0 +1,789 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#define KDBUS_MSG_MAX_ITEMS 128 +#define KDBUS_USER_MAX_CONN 256 + +/* maximum number of inflight fds in a target queue per user */ +#define KDBUS_CONN_MAX_FDS_PER_USER 16 + +/* maximum number of memfd items per message */ +#define KDBUS_MSG_MAX_MEMFD_ITEMS 16 + +static int make_msg_payload_dbus(uint64_t src_id, uint64_t dst_id, + uint64_t msg_size, + struct kdbus_msg **msg_dbus) +{ + struct kdbus_msg *msg; + + msg = malloc(msg_size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, msg_size); + msg->size = msg_size; + msg->src_id = src_id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + *msg_dbus = msg; + + return 0; +} + +static void make_item_memfds(struct kdbus_item *item, + int *memfds, size_t memfd_size) +{ + size_t i; + + for (i = 0; i < memfd_size; i++) { + item->type = KDBUS_ITEM_PAYLOAD_MEMFD; + item->size = KDBUS_ITEM_HEADER_SIZE + + sizeof(struct kdbus_memfd); + item->memfd.fd = memfds[i]; + item->memfd.size = sizeof(uint64_t); /* const size */ + item = KDBUS_ITEM_NEXT(item); + } +} + +static void make_item_fds(struct kdbus_item *item, + int *fd_array, size_t fd_size) +{ + size_t i; + item->type = KDBUS_ITEM_FDS; + item->size = KDBUS_ITEM_HEADER_SIZE + (sizeof(int) * fd_size); + + for (i = 0; i < fd_size; i++) + item->fds[i] = fd_array[i]; +} + +static int memfd_write(const char *name, void *buf, size_t bufsize) +{ + ssize_t ret; + int memfd; + + memfd = sys_memfd_create(name, 0); + ASSERT_RETURN_VAL(memfd >= 0, memfd); + + ret = write(memfd, buf, bufsize); + ASSERT_RETURN_VAL(ret == (ssize_t)bufsize, -EAGAIN); + + ret = sys_memfd_seal_set(memfd); + ASSERT_RETURN_VAL(ret == 0, -errno); + + return memfd; +} + +static int send_memfds(struct kdbus_conn *conn, uint64_t dst_id, + int *memfds_array, size_t memfd_count) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += memfd_count * KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + + if (dst_id == KDBUS_DST_ID_BROADCAST) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + + ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, ret); + + item = msg->items; + + if (dst_id == KDBUS_DST_ID_BROADCAST) { + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + item = KDBUS_ITEM_NEXT(item); + + msg->flags |= KDBUS_MSG_SIGNAL; + } + + make_item_memfds(item, memfds_array, memfd_count); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + free(msg); + return 0; +} + +static int send_fds(struct kdbus_conn *conn, uint64_t dst_id, + int *fd_array, size_t fd_count) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(int) * fd_count); + + if (dst_id == KDBUS_DST_ID_BROADCAST) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + + ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, ret); + + item = msg->items; + + if (dst_id == KDBUS_DST_ID_BROADCAST) { + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + 64; + item = KDBUS_ITEM_NEXT(item); + + msg->flags |= KDBUS_MSG_SIGNAL; + } + + make_item_fds(item, fd_array, fd_count); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + free(msg); + return ret; +} + +static int send_fds_memfds(struct kdbus_conn *conn, uint64_t dst_id, + int *fds_array, size_t fd_count, + int *memfds_array, size_t memfd_count) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += memfd_count * KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd)); + size += KDBUS_ITEM_SIZE(sizeof(int) * fd_count); + + ret = make_msg_payload_dbus(conn->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, ret); + + item = msg->items; + + make_item_fds(item, fds_array, fd_count); + item = KDBUS_ITEM_NEXT(item); + make_item_memfds(item, memfds_array, memfd_count); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + free(msg); + return ret; +} + +/* Return the number of received fds */ +static unsigned int kdbus_item_get_nfds(struct kdbus_msg *msg) +{ + unsigned int fds = 0; + const struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, msg, items) { + switch (item->type) { + case KDBUS_ITEM_FDS: { + fds += (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + break; + } + + case KDBUS_ITEM_PAYLOAD_MEMFD: + fds++; + break; + + default: + break; + } + } + + return fds; +} + +static struct kdbus_msg * +get_kdbus_msg_with_fd(struct kdbus_conn *conn_src, + uint64_t dst_id, uint64_t cookie, int fd) +{ + int ret; + uint64_t size; + struct kdbus_item *item; + struct kdbus_msg *msg; + + size = sizeof(struct kdbus_msg); + if (fd >= 0) + size += KDBUS_ITEM_SIZE(sizeof(int)); + + ret = make_msg_payload_dbus(conn_src->id, dst_id, size, &msg); + ASSERT_RETURN_VAL(ret == 0, NULL); + + msg->cookie = cookie; + + if (fd >= 0) { + item = msg->items; + + make_item_fds(item, (int *)&fd, 1); + } + + return msg; +} + +static int kdbus_test_no_fds(struct kdbus_test_env *env, + int *fds, int *memfd) +{ + pid_t pid; + int ret, status; + uint64_t cookie; + int connfd1, connfd2; + struct kdbus_msg *msg, *msg_sync_reply; + struct kdbus_cmd_hello hello; + struct kdbus_conn *conn_src, *conn_dst, *conn_dummy; + struct kdbus_cmd_send cmd = {}; + struct kdbus_cmd_free cmd_free = {}; + + conn_src = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_src); + + connfd1 = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(connfd1 >= 0); + + connfd2 = open(env->buspath, O_RDWR|O_CLOEXEC); + ASSERT_RETURN(connfd2 >= 0); + + /* + * Create connections without KDBUS_HELLO_ACCEPT_FD + * to test if send fd operations are blocked + */ + conn_dst = malloc(sizeof(*conn_dst)); + ASSERT_RETURN(conn_dst); + + conn_dummy = malloc(sizeof(*conn_dummy)); + ASSERT_RETURN(conn_dummy); + + memset(&hello, 0, sizeof(hello)); + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + + ret = kdbus_cmd_hello(connfd1, &hello); + ASSERT_RETURN(ret == 0); + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(connfd1, &cmd_free); + ASSERT_RETURN(ret >= 0); + + conn_dst->fd = connfd1; + conn_dst->id = hello.id; + + memset(&hello, 0, sizeof(hello)); + hello.size = sizeof(struct kdbus_cmd_hello); + hello.pool_size = POOL_SIZE; + hello.attach_flags_send = _KDBUS_ATTACH_ALL; + + ret = kdbus_cmd_hello(connfd2, &hello); + ASSERT_RETURN(ret == 0); + + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = hello.offset; + ret = kdbus_cmd_free(connfd2, &cmd_free); + ASSERT_RETURN(ret >= 0); + + conn_dummy->fd = connfd2; + conn_dummy->id = hello.id; + + conn_dst->buf = mmap(NULL, POOL_SIZE, PROT_READ, + MAP_SHARED, connfd1, 0); + ASSERT_RETURN(conn_dst->buf != MAP_FAILED); + + conn_dummy->buf = mmap(NULL, POOL_SIZE, PROT_READ, + MAP_SHARED, connfd2, 0); + ASSERT_RETURN(conn_dummy->buf != MAP_FAILED); + + /* + * Send fds to connection that do not accept fd passing + */ + ret = send_fds(conn_src, conn_dst->id, fds, 1); + ASSERT_RETURN(ret == -ECOMM); + + /* + * memfd are kdbus payload + */ + ret = send_memfds(conn_src, conn_dst->id, memfd, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(conn_dst, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + cookie = time(NULL); + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + struct timespec now; + + /* + * A sync send/reply to a connection that do not + * accept fds should fail if it contains an fd + */ + msg_sync_reply = get_kdbus_msg_with_fd(conn_dst, + conn_dummy->id, + cookie, fds[0]); + ASSERT_EXIT(msg_sync_reply); + + ret = clock_gettime(CLOCK_MONOTONIC_COARSE, &now); + ASSERT_EXIT(ret == 0); + + msg_sync_reply->timeout_ns = now.tv_sec * 1000000000ULL + + now.tv_nsec + 100000000ULL; + msg_sync_reply->flags = KDBUS_MSG_EXPECT_REPLY; + + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg_sync_reply; + cmd.flags = KDBUS_SEND_SYNC_REPLY; + + ret = kdbus_cmd_send(conn_dst->fd, &cmd); + ASSERT_EXIT(ret == -ECOMM); + + /* + * Now send a normal message, but the sync reply + * will fail since it contains an fd that the + * original sender do not want. + * + * The original sender will fail with -ETIMEDOUT + */ + cookie++; + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_src->id, -1); + ASSERT_EXIT(ret == -EREMOTEIO); + + cookie++; + ret = kdbus_msg_recv_poll(conn_dst, 100, &msg, NULL); + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->cookie == cookie); + + free(msg_sync_reply); + kdbus_msg_free(msg); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_dummy, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + cookie++; + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* + * Try to reply with a kdbus connection handle, this should + * fail with -EOPNOTSUPP + */ + msg_sync_reply = get_kdbus_msg_with_fd(conn_src, + conn_dst->id, + cookie, conn_dst->fd); + ASSERT_RETURN(msg_sync_reply); + + msg_sync_reply->cookie_reply = cookie; + + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg_sync_reply; + + ret = kdbus_cmd_send(conn_src->fd, &cmd); + ASSERT_RETURN(ret == -EOPNOTSUPP); + + free(msg_sync_reply); + + /* + * Try to reply with a normal fd, this should fail even + * if the response is a sync reply + * + * From the sender view we fail with -ECOMM + */ + msg_sync_reply = get_kdbus_msg_with_fd(conn_src, + conn_dst->id, + cookie, fds[0]); + ASSERT_RETURN(msg_sync_reply); + + msg_sync_reply->cookie_reply = cookie; + + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg_sync_reply; + + ret = kdbus_cmd_send(conn_src->fd, &cmd); + ASSERT_RETURN(ret == -ECOMM); + + free(msg_sync_reply); + + /* + * Resend another normal message and check if the queue + * is clear + */ + cookie++; + ret = kdbus_msg_send(conn_src, NULL, cookie, 0, 0, 0, + conn_dst->id, 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + kdbus_conn_free(conn_dummy); + kdbus_conn_free(conn_dst); + kdbus_conn_free(conn_src); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int kdbus_send_multiple_fds(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + int ret, i; + unsigned int nfds; + int fds[KDBUS_CONN_MAX_FDS_PER_USER + 1]; + int memfds[KDBUS_MSG_MAX_ITEMS + 1]; + struct kdbus_msg *msg; + uint64_t dummy_value; + + dummy_value = time(NULL); + + for (i = 0; i < KDBUS_CONN_MAX_FDS_PER_USER + 1; i++) { + fds[i] = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_RETURN_VAL(fds[i] >= 0, -errno); + } + + /* Send KDBUS_CONN_MAX_FDS_PER_USER with one more fd */ + ret = send_fds(conn_src, conn_dst->id, fds, + KDBUS_CONN_MAX_FDS_PER_USER + 1); + ASSERT_RETURN(ret == -EMFILE); + + /* Retry with the correct KDBUS_CONN_MAX_FDS_PER_USER */ + ret = send_fds(conn_src, conn_dst->id, fds, + KDBUS_CONN_MAX_FDS_PER_USER); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* Check we got the right number of fds */ + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_CONN_MAX_FDS_PER_USER); + + kdbus_msg_free(msg); + + for (i = 0; i < KDBUS_MSG_MAX_ITEMS + 1; i++, dummy_value++) { + memfds[i] = memfd_write("memfd-name", + &dummy_value, + sizeof(dummy_value)); + ASSERT_RETURN_VAL(memfds[i] >= 0, memfds[i]); + } + + /* Send KDBUS_MSG_MAX_ITEMS with one more memfd */ + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_ITEMS + 1); + ASSERT_RETURN(ret == -E2BIG); + + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS + 1); + ASSERT_RETURN(ret == -E2BIG); + + /* Retry with the correct KDBUS_MSG_MAX_ITEMS */ + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* Check we got the right number of fds */ + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + + /* + * Combine multiple KDBUS_CONN_MAX_FDS_PER_USER+1 fds and + * 10 memfds + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER + 1, + memfds, 10); + ASSERT_RETURN(ret == -EMFILE); + + ret = kdbus_msg_recv(conn_dst, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Combine multiple KDBUS_CONN_MAX_FDS_PER_USER fds and + * (128 - 1) + 1 memfds, all fds take one item, while each + * memfd takes one item + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, (KDBUS_MSG_MAX_ITEMS - 1) + 1); + ASSERT_RETURN(ret == -E2BIG); + + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS + 1); + ASSERT_RETURN(ret == -E2BIG); + + ret = kdbus_msg_recv(conn_dst, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Send KDBUS_CONN_MAX_FDS_PER_USER fds + + * KDBUS_MSG_MAX_MEMFD_ITEMS memfds + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* Check we got the right number of fds */ + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_CONN_MAX_FDS_PER_USER + + KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + + /* + * Re-send fds + memfds, close them, but do not receive them + * and try to queue more + */ + ret = send_fds_memfds(conn_src, conn_dst->id, + fds, KDBUS_CONN_MAX_FDS_PER_USER, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + /* close old references and get a new ones */ + for (i = 0; i < KDBUS_CONN_MAX_FDS_PER_USER + 1; i++) { + close(fds[i]); + fds[i] = open("/dev/null", O_RDWR|O_CLOEXEC); + ASSERT_RETURN_VAL(fds[i] >= 0, -errno); + } + + /* should fail since we have already fds in the queue */ + ret = send_fds(conn_src, conn_dst->id, fds, + KDBUS_CONN_MAX_FDS_PER_USER); + ASSERT_RETURN(ret == -EMFILE); + + /* This should succeed */ + ret = send_memfds(conn_src, conn_dst->id, + memfds, KDBUS_MSG_MAX_MEMFD_ITEMS); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_CONN_MAX_FDS_PER_USER + + KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + nfds = kdbus_item_get_nfds(msg); + ASSERT_RETURN(nfds == KDBUS_MSG_MAX_MEMFD_ITEMS); + + kdbus_msg_free(msg); + + ret = kdbus_msg_recv(conn_dst, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + for (i = 0; i < KDBUS_CONN_MAX_FDS_PER_USER + 1; i++) + close(fds[i]); + + for (i = 0; i < KDBUS_MSG_MAX_ITEMS + 1; i++) + close(memfds[i]); + + return 0; +} + +int kdbus_test_fd_passing(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_src, *conn_dst; + const char *str = "stackenblocken"; + const struct kdbus_item *item; + struct kdbus_msg *msg; + unsigned int i; + uint64_t now; + int fds_conn[2]; + int sock_pair[2]; + int fds[2]; + int memfd; + int ret; + + now = (uint64_t) time(NULL); + + /* create two connections */ + conn_src = kdbus_hello(env->buspath, 0, NULL, 0); + conn_dst = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_src && conn_dst); + + fds_conn[0] = conn_src->fd; + fds_conn[1] = conn_dst->fd; + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair); + ASSERT_RETURN(ret == 0); + + /* Setup memfd */ + memfd = memfd_write("memfd-name", &now, sizeof(now)); + ASSERT_RETURN(memfd >= 0); + + /* Setup pipes */ + ret = pipe(fds); + ASSERT_RETURN(ret == 0); + + i = write(fds[1], str, strlen(str)); + ASSERT_RETURN(i == strlen(str)); + + /* + * Try to ass the handle of a connection as message payload. + * This must fail. + */ + ret = send_fds(conn_src, conn_dst->id, fds_conn, 2); + ASSERT_RETURN(ret == -ENOTSUP); + + ret = send_fds(conn_dst, conn_src->id, fds_conn, 2); + ASSERT_RETURN(ret == -ENOTSUP); + + ret = send_fds(conn_src, conn_dst->id, sock_pair, 2); + ASSERT_RETURN(ret == -ENOTSUP); + + /* + * Send fds and memfds to connection that do not accept fds + */ + ret = kdbus_test_no_fds(env, fds, (int *)&memfd); + ASSERT_RETURN(ret == 0); + + /* Try to broadcast file descriptors. This must fail. */ + ret = send_fds(conn_src, KDBUS_DST_ID_BROADCAST, fds, 1); + ASSERT_RETURN(ret == -ENOTUNIQ); + + /* Try to broadcast memfd. This must succeed. */ + ret = send_memfds(conn_src, KDBUS_DST_ID_BROADCAST, (int *)&memfd, 1); + ASSERT_RETURN(ret == 0); + + /* Open code this loop */ +loop_send_fds: + + /* + * Send the read end of the pipe and close it. + */ + ret = send_fds(conn_src, conn_dst->id, fds, 1); + ASSERT_RETURN(ret == 0); + close(fds[0]); + + ret = kdbus_msg_recv(conn_dst, &msg, NULL); + ASSERT_RETURN(ret == 0); + + KDBUS_ITEM_FOREACH(item, msg, items) { + if (item->type == KDBUS_ITEM_FDS) { + char tmp[14]; + int nfds = (item->size - KDBUS_ITEM_HEADER_SIZE) / + sizeof(int); + ASSERT_RETURN(nfds == 1); + + i = read(item->fds[0], tmp, sizeof(tmp)); + if (i != 0) { + ASSERT_RETURN(i == sizeof(tmp)); + ASSERT_RETURN(memcmp(tmp, str, sizeof(tmp)) == 0); + + /* Write EOF */ + close(fds[1]); + + /* + * Resend the read end of the pipe, + * the receiver still holds a reference + * to it... + */ + goto loop_send_fds; + } + + /* Got EOF */ + + /* + * Close the last reference to the read end + * of the pipe, other references are + * automatically closed just after send. + */ + close(item->fds[0]); + } + } + + /* + * Try to resend the read end of the pipe. Must fail with + * -EBADF since both the sender and receiver closed their + * references to it. We assume the above since sender and + * receiver are on the same process. + */ + ret = send_fds(conn_src, conn_dst->id, fds, 1); + ASSERT_RETURN(ret == -EBADF); + + /* Then we clear out received any data... */ + kdbus_msg_free(msg); + + ret = kdbus_send_multiple_fds(conn_src, conn_dst); + ASSERT_RETURN(ret == 0); + + close(sock_pair[0]); + close(sock_pair[1]); + close(memfd); + + kdbus_conn_free(conn_src); + kdbus_conn_free(conn_dst); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-free.c b/tools/testing/selftests/kdbus/test-free.c new file mode 100644 index 000000000000..f666da3e87cc --- /dev/null +++ b/tools/testing/selftests/kdbus/test-free.c @@ -0,0 +1,64 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +static int sample_ioctl_call(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_cmd_list cmd_list = { + .flags = KDBUS_LIST_QUEUED, + .size = sizeof(cmd_list), + }; + + ret = kdbus_cmd_list(env->conn->fd, &cmd_list); + ASSERT_RETURN(ret == 0); + + /* DON'T FREE THIS SLICE OF MEMORY! */ + + return TEST_OK; +} + +int kdbus_test_free(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_cmd_free cmd_free = {}; + + /* free an unallocated buffer */ + cmd_free.size = sizeof(cmd_free); + cmd_free.flags = 0; + cmd_free.offset = 0; + ret = kdbus_cmd_free(env->conn->fd, &cmd_free); + ASSERT_RETURN(ret == -ENXIO); + + /* free a buffer out of the pool's bounds */ + cmd_free.size = sizeof(cmd_free); + cmd_free.offset = POOL_SIZE + 1; + ret = kdbus_cmd_free(env->conn->fd, &cmd_free); + ASSERT_RETURN(ret == -ENXIO); + + /* + * The user application is responsible for freeing the allocated + * memory with the KDBUS_CMD_FREE ioctl, so let's test what happens + * if we forget about it. + */ + + ret = sample_ioctl_call(env); + ASSERT_RETURN(ret == 0); + + ret = sample_ioctl_call(env); + ASSERT_RETURN(ret == 0); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-match.c b/tools/testing/selftests/kdbus/test-match.c new file mode 100644 index 000000000000..2360dc1d76a4 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-match.c @@ -0,0 +1,441 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +int kdbus_test_match_id_add(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + int ret; + + memset(&buf, 0, sizeof(buf)); + + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = 0xdeafbeefdeaddead; + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_ID_ADD; + buf.item.chg.id = KDBUS_MATCH_ID_ANY; + + /* match on id add */ + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* create 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* 1st connection should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_ID_ADD); + ASSERT_RETURN(msg->items[0].id_change.id == conn->id); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_match_id_remove(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + size_t id; + int ret; + + /* create 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + id = conn->id; + + memset(&buf, 0, sizeof(buf)); + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = 0xdeafbeefdeaddead; + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_ID_REMOVE; + buf.item.chg.id = id; + + /* register match on 2nd connection */ + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* remove 2nd connection again */ + kdbus_conn_free(conn); + + /* 1st connection should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_ID_REMOVE); + ASSERT_RETURN(msg->items[0].id_change.id == id); + + return TEST_OK; +} + +int kdbus_test_match_replace(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_id_change chg; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + size_t id; + int ret; + + /* add a match to id_add */ + ASSERT_RETURN(kdbus_test_match_id_add(env) == TEST_OK); + + /* do a replace of the match from id_add to id_remove */ + memset(&buf, 0, sizeof(buf)); + + buf.cmd.size = sizeof(buf); + buf.cmd.cookie = 0xdeafbeefdeaddead; + buf.cmd.flags = KDBUS_MATCH_REPLACE; + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_ID_REMOVE; + buf.item.chg.id = KDBUS_MATCH_ID_ANY; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + + /* create 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + id = conn->id; + + /* 1st connection should _not_ have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret != 0); + + /* remove 2nd connection */ + kdbus_conn_free(conn); + + /* 1st connection should _now_ have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_ID_REMOVE); + ASSERT_RETURN(msg->items[0].id_change.id == id); + + return TEST_OK; +} + +int kdbus_test_match_name_add(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + struct kdbus_msg *msg; + char *name; + int ret; + + name = "foo.bla.blaz"; + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_ADD; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* acquire the name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + /* we should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_ADD); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == 0); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == env->conn->id); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + return TEST_OK; +} + +int kdbus_test_match_name_remove(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + struct kdbus_msg *msg; + char *name; + int ret; + + name = "foo.bla.blaz"; + + /* acquire the name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_REMOVE; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* release the name again */ + kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* we should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_REMOVE); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == env->conn->id); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == 0); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + return TEST_OK; +} + +int kdbus_test_match_name_change(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + struct kdbus_notify_name_change chg; + } item; + char name[64]; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + uint64_t flags; + char *name = "foo.bla.baz"; + int ret; + + /* acquire the name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.item.type = KDBUS_ITEM_NAME_CHANGE; + buf.item.chg.old_id.id = KDBUS_MATCH_ID_ANY; + buf.item.chg.new_id.id = KDBUS_MATCH_ID_ANY; + strncpy(buf.name, name, sizeof(buf.name) - 1); + buf.item.size = sizeof(buf.item) + strlen(buf.name) + 1; + buf.cmd.size = sizeof(buf.cmd) + buf.item.size; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* allow the new connection to own the same name */ + /* queue the 2nd connection as waiting owner */ + flags = KDBUS_NAME_QUEUE; + ret = kdbus_name_acquire(conn, name, &flags); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(flags & KDBUS_NAME_IN_QUEUE); + + /* release name from 1st connection */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* we should have received a notification */ + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg->items[0].type == KDBUS_ITEM_NAME_CHANGE); + ASSERT_RETURN(msg->items[0].name_change.old_id.id == env->conn->id); + ASSERT_RETURN(msg->items[0].name_change.new_id.id == conn->id); + ASSERT_RETURN(strcmp(msg->items[0].name_change.name, name) == 0); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +static int send_bloom_filter(const struct kdbus_conn *conn, + uint64_t cookie, + const uint8_t *filter, + size_t filter_size, + uint64_t filter_generation) +{ + struct kdbus_cmd_send cmd = {}; + struct kdbus_msg *msg; + struct kdbus_item *item; + uint64_t size; + int ret; + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + filter_size; + + msg = alloca(size); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn->id; + msg->dst_id = KDBUS_DST_ID_BROADCAST; + msg->flags = KDBUS_MSG_SIGNAL; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + msg->cookie = cookie; + + item = msg->items; + item->type = KDBUS_ITEM_BLOOM_FILTER; + item->size = KDBUS_ITEM_SIZE(sizeof(struct kdbus_bloom_filter)) + + filter_size; + + item->bloom_filter.generation = filter_generation; + memcpy(item->bloom_filter.data, filter, filter_size); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(conn->fd, &cmd); + if (ret < 0) { + kdbus_printf("error sending message: %d (%m)\n", ret); + return ret; + } + + return 0; +} + +int kdbus_test_match_bloom(struct kdbus_test_env *env) +{ + struct { + struct kdbus_cmd_match cmd; + struct { + uint64_t size; + uint64_t type; + uint8_t data_gen0[64]; + uint8_t data_gen1[64]; + } item; + } buf; + struct kdbus_conn *conn; + struct kdbus_msg *msg; + uint64_t cookie = 0xf000f00f; + uint8_t filter[64]; + int ret; + + /* install the match rule */ + memset(&buf, 0, sizeof(buf)); + buf.cmd.size = sizeof(buf); + + buf.item.size = sizeof(buf.item); + buf.item.type = KDBUS_ITEM_BLOOM_MASK; + buf.item.data_gen0[0] = 0x55; + buf.item.data_gen0[63] = 0x80; + + buf.item.data_gen1[1] = 0xaa; + buf.item.data_gen1[9] = 0x02; + + ret = kdbus_cmd_match_add(env->conn->fd, &buf.cmd); + ASSERT_RETURN(ret == 0); + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* a message with a 0'ed out filter must not reach the other peer */ + memset(filter, 0, sizeof(filter)); + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* now set the filter to the connection's mask and expect success */ + filter[0] = 0x55; + filter[63] = 0x80; + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + /* broaden the filter and try again. this should also succeed. */ + filter[0] = 0xff; + filter[8] = 0xff; + filter[63] = 0xff; + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + /* the same filter must not match against bloom generation 1 */ + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* set a different filter and try again */ + filter[1] = 0xaa; + filter[9] = 0x02; + ret = send_bloom_filter(conn, ++cookie, filter, sizeof(filter), 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(env->conn, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_conn_free(conn); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-message.c b/tools/testing/selftests/kdbus/test-message.c new file mode 100644 index 000000000000..d9b8fbddb1da --- /dev/null +++ b/tools/testing/selftests/kdbus/test-message.c @@ -0,0 +1,748 @@ +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <stdbool.h> +#include <sys/eventfd.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +/* maximum number of queued messages from the same individual user */ +#define KDBUS_CONN_MAX_MSGS 256 + +/* maximum number of queued requests waiting for a reply */ +#define KDBUS_CONN_MAX_REQUESTS_PENDING 128 + +/* maximum message payload size */ +#define KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE (2 * 1024UL * 1024UL) + +int kdbus_test_message_basic(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + struct kdbus_conn *sender; + struct kdbus_msg *msg; + uint64_t cookie = 0x1234abcd5678eeff; + uint64_t offset; + int ret; + + sender = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(sender != NULL); + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_empty(sender); + ASSERT_RETURN(ret == 0); + + /* send over 1st connection */ + ret = kdbus_msg_send(sender, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* Make sure that we do not get our own broadcasts */ + ret = kdbus_msg_recv(sender, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* ... and receive on the 2nd */ + ret = kdbus_msg_recv_poll(conn, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* Msgs that expect a reply must have timeout and cookie */ + ret = kdbus_msg_send(sender, NULL, 0, KDBUS_MSG_EXPECT_REPLY, + 0, 0, conn->id, 0, NULL); + ASSERT_RETURN(ret == -EINVAL); + + /* Faked replies with a valid reply cookie are rejected */ + ret = kdbus_msg_send_reply(conn, time(NULL) ^ cookie, sender->id); + ASSERT_RETURN(ret == -EPERM); + + ret = kdbus_free(conn, offset); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(sender); + kdbus_conn_free(conn); + + return TEST_OK; +} + +static int msg_recv_prio(struct kdbus_conn *conn, + int64_t requested_prio, + int64_t expected_prio) +{ + struct kdbus_cmd_recv recv = { + .size = sizeof(recv), + .flags = KDBUS_RECV_USE_PRIORITY, + .priority = requested_prio, + }; + struct kdbus_msg *msg; + int ret; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret < 0) { + kdbus_printf("error receiving message: %d (%m)\n", -errno); + return ret; + } + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + kdbus_msg_dump(conn, msg); + + if (msg->priority != expected_prio) { + kdbus_printf("expected message prio %lld, got %lld\n", + (unsigned long long) expected_prio, + (unsigned long long) msg->priority); + return -EINVAL; + } + + kdbus_msg_free(msg); + ret = kdbus_free(conn, recv.msg.offset); + if (ret < 0) + return ret; + + return 0; +} + +int kdbus_test_message_prio(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b; + uint64_t cookie = 0; + int ret; + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(a && b); + + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 25, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -600, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 10, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -35, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -100, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 20, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -15, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -800, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -150, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 10, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -800, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, -10, a->id, 0, NULL); + ASSERT_RETURN(ret == 0); + + ASSERT_RETURN(msg_recv_prio(a, -200, -800) == 0); + ASSERT_RETURN(msg_recv_prio(a, -100, -800) == 0); + ASSERT_RETURN(msg_recv_prio(a, -400, -600) == 0); + ASSERT_RETURN(msg_recv_prio(a, -400, -600) == -EAGAIN); + ASSERT_RETURN(msg_recv_prio(a, 10, -150) == 0); + ASSERT_RETURN(msg_recv_prio(a, 10, -100) == 0); + + kdbus_printf("--- get priority (all)\n"); + ASSERT_RETURN(kdbus_msg_recv(a, NULL, NULL) == 0); + + kdbus_conn_free(a); + kdbus_conn_free(b); + + return TEST_OK; +} + +static int kdbus_test_notify_kernel_quota(struct kdbus_test_env *env) +{ + int ret; + unsigned int i; + struct kdbus_conn *conn; + struct kdbus_conn *reader; + struct kdbus_msg *msg = NULL; + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + + reader = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(reader); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* Register for ID signals */ + ret = kdbus_add_match_id(reader, 0x1, KDBUS_ITEM_ID_ADD, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_id(reader, 0x2, KDBUS_ITEM_ID_REMOVE, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + /* Each iteration two notifications: add and remove ID */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS / 2; i++) { + struct kdbus_conn *notifier; + + notifier = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(notifier); + + kdbus_conn_free(notifier); + } + + /* + * Now the reader queue is full with kernel notfications, + * but as a user we still have room to push our messages. + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, 0, reader->id, + 0, NULL); + ASSERT_RETURN(ret == 0); + + /* More ID kernel notifications that will be lost */ + kdbus_conn_free(conn); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + kdbus_conn_free(conn); + + /* + * We lost only 3 packets since only signal msgs are + * accounted. The connection ID add/remove notification + */ + ret = kdbus_cmd_recv(reader->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS); + ASSERT_RETURN(recv.dropped_msgs == 3); + + msg = (struct kdbus_msg *)(reader->buf + recv.msg.offset); + kdbus_msg_free(msg); + + /* Read our queue */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS - 1; i++) { + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + + ret = kdbus_cmd_recv(reader->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(!(recv.return_flags & + KDBUS_RECV_RETURN_DROPPED_MSGS)); + + msg = (struct kdbus_msg *)(reader->buf + recv.msg.offset); + kdbus_msg_free(msg); + } + + ret = kdbus_msg_recv(reader, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(reader, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + kdbus_conn_free(reader); + + return 0; +} + +/* Return the number of message successfully sent */ +static int kdbus_fill_conn_queue(struct kdbus_conn *conn_src, + uint64_t dst_id, + unsigned int max_msgs) +{ + unsigned int i; + uint64_t cookie = 0; + size_t size; + struct kdbus_cmd_send cmd = {}; + struct kdbus_msg *msg; + int ret; + + size = sizeof(struct kdbus_msg); + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = conn_src->id; + msg->dst_id = dst_id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + for (i = 0; i < max_msgs; i++) { + msg->cookie = cookie++; + ret = kdbus_cmd_send(conn_src->fd, &cmd); + if (ret < 0) + break; + } + + free(msg); + + return i; +} + +static int kdbus_test_activator_quota(struct kdbus_test_env *env) +{ + int ret; + unsigned int i; + unsigned int activator_msgs_count = 0; + uint64_t cookie = time(NULL); + struct kdbus_conn *conn; + struct kdbus_conn *sender; + struct kdbus_conn *activator; + struct kdbus_msg *msg; + uint64_t flags = KDBUS_NAME_REPLACE_EXISTING; + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_policy_access access = { + .type = KDBUS_POLICY_ACCESS_USER, + .id = geteuid(), + .access = KDBUS_POLICY_OWN, + }; + + activator = kdbus_hello_activator(env->buspath, "foo.test.activator", + &access, 1); + ASSERT_RETURN(activator); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + sender = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn || sender); + + ret = kdbus_list(sender, KDBUS_LIST_NAMES | + KDBUS_LIST_UNIQUE | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED); + ASSERT_RETURN(ret == 0); + + for (i = 0; i < KDBUS_CONN_MAX_MSGS; i++) { + ret = kdbus_msg_send(sender, "foo.test.activator", + cookie++, 0, 0, 0, + KDBUS_DST_ID_NAME, + 0, NULL); + if (ret < 0) + break; + activator_msgs_count++; + } + + /* we must have at least sent one message */ + ASSERT_RETURN_VAL(i > 0, -errno); + ASSERT_RETURN(ret == -ENOBUFS); + + /* Good, activator queue is full now */ + + /* ENXIO on direct send (activators can never be addressed by ID) */ + ret = kdbus_msg_send(conn, NULL, cookie++, 0, 0, 0, activator->id, + 0, NULL); + ASSERT_RETURN(ret == -ENXIO); + + /* can't queue more */ + ret = kdbus_msg_send(conn, "foo.test.activator", cookie++, + 0, 0, 0, KDBUS_DST_ID_NAME, 0, NULL); + ASSERT_RETURN(ret == -ENOBUFS); + + /* no match installed, so the broadcast will not inc dropped_msgs */ + ret = kdbus_msg_send(sender, NULL, cookie++, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* Check activator queue */ + ret = kdbus_cmd_recv(activator->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs == 0); + + activator_msgs_count--; + + msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset); + kdbus_msg_free(msg); + + + /* Stage 1) of test check the pool memory quota */ + + /* Consume the connection pool memory */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS; i++) { + ret = kdbus_msg_send(sender, NULL, + cookie++, 0, 0, 0, conn->id, 0, NULL); + if (ret < 0) + break; + } + + /* consume one message, so later at least one can be moved */ + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + ret = kdbus_cmd_recv(conn->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs == 0); + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + kdbus_msg_free(msg); + + /* Try to acquire the name now */ + ret = kdbus_name_acquire(conn, "foo.test.activator", &flags); + ASSERT_RETURN(ret == 0); + + /* try to read messages and see if we have lost some */ + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + ret = kdbus_cmd_recv(conn->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs != 0); + + /* number of dropped msgs < received ones (at least one was moved) */ + ASSERT_RETURN(recv.dropped_msgs < activator_msgs_count); + + /* Deduct the number of dropped msgs from the activator msgs */ + activator_msgs_count -= recv.dropped_msgs; + + msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset); + kdbus_msg_free(msg); + + /* + * Release the name and hand it back to activator, now + * we should have 'activator_msgs_count' msgs again in + * the activator queue + */ + ret = kdbus_name_release(conn, "foo.test.activator"); + ASSERT_RETURN(ret == 0); + + /* make sure that we got our previous activator msgs */ + ret = kdbus_msg_recv(activator, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->src_id == sender->id); + + activator_msgs_count--; + + kdbus_msg_free(msg); + + + /* Stage 2) of test check max message quota */ + + /* Empty conn queue */ + for (i = 0; i < KDBUS_CONN_MAX_MSGS; i++) { + ret = kdbus_msg_recv(conn, NULL, NULL); + if (ret == -EAGAIN) + break; + } + + /* fill queue with max msgs quota */ + ret = kdbus_fill_conn_queue(sender, conn->id, KDBUS_CONN_MAX_MSGS); + ASSERT_RETURN(ret == KDBUS_CONN_MAX_MSGS); + + /* This one is lost but it is not accounted */ + ret = kdbus_msg_send(sender, NULL, + cookie++, 0, 0, 0, conn->id, 0, NULL); + ASSERT_RETURN(ret == -ENOBUFS); + + /* Acquire the name again */ + ret = kdbus_name_acquire(conn, "foo.test.activator", &flags); + ASSERT_RETURN(ret == 0); + + memset(&recv, 0, sizeof(recv)); + recv.size = sizeof(recv); + + /* + * Try to read messages and make sure that we have lost all + * the activator messages due to quota checks. Our queue is + * already full. + */ + ret = kdbus_cmd_recv(conn->fd, &recv); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv.dropped_msgs == activator_msgs_count); + + msg = (struct kdbus_msg *)(activator->buf + recv.msg.offset); + kdbus_msg_free(msg); + + kdbus_conn_free(sender); + kdbus_conn_free(conn); + kdbus_conn_free(activator); + + return 0; +} + +static int kdbus_test_expected_reply_quota(struct kdbus_test_env *env) +{ + int ret; + unsigned int i, n; + unsigned int count; + uint64_t cookie = 0x1234abcd5678eeff; + struct kdbus_conn *conn; + struct kdbus_conn *connections[9]; + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + for (i = 0; i < 9; i++) { + connections[i] = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(connections[i]); + } + + count = 0; + /* Send 16 messages to 8 different connections */ + for (i = 0; i < 8; i++) { + for (n = 0; n < 16; n++) { + ret = kdbus_msg_send(conn, NULL, cookie++, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, + connections[i]->id, + 0, NULL); + if (ret < 0) + break; + + count++; + } + } + + /* + * We should have queued at least + * KDBUS_CONN_MAX_REQUESTS_PENDING method call + */ + ASSERT_RETURN(count == KDBUS_CONN_MAX_REQUESTS_PENDING); + + /* + * Now try to send a message to the last connection, + * if we have reached KDBUS_CONN_MAX_REQUESTS_PENDING + * no further requests are allowed + */ + ret = kdbus_msg_send(conn, NULL, cookie++, KDBUS_MSG_EXPECT_REPLY, + 1000000000ULL, 0, connections[8]->id, 0, NULL); + ASSERT_RETURN(ret == -EMLINK); + + for (i = 0; i < 9; i++) + kdbus_conn_free(connections[i]); + + kdbus_conn_free(conn); + + return 0; +} + +int kdbus_test_pool_quota(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b, *c; + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *recv_msg; + struct kdbus_msg *msg; + uint64_t cookie = time(NULL); + uint64_t size; + unsigned int i; + char *payload; + int ret; + + /* just a guard */ + if (POOL_SIZE <= KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE || + POOL_SIZE % KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE != 0) + return 0; + + payload = calloc(KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE, sizeof(char)); + ASSERT_RETURN_VAL(payload, -ENOMEM); + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + c = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(a && b && c); + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = malloc(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = a->id; + msg->dst_id = c->id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = (uintptr_t)payload; + item->vec.size = KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE; + item = KDBUS_ITEM_NEXT(item); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + /* + * Send 2097248 bytes, a user is only allowed to get 33% of half of + * the free space of the pool, the already used space is + * accounted as free space + */ + size += KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE; + for (i = size; i < (POOL_SIZE / 2 / 3); i += size) { + msg->cookie = cookie++; + + ret = kdbus_cmd_send(a->fd, &cmd); + ASSERT_RETURN_VAL(ret == 0, ret); + } + + /* Try to get more than 33% */ + msg->cookie = cookie++; + ret = kdbus_cmd_send(a->fd, &cmd); + ASSERT_RETURN(ret == -ENOBUFS); + + /* We still can pass small messages */ + ret = kdbus_msg_send(b, NULL, cookie++, 0, 0, 0, c->id, 0, NULL); + ASSERT_RETURN(ret == 0); + + for (i = size; i < (POOL_SIZE / 2 / 3); i += size) { + ret = kdbus_msg_recv(c, &recv_msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv_msg->src_id == a->id); + + kdbus_msg_free(recv_msg); + } + + ret = kdbus_msg_recv(c, &recv_msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(recv_msg->src_id == b->id); + + kdbus_msg_free(recv_msg); + + ret = kdbus_msg_recv(c, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + free(msg); + free(payload); + + kdbus_conn_free(c); + kdbus_conn_free(b); + kdbus_conn_free(a); + + return 0; +} + +int kdbus_test_message_quota(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b; + uint64_t cookie = 0; + int ret; + int i; + + ret = kdbus_test_activator_quota(env); + ASSERT_RETURN(ret == 0); + + ret = kdbus_test_notify_kernel_quota(env); + ASSERT_RETURN(ret == 0); + + ret = kdbus_test_pool_quota(env); + ASSERT_RETURN(ret == 0); + + ret = kdbus_test_expected_reply_quota(env); + ASSERT_RETURN(ret == 0); + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + + ret = kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS); + ASSERT_RETURN(ret == KDBUS_CONN_MAX_MSGS); + + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 0, a->id, 0, NULL); + ASSERT_RETURN(ret == -ENOBUFS); + + for (i = 0; i < KDBUS_CONN_MAX_MSGS; ++i) { + ret = kdbus_msg_recv(a, NULL, NULL); + ASSERT_RETURN(ret == 0); + } + + ret = kdbus_msg_recv(a, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + ret = kdbus_fill_conn_queue(b, a->id, KDBUS_CONN_MAX_MSGS + 1); + ASSERT_RETURN(ret == KDBUS_CONN_MAX_MSGS); + + ret = kdbus_msg_send(b, NULL, ++cookie, 0, 0, 0, a->id, 0, NULL); + ASSERT_RETURN(ret == -ENOBUFS); + + kdbus_conn_free(a); + kdbus_conn_free(b); + + return TEST_OK; +} + +int kdbus_test_memory_access(struct kdbus_test_env *env) +{ + struct kdbus_conn *a, *b; + struct kdbus_cmd_send cmd = {}; + struct kdbus_item *item; + struct kdbus_msg *msg; + uint64_t test_addr = 0; + char line[256]; + uint64_t size; + FILE *f; + int ret; + + /* + * Search in /proc/kallsyms for the address of a kernel symbol that + * should always be there, regardless of the config. Use that address + * in a PAYLOAD_VEC item and make sure it's inaccessible. + */ + + f = fopen("/proc/kallsyms", "r"); + if (!f) + return TEST_SKIP; + + while (fgets(line, sizeof(line), f)) { + char *s = line; + + if (!strsep(&s, " ")) + continue; + + if (!strsep(&s, " ")) + continue; + + if (!strncmp(s, "mutex_lock", 10)) { + test_addr = strtoull(line, NULL, 16); + break; + } + } + + fclose(f); + + if (!test_addr) + return TEST_SKIP; + + a = kdbus_hello(env->buspath, 0, NULL, 0); + b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(a && b); + + size = sizeof(struct kdbus_msg); + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec)); + + msg = alloca(size); + ASSERT_RETURN_VAL(msg, -ENOMEM); + + memset(msg, 0, size); + msg->size = size; + msg->src_id = a->id; + msg->dst_id = b->id; + msg->payload_type = KDBUS_PAYLOAD_DBUS; + + item = msg->items; + item->type = KDBUS_ITEM_PAYLOAD_VEC; + item->size = KDBUS_ITEM_HEADER_SIZE + sizeof(struct kdbus_vec); + item->vec.address = test_addr; + item->vec.size = sizeof(void*); + item = KDBUS_ITEM_NEXT(item); + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)msg; + + ret = kdbus_cmd_send(a->fd, &cmd); + ASSERT_RETURN(ret == -EFAULT); + + kdbus_conn_free(b); + kdbus_conn_free(a); + + return 0; +} diff --git a/tools/testing/selftests/kdbus/test-metadata-ns.c b/tools/testing/selftests/kdbus/test-metadata-ns.c new file mode 100644 index 000000000000..395a3faabf07 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-metadata-ns.c @@ -0,0 +1,506 @@ +/* + * Test metadata in new namespaces. Even if our tests can run + * in a namespaced setup, this test is necessary so we can inspect + * metadata on the same kdbusfs but between multiple namespaces + */ + +#include <stdio.h> +#include <string.h> +#include <sched.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/eventfd.h> +#include <sys/syscall.h> +#include <sys/capability.h> +#include <linux/sched.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static const struct kdbus_creds privileged_creds = {}; + +static const struct kdbus_creds unmapped_creds = { + .uid = UNPRIV_UID, + .euid = UNPRIV_UID, + .suid = UNPRIV_UID, + .fsuid = UNPRIV_UID, + .gid = UNPRIV_GID, + .egid = UNPRIV_GID, + .sgid = UNPRIV_GID, + .fsgid = UNPRIV_GID, +}; + +static const struct kdbus_pids unmapped_pids = {}; + +/* Get only the first item */ +static struct kdbus_item *kdbus_get_item(struct kdbus_msg *msg, + uint64_t type) +{ + struct kdbus_item *item; + + KDBUS_ITEM_FOREACH(item, msg, items) + if (item->type == type) + return item; + + return NULL; +} + +static int kdbus_match_kdbus_creds(struct kdbus_msg *msg, + const struct kdbus_creds *expected_creds) +{ + struct kdbus_item *item; + + item = kdbus_get_item(msg, KDBUS_ITEM_CREDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(memcmp(&item->creds, expected_creds, + sizeof(struct kdbus_creds)) == 0); + + return 0; +} + +static int kdbus_match_kdbus_pids(struct kdbus_msg *msg, + const struct kdbus_pids *expected_pids) +{ + struct kdbus_item *item; + + item = kdbus_get_item(msg, KDBUS_ITEM_PIDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(memcmp(&item->pids, expected_pids, + sizeof(struct kdbus_pids)) == 0); + + return 0; +} + +static int __kdbus_clone_userns_test(const char *bus, + struct kdbus_conn *conn, + uint64_t grandpa_pid, + int signal_fd) +{ + int clone_ret; + int ret; + struct kdbus_msg *msg = NULL; + const struct kdbus_item *item; + uint64_t cookie = time(NULL) ^ 0xdeadbeef; + struct kdbus_conn *unpriv_conn = NULL; + struct kdbus_pids parent_pids = { + .pid = getppid(), + .tid = getppid(), + .ppid = grandpa_pid, + }; + + ret = drop_privileges(UNPRIV_UID, UNPRIV_GID); + ASSERT_EXIT(ret == 0); + + unpriv_conn = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(unpriv_conn); + + ret = kdbus_add_match_empty(unpriv_conn); + ASSERT_EXIT(ret == 0); + + /* + * ping privileged connection from this new unprivileged + * one + */ + + ret = kdbus_msg_send(unpriv_conn, NULL, cookie, 0, 0, + 0, conn->id, 0, NULL); + ASSERT_EXIT(ret == 0); + + /* + * Since we just dropped privileges, the dumpable flag + * was just cleared which makes the /proc/$clone_child/uid_map + * to be owned by root, hence any userns uid mapping will fail + * with -EPERM since the mapping will be done by uid 65534. + * + * To avoid this set the dumpable flag again which makes + * procfs update the /proc/$clone_child/ inodes owner to 65534. + * + * Using this we will be able write to /proc/$clone_child/uid_map + * as uid 65534 and map the uid 65534 to 0 inside the user namespace. + */ + ret = prctl(PR_SET_DUMPABLE, SUID_DUMP_USER); + ASSERT_EXIT(ret == 0); + + /* Make child privileged in its new userns and run tests */ + + ret = RUN_CLONE_CHILD(&clone_ret, + SIGCHLD | CLONE_NEWUSER | CLONE_NEWPID, + ({ 0; /* Clone setup, nothing */ }), + ({ + eventfd_t event_status = 0; + struct kdbus_conn *userns_conn; + + /* ping connection from the new user namespace */ + userns_conn = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(userns_conn); + + ret = kdbus_add_match_empty(userns_conn); + ASSERT_EXIT(ret == 0); + + cookie++; + ret = kdbus_msg_send(userns_conn, NULL, cookie, + 0, 0, 0, conn->id, 0, NULL); + ASSERT_EXIT(ret == 0); + + /* Parent did send */ + ret = eventfd_read(signal_fd, &event_status); + ASSERT_RETURN(ret >= 0 && event_status == 1); + + /* + * Receive from privileged connection + */ + kdbus_printf("Privileged → unprivileged/privileged " + "in its userns " + "(different userns and pidns):\n"); + ret = kdbus_msg_recv_poll(userns_conn, 300, &msg, NULL); + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == userns_conn->id); + + /* Different namespaces no CAPS */ + item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); + ASSERT_EXIT(item == NULL); + + /* uid/gid not mapped, so we have unpriv cached creds */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_EXIT(ret == 0); + + /* + * Diffent pid namepsaces. This is the child pidns + * so it should not see its parent kdbus_pids + */ + ret = kdbus_match_kdbus_pids(msg, &unmapped_pids); + ASSERT_EXIT(ret == 0); + + kdbus_msg_free(msg); + + + /* + * Receive broadcast from privileged connection + */ + kdbus_printf("Privileged → unprivileged/privileged " + "in its userns " + "(different userns and pidns):\n"); + ret = kdbus_msg_recv_poll(userns_conn, 300, &msg, NULL); + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == KDBUS_DST_ID_BROADCAST); + + /* Different namespaces no CAPS */ + item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); + ASSERT_EXIT(item == NULL); + + /* uid/gid not mapped, so we have unpriv cached creds */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_EXIT(ret == 0); + + /* + * Diffent pid namepsaces. This is the child pidns + * so it should not see its parent kdbus_pids + */ + ret = kdbus_match_kdbus_pids(msg, &unmapped_pids); + ASSERT_EXIT(ret == 0); + + kdbus_msg_free(msg); + + kdbus_conn_free(userns_conn); + }), + ({ + /* Parent setup map child uid/gid */ + ret = userns_map_uid_gid(pid, "0 65534 1", "0 65534 1"); + ASSERT_EXIT(ret == 0); + }), + ({ 0; })); + /* Unprivileged was not able to create user namespace */ + if (clone_ret == -EPERM) { + kdbus_printf("-- CLONE_NEWUSER TEST Failed for " + "uid: %u\n -- Make sure that your kernel " + "do not allow CLONE_NEWUSER for " + "unprivileged users\n", UNPRIV_UID); + ret = 0; + goto out; + } + + ASSERT_EXIT(ret == 0); + + + /* + * Receive from privileged connection + */ + kdbus_printf("\nPrivileged → unprivileged (same namespaces):\n"); + ret = kdbus_msg_recv_poll(unpriv_conn, 300, &msg, NULL); + + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == unpriv_conn->id); + + /* will get the privileged creds */ + ret = kdbus_match_kdbus_creds(msg, &privileged_creds); + ASSERT_EXIT(ret == 0); + + /* Same pidns so will get the kdbus_pids */ + ret = kdbus_match_kdbus_pids(msg, &parent_pids); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + + + /* + * Receive broadcast from privileged connection + */ + kdbus_printf("\nPrivileged → unprivileged (same namespaces):\n"); + ret = kdbus_msg_recv_poll(unpriv_conn, 300, &msg, NULL); + + ASSERT_EXIT(ret == 0); + ASSERT_EXIT(msg->dst_id == KDBUS_DST_ID_BROADCAST); + + /* will get the privileged creds */ + ret = kdbus_match_kdbus_creds(msg, &privileged_creds); + ASSERT_EXIT(ret == 0); + + ret = kdbus_match_kdbus_pids(msg, &parent_pids); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + +out: + kdbus_conn_free(unpriv_conn); + + return ret; +} + +static int kdbus_clone_userns_test(const char *bus, + struct kdbus_conn *conn) +{ + int ret; + int status; + int efd = -1; + pid_t pid, ppid; + uint64_t unpriv_conn_id = 0; + uint64_t userns_conn_id = 0; + struct kdbus_msg *msg; + const struct kdbus_item *item; + struct kdbus_pids expected_pids; + struct kdbus_conn *monitor = NULL; + + kdbus_printf("STARTING TEST 'metadata-ns'.\n"); + + monitor = kdbus_hello(bus, KDBUS_HELLO_MONITOR, NULL, 0); + ASSERT_EXIT(monitor); + + /* + * parent will signal to child that is in its + * userns to read its queue + */ + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + ppid = getppid(); + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, -errno); + + if (pid == 0) { + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT_VAL(ret == 0, -errno); + + ret = __kdbus_clone_userns_test(bus, conn, ppid, efd); + _exit(ret); + } + + + /* Phase 1) privileged receives from unprivileged */ + + /* + * Receive from the unprivileged child + */ + kdbus_printf("\nUnprivileged → privileged (same namespaces):\n"); + ret = kdbus_msg_recv_poll(conn, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + unpriv_conn_id = msg->src_id; + + /* Unprivileged user */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_RETURN(ret == 0); + + /* Set the expected creds_pids */ + expected_pids = (struct kdbus_pids) { + .pid = pid, + .tid = pid, + .ppid = getpid(), + }; + ret = kdbus_match_kdbus_pids(msg, &expected_pids); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + + + /* + * Receive from the unprivileged that is in his own + * userns and pidns + */ + + kdbus_printf("\nUnprivileged/privileged in its userns → privileged " + "(different userns and pidns)\n"); + ret = kdbus_msg_recv_poll(conn, 300, &msg, NULL); + if (ret == -ETIMEDOUT) + /* perhaps unprivileged userns is not allowed */ + goto wait; + + ASSERT_RETURN(ret == 0); + + userns_conn_id = msg->src_id; + + /* We do not share the userns, os no KDBUS_ITEM_CAPS */ + item = kdbus_get_item(msg, KDBUS_ITEM_CAPS); + ASSERT_RETURN(item == NULL); + + /* + * Compare received items, creds must be translated into + * the receiver user namespace, so the user is unprivileged + */ + ret = kdbus_match_kdbus_creds(msg, &unmapped_creds); + ASSERT_RETURN(ret == 0); + + /* + * We should have the kdbus_pids since we are the parent + * pidns + */ + item = kdbus_get_item(msg, KDBUS_ITEM_PIDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(memcmp(&item->pids, &unmapped_pids, + sizeof(struct kdbus_pids)) != 0); + + /* + * Parent pid of the unprivileged/privileged in its userns + * is the unprivileged child pid that was forked here. + */ + ASSERT_RETURN((uint64_t)pid == item->pids.ppid); + + kdbus_msg_free(msg); + + + /* Phase 2) Privileged connection sends now 3 packets */ + + /* + * Sending to unprivileged connections a unicast + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, + 0, unpriv_conn_id, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* signal to child that is in its userns */ + ret = eventfd_write(efd, 1); + ASSERT_EXIT(ret == 0); + + /* + * Sending to unprivileged/privilged in its userns + * connections a unicast + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, + 0, userns_conn_id, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* + * Sending to unprivileged connections a broadcast + */ + ret = kdbus_msg_send(conn, NULL, 0xdeadbeef, 0, 0, + 0, KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + +wait: + ret = waitpid(pid, &status, 0); + ASSERT_RETURN(ret >= 0); + + ASSERT_RETURN(WIFEXITED(status)) + ASSERT_RETURN(!WEXITSTATUS(status)); + + /* Dump monitor queue */ + kdbus_printf("\n\nMonitor queue:\n"); + for (;;) { + ret = kdbus_msg_recv_poll(monitor, 100, &msg, NULL); + if (ret < 0) + break; + + if (msg->payload_type == KDBUS_PAYLOAD_DBUS) { + /* + * Parent pidns should see all the + * pids + */ + item = kdbus_get_item(msg, KDBUS_ITEM_PIDS); + ASSERT_RETURN(item); + + ASSERT_RETURN(item->pids.pid != 0 && + item->pids.tid != 0 && + item->pids.ppid != 0); + } + + kdbus_msg_free(msg); + } + + kdbus_conn_free(monitor); + close(efd); + + return 0; +} + +int kdbus_test_metadata_ns(struct kdbus_test_env *env) +{ + int ret; + struct kdbus_conn *holder, *conn; + struct kdbus_policy_access policy_access = { + /* Allow world so we can inspect metadata in namespace */ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = geteuid(), + .access = KDBUS_POLICY_TALK, + }; + + /* + * We require user-namespaces and all uids/gids + * should be mapped (we can just require the necessary ones) + */ + if (!config_user_ns_is_enabled() || + !all_uids_gids_are_mapped()) + return TEST_SKIP; + + ret = test_is_capable(CAP_SETUID, CAP_SETGID, CAP_SYS_ADMIN, -1); + ASSERT_RETURN(ret >= 0); + + /* no enough privileges, SKIP test */ + if (!ret) + return TEST_SKIP; + + holder = kdbus_hello_registrar(env->buspath, "com.example.metadata", + &policy_access, 1, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(conn, "com.example.metadata", NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_clone_userns_test(env->buspath, conn); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(holder); + kdbus_conn_free(conn); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-monitor.c b/tools/testing/selftests/kdbus/test-monitor.c new file mode 100644 index 000000000000..ae87d879716c --- /dev/null +++ b/tools/testing/selftests/kdbus/test-monitor.c @@ -0,0 +1,177 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/mman.h> +#include <sys/capability.h> +#include <sys/wait.h> + +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +int kdbus_test_monitor(struct kdbus_test_env *env) +{ + struct kdbus_conn *monitor, *conn; + unsigned int cookie = 0xdeadbeef; + struct kdbus_msg *msg; + uint64_t offset = 0; + int ret; + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* add matches to make sure the monitor do not trigger an item add or + * remove on connect and disconnect, respectively. + */ + ret = kdbus_add_match_id(conn, 0x1, KDBUS_ITEM_ID_ADD, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + ret = kdbus_add_match_id(conn, 0x2, KDBUS_ITEM_ID_REMOVE, + KDBUS_MATCH_ID_ANY); + ASSERT_RETURN(ret == 0); + + /* register a monitor */ + monitor = kdbus_hello(env->buspath, KDBUS_HELLO_MONITOR, NULL, 0); + ASSERT_RETURN(monitor); + + /* make sure we did not receive a monitor connect notification */ + ret = kdbus_msg_recv(conn, &msg, &offset); + ASSERT_RETURN(ret == -EAGAIN); + + /* check that a monitor cannot acquire a name */ + ret = kdbus_name_acquire(monitor, "foo.bar.baz", NULL); + ASSERT_RETURN(ret == -EOPNOTSUPP); + + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, conn->id, + 0, NULL); + ASSERT_RETURN(ret == 0); + + /* the recipient should have gotten the message */ + ret = kdbus_msg_recv(conn, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + kdbus_msg_free(msg); + kdbus_free(conn, offset); + + /* and so should the monitor */ + ret = kdbus_msg_recv(monitor, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + /* Installing matches for monitors must fais must fail */ + ret = kdbus_add_match_empty(monitor); + ASSERT_RETURN(ret == -EOPNOTSUPP); + + cookie++; + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + /* The monitor should get the message. */ + ret = kdbus_msg_recv_poll(monitor, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + /* + * Since we are the only monitor, update the attach flags + * and tell we are not interessted in attach flags recv + */ + + ret = kdbus_conn_update_attach_flags(monitor, + _KDBUS_ATTACH_ALL, + 0); + ASSERT_RETURN(ret == 0); + + cookie++; + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(monitor, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + ret = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + /* + * Now we are interested in KDBUS_ITEM_TIMESTAMP and + * KDBUS_ITEM_CREDS + */ + ret = kdbus_conn_update_attach_flags(monitor, + _KDBUS_ATTACH_ALL, + KDBUS_ATTACH_TIMESTAMP | + KDBUS_ATTACH_CREDS); + ASSERT_RETURN(ret == 0); + + cookie++; + ret = kdbus_msg_send(env->conn, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(monitor, 100, &msg, &offset); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == cookie); + + ret = kdbus_item_in_message(msg, KDBUS_ITEM_TIMESTAMP); + ASSERT_RETURN(ret == 1); + + ret = kdbus_item_in_message(msg, KDBUS_ITEM_CREDS); + ASSERT_RETURN(ret == 1); + + /* the KDBUS_ITEM_PID_COMM was not requested */ + ret = kdbus_item_in_message(msg, KDBUS_ITEM_PID_COMM); + ASSERT_RETURN(ret == 0); + + kdbus_msg_free(msg); + kdbus_free(monitor, offset); + + kdbus_conn_free(monitor); + /* make sure we did not receive a monitor disconnect notification */ + ret = kdbus_msg_recv(conn, &msg, &offset); + ASSERT_RETURN(ret == -EAGAIN); + + kdbus_conn_free(conn); + + /* Make sure that monitor as unprivileged is not allowed */ + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + if (ret && all_uids_gids_are_mapped()) { + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({ + monitor = kdbus_hello(env->buspath, + KDBUS_HELLO_MONITOR, + NULL, 0); + ASSERT_EXIT(!monitor && errno == EPERM); + + _exit(EXIT_SUCCESS); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + } + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-names.c b/tools/testing/selftests/kdbus/test-names.c new file mode 100644 index 000000000000..66ebb47370eb --- /dev/null +++ b/tools/testing/selftests/kdbus/test-names.c @@ -0,0 +1,194 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <getopt.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" +#include "kdbus-test.h" + +static int conn_is_name_owner(const struct kdbus_conn *conn, + const char *needle) +{ + struct kdbus_cmd_list cmd_list = { .size = sizeof(cmd_list) }; + struct kdbus_info *name, *list; + bool found = false; + int ret; + + cmd_list.flags = KDBUS_LIST_NAMES; + + ret = kdbus_cmd_list(conn->fd, &cmd_list); + ASSERT_RETURN(ret == 0); + + list = (struct kdbus_info *)(conn->buf + cmd_list.offset); + KDBUS_FOREACH(name, list, cmd_list.list_size) { + struct kdbus_item *item; + const char *n = NULL; + + KDBUS_ITEM_FOREACH(item, name, items) + if (item->type == KDBUS_ITEM_OWNED_NAME) + n = item->name.name; + + if (name->id == conn->id && + n && strcmp(needle, n) == 0) { + found = true; + break; + } + } + + ret = kdbus_free(conn, cmd_list.offset); + ASSERT_RETURN(ret == 0); + + return found ? 0 : -1; +} + +int kdbus_test_name_basic(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + char *name, *dot_name, *invalid_name, *wildcard_name; + int ret; + + name = "foo.bla.blaz"; + dot_name = ".bla.blaz"; + invalid_name = "foo"; + wildcard_name = "foo.bla.bl.*"; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* acquire name "foo.bar.xxx" name */ + ret = kdbus_name_acquire(conn, "foo.bar.xxx", NULL); + ASSERT_RETURN(ret == 0); + + /* Name is not valid, must fail */ + ret = kdbus_name_acquire(env->conn, dot_name, NULL); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_name_acquire(env->conn, invalid_name, NULL); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_name_acquire(env->conn, wildcard_name, NULL); + ASSERT_RETURN(ret == -EINVAL); + + /* check that we can acquire a name */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* ... and release it again */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret != 0); + + /* check that we can't release it again */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == -ESRCH); + + /* check that we can't release a name that we don't own */ + ret = kdbus_name_release(env->conn, "foo.bar.xxx"); + ASSERT_RETURN(ret == -EADDRINUSE); + + /* Name is not valid, must fail */ + ret = kdbus_name_release(env->conn, dot_name); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_name_release(env->conn, invalid_name); + ASSERT_RETURN(ret == -ESRCH); + + ret = kdbus_name_release(env->conn, wildcard_name); + ASSERT_RETURN(ret == -ESRCH); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_name_conflict(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + char *name; + int ret; + + name = "foo.bla.blaz"; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* allow the new connection to own the same name */ + /* acquire name from the 1st connection */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* check that we can't acquire it again from the 1st connection */ + ret = kdbus_name_acquire(env->conn, name, NULL); + ASSERT_RETURN(ret == -EALREADY); + + /* check that we also can't acquire it again from the 2nd connection */ + ret = kdbus_name_acquire(conn, name, NULL); + ASSERT_RETURN(ret == -EEXIST); + + kdbus_conn_free(conn); + + return TEST_OK; +} + +int kdbus_test_name_queue(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn; + const char *name; + uint64_t flags; + int ret; + + name = "foo.bla.blaz"; + + flags = KDBUS_NAME_ALLOW_REPLACEMENT; + + /* create a 2nd connection */ + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn != NULL); + + /* allow the new connection to own the same name */ + /* acquire name from the 1st connection */ + ret = kdbus_name_acquire(env->conn, name, &flags); + ASSERT_RETURN(ret == 0); + + ret = conn_is_name_owner(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* queue the 2nd connection as waiting owner */ + flags = KDBUS_NAME_QUEUE; + ret = kdbus_name_acquire(conn, name, &flags); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(flags & KDBUS_NAME_IN_QUEUE); + + /* release name from 1st connection */ + ret = kdbus_name_release(env->conn, name); + ASSERT_RETURN(ret == 0); + + /* now the name should be owned by the 2nd connection */ + ret = conn_is_name_owner(conn, name); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-policy-ns.c b/tools/testing/selftests/kdbus/test-policy-ns.c new file mode 100644 index 000000000000..2d70ee486e76 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-policy-ns.c @@ -0,0 +1,632 @@ +/* + * Test metadata and policies in new namespaces. Even if our tests + * can run in a namespaced setup, this test is necessary so we can + * inspect policies on the same kdbusfs but between multiple + * namespaces. + * + * Copyright (C) 2014-2015 Djalal Harouni + * + * kdbus is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + */ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <pthread.h> +#include <sched.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/eventfd.h> +#include <sys/syscall.h> +#include <sys/capability.h> +#include <linux/sched.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +#define MAX_CONN 64 +#define POLICY_NAME "foo.test.policy-test" + +#define KDBUS_CONN_MAX_MSGS_PER_USER 16 + +/** + * Note: this test can be used to inspect policy_db->talk_access_hash + * + * The purpose of these tests: + * 1) Check KDBUS_POLICY_TALK + * 2) Check the cache state: kdbus_policy_db->talk_access_hash + * Should be extended + */ + +/** + * Check a list of connections against conn_db[0] + * conn_db[0] will own the name "foo.test.policy-test" and the + * policy holder connection for this name will update the policy + * entries, so different use cases can be tested. + */ +static struct kdbus_conn **conn_db; + +static void *kdbus_recv_echo(void *ptr) +{ + int ret; + struct kdbus_conn *conn = ptr; + + ret = kdbus_msg_recv_poll(conn, 200, NULL, NULL); + + return (void *)(long)ret; +} + +/* Trigger kdbus_policy_set() */ +static int kdbus_set_policy_talk(struct kdbus_conn *conn, + const char *name, + uid_t id, unsigned int type) +{ + int ret; + struct kdbus_policy_access access = { + .type = type, + .id = id, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn, name, &access, 1); + ASSERT_RETURN(ret == 0); + + return TEST_OK; +} + +/* return TEST_OK or TEST_ERR on failure */ +static int kdbus_register_same_activator(char *bus, const char *name, + struct kdbus_conn **c) +{ + int ret; + struct kdbus_conn *activator; + + activator = kdbus_hello_activator(bus, name, NULL, 0); + if (activator) { + *c = activator; + fprintf(stderr, "--- error was able to register name twice '%s'.\n", + name); + return TEST_ERR; + } + + ret = -errno; + /* -EEXIST means test succeeded */ + if (ret == -EEXIST) + return TEST_OK; + + return TEST_ERR; +} + +/* return TEST_OK or TEST_ERR on failure */ +static int kdbus_register_policy_holder(char *bus, const char *name, + struct kdbus_conn **conn) +{ + struct kdbus_conn *c; + struct kdbus_policy_access access[2]; + + access[0].type = KDBUS_POLICY_ACCESS_USER; + access[0].access = KDBUS_POLICY_OWN; + access[0].id = geteuid(); + + access[1].type = KDBUS_POLICY_ACCESS_WORLD; + access[1].access = KDBUS_POLICY_TALK; + access[1].id = geteuid(); + + c = kdbus_hello_registrar(bus, name, access, 2, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(c); + + *conn = c; + + return TEST_OK; +} + +/** + * Create new threads for receiving from multiple senders, + * The 'conn_db' will be populated by newly created connections. + * Caller should free all allocated connections. + * + * return 0 on success, negative errno on failure. + */ +static int kdbus_recv_in_threads(const char *bus, const char *name, + struct kdbus_conn **conn_db) +{ + int ret; + bool pool_full = false; + unsigned int sent_packets = 0; + unsigned int lost_packets = 0; + unsigned int i, tid; + unsigned long dst_id; + unsigned long cookie = 1; + unsigned int thread_nr = MAX_CONN - 1; + pthread_t thread_id[MAX_CONN - 1] = {'\0'}; + + dst_id = name ? KDBUS_DST_ID_NAME : conn_db[0]->id; + + for (tid = 0, i = 1; tid < thread_nr; tid++, i++) { + ret = pthread_create(&thread_id[tid], NULL, + kdbus_recv_echo, (void *)conn_db[0]); + if (ret < 0) { + ret = -errno; + kdbus_printf("error pthread_create: %d (%m)\n", + ret); + break; + } + + /* just free before re-using */ + kdbus_conn_free(conn_db[i]); + conn_db[i] = NULL; + + /* We need to create connections here */ + conn_db[i] = kdbus_hello(bus, 0, NULL, 0); + if (!conn_db[i]) { + ret = -errno; + break; + } + + ret = kdbus_add_match_empty(conn_db[i]); + if (ret < 0) + break; + + ret = kdbus_msg_send(conn_db[i], name, cookie++, + 0, 0, 0, dst_id, 0, NULL); + if (ret < 0) { + /* + * Receivers are not reading their messages, + * not scheduled ?! + * + * So set the pool full here, perhaps the + * connection pool or queue was full, later + * recheck receivers errors + */ + if (ret == -ENOBUFS || ret == -EXFULL) + pool_full = true; + break; + } + + sent_packets++; + } + + for (tid = 0; tid < thread_nr; tid++) { + int thread_ret = 0; + + if (thread_id[tid]) { + pthread_join(thread_id[tid], (void *)&thread_ret); + if (thread_ret < 0) { + /* Update only if send did not fail */ + if (ret == 0) + ret = thread_ret; + + lost_packets++; + } + } + } + + /* + * When sending if we did fail with -ENOBUFS or -EXFULL + * then we should have set lost_packet and we should at + * least have sent_packets set to KDBUS_CONN_MAX_MSGS_PER_USER + */ + if (pool_full) { + ASSERT_RETURN(lost_packets > 0); + + /* + * We should at least send KDBUS_CONN_MAX_MSGS_PER_USER + * + * For every send operation we create a thread to + * recv the packet, so we keep the queue clean + */ + ASSERT_RETURN(sent_packets >= KDBUS_CONN_MAX_MSGS_PER_USER); + + /* + * Set ret to zero since we only failed due to + * the receiving threads that have not been + * scheduled + */ + ret = 0; + } + + return ret; +} + +/* Return: TEST_OK or TEST_ERR on failure */ +static int kdbus_normal_test(const char *bus, const char *name, + struct kdbus_conn **conn_db) +{ + int ret; + + ret = kdbus_recv_in_threads(bus, name, conn_db); + ASSERT_RETURN(ret >= 0); + + return TEST_OK; +} + +static int kdbus_fork_test_by_id(const char *bus, + struct kdbus_conn **conn_db, + int parent_status, int child_status) +{ + int ret; + pid_t pid; + uint64_t cookie = 0x9876ecba; + struct kdbus_msg *msg = NULL; + uint64_t offset = 0; + int status = 0; + + /* + * If the child_status is not EXIT_SUCCESS, then we expect + * that sending from the child will fail, thus receiving + * from parent must error with -ETIMEDOUT, and vice versa. + */ + bool parent_timedout = !!child_status; + bool child_timedout = !!parent_status; + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + struct kdbus_conn *conn_src; + + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT(ret == 0); + + ret = drop_privileges(65534, 65534); + ASSERT_EXIT(ret == 0); + + conn_src = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(conn_src); + + ret = kdbus_add_match_empty(conn_src); + ASSERT_EXIT(ret == 0); + + /* + * child_status is always checked against send + * operations, in case it fails always return + * EXIT_FAILURE. + */ + ret = kdbus_msg_send(conn_src, NULL, cookie, + 0, 0, 0, conn_db[0]->id, 0, NULL); + ASSERT_EXIT(ret == child_status); + + ret = kdbus_msg_recv_poll(conn_src, 100, NULL, NULL); + + kdbus_conn_free(conn_src); + + /* + * Child kdbus_msg_recv_poll() should timeout since + * the parent_status was set to a non EXIT_SUCCESS + * value. + */ + if (child_timedout) + _exit(ret == -ETIMEDOUT ? EXIT_SUCCESS : EXIT_FAILURE); + + _exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + } + + ret = kdbus_msg_recv_poll(conn_db[0], 100, &msg, &offset); + /* + * If parent_timedout is set then this should fail with + * -ETIMEDOUT since the child_status was set to a non + * EXIT_SUCCESS value. Otherwise, assume + * that kdbus_msg_recv_poll() has succeeded. + */ + if (parent_timedout) { + ASSERT_RETURN_VAL(ret == -ETIMEDOUT, TEST_ERR); + + /* timedout no need to continue, we don't have the + * child connection ID, so just terminate. */ + goto out; + } else { + ASSERT_RETURN_VAL(ret == 0, ret); + } + + ret = kdbus_msg_send(conn_db[0], NULL, ++cookie, + 0, 0, 0, msg->src_id, 0, NULL); + /* + * parent_status is checked against send operations, + * on failures always return TEST_ERR. + */ + ASSERT_RETURN_VAL(ret == parent_status, TEST_ERR); + + kdbus_msg_free(msg); + kdbus_free(conn_db[0], offset); + +out: + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +/* + * Return: TEST_OK, TEST_ERR or TEST_SKIP + * we return TEST_OK only if the children return with the expected + * 'expected_status' that is specified as an argument. + */ +static int kdbus_fork_test(const char *bus, const char *name, + struct kdbus_conn **conn_db, int expected_status) +{ + pid_t pid; + int ret = 0; + int status = 0; + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT(ret == 0); + + ret = drop_privileges(65534, 65534); + ASSERT_EXIT(ret == 0); + + ret = kdbus_recv_in_threads(bus, name, conn_db); + _exit(ret == expected_status ? EXIT_SUCCESS : EXIT_FAILURE); + } + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN(ret >= 0); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +/* Return EXIT_SUCCESS, EXIT_FAILURE or negative errno */ +static int __kdbus_clone_userns_test(const char *bus, + const char *name, + struct kdbus_conn **conn_db, + int expected_status) +{ + int efd; + pid_t pid; + int ret = 0; + unsigned int uid = 65534; + int status; + + ret = drop_privileges(uid, uid); + ASSERT_RETURN_VAL(ret == 0, ret); + + /* + * Since we just dropped privileges, the dumpable flag was just + * cleared which makes the /proc/$clone_child/uid_map to be + * owned by root, hence any userns uid mapping will fail with + * -EPERM since the mapping will be done by uid 65534. + * + * To avoid this set the dumpable flag again which makes procfs + * update the /proc/$clone_child/ inodes owner to 65534. + * + * Using this we will be able write to /proc/$clone_child/uid_map + * as uid 65534 and map the uid 65534 to 0 inside the user + * namespace. + */ + ret = prctl(PR_SET_DUMPABLE, SUID_DUMP_USER); + ASSERT_RETURN_VAL(ret == 0, ret); + + /* sync parent/child */ + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + pid = syscall(__NR_clone, SIGCHLD|CLONE_NEWUSER, NULL); + if (pid < 0) { + ret = -errno; + kdbus_printf("error clone: %d (%m)\n", ret); + /* + * Normal user not allowed to create userns, + * so nothing to worry about ? + */ + if (ret == -EPERM) { + kdbus_printf("-- CLONE_NEWUSER TEST Failed for uid: %u\n" + "-- Make sure that your kernel do not allow " + "CLONE_NEWUSER for unprivileged users\n" + "-- Upstream Commit: " + "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=5eaf563e\n", + uid); + ret = 0; + } + + return ret; + } + + if (pid == 0) { + struct kdbus_conn *conn_src; + eventfd_t event_status = 0; + + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + ASSERT_EXIT(ret == 0); + + ret = eventfd_read(efd, &event_status); + ASSERT_EXIT(ret >= 0 && event_status == 1); + + /* ping connection from the new user namespace */ + conn_src = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(conn_src); + + ret = kdbus_add_match_empty(conn_src); + ASSERT_EXIT(ret == 0); + + ret = kdbus_msg_send(conn_src, name, 0xabcd1234, + 0, 0, 0, KDBUS_DST_ID_NAME, 0, NULL); + kdbus_conn_free(conn_src); + + _exit(ret == expected_status ? EXIT_SUCCESS : EXIT_FAILURE); + } + + ret = userns_map_uid_gid(pid, "0 65534 1", "0 65534 1"); + ASSERT_RETURN_VAL(ret == 0, ret); + + /* Tell child we are ready */ + ret = eventfd_write(efd, 1); + ASSERT_RETURN_VAL(ret == 0, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + close(efd); + + return status == EXIT_SUCCESS ? TEST_OK : TEST_ERR; +} + +static int kdbus_clone_userns_test(const char *bus, + const char *name, + struct kdbus_conn **conn_db, + int expected_status) +{ + pid_t pid; + int ret = 0; + int status; + + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, -errno); + + if (pid == 0) { + ret = prctl(PR_SET_PDEATHSIG, SIGKILL); + if (ret < 0) + _exit(EXIT_FAILURE); + + ret = __kdbus_clone_userns_test(bus, name, conn_db, + expected_status); + _exit(ret); + } + + /* + * Receive in the original (root privileged) user namespace, + * must fail with -ETIMEDOUT. + */ + ret = kdbus_msg_recv_poll(conn_db[0], 100, NULL, NULL); + ASSERT_RETURN_VAL(ret == -ETIMEDOUT, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +int kdbus_test_policy_ns(struct kdbus_test_env *env) +{ + int i; + int ret; + struct kdbus_conn *activator = NULL; + struct kdbus_conn *policy_holder = NULL; + char *bus = env->buspath; + + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + /* no enough privileges, SKIP test */ + if (!ret) + return TEST_SKIP; + + /* we require user-namespaces */ + if (access("/proc/self/uid_map", F_OK) != 0) + return TEST_SKIP; + + /* uids/gids must be mapped */ + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + conn_db = calloc(MAX_CONN, sizeof(struct kdbus_conn *)); + ASSERT_RETURN(conn_db); + + memset(conn_db, 0, MAX_CONN * sizeof(struct kdbus_conn *)); + + conn_db[0] = kdbus_hello(bus, 0, NULL, 0); + ASSERT_RETURN(conn_db[0]); + + ret = kdbus_add_match_empty(conn_db[0]); + ASSERT_RETURN(ret == 0); + + ret = kdbus_fork_test_by_id(bus, conn_db, -EPERM, -EPERM); + ASSERT_EXIT(ret == 0); + + ret = kdbus_register_policy_holder(bus, POLICY_NAME, + &policy_holder); + ASSERT_RETURN(ret == 0); + + /* Try to register the same name with an activator */ + ret = kdbus_register_same_activator(bus, POLICY_NAME, + &activator); + ASSERT_RETURN(ret == 0); + + /* Acquire POLICY_NAME */ + ret = kdbus_name_acquire(conn_db[0], POLICY_NAME, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_normal_test(bus, POLICY_NAME, conn_db); + ASSERT_RETURN(ret == 0); + + ret = kdbus_list(conn_db[0], KDBUS_LIST_NAMES | + KDBUS_LIST_UNIQUE | + KDBUS_LIST_ACTIVATORS | + KDBUS_LIST_QUEUED); + ASSERT_RETURN(ret == 0); + + ret = kdbus_fork_test(bus, POLICY_NAME, conn_db, EXIT_SUCCESS); + ASSERT_RETURN(ret == 0); + + /* + * children connections are able to talk to conn_db[0] since + * current POLICY_NAME TALK type is KDBUS_POLICY_ACCESS_WORLD, + * so expect EXIT_SUCCESS when sending from child. However, + * since the child's connection does not own any well-known + * name, The parent connection conn_db[0] should fail with + * -EPERM but since it is a privileged bus user the TALK is + * allowed. + */ + ret = kdbus_fork_test_by_id(bus, conn_db, + EXIT_SUCCESS, EXIT_SUCCESS); + ASSERT_EXIT(ret == 0); + + /* + * Connections that can talk are perhaps being destroyed now. + * Restrict the policy and purge cache entries where the + * conn_db[0] is the destination. + * + * Now only connections with uid == 0 are allowed to talk. + */ + ret = kdbus_set_policy_talk(policy_holder, POLICY_NAME, + geteuid(), KDBUS_POLICY_ACCESS_USER); + ASSERT_RETURN(ret == 0); + + /* + * Testing connections (FORK+DROP) again: + * After setting the policy re-check connections + * we expect the children to fail with -EPERM + */ + ret = kdbus_fork_test(bus, POLICY_NAME, conn_db, -EPERM); + ASSERT_RETURN(ret == 0); + + /* + * Now expect that both parent and child to fail. + * + * Child should fail with -EPERM since we just restricted + * the POLICY_NAME TALK to uid 0 and its uid is 65534. + * + * Since the parent's connection will timeout when receiving + * from the child, we never continue. FWIW just put -EPERM. + */ + ret = kdbus_fork_test_by_id(bus, conn_db, -EPERM, -EPERM); + ASSERT_EXIT(ret == 0); + + /* Check if the name can be reached in a new userns */ + ret = kdbus_clone_userns_test(bus, POLICY_NAME, conn_db, -EPERM); + ASSERT_RETURN(ret == 0); + + for (i = 0; i < MAX_CONN; i++) + kdbus_conn_free(conn_db[i]); + + kdbus_conn_free(activator); + kdbus_conn_free(policy_holder); + + free(conn_db); + + return ret; +} diff --git a/tools/testing/selftests/kdbus/test-policy-priv.c b/tools/testing/selftests/kdbus/test-policy-priv.c new file mode 100644 index 000000000000..14a09c6b98d0 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-policy-priv.c @@ -0,0 +1,1275 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <time.h> +#include <sys/capability.h> +#include <sys/eventfd.h> +#include <sys/wait.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static int test_policy_priv_by_id(const char *bus, + struct kdbus_conn *conn_dst, + bool drop_second_user, + int parent_status, + int child_status) +{ + int ret = 0; + uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef; + + ASSERT_RETURN(conn_dst); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, bus, ({ + ret = kdbus_msg_send(unpriv, NULL, + expected_cookie, 0, 0, 0, + conn_dst->id, 0, NULL); + ASSERT_EXIT(ret == child_status); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_dst, 300, NULL, NULL); + ASSERT_RETURN(ret == parent_status); + + return 0; +} + +static int test_policy_priv_by_broadcast(const char *bus, + struct kdbus_conn *conn_dst, + int drop_second_user, + int parent_status, + int child_status) +{ + int efd; + int ret = 0; + eventfd_t event_status = 0; + struct kdbus_msg *msg = NULL; + uid_t second_uid = UNPRIV_UID; + gid_t second_gid = UNPRIV_GID; + struct kdbus_conn *child_2 = conn_dst; + uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef; + + /* Drop to another unprivileged user other than UNPRIV_UID */ + if (drop_second_user == DROP_OTHER_UNPRIV) { + second_uid = UNPRIV_UID - 1; + second_gid = UNPRIV_GID - 1; + } + + /* child will signal parent to send broadcast */ + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + struct kdbus_conn *child; + + child = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(child); + + ret = kdbus_add_match_empty(child); + ASSERT_EXIT(ret == 0); + + /* signal parent */ + ret = eventfd_write(efd, 1); + ASSERT_EXIT(ret == 0); + + /* Use a little bit high time */ + ret = kdbus_msg_recv_poll(child, 500, &msg, NULL); + ASSERT_EXIT(ret == child_status); + + /* + * If we expect the child to get the broadcast + * message, then check the received cookie. + */ + if (ret == 0) { + ASSERT_EXIT(expected_cookie == msg->cookie); + } + + /* Use expected_cookie since 'msg' might be NULL */ + ret = kdbus_msg_send(child, NULL, expected_cookie + 1, + 0, 0, 0, KDBUS_DST_ID_BROADCAST, + 0, NULL); + ASSERT_EXIT(ret == 0); + + kdbus_msg_free(msg); + kdbus_conn_free(child); + }), + ({ + if (drop_second_user == DO_NOT_DROP) { + ASSERT_RETURN(child_2); + + ret = eventfd_read(efd, &event_status); + ASSERT_RETURN(ret >= 0 && event_status == 1); + + ret = kdbus_msg_send(child_2, NULL, + expected_cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, + 0, NULL); + ASSERT_RETURN(ret == 0); + + /* Use a little bit high time */ + ret = kdbus_msg_recv_poll(child_2, 1000, + &msg, NULL); + ASSERT_RETURN(ret == parent_status); + + /* + * Check returned cookie in case we expect + * success. + */ + if (ret == 0) { + ASSERT_RETURN(msg->cookie == + expected_cookie + 1); + } + + kdbus_msg_free(msg); + } else { + /* + * Two unprivileged users will try to + * communicate using broadcast. + */ + ret = RUN_UNPRIVILEGED(second_uid, second_gid, ({ + child_2 = kdbus_hello(bus, 0, NULL, 0); + ASSERT_EXIT(child_2); + + ret = kdbus_add_match_empty(child_2); + ASSERT_EXIT(ret == 0); + + ret = eventfd_read(efd, &event_status); + ASSERT_EXIT(ret >= 0 && event_status == 1); + + ret = kdbus_msg_send(child_2, NULL, + expected_cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, + 0, NULL); + ASSERT_EXIT(ret == 0); + + /* Use a little bit high time */ + ret = kdbus_msg_recv_poll(child_2, 1000, + &msg, NULL); + ASSERT_EXIT(ret == parent_status); + + /* + * Check returned cookie in case we expect + * success. + */ + if (ret == 0) { + ASSERT_EXIT(msg->cookie == + expected_cookie + 1); + } + + kdbus_msg_free(msg); + kdbus_conn_free(child_2); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + } + })); + ASSERT_RETURN(ret == 0); + + close(efd); + + return ret; +} + +static void nosig(int sig) +{ +} + +static int test_priv_before_policy_upload(struct kdbus_test_env *env) +{ + int ret = 0; + struct kdbus_conn *conn; + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* + * Make sure unprivileged bus user cannot acquire names + * before registring any policy holder. + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret == 0); + + /* + * Make sure unprivileged bus users cannot talk by default + * to privileged ones, unless a policy holder that allows + * this was uploaded. + */ + + ret = test_policy_priv_by_id(env->buspath, conn, false, + -ETIMEDOUT, -EPERM); + ASSERT_RETURN(ret == 0); + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(conn); + ASSERT_RETURN(ret == 0); + + /* + * First make sure that BROADCAST with msg flag + * KDBUS_MSG_EXPECT_REPLY will fail with -ENOTUNIQ + */ + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, NULL, 0xdeadbeef, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, + KDBUS_DST_ID_BROADCAST, + 0, NULL); + ASSERT_EXIT(ret == -ENOTUNIQ); + })); + ASSERT_RETURN(ret == 0); + + /* + * Test broadcast with a privileged connection. + * + * The first unprivileged receiver should not get the + * broadcast message sent by the privileged connection, + * since there is no a TALK policy that allows the + * unprivileged to TALK to the privileged connection. It + * will fail with -ETIMEDOUT + * + * Then second case: + * The privileged connection should get the broadcast + * message from the unprivileged one. Since the receiver is + * a privileged bus user and it has default TALK access to + * all connections it will receive those. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, conn, + DO_NOT_DROP, + 0, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + + /* + * Test broadcast with two unprivileged connections running + * under the same user. + * + * Both connections should succeed. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_SAME_UNPRIV, 0, 0); + ASSERT_RETURN(ret == 0); + + /* + * Test broadcast with two unprivileged connections running + * under different users. + * + * Both connections will fail with -ETIMEDOUT. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_OTHER_UNPRIV, + -ETIMEDOUT, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(conn); + + return ret; +} + +static int test_broadcast_after_policy_upload(struct kdbus_test_env *env) +{ + int ret; + int efd; + eventfd_t event_status = 0; + struct kdbus_msg *msg = NULL; + struct kdbus_conn *owner_a, *owner_b; + struct kdbus_conn *holder_a, *holder_b; + struct kdbus_policy_access access = {}; + uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef; + + owner_a = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner_a); + + ret = kdbus_name_acquire(owner_a, "com.example.broadcastA", NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users cannot talk by default + * to privileged ones, unless a policy holder that allows + * this was uploaded. + */ + + ++expected_cookie; + ret = test_policy_priv_by_id(env->buspath, owner_a, false, + -ETIMEDOUT, -EPERM); + ASSERT_RETURN(ret == 0); + + /* + * Make sure that privileged won't receive broadcasts unless + * it installs a match. It will fail with -ETIMEDOUT + * + * At same time check that the unprivileged connection will + * not receive the broadcast message from the privileged one + * since the privileged one owns a name with a restricted + * policy TALK (actually the TALK policy is still not + * registered so we fail by default), thus the unprivileged + * receiver is not able to TALK to that name. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, owner_a, + DO_NOT_DROP, + -ETIMEDOUT, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(owner_a); + ASSERT_RETURN(ret == 0); + + /* + * Redo the previous test. The privileged conn owner_a is + * able to TALK to any connection so it will receive the + * broadcast message now. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, owner_a, + DO_NOT_DROP, + 0, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + /* + * Test that broadcast between two unprivileged users running + * under the same user still succeed. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_SAME_UNPRIV, 0, 0); + ASSERT_RETURN(ret == 0); + + /* + * Test broadcast with two unprivileged connections running + * under different users. + * + * Both connections will fail with -ETIMEDOUT. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_OTHER_UNPRIV, + -ETIMEDOUT, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = geteuid(), + .access = KDBUS_POLICY_OWN, + }; + + holder_a = kdbus_hello_registrar(env->buspath, + "com.example.broadcastA", + &access, 1, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder_a); + + holder_b = kdbus_hello_registrar(env->buspath, + "com.example.broadcastB", + &access, 1, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(holder_b); + + /* Free connections and their received messages and restart */ + kdbus_conn_free(owner_a); + + owner_a = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner_a); + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(owner_a); + ASSERT_RETURN(ret == 0); + + ret = kdbus_name_acquire(owner_a, "com.example.broadcastA", NULL); + ASSERT_EXIT(ret >= 0); + + owner_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner_b); + + ret = kdbus_name_acquire(owner_b, "com.example.broadcastB", NULL); + ASSERT_EXIT(ret >= 0); + + /* Activate matching for a privileged connection */ + ret = kdbus_add_match_empty(owner_b); + ASSERT_RETURN(ret == 0); + + /* + * Test that even if "com.example.broadcastA" and + * "com.example.broadcastB" do have a TALK access by default + * they are able to signal each other using broadcast due to + * the fact they are privileged connections, they receive + * all broadcasts if the match allows it. + */ + + ++expected_cookie; + ret = kdbus_msg_send(owner_a, NULL, expected_cookie, 0, + 0, 0, KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv_poll(owner_b, 100, &msg, NULL); + ASSERT_RETURN(ret == 0); + ASSERT_RETURN(msg->cookie == expected_cookie); + + /* Check src ID */ + ASSERT_RETURN(msg->src_id == owner_a->id); + + kdbus_msg_free(msg); + + /* Release name "com.example.broadcastB" */ + + ret = kdbus_name_release(owner_b, "com.example.broadcastB"); + ASSERT_EXIT(ret >= 0); + + /* KDBUS_POLICY_OWN for unprivileged connections */ + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = geteuid(), + .access = KDBUS_POLICY_OWN, + }; + + /* Update the policy so unprivileged will own the name */ + + ret = kdbus_conn_update_policy(holder_b, + "com.example.broadcastB", + &access, 1); + ASSERT_RETURN(ret == 0); + + /* + * Send broadcasts from an unprivileged connection that + * owns a name "com.example.broadcastB". + * + * We'll have four destinations here: + * + * 1) destination owner_a: privileged connection that owns + * "com.example.broadcastA". It will receive the broadcast + * since it is a privileged has default TALK access to all + * connections, and it is subscribed to the match. + * Will succeed. + * + * owner_b: privileged connection (running under a different + * uid) that do not own names, but with an empty broadcast + * match, so it will receive broadcasts since it has default + * TALK access to all connection. + * + * unpriv_a: unpriv connection that do not own any name. + * It will receive the broadcast since it is running under + * the same user of the one broadcasting and did install + * matches. It should get the message. + * + * unpriv_b: unpriv connection is not interested in broadcast + * messages, so it did not install broadcast matches. Should + * fail with -ETIMEDOUT + */ + + ++expected_cookie; + efd = eventfd(0, EFD_CLOEXEC); + ASSERT_RETURN_VAL(efd >= 0, efd); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_UID, ({ + struct kdbus_conn *unpriv_owner; + struct kdbus_conn *unpriv_a, *unpriv_b; + + unpriv_owner = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_EXIT(unpriv_owner); + + unpriv_a = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_EXIT(unpriv_a); + + unpriv_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_EXIT(unpriv_b); + + ret = kdbus_name_acquire(unpriv_owner, + "com.example.broadcastB", + NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_add_match_empty(unpriv_a); + ASSERT_EXIT(ret == 0); + + /* Signal that we are doing broadcasts */ + ret = eventfd_write(efd, 1); + ASSERT_EXIT(ret == 0); + + /* + * Do broadcast from a connection that owns the + * names "com.example.broadcastB". + */ + ret = kdbus_msg_send(unpriv_owner, NULL, + expected_cookie, + 0, 0, 0, + KDBUS_DST_ID_BROADCAST, + 0, NULL); + ASSERT_EXIT(ret == 0); + + /* + * Unprivileged connection running under the same + * user. It should succeed. + */ + ret = kdbus_msg_recv_poll(unpriv_a, 300, &msg, NULL); + ASSERT_EXIT(ret == 0 && msg->cookie == expected_cookie); + + /* + * Did not install matches, not interested in + * broadcasts + */ + ret = kdbus_msg_recv_poll(unpriv_b, 300, NULL, NULL); + ASSERT_EXIT(ret == -ETIMEDOUT); + }), + ({ + ret = eventfd_read(efd, &event_status); + ASSERT_RETURN(ret >= 0 && event_status == 1); + + /* + * owner_a must fail with -ETIMEDOUT, since it owns + * name "com.example.broadcastA" and its TALK + * access is restriced. + */ + ret = kdbus_msg_recv_poll(owner_a, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + /* + * owner_b got the broadcast from an unprivileged + * connection. + */ + ret = kdbus_msg_recv_poll(owner_b, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + })); + ASSERT_RETURN(ret == 0); + + close(efd); + + /* + * Test broadcast with two unprivileged connections running + * under different users. + * + * Both connections will fail with -ETIMEDOUT. + */ + + ret = test_policy_priv_by_broadcast(env->buspath, NULL, + DROP_OTHER_UNPRIV, + -ETIMEDOUT, -ETIMEDOUT); + ASSERT_RETURN(ret == 0); + + /* Drop received broadcasts by privileged */ + ret = kdbus_msg_recv_poll(owner_a, 100, NULL, NULL); + ret = kdbus_msg_recv_poll(owner_a, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(owner_a, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + ret = kdbus_msg_recv_poll(owner_b, 100, NULL, NULL); + ret = kdbus_msg_recv_poll(owner_b, 100, NULL, NULL); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_recv(owner_b, NULL, NULL); + ASSERT_RETURN(ret == -EAGAIN); + + /* + * Perform last tests, allow others to talk to name + * "com.example.broadcastA". So now receiving broadcasts + * from it should succeed since the TALK policy allow it. + */ + + /* KDBUS_POLICY_OWN for unprivileged connections */ + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = geteuid(), + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(holder_a, + "com.example.broadcastA", + &access, 1); + ASSERT_RETURN(ret == 0); + + /* + * Unprivileged is able to TALK to "com.example.broadcastA" + * now so it will receive its broadcasts + */ + ret = test_policy_priv_by_broadcast(env->buspath, owner_a, + DO_NOT_DROP, 0, 0); + ASSERT_RETURN(ret == 0); + + ++expected_cookie; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.broadcastB", + NULL); + ASSERT_EXIT(ret >= 0); + ret = kdbus_msg_send(unpriv, NULL, expected_cookie, + 0, 0, 0, KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_EXIT(ret == 0); + })); + ASSERT_RETURN(ret == 0); + + /* owner_a is privileged it will get the broadcast now. */ + ret = kdbus_msg_recv_poll(owner_a, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + /* + * owner_a released name "com.example.broadcastA". It should + * receive broadcasts since it is still privileged and has + * the right match. + * + * Unprivileged connection will own a name and will try to + * signal to the privileged connection. + */ + + ret = kdbus_name_release(owner_a, "com.example.broadcastA"); + ASSERT_EXIT(ret >= 0); + + ++expected_cookie; + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.broadcastB", + NULL); + ASSERT_EXIT(ret >= 0); + ret = kdbus_msg_send(unpriv, NULL, expected_cookie, + 0, 0, 0, KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_EXIT(ret == 0); + })); + ASSERT_RETURN(ret == 0); + + /* owner_a will get the broadcast now. */ + ret = kdbus_msg_recv_poll(owner_a, 300, &msg, NULL); + ASSERT_RETURN(ret == 0); + + /* confirm the received cookie */ + ASSERT_RETURN(msg->cookie == expected_cookie); + + kdbus_msg_free(msg); + + kdbus_conn_free(owner_a); + kdbus_conn_free(owner_b); + kdbus_conn_free(holder_a); + kdbus_conn_free(holder_b); + + return 0; +} + +static int test_policy_priv(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_a, *conn_b, *conn, *owner; + struct kdbus_policy_access access, *acc; + sigset_t sset; + size_t num; + int ret; + + /* + * Make sure we have CAP_SETUID/SETGID so we can drop privileges + */ + + ret = test_is_capable(CAP_SETUID, CAP_SETGID, -1); + ASSERT_RETURN(ret >= 0); + + if (!ret) + return TEST_SKIP; + + /* make sure that uids and gids are mapped */ + if (!all_uids_gids_are_mapped()) + return TEST_SKIP; + + /* + * Setup: + * conn_a: policy holder for com.example.a + * conn_b: name holder of com.example.b + */ + + signal(SIGUSR1, nosig); + sigemptyset(&sset); + sigaddset(&sset, SIGUSR1); + sigprocmask(SIG_BLOCK, &sset, NULL); + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + /* + * Before registering any policy holder, make sure that the + * bus is secure by default. This test is necessary, it catches + * several cases where old D-Bus was vulnerable. + */ + + ret = test_priv_before_policy_upload(env); + ASSERT_RETURN(ret == 0); + + /* + * Make sure unprivileged are not able to register policy + * holders + */ + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + struct kdbus_conn *holder; + + holder = kdbus_hello_registrar(env->buspath, + "com.example.a", NULL, 0, + KDBUS_HELLO_POLICY_HOLDER); + ASSERT_EXIT(holder == NULL && errno == EPERM); + }), + ({ 0; })); + ASSERT_RETURN(ret == 0); + + + /* Register policy holder */ + + conn_a = kdbus_hello_registrar(env->buspath, "com.example.a", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a); + + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_b); + + ret = kdbus_name_acquire(conn_b, "com.example.b", NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure bus-owners can always acquire names. + */ + ret = kdbus_name_acquire(conn, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + + kdbus_conn_free(conn); + + /* + * Make sure unprivileged users cannot acquire names with default + * policy assigned. + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if we make them + * world-accessible. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_OWN, + }; + + /* + * Make sure unprivileged/normal connections are not able + * to update policies + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_conn_update_policy(unpriv, "com.example.a", + &access, 1); + ASSERT_EXIT(ret == -EOPNOTSUPP); + })); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if we make them + * gid-accessible. But only if the gid matches. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = UNPRIV_GID, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = 1, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if we make them + * uid-accessible. But only if the uid matches. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = 1, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users cannot acquire names if no owner-policy + * matches, even if SEE/TALK policies match. + */ + + num = 4; + acc = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = UNPRIV_GID, + .access = KDBUS_POLICY_SEE, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_SEE, + }, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", acc, num); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret < 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged users can acquire names if the only matching + * policy is somewhere in the middle. + */ + + num = 5; + acc = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 1, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 2, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 3, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 4, + .access = KDBUS_POLICY_OWN, + }, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", acc, num); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_name_acquire(unpriv, "com.example.a", NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Clear policies + */ + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", NULL, 0); + ASSERT_RETURN(ret == 0); + + /* + * Make sure privileged bus users can _always_ talk to others. + */ + + conn = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn); + + ret = kdbus_msg_send(conn, "com.example.b", 0xdeadbeef, 0, 0, 0, 0, + 0, NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 300, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + kdbus_conn_free(conn); + + /* + * Make sure unprivileged bus users cannot talk by default. + */ + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to equals, even without + * policy. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.c", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + struct kdbus_conn *owner; + + owner = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner); + + ret = kdbus_name_acquire(owner, "com.example.c", NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret >= 0); + ret = kdbus_msg_recv_poll(owner, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + kdbus_conn_free(owner); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable UID policy is set. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable GID policy is set. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_GROUP, + .id = UNPRIV_GID, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable WORLD policy is set. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure unprivileged bus users cannot talk to privileged users if + * no suitable policy is set. + */ + + num = 5; + acc = (struct kdbus_policy_access[]){ + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 0, + .access = KDBUS_POLICY_OWN, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 1, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = UNPRIV_UID, + .access = KDBUS_POLICY_SEE, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 3, + .access = KDBUS_POLICY_TALK, + }, + { + .type = KDBUS_POLICY_ACCESS_USER, + .id = 4, + .access = KDBUS_POLICY_TALK, + }, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", acc, num); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure unprivileged bus users can talk to privileged users if a + * suitable OWN privilege overwrites TALK. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_OWN, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret >= 0); + })); + ASSERT_RETURN(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* + * Make sure the TALK cache is reset correctly when policies are + * updated. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_TALK, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({ + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_recv_poll(conn_b, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_conn_update_policy(conn_a, "com.example.b", + NULL, 0); + ASSERT_RETURN(ret == 0); + + ret = kdbus_msg_send(unpriv, "com.example.b", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret == -EPERM); + })); + ASSERT_RETURN(ret >= 0); + + /* + * Make sure the TALK cache is reset correctly when policy holders + * disconnect. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_WORLD, + .id = 0, + .access = KDBUS_POLICY_OWN, + }; + + conn = kdbus_hello_registrar(env->buspath, "com.example.c", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn); + + ret = kdbus_conn_update_policy(conn, "com.example.c", &access, 1); + ASSERT_RETURN(ret == 0); + + owner = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(owner); + + ret = kdbus_name_acquire(owner, "com.example.c", NULL); + ASSERT_RETURN(ret >= 0); + + ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({ + struct kdbus_conn *unpriv; + + /* wait for parent to be finished */ + sigemptyset(&sset); + ret = sigsuspend(&sset); + ASSERT_RETURN(ret == -1 && errno == EINTR); + + unpriv = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(unpriv); + + ret = kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret >= 0); + + ret = kdbus_msg_recv_poll(owner, 100, NULL, NULL); + ASSERT_EXIT(ret >= 0); + + /* free policy holder */ + kdbus_conn_free(conn); + + ret = kdbus_msg_send(unpriv, "com.example.c", 0xdeadbeef, 0, 0, + 0, 0, 0, NULL); + ASSERT_EXIT(ret == -EPERM); + + kdbus_conn_free(unpriv); + }), ({ + /* make sure policy holder is only valid in child */ + kdbus_conn_free(conn); + kill(pid, SIGUSR1); + })); + ASSERT_RETURN(ret >= 0); + + + /* + * The following tests are necessary. + */ + + ret = test_broadcast_after_policy_upload(env); + ASSERT_RETURN(ret == 0); + + kdbus_conn_free(owner); + + /* + * cleanup resources + */ + + kdbus_conn_free(conn_b); + kdbus_conn_free(conn_a); + + return TEST_OK; +} + +int kdbus_test_policy_priv(struct kdbus_test_env *env) +{ + pid_t pid; + int ret; + + /* make sure to exit() if a child returns from fork() */ + pid = getpid(); + ret = test_policy_priv(env); + if (pid != getpid()) + exit(1); + + return ret; +} diff --git a/tools/testing/selftests/kdbus/test-policy.c b/tools/testing/selftests/kdbus/test-policy.c new file mode 100644 index 000000000000..96d20d5e9172 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-policy.c @@ -0,0 +1,80 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int kdbus_test_policy(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_a, *conn_b; + struct kdbus_policy_access access; + int ret; + + /* Invalid name */ + conn_a = kdbus_hello_registrar(env->buspath, ".example.a", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a == NULL); + + conn_a = kdbus_hello_registrar(env->buspath, "example", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a == NULL); + + conn_a = kdbus_hello_registrar(env->buspath, "com.example.a", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_a); + + conn_b = kdbus_hello_registrar(env->buspath, "com.example.b", + NULL, 0, KDBUS_HELLO_POLICY_HOLDER); + ASSERT_RETURN(conn_b); + + /* + * Verify there cannot be any duplicate entries, except for specific vs. + * wildcard entries. + */ + + access = (struct kdbus_policy_access){ + .type = KDBUS_POLICY_ACCESS_USER, + .id = geteuid(), + .access = KDBUS_POLICY_SEE, + }; + + ret = kdbus_conn_update_policy(conn_a, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_b, "com.example.a", &access, 1); + ASSERT_RETURN(ret == -EEXIST); + + ret = kdbus_conn_update_policy(conn_b, "com.example.a.*", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_a, "com.example.a.*", &access, 1); + ASSERT_RETURN(ret == -EEXIST); + + ret = kdbus_conn_update_policy(conn_a, "com.example.*", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_b, "com.example.a", &access, 1); + ASSERT_RETURN(ret == 0); + + ret = kdbus_conn_update_policy(conn_b, "com.example.*", &access, 1); + ASSERT_RETURN(ret == -EEXIST); + + /* Invalid name */ + ret = kdbus_conn_update_policy(conn_b, ".example.*", &access, 1); + ASSERT_RETURN(ret == -EINVAL); + + ret = kdbus_conn_update_policy(conn_b, "example", &access, 1); + ASSERT_RETURN(ret == -EINVAL); + + kdbus_conn_free(conn_b); + kdbus_conn_free(conn_a); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-send.c b/tools/testing/selftests/kdbus/test-send.c new file mode 100644 index 000000000000..9bd8c8f74500 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-send.c @@ -0,0 +1,84 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +/* Use in conjunction with test-kdbus-daemon */ + +#include <unistd.h> +#include <stddef.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <sys/ioctl.h> + +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int get_file(const char *fname, int flags, const char *content) +{ + FILE *f; + + if (access(fname, F_OK) < 0) { + f = fopen(fname, "w"); + if (!f) + return -1; + fprintf(f, "%s\n", content); + fclose(f); + } + + return open(fname, flags); +} + +int kdbus_test_send(struct kdbus_test_env *env) +{ + int ret; + int serial = 1; + int fds[3]; + size_t i; + + if (!env->conn) + return EXIT_FAILURE; + + fds[0] = get_file("/tmp/kdbus-test-send.rd", O_RDONLY, "foo"); + fds[1] = get_file("/tmp/kdbus-test-send.wr", O_WRONLY, "bar"); + fds[2] = get_file("/tmp/kdbus-test-send.rdwr", O_RDWR, "baz"); + + for (i = 0; i < ELEMENTSOF(fds); i++) { + if (fds[i] < 0) { + fprintf(stderr, "Unable to open data/fileN file(s)\n"); + return EXIT_FAILURE; + } + } + + ret = kdbus_msg_send(env->conn, "com.example.kdbus-test", serial++, + 0, 0, 0, 0, 0, NULL); + if (ret < 0) + fprintf(stderr, "error sending simple message: %d (%m)\n", + ret); + + ret = kdbus_msg_send(env->conn, "com.example.kdbus-test", serial++, + 0, 0, 0, 0, 1, fds); + if (ret < 0) + fprintf(stderr, "error sending message with 1 fd: %d (%m)\n", + ret); + + ret = kdbus_msg_send(env->conn, "com.example.kdbus-test", serial++, + 0, 0, 0, 0, 2, fds); + if (ret < 0) + fprintf(stderr, "error sending message with 2 fds: %d (%m)\n", + ret); + + ret = kdbus_msg_send(env->conn, "com.example.kdbus-test", serial++, + 0, 0, 0, 0, 3, fds); + if (ret < 0) + fprintf(stderr, "error sending message with 3 fds: %d (%m)\n", + ret); + + for (i = 0; i < ELEMENTSOF(fds); i++) + close(fds[i]); + + return EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/kdbus/test-sync.c b/tools/testing/selftests/kdbus/test-sync.c new file mode 100644 index 000000000000..77c3c4142246 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-sync.c @@ -0,0 +1,369 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <pthread.h> +#include <stdbool.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/eventfd.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +static struct kdbus_conn *conn_a, *conn_b; +static unsigned int cookie = 0xdeadbeef; + +static void nop_handler(int sig) {} + +static int interrupt_sync(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + pid_t pid; + int ret, status; + struct kdbus_msg *msg = NULL; + struct sigaction sa = { + .sa_handler = nop_handler, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, + }; + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = sigaction(SIGINT, &sa, NULL); + ASSERT_EXIT(ret == 0); + + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, conn_src->id, -1); + ASSERT_EXIT(ret == -ETIMEDOUT); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + ret = kill(pid, SIGINT); + ASSERT_RETURN_VAL(ret == 0, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (WIFSIGNALED(status)) + return TEST_ERR; + + ret = kdbus_msg_recv_poll(conn_src, 100, NULL, NULL); + ASSERT_RETURN(ret == -ETIMEDOUT); + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int close_epipe_sync(const char *bus) +{ + pid_t pid; + int ret, status; + struct kdbus_conn *conn_src; + struct kdbus_conn *conn_dst; + struct kdbus_msg *msg = NULL; + + conn_src = kdbus_hello(bus, 0, NULL, 0); + ASSERT_RETURN(conn_src); + + ret = kdbus_add_match_empty(conn_src); + ASSERT_RETURN(ret == 0); + + conn_dst = kdbus_hello(bus, 0, NULL, 0); + ASSERT_RETURN(conn_dst); + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + uint64_t dst_id; + + /* close our reference */ + dst_id = conn_dst->id; + kdbus_conn_free(conn_dst); + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_EXIT(ret == 0 && msg->cookie == cookie); + ASSERT_EXIT(msg->src_id == dst_id); + + cookie++; + ret = kdbus_msg_send_sync(conn_src, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, dst_id, -1); + ASSERT_EXIT(ret == -EPIPE); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_send(conn_dst, NULL, cookie, 0, 0, 0, + KDBUS_DST_ID_BROADCAST, 0, NULL); + ASSERT_RETURN(ret == 0); + + cookie++; + ret = kdbus_msg_recv_poll(conn_dst, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + /* destroy connection */ + kdbus_conn_free(conn_dst); + kdbus_conn_free(conn_src); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (!WIFEXITED(status)) + return TEST_ERR; + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int cancel_fd_sync(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + pid_t pid; + int cancel_fd; + int ret, status; + uint64_t counter = 1; + struct kdbus_msg *msg = NULL; + + cancel_fd = eventfd(0, 0); + ASSERT_RETURN_VAL(cancel_fd >= 0, cancel_fd); + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, conn_src->id, + cancel_fd); + ASSERT_EXIT(ret == -ECANCELED); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN(ret == 0 && msg->cookie == cookie); + + kdbus_msg_free(msg); + + ret = write(cancel_fd, &counter, sizeof(counter)); + ASSERT_RETURN(ret == sizeof(counter)); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (WIFSIGNALED(status)) + return TEST_ERR; + + return (status == EXIT_SUCCESS) ? TEST_OK : TEST_ERR; +} + +static int no_cancel_sync(struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + pid_t pid; + int cancel_fd; + int ret, status; + struct kdbus_msg *msg = NULL; + + /* pass eventfd, but never signal it so it shouldn't have any effect */ + + cancel_fd = eventfd(0, 0); + ASSERT_RETURN_VAL(cancel_fd >= 0, cancel_fd); + + cookie++; + pid = fork(); + ASSERT_RETURN_VAL(pid >= 0, pid); + + if (pid == 0) { + ret = kdbus_msg_send_sync(conn_dst, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 100000000ULL, 0, conn_src->id, + cancel_fd); + ASSERT_EXIT(ret == 0); + + _exit(EXIT_SUCCESS); + } + + ret = kdbus_msg_recv_poll(conn_src, 100, &msg, NULL); + ASSERT_RETURN_VAL(ret == 0 && msg->cookie == cookie, -1); + + kdbus_msg_free(msg); + + ret = kdbus_msg_send_reply(conn_src, cookie, conn_dst->id); + ASSERT_RETURN_VAL(ret >= 0, ret); + + ret = waitpid(pid, &status, 0); + ASSERT_RETURN_VAL(ret >= 0, ret); + + if (WIFSIGNALED(status)) + return -1; + + return (status == EXIT_SUCCESS) ? 0 : -1; +} + +static void *run_thread_reply(void *data) +{ + int ret; + unsigned long status = TEST_OK; + + ret = kdbus_msg_recv_poll(conn_a, 3000, NULL, NULL); + if (ret < 0) + goto exit_thread; + + kdbus_printf("Thread received message, sending reply ...\n"); + + /* using an unknown cookie must fail */ + ret = kdbus_msg_send_reply(conn_a, ~cookie, conn_b->id); + if (ret != -EPERM) { + status = TEST_ERR; + goto exit_thread; + } + + ret = kdbus_msg_send_reply(conn_a, cookie, conn_b->id); + if (ret != 0) { + status = TEST_ERR; + goto exit_thread; + } + +exit_thread: + pthread_exit(NULL); + return (void *) status; +} + +int kdbus_test_sync_reply(struct kdbus_test_env *env) +{ + unsigned long status; + pthread_t thread; + int ret; + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + pthread_create(&thread, NULL, run_thread_reply, NULL); + + ret = kdbus_msg_send_sync(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_a->id, -1); + + pthread_join(thread, (void *) &status); + ASSERT_RETURN(status == 0); + ASSERT_RETURN(ret == 0); + + ret = interrupt_sync(conn_a, conn_b); + ASSERT_RETURN(ret == 0); + + ret = close_epipe_sync(env->buspath); + ASSERT_RETURN(ret == 0); + + ret = cancel_fd_sync(conn_a, conn_b); + ASSERT_RETURN(ret == 0); + + ret = no_cancel_sync(conn_a, conn_b); + ASSERT_RETURN(ret == 0); + + kdbus_printf("-- closing bus connections\n"); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} + +#define BYEBYE_ME ((void*)0L) +#define BYEBYE_THEM ((void*)1L) + +static void *run_thread_byebye(void *data) +{ + struct kdbus_cmd cmd_byebye = { .size = sizeof(cmd_byebye) }; + int ret; + + ret = kdbus_msg_recv_poll(conn_a, 3000, NULL, NULL); + if (ret == 0) { + kdbus_printf("Thread received message, invoking BYEBYE ...\n"); + kdbus_msg_recv(conn_a, NULL, NULL); + if (data == BYEBYE_ME) + kdbus_cmd_byebye(conn_b->fd, &cmd_byebye); + else if (data == BYEBYE_THEM) + kdbus_cmd_byebye(conn_a->fd, &cmd_byebye); + } + + pthread_exit(NULL); + return NULL; +} + +int kdbus_test_sync_byebye(struct kdbus_test_env *env) +{ + pthread_t thread; + int ret; + + /* + * This sends a synchronous message to a thread, which waits until it + * received the message and then invokes BYEBYE on the *ORIGINAL* + * connection. That is, on the same connection that synchronously waits + * for an reply. + * This should properly wake the connection up and cause ECONNRESET as + * the connection is disconnected now. + * + * The second time, we do the same but invoke BYEBYE on the *TARGET* + * connection. This should also wake up the synchronous sender as the + * reply cannot be sent by a disconnected target. + */ + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + pthread_create(&thread, NULL, run_thread_byebye, BYEBYE_ME); + + ret = kdbus_msg_send_sync(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_a->id, -1); + + ASSERT_RETURN(ret == -ECONNRESET); + + pthread_join(thread, NULL); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + pthread_create(&thread, NULL, run_thread_byebye, BYEBYE_THEM); + + ret = kdbus_msg_send_sync(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + 5000000000ULL, 0, conn_a->id, -1); + + ASSERT_RETURN(ret == -EPIPE); + + pthread_join(thread, NULL); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} diff --git a/tools/testing/selftests/kdbus/test-timeout.c b/tools/testing/selftests/kdbus/test-timeout.c new file mode 100644 index 000000000000..a5cdaf270809 --- /dev/null +++ b/tools/testing/selftests/kdbus/test-timeout.c @@ -0,0 +1,99 @@ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <poll.h> +#include <stdbool.h> + +#include "kdbus-api.h" +#include "kdbus-test.h" +#include "kdbus-util.h" +#include "kdbus-enum.h" + +int timeout_msg_recv(struct kdbus_conn *conn, uint64_t *expected) +{ + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *msg; + int ret; + + ret = kdbus_cmd_recv(conn->fd, &recv); + if (ret < 0) { + kdbus_printf("error receiving message: %d (%m)\n", ret); + return ret; + } + + msg = (struct kdbus_msg *)(conn->buf + recv.msg.offset); + + ASSERT_RETURN_VAL(msg->payload_type == KDBUS_PAYLOAD_KERNEL, -EINVAL); + ASSERT_RETURN_VAL(msg->src_id == KDBUS_SRC_ID_KERNEL, -EINVAL); + ASSERT_RETURN_VAL(msg->dst_id == conn->id, -EINVAL); + + *expected &= ~(1ULL << msg->cookie_reply); + kdbus_printf("Got message timeout for cookie %llu\n", + msg->cookie_reply); + + ret = kdbus_free(conn, recv.msg.offset); + if (ret < 0) + return ret; + + return 0; +} + +int kdbus_test_timeout(struct kdbus_test_env *env) +{ + struct kdbus_conn *conn_a, *conn_b; + struct pollfd fd; + int ret, i, n_msgs = 4; + uint64_t expected = 0; + uint64_t cookie = 0xdeadbeef; + + conn_a = kdbus_hello(env->buspath, 0, NULL, 0); + conn_b = kdbus_hello(env->buspath, 0, NULL, 0); + ASSERT_RETURN(conn_a && conn_b); + + fd.fd = conn_b->fd; + + /* + * send messages that expect a reply (within 100 msec), + * but never answer it. + */ + for (i = 0; i < n_msgs; i++, cookie++) { + kdbus_printf("Sending message with cookie %llu ...\n", + (unsigned long long)cookie); + ASSERT_RETURN(kdbus_msg_send(conn_b, NULL, cookie, + KDBUS_MSG_EXPECT_REPLY, + (i + 1) * 100ULL * 1000000ULL, 0, + conn_a->id, 0, NULL) == 0); + expected |= 1ULL << cookie; + } + + for (;;) { + fd.events = POLLIN | POLLPRI | POLLHUP; + fd.revents = 0; + + ret = poll(&fd, 1, (n_msgs + 1) * 100); + if (ret == 0) + kdbus_printf("--- timeout\n"); + if (ret <= 0) + break; + + if (fd.revents & POLLIN) + ASSERT_RETURN(!timeout_msg_recv(conn_b, &expected)); + + if (expected == 0) + break; + } + + ASSERT_RETURN(expected == 0); + + kdbus_conn_free(conn_a); + kdbus_conn_free(conn_b); + + return TEST_OK; +} |