diff options
| -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 | /* |
