mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 21:38:32 +08:00
EFI efivars sysfs interface removal
Remove the obsolete 'efivars' sysfs based interface to the EFI variable store, now that all users have moved to the efivarfs pseudo file system, which was created ~10 years ago to address some fundamental shortcomings in the sysfs based driver. Move the 'business logic' related to which EFI variables are important and may affect the boot flow from the efivars support layer into the efivarfs pseudo file system, so it is no longer exposed to other parts of the kernel. -----BEGIN PGP SIGNATURE----- iQGzBAABCgAdFiEE+9lifEBpyUIVN1cpw08iOZLZjyQFAmLhuYIACgkQw08iOZLZ jyRYYgwAwUvbFtBL+uOTB+jynOq1MkM1xrNSeEgzPyLhh7CXSa/e+48XqpQjCUxY 8HNqDvd4toiCeVKp25AO+FPrQBjT7NdyjnwGMP3DLkGCOSIrVqVJ/2OmbOY52Kzy z1qbF2+7ak1TUsGzMnJHVDB+baGnUlZ39DuR6IAWstM9tkH/QEnRJV+ejS4h7jdN XW42/M96mAYFfT4Q4gs1HJMPWLP22xoEbnb9SkOyFCB2tHDjrY94CqH8L7Ee1DgA 7sc2cAZ2mPlS8Rbs8NY8IA/LpN4tRu2EhbIxtmKMRgXA88aTKcsub9Tq9RXxzE/x BpkH77mGbSBSrntg0gskKCrYGyJaGS9EYnHVy5HPU8hJagK295NmO3c/trHTb90Z 1RZClYsIUbNXe06e9rdk8w8Ozn+7ABstxzLCvj2MThtTBnAbjU0kQ9VeD0xwMvOp EaF6D6tbEmHrZkGtKP2WEyOZm0tF2I3OP1U9LzjyTpvsIOINhqfVozlhTB6pU5YE kW9E59i+ =VVQ6 -----END PGP SIGNATURE----- Merge tag 'efi-efivars-removal-for-v5.20' of git://git.kernel.org/pub/scm/linux/kernel/git/efi/efi Pull efivars sysfs interface removal from Ard Biesheuvel: "Remove the obsolete 'efivars' sysfs based interface to the EFI variable store, now that all users have moved to the efivarfs pseudo file system, which was created ~10 years ago to address some fundamental shortcomings in the sysfs based driver. Move the 'business logic' related to which EFI variables are important and may affect the boot flow from the efivars support layer into the efivarfs pseudo file system, so it is no longer exposed to other parts of the kernel" * tag 'efi-efivars-removal-for-v5.20' of git://git.kernel.org/pub/scm/linux/kernel/git/efi/efi: efi: vars: Move efivar caching layer into efivarfs efi: vars: Switch to new wrapper layer efi: vars: Remove deprecated 'efivars' sysfs interface
This commit is contained in:
commit
ab17c0cd37
@ -29,7 +29,7 @@ Mechanics
|
||||
be selected::
|
||||
|
||||
CONFIG_EFI=y
|
||||
CONFIG_EFI_VARS=y or m # optional
|
||||
CONFIG_EFIVAR_FS=y or m # optional
|
||||
|
||||
- Create a VFAT partition on the disk
|
||||
- Copy the following to the VFAT partition:
|
||||
|
@ -44,7 +44,6 @@ CONFIG_ARM_CPUIDLE=y
|
||||
CONFIG_VFP=y
|
||||
CONFIG_NEON=y
|
||||
CONFIG_KERNEL_MODE_NEON=y
|
||||
CONFIG_EFI_VARS=m
|
||||
CONFIG_ARM_CRYPTO=y
|
||||
CONFIG_CRYPTO_SHA1_ARM_NEON=m
|
||||
CONFIG_CRYPTO_SHA1_ARM_CE=m
|
||||
|
@ -10,7 +10,6 @@ CONFIG_SMP=y
|
||||
CONFIG_NR_CPUS=2
|
||||
CONFIG_PREEMPT=y
|
||||
CONFIG_IA64_PALINFO=y
|
||||
CONFIG_EFI_VARS=y
|
||||
CONFIG_BINFMT_MISC=m
|
||||
CONFIG_ACPI_BUTTON=m
|
||||
CONFIG_ACPI_FAN=m
|
||||
|
@ -21,7 +21,6 @@ CONFIG_IA64_MCA_RECOVERY=y
|
||||
CONFIG_IA64_PALINFO=y
|
||||
CONFIG_KEXEC=y
|
||||
CONFIG_CRASH_DUMP=y
|
||||
CONFIG_EFI_VARS=y
|
||||
CONFIG_BINFMT_MISC=m
|
||||
CONFIG_ACPI_BUTTON=m
|
||||
CONFIG_ACPI_FAN=m
|
||||
|
@ -18,7 +18,6 @@ CONFIG_HOTPLUG_CPU=y
|
||||
CONFIG_SPARSEMEM_MANUAL=y
|
||||
CONFIG_IA64_MCA_RECOVERY=y
|
||||
CONFIG_IA64_PALINFO=y
|
||||
CONFIG_EFI_VARS=y
|
||||
CONFIG_BINFMT_MISC=m
|
||||
CONFIG_ACPI_BUTTON=m
|
||||
CONFIG_ACPI_FAN=m
|
||||
|
@ -23,7 +23,6 @@ CONFIG_FORCE_CPEI_RETARGET=y
|
||||
CONFIG_IA64_MCA_RECOVERY=y
|
||||
CONFIG_IA64_PALINFO=y
|
||||
CONFIG_KEXEC=y
|
||||
CONFIG_EFI_VARS=y
|
||||
CONFIG_BINFMT_MISC=m
|
||||
CONFIG_ACPI_BUTTON=m
|
||||
CONFIG_ACPI_FAN=m
|
||||
|
@ -12,7 +12,6 @@ CONFIG_FLATMEM_MANUAL=y
|
||||
CONFIG_IA64_MCA_RECOVERY=y
|
||||
CONFIG_IA64_PALINFO=y
|
||||
CONFIG_CRASH_DUMP=y
|
||||
CONFIG_EFI_VARS=y
|
||||
CONFIG_BINFMT_MISC=y
|
||||
CONFIG_HOTPLUG_PCI=y
|
||||
CONFIG_HOTPLUG_PCI_ACPI=y
|
||||
|
@ -135,7 +135,6 @@ CONFIG_DEVTMPFS=y
|
||||
CONFIG_DEVTMPFS_MOUNT=y
|
||||
CONFIG_DEBUG_DEVRES=y
|
||||
CONFIG_CONNECTOR=y
|
||||
CONFIG_EFI_VARS=y
|
||||
CONFIG_EFI_CAPSULE_LOADER=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
|
@ -134,7 +134,6 @@ CONFIG_DEVTMPFS=y
|
||||
CONFIG_DEVTMPFS_MOUNT=y
|
||||
CONFIG_DEBUG_DEVRES=y
|
||||
CONFIG_CONNECTOR=y
|
||||
CONFIG_EFI_VARS=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
CONFIG_BLK_DEV_SD=y
|
||||
|
@ -2,18 +2,6 @@
|
||||
menu "EFI (Extensible Firmware Interface) Support"
|
||||
depends on EFI
|
||||
|
||||
config EFI_VARS
|
||||
tristate "EFI Variable Support via sysfs"
|
||||
depends on EFI && (X86 || IA64)
|
||||
default n
|
||||
help
|
||||
If you say Y here, you are able to get EFI (Extensible Firmware
|
||||
Interface) variable information via sysfs. You may read,
|
||||
write, create, and destroy EFI variables through this interface.
|
||||
Note that this driver is only retained for compatibility with
|
||||
legacy users: new users should use the efivarfs filesystem
|
||||
instead.
|
||||
|
||||
config EFI_ESRT
|
||||
bool
|
||||
depends on EFI && !IA64
|
||||
|
@ -17,7 +17,6 @@ ifneq ($(CONFIG_EFI_CAPSULE_LOADER),)
|
||||
obj-$(CONFIG_EFI) += capsule.o
|
||||
endif
|
||||
obj-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdtparams.o
|
||||
obj-$(CONFIG_EFI_VARS) += efivars.o
|
||||
obj-$(CONFIG_EFI_ESRT) += esrt.o
|
||||
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
|
||||
obj-$(CONFIG_UEFI_CPER) += cper.o
|
||||
|
@ -899,6 +899,7 @@ int efi_status_to_err(efi_status_t status)
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(efi_status_to_err);
|
||||
|
||||
static DEFINE_SPINLOCK(efi_mem_reserve_persistent_lock);
|
||||
static struct linux_efi_memreserve *efi_memreserve_root __ro_after_init;
|
||||
|
@ -1,660 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Originally from efivars.c,
|
||||
*
|
||||
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
|
||||
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
|
||||
*
|
||||
* This code takes all variables accessible from EFI runtime and
|
||||
* exports them via sysfs
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
#include <linux/compat.h>
|
||||
|
||||
#define EFIVARS_VERSION "0.08"
|
||||
#define EFIVARS_DATE "2004-May-17"
|
||||
|
||||
MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
|
||||
MODULE_DESCRIPTION("sysfs interface to EFI Variables");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(EFIVARS_VERSION);
|
||||
|
||||
static LIST_HEAD(efivar_sysfs_list);
|
||||
|
||||
static struct kset *efivars_kset;
|
||||
|
||||
static struct bin_attribute *efivars_new_var;
|
||||
static struct bin_attribute *efivars_del_var;
|
||||
|
||||
struct compat_efi_variable {
|
||||
efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
|
||||
efi_guid_t VendorGuid;
|
||||
__u32 DataSize;
|
||||
__u8 Data[1024];
|
||||
__u32 Status;
|
||||
__u32 Attributes;
|
||||
} __packed;
|
||||
|
||||
struct efivar_attribute {
|
||||
struct attribute attr;
|
||||
ssize_t (*show) (struct efivar_entry *entry, char *buf);
|
||||
ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
|
||||
};
|
||||
|
||||
#define EFIVAR_ATTR(_name, _mode, _show, _store) \
|
||||
struct efivar_attribute efivar_attr_##_name = { \
|
||||
.attr = {.name = __stringify(_name), .mode = _mode}, \
|
||||
.show = _show, \
|
||||
.store = _store, \
|
||||
};
|
||||
|
||||
#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr)
|
||||
#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj)
|
||||
|
||||
/*
|
||||
* Prototype for sysfs creation function
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(struct efivar_entry *new_var);
|
||||
|
||||
static ssize_t
|
||||
efivar_guid_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
char *str = buf;
|
||||
|
||||
if (!entry || !buf)
|
||||
return 0;
|
||||
|
||||
efi_guid_to_str(&var->VendorGuid, str);
|
||||
str += strlen(str);
|
||||
str += sprintf(str, "\n");
|
||||
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_attr_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
unsigned long size = sizeof(var->Data);
|
||||
char *str = buf;
|
||||
int ret;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
|
||||
var->DataSize = size;
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
|
||||
str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
|
||||
if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
|
||||
str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
|
||||
str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
|
||||
str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
|
||||
if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
|
||||
str += sprintf(str,
|
||||
"EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
|
||||
if (var->Attributes &
|
||||
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
|
||||
str += sprintf(str,
|
||||
"EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
|
||||
if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
|
||||
str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_size_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
unsigned long size = sizeof(var->Data);
|
||||
char *str = buf;
|
||||
int ret;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
|
||||
var->DataSize = size;
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
str += sprintf(str, "0x%lx\n", var->DataSize);
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_data_read(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
unsigned long size = sizeof(var->Data);
|
||||
int ret;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
|
||||
var->DataSize = size;
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
memcpy(buf, var->Data, var->DataSize);
|
||||
return var->DataSize;
|
||||
}
|
||||
|
||||
static inline int
|
||||
sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor,
|
||||
unsigned long size, u32 attributes, u8 *data)
|
||||
{
|
||||
/*
|
||||
* If only updating the variable data, then the name
|
||||
* and guid should remain the same
|
||||
*/
|
||||
if (memcmp(name, var->VariableName, sizeof(var->VariableName)) ||
|
||||
efi_guidcmp(vendor, var->VendorGuid)) {
|
||||
printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((size <= 0) || (attributes == 0)){
|
||||
printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
|
||||
efivar_validate(vendor, name, data, size) == false) {
|
||||
printk(KERN_ERR "efivars: Malformed variable content\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src)
|
||||
{
|
||||
memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN);
|
||||
memcpy(dst->Data, src->Data, sizeof(src->Data));
|
||||
|
||||
dst->VendorGuid = src->VendorGuid;
|
||||
dst->DataSize = src->DataSize;
|
||||
dst->Attributes = src->Attributes;
|
||||
}
|
||||
|
||||
/*
|
||||
* We allow each variable to be edited via rewriting the
|
||||
* entire efi variable structure.
|
||||
*/
|
||||
static ssize_t
|
||||
efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
|
||||
{
|
||||
struct efi_variable *new_var, *var = &entry->var;
|
||||
efi_char16_t *name;
|
||||
unsigned long size;
|
||||
efi_guid_t vendor;
|
||||
u32 attributes;
|
||||
u8 *data;
|
||||
int err;
|
||||
|
||||
if (!entry || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
if (in_compat_syscall()) {
|
||||
struct compat_efi_variable *compat;
|
||||
|
||||
if (count != sizeof(*compat))
|
||||
return -EINVAL;
|
||||
|
||||
compat = (struct compat_efi_variable *)buf;
|
||||
attributes = compat->Attributes;
|
||||
vendor = compat->VendorGuid;
|
||||
name = compat->VariableName;
|
||||
size = compat->DataSize;
|
||||
data = compat->Data;
|
||||
|
||||
err = sanity_check(var, name, vendor, size, attributes, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
copy_out_compat(&entry->var, compat);
|
||||
} else {
|
||||
if (count != sizeof(struct efi_variable))
|
||||
return -EINVAL;
|
||||
|
||||
new_var = (struct efi_variable *)buf;
|
||||
|
||||
attributes = new_var->Attributes;
|
||||
vendor = new_var->VendorGuid;
|
||||
name = new_var->VariableName;
|
||||
size = new_var->DataSize;
|
||||
data = new_var->Data;
|
||||
|
||||
err = sanity_check(var, name, vendor, size, attributes, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
memcpy(&entry->var, new_var, count);
|
||||
}
|
||||
|
||||
err = efivar_entry_set(entry, attributes, size, data, NULL);
|
||||
if (err) {
|
||||
printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
efivar_show_raw(struct efivar_entry *entry, char *buf)
|
||||
{
|
||||
struct efi_variable *var = &entry->var;
|
||||
struct compat_efi_variable *compat;
|
||||
unsigned long datasize = sizeof(var->Data);
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
if (!entry || !buf)
|
||||
return 0;
|
||||
|
||||
ret = efivar_entry_get(entry, &var->Attributes, &datasize, var->Data);
|
||||
var->DataSize = datasize;
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
if (in_compat_syscall()) {
|
||||
compat = (struct compat_efi_variable *)buf;
|
||||
|
||||
size = sizeof(*compat);
|
||||
memcpy(compat->VariableName, var->VariableName,
|
||||
EFI_VAR_NAME_LEN);
|
||||
memcpy(compat->Data, var->Data, sizeof(compat->Data));
|
||||
|
||||
compat->VendorGuid = var->VendorGuid;
|
||||
compat->DataSize = var->DataSize;
|
||||
compat->Attributes = var->Attributes;
|
||||
} else {
|
||||
size = sizeof(*var);
|
||||
memcpy(buf, var, size);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic read/write functions that call the specific functions of
|
||||
* the attributes...
|
||||
*/
|
||||
static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->show) {
|
||||
ret = efivar_attr->show(var, buf);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (efivar_attr->store)
|
||||
ret = efivar_attr->store(var, buf, count);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct sysfs_ops efivar_attr_ops = {
|
||||
.show = efivar_attr_show,
|
||||
.store = efivar_attr_store,
|
||||
};
|
||||
|
||||
static void efivar_release(struct kobject *kobj)
|
||||
{
|
||||
struct efivar_entry *var = to_efivar_entry(kobj);
|
||||
kfree(var);
|
||||
}
|
||||
|
||||
static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL);
|
||||
static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL);
|
||||
static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL);
|
||||
static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL);
|
||||
static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw);
|
||||
|
||||
static struct attribute *def_attrs[] = {
|
||||
&efivar_attr_guid.attr,
|
||||
&efivar_attr_size.attr,
|
||||
&efivar_attr_attributes.attr,
|
||||
&efivar_attr_data.attr,
|
||||
&efivar_attr_raw_var.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(def);
|
||||
|
||||
static struct kobj_type efivar_ktype = {
|
||||
.release = efivar_release,
|
||||
.sysfs_ops = &efivar_attr_ops,
|
||||
.default_groups = def_groups,
|
||||
};
|
||||
|
||||
static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct compat_efi_variable *compat = (struct compat_efi_variable *)buf;
|
||||
struct efi_variable *new_var = (struct efi_variable *)buf;
|
||||
struct efivar_entry *new_entry;
|
||||
bool need_compat = in_compat_syscall();
|
||||
efi_char16_t *name;
|
||||
unsigned long size;
|
||||
u32 attributes;
|
||||
u8 *data;
|
||||
int err;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (need_compat) {
|
||||
if (count != sizeof(*compat))
|
||||
return -EINVAL;
|
||||
|
||||
attributes = compat->Attributes;
|
||||
name = compat->VariableName;
|
||||
size = compat->DataSize;
|
||||
data = compat->Data;
|
||||
} else {
|
||||
if (count != sizeof(*new_var))
|
||||
return -EINVAL;
|
||||
|
||||
attributes = new_var->Attributes;
|
||||
name = new_var->VariableName;
|
||||
size = new_var->DataSize;
|
||||
data = new_var->Data;
|
||||
}
|
||||
|
||||
if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
|
||||
efivar_validate(new_var->VendorGuid, name, data,
|
||||
size) == false) {
|
||||
printk(KERN_ERR "efivars: Malformed variable content\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
|
||||
if (!new_entry)
|
||||
return -ENOMEM;
|
||||
|
||||
if (need_compat)
|
||||
copy_out_compat(&new_entry->var, compat);
|
||||
else
|
||||
memcpy(&new_entry->var, new_var, sizeof(*new_var));
|
||||
|
||||
err = efivar_entry_set(new_entry, attributes, size,
|
||||
data, &efivar_sysfs_list);
|
||||
if (err) {
|
||||
if (err == -EEXIST)
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (efivar_create_sysfs_entry(new_entry)) {
|
||||
printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
|
||||
kfree(new_entry);
|
||||
}
|
||||
return count;
|
||||
|
||||
out:
|
||||
kfree(new_entry);
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct efi_variable *del_var = (struct efi_variable *)buf;
|
||||
struct compat_efi_variable *compat;
|
||||
struct efivar_entry *entry;
|
||||
efi_char16_t *name;
|
||||
efi_guid_t vendor;
|
||||
int err = 0;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
if (in_compat_syscall()) {
|
||||
if (count != sizeof(*compat))
|
||||
return -EINVAL;
|
||||
|
||||
compat = (struct compat_efi_variable *)buf;
|
||||
name = compat->VariableName;
|
||||
vendor = compat->VendorGuid;
|
||||
} else {
|
||||
if (count != sizeof(*del_var))
|
||||
return -EINVAL;
|
||||
|
||||
name = del_var->VariableName;
|
||||
vendor = del_var->VendorGuid;
|
||||
}
|
||||
|
||||
if (efivar_entry_iter_begin())
|
||||
return -EINTR;
|
||||
entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);
|
||||
if (!entry)
|
||||
err = -EINVAL;
|
||||
else if (__efivar_entry_delete(entry))
|
||||
err = -EIO;
|
||||
|
||||
efivar_entry_iter_end();
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
efivar_unregister(entry);
|
||||
|
||||
/* It's dead Jim.... */
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_create_sysfs_entry - create a new entry in sysfs
|
||||
* @new_var: efivar entry to create
|
||||
*
|
||||
* Returns 0 on success, negative error code on failure
|
||||
*/
|
||||
static int
|
||||
efivar_create_sysfs_entry(struct efivar_entry *new_var)
|
||||
{
|
||||
int short_name_size;
|
||||
char *short_name;
|
||||
unsigned long utf8_name_size;
|
||||
efi_char16_t *variable_name = new_var->var.VariableName;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Length of the variable bytes in UTF8, plus the '-' separator,
|
||||
* plus the GUID, plus trailing NUL
|
||||
*/
|
||||
utf8_name_size = ucs2_utf8size(variable_name);
|
||||
short_name_size = utf8_name_size + 1 + EFI_VARIABLE_GUID_LEN + 1;
|
||||
|
||||
short_name = kmalloc(short_name_size, GFP_KERNEL);
|
||||
if (!short_name)
|
||||
return -ENOMEM;
|
||||
|
||||
ucs2_as_utf8(short_name, variable_name, short_name_size);
|
||||
|
||||
/* This is ugly, but necessary to separate one vendor's
|
||||
private variables from another's. */
|
||||
short_name[utf8_name_size] = '-';
|
||||
efi_guid_to_str(&new_var->var.VendorGuid,
|
||||
short_name + utf8_name_size + 1);
|
||||
|
||||
new_var->kobj.kset = efivars_kset;
|
||||
|
||||
ret = kobject_init_and_add(&new_var->kobj, &efivar_ktype,
|
||||
NULL, "%s", short_name);
|
||||
kfree(short_name);
|
||||
if (ret) {
|
||||
kobject_put(&new_var->kobj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
kobject_uevent(&new_var->kobj, KOBJ_ADD);
|
||||
__efivar_entry_add(new_var, &efivar_sysfs_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
create_efivars_bin_attributes(void)
|
||||
{
|
||||
struct bin_attribute *attr;
|
||||
int error;
|
||||
|
||||
/* new_var */
|
||||
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
||||
if (!attr)
|
||||
return -ENOMEM;
|
||||
|
||||
attr->attr.name = "new_var";
|
||||
attr->attr.mode = 0200;
|
||||
attr->write = efivar_create;
|
||||
efivars_new_var = attr;
|
||||
|
||||
/* del_var */
|
||||
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
||||
if (!attr) {
|
||||
error = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
attr->attr.name = "del_var";
|
||||
attr->attr.mode = 0200;
|
||||
attr->write = efivar_delete;
|
||||
efivars_del_var = attr;
|
||||
|
||||
sysfs_bin_attr_init(efivars_new_var);
|
||||
sysfs_bin_attr_init(efivars_del_var);
|
||||
|
||||
/* Register */
|
||||
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: unable to create new_var sysfs file"
|
||||
" due to error %d\n", error);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var);
|
||||
if (error) {
|
||||
printk(KERN_ERR "efivars: unable to create del_var sysfs file"
|
||||
" due to error %d\n", error);
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_free:
|
||||
kfree(efivars_del_var);
|
||||
efivars_del_var = NULL;
|
||||
kfree(efivars_new_var);
|
||||
efivars_new_var = NULL;
|
||||
return error;
|
||||
}
|
||||
|
||||
static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
|
||||
unsigned long name_size, void *data)
|
||||
{
|
||||
struct efivar_entry *entry;
|
||||
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(entry->var.VariableName, name, name_size);
|
||||
memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
|
||||
|
||||
efivar_create_sysfs_entry(entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
|
||||
{
|
||||
efivar_entry_remove(entry);
|
||||
efivar_unregister(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void efivars_sysfs_exit(void)
|
||||
{
|
||||
/* Remove all entries and destroy */
|
||||
int err;
|
||||
|
||||
err = efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list, NULL);
|
||||
if (err) {
|
||||
pr_err("efivars: Failed to destroy sysfs entries\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (efivars_new_var)
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
|
||||
if (efivars_del_var)
|
||||
sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var);
|
||||
kfree(efivars_new_var);
|
||||
kfree(efivars_del_var);
|
||||
kset_unregister(efivars_kset);
|
||||
}
|
||||
|
||||
static int efivars_sysfs_init(void)
|
||||
{
|
||||
struct kobject *parent_kobj = efivars_kobject();
|
||||
int error = 0;
|
||||
|
||||
/* No efivars has been registered yet */
|
||||
if (!parent_kobj || !efivar_supports_writes())
|
||||
return 0;
|
||||
|
||||
printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
|
||||
EFIVARS_DATE);
|
||||
|
||||
efivars_kset = kset_create_and_add("vars", NULL, parent_kobj);
|
||||
if (!efivars_kset) {
|
||||
printk(KERN_ERR "efivars: Subsystem registration failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
efivar_init(efivars_sysfs_callback, NULL, true, &efivar_sysfs_list);
|
||||
|
||||
error = create_efivars_bin_attributes();
|
||||
if (error) {
|
||||
efivars_sysfs_exit();
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(efivars_sysfs_init);
|
||||
module_exit(efivars_sysfs_exit);
|
File diff suppressed because it is too large
Load Diff
@ -5,4 +5,4 @@
|
||||
|
||||
obj-$(CONFIG_EFIVAR_FS) += efivarfs.o
|
||||
|
||||
efivarfs-objs := inode.o file.o super.o
|
||||
efivarfs-objs := inode.o file.o super.o vars.o
|
||||
|
@ -7,6 +7,46 @@
|
||||
#define EFIVAR_FS_INTERNAL_H
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/efi.h>
|
||||
|
||||
struct efi_variable {
|
||||
efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
|
||||
efi_guid_t VendorGuid;
|
||||
unsigned long DataSize;
|
||||
__u8 Data[1024];
|
||||
efi_status_t Status;
|
||||
__u32 Attributes;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct efivar_entry {
|
||||
struct efi_variable var;
|
||||
struct list_head list;
|
||||
struct kobject kobj;
|
||||
};
|
||||
|
||||
int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
|
||||
void *data, bool duplicates, struct list_head *head);
|
||||
|
||||
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
|
||||
void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
|
||||
void efivar_entry_remove(struct efivar_entry *entry);
|
||||
int efivar_entry_delete(struct efivar_entry *entry);
|
||||
|
||||
int efivar_entry_size(struct efivar_entry *entry, unsigned long *size);
|
||||
int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
unsigned long *size, void *data);
|
||||
int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
unsigned long *size, void *data);
|
||||
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
|
||||
unsigned long *size, void *data, bool *set);
|
||||
|
||||
int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
|
||||
struct list_head *head, void *data);
|
||||
|
||||
bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
|
||||
unsigned long data_size);
|
||||
bool efivar_variable_is_removable(efi_guid_t vendor, const char *name,
|
||||
size_t len);
|
||||
|
||||
extern const struct file_operations efivarfs_file_operations;
|
||||
extern const struct inode_operations efivarfs_dir_inode_operations;
|
||||
|
738
fs/efivarfs/vars.c
Normal file
738
fs/efivarfs/vars.c
Normal file
@ -0,0 +1,738 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Originally from efivars.c
|
||||
*
|
||||
* Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
|
||||
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/ucs2_string.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
MODULE_IMPORT_NS(EFIVAR);
|
||||
|
||||
static bool
|
||||
validate_device_path(efi_char16_t *var_name, int match, u8 *buffer,
|
||||
unsigned long len)
|
||||
{
|
||||
struct efi_generic_dev_path *node;
|
||||
int offset = 0;
|
||||
|
||||
node = (struct efi_generic_dev_path *)buffer;
|
||||
|
||||
if (len < sizeof(*node))
|
||||
return false;
|
||||
|
||||
while (offset <= len - sizeof(*node) &&
|
||||
node->length >= sizeof(*node) &&
|
||||
node->length <= len - offset) {
|
||||
offset += node->length;
|
||||
|
||||
if ((node->type == EFI_DEV_END_PATH ||
|
||||
node->type == EFI_DEV_END_PATH2) &&
|
||||
node->sub_type == EFI_DEV_END_ENTIRE)
|
||||
return true;
|
||||
|
||||
node = (struct efi_generic_dev_path *)(buffer + offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're here then either node->length pointed past the end
|
||||
* of the buffer or we reached the end of the buffer without
|
||||
* finding a device path end node.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_boot_order(efi_char16_t *var_name, int match, u8 *buffer,
|
||||
unsigned long len)
|
||||
{
|
||||
/* An array of 16-bit integers */
|
||||
if ((len % 2) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_load_option(efi_char16_t *var_name, int match, u8 *buffer,
|
||||
unsigned long len)
|
||||
{
|
||||
u16 filepathlength;
|
||||
int i, desclength = 0, namelen;
|
||||
|
||||
namelen = ucs2_strnlen(var_name, EFI_VAR_NAME_LEN);
|
||||
|
||||
/* Either "Boot" or "Driver" followed by four digits of hex */
|
||||
for (i = match; i < match+4; i++) {
|
||||
if (var_name[i] > 127 ||
|
||||
hex_to_bin(var_name[i] & 0xff) < 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Reject it if there's 4 digits of hex and then further content */
|
||||
if (namelen > match + 4)
|
||||
return false;
|
||||
|
||||
/* A valid entry must be at least 8 bytes */
|
||||
if (len < 8)
|
||||
return false;
|
||||
|
||||
filepathlength = buffer[4] | buffer[5] << 8;
|
||||
|
||||
/*
|
||||
* There's no stored length for the description, so it has to be
|
||||
* found by hand
|
||||
*/
|
||||
desclength = ucs2_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
|
||||
|
||||
/* Each boot entry must have a descriptor */
|
||||
if (!desclength)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If the sum of the length of the description, the claimed filepath
|
||||
* length and the original header are greater than the length of the
|
||||
* variable, it's malformed
|
||||
*/
|
||||
if ((desclength + filepathlength + 6) > len)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* And, finally, check the filepath
|
||||
*/
|
||||
return validate_device_path(var_name, match, buffer + desclength + 6,
|
||||
filepathlength);
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_uint16(efi_char16_t *var_name, int match, u8 *buffer,
|
||||
unsigned long len)
|
||||
{
|
||||
/* A single 16-bit integer */
|
||||
if (len != 2)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer,
|
||||
unsigned long len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (buffer[i] > 127)
|
||||
return false;
|
||||
|
||||
if (buffer[i] == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct variable_validate {
|
||||
efi_guid_t vendor;
|
||||
char *name;
|
||||
bool (*validate)(efi_char16_t *var_name, int match, u8 *data,
|
||||
unsigned long len);
|
||||
};
|
||||
|
||||
/*
|
||||
* This is the list of variables we need to validate, as well as the
|
||||
* whitelist for what we think is safe not to default to immutable.
|
||||
*
|
||||
* If it has a validate() method that's not NULL, it'll go into the
|
||||
* validation routine. If not, it is assumed valid, but still used for
|
||||
* whitelisting.
|
||||
*
|
||||
* Note that it's sorted by {vendor,name}, but globbed names must come after
|
||||
* any other name with the same prefix.
|
||||
*/
|
||||
static const struct variable_validate variable_validate[] = {
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "BootNext", validate_uint16 },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "BootOrder", validate_boot_order },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "Boot*", validate_load_option },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "DriverOrder", validate_boot_order },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "Driver*", validate_load_option },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "ConIn", validate_device_path },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "ConInDev", validate_device_path },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "ConOut", validate_device_path },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "ConOutDev", validate_device_path },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "ErrOut", validate_device_path },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "ErrOutDev", validate_device_path },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "Lang", validate_ascii_string },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "OsIndications", NULL },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "PlatformLang", validate_ascii_string },
|
||||
{ EFI_GLOBAL_VARIABLE_GUID, "Timeout", validate_uint16 },
|
||||
{ LINUX_EFI_CRASH_GUID, "*", NULL },
|
||||
{ NULL_GUID, "", NULL },
|
||||
};
|
||||
|
||||
/*
|
||||
* Check if @var_name matches the pattern given in @match_name.
|
||||
*
|
||||
* @var_name: an array of @len non-NUL characters.
|
||||
* @match_name: a NUL-terminated pattern string, optionally ending in "*". A
|
||||
* final "*" character matches any trailing characters @var_name,
|
||||
* including the case when there are none left in @var_name.
|
||||
* @match: on output, the number of non-wildcard characters in @match_name
|
||||
* that @var_name matches, regardless of the return value.
|
||||
* @return: whether @var_name fully matches @match_name.
|
||||
*/
|
||||
static bool
|
||||
variable_matches(const char *var_name, size_t len, const char *match_name,
|
||||
int *match)
|
||||
{
|
||||
for (*match = 0; ; (*match)++) {
|
||||
char c = match_name[*match];
|
||||
|
||||
switch (c) {
|
||||
case '*':
|
||||
/* Wildcard in @match_name means we've matched. */
|
||||
return true;
|
||||
|
||||
case '\0':
|
||||
/* @match_name has ended. Has @var_name too? */
|
||||
return (*match == len);
|
||||
|
||||
default:
|
||||
/*
|
||||
* We've reached a non-wildcard char in @match_name.
|
||||
* Continue only if there's an identical character in
|
||||
* @var_name.
|
||||
*/
|
||||
if (*match < len && c == var_name[*match])
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
|
||||
unsigned long data_size)
|
||||
{
|
||||
int i;
|
||||
unsigned long utf8_size;
|
||||
u8 *utf8_name;
|
||||
|
||||
utf8_size = ucs2_utf8size(var_name);
|
||||
utf8_name = kmalloc(utf8_size + 1, GFP_KERNEL);
|
||||
if (!utf8_name)
|
||||
return false;
|
||||
|
||||
ucs2_as_utf8(utf8_name, var_name, utf8_size);
|
||||
utf8_name[utf8_size] = '\0';
|
||||
|
||||
for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
|
||||
const char *name = variable_validate[i].name;
|
||||
int match = 0;
|
||||
|
||||
if (efi_guidcmp(vendor, variable_validate[i].vendor))
|
||||
continue;
|
||||
|
||||
if (variable_matches(utf8_name, utf8_size+1, name, &match)) {
|
||||
if (variable_validate[i].validate == NULL)
|
||||
break;
|
||||
kfree(utf8_name);
|
||||
return variable_validate[i].validate(var_name, match,
|
||||
data, data_size);
|
||||
}
|
||||
}
|
||||
kfree(utf8_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
|
||||
size_t len)
|
||||
{
|
||||
int i;
|
||||
bool found = false;
|
||||
int match = 0;
|
||||
|
||||
/*
|
||||
* Check if our variable is in the validated variables list
|
||||
*/
|
||||
for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
|
||||
if (efi_guidcmp(variable_validate[i].vendor, vendor))
|
||||
continue;
|
||||
|
||||
if (variable_matches(var_name, len,
|
||||
variable_validate[i].name, &match)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If it's in our list, it is removable.
|
||||
*/
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor,
|
||||
struct list_head *head)
|
||||
{
|
||||
struct efivar_entry *entry, *n;
|
||||
unsigned long strsize1, strsize2;
|
||||
bool found = false;
|
||||
|
||||
strsize1 = ucs2_strsize(variable_name, 1024);
|
||||
list_for_each_entry_safe(entry, n, head, list) {
|
||||
strsize2 = ucs2_strsize(entry->var.VariableName, 1024);
|
||||
if (strsize1 == strsize2 &&
|
||||
!memcmp(variable_name, &(entry->var.VariableName),
|
||||
strsize2) &&
|
||||
!efi_guidcmp(entry->var.VendorGuid,
|
||||
*vendor)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the size of variable_name, in bytes, including the
|
||||
* terminating NULL character, or variable_name_size if no NULL
|
||||
* character is found among the first variable_name_size bytes.
|
||||
*/
|
||||
static unsigned long var_name_strnsize(efi_char16_t *variable_name,
|
||||
unsigned long variable_name_size)
|
||||
{
|
||||
unsigned long len;
|
||||
efi_char16_t c;
|
||||
|
||||
/*
|
||||
* The variable name is, by definition, a NULL-terminated
|
||||
* string, so make absolutely sure that variable_name_size is
|
||||
* the value we expect it to be. If not, return the real size.
|
||||
*/
|
||||
for (len = 2; len <= variable_name_size; len += sizeof(c)) {
|
||||
c = variable_name[(len / sizeof(c)) - 1];
|
||||
if (!c)
|
||||
break;
|
||||
}
|
||||
|
||||
return min(len, variable_name_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a warning when duplicate EFI variables are encountered and
|
||||
* disable the sysfs workqueue since the firmware is buggy.
|
||||
*/
|
||||
static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid,
|
||||
unsigned long len16)
|
||||
{
|
||||
size_t i, len8 = len16 / sizeof(efi_char16_t);
|
||||
char *str8;
|
||||
|
||||
str8 = kzalloc(len8, GFP_KERNEL);
|
||||
if (!str8)
|
||||
return;
|
||||
|
||||
for (i = 0; i < len8; i++)
|
||||
str8[i] = str16[i];
|
||||
|
||||
printk(KERN_WARNING "efivars: duplicate variable: %s-%pUl\n",
|
||||
str8, vendor_guid);
|
||||
kfree(str8);
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_init - build the initial list of EFI variables
|
||||
* @func: callback function to invoke for every variable
|
||||
* @data: function-specific data to pass to @func
|
||||
* @duplicates: error if we encounter duplicates on @head?
|
||||
* @head: initialised head of variable list
|
||||
*
|
||||
* Get every EFI variable from the firmware and invoke @func. @func
|
||||
* should call efivar_entry_add() to build the list of variables.
|
||||
*
|
||||
* Returns 0 on success, or a kernel error code on failure.
|
||||
*/
|
||||
int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
|
||||
void *data, bool duplicates, struct list_head *head)
|
||||
{
|
||||
unsigned long variable_name_size = 1024;
|
||||
efi_char16_t *variable_name;
|
||||
efi_status_t status;
|
||||
efi_guid_t vendor_guid;
|
||||
int err = 0;
|
||||
|
||||
variable_name = kzalloc(variable_name_size, GFP_KERNEL);
|
||||
if (!variable_name) {
|
||||
printk(KERN_ERR "efivars: Memory allocation failed.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = efivar_lock();
|
||||
if (err)
|
||||
goto free;
|
||||
|
||||
/*
|
||||
* Per EFI spec, the maximum storage allocated for both
|
||||
* the variable name and variable data is 1024 bytes.
|
||||
*/
|
||||
|
||||
do {
|
||||
variable_name_size = 1024;
|
||||
|
||||
status = efivar_get_next_variable(&variable_name_size,
|
||||
variable_name,
|
||||
&vendor_guid);
|
||||
switch (status) {
|
||||
case EFI_SUCCESS:
|
||||
variable_name_size = var_name_strnsize(variable_name,
|
||||
variable_name_size);
|
||||
|
||||
/*
|
||||
* Some firmware implementations return the
|
||||
* same variable name on multiple calls to
|
||||
* get_next_variable(). Terminate the loop
|
||||
* immediately as there is no guarantee that
|
||||
* we'll ever see a different variable name,
|
||||
* and may end up looping here forever.
|
||||
*/
|
||||
if (duplicates &&
|
||||
variable_is_present(variable_name, &vendor_guid,
|
||||
head)) {
|
||||
dup_variable_bug(variable_name, &vendor_guid,
|
||||
variable_name_size);
|
||||
status = EFI_NOT_FOUND;
|
||||
} else {
|
||||
err = func(variable_name, vendor_guid,
|
||||
variable_name_size, data);
|
||||
if (err)
|
||||
status = EFI_NOT_FOUND;
|
||||
}
|
||||
break;
|
||||
case EFI_UNSUPPORTED:
|
||||
err = -EOPNOTSUPP;
|
||||
status = EFI_NOT_FOUND;
|
||||
break;
|
||||
case EFI_NOT_FOUND:
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING "efivars: get_next_variable: status=%lx\n",
|
||||
status);
|
||||
status = EFI_NOT_FOUND;
|
||||
break;
|
||||
}
|
||||
|
||||
} while (status != EFI_NOT_FOUND);
|
||||
|
||||
efivar_unlock();
|
||||
free:
|
||||
kfree(variable_name);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_entry_add - add entry to variable list
|
||||
* @entry: entry to add to list
|
||||
* @head: list head
|
||||
*
|
||||
* Returns 0 on success, or a kernel error code on failure.
|
||||
*/
|
||||
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = efivar_lock();
|
||||
if (err)
|
||||
return err;
|
||||
list_add(&entry->list, head);
|
||||
efivar_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* __efivar_entry_add - add entry to variable list
|
||||
* @entry: entry to add to list
|
||||
* @head: list head
|
||||
*/
|
||||
void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
|
||||
{
|
||||
list_add(&entry->list, head);
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_entry_remove - remove entry from variable list
|
||||
* @entry: entry to remove from list
|
||||
*
|
||||
* Returns 0 on success, or a kernel error code on failure.
|
||||
*/
|
||||
void efivar_entry_remove(struct efivar_entry *entry)
|
||||
{
|
||||
list_del(&entry->list);
|
||||
}
|
||||
|
||||
/*
|
||||
* efivar_entry_list_del_unlock - remove entry from variable list
|
||||
* @entry: entry to remove
|
||||
*
|
||||
* Remove @entry from the variable list and release the list lock.
|
||||
*
|
||||
* NOTE: slightly weird locking semantics here - we expect to be
|
||||
* called with the efivars lock already held, and we release it before
|
||||
* returning. This is because this function is usually called after
|
||||
* set_variable() while the lock is still held.
|
||||
*/
|
||||
static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
|
||||
{
|
||||
list_del(&entry->list);
|
||||
efivar_unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_entry_delete - delete variable and remove entry from list
|
||||
* @entry: entry containing variable to delete
|
||||
*
|
||||
* Delete the variable from the firmware and remove @entry from the
|
||||
* variable list. It is the caller's responsibility to free @entry
|
||||
* once we return.
|
||||
*
|
||||
* Returns 0 on success, -EINTR if we can't grab the semaphore,
|
||||
* converted EFI status code if set_variable() fails.
|
||||
*/
|
||||
int efivar_entry_delete(struct efivar_entry *entry)
|
||||
{
|
||||
efi_status_t status;
|
||||
int err;
|
||||
|
||||
err = efivar_lock();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
status = efivar_set_variable_locked(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
0, 0, NULL, false);
|
||||
if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) {
|
||||
efivar_unlock();
|
||||
return efi_status_to_err(status);
|
||||
}
|
||||
|
||||
efivar_entry_list_del_unlock(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_entry_size - obtain the size of a variable
|
||||
* @entry: entry for this variable
|
||||
* @size: location to store the variable's size
|
||||
*/
|
||||
int efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
|
||||
{
|
||||
efi_status_t status;
|
||||
int err;
|
||||
|
||||
*size = 0;
|
||||
|
||||
err = efivar_lock();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
status = efivar_get_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid, NULL, size, NULL);
|
||||
efivar_unlock();
|
||||
|
||||
if (status != EFI_BUFFER_TOO_SMALL)
|
||||
return efi_status_to_err(status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* __efivar_entry_get - call get_variable()
|
||||
* @entry: read data for this variable
|
||||
* @attributes: variable attributes
|
||||
* @size: size of @data buffer
|
||||
* @data: buffer to store variable data
|
||||
*
|
||||
* The caller MUST call efivar_entry_iter_begin() and
|
||||
* efivar_entry_iter_end() before and after the invocation of this
|
||||
* function, respectively.
|
||||
*/
|
||||
int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
unsigned long *size, void *data)
|
||||
{
|
||||
efi_status_t status;
|
||||
|
||||
status = efivar_get_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
attributes, size, data);
|
||||
|
||||
return efi_status_to_err(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_entry_get - call get_variable()
|
||||
* @entry: read data for this variable
|
||||
* @attributes: variable attributes
|
||||
* @size: size of @data buffer
|
||||
* @data: buffer to store variable data
|
||||
*/
|
||||
int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
unsigned long *size, void *data)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = efivar_lock();
|
||||
if (err)
|
||||
return err;
|
||||
err = __efivar_entry_get(entry, attributes, size, data);
|
||||
efivar_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_entry_set_get_size - call set_variable() and get new size (atomic)
|
||||
* @entry: entry containing variable to set and get
|
||||
* @attributes: attributes of variable to be written
|
||||
* @size: size of data buffer
|
||||
* @data: buffer containing data to write
|
||||
* @set: did the set_variable() call succeed?
|
||||
*
|
||||
* This is a pretty special (complex) function. See efivarfs_file_write().
|
||||
*
|
||||
* Atomically call set_variable() for @entry and if the call is
|
||||
* successful, return the new size of the variable from get_variable()
|
||||
* in @size. The success of set_variable() is indicated by @set.
|
||||
*
|
||||
* Returns 0 on success, -EINVAL if the variable data is invalid,
|
||||
* -ENOSPC if the firmware does not have enough available space, or a
|
||||
* converted EFI status code if either of set_variable() or
|
||||
* get_variable() fail.
|
||||
*
|
||||
* If the EFI variable does not exist when calling set_variable()
|
||||
* (EFI_NOT_FOUND), @entry is removed from the variable list.
|
||||
*/
|
||||
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
|
||||
unsigned long *size, void *data, bool *set)
|
||||
{
|
||||
efi_char16_t *name = entry->var.VariableName;
|
||||
efi_guid_t *vendor = &entry->var.VendorGuid;
|
||||
efi_status_t status;
|
||||
int err;
|
||||
|
||||
*set = false;
|
||||
|
||||
if (efivar_validate(*vendor, name, data, *size) == false)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The lock here protects the get_variable call, the conditional
|
||||
* set_variable call, and removal of the variable from the efivars
|
||||
* list (in the case of an authenticated delete).
|
||||
*/
|
||||
err = efivar_lock();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Ensure that the available space hasn't shrunk below the safe level
|
||||
*/
|
||||
status = check_var_size(attributes, *size + ucs2_strsize(name, 1024));
|
||||
if (status != EFI_SUCCESS) {
|
||||
if (status != EFI_UNSUPPORTED) {
|
||||
err = efi_status_to_err(status);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (*size > 65536) {
|
||||
err = -ENOSPC;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
status = efivar_set_variable_locked(name, vendor, attributes, *size,
|
||||
data, false);
|
||||
if (status != EFI_SUCCESS) {
|
||||
err = efi_status_to_err(status);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*set = true;
|
||||
|
||||
/*
|
||||
* Writing to the variable may have caused a change in size (which
|
||||
* could either be an append or an overwrite), or the variable to be
|
||||
* deleted. Perform a GetVariable() so we can tell what actually
|
||||
* happened.
|
||||
*/
|
||||
*size = 0;
|
||||
status = efivar_get_variable(entry->var.VariableName,
|
||||
&entry->var.VendorGuid,
|
||||
NULL, size, NULL);
|
||||
|
||||
if (status == EFI_NOT_FOUND)
|
||||
efivar_entry_list_del_unlock(entry);
|
||||
else
|
||||
efivar_unlock();
|
||||
|
||||
if (status && status != EFI_BUFFER_TOO_SMALL)
|
||||
return efi_status_to_err(status);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
efivar_unlock();
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* efivar_entry_iter - iterate over variable list
|
||||
* @func: callback function
|
||||
* @head: head of variable list
|
||||
* @data: function-specific data to pass to callback
|
||||
*
|
||||
* Iterate over the list of EFI variables and call @func with every
|
||||
* entry on the list. It is safe for @func to remove entries in the
|
||||
* list via efivar_entry_delete() while iterating.
|
||||
*
|
||||
* Some notes for the callback function:
|
||||
* - a non-zero return value indicates an error and terminates the loop
|
||||
* - @func is called from atomic context
|
||||
*/
|
||||
int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
|
||||
struct list_head *head, void *data)
|
||||
{
|
||||
struct efivar_entry *entry, *n;
|
||||
int err = 0;
|
||||
|
||||
err = efivar_lock();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
list_for_each_entry_safe(entry, n, head, list) {
|
||||
err = func(entry, data);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
efivar_unlock();
|
||||
|
||||
return err;
|
||||
}
|
@ -1030,27 +1030,6 @@ struct efivars {
|
||||
|
||||
#define EFI_VAR_NAME_LEN 1024
|
||||
|
||||
struct efi_variable {
|
||||
efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
|
||||
efi_guid_t VendorGuid;
|
||||
unsigned long DataSize;
|
||||
__u8 Data[1024];
|
||||
efi_status_t Status;
|
||||
__u32 Attributes;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct efivar_entry {
|
||||
struct efi_variable var;
|
||||
struct list_head list;
|
||||
struct kobject kobj;
|
||||
};
|
||||
|
||||
static inline void
|
||||
efivar_unregister(struct efivar_entry *var)
|
||||
{
|
||||
kobject_put(&var->kobj);
|
||||
}
|
||||
|
||||
int efivars_register(struct efivars *efivars,
|
||||
const struct efivar_operations *ops,
|
||||
struct kobject *kobject);
|
||||
@ -1058,41 +1037,6 @@ int efivars_unregister(struct efivars *efivars);
|
||||
struct kobject *efivars_kobject(void);
|
||||
|
||||
int efivar_supports_writes(void);
|
||||
int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
|
||||
void *data, bool duplicates, struct list_head *head);
|
||||
|
||||
int efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
|
||||
void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head);
|
||||
void efivar_entry_remove(struct efivar_entry *entry);
|
||||
|
||||
int __efivar_entry_delete(struct efivar_entry *entry);
|
||||
int efivar_entry_delete(struct efivar_entry *entry);
|
||||
|
||||
int efivar_entry_size(struct efivar_entry *entry, unsigned long *size);
|
||||
int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
unsigned long *size, void *data);
|
||||
int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
|
||||
unsigned long *size, void *data);
|
||||
int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
|
||||
unsigned long size, void *data, struct list_head *head);
|
||||
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
|
||||
unsigned long *size, void *data, bool *set);
|
||||
int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
|
||||
bool block, unsigned long size, void *data);
|
||||
|
||||
int efivar_entry_iter_begin(void);
|
||||
void efivar_entry_iter_end(void);
|
||||
|
||||
int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
|
||||
struct list_head *head, void *data);
|
||||
|
||||
struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
|
||||
struct list_head *head, bool remove);
|
||||
|
||||
bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
|
||||
unsigned long data_size);
|
||||
bool efivar_variable_is_removable(efi_guid_t vendor, const char *name,
|
||||
size_t len);
|
||||
|
||||
int efivar_lock(void);
|
||||
int efivar_trylock(void);
|
||||
|
Loading…
Reference in New Issue
Block a user