diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c index 90a065fcb1fa..02c56a02eb9b 100644 --- a/arch/x86/platform/efi/quirks.c +++ b/arch/x86/platform/efi/quirks.c @@ -832,12 +832,5 @@ void efi_crash_gracefully_on_page_fault(unsigned long phys_addr, clear_bit(EFI_RUNTIME_SERVICES, &efi.flags); pr_info("Froze efi_rts_wq and disabled EFI Runtime Services\n"); - /* - * Call schedule() in an infinite loop, so that any spurious wake ups - * will never run efi_rts_wq again. - */ - for (;;) { - set_current_state(TASK_IDLE); - schedule(); - } + efi_rts_park_worker(); } diff --git a/drivers/firmware/efi/apple-properties.c b/drivers/firmware/efi/apple-properties.c index 2e525e17fba7..f401a04edf4c 100644 --- a/drivers/firmware/efi/apple-properties.c +++ b/drivers/firmware/efi/apple-properties.c @@ -207,7 +207,10 @@ static int __init map_properties(void) } properties = (struct properties_header *)data->data; - if (properties->version != 1) { + if (data_len < sizeof(*properties)) { + pr_err("truncated properties header\n"); + ret = -EINVAL; + } else if (properties->version != 1) { pr_err("unsupported version:\n"); print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, 16, 1, properties, data_len, true); diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c index da8d29621644..2344b9d1e81f 100644 --- a/drivers/firmware/efi/runtime-wrappers.c +++ b/drivers/firmware/efi/runtime-wrappers.c @@ -118,6 +118,14 @@ union efi_rts_args { struct efi_runtime_work efi_rts_work; +/* + * Upper bound on how long we wait for a single EFI runtime service + * call to finish before declaring firmware wedged. Chosen to be longer + * than any plausible legitimate call (including UpdateCapsule on slow + * SPI-NOR) while still bounding userspace wait time. + */ +#define EFI_RTS_TIMEOUT (120 * HZ) + /* * efi_queue_work: Queue EFI runtime service call and wait for completion * @_rts: EFI runtime service function identifier @@ -202,7 +210,7 @@ void efi_call_virt_check_flags(unsigned long flags, const void *caller) */ static DEFINE_SEMAPHORE(efi_runtime_lock, 1); -static struct task_struct *efi_runtime_lock_owner; +static struct task_struct *efi_runtime_lock_owner __used; /* * Expose the EFI runtime lock to the UV platform @@ -211,6 +219,19 @@ static struct task_struct *efi_runtime_lock_owner; extern struct semaphore __efi_uv_runtime_lock __alias(efi_runtime_lock); #endif +/* + * Park a worker that must never run efi_rts_wq again: EFI runtime services + * have been disabled and its efi_rts_work is abandoned. Loop in schedule() + * so a spurious wakeup cannot resume it. + */ +void __noreturn efi_rts_park_worker(void) +{ + for (;;) { + set_current_state(TASK_IDLE); + schedule(); + } +} + /* * Calls the appropriate efi_runtime_service() with the appropriate * arguments. @@ -221,6 +242,9 @@ static void __nocfi efi_call_rts(struct work_struct *work) efi_status_t status = EFI_NOT_FOUND; unsigned long flags; + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + efi_rts_park_worker(); + efi_runtime_lock_owner = current; arch_efi_call_virt_setup(); @@ -312,6 +336,9 @@ static void __nocfi efi_call_rts(struct work_struct *work) efi_call_virt_check_flags(flags, efi_rts_work.caller); arch_efi_call_virt_teardown(); + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + efi_rts_park_worker(); + efi_rts_work.status = status; complete(&efi_rts_work.efi_rts_comp); efi_runtime_lock_owner = NULL; @@ -320,17 +347,16 @@ static void __nocfi efi_call_rts(struct work_struct *work) static efi_status_t __efi_queue_work(enum efi_rts_ids id, union efi_rts_args *args) { + if (!efi_enabled(EFI_RUNTIME_SERVICES)) { + pr_warn_once("EFI Runtime Services are disabled!\n"); + return EFI_DEVICE_ERROR; + } + efi_rts_work.efi_rts_id = id; efi_rts_work.args = args; efi_rts_work.caller = __builtin_return_address(0); efi_rts_work.status = EFI_ABORTED; - if (!efi_enabled(EFI_RUNTIME_SERVICES)) { - pr_warn_once("EFI Runtime Services are disabled!\n"); - efi_rts_work.status = EFI_DEVICE_ERROR; - goto exit; - } - init_completion(&efi_rts_work.efi_rts_comp); INIT_WORK(&efi_rts_work.work, efi_call_rts); @@ -338,10 +364,18 @@ static efi_status_t __efi_queue_work(enum efi_rts_ids id, * queue_work() returns 0 if work was already on queue, * _ideally_ this should never happen. */ - if (queue_work(efi_rts_wq, &efi_rts_work.work)) - wait_for_completion(&efi_rts_work.efi_rts_comp); - else + if (!queue_work(efi_rts_wq, &efi_rts_work.work)) { pr_err("Failed to queue work to efi_rts_wq.\n"); + goto exit; + } + + if (!wait_for_completion_timeout(&efi_rts_work.efi_rts_comp, + EFI_RTS_TIMEOUT)) { + pr_err("EFI runtime service %d wedged in firmware; disabling EFI runtime services\n", + id); + clear_bit(EFI_RUNTIME_SERVICES, &efi.flags); + return EFI_ABORTED; + } WARN_ON_ONCE(efi_rts_work.status == EFI_ABORTED); exit: @@ -449,6 +483,11 @@ virt_efi_set_variable_nb(efi_char16_t *name, efi_guid_t *vendor, u32 attr, if (down_trylock(&efi_runtime_lock)) return EFI_NOT_READY; + if (!efi_enabled(EFI_RUNTIME_SERVICES)) { + up(&efi_runtime_lock); + return EFI_DEVICE_ERROR; + } + efi_runtime_lock_owner = current; status = efi_call_virt_pointer(efi.runtime, set_variable, name, vendor, attr, data_size, data); @@ -488,6 +527,11 @@ virt_efi_query_variable_info_nb(u32 attr, u64 *storage_space, if (down_trylock(&efi_runtime_lock)) return EFI_NOT_READY; + if (!efi_enabled(EFI_RUNTIME_SERVICES)) { + up(&efi_runtime_lock); + return EFI_DEVICE_ERROR; + } + efi_runtime_lock_owner = current; status = efi_call_virt_pointer(efi.runtime, query_variable_info, attr, storage_space, remaining_space, @@ -518,6 +562,12 @@ virt_efi_reset_system(int reset_type, efi_status_t status, return; } + if (!efi_enabled(EFI_RUNTIME_SERVICES)) { + pr_warn("EFI Runtime Services are disabled, not invoking reset_system()\n"); + up(&efi_runtime_lock); + return; + } + efi_runtime_lock_owner = current; arch_efi_call_virt_setup(); efi_rts_work.efi_rts_id = EFI_RESET_SYSTEM; diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c index 733c19571f1c..8d33f11db2a1 100644 --- a/fs/efivarfs/super.c +++ b/fs/efivarfs/super.c @@ -89,12 +89,30 @@ static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf) /* Some UEFI firmware does not implement QueryVariableInfo() */ storage_space = remaining_space = 0; if (efi_rt_services_supported(EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO)) { - status = efivar_query_variable_info(attr, &storage_space, - &remaining_space, - &max_variable_size); - if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED) - pr_warn_ratelimited("query_variable_info() failed: 0x%lx\n", - status); + static DEFINE_RATELIMIT_STATE(_rs, 2 * HZ, 5); + static u64 storage, remaining; + static DEFINE_SPINLOCK(lock); + + if (!__ratelimit(&_rs)) { + ratelimit_set_flags(&_rs, RATELIMIT_MSG_ON_RELEASE); + + spin_lock(&lock); + storage_space = storage; + remaining_space = remaining; + spin_unlock(&lock); + } else { + status = efivar_query_variable_info(attr, &storage_space, + &remaining_space, + &max_variable_size); + if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED) + pr_warn("query_variable_info() failed: 0x%lx\n", + status); + + spin_lock(&lock); + storage = storage_space; + remaining = remaining_space; + spin_unlock(&lock); + } } /* diff --git a/include/linux/efi.h b/include/linux/efi.h index ccbc35479684..aa15ff88539b 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -705,7 +705,7 @@ efi_guidcmp (efi_guid_t left, efi_guid_t right) } static inline char * -efi_guid_to_str(efi_guid_t *guid, char *out) +efi_guid_to_str(const efi_guid_t *guid, char *out) { sprintf(out, "%pUl", guid->b); return out; @@ -1212,8 +1212,8 @@ efi_call_acpi_prm_handler(efi_status_t (__efiapi *handler_addr)(u64, void *), /* * efi_runtime_service() function identifiers. - * "NONE" is used by efi_recover_from_page_fault() to check if the page - * fault happened while executing an efi runtime service. + * "NONE" is used by efi_crash_gracefully_on_page_fault() to check if the + * page fault happened while executing an efi runtime service. */ enum efi_rts_ids { EFI_NONE, @@ -1256,6 +1256,8 @@ extern struct efi_runtime_work efi_rts_work; /* Workqueue to queue EFI Runtime Services */ extern struct workqueue_struct *efi_rts_wq; +void __noreturn efi_rts_park_worker(void); + struct linux_efi_memreserve { int size; // allocated size of the array atomic_t count; // number of entries used diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h index 14f8ab653449..0e9dfd0a2446 100644 --- a/tools/objtool/noreturns.h +++ b/tools/objtool/noreturns.h @@ -26,6 +26,7 @@ NORETURN(cpu_startup_entry) NORETURN(do_exit) NORETURN(do_group_exit) NORETURN(do_task_dead) +NORETURN(efi_rts_park_worker) NORETURN(ex_handler_msr_mce) NORETURN(hlt_play_dead) NORETURN(hv_ghcb_terminate)