diff options
Diffstat (limited to 'ipc')
-rw-r--r-- | ipc/kdbus/metadata.c | 1164 | ||||
-rw-r--r-- | ipc/kdbus/metadata.h | 57 |
2 files changed, 1221 insertions, 0 deletions
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 <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 <linux/version.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 + * @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 <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_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 |