Re: [PATCH v6 2/2] misc: open-dice: Add driver to expose DICE data to userspace

From: Wedson Almeida Filho
Date: Tue Jan 11 2022 - 18:32:54 EST


Hey David,

Following up here on v6.

On Tue, Jan 04, 2022 at 10:06:45AM +0000, David Brazdil wrote:
> Open Profile for DICE is an open protocol for measured boot compatible
> with the Trusted Computing Group's Device Identifier Composition
> Engine (DICE) specification. The generated Compound Device Identifier
> (CDI) certificates represent the hardware/software combination measured
> by DICE, and can be used for remote attestation and sealing.
>
> Add a driver that exposes reserved memory regions populated by firmware
> with DICE CDIs and exposes them to userspace via a character device.
>
> Userspace obtains the memory region's size from read() and calls mmap()
> to create a mapping of the memory region in its address space. The
> mapping is not allowed to be write+shared, giving userspace a guarantee
> that the data were not overwritten by another process.
>
> Userspace can also call write(), which triggers a wipe of the DICE data
> by the driver. Because both the kernel and userspace mappings use
> write-combine semantics, all clients observe the memory as zeroed after
> the syscall has returned.
>
> Acked-by: Rob Herring <robh@xxxxxxxxxx>
> Cc: Andrew Scull <ascull@xxxxxxxxxx>
> Cc: Will Deacon <will@xxxxxxxxxx>
> Signed-off-by: David Brazdil <dbrazdil@xxxxxxxxxx>
> ---
> drivers/misc/Kconfig | 12 +++
> drivers/misc/Makefile | 1 +
> drivers/misc/open-dice.c | 188 +++++++++++++++++++++++++++++++++++++++
> drivers/of/platform.c | 1 +
> 4 files changed, 202 insertions(+)
> create mode 100644 drivers/misc/open-dice.c
>
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 0f5a49fc7c9e..a2b26426efba 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -470,6 +470,18 @@ config HISI_HIKEY_USB
> switching between the dual-role USB-C port and the USB-A host ports
> using only one USB controller.
>
> +config OPEN_DICE
> + tristate "Open Profile for DICE driver"
> + depends on OF_RESERVED_MEM
> + help
> + This driver exposes a DICE reserved memory region to userspace via
> + a character device. The memory region contains Compound Device
> + Identifiers (CDIs) generated by firmware as an output of DICE
> + measured boot flow. Userspace can use CDIs for remote attestation
> + and sealing.
> +
> + If unsure, say N.
> +
> source "drivers/misc/c2port/Kconfig"
> source "drivers/misc/eeprom/Kconfig"
> source "drivers/misc/cb710/Kconfig"
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index a086197af544..70e800e9127f 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -59,3 +59,4 @@ obj-$(CONFIG_UACCE) += uacce/
> obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
> obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o
> obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o
> +obj-$(CONFIG_OPEN_DICE) += open-dice.o
> diff --git a/drivers/misc/open-dice.c b/drivers/misc/open-dice.c
> new file mode 100644
> index 000000000000..f1819f951173
> --- /dev/null
> +++ b/drivers/misc/open-dice.c
> @@ -0,0 +1,188 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2021 - Google LLC
> + * Author: David Brazdil <dbrazdil@xxxxxxxxxx>
> + *
> + * Driver for Open Profile for DICE.
> + *
> + * This driver takes ownership of a reserved memory region containing data
> + * generated by the Open Profile for DICE measured boot protocol. The memory
> + * contents are not interpreted by the kernel but can be mapped into a userspace
> + * process via a misc device. Userspace can also request a wipe of the memory.
> + *
> + * Userspace can access the data with (w/o error handling):
> + *
> + * fd = open("/dev/open-dice0", O_RDWR);
> + * read(fd, &size, sizeof(unsigned long));
> + * data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
> + * write(fd, NULL, 0); // wipe
> + * close(fd);
> + */
> +
> +#include <linux/io.h>
> +#include <linux/miscdevice.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/of_reserved_mem.h>
> +#include <linux/platform_device.h>
> +
> +#define DRIVER_NAME "open-dice"
> +
> +struct open_dice_drvdata {
> + spinlock_t lock;
> + char name[16];
> + struct reserved_mem *rmem;
> + struct miscdevice misc;
> +};
> +
> +static inline struct open_dice_drvdata *to_open_dice_drvdata(struct file *filp)
> +{
> + return container_of(filp->private_data, struct open_dice_drvdata, misc);
> +}
> +
> +static int open_dice_wipe(struct open_dice_drvdata *drvdata)
> +{
> + void *kaddr;
> +
> + spin_lock(&drvdata->lock);
> + kaddr = devm_memremap(drvdata->misc.this_device, drvdata->rmem->base,
> + drvdata->rmem->size, MEMREMAP_WC);

What's the plan here if devm_memremap sleeps while you're holding the spinlock?

> + if (IS_ERR(kaddr)) {
> + spin_unlock(&drvdata->lock);
> + return PTR_ERR(kaddr);
> + }
> +
> + memset(kaddr, 0, drvdata->rmem->size);
> + devm_memunmap(drvdata->misc.this_device, kaddr);
> + spin_unlock(&drvdata->lock);
> + return 0;
> +}
> +
> +/*
> + * Copies the size of the reserved memory region to the user-provided buffer.
> + */
> +static ssize_t open_dice_read(struct file *filp, char __user *ptr, size_t len,
> + loff_t *off)
> +{
> + unsigned long val = to_open_dice_drvdata(filp)->rmem->size;
> +
> + return simple_read_from_buffer(ptr, len, off, &val, sizeof(val));
> +}
> +
> +/*
> + * Triggers a wipe of the reserved memory region. The user-provided pointer
> + * is never dereferenced.
> + */
> +static ssize_t open_dice_write(struct file *filp, const char __user *ptr,
> + size_t len, loff_t *off)
> +{
> + if (open_dice_wipe(to_open_dice_drvdata(filp)))
> + return -EIO;
> +
> + /* Consume the input buffer. */
> + return len;
> +}
> +
> +/*
> + * Creates a mapping of the reserved memory region in user address space.
> + */
> +static int open_dice_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> + struct open_dice_drvdata *drvdata = to_open_dice_drvdata(filp);
> +
> + /* Do not allow userspace to modify the underlying data. */
> + if ((vma->vm_flags & VM_WRITE) && (vma->vm_flags & VM_SHARED))
> + return -EPERM;
> +
> + /* Create write-combine mapping so all clients observe a wipe. */
> + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
> + vma->vm_flags |= VM_DONTCOPY | VM_DONTDUMP;

I think we need to clear VM_MAYWRITE here too, otherwise what prevents a user
(that opened the file for write as well) from later adding VM_WRITE to the vma
by calling mprotect?

> + return vm_iomap_memory(vma, drvdata->rmem->base, drvdata->rmem->size);
> +}
> +
> +static const struct file_operations open_dice_fops = {
> + .owner = THIS_MODULE,
> + .read = open_dice_read,
> + .write = open_dice_write,
> + .mmap = open_dice_mmap,
> +};
> +
> +static int __init open_dice_probe(struct platform_device *pdev)
> +{
> + static unsigned int dev_idx;
> + struct device *dev = &pdev->dev;
> + struct reserved_mem *rmem;
> + struct open_dice_drvdata *drvdata;
> + int ret;
> +
> + rmem = of_reserved_mem_lookup(dev->of_node);
> + if (!rmem) {
> + dev_err(dev, "failed to lookup reserved memory\n");
> + return -EINVAL;
> + }
> +
> + if (!rmem->size || (rmem->size > ULONG_MAX)) {
> + dev_err(dev, "invalid memory region size\n");
> + return -EINVAL;
> + }
> +
> + if (!PAGE_ALIGNED(rmem->base) || !PAGE_ALIGNED(rmem->size)) {
> + dev_err(dev, "memory region must be page-aligned\n");
> + return -EINVAL;
> + }
> +
> + drvdata = devm_kmalloc(dev, sizeof(*drvdata), GFP_KERNEL);
> + if (!drvdata)
> + return -ENOMEM;
> +
> + *drvdata = (struct open_dice_drvdata){
> + .lock = __SPIN_LOCK_UNLOCKED(drvdata->lock),
> + .rmem = rmem,
> + .misc = (struct miscdevice){
> + .parent = dev,
> + .name = drvdata->name,
> + .minor = MISC_DYNAMIC_MINOR,
> + .fops = &open_dice_fops,
> + .mode = 0600,
> + },
> + };
> +
> + /* Index overflow check not needed, misc_register() will fail. */
> + snprintf(drvdata->name, sizeof(drvdata->name), DRIVER_NAME"%u", dev_idx++);
> +
> + ret = misc_register(&drvdata->misc);
> + if (ret) {
> + dev_err(dev, "failed to register misc device '%s': %d\n",
> + drvdata->name, ret);
> + return ret;
> + }
> +
> + platform_set_drvdata(pdev, drvdata);
> + return 0;
> +}
> +
> +static int open_dice_remove(struct platform_device *pdev)
> +{

As we discussed before, this should never be called, right? If it does, users
can trigger UAF. Should we call BUG or WARN here?

> + struct open_dice_drvdata *drvdata = platform_get_drvdata(pdev);
> +
> + misc_deregister(&drvdata->misc);
> + return 0;
> +}
> +
> +static const struct of_device_id open_dice_of_match[] = {
> + { .compatible = "google,open-dice" },
> + {},
> +};
> +
> +static struct platform_driver open_dice_driver = {
> + .remove = open_dice_remove,
> + .driver = {
> + .name = DRIVER_NAME,
> + .of_match_table = open_dice_of_match,
> + },
> +};
> +
> +module_platform_driver_probe(open_dice_driver, open_dice_probe);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("David Brazdil <dbrazdil@xxxxxxxxxx>");
> diff --git a/drivers/of/platform.c b/drivers/of/platform.c
> index b3faf89744aa..d659ed0be342 100644
> --- a/drivers/of/platform.c
> +++ b/drivers/of/platform.c
> @@ -514,6 +514,7 @@ static const struct of_device_id reserved_mem_matches[] = {
> { .compatible = "qcom,smem" },
> { .compatible = "ramoops" },
> { .compatible = "nvmem-rmem" },
> + { .compatible = "google,open-dice" },
> {}
> };
>
> --
> 2.34.1.448.ga2b2bfdf31-goog
>