Received: by 10.223.185.116 with SMTP id b49csp1508669wrg; Sun, 11 Feb 2018 13:53:10 -0800 (PST) X-Google-Smtp-Source: AH8x225a4W4zTyldukos/hJHR5unmXbHv4UODWhXAmzGUpgPa5suByNnhmuzY8eubscKVVJzVADc X-Received: by 10.99.121.76 with SMTP id u73mr7796152pgc.154.1518385990447; Sun, 11 Feb 2018 13:53:10 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1518385990; cv=none; d=google.com; s=arc-20160816; b=tdtFIGNnOTM/vZ0FvKOEhZCzwOpUds5a+Y93j3DK2O/+F0kVfOHIizS5rNLtZdFibd CPdyiZVhq7CqLtMPrdAFZYm9ixpFCpa/t86yj32cFyUJoigoo1jGB9T4ImbEpnUZhNf5 6Bup2Cm/dOLqUMknvnHb7mcGHaDW8a3CMamjwh4ErS9nhZ+upPMRX1hDPCqHDVwonPjl oe3UwTTBzAsiiowYNn/CotPmityouMv+HN3bcyrqXwg9v7akdLfFM6/yre/AmP4BI1Ge kzjzpif+r0nXIHOxbGK3ytXgKp+KF9pUZ12GmotEEhKx+B1iS5+pMZ9Tu+KJhhGFfeBf DsYA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:to:from:arc-authentication-results; bh=nsvt8Mu0CCON9LIxBp1AF92MC4iMgHIxw2UnMC79BdE=; b=KMw1FAlRMOF9Ym4K8BDeEPfo4qMimnv3YypahqzjyndYxpKTGdwiwA+icyWRhy8rEt E5FexfnE371HUN5QZOehAE8mTro6GNBBPWDqkHRCJ58XxMu4oN6fO5+IR1lL+n6CerQi 2lAB0O5HEVpsPgGYx13NVvIfvpVMjRcDIo5F+iWB1yaozNu6/+2lMXpFkUSMsRgC0g/l Jy+TXjV/X7nkcIrsfiDrxXKBOYu2DM5wLv4FPyxA+sPe/BuOaa2i3kOn+tt3WbjHv4JT E/DqBbH/pHJUEBUQCgk6+A1eQybqtzDBUAncp7VW8qvdST9qFOa47zeVuRvnKgO84wLt sJCg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id v27si754903pfi.191.2018.02.11.13.52.56; Sun, 11 Feb 2018 13:53:10 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932205AbeBKVul (ORCPT + 99 others); Sun, 11 Feb 2018 16:50:41 -0500 Received: from mout.gmx.net ([212.227.17.22]:43233 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752803AbeBKVuj (ORCPT ); Sun, 11 Feb 2018 16:50:39 -0500 Received: from orion.intern ([84.184.20.212]) by mail.gmx.com (mrgmx102 [212.227.17.168]) with ESMTPSA (Nemesis) id 0LhkiL-1eP4xg1BAX-00mvim for ; Sun, 11 Feb 2018 22:50:37 +0100 From: "Enrico Weigelt, metux IT consult" To: linux-kernel@vger.kernel.org Subject: [PATCH] p9caps: add Plan9 capability devices Date: Sun, 11 Feb 2018 21:50:28 +0000 Message-Id: <20180211215028.16210-2-metux@gmx.de> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20180211215028.16210-1-metux@gmx.de> References: <40d4c871-a16a-7b8f-2d4a-422a5a490693@infradead.org> <20180211215028.16210-1-metux@gmx.de> X-Provags-ID: V03:K0:GS422zmCf5/MiYxBopUSIRF8K+QKsmSDjPoLMRKGASuPzmXo2tQ uemOs/ZGj8kdlQvuEbGiEsbjL3m6IEZzwniYwF19aTh9AoVuBy5/AuP+YAvBQ+FH6wwvB1n cv8B2t6Q/vVHVwzIhHS/1kBdGOfEHmh2qucWK6+Q+T3YV5b7rmzFUIV+lkrai3k25ku7lJi JtBIZD9qgQZzewS/dtMFA== X-UI-Out-Filterresults: notjunk:1;V01:K0:Vtl3LN7NDUw=:yCxkREQ1jKrOU4Eb65Np3f 1rs+zyZFvlarsToVa/EBoX95DkpW4mZyC/2N8A0Iij29Jkn7fxt/38SCCMrBsILfbTv5m2cwh Tf2scRmhDVVwPnkJqUbasd5uiGDh9qoZqEY2j1VzFCq6ejx0F//t8NljsganLbdOMOfrSQngl 5Vy2u9qHAfyGOtFjmgLcsn6Rv2LPBMXiXkJNd0viIrJUG39xpnoASb1xBaaCSHrEBoCVAJ3w8 ttlCFayavWQ8WjR391XOlZEGDftIe3bXowLijyIRqd4dMhTrnj931Z/W7GWaZSNJLmbGPX88Q 8j/MEh6cB1dbVI0u0xSmYFqdVYr02RjpyksH/dXhRoOWWW6/d2t1ryOg3lt4YRvsvIBwhSKIC rHTfFc9yzMw+iIerudq7i95ZGgdwpwW2wnhEdqe6hkJYnq97p2VztnpvoSQ3+3MrHC5cVHWgY +aXazbqm8DxmM6MppHO2/lVyRs1ykpDMzguBjsH9TeUCWfjXKXyoxg8fA1JoCTiZT9Az7LdSn FFw/f7X/XVLJskkDonVcVv8mF+pHu5QwThMwkw7BG7mn/HwDSJIwLarfKnQhR0m7hICIhT0LJ e24I60aSn9brtW1Zr5ClpwfzmJTQ3WYd7fMjQktcnD2JfzyEupQOrCjb16D9bgqxNznwqWBwj z2T/OQ4EHkqFudZVwr4OsAst9K6di8JIrP3okS2QMCuFw7Cmr2tU/ZRa4pNr13KusrdcsCTiY /pi+gTM6oqn8CbVa1l/TVn9JeeS2541Vp2gp27+0SvpBXxvOYeK7JWCyfUgMO7DRTdaRFmiwK Q4AfPnPTzYwIcnzmNPygf5xTPX43A== Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: "Enrico Weigelt, metux IT consult" This driver implements the Plan9 capability devices, used for switching user id via capability tokens. https://9p.io/sys/doc/auth.html --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/p9caps/Kconfig | 11 ++ drivers/staging/p9caps/Makefile | 1 + drivers/staging/p9caps/p9caps.c | 369 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 384 insertions(+) create mode 100644 drivers/staging/p9caps/Kconfig create mode 100644 drivers/staging/p9caps/Makefile create mode 100644 drivers/staging/p9caps/p9caps.c diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 554683912cff..23f325339fe8 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -118,4 +118,6 @@ source "drivers/staging/vboxvideo/Kconfig" source "drivers/staging/pi433/Kconfig" +source "drivers/staging/p9caps/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 6e536020029a..eccdf4643453 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -3,6 +3,7 @@ obj-y += media/ obj-y += typec/ +obj-$(CONFIG_PLAN9CAPS) += p9caps/ obj-$(CONFIG_IRDA) += irda/net/ obj-$(CONFIG_IRDA) += irda/drivers/ obj-$(CONFIG_PRISM2_USB) += wlan-ng/ diff --git a/drivers/staging/p9caps/Kconfig b/drivers/staging/p9caps/Kconfig new file mode 100644 index 000000000000..b909daaa79ce --- /dev/null +++ b/drivers/staging/p9caps/Kconfig @@ -0,0 +1,11 @@ +config PLAN9CAPS + tristate "Plan 9 capability device" + default n + select CRYPTO_HMAC + select CRYPTO_SHA1 + help + This module implements the Plan 9 capability devices + /dev/caphash and /dev/capuse + + To compile this driver as a module, choose + M here: the module will be called p9caps. diff --git a/drivers/staging/p9caps/Makefile b/drivers/staging/p9caps/Makefile new file mode 100644 index 000000000000..67d38099a249 --- /dev/null +++ b/drivers/staging/p9caps/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PLAN9CAPS) += p9caps.o diff --git a/drivers/staging/p9caps/p9caps.c b/drivers/staging/p9caps/p9caps.c new file mode 100644 index 000000000000..e46b09821c18 --- /dev/null +++ b/drivers/staging/p9caps/p9caps.c @@ -0,0 +1,369 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Plan9 /dev/caphash and /dev/capuse device + * + * 2DO: - caphash should only allow one process (per userns) + * - support textual user names + * - invalidate old caps + */ + +#define DEVICE_CAPUSE "/dev/capuse" +#define DEVICE_CAPHASH "/dev/caphash" + +struct caphash_entry { + struct list_head list; + struct user_namespace *user_ns; + char data[SHA1_DIGEST_SIZE]; +}; + +struct caphash_writer { + struct list_head list; + struct user_namespace *user_ns; +}; + +static dev_t caphash_devid = 0; +static dev_t capuse_devid = 0; + +static LIST_HEAD(caphash_entries); +static LIST_HEAD(caphash_writers); + +static DEFINE_MUTEX(lock); + +struct crypto_ahash *hmac_tfm = NULL; + +static int caphash_open(struct inode *inode, struct file *filp) +{ + struct caphash_writer *tmp = NULL; + struct user_namespace *user_ns = current_user_ns(); + int retval = 0; + struct list_head *pos, *q; + + /* make sure only one instance per namespace can be opened */ + mutex_lock(&lock); + + list_for_each_safe(pos, q, &(caphash_writers)) { + tmp = list_entry(pos, struct caphash_writer, list); + if (tmp->user_ns == user_ns) { + pr_err("already locked in this namespace\n"); + retval = -EBUSY; + goto out; + } + } + + if (!(tmp = kzalloc(sizeof(struct caphash_writer), GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + + tmp->user_ns = get_user_ns(user_ns); + list_add(&(tmp->list), &caphash_writers); + +out: + mutex_unlock(&lock); + return retval; +} + +static int caphash_release(struct inode *inode, struct file *filp) +{ + int retval = 0; + struct user_namespace *user_ns = current_user_ns(); + struct list_head *pos, *q; + struct caphash_entry *tmp; + + mutex_lock(&lock); + + list_for_each_safe(pos, q, &(caphash_writers)) { + tmp = list_entry(pos, struct caphash_entry, list); + if (tmp->user_ns == user_ns) { + list_del(pos); + kfree(tmp); + goto out; + } + } + +out: + mutex_unlock(&lock); + return retval; +} + +static ssize_t caphash_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct caphash_entry *ent; + + if (count > SHA1_DIGEST_SIZE) { + pr_err("SHA1 digest size too large: %d\n", count); + return -E2BIG; + } + + if (!(ent = kzalloc(sizeof(struct caphash_entry), GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(&(ent->data), buf, count)) { + kfree(ent); + return -EFAULT; + } + + ent->user_ns = get_user_ns(current_user_ns()); + + mutex_lock(&lock); + list_add(&(ent->list), &caphash_entries); + mutex_unlock(&lock); + + return count; +} + +/* called w/ lock held. we can relieve this by allocating tfm locally */ +static ssize_t hash(const char *src, const char* dst, const char *key, u8 *result) +{ + struct scatterlist sg; + struct ahash_request *req; + int retval; + char *text = NULL; + size_t text_len; + int digest_len; + u8* digest = NULL; + + text_len = strlen(src)+strlen(dst)+1; /* src@dst */ + digest_len = crypto_ahash_reqsize(hmac_tfm); + + digest = kzalloc(digest_len, GFP_KERNEL); + text = kzalloc(text_len+1, GFP_KERNEL); + + if (!digest || !text) { + retval = -ENOMEM; + goto out; + } + + if (!(req = ahash_request_alloc(hmac_tfm, GFP_KERNEL))) { + pr_err("failed to alloc ahash_request\n"); + retval = -ENOMEM; + goto out; + } + + snprintf(text, text_len+1, "%s@%s", src, dst); + sg_set_buf(&sg, text, text_len); + + ahash_request_set_callback(req, 0, NULL, NULL); + ahash_request_set_crypt(req, &sg, digest, text_len); + + if ((retval = crypto_ahash_setkey(hmac_tfm, key, strlen(key)))) { + pr_err("crypto_ahash_setkey() failed ret=%d\n", retval); + goto out; + } + + if ((retval = crypto_ahash_digest(req))) { + pr_err("digest() failed ret=%d\n", retval); + goto out; + } + + memcpy(result, digest, SHA1_DIGEST_SIZE); + +out: + kfree(text); + kfree(digest); + + return 0; +} + +static inline kuid_t convert_uid(const char* uname) +{ + return make_kuid(current_user_ns(), simple_strtol(uname, NULL, 0)); +} + +static ssize_t switch_uid(const char *src_uname, const char *dst_uname) +{ + struct cred *creds = prepare_creds(); + + kuid_t src_uid = convert_uid(src_uname); + kuid_t dst_uid = convert_uid(dst_uname); + + if (!uid_eq(src_uid, current_uid())) { + pr_info("src uid mismatch\n"); + return -EPERM; + } + + if (!(creds = prepare_creds())) + return -ENOMEM; + + creds->uid = dst_uid; + creds->euid = dst_uid; + + pr_info("switching from kuid %d to %d\n", src_uid.val, dst_uid.val); + return commit_creds(creds); +} + +static ssize_t try_switch(const char* src_uname, const char* dst_uname, const u8* hashval) +{ + struct list_head *pos; + list_for_each(pos, &(caphash_entries)) { + struct caphash_entry *tmp = list_entry(pos, struct caphash_entry, list); + if ((0 == memcmp(hashval, tmp->data, SHA1_DIGEST_SIZE)) && + (tmp->user_ns == current_user_ns())) { + + int retval; + + if ((retval = switch_uid(src_uname, dst_uname))) { + pr_info("uid switch failed\n"); + return retval; + } + + tmp = list_entry(pos, struct caphash_entry, list); + list_del(pos); + put_user_ns(tmp->user_ns); + kfree(tmp); + + return 0; + } + } + + pr_info("cap not found\n"); + + return -ENOENT; +} + +static ssize_t capuse_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + ssize_t retval = count; + char *rand_str, *src_uname, *dst_uname; + u8 hashval[SHA1_DIGEST_SIZE] = { 0 }; + char *cmdbuf; + + if (!(cmdbuf = kzalloc(count, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(cmdbuf, buf, count)) { + retval = -EFAULT; + goto out_free; + } + + { + char *walk = cmdbuf; + src_uname = strsep(&walk, "@"); + dst_uname = strsep(&walk, "@"); + rand_str = walk; + if (!src_uname || !dst_uname || !rand_str) { + retval = -EINVAL; + goto out_free; + } + } + + mutex_lock(&lock); + + if ((retval = hash(src_uname, dst_uname, rand_str, hashval))) + goto out_unlock; + + if ((retval = try_switch(src_uname, dst_uname, hashval))) + goto out_unlock; + + retval = count; + +out_unlock: + mutex_unlock(&lock); + +out_free: + kfree(cmdbuf); + return retval; +} + +static const struct file_operations caphash_fops = { + .owner = THIS_MODULE, + .write = caphash_write, + .open = caphash_open, + .release = caphash_release, +}; + +static const struct file_operations capuse_fops = { + .owner = THIS_MODULE, + .write = capuse_write, +}; + +static struct cdev caphash_dev; +static struct cdev capuse_dev; + +static int clear(void) +{ + struct caphash_entry *tmp; + struct list_head *pos, *q; + + list_for_each_safe(pos, q, &(caphash_entries)) { + tmp = list_entry(pos, struct caphash_entry, list); + list_del(pos); + kfree(tmp); + } + + return 0; +} + +static void _cleanup_module(void) +{ + clear(); + + cdev_del(&caphash_dev); + cdev_del(&capuse_dev); + + unregister_chrdev_region(caphash_devid, 1); + unregister_chrdev_region(capuse_devid, 1); + + if (hmac_tfm) + crypto_free_ahash(hmac_tfm); +} + +static int _init_module(void) +{ + int retval; + + hmac_tfm = crypto_alloc_ahash("hmac(sha1)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hmac_tfm)) { + retval = -PTR_ERR(hmac_tfm); + pr_err("failed to load transform for hmac(sha1): %d\n", retval); + goto fail; + } + + if ((retval = alloc_chrdev_region(&caphash_devid, 0, 1, DEVICE_CAPHASH))) + goto fail; + + if ((retval = alloc_chrdev_region(&capuse_devid, 0, 1, DEVICE_CAPUSE))) + goto fail; + + cdev_init(&caphash_dev, &caphash_fops); + caphash_dev.owner = THIS_MODULE; + if ((retval = cdev_add(&caphash_dev, caphash_devid, 1))) + pr_err("failed adding " DEVICE_CAPHASH ": %d\n", retval); + + cdev_init(&capuse_dev, &capuse_fops); + capuse_dev.owner = THIS_MODULE; + if ((retval = cdev_add(&capuse_dev, capuse_devid, 1))) + pr_err("failed adding " DEVICE_CAPUSE ": %d\n", retval); + + return 0; + +fail: + _cleanup_module(); + return retval; +} + +MODULE_AUTHOR("Enrico Weigelt, metux IT consult "); +MODULE_LICENSE("GPL"); + +module_init(_init_module); +module_exit(_cleanup_module); -- 2.11.0