Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1757926AbaJ2WDr (ORCPT ); Wed, 29 Oct 2014 18:03:47 -0400 Received: from mail.linuxfoundation.org ([140.211.169.12]:53249 "EHLO mail.linuxfoundation.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756966AbaJ2WDi (ORCPT ); Wed, 29 Oct 2014 18:03:38 -0400 From: Greg Kroah-Hartman To: linux-api@vger.kernel.org, linux-kernel@vger.kernel.org Cc: john.stultz@linaro.org, arnd@arndb.de, tj@kernel.org, marcel@holtmann.org, desrt@desrt.ca, hadess@hadess.net, dh.herrmann@gmail.com, tixxdz@opendz.org, gregkh@linuxfoundation.org, simon.mcvittie@collabora.co.uk, daniel@zonque.org, alban.crequy@collabora.co.uk, javier.martinez@collabora.co.uk, teg@jklm.no Subject: kdbus: add policy database implementation Date: Wed, 29 Oct 2014 15:00:54 -0700 Message-Id: <1414620056-6675-11-git-send-email-gregkh@linuxfoundation.org> X-Mailer: git-send-email 2.1.2 In-Reply-To: <1414620056-6675-1-git-send-email-gregkh@linuxfoundation.org> References: <1414620056-6675-1-git-send-email-gregkh@linuxfoundation.org> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Daniel Mack This patch adds the policy database implementation. A policy databases restrict the possibilities of connections to own, see and talk to well-known names. It can be associated with a bus (through a policy holder connection) or a custom endpoint. By default, buses have an empty policy database that is augmented on demand when a policy holder connection is instantiated. Policies are set through KDBUS_CMD_HELLO (when creating a policy holder connection), KDBUS_CMD_CONN_UPDATE (when updating a policy holder connection), KDBUS_CMD_EP_MAKE (creating a custom endpoint) or KDBUS_CMD_EP_UPDATE (updating a custom endpoint). In all cases, the name and policy access information is stored in items of type KDBUS_ITEM_NAME and KDBUS_ITEM_POLICY_ACCESS. See Documentation/kdbus.txt for more details. Signed-off-by: Daniel Mack Signed-off-by: Greg Kroah-Hartman --- drivers/misc/kdbus/policy.c | 617 ++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/kdbus/policy.h | 60 +++++ 2 files changed, 677 insertions(+) create mode 100644 drivers/misc/kdbus/policy.c create mode 100644 drivers/misc/kdbus/policy.h diff --git a/drivers/misc/kdbus/policy.c b/drivers/misc/kdbus/policy.c new file mode 100644 index 000000000000..66bce57eb5e6 --- /dev/null +++ b/drivers/misc/kdbus/policy.c @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2013-2014 Kay Sievers + * Copyright (C) 2013-2014 Greg Kroah-Hartman + * Copyright (C) 2013-2014 Daniel Mack + * Copyright (C) 2013-2014 David Herrmann + * Copyright (C) 2013-2014 Linux Foundation + * Copyright (C) 2014 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 "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_cache_entry - a cached entry + * @conn_a: Connection A + * @conn_b: Connection B + * @owner: Owner of policy-entry that produced this cache-entry + * @hentry: The hash table entry for the database's entries_hash + */ +struct kdbus_policy_db_cache_entry { + struct kdbus_conn *conn_a; + struct kdbus_conn *conn_b; + const void *owner; + struct hlist_node hentry; +}; + +/** + * 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 const struct kdbus_policy_db_entry * +kdbus_policy_lookup(struct kdbus_policy_db *db, + const char *name, u32 hash, bool wildcard) +{ + struct kdbus_policy_db_entry *e, *found = NULL; + + hash_for_each_possible(db->entries_hash, e, hentry, hash) + if (strcmp(e->name, name) == 0 && !e->wildcard) + return e; + + if (wildcard) { + const char *tmp; + char *dot; + + tmp = kstrdup(name, GFP_KERNEL); + if (!tmp) + return NULL; + + dot = strrchr(tmp, '.'); + if (!dot) + goto exit_free; + + *dot = '\0'; + hash = kdbus_str_hash(tmp); + + hash_for_each_possible(db->entries_hash, e, hentry, hash) + if (strcmp(e->name, tmp) == 0 && e->wildcard) { + found = e; + /* never "break;" in hash_for_each() */ + goto exit_free; + } + +exit_free: + kfree(tmp); + } + + return found; +} + +/** + * 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_cache_entry *ce; + struct kdbus_policy_db_entry *e; + struct hlist_node *tmp; + unsigned int i; + + BUG_ON(!db); + + /* 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); + + /* purge cache */ + mutex_lock(&db->cache_lock); + hash_for_each_safe(db->talk_access_hash, i, tmp, ce, hentry) { + hash_del(&ce->hentry); + kfree(ce); + } + mutex_unlock(&db->cache_lock); +} + +/** + * 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); + hash_init(db->talk_access_hash); + init_rwsem(&db->entries_rwlock); + mutex_init(&db->cache_lock); +} + +static int kdbus_policy_check_access(const struct kdbus_policy_db_entry *e, + const struct cred *cred, + unsigned int access) +{ + struct kdbus_policy_db_entry_access *a; + struct group_info *group_info; + int i; + + if (!e) + return -EPERM; + + group_info = cred->group_info; + + list_for_each_entry(a, &e->access_list, list) { + if (a->access >= access) { + switch (a->type) { + case KDBUS_POLICY_ACCESS_USER: + if (uid_eq(cred->uid, a->uid)) + return 0; + break; + case KDBUS_POLICY_ACCESS_GROUP: + if (gid_eq(cred->gid, a->gid)) + return 0; + + for (i = 0; i < group_info->ngroups; i++) { + kgid_t gid = GROUP_AT(group_info, i); + + if (gid_eq(gid, a->gid)) + return 0; + } + + break; + case KDBUS_POLICY_ACCESS_WORLD: + return 0; + } + } + } + + return -EPERM; +} + +/** + * kdbus_policy_check_own_access() - check whether a connection is allowed + * to own a name + * @db: The policy database + * @conn: The connection to check + * @name: The name to check + * + * Return: 0 if the connection is allowed to own the name, -EPERM otherwise + */ +int kdbus_policy_check_own_access(struct kdbus_policy_db *db, + const struct kdbus_conn *conn, + const char *name) +{ + const struct kdbus_policy_db_entry *e; + int ret; + + down_read(&db->entries_rwlock); + e = kdbus_policy_lookup(db, name, kdbus_str_hash(name), true); + ret = kdbus_policy_check_access(e, conn->cred, KDBUS_POLICY_OWN); + up_read(&db->entries_rwlock); + + return ret; +} + +/** + * kdbus_policy_check_talk_access() - check if one connection is allowed + * to send a message to another connection + * @db: The policy database + * @conn_src: The source connection + * @conn_dst: The destination connection + * + * Return: 0 if access is granted, -EPERM if not, negative errno on failure + */ +int kdbus_policy_check_talk_access(struct kdbus_policy_db *db, + struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst) +{ + struct kdbus_policy_db_cache_entry *ce; + struct kdbus_name_entry *name_entry; + unsigned int hash = 0; + const void *owner; + int ret; + + /* + * If there was a positive match for these two connections before, + * there's an entry in the hash table for them. + */ + hash ^= hash_ptr(conn_src, KDBUS_POLICY_HASH_SIZE); + hash ^= hash_ptr(conn_dst, KDBUS_POLICY_HASH_SIZE); + + mutex_lock(&db->cache_lock); + hash_for_each_possible(db->talk_access_hash, ce, hentry, hash) + if (ce->conn_a == conn_src && ce->conn_b == conn_dst) { + mutex_unlock(&db->cache_lock); + return 0; + } + mutex_unlock(&db->cache_lock); + + /* + * Otherwise, walk the connection list and store a hash-table entry if + * send access is granted. + */ + + down_read(&db->entries_rwlock); + + ret = -EPERM; + mutex_lock(&conn_dst->lock); + list_for_each_entry(name_entry, &conn_dst->names_list, conn_entry) { + u32 hash = kdbus_str_hash(name_entry->name); + const struct kdbus_policy_db_entry *e; + + e = kdbus_policy_lookup(db, name_entry->name, hash, true); + if (kdbus_policy_check_access(e, conn_src->cred, + KDBUS_POLICY_TALK) == 0) { + owner = e->owner; + ret = 0; + break; + } + } + mutex_unlock(&conn_dst->lock); + + if (ret >= 0) { + ret = -ENOMEM; + ce = kmalloc(sizeof(*ce), GFP_KERNEL); + if (ce) { + ce->conn_a = conn_src; + ce->conn_b = conn_dst; + ce->owner = owner; + INIT_HLIST_NODE(&ce->hentry); + + mutex_lock(&db->cache_lock); + hash_add(db->talk_access_hash, &ce->hentry, hash); + mutex_unlock(&db->cache_lock); + + ret = 0; + } + } + + up_read(&db->entries_rwlock); + + return ret; +} + +/** + * kdbus_policy_check_see_access_unlocked() - Check whether a connection is + * allowed to see a given name + * @db: The policy database + * @conn: The connection performing the lookup + * @name: The name + * + * Return: 0 if permission to see the name is granted, -EPERM otherwise + */ +int kdbus_policy_check_see_access_unlocked(struct kdbus_policy_db *db, + struct kdbus_conn *conn, + const char *name) +{ + const struct kdbus_policy_db_entry *e; + + e = kdbus_policy_lookup(db, name, kdbus_str_hash(name), true); + return kdbus_policy_check_access(e, conn->cred, KDBUS_POLICY_SEE); +} + +static void __kdbus_policy_remove_owner_cache(struct kdbus_policy_db *db, + const void *owner) +{ + struct kdbus_policy_db_cache_entry *ce; + struct hlist_node *tmp; + int i; + + mutex_lock(&db->cache_lock); + hash_for_each_safe(db->talk_access_hash, i, tmp, ce, hentry) + if (ce->owner == owner) { + hash_del(&ce->hentry); + kfree(ce); + } + mutex_unlock(&db->cache_lock); +} + +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); + __kdbus_policy_remove_owner_cache(db, owner); + up_write(&db->entries_rwlock); +} + +/** + * kdbus_policy_purge_cache_for_conn() - remove all cached entries related to + * a connection + * @db: The policy database + * @conn: The connection which items to remove + */ +void kdbus_policy_purge_cache(struct kdbus_policy_db *db, + const struct kdbus_conn *conn) +{ + struct kdbus_policy_db_cache_entry *ce; + struct hlist_node *tmp; + int i; + + mutex_lock(&db->cache_lock); + hash_for_each_safe(db->talk_access_hash, i, tmp, ce, hentry) + if (ce->conn_a == conn || ce->conn_b == conn) { + hash_del(&ce->hentry); + kfree(ce); + } + mutex_unlock(&db->cache_lock); +} + +/* + * Convert user provided policy access to internal kdbus policy + * access + */ +static int +kdbus_policy_make_access(const struct kdbus_policy_access *uaccess, + struct kdbus_policy_db_entry_access **entry) +{ + int ret; + struct kdbus_policy_db_entry_access *a; + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) + return -ENOMEM; + + ret = -EINVAL; + 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; + } + + a->type = uaccess->type; + a->access = uaccess->access; + + *entry = a; + + return 0; + +err: + kfree(a); + return 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. + * + * 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; + + if (items_size > KDBUS_POLICY_MAX_SIZE) + return -E2BIG; + + /* 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; + } + + ret = kdbus_policy_make_access(&item->policy_access, + &a); + if (ret < 0) + 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_str_hash(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); + } + + /* purge all cache-entries produced by previous rules */ + __kdbus_policy_remove_owner_cache(db, owner); + +restore: + /* if we failed, flush all entries we added so far, but keep cache */ + 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_str_hash(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/drivers/misc/kdbus/policy.h b/drivers/misc/kdbus/policy.h new file mode 100644 index 000000000000..f4f6f044b4c1 --- /dev/null +++ b/drivers/misc/kdbus/policy.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013-2014 Kay Sievers + * Copyright (C) 2013-2014 Greg Kroah-Hartman + * Copyright (C) 2013-2014 Daniel Mack + * Copyright (C) 2013-2014 David Herrmann + * Copyright (C) 2013-2014 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 +#include +#include + +struct kdbus_conn; +struct kdbus_item; + +/** + * struct kdbus_policy_db - policy database + * @entries_hash: Hashtable of entries + * @talk_access_hash: Hashtable of send access elements + * @entries_lock: Mutex to protect the database's access entries + * @cache_lock: Mutex to protect the database's cache + */ +struct kdbus_policy_db { + DECLARE_HASHTABLE(entries_hash, 6); + DECLARE_HASHTABLE(talk_access_hash, 6); + struct rw_semaphore entries_rwlock; + struct mutex cache_lock; +}; + +void kdbus_policy_db_init(struct kdbus_policy_db *db); +void kdbus_policy_db_clear(struct kdbus_policy_db *db); + +int kdbus_policy_check_see_access_unlocked(struct kdbus_policy_db *db, + struct kdbus_conn *conn, + const char *name); +int kdbus_policy_check_talk_access(struct kdbus_policy_db *db, + struct kdbus_conn *conn_src, + struct kdbus_conn *conn_dst); +int kdbus_policy_check_own_access(struct kdbus_policy_db *db, + const struct kdbus_conn *conn, + const char *name); +void kdbus_policy_purge_cache(struct kdbus_policy_db *db, + const struct kdbus_conn *conn); +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 -- 2.1.2 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/