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 18:30:18 -0400 |
commit | fec6c20b570bcf541e581fc97f2e0cbdb9725b98 (patch) | |
tree | 2c84d6748a2aeff0f98ef5abbcc0144273bb8978 /drivers/firmware | |
parent | 41b3254c93acc56adc3c4477fef7c9512d47659e (diff) |
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>
Diffstat (limited to 'drivers/firmware')
-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 d25599f2a3f8..891e4674d29b 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) | |||
191 | } | 191 | } |
192 | } | 192 | } |
193 | 193 | ||
194 | static bool | ||
195 | validate_device_path(struct efi_variable *var, int match, u8 *buffer, int len) | ||
196 | { | ||
197 | struct efi_generic_dev_path *node; | ||
198 | int offset = 0; | ||
199 | |||
200 | node = (struct efi_generic_dev_path *)buffer; | ||
201 | |||
202 | while (offset < len) { | ||
203 | offset += node->length; | ||
204 | |||
205 | if (offset > len) | ||
206 | return false; | ||
207 | |||
208 | if ((node->type == EFI_DEV_END_PATH || | ||
209 | node->type == EFI_DEV_END_PATH2) && | ||
210 | node->sub_type == EFI_DEV_END_ENTIRE) | ||
211 | return true; | ||
212 | |||
213 | node = (struct efi_generic_dev_path *)(buffer + offset); | ||
214 | } | ||
215 | |||
216 | /* | ||
217 | * If we're here then either node->length pointed past the end | ||
218 | * of the buffer or we reached the end of the buffer without | ||
219 | * finding a device path end node. | ||
220 | */ | ||
221 | return false; | ||
222 | } | ||
223 | |||
224 | static bool | ||
225 | validate_boot_order(struct efi_variable *var, int match, u8 *buffer, int len) | ||
226 | { | ||
227 | /* An array of 16-bit integers */ | ||
228 | if ((len % 2) != 0) | ||
229 | return false; | ||
230 | |||
231 | return true; | ||
232 | } | ||
233 | |||
234 | static bool | ||
235 | validate_load_option(struct efi_variable *var, int match, u8 *buffer, int len) | ||
236 | { | ||
237 | u16 filepathlength; | ||
238 | int i, desclength = 0; | ||
239 | |||
240 | /* Either "Boot" or "Driver" followed by four digits of hex */ | ||
241 | for (i = match; i < match+4; i++) { | ||
242 | if (hex_to_bin(var->VariableName[i] & 0xff) < 0) | ||
243 | return true; | ||
244 | } | ||
245 | |||
246 | /* A valid entry must be at least 6 bytes */ | ||
247 | if (len < 6) | ||
248 | return false; | ||
249 | |||
250 | filepathlength = buffer[4] | buffer[5] << 8; | ||
251 | |||
252 | /* | ||
253 | * There's no stored length for the description, so it has to be | ||
254 | * found by hand | ||
255 | */ | ||
256 | desclength = utf16_strsize((efi_char16_t *)(buffer + 6), len) + 2; | ||
257 | |||
258 | /* Each boot entry must have a descriptor */ | ||
259 | if (!desclength) | ||
260 | return false; | ||
261 | |||
262 | /* | ||
263 | * If the sum of the length of the description, the claimed filepath | ||
264 | * length and the original header are greater than the length of the | ||
265 | * variable, it's malformed | ||
266 | */ | ||
267 | if ((desclength + filepathlength + 6) > len) | ||
268 | return false; | ||
269 | |||
270 | /* | ||
271 | * And, finally, check the filepath | ||
272 | */ | ||
273 | return validate_device_path(var, match, buffer + desclength + 6, | ||
274 | filepathlength); | ||
275 | } | ||
276 | |||
277 | static bool | ||
278 | validate_uint16(struct efi_variable *var, int match, u8 *buffer, int len) | ||
279 | { | ||
280 | /* A single 16-bit integer */ | ||
281 | if (len != 2) | ||
282 | return false; | ||
283 | |||
284 | return true; | ||
285 | } | ||
286 | |||
287 | static bool | ||
288 | validate_ascii_string(struct efi_variable *var, int match, u8 *buffer, int len) | ||
289 | { | ||
290 | int i; | ||
291 | |||
292 | for (i = 0; i < len; i++) { | ||
293 | if (buffer[i] > 127) | ||
294 | return false; | ||
295 | |||
296 | if (buffer[i] == 0) | ||
297 | return true; | ||
298 | } | ||
299 | |||
300 | return false; | ||
301 | } | ||
302 | |||
303 | struct variable_validate { | ||
304 | char *name; | ||
305 | bool (*validate)(struct efi_variable *var, int match, u8 *data, | ||
306 | int len); | ||
307 | }; | ||
308 | |||
309 | static const struct variable_validate variable_validate[] = { | ||
310 | { "BootNext", validate_uint16 }, | ||
311 | { "BootOrder", validate_boot_order }, | ||
312 | { "DriverOrder", validate_boot_order }, | ||
313 | { "Boot*", validate_load_option }, | ||
314 | { "Driver*", validate_load_option }, | ||
315 | { "ConIn", validate_device_path }, | ||
316 | { "ConInDev", validate_device_path }, | ||
317 | { "ConOut", validate_device_path }, | ||
318 | { "ConOutDev", validate_device_path }, | ||
319 | { "ErrOut", validate_device_path }, | ||
320 | { "ErrOutDev", validate_device_path }, | ||
321 | { "Timeout", validate_uint16 }, | ||
322 | { "Lang", validate_ascii_string }, | ||
323 | { "PlatformLang", validate_ascii_string }, | ||
324 | { "", NULL }, | ||
325 | }; | ||
326 | |||
327 | static bool | ||
328 | validate_var(struct efi_variable *var, u8 *data, int len) | ||
329 | { | ||
330 | int i; | ||
331 | u16 *unicode_name = var->VariableName; | ||
332 | |||
333 | for (i = 0; variable_validate[i].validate != NULL; i++) { | ||
334 | const char *name = variable_validate[i].name; | ||
335 | int match; | ||
336 | |||
337 | for (match = 0; ; match++) { | ||
338 | char c = name[match]; | ||
339 | u16 u = unicode_name[match]; | ||
340 | |||
341 | /* All special variables are plain ascii */ | ||
342 | if (u > 127) | ||
343 | return true; | ||
344 | |||
345 | /* Wildcard in the matching name means we've matched */ | ||
346 | if (c == '*') | ||
347 | return variable_validate[i].validate(var, | ||
348 | match, data, len); | ||
349 | |||
350 | /* Case sensitive match */ | ||
351 | if (c != u) | ||
352 | break; | ||
353 | |||
354 | /* Reached the end of the string while matching */ | ||
355 | if (!c) | ||
356 | return variable_validate[i].validate(var, | ||
357 | match, data, len); | ||
358 | } | ||
359 | } | ||
360 | |||
361 | return true; | ||
362 | } | ||
363 | |||
194 | static efi_status_t | 364 | static efi_status_t |
195 | get_var_data_locked(struct efivars *efivars, struct efi_variable *var) | 365 | get_var_data_locked(struct efivars *efivars, struct efi_variable *var) |
196 | { | 366 | { |
@@ -324,6 +494,12 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) | |||
324 | return -EINVAL; | 494 | return -EINVAL; |
325 | } | 495 | } |
326 | 496 | ||
497 | if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || | ||
498 | validate_var(new_var, new_var->Data, new_var->DataSize) == false) { | ||
499 | printk(KERN_ERR "efivars: Malformed variable content\n"); | ||
500 | return -EINVAL; | ||
501 | } | ||
502 | |||
327 | spin_lock(&efivars->lock); | 503 | spin_lock(&efivars->lock); |
328 | status = efivars->ops->set_variable(new_var->VariableName, | 504 | status = efivars->ops->set_variable(new_var->VariableName, |
329 | &new_var->VendorGuid, | 505 | &new_var->VendorGuid, |
@@ -626,6 +802,12 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, | |||
626 | if (!capable(CAP_SYS_ADMIN)) | 802 | if (!capable(CAP_SYS_ADMIN)) |
627 | return -EACCES; | 803 | return -EACCES; |
628 | 804 | ||
805 | if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || | ||
806 | validate_var(new_var, new_var->Data, new_var->DataSize) == false) { | ||
807 | printk(KERN_ERR "efivars: Malformed variable content\n"); | ||
808 | return -EINVAL; | ||
809 | } | ||
810 | |||
629 | spin_lock(&efivars->lock); | 811 | spin_lock(&efivars->lock); |
630 | 812 | ||
631 | /* | 813 | /* |