From 720f2d03412b4701d8df48c438b0560ec6ee8e28 Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Fri, 8 May 2026 18:13:44 +0100 Subject: [PATCH] char: broadcom: vcio: Create addtional vcio nodes for fine grained acccess vcio has a number of uses, including clocks and power, board info, gencmd, display, vpu execution, provisioning, crypto, ab booting. Currently we have to give a user access to all or none of these. Provide some additional nodes which can only access a subset of these commands. That allows, say, gencmd access from a user or group, without exposing more dangerous commands. The initial extra nodes are for gencmd, provisioning, ab and crypto. Signed-off-by: Dom Cobley --- drivers/char/broadcom/vcio.c | 194 ++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 17 deletions(-) diff --git a/drivers/char/broadcom/vcio.c b/drivers/char/broadcom/vcio.c index 9db2408c781a74..f185c46d0c4644 100644 --- a/drivers/char/broadcom/vcio.c +++ b/drivers/char/broadcom/vcio.c @@ -30,12 +30,127 @@ #define IOCTL_MBOX_PROPERTY32 _IOWR(VCIO_IOC_MAGIC, 0, compat_uptr_t) #endif +/* Tags permitted via /dev/vcio_provisioning. */ +static const u32 vcio_provisioning_tags[] = { + 0x00038021, /* SET_CUSTOMER_OTP */ + 0x00038024, /* SET_USER_OTP */ + 0x00038082, /* SET_CUSTOMER_ETHER_MAC_OTP */ + 0x00038083, /* SET_CUSTOMER_WIFI_MAC_OTP */ + 0x00038084, /* SET_CUSTOMER_BT_MAC_OTP */ + 0x00030085, /* TST_MAC_OTP */ + 0x00030086, /* SET_CUSTOMER_OTP_LOCK */ +}; + +/* Tags permitted via /dev/vcio_ab. */ +static const u32 vcio_ab_tags[] = { + 0x00030048, /* SET_NOTIFY_REBOOT */ + 0x00030057, /* SET_GLOBAL_RESET */ + 0x00030064, /* GET_REBOOT_FLAGS */ + 0x00038064, /* SET_REBOOT_FLAGS */ + 0x0003008b, /* GET_REBOOT_ORDER */ + 0x0003808b, /* SET_REBOOT_ORDER */ + 0x0003008c, /* GET_REBOOT_ARG */ + 0x0003808c, /* SET_REBOOT_ARG */ + 0x0003008d, /* GET_BOOT_COUNT */ + 0x0003808d, /* SET_BOOT_COUNT */ +}; + +/* Tags permitted via /dev/vcio_gencmd. */ +static const u32 vcio_gencmd_tags[] = { + 0x00030080, /* GET_GENCMD_RESULT */ +}; + +/* Tags permitted via /dev/vcio_crypto. */ +static const u32 vcio_crypto_tags[] = { + 0x0003008e, /* GET_CRYPTO_LAST_ERROR */ + 0x0003008f, /* GET_CRYPTO_NUM_OTP_KEYS */ + 0x00030090, /* GET_CRYPTO_KEY_STATUS */ + 0x00038090, /* SET_CRYPTO_KEY_STATUS */ + 0x00030091, /* GET_CRYPTO_ECDSA_SIGN */ + 0x00030092, /* GET_CRYPTO_HMAC_SHA256 */ + 0x00030093, /* GET_CRYPTO_PUBLIC_KEY */ + 0x00030094, /* GET_CRYPTO_PRIVATE_KEY */ + 0x00030095, /* GET_CRYPTO_GEN_ECDSA */ +}; + +struct vcio_group { + const char *name; + const u32 *tags; + size_t num_tags; +}; + +#define VCIO_GROUP(suffix) { \ + .name = "vcio_" #suffix, \ + .tags = vcio_ ## suffix ## _tags, \ + .num_tags = ARRAY_SIZE(vcio_ ## suffix ## _tags), \ +} + +static const struct vcio_group vcio_groups[] = { + VCIO_GROUP(provisioning), + VCIO_GROUP(ab), + VCIO_GROUP(gencmd), + VCIO_GROUP(crypto), +}; + +struct vcio_dev { + struct miscdevice misc_dev; + struct rpi_firmware *fw; + const u32 *allowed_tags; + size_t num_allowed_tags; +}; + struct vcio_data { struct rpi_firmware *fw; - struct miscdevice misc_dev; + struct vcio_dev vcio; + struct vcio_dev groups[ARRAY_SIZE(vcio_groups)]; }; -static int vcio_user_property_list(struct vcio_data *vcio, void *user) +static bool vcio_tag_allowed(const struct vcio_dev *vdev, u32 tag) +{ + size_t i; + + if (!vdev->allowed_tags) + return true; + + for (i = 0; i < vdev->num_allowed_tags; i++) + if (vdev->allowed_tags[i] == tag) + return true; + + return false; +} + +static int vcio_check_tags(const struct vcio_dev *vdev, const u32 *buf, + size_t size) +{ + size_t offset = 8; /* skip total size and status words */ + + if (!vdev->allowed_tags) + return 0; + + while (offset + 12 <= size) { + u32 tag = buf[offset / 4]; + u32 val_size; + + if (tag == 0) + return 0; + + if (!vcio_tag_allowed(vdev, tag)) { + pr_warn_ratelimited("%s: tag 0x%08x not permitted\n", + vdev->misc_dev.name, tag); + return -EPERM; + } + + val_size = buf[offset / 4 + 1]; + offset += 12 + ALIGN(val_size, 4); + + if (offset < 12 || offset > size) + return -EINVAL; + } + + return 0; +} + +static int vcio_user_property_list(struct vcio_dev *vdev, void *user) { u32 *buf, size; int ret; @@ -44,6 +159,9 @@ static int vcio_user_property_list(struct vcio_data *vcio, void *user) if (copy_from_user(&size, user, sizeof(size))) return -EFAULT; + if (size < 12) + return -EINVAL; + buf = kmalloc(size, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -53,8 +171,14 @@ static int vcio_user_property_list(struct vcio_data *vcio, void *user) return -EFAULT; } + ret = vcio_check_tags(vdev, buf, size); + if (ret) { + kfree(buf); + return ret; + } + /* Strip off protocol encapsulation */ - ret = rpi_firmware_property_list(vcio->fw, &buf[2], size - 12); + ret = rpi_firmware_property_list(vdev->fw, &buf[2], size - 12); if (ret) { kfree(buf); return ret; @@ -86,12 +210,12 @@ static int vcio_device_release(struct inode *inode, struct file *file) static long vcio_device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) { - struct vcio_data *vcio = container_of(file->private_data, - struct vcio_data, misc_dev); + struct vcio_dev *vdev = container_of(file->private_data, + struct vcio_dev, misc_dev); switch (ioctl_num) { case IOCTL_MBOX_PROPERTY: - return vcio_user_property_list(vcio, (void *)ioctl_param); + return vcio_user_property_list(vdev, (void *)ioctl_param); default: pr_err("unknown ioctl: %x\n", ioctl_num); return -EINVAL; @@ -102,12 +226,12 @@ static long vcio_device_ioctl(struct file *file, unsigned int ioctl_num, static long vcio_device_compat_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) { - struct vcio_data *vcio = container_of(file->private_data, - struct vcio_data, misc_dev); + struct vcio_dev *vdev = container_of(file->private_data, + struct vcio_dev, misc_dev); switch (ioctl_num) { case IOCTL_MBOX_PROPERTY32: - return vcio_user_property_list(vcio, compat_ptr(ioctl_param)); + return vcio_user_property_list(vdev, compat_ptr(ioctl_param)); default: pr_err("unknown ioctl: %x\n", ioctl_num); return -EINVAL; @@ -124,6 +248,21 @@ const struct file_operations vcio_fops = { .release = vcio_device_release, }; +static int vcio_register(struct device *dev, struct vcio_dev *vdev, + struct rpi_firmware *fw, const char *name, + const u32 *allowed_tags, size_t num_allowed_tags) +{ + vdev->fw = fw; + vdev->allowed_tags = allowed_tags; + vdev->num_allowed_tags = num_allowed_tags; + vdev->misc_dev.fops = &vcio_fops; + vdev->misc_dev.minor = MISC_DYNAMIC_MINOR; + vdev->misc_dev.name = name; + vdev->misc_dev.parent = dev; + + return misc_register(&vdev->misc_dev); +} + static int vcio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -131,6 +270,8 @@ static int vcio_probe(struct platform_device *pdev) struct device_node *fw_node; struct rpi_firmware *fw; struct vcio_data *vcio; + size_t i; + int ret; fw_node = of_get_parent(np); if (!fw_node) { @@ -143,24 +284,43 @@ static int vcio_probe(struct platform_device *pdev) if (!fw) return -EPROBE_DEFER; - vcio = devm_kzalloc(dev, sizeof(struct vcio_data), GFP_KERNEL); + vcio = devm_kzalloc(dev, sizeof(*vcio), GFP_KERNEL); if (!vcio) return -ENOMEM; vcio->fw = fw; - vcio->misc_dev.fops = &vcio_fops; - vcio->misc_dev.minor = MISC_DYNAMIC_MINOR; - vcio->misc_dev.name = "vcio"; - vcio->misc_dev.parent = dev; + platform_set_drvdata(pdev, vcio); + + ret = vcio_register(dev, &vcio->vcio, fw, "vcio", NULL, 0); + if (ret) + return ret; - return misc_register(&vcio->misc_dev); + for (i = 0; i < ARRAY_SIZE(vcio_groups); i++) { + const struct vcio_group *g = &vcio_groups[i]; + + ret = vcio_register(dev, &vcio->groups[i], fw, g->name, + g->tags, g->num_tags); + if (ret) + goto err_groups; + } + + return 0; + +err_groups: + while (i--) + misc_deregister(&vcio->groups[i].misc_dev); + misc_deregister(&vcio->vcio.misc_dev); + return ret; } static void vcio_remove(struct platform_device *pdev) { - struct device *dev = &pdev->dev; + struct vcio_data *vcio = platform_get_drvdata(pdev); + size_t i = ARRAY_SIZE(vcio_groups); - misc_deregister(dev_get_drvdata(dev)); + while (i--) + misc_deregister(&vcio->groups[i].misc_dev); + misc_deregister(&vcio->vcio.misc_dev); } static const struct of_device_id vcio_ids[] = {