diff options
author | Matthew Garrett <mjg@redhat.com> | 2012-04-30 16:11:30 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-05-07 11:56:38 -0400 |
commit | 173c412ef8f37d781eb90f5cc8eeab118b987e68 (patch) | |
tree | 61a091ed208a07757dfde7673855720db9756083 | |
parent | ca14f0481bc8653c39e7b2fca81bc5131ac9afa8 (diff) |
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: Greg Kroah-Hartman <gregkh@linuxfoundation.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 d5106c0f46c..a15c0d45f0a 100644 --- a/drivers/firmware/efivars.c +++ b/drivers/firmware/efivars.c | |||
@@ -166,6 +166,176 @@ utf16_strsize(efi_char16_t *data, unsigned long maxlength) | |||
166 | return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); | 166 | return utf16_strnlen(data, maxlength/sizeof(efi_char16_t)) * sizeof(efi_char16_t); |
167 | } | 167 | } |
168 | 168 | ||
169 | static bool | ||
170 | validate_device_path(struct efi_variable *var, int match, u8 *buffer, int len) | ||
171 | { | ||
172 | struct efi_generic_dev_path *node; | ||
173 | int offset = 0; | ||
174 | |||
175 | node = (struct efi_generic_dev_path *)buffer; | ||
176 | |||
177 | while (offset < len) { | ||
178 | offset += node->length; | ||
179 | |||
180 | if (offset > len) | ||
181 | return false; | ||
182 | |||
183 | if ((node->type == EFI_DEV_END_PATH || | ||
184 | node->type == EFI_DEV_END_PATH2) && | ||
185 | node->sub_type == EFI_DEV_END_ENTIRE) | ||
186 | return true; | ||
187 | |||
188 | node = (struct efi_generic_dev_path *)(buffer + offset); | ||
189 | } | ||
190 | |||
191 | /* | ||
192 | * If we're here then either node->length pointed past the end | ||
193 | * of the buffer or we reached the end of the buffer without | ||
194 | * finding a device path end node. | ||
195 | */ | ||
196 | return false; | ||
197 | } | ||
198 | |||
199 | static bool | ||
200 | validate_boot_order(struct efi_variable *var, int match, u8 *buffer, int len) | ||
201 | { | ||
202 | /* An array of 16-bit integers */ | ||
203 | if ((len % 2) != 0) | ||
204 | return false; | ||
205 | |||
206 | return true; | ||
207 | } | ||
208 | |||
209 | static bool | ||
210 | validate_load_option(struct efi_variable *var, int match, u8 *buffer, int len) | ||
211 | { | ||
212 | u16 filepathlength; | ||
213 | int i, desclength = 0; | ||
214 | |||
215 | /* Either "Boot" or "Driver" followed by four digits of hex */ | ||
216 | for (i = match; i < match+4; i++) { | ||
217 | if (hex_to_bin(var->VariableName[i] & 0xff) < 0) | ||
218 | return true; | ||
219 | } | ||
220 | |||
221 | /* A valid entry must be at least 6 bytes */ | ||
222 | if (len < 6) | ||
223 | return false; | ||
224 | |||
225 | filepathlength = buffer[4] | buffer[5] << 8; | ||
226 | |||
227 | /* | ||
228 | * There's no stored length for the description, so it has to be | ||
229 | * found by hand | ||
230 | */ | ||
231 | desclength = utf16_strsize((efi_char16_t *)(buffer + 6), len) + 2; | ||
232 | |||
233 | /* Each boot entry must have a descriptor */ | ||
234 | if (!desclength) | ||
235 | return false; | ||
236 | |||
237 | /* | ||
238 | * If the sum of the length of the description, the claimed filepath | ||
239 | * length and the original header are greater than the length of the | ||
240 | * variable, it's malformed | ||
241 | */ | ||
242 | if ((desclength + filepathlength + 6) > len) | ||
243 | return false; | ||
244 | |||
245 | /* | ||
246 | * And, finally, check the filepath | ||
247 | */ | ||
248 | return validate_device_path(var, match, buffer + desclength + 6, | ||
249 | filepathlength); | ||
250 | } | ||
251 | |||
252 | static bool | ||
253 | validate_uint16(struct efi_variable *var, int match, u8 *buffer, int len) | ||
254 | { | ||
255 | /* A single 16-bit integer */ | ||
256 | if (len != 2) | ||
257 | return false; | ||
258 | |||
259 | return true; | ||
260 | } | ||
261 | |||
262 | static bool | ||
263 | validate_ascii_string(struct efi_variable *var, int match, u8 *buffer, int len) | ||
264 | { | ||
265 | int i; | ||
266 | |||
267 | for (i = 0; i < len; i++) { | ||
268 | if (buffer[i] > 127) | ||
269 | return false; | ||
270 | |||
271 | if (buffer[i] == 0) | ||
272 | return true; | ||
273 | } | ||
274 | |||
275 | return false; | ||
276 | } | ||
277 | |||
278 | struct variable_validate { | ||
279 | char *name; | ||
280 | bool (*validate)(struct efi_variable *var, int match, u8 *data, | ||
281 | int len); | ||
282 | }; | ||
283 | |||
284 | static const struct variable_validate variable_validate[] = { | ||
285 | { "BootNext", validate_uint16 }, | ||
286 | { "BootOrder", validate_boot_order }, | ||
287 | { "DriverOrder", validate_boot_order }, | ||
288 | { "Boot*", validate_load_option }, | ||
289 | { "Driver*", validate_load_option }, | ||
290 | { "ConIn", validate_device_path }, | ||
291 | { "ConInDev", validate_device_path }, | ||
292 | { "ConOut", validate_device_path }, | ||
293 | { "ConOutDev", validate_device_path }, | ||
294 | { "ErrOut", validate_device_path }, | ||
295 | { "ErrOutDev", validate_device_path }, | ||
296 | { "Timeout", validate_uint16 }, | ||
297 | { "Lang", validate_ascii_string }, | ||
298 | { "PlatformLang", validate_ascii_string }, | ||
299 | { "", NULL }, | ||
300 | }; | ||
301 | |||
302 | static bool | ||
303 | validate_var(struct efi_variable *var, u8 *data, int len) | ||
304 | { | ||
305 | int i; | ||
306 | u16 *unicode_name = var->VariableName; | ||
307 | |||
308 | for (i = 0; variable_validate[i].validate != NULL; i++) { | ||
309 | const char *name = variable_validate[i].name; | ||
310 | int match; | ||
311 | |||
312 | for (match = 0; ; match++) { | ||
313 | char c = name[match]; | ||
314 | u16 u = unicode_name[match]; | ||
315 | |||
316 | /* All special variables are plain ascii */ | ||
317 | if (u > 127) | ||
318 | return true; | ||
319 | |||
320 | /* Wildcard in the matching name means we've matched */ | ||
321 | if (c == '*') | ||
322 | return variable_validate[i].validate(var, | ||
323 | match, data, len); | ||
324 | |||
325 | /* Case sensitive match */ | ||
326 | if (c != u) | ||
327 | break; | ||
328 | |||
329 | /* Reached the end of the string while matching */ | ||
330 | if (!c) | ||
331 | return variable_validate[i].validate(var, | ||
332 | match, data, len); | ||
333 | } | ||
334 | } | ||
335 | |||
336 | return true; | ||
337 | } | ||
338 | |||
169 | static efi_status_t | 339 | static efi_status_t |
170 | get_var_data(struct efivars *efivars, struct efi_variable *var) | 340 | get_var_data(struct efivars *efivars, struct efi_variable *var) |
171 | { | 341 | { |
@@ -289,6 +459,12 @@ efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count) | |||
289 | return -EINVAL; | 459 | return -EINVAL; |
290 | } | 460 | } |
291 | 461 | ||
462 | if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || | ||
463 | validate_var(new_var, new_var->Data, new_var->DataSize) == false) { | ||
464 | printk(KERN_ERR "efivars: Malformed variable content\n"); | ||
465 | return -EINVAL; | ||
466 | } | ||
467 | |||
292 | spin_lock(&efivars->lock); | 468 | spin_lock(&efivars->lock); |
293 | status = efivars->ops->set_variable(new_var->VariableName, | 469 | status = efivars->ops->set_variable(new_var->VariableName, |
294 | &new_var->VendorGuid, | 470 | &new_var->VendorGuid, |
@@ -414,6 +590,12 @@ static ssize_t efivar_create(struct file *filp, struct kobject *kobj, | |||
414 | if (!capable(CAP_SYS_ADMIN)) | 590 | if (!capable(CAP_SYS_ADMIN)) |
415 | return -EACCES; | 591 | return -EACCES; |
416 | 592 | ||
593 | if ((new_var->Attributes & ~EFI_VARIABLE_MASK) != 0 || | ||
594 | validate_var(new_var, new_var->Data, new_var->DataSize) == false) { | ||
595 | printk(KERN_ERR "efivars: Malformed variable content\n"); | ||
596 | return -EINVAL; | ||
597 | } | ||
598 | |||
417 | spin_lock(&efivars->lock); | 599 | spin_lock(&efivars->lock); |
418 | 600 | ||
419 | /* | 601 | /* |