mirror of
https://git.yoctoproject.org/meta-ti
synced 2026-07-17 06:48:07 +00:00
2177f80076
Duplicate patches between PSP and 3.2.17 have been dropped from the 3.2.17 series Signed-off-by: Koen Kooi <koen@dominion.thruhere.net> Signed-off-by: Denys Dmytriyenko <denys@ti.com>
232 lines
6.1 KiB
Diff
232 lines
6.1 KiB
Diff
From 620a8b56785ca32ec7cde5f632617d89409a21ba Mon Sep 17 00:00:00 2001
|
|
From: Matthew Garrett <mjg@redhat.com>
|
|
Date: Mon, 30 Apr 2012 16:11:30 -0400
|
|
Subject: [PATCH 127/165] efi: Validate UEFI boot variables
|
|
|
|
commit fec6c20b570bcf541e581fc97f2e0cbdb9725b98 upstream.
|
|
|
|
A common flaw in UEFI systems is a refusal to POST triggered by a malformed
|
|
boot variable. Once in this state, machines may only be restored by
|
|
reflashing their firmware with an external hardware device. While this is
|
|
obviously a firmware bug, the serious nature of the outcome suggests that
|
|
operating systems should filter their variable writes in order to prevent
|
|
a malicious user from rendering the machine unusable.
|
|
|
|
Signed-off-by: Matthew Garrett <mjg@redhat.com>
|
|
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
|
|
---
|
|
drivers/firmware/efivars.c | 182 ++++++++++++++++++++++++++++++++++++++++++++
|
|
1 files changed, 182 insertions(+), 0 deletions(-)
|
|
|
|
diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
|
|
index b0a8117..150e4ec 100644
|
|
--- a/drivers/firmware/efivars.c
|
|
+++ b/drivers/firmware/efivars.c
|
|
@@ -191,6 +191,176 @@ utf16_strncmp(const efi_char16_t *a, const efi_char16_t *b, size_t len)
|
|
}
|
|
}
|
|
|
|
+static bool
|
|
+validate_device_path(struct efi_variable *var, int match, u8 *buffer, int len)
|
|
+{
|
|
+ struct efi_generic_dev_path *node;
|
|
+ int offset = 0;
|
|
+
|
|
+ node = (struct efi_generic_dev_path *)buffer;
|
|
+
|
|
+ while (offset < len) {
|
|
+ offset += node->length;
|
|
+
|
|
+ if (offset > len)
|
|
+ return false;
|
|
+
|
|
+ if ((node->type == EFI_DEV_END_PATH ||
|
|
+ node->type == EFI_DEV_END_PATH2) &&
|
|
+ node->sub_type == EFI_DEV_END_ENTIRE)
|
|
+ return true;
|
|
+
|
|
+ node = (struct efi_generic_dev_path *)(buffer + offset);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If we're here then either node->length pointed past the end
|
|
+ * of the buffer or we reached the end of the buffer without
|
|
+ * finding a device path end node.
|
|
+ */
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static bool
|
|
+validate_boot_order(struct efi_variable *var, int match, u8 *buffer, int len)
|
|
+{
|
|
+ /* An array of 16-bit integers */
|
|
+ if ((len % 2) != 0)
|
|
+ return false;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool
|
|
+validate_load_option(struct efi_variable *var, int match, u8 *buffer, int len)
|
|
+{
|
|
+ u16 filepathlength;
|
|
+ int i, desclength = 0;
|
|
+
|
|
+ /* Either "Boot" or "Driver" followed by four digits of hex */
|
|
+ for (i = match; i < match+4; i++) {
|
|
+ if (hex_to_bin(var->VariableName[i] & 0xff) < 0)
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ /* A valid entry must be at least 6 bytes */
|
|
+ if (len < 6)
|
|
+ return false;
|
|
+
|
|
+ filepathlength = buffer[4] | buffer[5] << 8;
|
|
+
|
|
+ /*
|
|
+ * There's no stored length for the description, so it has to be
|
|
+ * found by hand
|
|
+ */
|
|
+ desclength = utf16_strsize((efi_char16_t *)(buffer + 6), len) + 2;
|
|
+
|
|
+ /* Each boot entry must have a descriptor */
|
|
+ if (!desclength)
|
|
+ return false;
|
|
+
|
|
+ /*
|
|
+ * If the sum of the length of the description, the claimed filepath
|
|
+ * length and the original header are greater than the length of the
|
|
+ * variable, it's malformed
|
|
+ */
|
|
+ if ((desclength + filepathlength + 6) > len)
|
|
+ return false;
|
|
+
|
|
+ /*
|
|
+ * And, finally, check the filepath
|
|
+ */
|
|
+ return validate_device_path(var, match, buffer + desclength + 6,
|
|
+ filepathlength);
|
|
+}
|
|
+
|
|
+static bool
|
|
+validate_uint16(struct efi_variable *var, int match, u8 *buffer, int len)
|
|
+{
|
|
+ /* A single 16-bit integer */
|
|
+ if (len != 2)
|
|
+ return false;
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool
|
|
+validate_ascii_string(struct efi_variable *var, int match, u8 *buffer, int len)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < len; i++) {
|
|
+ if (buffer[i] > 127)
|
|
+ return false;
|
|
+
|
|
+ if (buffer[i] == 0)
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+struct variable_validate {
|
|
+ char *name;
|
|
+ bool (*validate)(struct efi_variable *var, int match, u8 *data,
|
|
+ int len);
|
|
+};
|
|
+
|
|
+static const struct variable_validate variable_validate[] = {
|
|
+ { "BootNext", validate_uint16 },
|
|
+ { "BootOrder", validate_boot_order },
|
|
+ { "DriverOrder", validate_boot_order },
|
|
+ { "Boot*", validate_load_option },
|
|
+ { "Driver*", validate_load_option },
|
|
+ { "ConIn", validate_device_path },
|
|
+ { "ConInDev", validate_device_path },
|
|
+ { "ConOut", validate_device_path },
|
|
+ { "ConOutDev", validate_device_path },
|
|
+ { "ErrOut", validate_device_path },
|
|
+ { "ErrOutDev", validate_device_path },
|
|
+ { "Timeout", validate_uint16 },
|
|
+ { "Lang", validate_ascii_string },
|
|
+ { "PlatformLang", validate_ascii_string },
|
|
+ { "", NULL },
|
|
+};
|
|
+
|
|
+static bool
|
|
+validate_var(struct efi_variable *var, u8 *data, int len)
|
|
+{
|
|
+ int i;
|
|
+ u16 *unicode_name = var->VariableName;
|
|
+
|
|
+ for (i = 0; variable_validate[i].validate != NULL; i++) {
|
|
+ const char *name = variable_validate[i].name;
|
|
+ int match;
|
|
+
|
|
+ for (match = 0; ; match++) {
|
|
+ char c = name[match];
|
|
+ u16 u = unicode_name[match];
|
|
+
|
|
+ /* All special variables are plain ascii */
|
|
+ if (u > 127)
|
|
+ return true;
|
|
+
|
|
+ /* Wildcard in the matching name means we've matched */
|
|
+ if (c == '*')
|
|
+ return variable_validate[i].validate(var,
|
|
+ match, data, len);
|
|
+
|
|
+ /* Case sensitive match */
|
|
+ if (c != u)
|
|
+ break;
|
|
+
|
|
+ /* Reached the end of the string while matching */
|
|
+ if (!c)
|
|
+ return variable_validate[i].validate(var,
|
|
+ match, data, len);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
static efi_status_t
|
|
get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
|
|
{
|
|
@@ -324,6 +494,12 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
|
|
return -EINVAL;
|
|
}
|
|
|
|
+ if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 ||
|
|
+ validate_var(new_var, new_var->Data, new_var->DataSize) == false) {
|
|
+ printk(KERN_ERR "efivars: Malformed variable content\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
spin_lock(&efivars->lock);
|
|
status = efivars->ops->set_variable(new_var->VariableName,
|
|
&new_var->VendorGuid,
|
|
@@ -624,6 +800,12 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EACCES;
|
|
|
|
+ if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 ||
|
|
+ validate_var(new_var, new_var->Data, new_var->DataSize) == false) {
|
|
+ printk(KERN_ERR "efivars: Malformed variable content\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
spin_lock(&efivars->lock);
|
|
|
|
/*
|
|
--
|
|
1.7.7.6
|
|
|