efi: Validate UEFI boot variables
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> Cc: stable@vger.kernel.org Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
committed by
Linus Torvalds
parent
41b3254c93
commit
fec6c20b57
@@ -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
|
static efi_status_t
|
||||||
get_var_data_locked(struct efivars *efivars, struct efi_variable *var)
|
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;
|
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);
|
spin_lock(&efivars->lock);
|
||||||
status = efivars->ops->set_variable(new_var->VariableName,
|
status = efivars->ops->set_variable(new_var->VariableName,
|
||||||
&new_var->VendorGuid,
|
&new_var->VendorGuid,
|
||||||
@@ -626,6 +802,12 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
|
|||||||
if (!capable(CAP_SYS_ADMIN))
|
if (!capable(CAP_SYS_ADMIN))
|
||||||
return -EACCES;
|
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);
|
spin_lock(&efivars->lock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user