All of lore.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.