public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* Update UEFI variable support
@ 2011-12-14 21:06 Matthew Garrett
  2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett
                   ` (3 more replies)
  0 siblings, 4 replies; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw)
  To: linux-kernel; +Cc: mikew, x86, hpa

Our EFI variable support is artifically limited at the moment. This patchset
adds support for arbitrary variable sizes, limited only by what the platform
supports. It also fixes a case where we were unnecessarily fiddling with
sysfs while in the process of shutting down or crashing, which triggered
some other issues.


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 1/4] uefi: Populate runtime_version
  2011-12-14 21:06 Update UEFI variable support Matthew Garrett
@ 2011-12-14 21:06 ` Matthew Garrett
  2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw)
  To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett

The runtime_version field isn't getting populated, causing various UEFI
calls to fail. Fix.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
---
 arch/x86/platform/efi/efi.c |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index 37718f0..4294cb5 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -552,6 +552,8 @@ void __init efi_init(void)
 		 * virtual mode.
 		 */
 		efi.get_time = phys_efi_get_time;
+
+		efi.runtime_version = runtime->hdr.revision;
 	} else
 		printk(KERN_ERR "Could not map the EFI runtime service "
 		       "table!\n");
-- 
1.7.7.1


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 2/4] efi: Only create sysfs entries while booting or running
  2011-12-14 21:06 Update UEFI variable support Matthew Garrett
  2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett
@ 2011-12-14 21:06 ` Matthew Garrett
  2011-12-14 21:32   ` Mike Waychison
  2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett
  2011-12-14 21:06 ` [PATCH 4/4] EFI: Add support for QueryVariableInfo() call Matthew Garrett
  3 siblings, 1 reply; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw)
  To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett

There's no point in creating sysfs entries while the machine's on its way
down. Skip it in that case in order to avoid complexity.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
---
 drivers/firmware/efivars.c |   17 ++++++++++++-----
 1 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index b0a8117..eb07f13 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -552,11 +552,18 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id,
 	if (found)
 		efivar_unregister(found);
 
-	if (size)
-		ret = efivar_create_sysfs_entry(efivars,
-					  utf16_strsize(efi_name,
-							DUMP_NAME_LEN * 2),
-					  efi_name, &vendor);
+	if (size) {
+		switch (system_state) {
+		case SYSTEM_BOOTING:
+		case SYSTEM_RUNNING:
+			ret = efivar_create_sysfs_entry(efivars,
+							utf16_strsize(efi_name,
+							    DUMP_NAME_LEN * 2),
+							efi_name, &vendor);
+		default:
+			break;
+		}
+	}
 
 	*id = part;
 	return ret;
-- 
1.7.7.1


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 21:06 Update UEFI variable support Matthew Garrett
  2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett
  2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett
@ 2011-12-14 21:06 ` Matthew Garrett
  2011-12-14 22:14   ` Mike Waychison
  2011-12-14 21:06 ` [PATCH 4/4] EFI: Add support for QueryVariableInfo() call Matthew Garrett
  3 siblings, 1 reply; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw)
  To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett

The EFI variables code has a hard limit of 1024 bytes for variable length.
This restriction only existed in version 0.99 of the EFI specification,
and is not relevant on any existing hardware. Add support for a longer
structure that incorporates the existing one, allowing old userspace to
carry on working while allowing newer userspace to write larger variables
via the same interface.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
---
 drivers/firmware/efivars.c |  240 ++++++++++++++++++++++++++++----------------
 1 files changed, 154 insertions(+), 86 deletions(-)

diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index eb07f13..1bef80c 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -92,13 +92,6 @@ MODULE_VERSION(EFIVARS_VERSION);
 
 #define DUMP_NAME_LEN 52
 
-/*
- * The maximum size of VariableName + Data = 1024
- * Therefore, it's reasonable to save that much
- * space in each part of the structure,
- * and we use a page for reading/writing.
- */
-
 struct efi_variable {
 	efi_char16_t  VariableName[1024/sizeof(efi_char16_t)];
 	efi_guid_t    VendorGuid;
@@ -108,10 +101,20 @@ struct efi_variable {
 	__u32         Attributes;
 } __attribute__((packed));
 
+/*
+ * Older versions of this driver had a fixed 1024-byte buffer. We need to
+ * maintain compatibility with old userspace, so we provide an extended
+ * structure to allow userspace to write larger variables
+ */
+
+struct extended_efi_variable {
+	struct efi_variable header;
+	__u8 Data[0];
+} __attribute__((packed));
 
 struct efivar_entry {
 	struct efivars *efivars;
-	struct efi_variable var;
+	struct extended_efi_variable *var;
 	struct list_head list;
 	struct kobject kobj;
 };
@@ -192,21 +195,41 @@ utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len)
 }
 
 static efi_status_t
-get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
+get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var)
 {
 	efi_status_t status;
+	unsigned long length;
+
+	if (!*var)
+		*var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL);
+
+	(*var)->header.DataSize = 0;
+	status = efivars->ops->get_variable((*var)->header.VariableName,
+					    &(*var)->header.VendorGuid,
+					    &(*var)->header.Attributes,
+					    &(*var)->header.DataSize,
+					    (*var)->Data);
+
+	if (status == EFI_BUFFER_TOO_SMALL) {
+		*var = krealloc(*var, sizeof(struct extended_efi_variable) +
+				(*var)->header.DataSize, GFP_KERNEL);
+		status = efivars->ops->get_variable((*var)->header.VariableName,
+						    &(*var)->header.VendorGuid,
+						    &(*var)->header.Attributes,
+						    &(*var)->header.DataSize,
+						    (*var)->Data);
+	}
+
+	length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize :
+		1024;
+
+	memcpy(&(*var)->header.Data, &(*var)->Data, length);
 
-	var->DataSize = 1024;
-	status = efivars->ops->get_variable(var->VariableName,
-					    &var->VendorGuid,
-					    &var->Attributes,
-					    &var->DataSize,
-					    var->Data);
 	return status;
 }
 
 static efi_status_t
-get_var_data(struct efivars *efivars, struct efi_variable *var)
+get_var_data(struct efivars *efivars, struct extended_efi_variable **var)
 {
 	efi_status_t status;
 
@@ -224,13 +247,13 @@ get_var_data(struct efivars *efivars, struct efi_variable *var)
 static ssize_t
 efivar_guid_read(struct efivar_entry *entry, char *buf)
 {
-	struct efi_variable *var = &entry->var;
+	struct extended_efi_variable *var = entry->var;
 	char *str = buf;
 
 	if (!entry || !buf)
 		return 0;
 
-	efi_guid_unparse(&var->VendorGuid, str);
+	efi_guid_unparse(&var->header.VendorGuid, str);
 	str += strlen(str);
 	str += sprintf(str, "\n");
 
@@ -240,22 +263,22 @@ efivar_guid_read(struct efivar_entry *entry, char *buf)
 static ssize_t
 efivar_attr_read(struct efivar_entry *entry, char *buf)
 {
-	struct efi_variable *var = &entry->var;
+	struct extended_efi_variable *var = entry->var;
 	char *str = buf;
 	efi_status_t status;
 
 	if (!entry || !buf)
 		return -EINVAL;
 
-	status = get_var_data(entry->efivars, var);
+	status = get_var_data(entry->efivars, &var);
 	if (status != EFI_SUCCESS)
 		return -EIO;
 
-	if (var->Attributes & 0x1)
+	if (var->header.Attributes & 0x1)
 		str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
-	if (var->Attributes & 0x2)
+	if (var->header.Attributes & 0x2)
 		str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
-	if (var->Attributes & 0x4)
+	if (var->header.Attributes & 0x4)
 		str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
 	return str - buf;
 }
@@ -263,36 +286,36 @@ efivar_attr_read(struct efivar_entry *entry, char *buf)
 static ssize_t
 efivar_size_read(struct efivar_entry *entry, char *buf)
 {
-	struct efi_variable *var = &entry->var;
+	struct extended_efi_variable *var = entry->var;
 	char *str = buf;
 	efi_status_t status;
 
 	if (!entry || !buf)
 		return -EINVAL;
 
-	status = get_var_data(entry->efivars, var);
+	status = get_var_data(entry->efivars, &var);
 	if (status != EFI_SUCCESS)
 		return -EIO;
 
-	str += sprintf(str, "0x%lx\n", var->DataSize);
+	str += sprintf(str, "0x%lx\n", var->header.DataSize);
 	return str - buf;
 }
 
 static ssize_t
 efivar_data_read(struct efivar_entry *entry, char *buf)
 {
-	struct efi_variable *var = &entry->var;
+	struct extended_efi_variable *var = entry->var;
 	efi_status_t status;
 
 	if (!entry || !buf)
 		return -EINVAL;
 
-	status = get_var_data(entry->efivars, var);
+	status = get_var_data(entry->efivars, &var);
 	if (status != EFI_SUCCESS)
 		return -EIO;
 
-	memcpy(buf, var->Data, var->DataSize);
-	return var->DataSize;
+	memcpy(buf, var->Data, var->header.DataSize);
+	return var->header.DataSize;
 }
 /*
  * We allow each variable to be edited via rewriting the
@@ -301,34 +324,46 @@ efivar_data_read(struct efivar_entry *entry, char *buf)
 static ssize_t
 efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
 {
-	struct efi_variable *new_var, *var = &entry->var;
+	struct extended_efi_variable *new_var, *var = entry->var;
+	struct efi_variable *tmp_var = NULL;
 	struct efivars *efivars = entry->efivars;
 	efi_status_t status = EFI_NOT_FOUND;
+	int error = 0;
 
-	if (count != sizeof(struct efi_variable))
+	if (count == sizeof(struct efi_variable)) {
+		tmp_var = (struct efi_variable *)buf;
+		new_var = kmalloc(sizeof(struct efi_variable) +
+				  tmp_var->DataSize, GFP_KERNEL);
+		memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable));
+		memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize);
+	} else if (count > sizeof(struct efi_variable)) {
+		new_var = (struct extended_efi_variable *)buf;
+	} else {
 		return -EINVAL;
+	}
 
-	new_var = (struct efi_variable *)buf;
 	/*
 	 * If only updating the variable data, then the name
 	 * and guid should remain the same
 	 */
-	if (memcmp(new_var->VariableName, var->VariableName, sizeof(var->VariableName)) ||
-		efi_guidcmp(new_var->VendorGuid, var->VendorGuid)) {
+	if (memcmp(new_var->header.VariableName, var->header.VariableName, sizeof(var->header.VariableName)) ||
+		efi_guidcmp(new_var->header.VendorGuid, var->header.VendorGuid)) {
 		printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
-		return -EINVAL;
+		error = -EINVAL;
+		goto out;
 	}
 
-	if ((new_var->DataSize <= 0) || (new_var->Attributes == 0)){
+	if ((new_var->header.DataSize <= 0) || (new_var->header.Attributes == 0)) {
 		printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
-		return -EINVAL;
+		error = -EINVAL;
+		goto out;
 	}
 
 	spin_lock(&efivars->lock);
-	status = efivars->ops->set_variable(new_var->VariableName,
-					    &new_var->VendorGuid,
-					    new_var->Attributes,
-					    new_var->DataSize,
+	status = efivars->ops->set_variable(new_var->header.VariableName,
+					    &new_var->header.VendorGuid,
+					    new_var->header.Attributes,
+					    new_var->header.DataSize,
 					    new_var->Data);
 
 	spin_unlock(&efivars->lock);
@@ -336,28 +371,37 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
 	if (status != EFI_SUCCESS) {
 		printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
 			status);
-		return -EIO;
+		error = -EIO;
+		goto out;
 	}
 
-	memcpy(&entry->var, new_var, count);
+	memcpy(entry->var, new_var, count);
+out:
+	/* Free the extended structure if we needed to allocate it */
+	if (tmp_var && new_var)
+		kfree(new_var);
+
+	if (error)
+		return error;
+
 	return count;
 }
 
 static ssize_t
 efivar_show_raw(struct efivar_entry *entry, char *buf)
 {
-	struct efi_variable *var = &entry->var;
+	struct extended_efi_variable *var = entry->var;
 	efi_status_t status;
 
 	if (!entry || !buf)
 		return 0;
 
-	status = get_var_data(entry->efivars, var);
+	status = get_var_data(entry->efivars, &var);
 	if (status != EFI_SUCCESS)
 		return -EIO;
 
-	memcpy(buf, var, sizeof(*var));
-	return sizeof(*var);
+	memcpy(buf, var, sizeof(*var) + var->header.DataSize);
+	return sizeof(*var) + var->header.DataSize;
 }
 
 /*
@@ -404,6 +448,7 @@ static const struct sysfs_ops efivar_attr_ops = {
 static void efivar_release(struct kobject *kobj)
 {
 	struct efivar_entry *var = container_of(kobj, struct efivar_entry, kobj);
+	kfree(var->var);
 	kfree(var);
 }
 
@@ -468,21 +513,21 @@ static ssize_t efi_pstore_read(u64 *id, enum pstore_type_id *type,
 	unsigned long time;
 
 	while (&efivars->walk_entry->list != &efivars->list) {
-		if (!efi_guidcmp(efivars->walk_entry->var.VendorGuid,
+		if (!efi_guidcmp(efivars->walk_entry->var->header.VendorGuid,
 				 vendor)) {
 			for (i = 0; i < DUMP_NAME_LEN; i++) {
-				name[i] = efivars->walk_entry->var.VariableName[i];
+				name[i] = efivars->walk_entry->var->header.VariableName[i];
 			}
 			if (sscanf(name, "dump-type%u-%u-%lu", type, &part, &time) == 3) {
 				*id = part;
 				timespec->tv_sec = time;
 				timespec->tv_nsec = 0;
 				get_var_data_locked(efivars, &efivars->walk_entry->var);
-				size = efivars->walk_entry->var.DataSize;
+				size = efivars->walk_entry->var->header.DataSize;
 				*buf = kmalloc(size, GFP_KERNEL);
 				if (*buf == NULL)
 					return -ENOMEM;
-				memcpy(*buf, efivars->walk_entry->var.Data,
+				memcpy(*buf, efivars->walk_entry->var->Data,
 				       size);
 				efivars->walk_entry = list_entry(efivars->walk_entry->list.next,
 					           struct efivar_entry, list);
@@ -521,19 +566,19 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id,
 	list_for_each_entry(entry, &efivars->list, list) {
 		get_var_data_locked(efivars, &entry->var);
 
-		if (efi_guidcmp(entry->var.VendorGuid, vendor))
+		if (efi_guidcmp(entry->var->header.VendorGuid, vendor))
 			continue;
-		if (utf16_strncmp(entry->var.VariableName, efi_name,
+		if (utf16_strncmp(entry->var->header.VariableName, efi_name,
 				  utf16_strlen(efi_name)))
 			continue;
 		/* Needs to be a prefix */
-		if (entry->var.VariableName[utf16_strlen(efi_name)] == 0)
+		if (entry->var->header.VariableName[utf16_strlen(efi_name)] == 0)
 			continue;
 
 		/* found */
 		found = entry;
-		efivars->ops->set_variable(entry->var.VariableName,
-					   &entry->var.VendorGuid,
+		efivars->ops->set_variable(entry->var->header.VariableName,
+					   &entry->var->header.VendorGuid,
 					   PSTORE_EFI_ATTRIBUTES,
 					   0, NULL);
 	}
@@ -552,7 +597,7 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id,
 	if (found)
 		efivar_unregister(found);
 
-	if (size) {
+	if (size)
 		switch (system_state) {
 		case SYSTEM_BOOTING:
 		case SYSTEM_RUNNING:
@@ -563,7 +608,6 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id,
 		default:
 			break;
 		}
-	}
 
 	*id = part;
 	return ret;
@@ -621,62 +665,89 @@ 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 efi_variable *new_var = (struct efi_variable *)buf;
+	struct efi_variable *new_var = NULL;
+	struct extended_efi_variable *ext_new_var = NULL;
 	struct efivars *efivars = bin_attr->private;
 	struct efivar_entry *search_efivar, *n;
 	unsigned long strsize1, strsize2;
 	efi_status_t status = EFI_NOT_FOUND;
 	int found = 0;
+	int error = 0;
 
 	if (!capable(CAP_SYS_ADMIN))
 		return -EACCES;
 
+	if (count > sizeof(struct efi_variable))
+		ext_new_var = (struct extended_efi_variable *)buf;
+	else if (count == sizeof(struct efi_variable))
+		new_var = (struct efi_variable *)buf;
+	else
+		return -EINVAL;
+
 	spin_lock(&efivars->lock);
 
+	if (new_var) {
+		ext_new_var = kmalloc(sizeof(struct extended_efi_variable) +
+				      new_var->DataSize, GFP_KERNEL);
+		memcpy(&ext_new_var->header, new_var, sizeof(struct efi_variable));
+		memcpy(ext_new_var->Data, new_var->Data, new_var->DataSize);
+	}
+
 	/*
 	 * Does this variable already exist?
 	 */
 	list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
-		strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
-		strsize2 = utf16_strsize(new_var->VariableName, 1024);
+		strsize1 = utf16_strsize(search_efivar->var->header.VariableName, 1024);
+		strsize2 = utf16_strsize(ext_new_var->header.VariableName, 1024);
 		if (strsize1 == strsize2 &&
-			!memcmp(&(search_efivar->var.VariableName),
-				new_var->VariableName, strsize1) &&
-			!efi_guidcmp(search_efivar->var.VendorGuid,
-				new_var->VendorGuid)) {
+			!memcmp(&(search_efivar->var->header.VariableName),
+				ext_new_var->header.VariableName, strsize1) &&
+			!efi_guidcmp(search_efivar->var->header.VendorGuid,
+				ext_new_var->header.VendorGuid)) {
 			found = 1;
 			break;
 		}
 	}
 	if (found) {
 		spin_unlock(&efivars->lock);
-		return -EINVAL;
+		error = -EINVAL;
+		goto out;
 	}
 
 	/* now *really* create the variable via EFI */
-	status = efivars->ops->set_variable(new_var->VariableName,
-					    &new_var->VendorGuid,
-					    new_var->Attributes,
-					    new_var->DataSize,
-					    new_var->Data);
+	status = efivars->ops->set_variable(ext_new_var->header.VariableName,
+					    &ext_new_var->header.VendorGuid,
+					    ext_new_var->header.Attributes,
+					    ext_new_var->header.DataSize,
+					    ext_new_var->Data);
 
 	if (status != EFI_SUCCESS) {
 		printk(KERN_WARNING "efivars: set_variable() failed: status=%lx\n",
 			status);
 		spin_unlock(&efivars->lock);
-		return -EIO;
+		error = -EIO;
+		goto out;
 	}
 	spin_unlock(&efivars->lock);
 
 	/* Create the entry in sysfs.  Locking is not required here */
 	status = efivar_create_sysfs_entry(efivars,
-					   utf16_strsize(new_var->VariableName,
+					   utf16_strsize(ext_new_var->header.VariableName,
 							 1024),
-					   new_var->VariableName,
-					   &new_var->VendorGuid);
+					   ext_new_var->header.VariableName,
+					   &ext_new_var->header.VendorGuid);
 	if (status) {
 		printk(KERN_WARNING "efivars: variable created, but sysfs entry wasn't.\n");
 	}
+
+out:
+	/* Free the temporary structure if we needed it */
+	if (new_var && ext_new_var)
+		kfree(ext_new_var);
+
+	if (error)
+		return error;
+
 	return count;
 }
 
@@ -700,12 +771,12 @@ static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
 	 * Does this variable already exist?
 	 */
 	list_for_each_entry_safe(search_efivar, n, &efivars->list, list) {
-		strsize1 = utf16_strsize(search_efivar->var.VariableName, 1024);
+		strsize1 = utf16_strsize(search_efivar->var->header.VariableName, 1024);
 		strsize2 = utf16_strsize(del_var->VariableName, 1024);
 		if (strsize1 == strsize2 &&
-			!memcmp(&(search_efivar->var.VariableName),
+			!memcmp(&(search_efivar->var->header.VariableName),
 				del_var->VariableName, strsize1) &&
-			!efi_guidcmp(search_efivar->var.VendorGuid,
+			!efi_guidcmp(search_efivar->var->header.VendorGuid,
 				del_var->VendorGuid)) {
 			found = 1;
 			break;
@@ -813,9 +884,11 @@ efivar_create_sysfs_entry(struct efivars *efivars,
 	}
 
 	new_efivar->efivars = efivars;
-	memcpy(new_efivar->var.VariableName, variable_name,
+	new_efivar->var = kzalloc(sizeof(struct extended_efi_variable),
+				  GFP_KERNEL);
+	memcpy(new_efivar->var->header.VariableName, variable_name,
 		variable_name_size);
-	memcpy(&(new_efivar->var.VendorGuid), vendor_guid, sizeof(efi_guid_t));
+	memcpy(&(new_efivar->var->header.VendorGuid), vendor_guid, sizeof(efi_guid_t));
 
 	/* Convert Unicode to normal chars (assume top bits are 0),
 	   ala UTF-8 */
@@ -954,11 +1027,6 @@ int register_efivars(struct efivars *efivars,
 		goto out;
 	}
 
-	/*
-	 * Per EFI spec, the maximum storage allocated for both
-	 * the variable name and variable data is 1024 bytes.
-	 */
-
 	do {
 		variable_name_size = 1024;
 
-- 
1.7.7.1


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 4/4] EFI: Add support for QueryVariableInfo() call
  2011-12-14 21:06 Update UEFI variable support Matthew Garrett
                   ` (2 preceding siblings ...)
  2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett
@ 2011-12-14 21:06 ` Matthew Garrett
  3 siblings, 0 replies; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 21:06 UTC (permalink / raw)
  To: linux-kernel; +Cc: mikew, x86, hpa, Matthew Garrett

UEFI 2.0 and later provides a call for identifying hardware variable
capabilities. Use this to work out the maximum size of the variables we
can store, and throw errors where appropriate.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
---
 drivers/firmware/efivars.c     |   53 +++++++++++++++++++++++++++++++++++++---
 drivers/firmware/google/gsmi.c |    9 +++++++
 include/linux/efi.h            |    1 +
 3 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index 1bef80c..805dba7 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -327,8 +327,9 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
 	struct extended_efi_variable *new_var, *var = entry->var;
 	struct efi_variable *tmp_var = NULL;
 	struct efivars *efivars = entry->efivars;
-	efi_status_t status = EFI_NOT_FOUND;
+	efi_status_t status;
 	int error = 0;
+	u64 storage_space, remaining_space, max_variable_size, size;
 
 	if (count == sizeof(struct efi_variable)) {
 		tmp_var = (struct efi_variable *)buf;
@@ -360,6 +361,22 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
 	}
 
 	spin_lock(&efivars->lock);
+
+	size = new_var->header.DataSize +
+		utf16_strnlen(new_var->header.VariableName, 512) * 2;
+
+	status = efivars->ops->query_variable_info(new_var->header.Attributes,
+						   &storage_space,
+						   &remaining_space,
+						   &max_variable_size);
+	if (status == EFI_SUCCESS) {
+		if (size > remaining_space || size > max_variable_size) {
+			spin_unlock(&efivars->lock);
+			error = -ENOSPC;
+			goto out;
+		}
+	}
+
 	status = efivars->ops->set_variable(new_var->header.VariableName,
 					    &new_var->header.VendorGuid,
 					    new_var->header.Attributes,
@@ -670,9 +687,10 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
 	struct efivars *efivars = bin_attr->private;
 	struct efivar_entry *search_efivar, *n;
 	unsigned long strsize1, strsize2;
-	efi_status_t status = EFI_NOT_FOUND;
+	efi_status_t status;
 	int found = 0;
 	int error = 0;
+	u64 storage_space, remaining_space, max_variable_size, size;
 
 	if (!capable(CAP_SYS_ADMIN))
 		return -EACCES;
@@ -693,6 +711,21 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
 		memcpy(ext_new_var->Data, new_var->Data, new_var->DataSize);
 	}
 
+	size = ext_new_var->header.DataSize +
+		utf16_strnlen(ext_new_var->header.VariableName, 512) * 2;
+
+	status = efivars->ops->query_variable_info(ext_new_var->header.Attributes,
+						   &storage_space,
+						   &remaining_space,
+						   &max_variable_size);
+	if (status == EFI_SUCCESS) {
+		if (size > remaining_space || size > max_variable_size) {
+			spin_unlock(&efivars->lock);
+			error = -ENOSPC;
+			goto out;
+		}
+	}
+
 	/*
 	 * Does this variable already exist?
 	 */
@@ -1009,6 +1042,7 @@ int register_efivars(struct efivars *efivars,
 	efi_char16_t *variable_name;
 	unsigned long variable_name_size = 1024;
 	int error = 0;
+	u64 storage_space, remaining_space, max_variable_size;
 
 	variable_name = kzalloc(variable_name_size, GFP_KERNEL);
 	if (!variable_name) {
@@ -1056,9 +1090,19 @@ int register_efivars(struct efivars *efivars,
 
 	efivars->efi_pstore_info = efi_pstore_info;
 
-	efivars->efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
+	status = efivars->ops->query_variable_info(PSTORE_EFI_ATTRIBUTES,
+						   &storage_space,
+						   &remaining_space,
+						   &max_variable_size);
+	if (status != EFI_SUCCESS)
+		max_variable_size = 1024;
+
+	max_variable_size -= DUMP_NAME_LEN * 2;
+
+	efivars->efi_pstore_info.buf = kmalloc(max_variable_size, GFP_KERNEL);
+
 	if (efivars->efi_pstore_info.buf) {
-		efivars->efi_pstore_info.bufsize = 1024;
+		efivars->efi_pstore_info.bufsize = max_variable_size;
 		efivars->efi_pstore_info.data = efivars;
 		spin_lock_init(&efivars->efi_pstore_info.buf_lock);
 		pstore_register(&efivars->efi_pstore_info);
@@ -1103,6 +1147,7 @@ efivars_init(void)
 	ops.get_variable = efi.get_variable;
 	ops.set_variable = efi.set_variable;
 	ops.get_next_variable = efi.get_next_variable;
+	ops.query_variable_info = efi.query_variable_info;
 	error = register_efivars(&__efivars, &ops, efi_kobj);
 	if (error)
 		goto err_put;
diff --git a/drivers/firmware/google/gsmi.c b/drivers/firmware/google/gsmi.c
index c4e7c59..8600e3f 100644
--- a/drivers/firmware/google/gsmi.c
+++ b/drivers/firmware/google/gsmi.c
@@ -419,6 +419,14 @@ static efi_status_t gsmi_get_next_variable(unsigned long *name_size,
 	return ret;
 }
 
+static efi_status_t gsmi_query_variable_info(u32 attr,
+					     u64 *storage_space,
+					     u64 *remaining_space,
+					     u64 *max_variable_size)
+{
+	return EFI_UNSUPPORTED;
+}
+
 static efi_status_t gsmi_set_variable(efi_char16_t *name,
 				      efi_guid_t *vendor,
 				      u32 attr,
@@ -473,6 +481,7 @@ static const struct efivar_operations efivar_ops = {
 	.get_variable = gsmi_get_variable,
 	.set_variable = gsmi_set_variable,
 	.get_next_variable = gsmi_get_next_variable,
+	.query_variable_info = gsmi_query_variable_info,
 };
 
 static ssize_t eventlog_write(struct file *filp, struct kobject *kobj,
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 2362a0b..f3f3860 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -446,6 +446,7 @@ struct efivar_operations {
 	efi_get_variable_t *get_variable;
 	efi_get_next_variable_t *get_next_variable;
 	efi_set_variable_t *set_variable;
+	efi_query_variable_info_t *query_variable_info;
 };
 
 struct efivars {
-- 
1.7.7.1


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/4] efi: Only create sysfs entries while booting or running
  2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett
@ 2011-12-14 21:32   ` Mike Waychison
  0 siblings, 0 replies; 20+ messages in thread
From: Mike Waychison @ 2011-12-14 21:32 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: linux-kernel, x86, hpa

On Wed, Dec 14, 2011 at 1:06 PM, Matthew Garrett <mjg@redhat.com> wrote:
> There's no point in creating sysfs entries while the machine's on its way
> down. Skip it in that case in order to avoid complexity.
>
> Signed-off-by: Matthew Garrett <mjg@redhat.com>

Acked-by: Mike Waychison <mikew@google.com>

> ---
>  drivers/firmware/efivars.c |   17 ++++++++++++-----
>  1 files changed, 12 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
> index b0a8117..eb07f13 100644
> --- a/drivers/firmware/efivars.c
> +++ b/drivers/firmware/efivars.c
> @@ -552,11 +552,18 @@ static int efi_pstore_write(enum pstore_type_id type, u64 *id,
>        if (found)
>                efivar_unregister(found);
>
> -       if (size)
> -               ret = efivar_create_sysfs_entry(efivars,
> -                                         utf16_strsize(efi_name,
> -                                                       DUMP_NAME_LEN * 2),
> -                                         efi_name, &vendor);
> +       if (size) {
> +               switch (system_state) {
> +               case SYSTEM_BOOTING:
> +               case SYSTEM_RUNNING:
> +                       ret = efivar_create_sysfs_entry(efivars,
> +                                                       utf16_strsize(efi_name,
> +                                                           DUMP_NAME_LEN * 2),
> +                                                       efi_name, &vendor);
> +               default:
> +                       break;
> +               }
> +       }
>
>        *id = part;
>        return ret;
> --
> 1.7.7.1
>

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett
@ 2011-12-14 22:14   ` Mike Waychison
  2011-12-14 22:39     ` Matthew Garrett
  0 siblings, 1 reply; 20+ messages in thread
From: Mike Waychison @ 2011-12-14 22:14 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: linux-kernel, x86, hpa

On Wed, Dec 14, 2011 at 1:06 PM, Matthew Garrett <mjg@redhat.com> wrote:
> The EFI variables code has a hard limit of 1024 bytes for variable length.
> This restriction only existed in version 0.99 of the EFI specification,
> and is not relevant on any existing hardware. Add support for a longer
> structure that incorporates the existing one, allowing old userspace to
> carry on working while allowing newer userspace to write larger variables
> via the same interface.
>
> Signed-off-by: Matthew Garrett <mjg@redhat.com>
> ---
>  drivers/firmware/efivars.c |  240 ++++++++++++++++++++++++++++----------------
>  1 files changed, 154 insertions(+), 86 deletions(-)
>
> diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
> index eb07f13..1bef80c 100644
> --- a/drivers/firmware/efivars.c
> +++ b/drivers/firmware/efivars.c
> @@ -92,13 +92,6 @@ MODULE_VERSION(EFIVARS_VERSION);
>
>  #define DUMP_NAME_LEN 52
>
> -/*
> - * The maximum size of VariableName + Data = 1024
> - * Therefore, it's reasonable to save that much
> - * space in each part of the structure,
> - * and we use a page for reading/writing.
> - */
> -
>  struct efi_variable {
>        efi_char16_t  VariableName[1024/sizeof(efi_char16_t)];
>        efi_guid_t    VendorGuid;
> @@ -108,10 +101,20 @@ struct efi_variable {
>        __u32         Attributes;
>  } __attribute__((packed));
>
> +/*
> + * Older versions of this driver had a fixed 1024-byte buffer. We need to
> + * maintain compatibility with old userspace, so we provide an extended
> + * structure to allow userspace to write larger variables
> + */
> +
> +struct extended_efi_variable {
> +       struct efi_variable header;
> +       __u8 Data[0];
> +} __attribute__((packed));
>
>  struct efivar_entry {
>        struct efivars *efivars;
> -       struct efi_variable var;
> +       struct extended_efi_variable *var;
>        struct list_head list;
>        struct kobject kobj;
>  };
> @@ -192,21 +195,41 @@ utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len)
>  }
>
>  static efi_status_t
> -get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
> +get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var)
>  {
>        efi_status_t status;
> +       unsigned long length;
> +
> +       if (!*var)
> +               *var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL);

Aren't we holding a spinlock here?

> +
> +       (*var)->header.DataSize = 0;
> +       status = efivars->ops->get_variable((*var)->header.VariableName,
> +                                           &(*var)->header.VendorGuid,
> +                                           &(*var)->header.Attributes,
> +                                           &(*var)->header.DataSize,
> +                                           (*var)->Data);

This doesn't look right.  ->Data here is after the Data[1024] buffer
embedded in (*var)->header, and a read into this buffer will corrupt
the heap.

> +
> +       if (status == EFI_BUFFER_TOO_SMALL) {
> +               *var = krealloc(*var, sizeof(struct extended_efi_variable) +
> +                               (*var)->header.DataSize, GFP_KERNEL);
> +               status = efivars->ops->get_variable((*var)->header.VariableName,
> +                                                   &(*var)->header.VendorGuid,
> +                                                   &(*var)->header.Attributes,
> +                                                   &(*var)->header.DataSize,
> +                                                   (*var)->Data);
> +       }
> +
> +       length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize :
> +               1024;
> +
> +       memcpy(&(*var)->header.Data, &(*var)->Data, length);

This memcpy clobbers the header.Data with the corrupted data when we
didn't use the second path.

>
> -       var->DataSize = 1024;
> -       status = efivars->ops->get_variable(var->VariableName,
> -                                           &var->VendorGuid,
> -                                           &var->Attributes,
> -                                           &var->DataSize,
> -                                           var->Data);
>        return status;
>  }
>
>  static efi_status_t
> -get_var_data(struct efivars *efivars, struct efi_variable *var)
> +get_var_data(struct efivars *efivars, struct extended_efi_variable **var)
>  {
>        efi_status_t status;
>
> @@ -224,13 +247,13 @@ get_var_data(struct efivars *efivars, struct efi_variable *var)
>  static ssize_t
>  efivar_guid_read(struct efivar_entry *entry, char *buf)
>  {
> -       struct efi_variable *var = &entry->var;
> +       struct extended_efi_variable *var = entry->var;
>        char *str = buf;
>
>        if (!entry || !buf)
>                return 0;
>
> -       efi_guid_unparse(&var->VendorGuid, str);
> +       efi_guid_unparse(&var->header.VendorGuid, str);
>        str += strlen(str);
>        str += sprintf(str, "\n");
>
> @@ -240,22 +263,22 @@ efivar_guid_read(struct efivar_entry *entry, char *buf)
>  static ssize_t
>  efivar_attr_read(struct efivar_entry *entry, char *buf)
>  {
> -       struct efi_variable *var = &entry->var;
> +       struct extended_efi_variable *var = entry->var;
>        char *str = buf;
>        efi_status_t status;
>
>        if (!entry || !buf)
>                return -EINVAL;
>
> -       status = get_var_data(entry->efivars, var);
> +       status = get_var_data(entry->efivars, &var);
>        if (status != EFI_SUCCESS)
>                return -EIO;
>
> -       if (var->Attributes & 0x1)
> +       if (var->header.Attributes & 0x1)
>                str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
> -       if (var->Attributes & 0x2)
> +       if (var->header.Attributes & 0x2)
>                str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
> -       if (var->Attributes & 0x4)
> +       if (var->header.Attributes & 0x4)
>                str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
>        return str - buf;
>  }
> @@ -263,36 +286,36 @@ efivar_attr_read(struct efivar_entry *entry, char *buf)
>  static ssize_t
>  efivar_size_read(struct efivar_entry *entry, char *buf)
>  {
> -       struct efi_variable *var = &entry->var;
> +       struct extended_efi_variable *var = entry->var;
>        char *str = buf;
>        efi_status_t status;
>
>        if (!entry || !buf)
>                return -EINVAL;
>
> -       status = get_var_data(entry->efivars, var);
> +       status = get_var_data(entry->efivars, &var);
>        if (status != EFI_SUCCESS)
>                return -EIO;
>
> -       str += sprintf(str, "0x%lx\n", var->DataSize);
> +       str += sprintf(str, "0x%lx\n", var->header.DataSize);
>        return str - buf;
>  }
>
>  static ssize_t
>  efivar_data_read(struct efivar_entry *entry, char *buf)
>  {
> -       struct efi_variable *var = &entry->var;
> +       struct extended_efi_variable *var = entry->var;
>        efi_status_t status;
>
>        if (!entry || !buf)
>                return -EINVAL;
>
> -       status = get_var_data(entry->efivars, var);
> +       status = get_var_data(entry->efivars, &var);
>        if (status != EFI_SUCCESS)
>                return -EIO;
>
> -       memcpy(buf, var->Data, var->DataSize);
> -       return var->DataSize;
> +       memcpy(buf, var->Data, var->header.DataSize);
> +       return var->header.DataSize;
>  }
>  /*
>  * We allow each variable to be edited via rewriting the
> @@ -301,34 +324,46 @@ efivar_data_read(struct efivar_entry *entry, char *buf)
>  static ssize_t
>  efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
>  {
> -       struct efi_variable *new_var, *var = &entry->var;
> +       struct extended_efi_variable *new_var, *var = entry->var;
> +       struct efi_variable *tmp_var = NULL;
>        struct efivars *efivars = entry->efivars;
>        efi_status_t status = EFI_NOT_FOUND;
> +       int error = 0;
>
> -       if (count != sizeof(struct efi_variable))
> +       if (count == sizeof(struct efi_variable)) {
> +               tmp_var = (struct efi_variable *)buf;
> +               new_var = kmalloc(sizeof(struct efi_variable) +
> +                                 tmp_var->DataSize, GFP_KERNEL);
> +               memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable));
> +               memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize);
> +       } else if (count > sizeof(struct efi_variable)) {
> +               new_var = (struct extended_efi_variable *)buf;
> +       } else {
>                return -EINVAL;
> +       }

Ugh.  This is difficult to follow, and complicates the memory freeing path :(

We need to be careful here.  The store_raw ABI is broken, in the sense
that the ABI from compat mode differs from that in 32bit mode (there
is a long in the efi_variable structure which changes the offsets).  I
don't know how to fix it properly and still maintain proper ABI
compatibility.

What are your thoughts on _not_ wrapping efi_variable with
extended_efi_variable, and instead just using a
"internal_efi_variable" structure that we copy stuff into/outof.  I
think that would make the memory management for dealing with the
different sizes a lot easier to follow.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:14   ` Mike Waychison
@ 2011-12-14 22:39     ` Matthew Garrett
  2011-12-14 22:44       ` H. Peter Anvin
  2011-12-14 22:57       ` Mike Waychison
  0 siblings, 2 replies; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 22:39 UTC (permalink / raw)
  To: Mike Waychison; +Cc: linux-kernel, x86, hpa

On Wed, Dec 14, 2011 at 02:14:27PM -0800, Mike Waychison wrote:

> >  static efi_status_t
> > -get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
> > +get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var)
> >  {
> >        efi_status_t status;
> > +       unsigned long length;
> > +
> > +       if (!*var)
> > +               *var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL);
> 
> Aren't we holding a spinlock here?

Good point.

> > +
> > +       (*var)->header.DataSize = 0;
> > +       status = efivars->ops->get_variable((*var)->header.VariableName,
> > +                                           &(*var)->header.VendorGuid,
> > +                                           &(*var)->header.Attributes,
> > +                                           &(*var)->header.DataSize,
> > +                                           (*var)->Data);
> 
> This doesn't look right.  ->Data here is after the Data[1024] buffer
> embedded in (*var)->header, and a read into this buffer will corrupt
> the heap.

DataSize is 0, so we'll never actually read anything back here.

> > +
> > +       if (status == EFI_BUFFER_TOO_SMALL) {
> > +               *var = krealloc(*var, sizeof(struct extended_efi_variable) +
> > +                               (*var)->header.DataSize, GFP_KERNEL);
> > +               status = efivars->ops->get_variable((*var)->header.VariableName,
> > +                                                   &(*var)->header.VendorGuid,
> > +                                                   &(*var)->header.Attributes,
> > +                                                   &(*var)->header.DataSize,
> > +                                                   (*var)->Data);
> > +       }
> > +
> > +       length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize :
> > +               1024;
> > +
> > +       memcpy(&(*var)->header.Data, &(*var)->Data, length);
> 
> This memcpy clobbers the header.Data with the corrupted data when we
> didn't use the second path.

We'll always follow the second path providing there's actually data to 
read back. If there isn't then length will be 0.

> > +       if (count == sizeof(struct efi_variable)) {
> > +               tmp_var = (struct efi_variable *)buf;
> > +               new_var = kmalloc(sizeof(struct efi_variable) +
> > +                                 tmp_var->DataSize, GFP_KERNEL);
> > +               memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable));
> > +               memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize);
> > +       } else if (count > sizeof(struct efi_variable)) {
> > +               new_var = (struct extended_efi_variable *)buf;
> > +       } else {
> >                return -EINVAL;
> > +       }
> 
> Ugh.  This is difficult to follow, and complicates the memory freeing path :(

Entirely agreed.

> We need to be careful here.  The store_raw ABI is broken, in the sense
> that the ABI from compat mode differs from that in 32bit mode (there
> is a long in the efi_variable structure which changes the offsets).  I
> don't know how to fix it properly and still maintain proper ABI
> compatibility.

True.

> What are your thoughts on _not_ wrapping efi_variable with
> extended_efi_variable, and instead just using a
> "internal_efi_variable" structure that we copy stuff into/outof.  I
> think that would make the memory management for dealing with the
> different sizes a lot easier to follow.

Hm. I think that'd only work if we expose a new interface. Writes would 
be easy enough to handle, but reads still need to work for old apps.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:39     ` Matthew Garrett
@ 2011-12-14 22:44       ` H. Peter Anvin
  2011-12-14 22:57         ` Matthew Garrett
  2011-12-14 22:57       ` Mike Waychison
  1 sibling, 1 reply; 20+ messages in thread
From: H. Peter Anvin @ 2011-12-14 22:44 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: Mike Waychison, linux-kernel, x86

On 12/14/2011 02:39 PM, Matthew Garrett wrote:
> 
>> We need to be careful here.  The store_raw ABI is broken, in the sense
>> that the ABI from compat mode differs from that in 32bit mode (there
>> is a long in the efi_variable structure which changes the offsets).  I
>> don't know how to fix it properly and still maintain proper ABI
>> compatibility.
> 
> True.
> 
>> What are your thoughts on _not_ wrapping efi_variable with
>> extended_efi_variable, and instead just using a
>> "internal_efi_variable" structure that we copy stuff into/outof.  I
>> think that would make the memory management for dealing with the
>> different sizes a lot easier to follow.
> 
> Hm. I think that'd only work if we expose a new interface. Writes would 
> be easy enough to handle, but reads still need to work for old apps.
> 

Would making the old ABI readonly and add a new write interface resolve
the problem with compat mode?

	-hpa

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:44       ` H. Peter Anvin
@ 2011-12-14 22:57         ` Matthew Garrett
  2011-12-14 22:58           ` H. Peter Anvin
  0 siblings, 1 reply; 20+ messages in thread
From: Matthew Garrett @ 2011-12-14 22:57 UTC (permalink / raw)
  To: H. Peter Anvin; +Cc: Mike Waychison, linux-kernel, x86

On Wed, Dec 14, 2011 at 02:44:34PM -0800, H. Peter Anvin wrote:
> On 12/14/2011 02:39 PM, Matthew Garrett wrote:
> >> What are your thoughts on _not_ wrapping efi_variable with
> >> extended_efi_variable, and instead just using a
> >> "internal_efi_variable" structure that we copy stuff into/outof.  I
> >> think that would make the memory management for dealing with the
> >> different sizes a lot easier to follow.
> > 
> > Hm. I think that'd only work if we expose a new interface. Writes would 
> > be easy enough to handle, but reads still need to work for old apps.
> > 
> 
> Would making the old ABI readonly and add a new write interface resolve
> the problem with compat mode?

Yes, but we'd break existing userspace.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:39     ` Matthew Garrett
  2011-12-14 22:44       ` H. Peter Anvin
@ 2011-12-14 22:57       ` Mike Waychison
  2011-12-14 23:00         ` H. Peter Anvin
  2011-12-15 15:43         ` Matthew Garrett
  1 sibling, 2 replies; 20+ messages in thread
From: Mike Waychison @ 2011-12-14 22:57 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: linux-kernel, x86, hpa

On Wed, Dec 14, 2011 at 2:39 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> On Wed, Dec 14, 2011 at 02:14:27PM -0800, Mike Waychison wrote:
>
>> >  static efi_status_t
>> > -get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
>> > +get_var_data_locked(struct efivars *efivars, struct extended_efi_variable **var)
>> >  {
>> >        efi_status_t status;
>> > +       unsigned long length;
>> > +
>> > +       if (!*var)
>> > +               *var = kmalloc(sizeof(struct extended_efi_variable), GFP_KERNEL);
>>
>> Aren't we holding a spinlock here?
>
> Good point.
>
>> > +
>> > +       (*var)->header.DataSize = 0;
>> > +       status = efivars->ops->get_variable((*var)->header.VariableName,
>> > +                                           &(*var)->header.VendorGuid,
>> > +                                           &(*var)->header.Attributes,
>> > +                                           &(*var)->header.DataSize,
>> > +                                           (*var)->Data);
>>
>> This doesn't look right.  ->Data here is after the Data[1024] buffer
>> embedded in (*var)->header, and a read into this buffer will corrupt
>> the heap.
>
> DataSize is 0, so we'll never actually read anything back here.

Ah.  I missed that.  Hmm, I wonder if this actually works :)

>
>> > +
>> > +       if (status == EFI_BUFFER_TOO_SMALL) {
>> > +               *var = krealloc(*var, sizeof(struct extended_efi_variable) +
>> > +                               (*var)->header.DataSize, GFP_KERNEL);
>> > +               status = efivars->ops->get_variable((*var)->header.VariableName,
>> > +                                                   &(*var)->header.VendorGuid,
>> > +                                                   &(*var)->header.Attributes,
>> > +                                                   &(*var)->header.DataSize,
>> > +                                                   (*var)->Data);
>> > +       }
>> > +
>> > +       length = ((*var)->header.DataSize < 1024) ? (*var)->header.DataSize :
>> > +               1024;
>> > +
>> > +       memcpy(&(*var)->header.Data, &(*var)->Data, length);
>>
>> This memcpy clobbers the header.Data with the corrupted data when we
>> didn't use the second path.
>
> We'll always follow the second path providing there's actually data to
> read back. If there isn't then length will be 0.
>
>> > +       if (count == sizeof(struct efi_variable)) {
>> > +               tmp_var = (struct efi_variable *)buf;
>> > +               new_var = kmalloc(sizeof(struct efi_variable) +
>> > +                                 tmp_var->DataSize, GFP_KERNEL);
>> > +               memcpy(&new_var->header, tmp_var, sizeof(struct efi_variable));
>> > +               memcpy(&new_var->Data, tmp_var->Data, tmp_var->DataSize);
>> > +       } else if (count > sizeof(struct efi_variable)) {
>> > +               new_var = (struct extended_efi_variable *)buf;
>> > +       } else {
>> >                return -EINVAL;
>> > +       }
>>
>> Ugh.  This is difficult to follow, and complicates the memory freeing path :(
>
> Entirely agreed.
>
>> We need to be careful here.  The store_raw ABI is broken, in the sense
>> that the ABI from compat mode differs from that in 32bit mode (there
>> is a long in the efi_variable structure which changes the offsets).  I
>> don't know how to fix it properly and still maintain proper ABI
>> compatibility.
>
> True.
>
>> What are your thoughts on _not_ wrapping efi_variable with
>> extended_efi_variable, and instead just using a
>> "internal_efi_variable" structure that we copy stuff into/outof.  I
>> think that would make the memory management for dealing with the
>> different sizes a lot easier to follow.
>
> Hm. I think that'd only work if we expose a new interface. Writes would
> be easy enough to handle, but reads still need to work for old apps.

Well,l we could *not* support returning all the data field for
datasize > 1024, and simply truncate the field.  We are limited by
PAGE_SIZE by sysfs here anyway (so we don't really want to have a
variable size memcpy in efivar_show_raw).

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:57         ` Matthew Garrett
@ 2011-12-14 22:58           ` H. Peter Anvin
  2011-12-15 15:44             ` Matthew Garrett
  0 siblings, 1 reply; 20+ messages in thread
From: H. Peter Anvin @ 2011-12-14 22:58 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: Mike Waychison, linux-kernel, x86

On 12/14/2011 02:57 PM, Matthew Garrett wrote:
> On Wed, Dec 14, 2011 at 02:44:34PM -0800, H. Peter Anvin wrote:
>> On 12/14/2011 02:39 PM, Matthew Garrett wrote:
>>>> What are your thoughts on _not_ wrapping efi_variable with
>>>> extended_efi_variable, and instead just using a
>>>> "internal_efi_variable" structure that we copy stuff into/outof.  I
>>>> think that would make the memory management for dealing with the
>>>> different sizes a lot easier to follow.
>>>
>>> Hm. I think that'd only work if we expose a new interface. Writes would 
>>> be easy enough to handle, but reads still need to work for old apps.
>>>
>>
>> Would making the old ABI readonly and add a new write interface resolve
>> the problem with compat mode?
> 
> Yes, but we'd break existing userspace.
> 

Obviously.  However, you indicated above that that might be acceptable
-- how many things use this ABI to set variables?

	-hpa


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:57       ` Mike Waychison
@ 2011-12-14 23:00         ` H. Peter Anvin
  2011-12-14 23:06           ` Mike Waychison
  2011-12-15 15:43         ` Matthew Garrett
  1 sibling, 1 reply; 20+ messages in thread
From: H. Peter Anvin @ 2011-12-14 23:00 UTC (permalink / raw)
  To: Mike Waychison; +Cc: Matthew Garrett, linux-kernel, x86

On 12/14/2011 02:57 PM, Mike Waychison wrote:
> 
> Well,l we could *not* support returning all the data field for
> datasize > 1024, and simply truncate the field.  We are limited by
> PAGE_SIZE by sysfs here anyway (so we don't really want to have a
> variable size memcpy in efivar_show_raw).
> 

That may be the biggest reason to avoid sysfs.  As far as I know sysfs
doesn't allow seq_file to be used.

	-hpa

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 23:00         ` H. Peter Anvin
@ 2011-12-14 23:06           ` Mike Waychison
  2011-12-14 23:22             ` H. Peter Anvin
  0 siblings, 1 reply; 20+ messages in thread
From: Mike Waychison @ 2011-12-14 23:06 UTC (permalink / raw)
  To: H. Peter Anvin; +Cc: Matthew Garrett, linux-kernel, x86

On Wed, Dec 14, 2011 at 3:00 PM, H. Peter Anvin <hpa@zytor.com> wrote:
> On 12/14/2011 02:57 PM, Mike Waychison wrote:
>>
>> Well,l we could *not* support returning all the data field for
>> datasize > 1024, and simply truncate the field.  We are limited by
>> PAGE_SIZE by sysfs here anyway (so we don't really want to have a
>> variable size memcpy in efivar_show_raw).
>>
>
> That may be the biggest reason to avoid sysfs.  As far as I know sysfs
> doesn't allow seq_file to be used.
>

Completely agreed.  I don't think a seq_file is warranted in this case
in particular, but the dummification of the interfaces in sysfs sure
makes it hard to do anything that isn't a "single value string".

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 23:06           ` Mike Waychison
@ 2011-12-14 23:22             ` H. Peter Anvin
  2011-12-14 23:24               ` Mike Waychison
  0 siblings, 1 reply; 20+ messages in thread
From: H. Peter Anvin @ 2011-12-14 23:22 UTC (permalink / raw)
  To: Mike Waychison; +Cc: Matthew Garrett, linux-kernel, x86

On 12/14/2011 03:06 PM, Mike Waychison wrote:
> On Wed, Dec 14, 2011 at 3:00 PM, H. Peter Anvin <hpa@zytor.com> wrote:
>> On 12/14/2011 02:57 PM, Mike Waychison wrote:
>>>
>>> Well,l we could *not* support returning all the data field for
>>> datasize > 1024, and simply truncate the field.  We are limited by
>>> PAGE_SIZE by sysfs here anyway (so we don't really want to have a
>>> variable size memcpy in efivar_show_raw).
>>>
>>
>> That may be the biggest reason to avoid sysfs.  As far as I know sysfs
>> doesn't allow seq_file to be used.
>>
> 
> Completely agreed.  I don't think a seq_file is warranted in this case
> in particular, but the dummification of the interfaces in sysfs sure
> makes it hard to do anything that isn't a "single value string".
> 

Well, seq_file is a good way to deal with arbitrary length data.

	-hpa


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 23:22             ` H. Peter Anvin
@ 2011-12-14 23:24               ` Mike Waychison
  0 siblings, 0 replies; 20+ messages in thread
From: Mike Waychison @ 2011-12-14 23:24 UTC (permalink / raw)
  To: H. Peter Anvin; +Cc: Matthew Garrett, linux-kernel, x86

On Wed, Dec 14, 2011 at 3:22 PM, H. Peter Anvin <hpa@zytor.com> wrote:
> On 12/14/2011 03:06 PM, Mike Waychison wrote:
>> On Wed, Dec 14, 2011 at 3:00 PM, H. Peter Anvin <hpa@zytor.com> wrote:
>>> On 12/14/2011 02:57 PM, Mike Waychison wrote:
>>>>
>>>> Well,l we could *not* support returning all the data field for
>>>> datasize > 1024, and simply truncate the field.  We are limited by
>>>> PAGE_SIZE by sysfs here anyway (so we don't really want to have a
>>>> variable size memcpy in efivar_show_raw).
>>>>
>>>
>>> That may be the biggest reason to avoid sysfs.  As far as I know sysfs
>>> doesn't allow seq_file to be used.
>>>
>>
>> Completely agreed.  I don't think a seq_file is warranted in this case
>> in particular, but the dummification of the interfaces in sysfs sure
>> makes it hard to do anything that isn't a "single value string".
>>
>
> Well, seq_file is a good way to deal with arbitrary length data.
>

seq_file maps well to arbitrary record counts (keeping records
self-consistent), but not so well for arbitrarily large records.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:57       ` Mike Waychison
  2011-12-14 23:00         ` H. Peter Anvin
@ 2011-12-15 15:43         ` Matthew Garrett
  2011-12-15 15:46           ` H. Peter Anvin
  1 sibling, 1 reply; 20+ messages in thread
From: Matthew Garrett @ 2011-12-15 15:43 UTC (permalink / raw)
  To: Mike Waychison; +Cc: linux-kernel, x86, hpa

On Wed, Dec 14, 2011 at 02:57:51PM -0800, Mike Waychison wrote:
> On Wed, Dec 14, 2011 at 2:39 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> > Hm. I think that'd only work if we expose a new interface. Writes would
> > be easy enough to handle, but reads still need to work for old apps.
> 
> Well,l we could *not* support returning all the data field for
> datasize > 1024, and simply truncate the field.  We are limited by
> PAGE_SIZE by sysfs here anyway (so we don't really want to have a
> variable size memcpy in efivar_show_raw).

We'll pretty much definitely want to be able to read values > 1024 once 
people start shoving keys in there. The PAGE_SIZE limit is one that I'd 
forgotten about, though.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-14 22:58           ` H. Peter Anvin
@ 2011-12-15 15:44             ` Matthew Garrett
  0 siblings, 0 replies; 20+ messages in thread
From: Matthew Garrett @ 2011-12-15 15:44 UTC (permalink / raw)
  To: H. Peter Anvin; +Cc: Mike Waychison, linux-kernel, x86

On Wed, Dec 14, 2011 at 02:58:20PM -0800, H. Peter Anvin wrote:
> On 12/14/2011 02:57 PM, Matthew Garrett wrote:
> > Yes, but we'd break existing userspace.
> > 
> 
> Obviously.  However, you indicated above that that might be acceptable
> -- how many things use this ABI to set variables?

Ah, sorry - my suggestion had been to add an additional interface, not 
just replace the existing one. efibootmgr is the only userland I know of 
that interacts directly, but it's entirely possible that there are 
others - I'd guess Google have something.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-15 15:43         ` Matthew Garrett
@ 2011-12-15 15:46           ` H. Peter Anvin
  2011-12-15 15:48             ` Matthew Garrett
  0 siblings, 1 reply; 20+ messages in thread
From: H. Peter Anvin @ 2011-12-15 15:46 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: Mike Waychison, linux-kernel, x86

On 12/15/2011 07:43 AM, Matthew Garrett wrote:
>
> We'll pretty much definitely want to be able to read values>  1024 once
> people start shoving keys in there. The PAGE_SIZE limit is one that I'd
> forgotten about, though.
>

The jump from 1024 to 4096 seems to not be very long... hence I think we 
may need a different approach.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes
  2011-12-15 15:46           ` H. Peter Anvin
@ 2011-12-15 15:48             ` Matthew Garrett
  0 siblings, 0 replies; 20+ messages in thread
From: Matthew Garrett @ 2011-12-15 15:48 UTC (permalink / raw)
  To: H. Peter Anvin; +Cc: Mike Waychison, linux-kernel, x86

On Thu, Dec 15, 2011 at 07:46:44AM -0800, H. Peter Anvin wrote:
> On 12/15/2011 07:43 AM, Matthew Garrett wrote:
> >
> >We'll pretty much definitely want to be able to read values>  1024 once
> >people start shoving keys in there. The PAGE_SIZE limit is one that I'd
> >forgotten about, though.
> >
> 
> The jump from 1024 to 4096 seems to not be very long... hence I
> think we may need a different approach.

Yeah. I'll start thinking about the most sensible filesystem interface 
for this.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2011-12-15 15:48 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-12-14 21:06 Update UEFI variable support Matthew Garrett
2011-12-14 21:06 ` [PATCH 1/4] uefi: Populate runtime_version Matthew Garrett
2011-12-14 21:06 ` [PATCH 2/4] efi: Only create sysfs entries while booting or running Matthew Garrett
2011-12-14 21:32   ` Mike Waychison
2011-12-14 21:06 ` [PATCH 3/4] EFI: Add support for variables longer than 1024 bytes Matthew Garrett
2011-12-14 22:14   ` Mike Waychison
2011-12-14 22:39     ` Matthew Garrett
2011-12-14 22:44       ` H. Peter Anvin
2011-12-14 22:57         ` Matthew Garrett
2011-12-14 22:58           ` H. Peter Anvin
2011-12-15 15:44             ` Matthew Garrett
2011-12-14 22:57       ` Mike Waychison
2011-12-14 23:00         ` H. Peter Anvin
2011-12-14 23:06           ` Mike Waychison
2011-12-14 23:22             ` H. Peter Anvin
2011-12-14 23:24               ` Mike Waychison
2011-12-15 15:43         ` Matthew Garrett
2011-12-15 15:46           ` H. Peter Anvin
2011-12-15 15:48             ` Matthew Garrett
2011-12-14 21:06 ` [PATCH 4/4] EFI: Add support for QueryVariableInfo() call Matthew Garrett

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox