/* * 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 "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 enpoint 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_cmd_ep_make() - handle KDBUS_CMD_ENDPOINT_MAKE * @bus: bus to operate on * @argp: command payload * * Return: 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: Newly created endpoint on success, ERR_PTR 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); }