diff options
| author | Matthew Garrett <mjg@redhat.com> | 2012-04-30 16:11:30 -0400 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-04-30 15:30:18 -0700 | 
| commit | fec6c20b570bcf541e581fc97f2e0cbdb9725b98 (patch) | |
| tree | 2c84d6748a2aeff0f98ef5abbcc0144273bb8978 | |
| parent | 41b3254c93acc56adc3c4477fef7c9512d47659e (diff) | |
| download | olio-linux-3.10-fec6c20b570bcf541e581fc97f2e0cbdb9725b98.tar.xz olio-linux-3.10-fec6c20b570bcf541e581fc97f2e0cbdb9725b98.zip  | |
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>
| -rw-r--r-- | 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 d25599f2a3f..891e4674d29 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, @@ -626,6 +802,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);  	/*  |