Received: by 10.223.185.116 with SMTP id b49csp2190582wrg; Sat, 17 Feb 2018 14:40:01 -0800 (PST) X-Google-Smtp-Source: AH8x225Ygv91p2z+H8dc1hi1farNlHGP3+32QOLe/SMNPgZCsZk2+Gao758o8FKj3r09pkQItkqk X-Received: by 10.99.178.2 with SMTP id x2mr8718072pge.98.1518907201556; Sat, 17 Feb 2018 14:40:01 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1518907201; cv=none; d=google.com; s=arc-20160816; b=hsuEQJnG3R5P1VgoxWxv/vSKV4yy3Qjw9DoJOrvCFvGoiCAFKJmxTwZoNCdjb9kPUh R8iRv3Ngky2f1hTverCwbACtepZyx0zRTYJuZOK415W5xbAxj/nvKLiiCOku6efliTB3 drGA1Hi8D2vMzb4diew1P/eQhp3NLx3Z+5VFt5TzbOZyuWZKSf/C8ItmD3pVeshRNCQ+ q7Gtz+97S5gwHH8VZ+XNsg+7Gpb0zK/hxfVKT/T+oea2zQFlzLTGCKwGLOblQn+Q6+sV 0gJ7gRIvzazdh1RcUy72EcXX+BNKl+qQQjNYKamL1bfnVLxKtglVAp40MI2Y7M11I/H5 S2+A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:cc:to:subject:message-id:date:from :references:in-reply-to:mime-version:dkim-signature :arc-authentication-results; bh=EuFCHzCixfaZ1J0LPPAdSOABLOgM4Etx5tWiNZhxKVo=; b=z2wUQtwek7lXGH6KPjPlZbp7ZlRkOWEqaZAGUuaMMD0ZNMykV+60AaXkZ9kUWoq6xX 3DUfyDhdNcouCt/dmsNLJjKf6qPURhCt23FeHQ8z+v+JzcCcSIQ1AjQaD+bDn/uw5ae2 PHGunwgyfDVZv55O3C3L4Jgo5EN/DBt1PhXGCqfb9H7JCceF2/6nPkSTbUepZRVMpfqd pqtRRT+z/Bf0NixLlpDdeFCD9PgrKFVMcdryxAHx630YlwTg392HakE3N5jcPO4UVObW 5/Rush5G8ZmJNpdVsgKC2mYRGiaOt0wn4+ZNeUd9Cu5jX6jn4OfHImmLbp+bW0/sIf25 KSsA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=Kpu5V9eo; 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; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id y67si2840871pgb.728.2018.02.17.14.39.47; Sat, 17 Feb 2018 14:40:01 -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; dkim=pass header.i=@gmail.com header.s=20161025 header.b=Kpu5V9eo; 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; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751278AbeBQWLd (ORCPT + 99 others); Sat, 17 Feb 2018 17:11:33 -0500 Received: from mail-wm0-f66.google.com ([74.125.82.66]:35118 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751213AbeBQWLb (ORCPT ); Sat, 17 Feb 2018 17:11:31 -0500 Received: by mail-wm0-f66.google.com with SMTP id x21so8859990wmh.0 for ; Sat, 17 Feb 2018 14:11:31 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc; bh=EuFCHzCixfaZ1J0LPPAdSOABLOgM4Etx5tWiNZhxKVo=; b=Kpu5V9eorctxPoJAM8QKt1rGHnMWj5l1S3q3exnXAhoaV5kXWQFVZejv6q8zjzsI8k Zs9RRATDy8V3lFfxg9UYx5JBx28zPB2c31xUh3IpP0+nYbkRlXoSJQDLucMzPgBgZXQP 6m4C73ryk8/cvVKKvCMp+3BkRovE2/DcbjkcXH0GcrtZ6xD4/AgHtav70Is74ZX7aBol VacO1M3+MWpohbdkWGsEajRR29qDEy4YKqLorCIG2yoM5Dmg0JERuECWS/uOhKX0Ka09 PiNnTYhwVcD926jp9bjwvyaGT6PJ3lQux0eYYanNpeoTaWaeYofL/vkQrpGaiKJW22ww aapQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to:cc; bh=EuFCHzCixfaZ1J0LPPAdSOABLOgM4Etx5tWiNZhxKVo=; b=PWjFkRv8xbAcpXPXvy+7wTlnmVUC3JzacuaRdw0i7JBliW0JvYczTip6IHUWtZN3DR LHdO8GYnAlyllKjllLbYvh2+hXyIttOeweXKf2mVNCx9DGaVbiIXZBA558nbmXI5tZ88 VimTIf8ClSFlcOuIO/qKh33IvMeCZBo8HQvuIagHBBSG2fG5HhAsZLRWnFWytMf8tNXy T5JlhJHn5RrFGC3JgPNpoZE991Yr/0l/B8q45zXbtKFhn42PLtALF6Z+mzaq14TdQki4 eZSG4Kw0TO2NQO455S4kjgX2YTWUkGf5M+ChM3PcqqodBEGa+8PrVP9W1Byda2ocIJ1a tDIQ== X-Gm-Message-State: APf1xPCd/QkbVoVbnzEAmukDzGjIwrwfwOpr7JSUgVI7qHYV3BQqtF9G 8hkHCkCbWlR0q78pkLGqjLViQ5MYyFLHo+HqDso= X-Received: by 10.28.0.88 with SMTP id 85mr6335419wma.63.1518905490159; Sat, 17 Feb 2018 14:11:30 -0800 (PST) MIME-Version: 1.0 Received: by 10.223.135.44 with HTTP; Sat, 17 Feb 2018 14:11:29 -0800 (PST) In-Reply-To: <20180211215028.16210-2-metux@gmx.de> References: <40d4c871-a16a-7b8f-2d4a-422a5a490693@infradead.org> <20180211215028.16210-1-metux@gmx.de> <20180211215028.16210-2-metux@gmx.de> From: Richard Weinberger Date: Sat, 17 Feb 2018 23:11:29 +0100 Message-ID: Subject: Re: [PATCH] p9caps: add Plan9 capability devices To: "Enrico Weigelt, metux IT consult" Cc: LKML Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Sun, Feb 11, 2018 at 10:50 PM, Enrico Weigelt, metux IT consult wrote: > 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 Please see some nit-picks below. I'm still reading the plan9 docs to fully get the big picture. > --- > 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 vars are already 0, no need to initialize them. > +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"); So, evil guy opens the device but does not close it, therefore the whole service is blocked in a namespace? In general, I think we should open that device in kernel_init_freeable() and hand over the fd to init/systemd. As the plan9 docs state "The write-only file /dev/caphash can be opened only by the host owner, and only once. Factotum opens this file immediately after booting." > + 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(); Why not obtaining the user namespace from the open file? That way one can close a caphash file hande she never opened. Think of open, followed by nsenter, ... > + 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); list_for_each_entry. > + 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()); Again, why not taking the user namespace from the file handle? > + 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))) count is unbound, please apply a limit check. > + return -ENOMEM; > + > + if (copy_from_user(cmdbuf, buf, count)) { > + retval = -EFAULT; > + goto out_free; > + } > + > + { > + char *walk = cmdbuf; cmdbuf is toxic, make sure it is at least nul-terminated. > + 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_for_each_entry_safe? > + 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) IS_ERR()? Otherwise you free a error value, if crypto_alloc_ahash() fails. > + 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 > -- Thanks, //richard