From: Daniel Mack Date: Thu, 11 Sep 2014 18:58:45 +0200 Subject: [PATCH] kdbus: add code to gather metadata A connection chooses which metadata it wants to have attached to each message it receives with kdbus_cmd_hello.attach_flags. The metadata will be attached as items to the messages. All metadata refers to information about the sending task at sending time, unless otherwise stated. Also, the metadata is copied, not referenced, so even if the sending task doesn't exist anymore at the time the message is received, the information is still preserved. In traditional D-Bus, userspace tasks like polkit or journald make a live lookup in procfs and sysfs to gain information about a sending task. This is racy, of course, as in a a connection-less system like D-Bus, the originating peer can go away immediately after sending the message. As we're moving D-Bus prmitives into the kernel, we have to provide the same semantics here, and inform the receiving peer on the live credentials of the sending peer. Metadata is collected at the following times. * When a bus is created (KDBUS_CMD_MAKE), information about the calling task is collected. This data is returned by the kernel via the KDBUS_CMD_BUS_CREATOR_INFO call. * When a connection is created (KDBUS_CMD_HELLO), 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 (KDBUS_CMD_CONN_INFO). Only metadata that a connection allowed to be sent (by setting its bit in attach_flags_send) will be exported in this way. * When a message is sent (KDBUS_CMD_SEND), information about the sending task and the sending connection are 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 kdbus.connection(7)), the message will not be augmented by any information about the currently sending task. Which metadata items are actually delivered depends on the following sets and masks: (a) the system-wide kmod creds mask (module parameter 'attach_flags_mask') (b) the per-connection send creds mask, set by the connecting client (c) the per-connection receive creds mask, set by the connecting client (d) the per-bus minimal creds mask, set by the bus creator (e) the per-bus owner creds mask, set by the bus creator (f) the mask specified when querying creds of a bus peer (g) the mask specified when querying creds of a bus owner With the following rules: [1] The creds attached to messages are determined as a & b & c. [2] When connecting to a bus (KDBUS_CMD_HELLO), and ~b & d != 0, the call will fail with, -1, and errno is set to ECONNREFUSED. [3] When querying creds of a bus peer, the creds returned are a & b & f. [4] When querying creds of a bus owner, the creds returned are a & e & g. See kdbus.metadata(7) and kdbus.item(7) for more details on which metadata can currently be attached to messages. Signed-off-by: Daniel Mack Signed-off-by: David Herrmann Signed-off-by: Djalal Harouni Signed-off-by: Greg Kroah-Hartman --- ipc/kdbus/metadata.c | 1164 ++++++++++++++++++++++++++++++++++++++++++++++++++ ipc/kdbus/metadata.h | 57 +++ 2 files changed, 1221 insertions(+) create mode 100644 ipc/kdbus/metadata.c create mode 100644 ipc/kdbus/metadata.h diff --git a/ipc/kdbus/metadata.c b/ipc/kdbus/metadata.c new file mode 100644 index 000000000000..06e0a54a276a --- /dev/null +++ b/ipc/kdbus/metadata.c @@ -0,0 +1,1164 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 David Herrmann + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * @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 + * @auxgrps: Auxiliary groups + * @n_auxgrps: Number of items in @auxgrps + * @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 + * @caps: Capabilities + * @caps_namespace: User-namespace of @caps + * @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 */ + kuid_t uid, euid, suid, fsuid; + kgid_t gid, egid, sgid, fsgid; + + /* KDBUS_ITEM_PIDS */ + struct pid *pid; + struct pid *tgid; + struct pid *ppid; + + /* KDBUS_ITEM_AUXGROUPS */ + kgid_t *auxgrps; + size_t n_auxgrps; + + /* 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_CAPS */ + struct caps { + /* binary compatible to kdbus_caps */ + u32 last_cap; + struct { + u32 caps[_KERNEL_CAPABILITY_U32S]; + } set[4]; + } caps; + struct user_namespace *caps_namespace; + + /* 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; +}; + +/** + * 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); + put_user_ns(mp->caps_namespace); + put_pid(mp->ppid); + put_pid(mp->tgid); + put_pid(mp->pid); + + kfree(mp->seclabel); + kfree(mp->auxgrps); + 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_creds(struct kdbus_meta_proc *mp) +{ + mp->uid = current_uid(); + mp->euid = current_euid(); + mp->suid = current_suid(); + mp->fsuid = current_fsuid(); + + mp->gid = current_gid(); + mp->egid = current_egid(); + mp->sgid = current_sgid(); + mp->fsgid = current_fsgid(); + + mp->valid |= KDBUS_ATTACH_CREDS; +} + +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 int kdbus_meta_proc_collect_auxgroups(struct kdbus_meta_proc *mp) +{ + struct group_info *info; + size_t i; + + info = get_current_groups(); + + if (info->ngroups > 0) { + mp->auxgrps = kmalloc_array(info->ngroups, sizeof(kgid_t), + GFP_KERNEL); + if (!mp->auxgrps) { + put_group_info(info); + return -ENOMEM; + } + + for (i = 0; i < info->ngroups; i++) + mp->auxgrps[i] = GROUP_AT(info, i); + } + + mp->n_auxgrps = info->ngroups; + put_group_info(info); + mp->valid |= KDBUS_ATTACH_AUXGROUPS; + + return 0; +} + +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 mm_struct *mm; + + mm = get_task_mm(current); + if (!mm) + return; + + down_read(&mm->mmap_sem); + if (mm->exe_file) { + mp->exe_path = mm->exe_file->f_path; + path_get(&mp->exe_path); + get_fs_root(current->fs, &mp->root_path); + mp->valid |= KDBUS_ATTACH_EXE; + } + up_read(&mm->mmap_sem); + + mmput(mm); +} + +static int kdbus_meta_proc_collect_cmdline(struct kdbus_meta_proc *mp) +{ + struct mm_struct *mm; + char *cmdline; + + mm = get_task_mm(current); + if (!mm) + return 0; + + if (!mm->arg_end) { + mmput(mm); + return 0; + } + + cmdline = strndup_user((const char __user *)mm->arg_start, + mm->arg_end - mm->arg_start); + mmput(mm); + + 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 void kdbus_meta_proc_collect_caps(struct kdbus_meta_proc *mp) +{ + const struct cred *c = current_cred(); + int i; + + /* ABI: "last_cap" equals /proc/sys/kernel/cap_last_cap */ + mp->caps.last_cap = CAP_LAST_CAP; + mp->caps_namespace = get_user_ns(current_user_ns()); + + CAP_FOR_EACH_U32(i) { + mp->caps.set[0].caps[i] = c->cap_inheritable.cap[i]; + mp->caps.set[1].caps[i] = c->cap_permitted.cap[i]; + mp->caps.set[2].caps[i] = c->cap_effective.cap[i]; + mp->caps.set[3].caps[i] = c->cap_bset.cap[i]; + } + + /* clear unused bits */ + for (i = 0; i < 4; i++) + mp->caps.set[i].caps[CAP_TO_INDEX(CAP_LAST_CAP)] &= + CAP_LAST_U32_VALID_MASK; + + mp->valid |= KDBUS_ATTACH_CAPS; +} + +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); + + if ((what & KDBUS_ATTACH_CREDS) && + !(mp->collected & KDBUS_ATTACH_CREDS)) { + kdbus_meta_proc_collect_creds(mp); + mp->collected |= KDBUS_ATTACH_CREDS; + } + + 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_AUXGROUPS) && + !(mp->collected & KDBUS_ATTACH_AUXGROUPS)) { + ret = kdbus_meta_proc_collect_auxgroups(mp); + if (ret < 0) + goto exit_unlock; + mp->collected |= KDBUS_ATTACH_AUXGROUPS; + } + + 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_CAPS) && + !(mp->collected & KDBUS_ATTACH_CAPS)) { + kdbus_meta_proc_collect_caps(mp); + mp->collected |= KDBUS_ATTACH_CAPS; + } + + 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_proc_fake() - Fill process metadata from faked credentials + * @mp: Metadata + * @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. A call to this function + * is considered an alternative to calling kdbus_meta_add_current(), which + * derives the same information from the 'current' task. + * + * This call uses the current task's namespaces to resolve the given + * information. + * + * Return: 0 on success, negative error number otherwise. + */ +int kdbus_meta_proc_fake(struct kdbus_meta_proc *mp, + const struct kdbus_creds *creds, + const struct kdbus_pids *pids, + const char *seclabel) +{ + int ret; + + if (!mp) + return 0; + + mutex_lock(&mp->lock); + + if (creds && !(mp->collected & KDBUS_ATTACH_CREDS)) { + struct user_namespace *ns = current_user_ns(); + + mp->uid = make_kuid(ns, creds->uid); + mp->euid = make_kuid(ns, creds->euid); + mp->suid = make_kuid(ns, creds->suid); + mp->fsuid = make_kuid(ns, creds->fsuid); + + mp->gid = make_kgid(ns, creds->gid); + mp->egid = make_kgid(ns, creds->egid); + mp->sgid = make_kgid(ns, creds->sgid); + mp->fsgid = make_kgid(ns, creds->fsgid); + + if ((creds->uid != (uid_t)-1 && !uid_valid(mp->uid)) || + (creds->euid != (uid_t)-1 && !uid_valid(mp->euid)) || + (creds->suid != (uid_t)-1 && !uid_valid(mp->suid)) || + (creds->fsuid != (uid_t)-1 && !uid_valid(mp->fsuid)) || + (creds->gid != (gid_t)-1 && !gid_valid(mp->gid)) || + (creds->egid != (gid_t)-1 && !gid_valid(mp->egid)) || + (creds->sgid != (gid_t)-1 && !gid_valid(mp->sgid)) || + (creds->fsgid != (gid_t)-1 && !gid_valid(mp->fsgid))) { + ret = -EINVAL; + goto exit_unlock; + } + + mp->valid |= KDBUS_ATTACH_CREDS; + mp->collected |= KDBUS_ATTACH_CREDS; + } + + if (pids && !(mp->collected & KDBUS_ATTACH_PIDS)) { + mp->pid = get_pid(find_vpid(pids->tid)); + mp->tgid = get_pid(find_vpid(pids->pid)); + mp->ppid = get_pid(find_vpid(pids->ppid)); + + if ((pids->tid != 0 && !mp->pid) || + (pids->pid != 0 && !mp->tgid) || + (pids->ppid != 0 && !mp->ppid)) { + put_pid(mp->pid); + put_pid(mp->tgid); + put_pid(mp->ppid); + mp->pid = NULL; + mp->tgid = NULL; + mp->ppid = NULL; + ret = -EINVAL; + goto exit_unlock; + } + + mp->valid |= KDBUS_ATTACH_PIDS; + mp->collected |= KDBUS_ATTACH_PIDS; + } + + if (seclabel && !(mp->collected & KDBUS_ATTACH_SECLABEL)) { + mp->seclabel = kstrdup(seclabel, GFP_KERNEL); + if (!mp->seclabel) { + ret = -ENOMEM; + goto exit_unlock; + } + + mp->valid |= KDBUS_ATTACH_SECLABEL; + mp->collected |= KDBUS_ATTACH_SECLABEL; + } + + ret = 0; + +exit_unlock: + mutex_unlock(&mp->lock); + return ret; +} + +/** + * 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, + struct kdbus_kmsg *kmsg) +{ + struct timespec ts; + + ktime_get_ts(&ts); + mc->ts.monotonic_ns = timespec_to_ns(&ts); + + ktime_get_real_ts(&ts); + mc->ts.realtime_ns = timespec_to_ns(&ts); + + if (kmsg) + mc->ts.seqnum = kmsg->seq; + + 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_entry *e; + struct kdbus_item *item; + size_t slen, size; + + lockdep_assert_held(&conn->ep->bus->name_registry->rwlock); + + size = 0; + list_for_each_entry(e, &conn->names_list, conn_entry) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_name) + + strlen(e->name) + 1); + + if (!size) + return 0; + + item = kmalloc(size, GFP_KERNEL); + if (!item) + return -ENOMEM; + + mc->owned_names_items = item; + mc->owned_names_size = size; + + list_for_each_entry(e, &conn->names_list, conn_entry) { + slen = strlen(e->name) + 1; + kdbus_item_set(item, KDBUS_ITEM_OWNED_NAME, NULL, + sizeof(struct kdbus_name) + slen); + item->name.flags = e->flags; + memcpy(item->name.name, e->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 + 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 + * @kmsg: Kmsg to collect data from + * @conn: Connection to collect data from + * @what: Attach flags to collect + * + * This collects connection metadata from @kmsg 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_kmsg *kmsg, + struct kdbus_conn *conn, + u64 what) +{ + int ret; + + if (!mc || !(what & (KDBUS_ATTACH_TIMESTAMP | + KDBUS_ATTACH_NAMES | + KDBUS_ATTACH_CONN_DESCRIPTION))) + return 0; + + mutex_lock(&mc->lock); + + if (kmsg && (what & KDBUS_ATTACH_TIMESTAMP) && + !(mc->collected & KDBUS_ATTACH_TIMESTAMP)) { + kdbus_meta_conn_collect_timestamp(mc, kmsg); + 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; +} + +/* + * kdbus_meta_export_prepare() - Prepare metadata for export + * @mp: Process metadata, or NULL + * @mc: Connection metadata, or NULL + * @mask: Pointer to mask of KDBUS_ATTACH_* flags to export + * @sz: Pointer to return the size needed by the metadata + * + * Does a conservative calculation of how much space metadata information + * will take up during export. It is 'conservative' because for string + * translations in namespaces, it will use the kernel namespaces, which is + * the longest possible version. + * + * The actual size consumed by kdbus_meta_export() may hence vary from the + * one reported here, but it is guaranteed never to be greater. + * + * Return: 0 on success, negative error number otherwise. + */ +int kdbus_meta_export_prepare(struct kdbus_meta_proc *mp, + struct kdbus_meta_conn *mc, + u64 *mask, size_t *sz) +{ + char *exe_pathname = NULL; + void *exe_page = NULL; + size_t size = 0; + u64 valid = 0; + int ret = 0; + + if (mp) { + mutex_lock(&mp->lock); + valid |= mp->valid; + mutex_unlock(&mp->lock); + } + + if (mc) { + mutex_lock(&mc->lock); + valid |= mc->valid; + mutex_unlock(&mc->lock); + } + + *mask &= valid; + *mask &= kdbus_meta_attach_mask; + + if (!*mask) + goto exit; + + /* process metadata */ + + if (mp && (*mask & KDBUS_ATTACH_CREDS)) + size += KDBUS_ITEM_SIZE(sizeof(struct kdbus_creds)); + + 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->n_auxgrps * 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 (mp && (*mask & KDBUS_ATTACH_EXE)) { + exe_page = (void *)__get_free_page(GFP_TEMPORARY); + if (!exe_page) { + ret = -ENOMEM; + goto exit; + } + + exe_pathname = d_path(&mp->exe_path, exe_page, PAGE_SIZE); + if (IS_ERR(exe_pathname)) { + ret = PTR_ERR(exe_pathname); + goto exit; + } + + size += KDBUS_ITEM_SIZE(strlen(exe_pathname) + 1); + free_page((unsigned long)exe_page); + } + + 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(mp->caps)); + + 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 += 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)); + +exit: + *sz = size; + + return ret; +} + +static int kdbus_meta_push_kvec(struct kvec *kvec, + struct kdbus_item_header *hdr, + u64 type, void *payload, + size_t payload_size, u64 *size) +{ + hdr->type = type; + hdr->size = KDBUS_ITEM_HEADER_SIZE + payload_size; + kdbus_kvec_set(kvec++, hdr, sizeof(*hdr), size); + kdbus_kvec_set(kvec++, payload, payload_size, size); + return 2 + !!kdbus_kvec_pad(kvec++, size); +} + +/* This is equivalent to from_kuid_munged(), but maps INVALID_UID to itself */ +static uid_t kdbus_from_kuid_keep(kuid_t uid) +{ + return uid_valid(uid) ? + from_kuid_munged(current_user_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(kgid_t gid) +{ + return gid_valid(gid) ? + from_kgid_munged(current_user_ns(), gid) : ((gid_t)-1); +} + +/** + * kdbus_meta_export() - export information from metadata into a slice + * @mp: Process metadata, or NULL + * @mc: Connection metadata, or NULL + * @mask: Mask of KDBUS_ATTACH_* flags to export + * @slice: The slice to export to + * @offset: The offset inside @slice to write to + * @real_size: The real size the metadata consumed + * + * This function exports information from metadata into @slice at offset + * @offset inside that slice. Only information that is requested in @mask + * and that has been collected before is exported. + * + * In order to make sure not to write out of bounds, @mask must be the same + * value that was previously returned from kdbus_meta_export_prepare(). The + * function will, however, not necessarily write as many bytes as returned by + * kdbus_meta_export_prepare(); depending on the namespaces in question, it + * might use up less than that. + * + * All information will be translated using the current namespaces. + * + * Return: 0 on success, negative error number otherwise. + */ +int kdbus_meta_export(struct kdbus_meta_proc *mp, + struct kdbus_meta_conn *mc, + u64 mask, + struct kdbus_pool_slice *slice, + off_t offset, + size_t *real_size) +{ + struct user_namespace *user_ns = current_user_ns(); + struct kdbus_item_header item_hdr[13], *hdr; + char *exe_pathname = NULL; + struct kdbus_creds creds; + struct kdbus_pids pids; + void *exe_page = NULL; + struct kvec kvec[40]; + u64 *auxgrps = NULL; + size_t cnt = 0; + u64 size = 0; + int ret = 0; + + hdr = &item_hdr[0]; + + /* + * TODO: We currently have no sane way of translating a set of caps + * between different user namespaces. Until that changes, we have + * to drop such items. + */ + if (mp && mp->caps_namespace != user_ns) + mask &= ~KDBUS_ATTACH_CAPS; + + if (mask == 0) { + *real_size = 0; + return 0; + } + + /* process metadata */ + + if (mp && (mask & KDBUS_ATTACH_CREDS)) { + creds.uid = kdbus_from_kuid_keep(mp->uid); + creds.euid = kdbus_from_kuid_keep(mp->euid); + creds.suid = kdbus_from_kuid_keep(mp->suid); + creds.fsuid = kdbus_from_kuid_keep(mp->fsuid); + creds.gid = kdbus_from_kgid_keep(mp->gid); + creds.egid = kdbus_from_kgid_keep(mp->egid); + creds.sgid = kdbus_from_kgid_keep(mp->sgid); + creds.fsgid = kdbus_from_kgid_keep(mp->fsgid); + + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, KDBUS_ITEM_CREDS, + &creds, sizeof(creds), &size); + } + + if (mp && (mask & KDBUS_ATTACH_PIDS)) { + pids.pid = pid_vnr(mp->tgid); + pids.tid = pid_vnr(mp->pid); + pids.ppid = pid_vnr(mp->ppid); + + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, KDBUS_ITEM_PIDS, + &pids, sizeof(pids), &size); + } + + if (mp && (mask & KDBUS_ATTACH_AUXGROUPS)) { + size_t payload_size = mp->n_auxgrps * sizeof(u64); + int i; + + auxgrps = kmalloc(payload_size, GFP_KERNEL); + if (!auxgrps) { + ret = -ENOMEM; + goto exit; + } + + for (i = 0; i < mp->n_auxgrps; i++) + auxgrps[i] = from_kgid_munged(user_ns, mp->auxgrps[i]); + + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_AUXGROUPS, + auxgrps, payload_size, &size); + } + + if (mp && (mask & KDBUS_ATTACH_TID_COMM)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_TID_COMM, mp->tid_comm, + strlen(mp->tid_comm) + 1, &size); + + if (mp && (mask & KDBUS_ATTACH_PID_COMM)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_PID_COMM, mp->pid_comm, + strlen(mp->pid_comm) + 1, &size); + + if (mp && (mask & KDBUS_ATTACH_EXE)) { + struct path p; + + /* + * TODO: 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, &mp->root_path)) { + exe_page = (void *)__get_free_page(GFP_TEMPORARY); + if (!exe_page) { + path_put(&p); + ret = -ENOMEM; + goto exit; + } + + exe_pathname = d_path(&mp->exe_path, exe_page, + PAGE_SIZE); + if (IS_ERR(exe_pathname)) { + path_put(&p); + ret = PTR_ERR(exe_pathname); + goto exit; + } + + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_EXE, + exe_pathname, + strlen(exe_pathname) + 1, + &size); + } + path_put(&p); + } + + if (mp && (mask & KDBUS_ATTACH_CMDLINE)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_CMDLINE, mp->cmdline, + strlen(mp->cmdline) + 1, &size); + + if (mp && (mask & KDBUS_ATTACH_CGROUP)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_CGROUP, mp->cgroup, + strlen(mp->cgroup) + 1, &size); + + if (mp && (mask & KDBUS_ATTACH_CAPS)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_CAPS, &mp->caps, + sizeof(mp->caps), &size); + + if (mp && (mask & KDBUS_ATTACH_SECLABEL)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_SECLABEL, mp->seclabel, + strlen(mp->seclabel) + 1, &size); + + if (mp && (mask & KDBUS_ATTACH_AUDIT)) { + struct kdbus_audit a = { + .loginuid = from_kuid(user_ns, mp->audit_loginuid), + .sessionid = mp->audit_sessionid, + }; + + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, KDBUS_ITEM_AUDIT, + &a, sizeof(a), &size); + } + + /* connection metadata */ + + if (mc && (mask & KDBUS_ATTACH_NAMES)) + kdbus_kvec_set(&kvec[cnt++], mc->owned_names_items, + mc->owned_names_size, &size); + + if (mc && (mask & KDBUS_ATTACH_CONN_DESCRIPTION)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_CONN_DESCRIPTION, + mc->conn_description, + strlen(mc->conn_description) + 1, + &size); + + if (mc && (mask & KDBUS_ATTACH_TIMESTAMP)) + cnt += kdbus_meta_push_kvec(kvec + cnt, hdr++, + KDBUS_ITEM_TIMESTAMP, &mc->ts, + sizeof(mc->ts), &size); + + ret = kdbus_pool_slice_copy_kvec(slice, offset, kvec, cnt, size); + *real_size = size; + +exit: + kfree(auxgrps); + + if (exe_page) + free_page((unsigned long)exe_page); + + return ret; +} + +/** + * kdbus_meta_calc_attach_flags() - calculate attach flags for a sender + * and a receiver + * @sender: Sending connection + * @receiver: Receiving connection + * + * Return: the attach flags both the sender and the receiver have opted-in + * for. + */ +u64 kdbus_meta_calc_attach_flags(const struct kdbus_conn *sender, + const struct kdbus_conn *receiver) +{ + return atomic64_read(&sender->attach_flags_send) & + atomic64_read(&receiver->attach_flags_recv); +} diff --git a/ipc/kdbus/metadata.h b/ipc/kdbus/metadata.h new file mode 100644 index 000000000000..42c942b34d2c --- /dev/null +++ b/ipc/kdbus/metadata.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013-2015 Kay Sievers + * Copyright (C) 2013-2015 Greg Kroah-Hartman + * Copyright (C) 2013-2015 Daniel Mack + * Copyright (C) 2013-2015 David Herrmann + * 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 + +struct kdbus_conn; +struct kdbus_kmsg; +struct kdbus_pool_slice; + +struct kdbus_meta_proc; +struct kdbus_meta_conn; + +extern unsigned long long kdbus_meta_attach_mask; + +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); +int kdbus_meta_proc_fake(struct kdbus_meta_proc *mp, + 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_kmsg *kmsg, + struct kdbus_conn *conn, + u64 what); + +int kdbus_meta_export_prepare(struct kdbus_meta_proc *mp, + struct kdbus_meta_conn *mc, + u64 *mask, size_t *sz); +int kdbus_meta_export(struct kdbus_meta_proc *mp, + struct kdbus_meta_conn *mc, + u64 mask, + struct kdbus_pool_slice *slice, + off_t offset, size_t *real_size); +u64 kdbus_meta_calc_attach_flags(const struct kdbus_conn *sender, + const struct kdbus_conn *receiver); + +#endif