Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

efivars: Add compatibility code for compat tasks

It seems people are using 32-bit efibootmgr on top of 64-bit kernels,
which will currently fail horribly when using the efivars interface,
which is the traditional efibootmgr backend (the other being efivarfs).

Since there is no versioning info in the data structure, figure out when
we need to munge the structure data via judicious use of
is_compat_task().

Cc: Mike Waychison <mikew@google.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>

+115 -25
+115 -25
drivers/firmware/efi/efivars.c
··· 69 69 #include <linux/module.h> 70 70 #include <linux/slab.h> 71 71 #include <linux/ucs2_string.h> 72 + #include <linux/compat.h> 72 73 73 74 #define EFIVARS_VERSION "0.08" 74 75 #define EFIVARS_DATE "2004-May-17" ··· 86 85 87 86 static struct bin_attribute *efivars_new_var; 88 87 static struct bin_attribute *efivars_del_var; 88 + 89 + struct compat_efi_variable { 90 + efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)]; 91 + efi_guid_t VendorGuid; 92 + __u32 DataSize; 93 + __u8 Data[1024]; 94 + __u32 Status; 95 + __u32 Attributes; 96 + } __packed; 89 97 90 98 struct efivar_attribute { 91 99 struct attribute attr; ··· 228 218 return 0; 229 219 } 230 220 221 + static inline bool is_compat(void) 222 + { 223 + if (IS_ENABLED(CONFIG_COMPAT) && is_compat_task()) 224 + return true; 225 + 226 + return false; 227 + } 228 + 229 + static void 230 + copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src) 231 + { 232 + memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN); 233 + memcpy(dst->Data, src->Data, sizeof(src->Data)); 234 + 235 + dst->VendorGuid = src->VendorGuid; 236 + dst->DataSize = src->DataSize; 237 + dst->Attributes = src->Attributes; 238 + } 239 + 231 240 /* 232 241 * We allow each variable to be edited via rewriting the 233 242 * entire efi variable structure. ··· 262 233 u8 *data; 263 234 int err; 264 235 265 - if (count != sizeof(struct efi_variable)) 266 - return -EINVAL; 236 + if (is_compat()) { 237 + struct compat_efi_variable *compat; 267 238 268 - new_var = (struct efi_variable *)buf; 239 + if (count != sizeof(*compat)) 240 + return -EINVAL; 269 241 270 - attributes = new_var->Attributes; 271 - vendor = new_var->VendorGuid; 272 - name = new_var->VariableName; 273 - size = new_var->DataSize; 274 - data = new_var->Data; 242 + compat = (struct compat_efi_variable *)buf; 243 + attributes = compat->Attributes; 244 + vendor = compat->VendorGuid; 245 + name = compat->VariableName; 246 + size = compat->DataSize; 247 + data = compat->Data; 275 248 276 - err = sanity_check(var, name, vendor, size, attributes, data); 277 - if (err) 278 - return err; 249 + err = sanity_check(var, name, vendor, size, attributes, data); 250 + if (err) 251 + return err; 279 252 280 - memcpy(&entry->var, new_var, count); 253 + copy_out_compat(&entry->var, compat); 254 + } else { 255 + if (count != sizeof(struct efi_variable)) 256 + return -EINVAL; 257 + 258 + new_var = (struct efi_variable *)buf; 259 + 260 + attributes = new_var->Attributes; 261 + vendor = new_var->VendorGuid; 262 + name = new_var->VariableName; 263 + size = new_var->DataSize; 264 + data = new_var->Data; 265 + 266 + err = sanity_check(var, name, vendor, size, attributes, data); 267 + if (err) 268 + return err; 269 + 270 + memcpy(&entry->var, new_var, count); 271 + } 281 272 282 273 err = efivar_entry_set(entry, attributes, size, data, NULL); 283 274 if (err) { ··· 312 263 efivar_show_raw(struct efivar_entry *entry, char *buf) 313 264 { 314 265 struct efi_variable *var = &entry->var; 266 + struct compat_efi_variable *compat; 267 + size_t size; 315 268 316 269 if (!entry || !buf) 317 270 return 0; ··· 323 272 &entry->var.DataSize, entry->var.Data)) 324 273 return -EIO; 325 274 326 - memcpy(buf, var, sizeof(*var)); 275 + if (is_compat()) { 276 + compat = (struct compat_efi_variable *)buf; 327 277 328 - return sizeof(*var); 278 + size = sizeof(*compat); 279 + memcpy(compat->VariableName, var->VariableName, 280 + EFI_VAR_NAME_LEN); 281 + memcpy(compat->Data, var->Data, sizeof(compat->Data)); 282 + 283 + compat->VendorGuid = var->VendorGuid; 284 + compat->DataSize = var->DataSize; 285 + compat->Attributes = var->Attributes; 286 + } else { 287 + size = sizeof(*var); 288 + memcpy(buf, var, size); 289 + } 290 + 291 + return size; 329 292 } 330 293 331 294 /* ··· 414 349 struct bin_attribute *bin_attr, 415 350 char *buf, loff_t pos, size_t count) 416 351 { 352 + struct compat_efi_variable *compat = (struct compat_efi_variable *)buf; 417 353 struct efi_variable *new_var = (struct efi_variable *)buf; 418 354 struct efivar_entry *new_entry; 355 + bool need_compat = is_compat(); 419 356 efi_char16_t *name; 420 357 unsigned long size; 421 358 u32 attributes; ··· 427 360 if (!capable(CAP_SYS_ADMIN)) 428 361 return -EACCES; 429 362 430 - if (count != sizeof(*new_var)) 431 - return -EINVAL; 363 + if (need_compat) { 364 + if (count != sizeof(*compat)) 365 + return -EINVAL; 432 366 433 - attributes = new_var->Attributes; 434 - name = new_var->VariableName; 435 - size = new_var->DataSize; 436 - data = new_var->Data; 367 + attributes = compat->Attributes; 368 + name = compat->VariableName; 369 + size = compat->DataSize; 370 + data = compat->Data; 371 + } else { 372 + if (count != sizeof(*new_var)) 373 + return -EINVAL; 374 + 375 + attributes = new_var->Attributes; 376 + name = new_var->VariableName; 377 + size = new_var->DataSize; 378 + data = new_var->Data; 379 + } 437 380 438 381 if ((attributes & ~EFI_VARIABLE_MASK) != 0 || 439 382 efivar_validate(name, data, size) == false) { ··· 455 378 if (!new_entry) 456 379 return -ENOMEM; 457 380 458 - memcpy(&new_entry->var, new_var, sizeof(*new_var)); 381 + if (need_compat) 382 + copy_out_compat(&new_entry->var, compat); 383 + else 384 + memcpy(&new_entry->var, new_var, sizeof(*new_var)); 459 385 460 386 err = efivar_entry_set(new_entry, attributes, size, 461 387 data, &efivar_sysfs_list); ··· 484 404 char *buf, loff_t pos, size_t count) 485 405 { 486 406 struct efi_variable *del_var = (struct efi_variable *)buf; 407 + struct compat_efi_variable *compat; 487 408 struct efivar_entry *entry; 488 409 efi_char16_t *name; 489 410 efi_guid_t vendor; ··· 493 412 if (!capable(CAP_SYS_ADMIN)) 494 413 return -EACCES; 495 414 496 - if (count != sizeof(*del_var)) 497 - return -EINVAL; 415 + if (is_compat()) { 416 + if (count != sizeof(*compat)) 417 + return -EINVAL; 498 418 499 - name = del_var->VariableName; 500 - vendor = del_var->VendorGuid; 419 + compat = (struct compat_efi_variable *)buf; 420 + name = compat->VariableName; 421 + vendor = compat->VendorGuid; 422 + } else { 423 + if (count != sizeof(*del_var)) 424 + return -EINVAL; 425 + 426 + name = del_var->VariableName; 427 + vendor = del_var->VendorGuid; 428 + } 501 429 502 430 efivar_entry_iter_begin(); 503 431 entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);