Open Profile for DICE is a secret derivation protocol used by some
Android devices. The firmware/bootloader generates the secrets and hands
them over to Linux in a reserved memory region.
See https://pigweed.googlesource.com/open-dice for more details.
This patchset adds the corresponding DeviceTree bindings and a driver
that takes ownership of the memory region and exposes it to userspace
via a misc device.
The patches are based on top of v5.16-rc4 and can also be found here:
https://android-kvm.googlesource.com/linux topic/dice_v2
Changes since v1:
* converted to miscdevice
* all mappings now write-combine to simplify semantics
* removed atomic state, any attempt at exclusive access
* simplified wipe, applied on ioctl, not on release
* fixed ioctl return value
David Brazdil (2):
dt-bindings: firmware: Add Open Profile for DICE
misc: dice: Add driver to forward secrets to userspace
.../devicetree/bindings/firmware/dice.yaml | 51 ++++++
.../userspace-api/ioctl/ioctl-number.rst | 1 +
drivers/misc/Kconfig | 8 +
drivers/misc/Makefile | 1 +
drivers/misc/dice.c | 161 ++++++++++++++++++
include/uapi/linux/dice.h | 14 ++
6 files changed, 236 insertions(+)
create mode 100644 Documentation/devicetree/bindings/firmware/dice.yaml
create mode 100644 drivers/misc/dice.c
create mode 100644 include/uapi/linux/dice.h
--
2.34.1.400.ga245620fadb-goog
Add DeviceTree bindings for Open Profile for DICE. DICE is a protocol
for deriving Compound Device Identifier (CDI) certificates. These are
generated by the firmware/bootloader and stored in memory. Location of
the buffer is described as a reserved memory region referenced by
a compatible DICE device node.
See https://pigweed.googlesource.com/open-dice
Signed-off-by: David Brazdil <[email protected]>
---
.../devicetree/bindings/firmware/dice.yaml | 51 +++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 Documentation/devicetree/bindings/firmware/dice.yaml
diff --git a/Documentation/devicetree/bindings/firmware/dice.yaml b/Documentation/devicetree/bindings/firmware/dice.yaml
new file mode 100644
index 000000000000..c0726109e73d
--- /dev/null
+++ b/Documentation/devicetree/bindings/firmware/dice.yaml
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/firmware/dice.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Open Profile for DICE Device Tree Bindings
+
+description: |
+ This binding represents a reserved memory region containing secrets derived
+ derived following the Open Profile for DICE.
+
+ See https://pigweed.googlesource.com/open-dice/
+
+maintainers:
+ - David Brazdil <[email protected]>
+
+properties:
+ compatible:
+ enum:
+ - google,dice
+
+ memory-region:
+ maxItems: 1
+ description: |
+ phandle to the reserved memory node to be associated with the device
+ The reserved memory node should be defined as per the bindings,
+ Documentation/devicetree/bindings/reserved-memory/reserved-memory.yaml
+
+required:
+ - compatible
+ - memory-region
+
+additionalProperties: false
+
+examples:
+ - |
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <1>;
+
+ dice_reserved: dice@12340000 {
+ reg = <0x00 0x12340000 0x2000>;
+ no-map;
+ };
+ };
+
+ dice {
+ compatible = "google,dice";
+ memory-region = <&dice_reserved>;
+ };
--
2.34.1.400.ga245620fadb-goog
Open Profile for DICE is a protocol for deriving unique secrets at boot
used by some Android devices. The firmware/bootloader hands over secrets
in a reserved memory region, which this driver takes ownership of and
exposes it to userspace via a misc device.
Userspace obtains the region's size using an ioctl and mmaps the memory
to its address space. This mapping cannot be write+shared, giving
userspace a guarantee that the secrets have not been overwritten by
another process.
Userspace can also issue an ioctl requesting that the memory be wiped by
the driver. Because both the kernel and userspace mappings use
write-combine semantics, all clients will observe the memory as zeroed
after the ioctl has returned.
Cc: Andrew Scull <[email protected]>
Cc: Will Deacon <[email protected]>
Signed-off-by: David Brazdil <[email protected]>
---
.../userspace-api/ioctl/ioctl-number.rst | 1 +
drivers/misc/Kconfig | 8 +
drivers/misc/Makefile | 1 +
drivers/misc/dice.c | 161 ++++++++++++++++++
include/uapi/linux/dice.h | 14 ++
5 files changed, 185 insertions(+)
create mode 100644 drivers/misc/dice.c
create mode 100644 include/uapi/linux/dice.h
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index cfe6cccf0f44..4b8bee2ffd1e 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -341,6 +341,7 @@ Code Seq# Include File Comments
0xAE 40-FF linux/kvm.h Kernel-based Virtual Machine
<mailto:[email protected]>
0xAE 20-3F linux/nitro_enclaves.h Nitro Enclaves
+0xAE 40-5F uapi/linux/dice.h Open Profile for DICE driver
0xAF 00-1F linux/fsl_hypervisor.h Freescale hypervisor
0xB0 all RATIO devices in development:
<mailto:[email protected]>
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 0f5a49fc7c9e..7165f4b6c41b 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -470,6 +470,14 @@ config HISI_HIKEY_USB
switching between the dual-role USB-C port and the USB-A host ports
using only one USB controller.
+config DICE
+ tristate "Open Profile for DICE driver"
+ depends on OF_RESERVED_MEM
+ help
+ This driver allows to ownership of a reserved memory region
+ containing DICE secrets and expose them to userspace via
+ a character device.
+
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..f73c6bb23ccd 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_DICE) += dice.o
diff --git a/drivers/misc/dice.c b/drivers/misc/dice.c
new file mode 100644
index 000000000000..06f3754feb71
--- /dev/null
+++ b/drivers/misc/dice.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 - Google LLC
+ * Author: David Brazdil <[email protected]>
+ *
+ * Driver for Open Profile for DICE.
+ *
+ * This driver takes ownership of a reserved memory region containing secrets
+ * derived following the Open Profile for DICE. The contents of the memory
+ * region are not interpreted by the kernel but can be mapped into a userspace
+ * process via a misc device. The memory region can also be wiped, removing
+ * the secrets from memory.
+ *
+ * Userspace can access the data by (w/o error handling):
+ *
+ * int fd = open("/dev/dice", O_RDONLY | O_CLOEXEC);
+ * size_t size = ioctl(fd, DICE_GET_SIZE);
+ * void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ * ioctl(fd, DICE_WIPE);
+ * close(fd);
+ */
+
+#include <linux/dice.h>
+#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>
+
+static int dice_mmap(struct file *filp, struct vm_area_struct *vma);
+static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+
+static const struct file_operations dice_fops = {
+ .mmap = dice_mmap,
+ .unlocked_ioctl = dice_ioctl,
+};
+
+static struct miscdevice dice_misc = {
+ .name = "dice",
+ .minor = MISC_DYNAMIC_MINOR,
+ .fops = &dice_fops,
+ .mode = 0400,
+};
+
+static struct reserved_mem *dice_rmem;
+static DEFINE_SPINLOCK(dice_lock);
+
+static int dice_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ /* 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;
+ return vm_iomap_memory(vma, dice_rmem->base, dice_rmem->size);
+}
+
+static int dice_wipe(void)
+{
+ void *kaddr;
+
+ spin_lock(&dice_lock);
+ kaddr = devm_memremap(dice_misc.this_device, dice_rmem->base,
+ dice_rmem->size, MEMREMAP_WC);
+ if (IS_ERR(kaddr)) {
+ spin_unlock(&dice_lock);
+ return PTR_ERR(kaddr);
+ }
+
+ memzero_explicit(kaddr, dice_rmem->size);
+ devm_memunmap(dice_misc.this_device, kaddr);
+ spin_unlock(&dice_lock);
+ return 0;
+}
+
+static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case DICE_GET_SIZE:
+ /* Checked against INT_MAX in dice_probe(). */
+ return dice_rmem->size;
+ case DICE_WIPE:
+ return dice_wipe();
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static int __init dice_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *rmem_np;
+ struct reserved_mem *rmem;
+ int ret;
+
+ if (dice_rmem) {
+ dev_err(dev, "only one instance of device allowed\n");
+ return -EBUSY;
+ }
+
+ rmem_np = of_parse_phandle(dev->of_node, "memory-region", 0);
+ if (!rmem_np) {
+ dev_err(dev, "missing 'memory-region' property\n");
+ return -EINVAL;
+ }
+
+ rmem = of_reserved_mem_lookup(rmem_np);
+ of_node_put(rmem_np);
+ if (!rmem) {
+ dev_err(dev, "failed to lookup reserved memory\n");
+ return -EINVAL;
+ }
+
+ if (!PAGE_ALIGNED(rmem->base) || !PAGE_ALIGNED(rmem->size)) {
+ dev_err(dev, "memory region must be page-aligned\n");
+ return -EINVAL;
+ }
+
+ if (!rmem->size || (rmem->size > INT_MAX)) {
+ dev_err(dev, "invalid memory region size\n");
+ return -EINVAL;
+ }
+
+ dice_misc.parent = dev;
+ ret = misc_register(&dice_misc);
+ if (ret) {
+ dev_err(dev, "failed to register misc device: %d\n", ret);
+ return ret;
+ }
+
+ dice_rmem = rmem;
+ return 0;
+}
+
+static int dice_remove(struct platform_device *pdev)
+{
+ misc_deregister(&dice_misc);
+ dice_rmem = NULL;
+ return 0;
+}
+
+static const struct of_device_id dice_of_match[] = {
+ { .compatible = "google,dice" },
+ {},
+};
+
+static struct platform_driver dice_driver = {
+ .remove = dice_remove,
+ .driver = {
+ .name = "dice",
+ .of_match_table = dice_of_match,
+ },
+};
+
+module_platform_driver_probe(dice_driver, dice_probe);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("David Brazdil <[email protected]>");
diff --git a/include/uapi/linux/dice.h b/include/uapi/linux/dice.h
new file mode 100644
index 000000000000..68f7304408ed
--- /dev/null
+++ b/include/uapi/linux/dice.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * IOCTLs for Open Profile for DICE character device
+ */
+
+#ifndef _UAPI_DICE_H_
+#define _UAPI_DICE_H_
+
+#include <linux/ioctl.h>
+
+#define DICE_GET_SIZE _IO(0xAE, 0x40)
+#define DICE_WIPE _IO(0xAE, 0x41)
+
+#endif
--
2.34.1.400.ga245620fadb-goog
On Thu, Dec 09, 2021 at 03:11:23PM +0000, David Brazdil wrote:
> Open Profile for DICE is a protocol for deriving unique secrets at boot
> used by some Android devices. The firmware/bootloader hands over secrets
> in a reserved memory region, which this driver takes ownership of and
> exposes it to userspace via a misc device.
>
> Userspace obtains the region's size using an ioctl and mmaps the memory
> to its address space. This mapping cannot be write+shared, giving
> userspace a guarantee that the secrets have not been overwritten by
> another process.
>
> Userspace can also issue an ioctl requesting that the memory be wiped by
> the driver. Because both the kernel and userspace mappings use
> write-combine semantics, all clients will observe the memory as zeroed
> after the ioctl has returned.
>
> Cc: Andrew Scull <[email protected]>
> Cc: Will Deacon <[email protected]>
> Signed-off-by: David Brazdil <[email protected]>
> ---
> .../userspace-api/ioctl/ioctl-number.rst | 1 +
> drivers/misc/Kconfig | 8 +
> drivers/misc/Makefile | 1 +
> drivers/misc/dice.c | 161 ++++++++++++++++++
Nice, almost 100 lines shorter than before!
Much better, thanks for the changes, but it can be made simpler, see
comments below:
> include/uapi/linux/dice.h | 14 ++
> 5 files changed, 185 insertions(+)
> create mode 100644 drivers/misc/dice.c
> create mode 100644 include/uapi/linux/dice.h
>
> diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
> index cfe6cccf0f44..4b8bee2ffd1e 100644
> --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
> +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
> @@ -341,6 +341,7 @@ Code Seq# Include File Comments
> 0xAE 40-FF linux/kvm.h Kernel-based Virtual Machine
> <mailto:[email protected]>
> 0xAE 20-3F linux/nitro_enclaves.h Nitro Enclaves
> +0xAE 40-5F uapi/linux/dice.h Open Profile for DICE driver
Why the huge range? You are only really using 40 and 41. Stick to that
please.
> 0xAF 00-1F linux/fsl_hypervisor.h Freescale hypervisor
> 0xB0 all RATIO devices in development:
> <mailto:[email protected]>
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 0f5a49fc7c9e..7165f4b6c41b 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -470,6 +470,14 @@ config HISI_HIKEY_USB
> switching between the dual-role USB-C port and the USB-A host ports
> using only one USB controller.
>
> +config DICE
> + tristate "Open Profile for DICE driver"
> + depends on OF_RESERVED_MEM
> + help
> + This driver allows to ownership of a reserved memory region
> + containing DICE secrets and expose them to userspace via
> + a character device.
What is the module name, please add that here.
And "dice" is a very generic name. I don't mind, but if you want to
name it a bit more specific, that might be better.
> +
> 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..f73c6bb23ccd 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_DICE) += dice.o
> diff --git a/drivers/misc/dice.c b/drivers/misc/dice.c
> new file mode 100644
> index 000000000000..06f3754feb71
> --- /dev/null
> +++ b/drivers/misc/dice.c
> @@ -0,0 +1,161 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2021 - Google LLC
> + * Author: David Brazdil <[email protected]>
> + *
> + * Driver for Open Profile for DICE.
> + *
> + * This driver takes ownership of a reserved memory region containing secrets
> + * derived following the Open Profile for DICE. The contents of the memory
> + * region are not interpreted by the kernel but can be mapped into a userspace
> + * process via a misc device. The memory region can also be wiped, removing
> + * the secrets from memory.
> + *
> + * Userspace can access the data by (w/o error handling):
> + *
> + * int fd = open("/dev/dice", O_RDONLY | O_CLOEXEC);
> + * size_t size = ioctl(fd, DICE_GET_SIZE);
> + * void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
> + * ioctl(fd, DICE_WIPE);
> + * close(fd);
> + */
> +
> +#include <linux/dice.h>
> +#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>
> +
> +static int dice_mmap(struct file *filp, struct vm_area_struct *vma);
> +static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
> +
> +static const struct file_operations dice_fops = {
> + .mmap = dice_mmap,
> + .unlocked_ioctl = dice_ioctl,
> +};
> +
> +static struct miscdevice dice_misc = {
> + .name = "dice",
> + .minor = MISC_DYNAMIC_MINOR,
> + .fops = &dice_fops,
> + .mode = 0400,
> +};
> +
> +static struct reserved_mem *dice_rmem;
> +static DEFINE_SPINLOCK(dice_lock);
These should be per-device, not global, right? Please put them in a
local structure that lives off of the platform device. Then everything
is dynamic and nothing is static making it all much simpler again.
Do that and you can get rid of the pre-definitions of dice_mmap() and
dice_ioctl() above as well.
> +
> +static int dice_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> + /* 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;
> + return vm_iomap_memory(vma, dice_rmem->base, dice_rmem->size);
> +}
> +
> +static int dice_wipe(void)
> +{
> + void *kaddr;
> +
> + spin_lock(&dice_lock);
> + kaddr = devm_memremap(dice_misc.this_device, dice_rmem->base,
> + dice_rmem->size, MEMREMAP_WC);
> + if (IS_ERR(kaddr)) {
> + spin_unlock(&dice_lock);
> + return PTR_ERR(kaddr);
> + }
> +
> + memzero_explicit(kaddr, dice_rmem->size);
> + devm_memunmap(dice_misc.this_device, kaddr);
Do you really need to call memzero_explicit()? This isn't "local"
memory, if the compiler "optimizes away" a normal call, it would be
_VERY_ broken.
> + spin_unlock(&dice_lock);
> + return 0;
> +}
> +
> +static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> +{
> + switch (cmd) {
> + case DICE_GET_SIZE:
> + /* Checked against INT_MAX in dice_probe(). */
> + return dice_rmem->size;
> + case DICE_WIPE:
> + return dice_wipe();
> + }
> +
> + return -ENOIOCTLCMD;
-ENOTTY please.
As you only have 2 ioctls, why not just use read/write for this? Write
would cause dice_wipe() to happen, and read would return the size in the
buffer provided. Then no ioctl is needed at all.
> +}
> +
> +static int __init dice_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *rmem_np;
> + struct reserved_mem *rmem;
> + int ret;
> +
> + if (dice_rmem) {
> + dev_err(dev, "only one instance of device allowed\n");
Why? That's a device-specific thing, not a driver-specific thing. Make
this all dynamic and you don't need to care about it at all.
Again, simplification :)
thanks,
greg k-h
Hi!
> > + memzero_explicit(kaddr, dice_rmem->size);
> > + devm_memunmap(dice_misc.this_device, kaddr);
>
> Do you really need to call memzero_explicit()? This isn't "local"
> memory, if the compiler "optimizes away" a normal call, it would be
> _VERY_ broken.
For clearing secrets, I believe memzero_explicit is nice
documentation.
Best regards,
Pavel
--
http://www.livejournal.com/~pavelmachek
Hi!
> +config DICE
> + tristate "Open Profile for DICE driver"
> + depends on OF_RESERVED_MEM
> + help
> + This driver allows to ownership of a reserved memory region
> + containing DICE secrets and expose them to userspace via
> + a character device.
> +
Explaining what DICE is (and what Open Profile is) would be useful.
I see it is for some kind of DRM? Why is in non-evil and why do we
want it in Linux?
Pavel
--
http://www.livejournal.com/~pavelmachek
On Thu, Dec 09, 2021 at 08:38:57PM +0100, Pavel Machek wrote:
> Hi!
>
> > > + memzero_explicit(kaddr, dice_rmem->size);
> > > + devm_memunmap(dice_misc.this_device, kaddr);
> >
> > Do you really need to call memzero_explicit()? This isn't "local"
> > memory, if the compiler "optimizes away" a normal call, it would be
> > _VERY_ broken.
>
> For clearing secrets, I believe memzero_explicit is nice
> documentation.
Only if it's really needed please.
On Thu, Dec 09, 2021 at 04:31:53PM +0100, Greg Kroah-Hartman wrote:
> On Thu, Dec 09, 2021 at 03:11:23PM +0000, David Brazdil wrote:
> > Open Profile for DICE is a protocol for deriving unique secrets at boot
> > used by some Android devices. The firmware/bootloader hands over secrets
> > in a reserved memory region, which this driver takes ownership of and
> > exposes it to userspace via a misc device.
> >
> > Userspace obtains the region's size using an ioctl and mmaps the memory
> > to its address space. This mapping cannot be write+shared, giving
> > userspace a guarantee that the secrets have not been overwritten by
> > another process.
> >
> > Userspace can also issue an ioctl requesting that the memory be wiped by
> > the driver. Because both the kernel and userspace mappings use
> > write-combine semantics, all clients will observe the memory as zeroed
> > after the ioctl has returned.
> >
> > Cc: Andrew Scull <[email protected]>
> > Cc: Will Deacon <[email protected]>
> > Signed-off-by: David Brazdil <[email protected]>
> > ---
> > .../userspace-api/ioctl/ioctl-number.rst | 1 +
> > drivers/misc/Kconfig | 8 +
> > drivers/misc/Makefile | 1 +
> > drivers/misc/dice.c | 161 ++++++++++++++++++
>
> Nice, almost 100 lines shorter than before!
>
> Much better, thanks for the changes, but it can be made simpler, see
> comments below:
Yep, thanks for all the tips!
>
> > include/uapi/linux/dice.h | 14 ++
> > 5 files changed, 185 insertions(+)
> > create mode 100644 drivers/misc/dice.c
> > create mode 100644 include/uapi/linux/dice.h
> >
> > diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
> > index cfe6cccf0f44..4b8bee2ffd1e 100644
> > --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
> > +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
> > @@ -341,6 +341,7 @@ Code Seq# Include File Comments
> > 0xAE 40-FF linux/kvm.h Kernel-based Virtual Machine
> > <mailto:[email protected]>
> > 0xAE 20-3F linux/nitro_enclaves.h Nitro Enclaves
> > +0xAE 40-5F uapi/linux/dice.h Open Profile for DICE driver
>
> Why the huge range? You are only really using 40 and 41. Stick to that
> please.
I understood the comments at the top of this file as encouraging devs to
err on the side of caution and be more liberal with the reservations in
case more ioctls are needed in the future. But I agree that it is highly
unlikely this driver will ever need 32 of them.
>
> > 0xAF 00-1F linux/fsl_hypervisor.h Freescale hypervisor
> > 0xB0 all RATIO devices in development:
> > <mailto:[email protected]>
> > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> > index 0f5a49fc7c9e..7165f4b6c41b 100644
> > --- a/drivers/misc/Kconfig
> > +++ b/drivers/misc/Kconfig
> > @@ -470,6 +470,14 @@ config HISI_HIKEY_USB
> > switching between the dual-role USB-C port and the USB-A host ports
> > using only one USB controller.
> >
> > +config DICE
> > + tristate "Open Profile for DICE driver"
> > + depends on OF_RESERVED_MEM
> > + help
> > + This driver allows to ownership of a reserved memory region
> > + containing DICE secrets and expose them to userspace via
> > + a character device.
>
> What is the module name, please add that here.
>
> And "dice" is a very generic name. I don't mind, but if you want to
> name it a bit more specific, that might be better.
Does "open-dice" sound good? I think that's the shorthand used on the
official website.
> > +
> > 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..f73c6bb23ccd 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_DICE) += dice.o
> > diff --git a/drivers/misc/dice.c b/drivers/misc/dice.c
> > new file mode 100644
> > index 000000000000..06f3754feb71
> > --- /dev/null
> > +++ b/drivers/misc/dice.c
> > @@ -0,0 +1,161 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2021 - Google LLC
> > + * Author: David Brazdil <[email protected]>
> > + *
> > + * Driver for Open Profile for DICE.
> > + *
> > + * This driver takes ownership of a reserved memory region containing secrets
> > + * derived following the Open Profile for DICE. The contents of the memory
> > + * region are not interpreted by the kernel but can be mapped into a userspace
> > + * process via a misc device. The memory region can also be wiped, removing
> > + * the secrets from memory.
> > + *
> > + * Userspace can access the data by (w/o error handling):
> > + *
> > + * int fd = open("/dev/dice", O_RDONLY | O_CLOEXEC);
> > + * size_t size = ioctl(fd, DICE_GET_SIZE);
> > + * void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
> > + * ioctl(fd, DICE_WIPE);
> > + * close(fd);
> > + */
> > +
> > +#include <linux/dice.h>
> > +#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>
> > +
> > +static int dice_mmap(struct file *filp, struct vm_area_struct *vma);
> > +static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
> > +
> > +static const struct file_operations dice_fops = {
> > + .mmap = dice_mmap,
> > + .unlocked_ioctl = dice_ioctl,
> > +};
> > +
> > +static struct miscdevice dice_misc = {
> > + .name = "dice",
> > + .minor = MISC_DYNAMIC_MINOR,
> > + .fops = &dice_fops,
> > + .mode = 0400,
> > +};
> > +
> > +static struct reserved_mem *dice_rmem;
> > +static DEFINE_SPINLOCK(dice_lock);
>
> These should be per-device, not global, right? Please put them in a
> local structure that lives off of the platform device. Then everything
> is dynamic and nothing is static making it all much simpler again.
>
> Do that and you can get rid of the pre-definitions of dice_mmap() and
> dice_ioctl() above as well.0
Ack
> > +
> > +static int dice_mmap(struct file *filp, struct vm_area_struct *vma)
> > +{
> > + /* 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;
> > + return vm_iomap_memory(vma, dice_rmem->base, dice_rmem->size);
> > +}
> > +
> > +static int dice_wipe(void)
> > +{
> > + void *kaddr;
> > +
> > + spin_lock(&dice_lock);
> > + kaddr = devm_memremap(dice_misc.this_device, dice_rmem->base,
> > + dice_rmem->size, MEMREMAP_WC);
> > + if (IS_ERR(kaddr)) {
> > + spin_unlock(&dice_lock);
> > + return PTR_ERR(kaddr);
> > + }
> > +
> > + memzero_explicit(kaddr, dice_rmem->size);
> > + devm_memunmap(dice_misc.this_device, kaddr);
>
> Do you really need to call memzero_explicit()? This isn't "local"
> memory, if the compiler "optimizes away" a normal call, it would be
> _VERY_ broken.
I agree that it's not needed here. Seemed in line with other code that
handles secrets but i'm happy to just use memset.
> > + spin_unlock(&dice_lock);
> > + return 0;
> > +}
> > +
> > +static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > +{
> > + switch (cmd) {
> > + case DICE_GET_SIZE:
> > + /* Checked against INT_MAX in dice_probe(). */
> > + return dice_rmem->size;
> > + case DICE_WIPE:
> > + return dice_wipe();
> > + }
> > +
> > + return -ENOIOCTLCMD;
>
> -ENOTTY please.
I have no personal attachment to ENOIOCTLCMD, but it is documented as
"no ioctl command" and converted to ENOTTY before returning to userspace.
That made me think this was the right thing to do.
> As you only have 2 ioctls, why not just use read/write for this? Write
> would cause dice_wipe() to happen, and read would return the size in the
> buffer provided. Then no ioctl is needed at all.
Fine by me but does feel like a bit of a hack. Is that a common pattern?
>
> > +}
> > +
> > +static int __init dice_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct device_node *rmem_np;
> > + struct reserved_mem *rmem;
> > + int ret;
> > +
> > + if (dice_rmem) {
> > + dev_err(dev, "only one instance of device allowed\n");
>
> Why? That's a device-specific thing, not a driver-specific thing. Make
> this all dynamic and you don't need to care about it at all.
>
> Again, simplification :)
>
> thanks,
>
> greg k-h
Hi Pavel,
On Thu, Dec 09, 2021 at 08:48:07PM +0100, Pavel Machek wrote:
> Hi!
>
> > +config DICE
> > + tristate "Open Profile for DICE driver"
> > + depends on OF_RESERVED_MEM
> > + help
> > + This driver allows to ownership of a reserved memory region
> > + containing DICE secrets and expose them to userspace via
> > + a character device.
> > +
>
> Explaining what DICE is (and what Open Profile is) would be useful.
Sure, I'll expand the description.
> I see it is for some kind of DRM? Why is in non-evil and why do we
> want it in Linux?
Best to think of this as an extension to verified boot where each boot
stage signs the hashes of the software it loaded. The certificate is
what's passed in this reserved memory region. It is used in the context
of confidential computing for remote attestation, and it is very similar
to the EFI-based approach from IBM for SEV:
https://lore.kernel.org/all/[email protected]/
There's a link to the project's documentation in the cover letter with
much more technical detail if you're interested.
-David
On Fri, Dec 10, 2021 at 11:16:06AM +0000, David Brazdil wrote:
> On Thu, Dec 09, 2021 at 04:31:53PM +0100, Greg Kroah-Hartman wrote:
> > What is the module name, please add that here.
> >
> > And "dice" is a very generic name. I don't mind, but if you want to
> > name it a bit more specific, that might be better.
> Does "open-dice" sound good? I think that's the shorthand used on the
> official website.
That might be better.
Naming is hard.
> > > +static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > +{
> > > + switch (cmd) {
> > > + case DICE_GET_SIZE:
> > > + /* Checked against INT_MAX in dice_probe(). */
> > > + return dice_rmem->size;
> > > + case DICE_WIPE:
> > > + return dice_wipe();
> > > + }
> > > +
> > > + return -ENOIOCTLCMD;
> >
> > -ENOTTY please.
> I have no personal attachment to ENOIOCTLCMD, but it is documented as
> "no ioctl command" and converted to ENOTTY before returning to userspace.
> That made me think this was the right thing to do.
ENOTTY is better please.
> > As you only have 2 ioctls, why not just use read/write for this? Write
> > would cause dice_wipe() to happen, and read would return the size in the
> > buffer provided. Then no ioctl is needed at all.
> Fine by me but does feel like a bit of a hack. Is that a common pattern?
ioctls are hacks too :)
read/write like this is fine to do, might make the code simpler, and
allow the code to be used by scripts easier. At the very least, wipe
can be done by any language instead of only those that allow ioctls.
thanks,
greg k-h
In your first email you also mentioned removing the check in dice_probe()
that only allows a single instance. On a second thought, I think it's
simpler to keep it there for now, even if the memory is dynamically
allocated, which I agree makes the code cleaner.
The reason being that if we allowed multiple instances, we'd also need
some static unique identifier that ties the cdev filename to the DT entry,
same as /dev/disk/by-uuid/. Just adding an index number to the misc
device nodename based on DT probe order sounds very fragile, and
anything more sophisticated sounds like too much trouble for something
we don't have a clear use case for right now.
On Fri, Dec 10, 2021 at 03:39:44PM +0100, Greg Kroah-Hartman wrote:
> On Fri, Dec 10, 2021 at 11:16:06AM +0000, David Brazdil wrote:
> > On Thu, Dec 09, 2021 at 04:31:53PM +0100, Greg Kroah-Hartman wrote:
> > > What is the module name, please add that here.
> > >
> > > And "dice" is a very generic name. I don't mind, but if you want to
> > > name it a bit more specific, that might be better.
> > Does "open-dice" sound good? I think that's the shorthand used on the
> > official website.
>
> That might be better.
>
> Naming is hard.
>
> > > > +static long dice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> > > > +{
> > > > + switch (cmd) {
> > > > + case DICE_GET_SIZE:
> > > > + /* Checked against INT_MAX in dice_probe(). */
> > > > + return dice_rmem->size;
> > > > + case DICE_WIPE:
> > > > + return dice_wipe();
> > > > + }
> > > > +
> > > > + return -ENOIOCTLCMD;
> > >
> > > -ENOTTY please.
> > I have no personal attachment to ENOIOCTLCMD, but it is documented as
> > "no ioctl command" and converted to ENOTTY before returning to userspace.
> > That made me think this was the right thing to do.
>
> ENOTTY is better please.
>
>
> > > As you only have 2 ioctls, why not just use read/write for this? Write
> > > would cause dice_wipe() to happen, and read would return the size in the
> > > buffer provided. Then no ioctl is needed at all.
> > Fine by me but does feel like a bit of a hack. Is that a common pattern?
>
> ioctls are hacks too :)
>
> read/write like this is fine to do, might make the code simpler, and
> allow the code to be used by scripts easier. At the very least, wipe
> can be done by any language instead of only those that allow ioctls.
Alright, read/write it is. And that gets rid of ioctls altogether.
-David
On Fri, Dec 10, 2021 at 03:48:05PM +0000, David Brazdil wrote:
> In your first email you also mentioned removing the check in dice_probe()
> that only allows a single instance. On a second thought, I think it's
> simpler to keep it there for now, even if the memory is dynamically
> allocated, which I agree makes the code cleaner.
I don't remember what check you are talking about at all, sorry.
Remember some of us review hundreds of patches each week :(
> The reason being that if we allowed multiple instances, we'd also need
> some static unique identifier that ties the cdev filename to the DT entry,
> same as /dev/disk/by-uuid/. Just adding an index number to the misc
> device nodename based on DT probe order sounds very fragile, and
> anything more sophisticated sounds like too much trouble for something
> we don't have a clear use case for right now.
Just add a number to the device node name like every other device in the
system has. Nothing new or special here, right?
thanks,
greg k-h