efivarfs: automatically update super block flag
authorMasahisa Kojima <masahisa.kojima@linaro.org>
Tue, 7 Nov 2023 05:40:55 +0000 (14:40 +0900)
committerArd Biesheuvel <ardb@kernel.org>
Mon, 11 Dec 2023 10:19:18 +0000 (11:19 +0100)
efivar operation is updated when the tee_stmm_efi module is probed.
tee_stmm_efi module supports SetVariable runtime service, but user needs
to manually remount the efivarfs as RW to enable the write access if the
previous efivar operation does not support SetVariable and efivarfs is
mounted as read-only.

This commit notifies the update of efivar operation to efivarfs
subsystem, then drops SB_RDONLY flag if the efivar operation supports
SetVariable.

Signed-off-by: Masahisa Kojima <masahisa.kojima@linaro.org>
[ardb: use per-superblock instance of the notifier block]
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
drivers/firmware/efi/efi.c
drivers/firmware/efi/vars.c
fs/efivarfs/internal.h
fs/efivarfs/super.c
include/linux/efi.h

index 32a67c61c3b84636fccab8d2bd3ad3e3be4f1449..4fcda50acfa4a8f81d6b80861436c0466f8eb592 100644 (file)
@@ -32,6 +32,7 @@
 #include <linux/ucs2_string.h>
 #include <linux/memblock.h>
 #include <linux/security.h>
+#include <linux/notifier.h>
 
 #include <asm/early_ioremap.h>
 
@@ -187,6 +188,9 @@ static const struct attribute_group efi_subsys_attr_group = {
        .is_visible = efi_attr_is_visible,
 };
 
+struct blocking_notifier_head efivar_ops_nh;
+EXPORT_SYMBOL_GPL(efivar_ops_nh);
+
 static struct efivars generic_efivars;
 static struct efivar_operations generic_ops;
 
@@ -431,6 +435,8 @@ static int __init efisubsys_init(void)
                platform_device_register_simple("efivars", 0, NULL, 0);
        }
 
+       BLOCKING_INIT_NOTIFIER_HEAD(&efivar_ops_nh);
+
        error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group);
        if (error) {
                pr_err("efi: Sysfs attribute export failed with error %d.\n",
index e9dc7116daf133e68750ed7148f2661f6c5ffc34..f654e6f6af87380da72fa9cb7de45fa5c876e764 100644 (file)
@@ -63,6 +63,7 @@ int efivars_register(struct efivars *efivars,
                     const struct efivar_operations *ops)
 {
        int rv;
+       int event;
 
        if (down_interruptible(&efivars_lock))
                return -EINTR;
@@ -77,6 +78,13 @@ int efivars_register(struct efivars *efivars,
 
        __efivars = efivars;
 
+       if (efivar_supports_writes())
+               event = EFIVAR_OPS_RDWR;
+       else
+               event = EFIVAR_OPS_RDONLY;
+
+       blocking_notifier_call_chain(&efivar_ops_nh, event, NULL);
+
        pr_info("Registered efivars operations\n");
        rv = 0;
 out:
index 1dc0ccce3cc30467b928f0bcb8554590a23244b9..169252e6dc4616c7712126adde4a36ce8e2d6922 100644 (file)
@@ -17,6 +17,8 @@ struct efivarfs_mount_opts {
 struct efivarfs_fs_info {
        struct efivarfs_mount_opts mount_opts;
        struct list_head efivarfs_list;
+       struct super_block *sb;
+       struct notifier_block nb;
 };
 
 struct efi_variable {
index cee325b5bbdd97f516bd056cce390570344505ec..6038dd39367abe41430c55b04448ced7727dd287 100644 (file)
 #include <linux/slab.h>
 #include <linux/magic.h>
 #include <linux/statfs.h>
+#include <linux/notifier.h>
 #include <linux/printk.h>
 
 #include "internal.h"
 
+static int efivarfs_ops_notifier(struct notifier_block *nb, unsigned long event,
+                                void *data)
+{
+       struct efivarfs_fs_info *sfi = container_of(nb, struct efivarfs_fs_info, nb);
+
+       switch (event) {
+       case EFIVAR_OPS_RDONLY:
+               sfi->sb->s_flags |= SB_RDONLY;
+               break;
+       case EFIVAR_OPS_RDWR:
+               sfi->sb->s_flags &= ~SB_RDONLY;
+               break;
+       default:
+               return NOTIFY_DONE;
+       }
+
+       return NOTIFY_OK;
+}
+
 static void efivarfs_evict_inode(struct inode *inode)
 {
        clear_inode(inode);
@@ -317,6 +337,12 @@ static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc)
        if (!root)
                return -ENOMEM;
 
+       sfi->sb = sb;
+       sfi->nb.notifier_call = efivarfs_ops_notifier;
+       err = blocking_notifier_chain_register(&efivar_ops_nh, &sfi->nb);
+       if (err)
+               return err;
+
        err = efivar_init(efivarfs_callback, (void *)sb, true,
                          &sfi->efivarfs_list);
        if (err)
@@ -371,6 +397,7 @@ static void efivarfs_kill_sb(struct super_block *sb)
 {
        struct efivarfs_fs_info *sfi = sb->s_fs_info;
 
+       blocking_notifier_chain_unregister(&efivar_ops_nh, &sfi->nb);
        kill_litter_super(sb);
 
        /* Remove all entries and destroy */
index 3668aa204c4783d1e89df6c18f06eae546b419b2..c74f47711f0bdf5b7bb0fddfccac4e6abac79c8c 100644 (file)
@@ -1349,6 +1349,14 @@ bool efi_config_table_is_usable(const efi_guid_t *guid, unsigned long table)
 
 umode_t efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n);
 
+/*
+ * efivar ops event type
+ */
+#define EFIVAR_OPS_RDONLY 0
+#define EFIVAR_OPS_RDWR 1
+
+extern struct blocking_notifier_head efivar_ops_nh;
+
 void efivars_generic_ops_register(void);
 void efivars_generic_ops_unregister(void);