From 8a854a750875ac4949a264eb3af47a1ba67a59a2 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Wed, 2 Sep 2026 15:13:40 -0500 Subject: [PATCH 01/35] efi/capsule: Replace kmap_atomic() with kmap_local_page() kmap_atomic() is deprecated in favour of kmap_local_page(), as described in Documentation/mm/highmem.rst. The conversion is safe here: the mapping is created and torn down within a single iteration of the sg_count loop, preserving the strict stack-based ordering that kmap_local_page() requires, and the mapped region is only written to, so neither pagefault_disable() nor preempt_disable() is needed. The surrounding code already sleeps (it allocates the pages with alloc_page(GFP_KERNEL) and later takes capsule_mutex), so there is no atomic context to preserve. Build-tested only. Assisted-by: LLM sparse Signed-off-by: Danish Khateeb Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/capsule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/capsule.c b/drivers/firmware/efi/capsule.c index dd62526380d2..417e8f13a014 100644 --- a/drivers/firmware/efi/capsule.c +++ b/drivers/firmware/efi/capsule.c @@ -245,7 +245,7 @@ int efi_capsule_update(efi_capsule_header_t *capsule, phys_addr_t *pages) for (i = 0; i < sg_count; i++) { efi_capsule_block_desc_t *sglist; - sglist = kmap_atomic(sg_pages[i]); + sglist = kmap_local_page(sg_pages[i]); for (j = 0; j < SGLIST_PER_PAGE && count > 0; j++) { u64 sz = min_t(u64, imagesize, @@ -277,7 +277,7 @@ int efi_capsule_update(efi_capsule_header_t *capsule, phys_addr_t *pages) */ efi_capsule_flush_cache_range(sglist, PAGE_SIZE); #endif - kunmap_atomic(sglist); + kunmap_local(sglist); } mutex_lock(&capsule_mutex); From 42bf9d266e52823c68e416040984edd0f6d62938 Mon Sep 17 00:00:00 2001 From: Junxiao Chang Date: Sat, 15 Aug 2026 17:50:56 +0800 Subject: [PATCH 02/35] efi: add dynamic control interface for EFI runtime services Add an interface for PREEMPT_RT kernels to dynamically enable or disable EFI runtime services. EFI runtime services are typically disabled on RT systems using kernel parameters such as "noefi" or "efi=disable" to avoid long latency caused by firmware calls. However, this permanently disables EFI runtime services, preventing operations such as UEFI firmware updates. With this change, EFI runtime services can be disabled while real-time workloads are running and re-enabled afterwards, providing low-latency operation without permanently sacrificing firmware functionality. Signed-off-by: Junxiao Chang Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/efi.c | 31 +++++++++++++++++++++++++ drivers/firmware/efi/runtime-wrappers.c | 28 ++++++++++++++++++++++ include/linux/efi.h | 1 + 3 files changed, 60 insertions(+) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 0327a39d31fa..6d987d7f9778 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -401,6 +401,32 @@ static void __init efi_debugfs_init(void) static inline void efi_debugfs_init(void) {} #endif +static ssize_t efi_runtime_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", efi_enabled(EFI_RUNTIME_SERVICES)); +} + +static ssize_t efi_runtime_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret; + bool enable; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + if (efi_runtime_set_enable_flag(enable) != EFI_SUCCESS) { + pr_warn("unable to enable/disable efi runtime service\n"); + return -EAGAIN; + } + + return count; +} + +static struct kobj_attribute efi_runtime_attr = + __ATTR(runtime_enable, 0644, efi_runtime_show, efi_runtime_store); + static int __init efipostcore_init(void) { if (!efi_enabled(EFI_RUNTIME_SERVICES)) @@ -446,6 +472,11 @@ static int __init efisubsys_init(void) goto err_destroy_wq; } + if (IS_ENABLED(CONFIG_PREEMPT_RT) && efi.runtime_supported_mask) { + if (sysfs_create_file(efi_kobj, &efi_runtime_attr.attr)) + pr_warn("unable to register efi dynamic sysfs interface\n"); + } + if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE | EFI_RT_SUPPORTED_GET_NEXT_VARIABLE_NAME)) { error = generic_ops_register(); diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c index 2344b9d1e81f..740f422df337 100644 --- a/drivers/firmware/efi/runtime-wrappers.c +++ b/drivers/firmware/efi/runtime-wrappers.c @@ -652,3 +652,31 @@ void efi_runtime_assert_lock_held(void) { WARN_ON(efi_runtime_lock_owner != current); } + +efi_status_t efi_runtime_set_enable_flag(bool enable) +{ + static bool runtime_disabled; + efi_status_t ret = EFI_NOT_READY; + + if (down_interruptible(&efi_runtime_lock)) + return EFI_ABORTED; + + if (enable) { + /* It could be enabled only if it is disabled here */ + if (runtime_disabled) { + set_bit(EFI_RUNTIME_SERVICES, &efi.flags); + runtime_disabled = false; + ret = EFI_SUCCESS; + } + } else { + if (efi_enabled(EFI_RUNTIME_SERVICES)) { + clear_bit(EFI_RUNTIME_SERVICES, &efi.flags); + runtime_disabled = true; + ret = EFI_SUCCESS; + } + } + + up(&efi_runtime_lock); + + return ret; +} diff --git a/include/linux/efi.h b/include/linux/efi.h index aa15ff88539b..c35446a0b66f 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1109,6 +1109,7 @@ extern void efi_call_virt_check_flags(unsigned long flags, const void *caller); extern unsigned long efi_call_virt_save_flags(void); void efi_runtime_assert_lock_held(void); +efi_status_t efi_runtime_set_enable_flag(bool enable); enum efi_secureboot_mode { efi_secureboot_mode_unset, From 8de9cc08fd736b6cec57c4128f8649b1b85804be Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 3 Sep 2026 23:25:15 +0200 Subject: [PATCH 03/35] efi/libstub: move direct GUID references to static storage Some EFI stub call sites pass the address of a GUID macro directly to EFI boot services. With gcc, this makes the compiler materialize the GUID data at the call site, which is wasteful in the size-sensitive EFI stub. For example, consider this program: #include #define GUID \ EFI_GUID(0xaaaaaaaa, 0xbbbb, 0xcccc, \ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd) void foo(const efi_guid_t *guid); void guid_direct(void) { foo(&GUID); } void guid_static(void) { static efi_guid_t guid = GUID; foo(&guid); } For guid_direct(), gcc materializes the GUID on the stack: 0: f3 0f 1e fa endbr64 4: 48 b8 aa aa aa aa bb movabs $0xccccbbbbaaaaaaaa,%rax b: bb cc cc e: 48 83 ec 18 sub $0x18,%rsp 12: 48 89 04 24 mov %rax,(%rsp) 16: 48 89 e7 mov %rsp,%rdi 19: 48 b8 dd dd dd dd dd movabs $0xdddddddddddddddd,%rax 20: dd dd dd 23: 48 89 44 24 08 mov %rax,0x8(%rsp) 28: e8 00 00 00 00 call 2d 2d: 48 83 c4 18 add $0x18,%rsp 31: c3 ret For guid_static(), gcc stores the GUID in .data and emits a RIP-relative address load: 32: f3 0f 1e fa endbr64 36: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi 3d: e9 00 00 00 00 jmp 42 Overall, guid_direct() consumes 50 bytes in the .text segment whereas guid_static() needs only 32 bytes in total: 16 bytes in .data and 16 bytes in .text. Move these direct GUID references to function-local static objects and pass their address instead. EFI boot service prototypes take non-const efi_guid_t pointers, so keep the GUID non-const to prevent a -Wdiscarded-qualifiers warning. For an x86_64 build with gcc 15.3.0, bloat-o-meter reports: add/remove: 6/0 grow/shrink: 0/3 up/down: 112/-458 (-346) Function old new delta graphics_output_guid - 32 +32 smbios_guid - 16 +16 edid_discovered_guid - 16 +16 edid_active_guid - 16 +16 console_out_device_guid - 16 +16 apple_set_os_guid - 16 +16 efi_stub_entry 4180 4136 -44 efi_get_smbios_record 283 226 -57 efi_setup_graphics 2210 1853 -357 Total: Before=29223, After=28877, chg -1.18% Signed-off-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/gop.c | 17 ++++++++++------- drivers/firmware/efi/libstub/smbios.c | 3 ++- drivers/firmware/efi/libstub/x86-stub.c | 3 ++- drivers/firmware/efi/libstub/zboot.c | 3 ++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/firmware/efi/libstub/gop.c b/drivers/firmware/efi/libstub/gop.c index 80dc8cfeb33e..acc2827ba4d7 100644 --- a/drivers/firmware/efi/libstub/gop.c +++ b/drivers/firmware/efi/libstub/gop.c @@ -425,6 +425,8 @@ static void setup_edid_info(struct edid_info *edid, u32 gop_size_of_edid, u8 *go static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_handle_t handles[], efi_graphics_output_protocol_t **found_gop) { + static efi_guid_t graphics_output_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + static efi_guid_t console_out_device_guid = EFI_CONSOLE_OUT_DEVICE_GUID; efi_graphics_output_protocol_t *first_gop; efi_handle_t h, first_gop_handle; @@ -439,8 +441,7 @@ static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_ha efi_graphics_output_mode_info_t *info; void *dummy = NULL; - status = efi_bs_call(handle_protocol, h, - &EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, + status = efi_bs_call(handle_protocol, h, &graphics_output_guid, (void **)&gop); if (status != EFI_SUCCESS) continue; @@ -462,7 +463,7 @@ static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_ha * don't bother looking any further. */ status = efi_bs_call(handle_protocol, h, - &EFI_CONSOLE_OUT_DEVICE_GUID, &dummy); + &console_out_device_guid, &dummy); if (status == EFI_SUCCESS) { if (found_gop) *found_gop = gop; @@ -480,6 +481,9 @@ static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_ha efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid) { + static efi_guid_t graphics_output_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + static efi_guid_t edid_active_guid = EFI_EDID_ACTIVE_PROTOCOL_GUID; + static efi_guid_t edid_discovered_guid = EFI_EDID_DISCOVERED_PROTOCOL_GUID; efi_handle_t *handles __free(efi_pool) = NULL; efi_handle_t handle; efi_graphics_output_protocol_t *gop; @@ -487,8 +491,7 @@ efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid) unsigned long num; status = efi_bs_call(locate_handle_buffer, EFI_LOCATE_BY_PROTOCOL, - &EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, NULL, &num, - &handles); + &graphics_output_guid, NULL, &num, &handles); if (status != EFI_SUCCESS) return status; @@ -510,14 +513,14 @@ efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid) u32 gop_size_of_edid = 0; u8 *gop_edid = NULL; - status = efi_bs_call(handle_protocol, handle, &EFI_EDID_ACTIVE_PROTOCOL_GUID, + status = efi_bs_call(handle_protocol, handle, &edid_active_guid, (void **)&active_edid); if (status == EFI_SUCCESS) { gop_size_of_edid = efi_table_attr(active_edid, size_of_edid); gop_edid = efi_table_attr(active_edid, edid); } else { status = efi_bs_call(handle_protocol, handle, - &EFI_EDID_DISCOVERED_PROTOCOL_GUID, + &edid_discovered_guid, (void **)&discovered_edid); if (status == EFI_SUCCESS) { gop_size_of_edid = efi_table_attr(discovered_edid, size_of_edid); diff --git a/drivers/firmware/efi/libstub/smbios.c b/drivers/firmware/efi/libstub/smbios.c index f31410d7e7e1..efbbfc3c2c0d 100644 --- a/drivers/firmware/efi/libstub/smbios.c +++ b/drivers/firmware/efi/libstub/smbios.c @@ -35,12 +35,13 @@ union efi_smbios_protocol { const struct efi_smbios_record *efi_get_smbios_record(u8 type) { + static efi_guid_t smbios_guid = EFI_SMBIOS_PROTOCOL_GUID; struct efi_smbios_record *record; efi_smbios_protocol_t *smbios; efi_status_t status; u16 handle = 0xfffe; - status = efi_bs_call(locate_protocol, &EFI_SMBIOS_PROTOCOL_GUID, NULL, + status = efi_bs_call(locate_protocol, &smbios_guid, NULL, (void **)&smbios) ?: efi_call_proto(smbios, get_next, &handle, &type, &record, NULL); if (status != EFI_SUCCESS) diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c index cef32e2c82d8..cb0abe13a6c4 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -335,6 +335,7 @@ static bool apple_match_product_name(void) static void apple_set_os(void) { + static efi_guid_t apple_set_os_guid = APPLE_SET_OS_PROTOCOL_GUID; struct { unsigned long version; efi_status_t (__efiapi *set_os_version)(const char *); @@ -345,7 +346,7 @@ static void apple_set_os(void) if (!efi_is_64bit() || !apple_match_product_name()) return; - status = efi_bs_call(locate_protocol, &APPLE_SET_OS_PROTOCOL_GUID, NULL, + status = efi_bs_call(locate_protocol, &apple_set_os_guid, NULL, (void **)&set_os); if (status != EFI_SUCCESS) return; diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c index 4b76f74c56da..4a5ffe164873 100644 --- a/drivers/firmware/efi/libstub/zboot.c +++ b/drivers/firmware/efi/libstub/zboot.c @@ -34,6 +34,7 @@ struct sysfb_display_info *alloc_primary_display(void) asmlinkage efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab) { + static efi_guid_t loaded_image_guid = LOADED_IMAGE_PROTOCOL_GUID; char *cmdline_ptr __free(efi_pool) = NULL; unsigned long image_base, alloc_size; efi_loaded_image_t *image; @@ -42,7 +43,7 @@ efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab) WRITE_ONCE(efi_system_table, systab); status = efi_bs_call(handle_protocol, handle, - &LOADED_IMAGE_PROTOCOL_GUID, (void **)&image); + &loaded_image_guid, (void **)&image); if (status != EFI_SUCCESS) { efi_err("Failed to locate parent's loaded image protocol\n"); return status; From e57b05932a49b1ef54c6f116b9b044ce37db7dcc Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 3 Sep 2026 23:25:16 +0200 Subject: [PATCH 04/35] efi/libstub: make local GUID variables static Local efi_guid_t variables with automatic storage produce assembly similar to passing the address of the GUID macro directly. For the same reason, make these GUID variables static. Put the static declarations first in their local declaration block to visually separate them from the automatic variables. For an x86_64 build with gcc 15.3.0, bloat-o-meter reports: add/remove: 15/0 grow/shrink: 0/10 up/down: 336/-742 (-406) Function old new delta tbl_guid - 32 +32 rng_proto - 32 +32 pci_proto - 32 +32 guid - 32 +32 fs_proto - 32 +32 cc_guid - 32 +32 tpm2_guid - 16 +16 text_to_dp_guid - 16 +16 tcg2_guid - 16 +16 rng_table_guid - 16 +16 rng_algo_raw - 16 +16 proto - 16 +16 linux_eventlog_guid - 16 +16 lf2_proto_guid - 16 +16 info_guid - 16 +16 efi_get_memory_map 707 696 -11 efi_pci_disable_bridge_busmaster 1199 1184 -15 efi_get_random_bytes 216 188 -28 efi_remap_image 357 328 -29 efi_load_initrd 1113 1065 -48 efi_random_get_seed 1368 1297 -71 efi_measure_tagged_event 935 854 -81 efi_retrieve_eventlog 1686 1570 -116 handle_cmdline_files 2501 2341 -160 efi_stub_entry 4136 3953 -183 Total: Before=28877, After=28471, chg -1.41% Signed-off-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/efi-stub-entry.c | 2 +- drivers/firmware/efi/libstub/efi-stub-helper.c | 10 +++++----- drivers/firmware/efi/libstub/efi-stub.c | 2 +- drivers/firmware/efi/libstub/file.c | 8 ++++---- drivers/firmware/efi/libstub/kaslr.c | 2 +- drivers/firmware/efi/libstub/mem.c | 2 +- drivers/firmware/efi/libstub/pci.c | 2 +- drivers/firmware/efi/libstub/random.c | 8 ++++---- drivers/firmware/efi/libstub/riscv.c | 2 +- drivers/firmware/efi/libstub/tpm.c | 8 ++++---- drivers/firmware/efi/libstub/unaccepted_memory.c | 2 +- drivers/firmware/efi/libstub/x86-stub.c | 10 +++++----- 12 files changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/firmware/efi/libstub/efi-stub-entry.c b/drivers/firmware/efi/libstub/efi-stub-entry.c index aa85e910fe59..8ecd8cd72561 100644 --- a/drivers/firmware/efi/libstub/efi-stub-entry.c +++ b/drivers/firmware/efi/libstub/efi-stub-entry.c @@ -35,13 +35,13 @@ struct sysfb_display_info *alloc_primary_display(void) efi_status_t __efiapi efi_pe_entry(efi_handle_t handle, efi_system_table_t *systab) { + static efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID; efi_loaded_image_t *image; efi_status_t status; unsigned long image_addr; unsigned long image_size = 0; /* addr/point and size pairs for memory management*/ char *cmdline_ptr = NULL; - efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID; unsigned long reserve_addr = 0; unsigned long reserve_size = 0; diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c index f27f2e1f0019..48f93f7758e9 100644 --- a/drivers/firmware/efi/libstub/efi-stub-helper.c +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -248,6 +248,7 @@ static efi_status_t efi_measure_tagged_event(unsigned long load_addr, unsigned long load_size, enum efistub_event_type event) { + static efi_guid_t tcg2_guid = EFI_TCG2_PROTOCOL_GUID; union { efi_status_t (__efiapi *hash_log_extend_event)(void *, u64, efi_physical_addr_t, @@ -257,7 +258,6 @@ static efi_status_t efi_measure_tagged_event(unsigned long load_addr, struct efistub_measured_event *evt __free(efi_pool) = NULL; int size = struct_size(evt, tagged_event.tagged_event_data, events[event].event_data_len); - efi_guid_t tcg2_guid = EFI_TCG2_PROTOCOL_GUID; efi_tcg2_protocol_t *tcg2 = NULL; union efistub_event ev; efi_status_t status; @@ -276,7 +276,7 @@ static efi_status_t efi_measure_tagged_event(unsigned long load_addr, method.hash_log_extend_event = (void *)efi_table_attr(tcg2, hash_log_extend_event); } else { - efi_guid_t cc_guid = EFI_CC_MEASUREMENT_PROTOCOL_GUID; + static efi_guid_t cc_guid = EFI_CC_MEASUREMENT_PROTOCOL_GUID; efi_cc_protocol_t *cc = NULL; efi_bs_call(locate_protocol, &cc_guid, NULL, (void **)&cc); @@ -552,7 +552,7 @@ static efi_status_t efi_load_initrd_dev_path(struct linux_efi_initrd *initrd, unsigned long max) { - efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID; + static efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID; efi_device_path_protocol_t *dp; efi_load_file2_protocol_t *lf2; efi_handle_t handle; @@ -614,7 +614,7 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image, unsigned long hard_limit, const struct linux_efi_initrd **out) { - efi_guid_t tbl_guid = LINUX_EFI_INITRD_MEDIA_GUID; + static efi_guid_t tbl_guid = LINUX_EFI_INITRD_MEDIA_GUID; efi_status_t status = EFI_SUCCESS; struct linux_efi_initrd initrd, *tbl; @@ -725,7 +725,7 @@ efi_status_t efi_wait_for_key(unsigned long usec, efi_input_key_t *key) void efi_remap_image(unsigned long image_base, unsigned alloc_size, unsigned long code_size) { - efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID; + static efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID; efi_memory_attribute_protocol_t *memattr; efi_status_t status; u64 attr; diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c index 42d6073bcd06..235c9738da2d 100644 --- a/drivers/firmware/efi/libstub/efi-stub.c +++ b/drivers/firmware/efi/libstub/efi-stub.c @@ -79,8 +79,8 @@ static struct sysfb_display_info *setup_primary_display(void) static void install_memreserve_table(void) { + static efi_guid_t memreserve_table_guid = LINUX_EFI_MEMRESERVE_TABLE_GUID; struct linux_efi_memreserve *rsv; - efi_guid_t memreserve_table_guid = LINUX_EFI_MEMRESERVE_TABLE_GUID; efi_status_t status; status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*rsv), diff --git a/drivers/firmware/efi/libstub/file.c b/drivers/firmware/efi/libstub/file.c index bd626d55dcbc..0dff9e08d49e 100644 --- a/drivers/firmware/efi/libstub/file.c +++ b/drivers/firmware/efi/libstub/file.c @@ -39,7 +39,7 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume, efi_file_protocol_t **handle, unsigned long *file_size) { - efi_guid_t info_guid = EFI_FILE_INFO_ID; + static efi_guid_t info_guid = EFI_FILE_INFO_ID; efi_file_protocol_t *fh; unsigned long info_sz; efi_status_t status; @@ -74,7 +74,7 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume, static efi_status_t efi_open_volume(efi_loaded_image_t *image, efi_file_protocol_t **fh) { - efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; + static efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; efi_simple_file_system_protocol_t *io; efi_status_t status; @@ -128,9 +128,9 @@ static int find_file_option(const efi_char16_t *cmdline, int cmdline_len, static efi_status_t efi_open_device_path(efi_file_protocol_t **volume, struct finfo *fi) { - efi_guid_t text_to_dp_guid = EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID; + static efi_guid_t text_to_dp_guid = EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID; + static efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; static efi_device_path_from_text_protocol_t *text_to_dp = NULL; - efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; efi_device_path_protocol_t *initrd_dp; efi_simple_file_system_protocol_t *io; struct efi_file_path_dev_path *fpath; diff --git a/drivers/firmware/efi/libstub/kaslr.c b/drivers/firmware/efi/libstub/kaslr.c index 4bc963e999eb..f5074656457a 100644 --- a/drivers/firmware/efi/libstub/kaslr.c +++ b/drivers/firmware/efi/libstub/kaslr.c @@ -18,7 +18,7 @@ */ u32 efi_kaslr_get_phys_seed(efi_handle_t image_handle) { - efi_guid_t li_fixed_proto = LINUX_EFI_LOADED_IMAGE_FIXED_GUID; + static efi_guid_t li_fixed_proto = LINUX_EFI_LOADED_IMAGE_FIXED_GUID; void *p; if (!IS_ENABLED(CONFIG_RANDOMIZE_BASE)) diff --git a/drivers/firmware/efi/libstub/mem.c b/drivers/firmware/efi/libstub/mem.c index 59f3f83de50c..fec561e3a792 100644 --- a/drivers/firmware/efi/libstub/mem.c +++ b/drivers/firmware/efi/libstub/mem.c @@ -20,10 +20,10 @@ efi_status_t efi_get_memory_map(struct efi_boot_memmap **map, bool install_cfg_tbl) { + static efi_guid_t tbl_guid = LINUX_EFI_BOOT_MEMMAP_GUID; struct efi_boot_memmap tmp, *m __free(efi_pool) = NULL; int memtype = install_cfg_tbl ? EFI_ACPI_RECLAIM_MEMORY : EFI_LOADER_DATA; - efi_guid_t tbl_guid = LINUX_EFI_BOOT_MEMMAP_GUID; efi_status_t status; unsigned long size; diff --git a/drivers/firmware/efi/libstub/pci.c b/drivers/firmware/efi/libstub/pci.c index 1dccf77958d3..5daa7a0a0e87 100644 --- a/drivers/firmware/efi/libstub/pci.c +++ b/drivers/firmware/efi/libstub/pci.c @@ -15,7 +15,7 @@ void efi_pci_disable_bridge_busmaster(void) { - efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID; + static efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID; efi_handle_t *pci_handle __free(efi_pool) = NULL; unsigned long pci_handle_num; efi_handle_t handle; diff --git a/drivers/firmware/efi/libstub/random.c b/drivers/firmware/efi/libstub/random.c index 7109b8a2dcba..3ca230fa7aa3 100644 --- a/drivers/firmware/efi/libstub/random.c +++ b/drivers/firmware/efi/libstub/random.c @@ -38,7 +38,7 @@ union efi_rng_protocol { */ efi_status_t efi_get_random_bytes(unsigned long size, u8 *out) { - efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; + static efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; efi_status_t status; efi_rng_protocol_t *rng = NULL; @@ -64,9 +64,9 @@ efi_status_t efi_get_random_bytes(unsigned long size, u8 *out) */ efi_status_t efi_random_get_seed(void) { - efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; - efi_guid_t rng_algo_raw = EFI_RNG_ALGORITHM_RAW; - efi_guid_t rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID; + static efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; + static efi_guid_t rng_algo_raw = EFI_RNG_ALGORITHM_RAW; + static efi_guid_t rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID; struct linux_efi_random_seed *prev_seed, *seed = NULL; int prev_seed_size = 0, seed_size = EFI_RANDOM_SEED_SIZE; unsigned long nv_seed_size = 0, offset = 0; diff --git a/drivers/firmware/efi/libstub/riscv.c b/drivers/firmware/efi/libstub/riscv.c index f66f33ceb99e..49db47c05bfe 100644 --- a/drivers/firmware/efi/libstub/riscv.c +++ b/drivers/firmware/efi/libstub/riscv.c @@ -45,7 +45,7 @@ static int get_boot_hartid_from_fdt(void) static efi_status_t get_boot_hartid_from_efi(void) { - efi_guid_t boot_protocol_guid = RISCV_EFI_BOOT_PROTOCOL_GUID; + static efi_guid_t boot_protocol_guid = RISCV_EFI_BOOT_PROTOCOL_GUID; struct riscv_efi_boot_protocol *boot_protocol; efi_status_t status; diff --git a/drivers/firmware/efi/libstub/tpm.c b/drivers/firmware/efi/libstub/tpm.c index a5c6c4f163fc..73f001114732 100644 --- a/drivers/firmware/efi/libstub/tpm.c +++ b/drivers/firmware/efi/libstub/tpm.c @@ -28,8 +28,8 @@ static const efi_char16_t efi_MemoryOverWriteRequest_name[] = */ void efi_enable_reset_attack_mitigation(void) { + static efi_guid_t var_guid = MEMORY_ONLY_RESET_CONTROL_GUID; u8 val = 1; - efi_guid_t var_guid = MEMORY_ONLY_RESET_CONTROL_GUID; efi_status_t status; unsigned long datasize = 0; @@ -52,7 +52,7 @@ static void efi_retrieve_tcg2_eventlog(int version, efi_physical_addr_t log_loca efi_bool_t truncated, struct efi_tcg2_final_events_table *final_events_table) { - efi_guid_t linux_eventlog_guid = LINUX_EFI_TPM_EVENT_LOG_GUID; + static efi_guid_t linux_eventlog_guid = LINUX_EFI_TPM_EVENT_LOG_GUID; efi_status_t status; struct linux_efi_tpm_eventlog *log_tbl = NULL; unsigned long first_entry_addr, last_entry_addr; @@ -150,9 +150,9 @@ static void efi_retrieve_tcg2_eventlog(int version, efi_physical_addr_t log_loca void efi_retrieve_eventlog(void) { + static efi_guid_t tpm2_guid = EFI_TCG2_PROTOCOL_GUID; struct efi_tcg2_final_events_table *final_events_table = NULL; efi_physical_addr_t log_location = 0, log_last_entry = 0; - efi_guid_t tpm2_guid = EFI_TCG2_PROTOCOL_GUID; int version = EFI_TCG2_EVENT_LOG_FORMAT_TCG_2; efi_tcg2_protocol_t *tpm2 = NULL; efi_bool_t truncated; @@ -173,7 +173,7 @@ void efi_retrieve_eventlog(void) get_efi_config_table(EFI_TCG2_FINAL_EVENTS_TABLE_GUID); } } else { - efi_guid_t cc_guid = EFI_CC_MEASUREMENT_PROTOCOL_GUID; + static efi_guid_t cc_guid = EFI_CC_MEASUREMENT_PROTOCOL_GUID; efi_cc_protocol_t *cc = NULL; status = efi_bs_call(locate_protocol, &cc_guid, NULL, (void **)&cc); diff --git a/drivers/firmware/efi/libstub/unaccepted_memory.c b/drivers/firmware/efi/libstub/unaccepted_memory.c index 757dbe734a47..2a7eac7fef86 100644 --- a/drivers/firmware/efi/libstub/unaccepted_memory.c +++ b/drivers/firmware/efi/libstub/unaccepted_memory.c @@ -9,7 +9,7 @@ struct efi_unaccepted_memory *unaccepted_table; efi_status_t allocate_unaccepted_bitmap(__u32 nr_desc, struct efi_boot_memmap *map) { - efi_guid_t unaccepted_table_guid = LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID; + static efi_guid_t unaccepted_table_guid = LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID; u64 unaccepted_start = ULLONG_MAX, unaccepted_end = 0, bitmap_size; efi_status_t status; int i; diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c index cb0abe13a6c4..0bae0f06b676 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -114,9 +114,9 @@ preserve_pci_rom_image(efi_pci_io_protocol_t *pci, struct pci_setup_rom **__rom) */ static void setup_efi_pci(struct boot_params *params) { + static efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID; efi_status_t status; efi_handle_t *pci_handle __free(efi_pool) = NULL; - efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID; struct setup_data *data; unsigned long num; efi_handle_t h; @@ -155,7 +155,7 @@ static void setup_efi_pci(struct boot_params *params) static void retrieve_apple_device_properties(struct boot_params *boot_params) { - efi_guid_t guid = APPLE_PROPERTIES_PROTOCOL_GUID; + static efi_guid_t guid = APPLE_PROPERTIES_PROTOCOL_GUID; struct setup_data *data, *new; efi_status_t status; u32 size = 0; @@ -445,7 +445,7 @@ efi_status_t efi_adjust_memory_range_protection(unsigned long start, static void setup_unaccepted_memory(void) { - efi_guid_t mem_acceptance_proto = OVMF_SEV_MEMORY_ACCEPTANCE_PROTOCOL_GUID; + static efi_guid_t mem_acceptance_proto = OVMF_SEV_MEMORY_ACCEPTANCE_PROTOCOL_GUID; sev_memory_acceptance_protocol_t *proto; efi_status_t status; @@ -508,7 +508,7 @@ static void __noreturn efi_exit(efi_handle_t handle, efi_status_t status) static efi_status_t efi_allocate_bootparams(efi_handle_t handle, struct boot_params **bp) { - efi_guid_t proto = LOADED_IMAGE_PROTOCOL_GUID; + static efi_guid_t proto = LOADED_IMAGE_PROTOCOL_GUID; struct boot_params *boot_params; struct setup_header *hdr; efi_status_t status; @@ -915,7 +915,7 @@ void __noreturn efi_stub_entry(efi_handle_t handle, struct boot_params *boot_params) { - efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID; + static efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID; const struct linux_efi_initrd *initrd = NULL; unsigned long kernel_entry; struct setup_header *hdr; From 2457ac9d44f3c2a33459eef39989f26d545fcece Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 3 Sep 2026 23:25:17 +0200 Subject: [PATCH 05/35] efi/libstub: factor shared static GUID variables Some GUIDs are used several times in the same translation unit. Factor the duplicated EFI_FILE_SYSTEM_GUID, EFI_RNG_PROTOCOL_GUID and EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID objects at file scope within their translation units. Leave the single-use GUID objects at function scope. For an x86_64 build with gcc 15.3.0, bloat-o-meter reports: add/remove: 0/0 grow/shrink: 0/3 up/down: 0/-48 (-48) Function old new delta rng_proto 32 16 -16 graphics_output_guid 32 16 -16 fs_proto 32 16 -16 Total: Before=28471, After=28423, chg -0.17% Signed-off-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/file.c | 4 ++-- drivers/firmware/efi/libstub/gop.c | 4 ++-- drivers/firmware/efi/libstub/random.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/firmware/efi/libstub/file.c b/drivers/firmware/efi/libstub/file.c index 0dff9e08d49e..b2601e284695 100644 --- a/drivers/firmware/efi/libstub/file.c +++ b/drivers/firmware/efi/libstub/file.c @@ -29,6 +29,8 @@ */ #define EFI_READ_CHUNK_SIZE SZ_1M +static efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; + struct finfo { efi_file_info_t info; efi_char16_t filename[MAX_FILENAME_SIZE]; @@ -74,7 +76,6 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume, static efi_status_t efi_open_volume(efi_loaded_image_t *image, efi_file_protocol_t **fh) { - static efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; efi_simple_file_system_protocol_t *io; efi_status_t status; @@ -129,7 +130,6 @@ static efi_status_t efi_open_device_path(efi_file_protocol_t **volume, struct finfo *fi) { static efi_guid_t text_to_dp_guid = EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID; - static efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID; static efi_device_path_from_text_protocol_t *text_to_dp = NULL; efi_device_path_protocol_t *initrd_dp; efi_simple_file_system_protocol_t *io; diff --git a/drivers/firmware/efi/libstub/gop.c b/drivers/firmware/efi/libstub/gop.c index acc2827ba4d7..fa324d4abcaa 100644 --- a/drivers/firmware/efi/libstub/gop.c +++ b/drivers/firmware/efi/libstub/gop.c @@ -24,6 +24,8 @@ enum efi_cmdline_option { EFI_CMDLINE_LIST }; +static efi_guid_t graphics_output_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + static struct { enum efi_cmdline_option option; union { @@ -425,7 +427,6 @@ static void setup_edid_info(struct edid_info *edid, u32 gop_size_of_edid, u8 *go static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_handle_t handles[], efi_graphics_output_protocol_t **found_gop) { - static efi_guid_t graphics_output_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; static efi_guid_t console_out_device_guid = EFI_CONSOLE_OUT_DEVICE_GUID; efi_graphics_output_protocol_t *first_gop; efi_handle_t h, first_gop_handle; @@ -481,7 +482,6 @@ static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_ha efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid) { - static efi_guid_t graphics_output_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; static efi_guid_t edid_active_guid = EFI_EDID_ACTIVE_PROTOCOL_GUID; static efi_guid_t edid_discovered_guid = EFI_EDID_DISCOVERED_PROTOCOL_GUID; efi_handle_t *handles __free(efi_pool) = NULL; diff --git a/drivers/firmware/efi/libstub/random.c b/drivers/firmware/efi/libstub/random.c index 3ca230fa7aa3..d63262d36e47 100644 --- a/drivers/firmware/efi/libstub/random.c +++ b/drivers/firmware/efi/libstub/random.c @@ -8,6 +8,8 @@ #include "efistub.h" +static efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; + typedef union efi_rng_protocol efi_rng_protocol_t; union efi_rng_protocol { @@ -38,7 +40,6 @@ union efi_rng_protocol { */ efi_status_t efi_get_random_bytes(unsigned long size, u8 *out) { - static efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; efi_status_t status; efi_rng_protocol_t *rng = NULL; @@ -64,7 +65,6 @@ efi_status_t efi_get_random_bytes(unsigned long size, u8 *out) */ efi_status_t efi_random_get_seed(void) { - static efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; static efi_guid_t rng_algo_raw = EFI_RNG_ALGORITHM_RAW; static efi_guid_t rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID; struct linux_efi_random_seed *prev_seed, *seed = NULL; From c12a3a4b35e47fd511fbd550d3637318be41c4eb Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 05:18:53 +0200 Subject: [PATCH 06/35] efi/libstub: Fix efi_set_event_at() parameter documentation efi_set_event_at() documents ids although its array index argument is named idx. This produces missing and excess parameter warnings. Match the argument name and fix the adjacent spelling of array. Fixes: 8c0a839c2bcc ("efi/libstub: Descriptions for stub helper functions") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/efistub.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h index fd91fc15ec81..049263a541ae 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -224,8 +224,8 @@ typedef void (__efiapi *efi_event_notify_t)(efi_event_t, void *); * efi_set_event_at() - add event to events array * * @events: array of UEFI events - * @ids: index where to put the event in the array - * @event: event to add to the aray + * @idx: index where to put the event in the array + * @event: event to add to the array * * boottime->wait_for_event() takes an array of events as input. * Provide a helper to set it up correctly for mixed mode. From f89563b30748bd439d86d729734d346c468b52a5 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:50:08 +0530 Subject: [PATCH 07/35] firmware: efi: fix typo "existance" in comment Correct "existance" to "existence", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/efi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 6d987d7f9778..e08ce813a3d4 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -8,7 +8,7 @@ * * This code registers /sys/firmware/efi{,/efivars} when EFI is supported, * allowing the efivarfs to be mounted or the efivars module to be loaded. - * The existance of /sys/firmware/efi may also be used by userspace to + * The existence of /sys/firmware/efi may also be used by userspace to * determine that the system supports EFI. */ From 7a8d764ad2f7af1a45a43a2131c95e1c7301c169 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 4 Sep 2026 10:31:31 -0500 Subject: [PATCH 08/35] efi/capsule-loader: Replace kmap() with kmap_local_page() kmap() is deprecated in favour of kmap_local_page(), as described in Documentation/mm/highmem.rst. The conversion is safe here. efi_capsule_write() maps the page and releases it within the same call, on both the success and the fail_unmap error path, so the mapping never escapes the thread that created it and the stack-based unmap ordering is preserved. No atomic context is involved: the page is allocated with alloc_page(GFP_KERNEL) just above, and the copy_from_user() performed while the page is mapped is fine because faults are permitted in a local kmap region. efi_capsule_setup_info() also runs while the mapping is live, but only reads through the pointer. kunmap_local() is handed a pointer that has been advanced into the page, which is fine as it masks the address back down to the page boundary. Build-tested only, on i386 with CONFIG_HIGHMEM=y -- where kmap_local_page() actually establishes a mapping rather than returning the direct-map address -- and on x86_64. No new gcc or sparse warnings. Assisted-by: LLM sparse Signed-off-by: Danish Khateeb Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/capsule-loader.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/efi/capsule-loader.c b/drivers/firmware/efi/capsule-loader.c index 8e8f81f0a5a0..e423df3438da 100644 --- a/drivers/firmware/efi/capsule-loader.c +++ b/drivers/firmware/efi/capsule-loader.c @@ -197,7 +197,7 @@ static ssize_t efi_capsule_write(struct file *file, const char __user *buff, page = cap_info->pages[cap_info->index - 1]; } - kbuff = kmap(page); + kbuff = kmap_local_page(page); kbuff += PAGE_SIZE - cap_info->page_bytes_remain; /* Copy capsule binary data from user space to kernel space buffer */ @@ -217,7 +217,7 @@ static ssize_t efi_capsule_write(struct file *file, const char __user *buff, } cap_info->count += write_byte; - kunmap(page); + kunmap_local(kbuff); /* Submit the full binary to efi_capsule_update() API */ if (cap_info->header.headersize > 0 && @@ -236,7 +236,7 @@ static ssize_t efi_capsule_write(struct file *file, const char __user *buff, return write_byte; fail_unmap: - kunmap(page); + kunmap_local(kbuff); failed: efi_free_all_buff_pages(cap_info); return ret; From 2a2b1e23c448ca405495dae162746c330e0ce15b Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 29 Aug 2026 23:20:35 +0200 Subject: [PATCH 09/35] efistub/x86: Use struct boot_e820_entry for size calculations alloc_e820ext() and setup_e820() use sizeof(struct e820_entry) when calculating the sizes of boot_e820_entry structures. Even though struct e820_entry and struct boot_e820_entry have the same size, use sizeof(struct boot_e820_entry) for consistency instead. Signed-off-by: Thorsten Blum Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/x86-stub.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c index 0bae0f06b676..dbfbe8a74ab2 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -649,7 +649,7 @@ setup_e820(struct boot_params *params, struct setup_data *e820ext, u32 e820ext_s } if (nr_entries == ARRAY_SIZE(params->e820_table)) { - u32 need = (nr_desc - i) * sizeof(struct e820_entry) + + u32 need = (nr_desc - i) * sizeof(struct boot_e820_entry) + sizeof(struct setup_data); if (!e820ext || e820ext_size < need) @@ -685,7 +685,7 @@ static efi_status_t alloc_e820ext(u32 nr_desc, struct setup_data **e820ext, unsigned long size; size = sizeof(struct setup_data) + - sizeof(struct e820_entry) * nr_desc; + sizeof(struct boot_e820_entry) * nr_desc; if (*e820ext) { efi_bs_call(free_pool, *e820ext); From d6c852af44d7df4ecf3b30ba082ca8a52bc9645a Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 30 Aug 2026 12:29:09 +0200 Subject: [PATCH 10/35] efi/libstub: Simplify check_image_region() Drop the local ret variable and the break statement by returning the result directly. Also use unsigned long for map_offset, since map_size and desc_size are both unsigned long. Signed-off-by: Thorsten Blum Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/kaslr.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/firmware/efi/libstub/kaslr.c b/drivers/firmware/efi/libstub/kaslr.c index f5074656457a..b73b6d752f43 100644 --- a/drivers/firmware/efi/libstub/kaslr.c +++ b/drivers/firmware/efi/libstub/kaslr.c @@ -59,8 +59,7 @@ static bool check_image_region(u64 base, u64 size) { struct efi_boot_memmap *map __free(efi_pool) = NULL; efi_status_t status; - bool ret = false; - int map_offset; + unsigned long map_offset; status = efi_get_memory_map(&map, false); if (status != EFI_SUCCESS) @@ -74,13 +73,11 @@ static bool check_image_region(u64 base, u64 size) * Find the region that covers base, and return whether * it covers base+size bytes. */ - if (base >= md->phys_addr && base < end) { - ret = (base + size) <= end; - break; - } + if (base >= md->phys_addr && base < end) + return (base + size) <= end; } - return ret; + return false; } /** From bb6cbbb42c3a4b1d949dbe0efdd5f7c952a9d7b0 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 30 Aug 2026 12:29:10 +0200 Subject: [PATCH 11/35] efi/libstub: Fix grammar in efi_kaslr_relocate_kernel() comment Fix the grammar in the kernel-doc comment and indent the @image_addr description correctly. Signed-off-by: Thorsten Blum Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/kaslr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/efi/libstub/kaslr.c b/drivers/firmware/efi/libstub/kaslr.c index b73b6d752f43..8f8dc335b3b3 100644 --- a/drivers/firmware/efi/libstub/kaslr.c +++ b/drivers/firmware/efi/libstub/kaslr.c @@ -82,7 +82,7 @@ static bool check_image_region(u64 base, u64 size) /** * efi_kaslr_relocate_kernel() - Relocate the kernel (random if KASLR enabled) - * @image_addr: Pointer to the current kernel location + * @image_addr: Pointer to the current kernel location * @reserve_addr: Pointer to the relocated kernel location * @reserve_size: Size of the relocated kernel * @kernel_size: Size of the text + data @@ -91,8 +91,8 @@ static bool check_image_region(u64 base, u64 size) * @phys_seed: Random seed used for the relocation * * If KASLR is not enabled, this function relocates the kernel to a fixed - * address (or leave it as its current location). If KASLR is enabled, the - * kernel physical location is randomized using the seed in parameter. + * address or leaves it at its current location. If KASLR is enabled, the + * kernel's physical location is randomized using the supplied seed. * * Return: status code, EFI_SUCCESS if relocation is successful */ From d56ae624591606655ac5657b206d01230b5f7a06 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Sat, 5 Sep 2026 09:25:17 +0200 Subject: [PATCH 12/35] lib/ucs2_string: Drop arbitrary input size limit and associated WARN() It's not really the job of library code to WARN and potentially bring down the system (with panic_on_warn=1) on a condition that is fairly arbitrary to begin with. So drop the WARN_ON_ONCE() and tweak the condition so it matches the actual constraint (count cannot exceed the range of the return type). Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- lib/ucs2_string.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c index 1f7dd4eb640a..d3e7eefba1b0 100644 --- a/lib/ucs2_string.c +++ b/lib/ucs2_string.c @@ -51,20 +51,18 @@ EXPORT_SYMBOL(ucs2_strsize); */ ssize_t ucs2_strscpy(ucs2_char_t *dst, const ucs2_char_t *src, size_t count) { - long res; - /* * Ensure that we have a valid amount of space. We need to store at * least one NUL-character. */ - if (count == 0 || WARN_ON_ONCE(count > INT_MAX / sizeof(*dst))) + if (count == 0 || count > SSIZE_MAX) return -E2BIG; /* * Copy at most 'count' characters, return early if we find a * NUL-terminator. */ - for (res = 0; res < count; res++) { + for (ssize_t res = 0; res < count; res++) { ucs2_char_t c; c = src[res]; From 4c0209bc31d26d1205fe0db29f33ce0290daf3d1 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Tue, 8 Sep 2026 09:08:08 +0200 Subject: [PATCH 13/35] lib/ucs2_string: Suppress modinfo when __DISABLE_EXPORTS is set Allow the UCS-2 string library to be reused in the EFI stub, by suppressing the modinfo data that is usually emitted so that the library can be built as a module. Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- lib/ucs2_string.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c index d3e7eefba1b0..185378e64d2e 100644 --- a/lib/ucs2_string.c +++ b/lib/ucs2_string.c @@ -163,5 +163,7 @@ ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, unsigned long maxlength) } EXPORT_SYMBOL(ucs2_as_utf8); +#ifndef __DISABLE_EXPORTS MODULE_DESCRIPTION("UCS2 string handling"); MODULE_LICENSE("GPL v2"); +#endif From 0f5a86b29fcc1c1f4e4be5d5899de52217ecd5be Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Wed, 9 Sep 2026 10:10:03 +0200 Subject: [PATCH 14/35] lib/ucs2_string: Split out ucs2_as_utf8_l() taking a separate limit ucs2_as_utf8() takes a maxlength argument, which specifies how many bytes the function is permitted to store into the destination buffer. The same value is used as an upper bound for the ucs2_strnlen() invocation, which is reasonable in the general case, as each UCS-2 character produces at least one byte of UTF-8 output, and so there is never a need to process more than 'maxlength' UCS-2 characters. However, if the UCS-2 string is not NUL terminated, ucs2_strnlen() may read past the end of the buffer if 'maxlength' is set to a high value. Current callers pass UCS-2 strings that are expected to be NUL terminated, but for processing the load options in the EFI stub, a version is needed that takes a separate limit argument. So split that off from the current implementation. Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- include/linux/ucs2_string.h | 11 ++++++++++- lib/ucs2_string.c | 15 ++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/include/linux/ucs2_string.h b/include/linux/ucs2_string.h index c499ae809c7d..74f23ca5a967 100644 --- a/include/linux/ucs2_string.h +++ b/include/linux/ucs2_string.h @@ -14,7 +14,16 @@ ssize_t ucs2_strscpy(ucs2_char_t *dst, const ucs2_char_t *src, size_t count); int ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len); unsigned long ucs2_utf8size(const ucs2_char_t *src); +unsigned long +ucs2_as_utf8_l(u8 *dest, const ucs2_char_t *src, unsigned long limit, + unsigned long maxlength); + +static inline unsigned long ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, - unsigned long maxlength); + unsigned long maxlength) +{ + return ucs2_as_utf8_l(dest, src, ucs2_strnlen(src, maxlength), + maxlength); +} #endif /* _LINUX_UCS2_STRING_H_ */ diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c index 185378e64d2e..32e167332840 100644 --- a/lib/ucs2_string.c +++ b/lib/ucs2_string.c @@ -123,18 +123,19 @@ ucs2_utf8size(const ucs2_char_t *src) EXPORT_SYMBOL(ucs2_utf8size); /* - * copy at most maxlength bytes of whole utf8 characters to dest from the - * ucs2 string src. + * Copy at most @limit whole utf8 characters to @dest from the ucs2 string + * @src, using no more than @maxlength bytes of buffer space. * - * The return value is the number of characters copied, not including the - * final NUL character. + * The return value is the number of bytes copied, not including the final NUL + * character. No NUL character will be appended if the output length equals + * @maxlength. */ unsigned long -ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, unsigned long maxlength) +ucs2_as_utf8_l(u8 *dest, const ucs2_char_t *src, unsigned long limit, + unsigned long maxlength) { unsigned int i; unsigned long j = 0; - unsigned long limit = ucs2_strnlen(src, maxlength); for (i = 0; maxlength && i < limit; i++) { u16 c = src[i]; @@ -161,7 +162,7 @@ ucs2_as_utf8(u8 *dest, const ucs2_char_t *src, unsigned long maxlength) dest[j] = '\0'; return j; } -EXPORT_SYMBOL(ucs2_as_utf8); +EXPORT_SYMBOL(ucs2_as_utf8_l); #ifndef __DISABLE_EXPORTS MODULE_DESCRIPTION("UCS2 string handling"); From 899ff1dbc8842b4b6d1b8ade94a86a2336bbc86c Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Mon, 7 Sep 2026 15:18:39 +0200 Subject: [PATCH 15/35] efi/libstub: Use ucs2_string library for UTF-16 to UTF-8 conversion Don't rely on sprintf() with a wide string conversion modifier to convert the command line from UTF-16 to UTF-8. Instead, use the existing ucs2 string library routine that does the same. Note that while UEFI claims support for UTF-16, in practice it ignores surrogate pairs entirely, and so the simplified UCS-2 character set (where each character takes up exactly 2 bytes) is sufficient here. This removes the only user of sprintf() in the EFI stub, so drop that function as well. Since boot memory is plentiful on UEFI systems, just establish a worst case upper bound for the size of the buffer (which can never exceed COMMAND_LINE_SIZE), and allocate that first. Then, perform the conversion, and only fall back to processing the command line character by character if that resulted in truncation. This makes the common execution path much simpler. Note that this no longer truncates the command line at the first newline, but there is no evidence that this has ever been needed. Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/Makefile | 3 +- .../firmware/efi/libstub/efi-stub-helper.c | 98 +++++++------------ drivers/firmware/efi/libstub/vsprintf.c | 11 --- 3 files changed, 39 insertions(+), 73 deletions(-) diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 77a2b2d74f3f..12c0c7deb5cb 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -66,7 +66,8 @@ KBUILD_AFLAGS := $(KBUILD_CFLAGS) -D__ASSEMBLY__ lib-y := efi-stub-helper.o gop.o secureboot.o tpm.o \ file.o mem.o random.o randomalloc.o pci.o \ skip_spaces.o lib-cmdline.o lib-ctype.o \ - alignedmem.o printk.o vsprintf.o + alignedmem.o printk.o vsprintf.o \ + lib-ucs2_string.o # include the stub's libfdt dependencies from lib/ when needed libfdt-deps := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c \ diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c index f27f2e1f0019..3dc365492301 100644 --- a/drivers/firmware/efi/libstub/efi-stub-helper.c +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -334,81 +335,56 @@ char *efi_convert_cmdline(efi_loaded_image_t *image) { const efi_char16_t *options = efi_table_attr(image, load_options); u32 options_size = efi_table_attr(image, load_options_size); - int options_bytes = 0, safe_options_bytes = 0; /* UTF-8 bytes */ - unsigned long cmdline_addr = 0; - const efi_char16_t *s2; - bool in_quote = false; + unsigned long options_chars = 0; + unsigned long cmdline_bytes; efi_status_t status; - u32 options_chars; + char *cmdline_addr; if (options_size > 0) efi_measure_tagged_event((unsigned long)options, options_size, EFISTUB_EVT_LOAD_OPTIONS); efi_apply_loadoptions_quirk((const void **)&options, &options_size); - options_chars = options_size / sizeof(efi_char16_t); + if (options) + options_chars = ucs2_strnlen(options, + options_size / sizeof(efi_char16_t)); - if (options) { - s2 = options; - while (options_bytes < COMMAND_LINE_SIZE && options_chars--) { - efi_char16_t c = *s2++; + /* Each UCS-2 char takes up at most 3 UTF-8 bytes */ + cmdline_bytes = min(3 * options_chars, COMMAND_LINE_SIZE - 1) + 3; - if (c < 0x80) { - if (c == L'\0' || c == L'\n') - break; - if (c == L'"') - in_quote = !in_quote; - else if (!in_quote && isspace((char)c)) - safe_options_bytes = options_bytes; - - options_bytes++; - continue; - } - - /* - * Get the number of UTF-8 bytes corresponding to a - * UTF-16 character. - * The first part handles everything in the BMP. - */ - options_bytes += 2 + (c >= 0x800); - /* - * Add one more byte for valid surrogate pairs. Invalid - * surrogates will be replaced with 0xfffd and take up - * only 3 bytes. - */ - if ((c & 0xfc00) == 0xd800) { - /* - * If the very last word is a high surrogate, - * we must ignore it since we can't access the - * low surrogate. - */ - if (!options_chars) { - options_bytes -= 3; - } else if ((*s2 & 0xfc00) == 0xdc00) { - options_bytes++; - options_chars--; - s2++; - } - } - } - if (options_bytes >= COMMAND_LINE_SIZE) { - options_bytes = safe_options_bytes; - efi_err("Command line is too long: truncated to %d bytes\n", - options_bytes); - } - } - - options_bytes++; /* NUL termination */ - - status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, options_bytes, + status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, cmdline_bytes, (void **)&cmdline_addr); if (status != EFI_SUCCESS) return NULL; - snprintf((char *)cmdline_addr, options_bytes, "%.*ls", - options_bytes - 1, options); + if (ucs2_as_utf8_l(cmdline_addr, options, options_chars, + cmdline_bytes) >= COMMAND_LINE_SIZE) { + /* + * The output fills up the entire buffer, and may have been + * truncated. Work backwards through the buffer to find a safe + * truncation point (i.e., a blank character not inside a + * quoted string). + */ + int safe_pos[2] = {}; + int in_quote = 0; - return (char *)cmdline_addr; + for (int i = COMMAND_LINE_SIZE - 1; i >= 0; i--) { + char c = cmdline_addr[i]; + + if (!c) + return cmdline_addr; + else if (c == '"') + in_quote ^= 1; + else if (!safe_pos[in_quote] && isspace(c)) + safe_pos[in_quote] = i; + } + + efi_err("Command line is too long: truncated to %d bytes\n", + safe_pos[in_quote]); + cmdline_addr[safe_pos[in_quote]] = '\0'; + } + + return cmdline_addr; } /** diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c index 71c71c222346..dba136679172 100644 --- a/drivers/firmware/efi/libstub/vsprintf.c +++ b/drivers/firmware/efi/libstub/vsprintf.c @@ -551,14 +551,3 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) return pos; } - -int snprintf(char *buf, size_t size, const char *fmt, ...) -{ - va_list args; - int i; - - va_start(args, fmt); - i = vsnprintf(buf, size, fmt, args); - va_end(args); - return i; -} From 1fe625b5df3d28f504677f067ab0be5d4bde10f3 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Sun, 6 Sep 2026 13:15:12 +0200 Subject: [PATCH 16/35] efi/libstub: Avoid efi_puts() for compile time constant strings efi_puts() performs a UTF-8 to UTF-16 conversion on its input, as the EFI console's native character set is UTF-16. This is pointless for compile time constant strings, since we can simply define those as UTF-16 to begin with. This takes slightly more space, but removes any runtime handling of those strings, simplifying the code. Note that efi_puts() also performs LF to CR-LF conversion, so this needs to be taken into account as well. Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/gop.c | 6 +++--- drivers/firmware/efi/libstub/printk.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/efi/libstub/gop.c b/drivers/firmware/efi/libstub/gop.c index 80dc8cfeb33e..6919e28ba92b 100644 --- a/drivers/firmware/efi/libstub/gop.c +++ b/drivers/firmware/efi/libstub/gop.c @@ -309,12 +309,12 @@ static u32 choose_mode_list(efi_graphics_output_protocol_t *gop) efi_status_t status; efi_printk("Available graphics modes are 0-%u\n", max_mode-1); - efi_puts(" * = current mode\n" - " - = unusable mode\n"); + efi_char16_puts(L" * = current mode\r\n" + " - = unusable mode\r\n"); choose_mode(gop, match_list, (void *)cur_mode); - efi_puts("\nPress any key to continue (or wait 10 seconds)\n"); + efi_char16_puts(L"\r\nPress any key to continue (or wait 10 seconds)\r\n"); status = efi_wait_for_key(10 * EFI_USEC_PER_SEC, &key); if (status != EFI_SUCCESS && status != EFI_TIMEOUT) { efi_err("Unable to read key, continuing in 10 seconds\n"); diff --git a/drivers/firmware/efi/libstub/printk.c b/drivers/firmware/efi/libstub/printk.c index bc599212c05d..f36639886d00 100644 --- a/drivers/firmware/efi/libstub/printk.c +++ b/drivers/firmware/efi/libstub/printk.c @@ -136,7 +136,7 @@ int efi_printk(const char *fmt, ...) return 0; if (loglevel >= 0) - efi_puts("EFI stub: "); + efi_char16_puts(L"EFI stub: "); fmt = printk_skip_level(fmt); @@ -146,7 +146,7 @@ int efi_printk(const char *fmt, ...) efi_puts(printf_buf); if (printed >= sizeof(printf_buf)) { - efi_puts("[Message truncated]\n"); + efi_char16_puts(L"[Message truncated]\r\n"); return -1; } From 48cfc2f96bff6d16b4aca30c97f1a592aa218857 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Sun, 6 Sep 2026 13:23:41 +0200 Subject: [PATCH 17/35] efi/libstub: Output UTF-16 directly from vsnprintf() The only remaining users of vsnprintf() in the EFI stub are the diagnostic printk()'s, which are emitted to the console and not recorded for posterity. The EFI console uses UTF-16 (or actually, UCS-2) natively, and so all non-UTF16 strings that are emitted need to be converted. Given the stub's vsnprintf() support for wide strings (using the %ls conversion modifier), which uses UTF-16 to UTF-8 conversion internally, the final conversion to UTF-16 needs to support not just plain ASCII but UTF-8 as well. This is all pointless, of course, and it makes more sense to use UTF-16 internally. This removes the need for UTF-16 to UTF-8 conversion in vsnprintf(), and given that all non-wide string inputs to vsnprintf() that exist in the stub today are compile time constant ASCII strings, the need to convert UTF-8 to UTF-16 disappears as well. So implement efi_vsnprintf() taking a const char *fmt as before, but outputting a efi_char16_t[] that can be passed to the EFI console directly, rather than via efi_puts(), leaving the latter unused and therefore removed. Note that efi_puts() performs LF to CR-LF conversion internally, so add this capability to efi_vsnprintf() as well. Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/efistub.h | 5 +- drivers/firmware/efi/libstub/printk.c | 91 ++--------------------- drivers/firmware/efi/libstub/vsprintf.c | 96 ++++--------------------- 3 files changed, 22 insertions(+), 170 deletions(-) diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h index fd91fc15ec81..36056c624782 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -1078,9 +1078,10 @@ efi_status_t check_platform_features(void); void *get_efi_config_table(efi_guid_t guid); -/* NOTE: These functions do not print a trailing newline after the string */ void efi_char16_puts(efi_char16_t *); -void efi_puts(const char *str); + +int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap, + bool crlf); __printf(1, 2) int efi_printk(char const *fmt, ...); diff --git a/drivers/firmware/efi/libstub/printk.c b/drivers/firmware/efi/libstub/printk.c index f36639886d00..0a18cfe32528 100644 --- a/drivers/firmware/efi/libstub/printk.c +++ b/drivers/firmware/efi/libstub/printk.c @@ -23,98 +23,20 @@ void efi_char16_puts(efi_char16_t *str) output_string, str); } -static -u32 utf8_to_utf32(const u8 **s8) -{ - u32 c32; - u8 c0, cx; - size_t clen, i; - - c0 = cx = *(*s8)++; - /* - * The position of the most-significant 0 bit gives us the length of - * a multi-octet encoding. - */ - for (clen = 0; cx & 0x80; ++clen) - cx <<= 1; - /* - * If the 0 bit is in position 8, this is a valid single-octet - * encoding. If the 0 bit is in position 7 or positions 1-3, the - * encoding is invalid. - * In either case, we just return the first octet. - */ - if (clen < 2 || clen > 4) - return c0; - /* Get the bits from the first octet. */ - c32 = cx >> clen--; - for (i = 0; i < clen; ++i) { - /* Trailing octets must have 10 in most significant bits. */ - cx = (*s8)[i] ^ 0x80; - if (cx & 0xc0) - return c0; - c32 = (c32 << 6) | cx; - } - /* - * Check for validity: - * - The character must be in the Unicode range. - * - It must not be a surrogate. - * - It must be encoded using the correct number of octets. - */ - if (c32 > 0x10ffff || - (c32 & 0xf800) == 0xd800 || - clen != (c32 >= 0x80) + (c32 >= 0x800) + (c32 >= 0x10000)) - return c0; - *s8 += clen; - return c32; -} - -/** - * efi_puts() - Write a UTF-8 encoded string to the console - * @str: UTF-8 encoded string - */ -void efi_puts(const char *str) -{ - efi_char16_t buf[128]; - size_t pos = 0, lim = ARRAY_SIZE(buf); - const u8 *s8 = (const u8 *)str; - u32 c32; - - while (*s8) { - if (*s8 == '\n') - buf[pos++] = L'\r'; - c32 = utf8_to_utf32(&s8); - if (c32 < 0x10000) { - /* Characters in plane 0 use a single word. */ - buf[pos++] = c32; - } else { - /* - * Characters in other planes encode into a surrogate - * pair. - */ - buf[pos++] = (0xd800 - (0x10000 >> 10)) + (c32 >> 10); - buf[pos++] = 0xdc00 + (c32 & 0x3ff); - } - if (*s8 == '\0' || pos >= lim - 2) { - buf[pos] = L'\0'; - efi_char16_puts(buf); - pos = 0; - } - } -} - /** * efi_printk() - Print a kernel message * @fmt: format string * * The first letter of the format string is used to determine the logging level * of the message. If the level is less then the current EFI logging level, the - * message is suppressed. The message will be truncated to 255 bytes. + * message is suppressed. The message will be truncated to 255 characters + * (ignoring surrogates). * * Return: number of printed characters */ int efi_printk(const char *fmt, ...) { - char printf_buf[256]; + efi_char16_t printf_buf[256]; va_list args; int printed; int loglevel = printk_get_level(fmt); @@ -141,11 +63,12 @@ int efi_printk(const char *fmt, ...) fmt = printk_skip_level(fmt); va_start(args, fmt); - printed = vsnprintf(printf_buf, sizeof(printf_buf), fmt, args); + printed = efi_vsnprintf(printf_buf, ARRAY_SIZE(printf_buf), fmt, args, + true); va_end(args); - efi_puts(printf_buf); - if (printed >= sizeof(printf_buf)) { + efi_char16_puts(printf_buf); + if (printed >= ARRAY_SIZE(printf_buf)) { efi_char16_puts(L"[Message truncated]\r\n"); return -1; } diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c index dba136679172..bd32af6b4f4d 100644 --- a/drivers/firmware/efi/libstub/vsprintf.c +++ b/drivers/firmware/efi/libstub/vsprintf.c @@ -14,10 +14,14 @@ #include #include +#include #include #include #include #include +#include + +#include "efistub.h" static int skip_atoi(const char **s) @@ -239,58 +243,6 @@ char get_sign(long long *num, int flags) return 0; } -static -size_t utf16s_utf8nlen(const u16 *s16, size_t maxlen) -{ - size_t len, clen; - - for (len = 0; len < maxlen && *s16; len += clen) { - u16 c0 = *s16++; - - /* First, get the length for a BMP character */ - clen = 1 + (c0 >= 0x80) + (c0 >= 0x800); - if (len + clen > maxlen) - break; - /* - * If this is a high surrogate, and we're already at maxlen, we - * can't include the character if it's a valid surrogate pair. - * Avoid accessing one extra word just to check if it's valid - * or not. - */ - if ((c0 & 0xfc00) == 0xd800) { - if (len + clen == maxlen) - break; - if ((*s16 & 0xfc00) == 0xdc00) { - ++s16; - ++clen; - } - } - } - - return len; -} - -static -u32 utf16_to_utf32(const u16 **s16) -{ - u16 c0, c1; - - c0 = *(*s16)++; - /* not a surrogate */ - if ((c0 & 0xf800) != 0xd800) - return c0; - /* invalid: low surrogate instead of high */ - if (c0 & 0x0400) - return 0xfffd; - c1 = **s16; - /* invalid: missing low surrogate */ - if ((c1 & 0xfc00) != 0xdc00) - return 0xfffd; - /* valid surrogate pair */ - ++(*s16); - return (0x10000 - (0xd800 << 10) - 0xdc00) + (c0 << 10) + c1; -} - #define PUTC(c) \ do { \ if (pos < size) \ @@ -298,7 +250,8 @@ do { \ ++pos; \ } while (0); -int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) +int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap, + bool crlf) { /* The maximum space required is to print a 64-bit number in octal */ char tmp[(sizeof(unsigned long long) * 8 + 2) / 3]; @@ -336,6 +289,8 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) for (pos = 0; *fmt; ++fmt) { if (*fmt != '%' || *++fmt == '%') { + if (crlf && *fmt == '\n') + PUTC('\r'); PUTC(*fmt); continue; } @@ -400,7 +355,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) else if (qualifier == 'l') { wstring: flags |= WIDE; - precision = len = utf16s_utf8nlen((const u16 *)s, precision); + precision = len = ucs2_strnlen((const u16 *)s, precision); goto output; } precision = len = strnlen(s, precision); @@ -505,36 +460,9 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) if (flags & WIDE) { const u16 *ws = (const u16 *)s; - while (len-- > 0) { - u32 c32 = utf16_to_utf32(&ws); - u8 *s8; - size_t clen; - - if (c32 < 0x80) { - PUTC(c32); - continue; - } - - /* Number of trailing octets */ - clen = 1 + (c32 >= 0x800) + (c32 >= 0x10000); - - len -= clen; - s8 = (u8 *)&buf[pos]; - - /* Avoid writing partial character */ - PUTC('\0'); - pos += clen; - if (pos >= size) - continue; - - /* Set high bits of leading octet */ - *s8 = (0xf00 >> 1) >> clen; - /* Write trailing octets in reverse order */ - for (s8 += clen; clen; --clen, c32 >>= 6) - *s8-- = 0x80 | (c32 & 0x3f); - /* Set low bits of leading octet */ - *s8 |= c32; - } + if (pos < size) + memcpy(&buf[pos], ws, min(len, size - pos) * sizeof(*ws)); + pos += len; } else { while (len-- > 0) PUTC(*s++); From 615afe6fa3bbe2e81fd9c7893c5a50b123246605 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 4 Sep 2026 17:46:58 +0200 Subject: [PATCH 18/35] efi/libstub: Add support for printing human readable GUIDs Add support for the %pUl printk conversion specifier, which takes a pointer to a GUID and prints it in the usual format: aaaaaaaa-bbbb-cccc-dddd-dddddddddddd Co-developed-by: Vincent Mailhol Signed-off-by: Vincent Mailhol Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/vsprintf.c | 40 +++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c index bd32af6b4f4d..7f6b891a338a 100644 --- a/drivers/firmware/efi/libstub/vsprintf.c +++ b/drivers/firmware/efi/libstub/vsprintf.c @@ -113,6 +113,9 @@ char *put_dec(char *end, unsigned long long n) return p; } +/* we are called with base 8, 10 or 16, only, thus don't need "G..." */ +static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */ + static char *number(char *end, unsigned long long num, int base, char locase) { @@ -121,9 +124,6 @@ char *number(char *end, unsigned long long num, int base, char locase) * produces same digits or (maybe lowercased) letters */ - /* we are called with base 8, 10 or 16, only, thus don't need "G..." */ - static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */ - switch (base) { case 10: if (num != 0) @@ -144,6 +144,29 @@ char *number(char *end, unsigned long long num, int base, char locase) return end; } +static char *guid_to_str(const efi_guid_t *guid, char *out, char locase) +{ + static const u8 guid_index[UUID_SIZE] = { + 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15, + }; + + for (int i = 0, p = 0; i < ARRAY_SIZE(guid_index); i++) { + u8 byte = guid->b[guid_index[i]]; + + out[p++] = locase | digits[byte >> 4]; + out[p++] = locase | digits[byte & 0xf]; + + switch (i) { + case 3: + case 5: + case 7: + case 9: + out[p++] = '-'; + } + } + return out; +} + #define ZEROPAD 1 /* pad with zero */ #define SIGN 2 /* unsigned/signed long */ #define PLUS 4 /* show plus */ @@ -253,8 +276,7 @@ do { \ int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap, bool crlf) { - /* The maximum space required is to print a 64-bit number in octal */ - char tmp[(sizeof(unsigned long long) * 8 + 2) / 3]; + char tmp[UUID_STRING_LEN]; char *tmp_end = &tmp[ARRAY_SIZE(tmp)]; long long num; int base; @@ -367,6 +389,14 @@ int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap, break; case 'p': + if (fmt[1] == 'U' && (fmt[2] | 0x20) == 'l') { + flags &= LEFT; + s = guid_to_str(va_arg(args, efi_guid_t *), tmp, fmt[2] & 0x20); + precision = len = UUID_STRING_LEN; + fmt += 2; + goto output; + } + if (precision < 0) precision = 2 * sizeof(void *); fallthrough; From 13e7fd357fc537be221dbf5600fc663727733dbc Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Sun, 6 Sep 2026 13:45:03 +0200 Subject: [PATCH 19/35] efi/libstub: Add efi_snprintf() to construct wide strings The native EFI character set is UTF-16 (or in practice, UCS-2). Implement efi_snprintf() to construct UTF-16 strings using printf style templates. This will be used in a subsequent patch to set the LoaderDevicePartUUID EFI variable. Reviewed-by: Vincent Mailhol Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/efistub.h | 1 + drivers/firmware/efi/libstub/vsprintf.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h index 36056c624782..880c1d0c464b 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -1084,6 +1084,7 @@ int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap, bool crlf); __printf(1, 2) int efi_printk(char const *fmt, ...); +__printf(3, 4) int efi_snprintf(efi_char16_t *buf, size_t size, const char *fmt, ...); void efi_free(unsigned long size, unsigned long addr); DEFINE_FREE(efi_pool, void *, if (_T) efi_bs_call(free_pool, _T)); diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c index 7f6b891a338a..7fd6589f44ef 100644 --- a/drivers/firmware/efi/libstub/vsprintf.c +++ b/drivers/firmware/efi/libstub/vsprintf.c @@ -509,3 +509,14 @@ int efi_vsnprintf(efi_char16_t *buf, size_t size, const char *fmt, va_list ap, return pos; } + +int efi_snprintf(efi_char16_t *buf, size_t size, const char *fmt, ...) +{ + va_list args; + int i; + + va_start(args, fmt); + i = efi_vsnprintf(buf, size, fmt, args, false); + va_end(args); + return i; +} From 0f7d63d9a725e60d509d8dc363a8fd5cbeae51b6 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Sun, 6 Sep 2026 23:52:11 +0200 Subject: [PATCH 20/35] efi/libstub: add initial Boot Loader Interface support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Boot Loader Interface (BLI) [1] defines EFI variables that expose boot loader state to the running OS. LoaderInfo identifies the boot loader, while LoaderDevicePartUUID records the GPT partition UUID of the partition containing it. LoaderDevicePartUUID is used, for example, by systemd-gpt-auto-generator [2] to identify the disk the boot loader was launched from and automatically detect and mount partitions on it. GRUB [3] and systemd-boot [4] populate these variables, but when the kernel is started directly by EFI firmware, there is no conventional external boot loader to provide them. In that case, because the EFI stub performs the boot loader role, it should provide the variables itself. Use LoaderInfo as a sentinel: if it is already set by an earlier boot stage or cannot be set, bail out. Otherwise, populate the other BLI variables. Parse the loaded image device path, extract the GUID signature from its GPT HD() node and publish it under the Linux loader entry vendor GUID as the volatile LoaderDevicePartUUID EFI variable. Install the efi_bli_set_variables() hook in both the generic efi-stub.c path and the x86-specific x86-stub.c path. [1] The Boot Loader Interface Link: https://systemd.io/BOOT_LOADER_INTERFACE/ [2] systemd-gpt-auto-generator Link: https://www.freedesktop.org/software/systemd/man/latest/systemd-gpt-auto-generator.html [3] GRUB -- §16.2 bli Link: https://www.gnu.org/software/grub/manual/grub/html_node/bli_005fmodule.html [4] systemd -- systemd-boot UEFI Boot Manager Link: https://github.com/systemd/systemd/blob/main/docs/BOOT.md?plain=1#L102 Signed-off-by: Vincent Mailhol [ardb: - constify 'image' pointer parameter - pass efi_guid_t* to efi_snprintf()] Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/Makefile | 2 +- drivers/firmware/efi/libstub/bli.c | 86 +++++++++++++++++++++++++ drivers/firmware/efi/libstub/efi-stub.c | 1 + drivers/firmware/efi/libstub/efistub.h | 2 + drivers/firmware/efi/libstub/x86-stub.c | 1 + include/linux/efi.h | 22 +++++++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/efi/libstub/bli.c diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 12c0c7deb5cb..564773c89d14 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -66,7 +66,7 @@ KBUILD_AFLAGS := $(KBUILD_CFLAGS) -D__ASSEMBLY__ lib-y := efi-stub-helper.o gop.o secureboot.o tpm.o \ file.o mem.o random.o randomalloc.o pci.o \ skip_spaces.o lib-cmdline.o lib-ctype.o \ - alignedmem.o printk.o vsprintf.o \ + alignedmem.o printk.o vsprintf.o bli.o \ lib-ucs2_string.o # include the stub's libfdt dependencies from lib/ when needed diff --git a/drivers/firmware/efi/libstub/bli.c b/drivers/firmware/efi/libstub/bli.c new file mode 100644 index 000000000000..10696886d222 --- /dev/null +++ b/drivers/firmware/efi/libstub/bli.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include +#include + +#include "efistub.h" + +static efi_guid_t loader_entry_guid = LINUX_EFI_LOADER_ENTRY_GUID; + +static const struct efi_hd_dev_path * +efi_bli_find_hd_node(const struct efi_dev_path *path) +{ + const struct efi_dev_path *node; + u16 node_len; + + for (node = path; + node->header.type != EFI_DEV_END_PATH && + node->header.type != EFI_DEV_END_PATH2; + node = (const void *)node + node_len) { + node_len = get_unaligned_le16(&node->header.length); + + if (node_len < sizeof(node->header)) + return NULL; + + if (node->header.type != EFI_DEV_MEDIA || + node->header.sub_type != EFI_DEV_MEDIA_HARD_DRIVE) + continue; + + if (node_len < sizeof(node->hd)) + return NULL; + + if (node->hd.partition_format != EFI_HD_PARTITION_FORMAT_GPT || + node->hd.signature_type != EFI_HD_SIGNATURE_TYPE_GUID) + continue; + + return &node->hd; + } + + return NULL; +} + +static void efi_bli_populate_loader_part_uuid(const efi_loaded_image_t *image) +{ + static efi_guid_t device_path_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; + efi_char16_t partuuid[UUID_STRING_LEN + 1]; + const struct efi_hd_dev_path *hd_node; + const struct efi_dev_path *path; + + if (efi_bs_call(handle_protocol, efi_table_attr(image, device_handle), + &device_path_guid, (void **)&path) != EFI_SUCCESS) + return; + + hd_node = efi_bli_find_hd_node(path); + if (!hd_node) + return; + + if (efi_snprintf(partuuid, ARRAY_SIZE(partuuid), "%pUl", + &hd_node->signature) != UUID_STRING_LEN) + return; + + set_efi_var(L"LoaderDevicePartUUID", &loader_entry_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(partuuid), partuuid); +} + +void efi_bli_set_variables(const efi_loaded_image_t *image) +{ + static efi_char16_t loader_info[] = L"Linux EFI stub " UTS_RELEASE; + unsigned long size = 0; + + if (!image) + return; + + if (get_efi_var(L"LoaderInfo", &loader_entry_guid, + NULL, &size, NULL) != EFI_NOT_FOUND) + return; + + if (set_efi_var(L"LoaderInfo", &loader_entry_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(loader_info), loader_info) != EFI_SUCCESS) + return; + + efi_bli_populate_loader_part_uuid(image); +} diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c index 42d6073bcd06..2a95f4ea104a 100644 --- a/drivers/firmware/efi/libstub/efi-stub.c +++ b/drivers/firmware/efi/libstub/efi-stub.c @@ -165,6 +165,7 @@ efi_status_t efi_stub_common(efi_handle_t handle, dpy = setup_primary_display(); efi_retrieve_eventlog(); + efi_bli_set_variables(image); /* Ask the firmware to clear memory on unclean shutdown */ efi_enable_reset_attack_mitigation(); diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h index 880c1d0c464b..4f9e7ae28b6c 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -1072,6 +1072,8 @@ efi_status_t efi_random_alloc(unsigned long size, unsigned long align, int memory_type, unsigned long alloc_min, unsigned long alloc_max); +void efi_bli_set_variables(const efi_loaded_image_t *image); + efi_status_t efi_random_get_seed(void); efi_status_t check_platform_features(void); diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c index 80556a7e7552..729a962042ab 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -1016,6 +1016,7 @@ void __noreturn efi_stub_entry(efi_handle_t handle, efi_random_get_seed(); efi_retrieve_eventlog(); + efi_bli_set_variables(image); setup_graphics(boot_params); diff --git a/include/linux/efi.h b/include/linux/efi.h index aa15ff88539b..4bc47df2d8a7 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -957,6 +957,17 @@ extern int efi_status_to_err(efi_status_t status); #define EFI_DEV_END_INSTANCE 0x01 #define EFI_DEV_END_ENTIRE 0xFF +enum efi_hd_partition_format { + EFI_HD_PARTITION_FORMAT_MBR = 1, + EFI_HD_PARTITION_FORMAT_GPT, +}; + +enum efi_hd_signature_type { + EFI_HD_SIGNATURE_TYPE_NONE, + EFI_HD_SIGNATURE_TYPE_MBR, + EFI_HD_SIGNATURE_TYPE_GUID, +}; + struct efi_generic_dev_path { u8 type; u8 sub_type; @@ -988,6 +999,16 @@ struct efi_rel_offset_dev_path { u64 ending_offset; } __packed; +struct efi_hd_dev_path { + struct efi_generic_dev_path header; + u32 partition_number; + u64 partition_start; + u64 partition_size; + efi_guid_t signature; + u8 partition_format; + u8 signature_type; +} __packed; + struct efi_mem_mapped_dev_path { struct efi_generic_dev_path header; u32 memory_type; @@ -1007,6 +1028,7 @@ struct efi_dev_path { struct efi_pci_dev_path pci; struct efi_vendor_dev_path vendor; struct efi_rel_offset_dev_path rel_offset; + struct efi_hd_dev_path hd; }; } __packed; From 1e50ed98255b2bf68fa881595c44c6be2a037ac0 Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Mon, 5 Oct 2026 16:22:50 -0700 Subject: [PATCH 21/35] efivarfs: add nostatfs mount option to skip QueryVariableInfo() QueryVariableInfo() is an EFI runtime service that, on some firmware, takes tens of milliseconds and runs with preemption disabled, stalling the CPU that services it (and, on firmware that services it via SMM, all CPUs). efivarfs_statfs() calls it (rate-limited since commit b2326338dc68 ("efivarfs: Rate limit statfs() handler")) to report the variable-store used/available capacity, so any statfs(2) -- e.g. every "df" -- can inject that stall into unrelated latency-sensitive workloads. Add a negatable "nostatfs" mount option: with nostatfs, statfs(2) skips QueryVariableInfo() and reports zero used/available; with statfs it reports the capacity as before. It defaults to nostatfs on CONFIG_PREEMPT_RT so real-time kernels do not take the stall out of the box, but statfs can be passed there to force reporting back on -- e.g. for tools such as fwupd that need the efivars free space to update Secure Boot key databases. The option can also be toggled on a live mount via remount, so reporting can be enabled only for the duration of a firmware update without unmounting the boot-time efivarfs mount: mount -o remount,statfs /sys/firmware/efi/efivars # reporting on mount -o remount,nostatfs /sys/firmware/efi/efivars # reporting off A remount that does not specify statfs/nostatfs keeps the current setting. Tested on an Intel Xeon E5-2628L v4, 6.12 kernel: a default statfs(2) takes ~66 ms (the firmware call), versus ~11 us with nostatfs. Suggested-by: Ard Biesheuvel Suggested-by: Sebastian Andrzej Siewior Signed-off-by: Prashant Singh Signed-off-by: Ard Biesheuvel --- Documentation/filesystems/efivarfs.rst | 12 +++++++++ fs/efivarfs/internal.h | 1 + fs/efivarfs/super.c | 36 ++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/Documentation/filesystems/efivarfs.rst b/Documentation/filesystems/efivarfs.rst index f646c3f0980f..23164464ca2b 100644 --- a/Documentation/filesystems/efivarfs.rst +++ b/Documentation/filesystems/efivarfs.rst @@ -37,6 +37,18 @@ accidentally. |4_bytes_of_attributes + efivar_data| +-----------------------------------+ +Mount options +============= + +========== ================================================================== +(no)statfs Control whether ``statfs(2)`` reports the variable-store + used/available. Disabling it skips the ``QueryVariableInfo()`` + EFI runtime service call, which might block all CPUs for a few + milliseconds, and reports 0/0 for used/available instead. + Defaults to ``statfs``, except on ``CONFIG_PREEMPT_RT`` where + it defaults to ``nostatfs``. +========== ================================================================== + *See also:* - Documentation/admin-guide/acpi/ssdt-overlays.rst diff --git a/fs/efivarfs/internal.h b/fs/efivarfs/internal.h index f913b6824289..f5c1a25be996 100644 --- a/fs/efivarfs/internal.h +++ b/fs/efivarfs/internal.h @@ -11,6 +11,7 @@ struct efivarfs_mount_opts { kuid_t uid; kgid_t gid; + bool nostatfs; }; struct efivarfs_fs_info { diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c index 8d33f11db2a1..12746ced6dc6 100644 --- a/fs/efivarfs/super.c +++ b/fs/efivarfs/super.c @@ -74,6 +74,8 @@ static int efivarfs_show_options(struct seq_file *m, struct dentry *root) if (!gid_eq(opts->gid, GLOBAL_ROOT_GID)) seq_printf(m, ",gid=%u", from_kgid_munged(&init_user_ns, opts->gid)); + if (opts->nostatfs) + seq_puts(m, ",nostatfs"); return 0; } @@ -82,13 +84,18 @@ static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf) const u32 attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS; + struct efivarfs_fs_info *sfi = dentry->d_sb->s_fs_info; u64 storage_space, remaining_space, max_variable_size; u64 id = huge_encode_dev(dentry->d_sb->s_dev); efi_status_t status; - /* Some UEFI firmware does not implement QueryVariableInfo() */ + /* + * With nostatfs, or on firmware that does not implement + * QueryVariableInfo(), report zero used/available. + */ storage_space = remaining_space = 0; - if (efi_rt_services_supported(EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO)) { + if (!sfi->mount_opts.nostatfs && + efi_rt_services_supported(EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO)) { static DEFINE_RATELIMIT_STATE(_rs, 2 * HZ, 5); static u64 storage, remaining; static DEFINE_SPINLOCK(lock); @@ -323,12 +330,13 @@ static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, } enum { - Opt_uid, Opt_gid, + Opt_uid, Opt_gid, Opt_statfs, }; static const struct fs_parameter_spec efivarfs_parameters[] = { fsparam_uid("uid", Opt_uid), fsparam_gid("gid", Opt_gid), + fsparam_flag_no("statfs", Opt_statfs), {}, }; @@ -350,6 +358,9 @@ static int efivarfs_parse_param(struct fs_context *fc, struct fs_parameter *para case Opt_gid: opts->gid = result.gid; break; + case Opt_statfs: + opts->nostatfs = result.negated; + break; default: return -EINVAL; } @@ -402,11 +413,17 @@ static int efivarfs_get_tree(struct fs_context *fc) static int efivarfs_reconfigure(struct fs_context *fc) { + struct efivarfs_fs_info *sfi = fc->root->d_sb->s_fs_info; + struct efivarfs_fs_info *new_sfi = fc->s_fs_info; + if (!efivar_supports_writes() && !(fc->sb_flags & SB_RDONLY)) { pr_err("Firmware does not support SetVariableRT. Can not remount with rw\n"); return -EINVAL; } + /* statfs()/show_options() read nostatfs locklessly; benign race. */ + data_race(sfi->mount_opts.nostatfs = new_sfi->mount_opts.nostatfs); + return 0; } @@ -524,8 +541,17 @@ static int efivarfs_init_fs_context(struct fs_context *fc) if (!sfi) return -ENOMEM; - sfi->mount_opts.uid = GLOBAL_ROOT_UID; - sfi->mount_opts.gid = GLOBAL_ROOT_GID; + if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) { + /* nostatfs is writable via remount; keep it if not respecified. */ + struct efivarfs_fs_info *old = fc->root->d_sb->s_fs_info; + + sfi->mount_opts.nostatfs = old->mount_opts.nostatfs; + } else { + sfi->mount_opts.uid = GLOBAL_ROOT_UID; + sfi->mount_opts.gid = GLOBAL_ROOT_GID; + /* QueryVariableInfo() stalls the CPU; default nostatfs on PREEMPT_RT. */ + sfi->mount_opts.nostatfs = IS_ENABLED(CONFIG_PREEMPT_RT); + } fc->s_fs_info = sfi; fc->ops = &efivarfs_context_ops; From d83fe67339f2032d8facf0fe8a047a4c5d15394f Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 2 Oct 2026 20:31:38 -0300 Subject: [PATCH 22/35] efi/libstub: Fix error unwind freeing fdt in allocate_new_fdt_and_exit_boot() Sashiko says fdt_addr can point to either an allocated fdt or the fdt from get_fdt() which is memory owned by FW. Only the allocated fdt should be freed on the error unwind path. Use a dedicated variable for the allocation's size so that the free does not get confused. Fixes: 4fc8e738ff3e ("efi: libstub: remove DT dependency from generic stub") Reviewed-by: Jonathan Cameron Signed-off-by: Jason Gunthorpe Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/fdt.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c index 23b3543d3041..f01450b49975 100644 --- a/drivers/firmware/efi/libstub/fdt.c +++ b/drivers/firmware/efi/libstub/fdt.c @@ -229,6 +229,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(void *handle, u32 desc_ver; efi_status_t status; struct exit_boot_struct priv; + unsigned long fdt_size_allocated = 0; unsigned long fdt_addr = 0; unsigned long fdt_size = 0; @@ -251,12 +252,13 @@ efi_status_t allocate_new_fdt_and_exit_boot(void *handle, if (strstr(cmdline_ptr, "dtb=")) efi_err("Ignoring DTB from command line.\n"); } else { - status = efi_load_dtb(image, &fdt_addr, &fdt_size); + status = efi_load_dtb(image, &fdt_addr, &fdt_size_allocated); if (status != EFI_SUCCESS && status != EFI_NOT_READY) { efi_err("Failed to load device tree!\n"); goto fail; } + fdt_size = fdt_size_allocated; } if (fdt_addr) { @@ -334,7 +336,7 @@ efi_status_t allocate_new_fdt_and_exit_boot(void *handle, efi_free(MAX_FDT_SIZE, *new_fdt_addr); fail: - efi_free(fdt_size, fdt_addr); + efi_free(fdt_size_allocated, fdt_addr); if (!efi_novamap) efi_bs_call(free_pool, priv.runtime_map); From fd4465f49b53c7379d10610f8edb71f77366a5b7 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 2 Oct 2026 20:31:39 -0300 Subject: [PATCH 23/35] efi/riscv: libstub: Don't set image_size in handle_kernel_image() Sashiko points out the image_size should only be set if image_addr was reassigned to some allocated memory. If image_size is not zero then the caller will free the image_addr pointer. efi_kaslr_relocate_kernel() never works like that, if it changes image_addr to allocated memory the free is done through reserve_size. Fixes: b7ac4b8ee73d ("riscv: libstub: Implement KASLR by using generic functions") Signed-off-by: Jason Gunthorpe Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/riscv-stub.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/efi/libstub/riscv-stub.c b/drivers/firmware/efi/libstub/riscv-stub.c index e7d9204baee3..725b634c5179 100644 --- a/drivers/firmware/efi/libstub/riscv-stub.c +++ b/drivers/firmware/efi/libstub/riscv-stub.c @@ -37,17 +37,14 @@ efi_status_t handle_kernel_image(unsigned long *image_addr, kernel_codesize = __init_text_end - _start; kernel_memsize = kernel_size + (_end - _edata); *image_addr = (unsigned long)_start; - *image_size = kernel_memsize; - *reserve_size = *image_size; + *reserve_size = kernel_memsize; status = efi_kaslr_relocate_kernel(image_addr, reserve_addr, reserve_size, kernel_size, kernel_codesize, kernel_memsize, efi_kaslr_get_phys_seed(image_handle)); - if (status != EFI_SUCCESS) { + if (status != EFI_SUCCESS) efi_err("Failed to relocate kernel\n"); - *image_size = 0; - } return status; } From 8ea857a2bb547e388e86ca42c58a97e162e09f83 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 2 Oct 2026 20:31:40 -0300 Subject: [PATCH 24/35] efi/libstub: Free cmdline_ptr in efi_pe_entry Sashiko points out that efi_handle_cmdline() allocates this memory and hands it over to the caller. If efi_pe_entry() ever returns it should be freed. Add a __free annotation. Fixes: 42c8ea3dca09 ("efi: libstub: Factor out EFI stub entrypoint into separate file") Signed-off-by: Jason Gunthorpe Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/efi-stub-entry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/efi/libstub/efi-stub-entry.c b/drivers/firmware/efi/libstub/efi-stub-entry.c index 8ecd8cd72561..efa3ab8ad1c6 100644 --- a/drivers/firmware/efi/libstub/efi-stub-entry.c +++ b/drivers/firmware/efi/libstub/efi-stub-entry.c @@ -41,7 +41,6 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle, unsigned long image_addr; unsigned long image_size = 0; /* addr/point and size pairs for memory management*/ - char *cmdline_ptr = NULL; unsigned long reserve_addr = 0; unsigned long reserve_size = 0; @@ -63,6 +62,7 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle, return status; } + char *cmdline_ptr __free(efi_pool) = NULL; status = efi_handle_cmdline(image, &cmdline_ptr); if (status != EFI_SUCCESS) return status; From 4c86b5f3fa78347fa9cbc9e8ec06615b2f80e397 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Thu, 15 Oct 2026 13:38:58 +0200 Subject: [PATCH 25/35] efi/libstub: arm64: Don't use the platform register x18 The UEFI specification designates x18, the platform register, as "do not use" on AArch64, so that UEFI code is compatible both with an OS that assigns a fixed role to x18, and with one that uses it as a temporary register. The arm64 EFI stub and the EFI zboot decompressor only avoid x18 if the kernel is built with shadow call stacks, as that is the only case where -ffixed-x18 is added to KBUILD_CFLAGS, which the stub inherits. Otherwise, the compiler is free to use x18 as a temporary register, and it does so. So pass -ffixed-x18 unconditionally when building the stub for arm64, so that the image can be run by loaders that rely on x18 being left alone, such as a kernel that uses it for its shadow call stack. Cc: Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 564773c89d14..e13d4dfe27c5 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -21,7 +21,7 @@ cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ $(CC_FLAGS_DIALECT) \ # arm64 uses the full KBUILD_CFLAGS so it's necessary to explicitly # disable the stackleak plugin -cflags-$(CONFIG_ARM64) += -fpie $(DISABLE_KSTACK_ERASE) \ +cflags-$(CONFIG_ARM64) += -fpie $(DISABLE_KSTACK_ERASE) -ffixed-x18 \ -fno-unwind-tables -fno-asynchronous-unwind-tables cflags-$(CONFIG_ARM) += -DEFI_HAVE_STRLEN -DEFI_HAVE_STRNLEN \ -DEFI_HAVE_MEMCHR -DEFI_HAVE_STRRCHR \ From d4d11273292442860751713cb061c5691dd52ae3 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Tue, 6 Oct 2026 13:42:21 -0700 Subject: [PATCH 26/35] efi/libstub: Declare the x86 stub's assembly entry points efi_stub_entry() and efi_handover_entry() are entered from efi-mixed.S and, through the efi32_stub_entry() and efi64_stub_entry() aliases, by boot loaders using the EFI handover protocol, but have no prototypes. Declare them in x86-stub.h, as asmlinkage, so that the stub can be built with the kernel's -Wmissing-prototypes. Signed-off-by: Kees Cook [ardb: fix i386 GCC build by scoping to CONFIG_EFI_MIXED=y] Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/x86-stub.c | 2 ++ drivers/firmware/efi/libstub/x86-stub.h | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c index f4799e29f4cf..253dd3ba8fab 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -912,6 +912,7 @@ static void __noreturn enter_kernel(unsigned long kernel_addr, * return. On failure, it will exit to the firmware via efi_exit() instead of * returning. */ +MIXED_EXTERN void __noreturn efi_stub_entry(efi_handle_t handle, efi_system_table_t *sys_table_arg, struct boot_params *boot_params) @@ -1055,6 +1056,7 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle, } #ifdef CONFIG_EFI_HANDOVER_PROTOCOL +MIXED_EXTERN void efi_handover_entry(efi_handle_t handle, efi_system_table_t *sys_table_arg, struct boot_params *boot_params) { diff --git a/drivers/firmware/efi/libstub/x86-stub.h b/drivers/firmware/efi/libstub/x86-stub.h index 1c20e99a6494..d955ac7cc694 100644 --- a/drivers/firmware/efi/libstub/x86-stub.h +++ b/drivers/firmware/efi/libstub/x86-stub.h @@ -8,6 +8,22 @@ extern const u16 trampoline_ljmp_imm_offset; efi_status_t efi_adjust_memory_range_protection(unsigned long start, unsigned long size); +#ifdef CONFIG_EFI_MIXED +#define MIXED_EXTERN asmlinkage + +struct boot_params; + +asmlinkage void __noreturn efi_stub_entry(efi_handle_t handle, + efi_system_table_t *sys_table_arg, + struct boot_params *boot_params); +asmlinkage void efi_handover_entry(efi_handle_t handle, + efi_system_table_t *sys_table_arg, + struct boot_params *boot_params); + +#else +#define MIXED_EXTERN static +#endif + #ifdef CONFIG_X86_64 efi_status_t efi_setup_5level_paging(void); void efi_5level_switch(void); From 7aea1470a4cbd2a20624991fe3c3d77201ee4518 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Tue, 6 Oct 2026 13:42:22 -0700 Subject: [PATCH 27/35] efi/libstub: Build the x86 stub from KBUILD_CFLAGS The x86 stub replaces KBUILD_CFLAGS with a short list of its own, so new kernel-wide compiler options go missing, unless explicitly remembered or when lacking them breaks the build, e.g. commit 5ff8ad3909524 ("kbuild: Add '-fms-extensions' to areas with dedicated CFLAGS") did. Today, several still go missing that are provided to non-x86 stub builds, e.g. -ftrivial-auto-var-init, -fzero-call-used-regs, -fstrict-flex-arrays=3, and the various kernel's warnings. Flip the x86 option logic to match the other architectures. Since the x86 stub is linked into the decompressor rather than the kernel proper, remove the kernel code model, the i386 register calling convention, and the kernel's reduced stack alignment, along with the retpoline and return thunks and the call padding, which only the kernel provides, and disable kernel stack erasing as the other architectures do. The existing -mcmodel=small, -march=i386, and -fPIC still get overrides. Build with -fcf-protection=none, since nothing enables IBT while the stub runs: the firmware applies forward-edge CFI only to images that advertise it, which bzImage does not. New flags that change code generation or semantics in a plain x86_64 defconfig hardening.config stub: Hardening: -ftrivial-auto-var-init=zero -fzero-init-padding-bits=all -fstrict-flex-arrays=3 -fno-strict-overflow -fno-delete-null-pointer-checks -fno-allow-store-data-races -fno-jump-tables (from the default IBT config) C semantics: -funsigned-char -fno-common x86 code generation: -mno-sse2 -mno-3dnow -mno-avx -mno-sse4a (stub already added -mno-mmx -mno-sse) -mno-80387 -mno-fp-ret-in-387 -mskip-rax-setup -falign-jumps=1 -falign-loops=1 -fmin-function-alignment=16 -fomit-frame-pointer -fconserve-stack -fno-stack-clash-protection -fno-stack-check -fno-builtin-wcslen Warnings (lots and lots, but notably): -Wall -Wextra -Wundef -Wmissing-prototypes -Wvla-larger-than=1 -Wimplicit-fallthrough=5 Present but with no effect, because the stub's later flags override them: -O2, overridden by -Os -fstack-protector-* flags, overridden by -fno-stack-protector -fno-PIE, overridden by -fPIC -fcf-protection=branch, overridden by -fcf-protection=none the stack-erase plugin, loaded and then disabled Build tested ARCH=x86_64 defconfig hardening.config, plus retpolines, return thunks, call depth tracking, IBT, GCC plugins, and EFI mixed mode, with GCC 16.2.0 and Clang 24.0.0git, and ARCH=i386 defconfig hardening.config with GCC 16.2.0. Each booted through the EFI stub to userspace under QEMU with x64 and IA32 OVMF, the latter in mixed mode for x86_64. Signed-off-by: Kees Cook Reviewed-by: Nathan Chancellor Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/Makefile | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index e13d4dfe27c5..1931d51ca15a 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -6,18 +6,22 @@ # enabled, even if doing so doesn't break the build. # -# non-x86 reuses KBUILD_CFLAGS, x86 does not cflags-y := $(KBUILD_CFLAGS) -cflags-$(CONFIG_X86_32) := -march=i386 -cflags-$(CONFIG_X86_64) := -mcmodel=small -cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ $(CC_FLAGS_DIALECT) \ - -fPIC -fno-strict-aliasing -mno-red-zone \ - -mno-mmx -mno-sse -fshort-wchar \ - -Wno-pointer-sign \ - $(call cc-disable-warning, address-of-packed-member) \ - -fno-asynchronous-unwind-tables \ - $(CLANG_FLAGS) +# x86 links the stub into the decompressor rather than the kernel proper, so +# drop the kernel's code model, calling convention, and stack alignment, and +# the mitigations that rely on thunks and patch sites only the kernel has. +cflags-$(CONFIG_X86) := $(filter-out -mcmodel=kernel \ + -mregparm=3 -freg-struct-return \ + -mpreferred-stack-boundary=% \ + -mstack-alignment=% \ + $(RETPOLINE_CFLAGS) $(RETHUNK_CFLAGS) \ + $(PADDING_CFLAGS), $(cflags-y)) +cflags-$(CONFIG_X86_32) += -march=i386 +cflags-$(CONFIG_X86_64) += -mcmodel=small +cflags-$(CONFIG_X86) += -fPIC $(DISABLE_KSTACK_ERASE) +# Nothing enables IBT while the stub runs, so ENDBR would only take space. +cflags-$(CONFIG_X86) += $(call cc-option,-fcf-protection=none) # arm64 uses the full KBUILD_CFLAGS so it's necessary to explicitly # disable the stackleak plugin From 7762562c9209fba6290f60cbf03a7071b524d751 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Tue, 6 Oct 2026 13:42:23 -0700 Subject: [PATCH 28/35] efi/libstub: Disable kernel stack erasing in the common flags Every architecture that builds the stub now adds $(DISABLE_KSTACK_ERASE) to its own flags, so add it once to the flags they share instead. Signed-off-by: Kees Cook Reviewed-by: Nathan Chancellor Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/Makefile | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 1931d51ca15a..da9173c12935 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -19,22 +19,18 @@ cflags-$(CONFIG_X86) := $(filter-out -mcmodel=kernel \ $(PADDING_CFLAGS), $(cflags-y)) cflags-$(CONFIG_X86_32) += -march=i386 cflags-$(CONFIG_X86_64) += -mcmodel=small -cflags-$(CONFIG_X86) += -fPIC $(DISABLE_KSTACK_ERASE) +cflags-$(CONFIG_X86) += -fPIC # Nothing enables IBT while the stub runs, so ENDBR would only take space. cflags-$(CONFIG_X86) += $(call cc-option,-fcf-protection=none) -# arm64 uses the full KBUILD_CFLAGS so it's necessary to explicitly -# disable the stackleak plugin -cflags-$(CONFIG_ARM64) += -fpie $(DISABLE_KSTACK_ERASE) -ffixed-x18 \ - -fno-unwind-tables -fno-asynchronous-unwind-tables +cflags-$(CONFIG_ARM64) += -fpie -ffixed-x18 -fno-unwind-tables \ + -fno-asynchronous-unwind-tables cflags-$(CONFIG_ARM) += -DEFI_HAVE_STRLEN -DEFI_HAVE_STRNLEN \ -DEFI_HAVE_MEMCHR -DEFI_HAVE_STRRCHR \ -DEFI_HAVE_STRCMP -fno-builtin -fpic \ - $(call cc-option,-mno-single-pic-base) \ - $(DISABLE_KSTACK_ERASE) -cflags-$(CONFIG_RISCV) += -fpic -DNO_ALTERNATIVE -mno-relax \ - $(DISABLE_KSTACK_ERASE) -cflags-$(CONFIG_LOONGARCH) += -fpie $(DISABLE_KSTACK_ERASE) + $(call cc-option,-mno-single-pic-base) +cflags-$(CONFIG_RISCV) += -fpic -DNO_ALTERNATIVE -mno-relax +cflags-$(CONFIG_LOONGARCH) += -fpie cflags-$(CONFIG_EFI_PARAMS_FROM_FDT) += -I$(srctree)/scripts/dtc/libfdt @@ -44,6 +40,7 @@ KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(cflags-y)) \ -D__NO_FORTIFY \ -ffreestanding \ -fno-stack-protector \ + $(DISABLE_KSTACK_ERASE) \ $(call cc-option,-fno-addrsig) \ -D__DISABLE_EXPORTS From 80779c1dca838b347a4b7e5e67f45e2adaee1c38 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Thu, 8 Oct 2026 10:13:19 +0200 Subject: [PATCH 29/35] efi: Constify sysfs attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement Thomas's suggestions to constify sysfs kobject attributes. Cc: Thomas Weißschuh Reviewed-by: Thomas Weißschuh Signed-off-by: Ard Biesheuvel --- arch/x86/platform/efi/efi.c | 11 ++++++----- drivers/firmware/efi/efi.c | 36 ++++++++++++++++++++---------------- include/linux/efi.h | 3 ++- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index 0c39adb96b91..d8fe93770c2a 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -891,7 +891,7 @@ bool efi_is_table_address(unsigned long phys_addr) #define EFI_ATTR_SHOW(name) \ static ssize_t name##_show(struct kobject *kobj, \ - struct kobj_attribute *attr, char *buf) \ + const struct kobj_attribute *attr, char *buf) \ { \ return sprintf(buf, "0x%lx\n", EFI_FIELD(name)); \ } @@ -900,11 +900,12 @@ EFI_ATTR_SHOW(fw_vendor); EFI_ATTR_SHOW(runtime); EFI_ATTR_SHOW(config_table); -struct kobj_attribute efi_attr_fw_vendor = __ATTR_RO(fw_vendor); -struct kobj_attribute efi_attr_runtime = __ATTR_RO(runtime); -struct kobj_attribute efi_attr_config_table = __ATTR_RO(config_table); +const struct kobj_attribute efi_attr_fw_vendor = __KOBJ_ATTR_RO(fw_vendor); +const struct kobj_attribute efi_attr_runtime = __KOBJ_ATTR_RO(runtime); +const struct kobj_attribute efi_attr_config_table = __KOBJ_ATTR_RO(config_table); -umode_t efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +umode_t efi_attr_is_visible(struct kobject *kobj, const struct attribute *attr, + int n) { if (attr == &efi_attr_fw_vendor.attr) { if (efi_enabled(EFI_PARAVIRT) || diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index e08ce813a3d4..8026264d4990 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -131,7 +131,7 @@ struct kobject *efi_kobj; * one value per file rule! */ static ssize_t systab_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) + const struct kobj_attribute *attr, char *buf) { char *str = buf; @@ -155,21 +155,21 @@ static ssize_t systab_show(struct kobject *kobj, return str - buf; } -static struct kobj_attribute efi_attr_systab = __ATTR_RO_MODE(systab, 0400); +static const struct kobj_attribute efi_attr_systab = __KOBJ_ATTR_RO_MODE(systab, 0400); static ssize_t fw_platform_size_show(struct kobject *kobj, - struct kobj_attribute *attr, char *buf) + const struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", efi_enabled(EFI_64BIT) ? 64 : 32); } -extern __weak struct kobj_attribute efi_attr_fw_vendor; -extern __weak struct kobj_attribute efi_attr_runtime; -extern __weak struct kobj_attribute efi_attr_config_table; -static struct kobj_attribute efi_attr_fw_platform_size = - __ATTR_RO(fw_platform_size); +extern __weak const struct kobj_attribute efi_attr_fw_vendor; +extern __weak const struct kobj_attribute efi_attr_runtime; +extern __weak const struct kobj_attribute efi_attr_config_table; +static const struct kobj_attribute efi_attr_fw_platform_size = + __KOBJ_ATTR_RO(fw_platform_size); -static struct attribute *efi_subsys_attrs[] = { +static const struct attribute *const efi_subsys_attrs[] = { &efi_attr_systab.attr, &efi_attr_fw_platform_size.attr, &efi_attr_fw_vendor.attr, @@ -178,15 +178,16 @@ static struct attribute *efi_subsys_attrs[] = { NULL, }; -umode_t __weak efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, +umode_t __weak efi_attr_is_visible(struct kobject *kobj, + const struct attribute *attr, int n) { return attr->mode; } static const struct attribute_group efi_subsys_attr_group = { - .attrs = efi_subsys_attrs, - .is_visible = efi_attr_is_visible, + .attrs_const = efi_subsys_attrs, + .is_visible_const = efi_attr_is_visible, }; struct blocking_notifier_head efivar_ops_nh; @@ -401,12 +402,15 @@ static void __init efi_debugfs_init(void) static inline void efi_debugfs_init(void) {} #endif -static ssize_t efi_runtime_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +static ssize_t efi_runtime_show(struct kobject *kobj, + const struct kobj_attribute *attr, + char *buf) { return sprintf(buf, "%d\n", efi_enabled(EFI_RUNTIME_SERVICES)); } -static ssize_t efi_runtime_store(struct kobject *kobj, struct kobj_attribute *attr, +static ssize_t efi_runtime_store(struct kobject *kobj, + const struct kobj_attribute *attr, const char *buf, size_t count) { int ret; @@ -424,8 +428,8 @@ static ssize_t efi_runtime_store(struct kobject *kobj, struct kobj_attribute *at return count; } -static struct kobj_attribute efi_runtime_attr = - __ATTR(runtime_enable, 0644, efi_runtime_show, efi_runtime_store); +static const struct kobj_attribute efi_runtime_attr = + __KOBJ_ATTR(runtime_enable, 0644, efi_runtime_show, efi_runtime_store); static int __init efipostcore_init(void) { diff --git a/include/linux/efi.h b/include/linux/efi.h index ecb34be37a87..fda81d7c4850 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1353,7 +1353,8 @@ bool efi_config_table_is_usable(const efi_guid_t *guid, unsigned long table) return xen_efi_config_table_is_usable(guid, table); } -umode_t efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n); +umode_t efi_attr_is_visible(struct kobject *kobj, const struct attribute *attr, + int n); int ovmf_log_probe(unsigned long ovmf_debug_log_table); From 64217cbfc7c2a1646bb7d63c2130c6ab85ca55ea Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Thu, 8 Oct 2026 10:13:20 +0200 Subject: [PATCH 30/35] efi: Use sysfs_emit() not sprintf() to generate sysfs file contents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use sysfs_emit() where appropriate to generate the contents of EFI related sysfs nodes. This is a more robust wrapper around sprintf() designed for this purpose. Reported-by: Thomas Weißschuh Reviewed-by: Thomas Weißschuh Signed-off-by: Ard Biesheuvel --- arch/x86/platform/efi/efi.c | 2 +- drivers/firmware/efi/efi.c | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index d8fe93770c2a..5eb96c17883e 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -893,7 +893,7 @@ bool efi_is_table_address(unsigned long phys_addr) static ssize_t name##_show(struct kobject *kobj, \ const struct kobj_attribute *attr, char *buf) \ { \ - return sprintf(buf, "0x%lx\n", EFI_FIELD(name)); \ + return sysfs_emit(buf, "0x%lx\n", EFI_FIELD(name)); \ } EFI_ATTR_SHOW(fw_vendor); diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 8026264d4990..b4504542cb2c 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -133,26 +133,26 @@ struct kobject *efi_kobj; static ssize_t systab_show(struct kobject *kobj, const struct kobj_attribute *attr, char *buf) { - char *str = buf; + int offset = 0; if (!kobj || !buf) return -EINVAL; if (efi.acpi20 != EFI_INVALID_TABLE_ADDR) - str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20); + offset = sysfs_emit(buf, "ACPI20=0x%lx\n", efi.acpi20); if (efi.acpi != EFI_INVALID_TABLE_ADDR) - str += sprintf(str, "ACPI=0x%lx\n", efi.acpi); + offset += sysfs_emit_at(buf, offset, "ACPI=0x%lx\n", efi.acpi); /* * If both SMBIOS and SMBIOS3 entry points are implemented, the * SMBIOS3 entry point shall be preferred, so we list it first to * let applications stop parsing after the first match. */ if (efi.smbios3 != EFI_INVALID_TABLE_ADDR) - str += sprintf(str, "SMBIOS3=0x%lx\n", efi.smbios3); + offset += sysfs_emit_at(buf, offset, "SMBIOS3=0x%lx\n", efi.smbios3); if (efi.smbios != EFI_INVALID_TABLE_ADDR) - str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios); + offset += sysfs_emit_at(buf, offset, "SMBIOS=0x%lx\n", efi.smbios); - return str - buf; + return offset; } static const struct kobj_attribute efi_attr_systab = __KOBJ_ATTR_RO_MODE(systab, 0400); @@ -160,7 +160,7 @@ static const struct kobj_attribute efi_attr_systab = __KOBJ_ATTR_RO_MODE(systab, static ssize_t fw_platform_size_show(struct kobject *kobj, const struct kobj_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", efi_enabled(EFI_64BIT) ? 64 : 32); + return sysfs_emit(buf, "%d\n", efi_enabled(EFI_64BIT) ? 64 : 32); } extern __weak const struct kobj_attribute efi_attr_fw_vendor; @@ -406,7 +406,7 @@ static ssize_t efi_runtime_show(struct kobject *kobj, const struct kobj_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", efi_enabled(EFI_RUNTIME_SERVICES)); + return sysfs_emit(buf, "%d\n", efi_enabled(EFI_RUNTIME_SERVICES)); } static ssize_t efi_runtime_store(struct kobject *kobj, From b49be1e7478afdb4ec96aedeb17f906253326c48 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 9 Oct 2026 16:48:15 +0200 Subject: [PATCH 31/35] efi/runtime-wrappers: Don't park the worker after a recovered firmware fault Commit 4b2c033b5bc9 ("efi/runtime-wrappers: retire the worker if a wedged call ever returns") parks the efi_rts_wq worker instead of completing the call if EFI runtime services have been disabled by the time the firmware returns. The assumption is that this only happens if __efi_queue_work() has given up waiting for the call. However, arm64 also disables EFI runtime services when it recovers from a synchronous exception taken by the firmware: efi_runtime_fixup_exception() clears EFI_RUNTIME_SERVICES and unwinds the call so that it returns EFI_ABORTED to efi_call_rts(), while the caller is still waiting for the completion. The worker now parks without signalling it, so the caller is stuck for the full EFI_RTS_TIMEOUT of 120 seconds, after which it reports that the firmware is wedged, which is misleading. x86 is not affected, as it signals the completion from its page fault handler before parking the worker. So record explicitly that the caller has given up on the call, and only park the worker in that case. Fixes: 4b2c033b5bc9 ("efi/runtime-wrappers: retire the worker if a wedged call ever returns") Cc: Breno Leitao Reviewed-by: Breno Leitao Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/runtime-wrappers.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c index 740f422df337..33f256dd3d2c 100644 --- a/drivers/firmware/efi/runtime-wrappers.c +++ b/drivers/firmware/efi/runtime-wrappers.c @@ -126,6 +126,12 @@ struct efi_runtime_work efi_rts_work; */ #define EFI_RTS_TIMEOUT (120 * HZ) +/* + * Set when __efi_queue_work() gives up waiting for a call that is wedged in + * firmware. EFI runtime services are disabled for good at that point. + */ +static bool efi_rts_abandoned; + /* * efi_queue_work: Queue EFI runtime service call and wait for completion * @_rts: EFI runtime service function identifier @@ -336,7 +342,12 @@ 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 runtime services may also have been disabled because the + * firmware faulted, but the caller is still waiting for us in that + * case. Only park the worker if the caller has given up on it. + */ + if (READ_ONCE(efi_rts_abandoned)) efi_rts_park_worker(); efi_rts_work.status = status; @@ -373,6 +384,7 @@ static efi_status_t __efi_queue_work(enum efi_rts_ids id, EFI_RTS_TIMEOUT)) { pr_err("EFI runtime service %d wedged in firmware; disabling EFI runtime services\n", id); + WRITE_ONCE(efi_rts_abandoned, true); clear_bit(EFI_RUNTIME_SERVICES, &efi.flags); return EFI_ABORTED; } From aaf3ed0f46298e18ec8bc477cf0a37478e41f2ea Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Mon, 12 Oct 2026 14:06:12 +0200 Subject: [PATCH 32/35] efi/libstub: Don't replace an existing LINUX_EFI_MEMRESERVE table The LINUX_EFI_MEMRESERVE table is the root of a linked list of memory reservations that need to persist across kexec, such as the GICv3 LPI pending and property tables, which cannot be moved once the redistributors have been enabled. Each kernel adds its own reservations to the list that it finds via the EFI configuration table, and honours the ones made by its predecessors. The EFI stub installs a new, empty table unconditionally, replacing any existing one. This is fine on a cold boot, but not if the stub is being executed by a loader that passes on the configuration tables of an earlier kernel, e.g., a kexec loader that runs the incoming kernel's EFI stub, as the reservations made by earlier kernels would be lost. So leave an existing table in place, and only install a new one if none exists yet. Cc: Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/libstub/efi-stub.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c index 975148f986bf..6db10590f89b 100644 --- a/drivers/firmware/efi/libstub/efi-stub.c +++ b/drivers/firmware/efi/libstub/efi-stub.c @@ -83,6 +83,14 @@ static void install_memreserve_table(void) struct linux_efi_memreserve *rsv; efi_status_t status; + /* + * If an earlier boot stage installed a memreserve table already, e.g., + * a kexec loader passing on the persistent reservations made by the + * previous kernel, use it as is: installing a new one would replace it. + */ + if (get_efi_config_table(memreserve_table_guid)) + return; + status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*rsv), (void **)&rsv); if (status != EFI_SUCCESS) { From a278b88b766128e6ef5a5de0f350d897ccd14cb6 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Mon, 12 Oct 2026 11:59:10 +0200 Subject: [PATCH 33/35] efi: Retain the address of the LINUX_EFI_MEMRESERVE table The physical address of the LINUX_EFI_MEMRESERVE configuration table is only kept in __initdata, but a kexec loader that runs the incoming kernel's EFI stub needs it in order to pass the table on, so that the persistent memory reservations made by this kernel and its predecessors (e.g., for the GICv3 LPI tables) are carried over. So move it into struct efi, alongside the addresses of the other EFI configuration tables that may be passed on in this way. Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/efi.c | 12 ++++++------ include/linux/efi.h | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index b4504542cb2c..14b9c7ebb5df 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -46,6 +46,7 @@ struct efi __read_mostly efi = { .tpm_log = EFI_INVALID_TABLE_ADDR, .tpm_final_log = EFI_INVALID_TABLE_ADDR, .ovmf_debug_log = EFI_INVALID_TABLE_ADDR, + .mem_reserve = EFI_INVALID_TABLE_ADDR, #ifdef CONFIG_LOAD_UEFI_KEYS .mokvar_table = EFI_INVALID_TABLE_ADDR, #endif @@ -59,7 +60,6 @@ struct efi __read_mostly efi = { EXPORT_SYMBOL(efi); unsigned long __ro_after_init efi_rng_seed = EFI_INVALID_TABLE_ADDR; -static unsigned long __initdata mem_reserve = EFI_INVALID_TABLE_ADDR; static unsigned long __initdata rt_prop = EFI_INVALID_TABLE_ADDR; static unsigned long __initdata initrd = EFI_INVALID_TABLE_ADDR; @@ -663,7 +663,7 @@ static const efi_config_table_type_t common_tables[] __initconst = { {LINUX_EFI_TPM_EVENT_LOG_GUID, &efi.tpm_log, "TPMEventLog" }, {EFI_TCG2_FINAL_EVENTS_TABLE_GUID, &efi.tpm_final_log, "TPMFinalLog" }, {EFI_CC_FINAL_EVENTS_TABLE_GUID, &efi.tpm_final_log, "CCFinalLog" }, - {LINUX_EFI_MEMRESERVE_TABLE_GUID, &mem_reserve, "MEMRESERVE" }, + {LINUX_EFI_MEMRESERVE_TABLE_GUID, &efi.mem_reserve, "MEMRESERVE" }, {LINUX_EFI_INITRD_MEDIA_GUID, &initrd, "INITRD" }, {EFI_RT_PROPERTIES_TABLE_GUID, &rt_prop, "RTPROP" }, #ifdef CONFIG_OVMF_DEBUG_LOG @@ -806,8 +806,8 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables, efi_tpm_eventlog_init(); - if (mem_reserve != EFI_INVALID_TABLE_ADDR) { - unsigned long prsv = mem_reserve; + if (efi.mem_reserve != EFI_INVALID_TABLE_ADDR) { + unsigned long prsv = efi.mem_reserve; while (prsv) { struct linux_efi_memreserve *rsv; @@ -1094,10 +1094,10 @@ static struct linux_efi_memreserve *efi_memreserve_root __ro_after_init; static int __init efi_memreserve_map_root(void) { - if (mem_reserve == EFI_INVALID_TABLE_ADDR) + if (efi.mem_reserve == EFI_INVALID_TABLE_ADDR) return -ENODEV; - efi_memreserve_root = memremap(mem_reserve, + efi_memreserve_root = memremap(efi.mem_reserve, sizeof(*efi_memreserve_root), MEMREMAP_WB); if (WARN_ON_ONCE(!efi_memreserve_root)) diff --git a/include/linux/efi.h b/include/linux/efi.h index fda81d7c4850..cd8728702e93 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -650,6 +650,7 @@ extern struct efi { unsigned long mokvar_table; /* MOK variable config table */ unsigned long coco_secret; /* Confidential computing secret table */ unsigned long unaccepted; /* Unaccepted memory table */ + unsigned long mem_reserve; /* Persistent memory reservations */ efi_get_time_t *get_time; efi_set_time_t *set_time; From da0ad36d2e95fb029c3a14ad99a78beebc29507f Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Mon, 12 Oct 2026 14:19:35 +0200 Subject: [PATCH 34/35] efi: Initialize all configuration table addresses to EFI_INVALID_TABLE_ADDR The addresses of the MOK variable table, the confidential computing secret area and the unaccepted memory table in struct efi are only initialized to EFI_INVALID_TABLE_ADDR if support for the respective table is enabled, and are left at 0 otherwise. This is harmless for the existing users, which are compiled out in that case, but code that consumes the table addresses in struct efi generically, such as a kexec loader that passes the configuration tables on to the incoming kernel, would mistake 0 for a valid table address. So initialize all of them unconditionally: efi_config_parse_tables() only assigns them if support for the table is enabled, and so they remain EFI_INVALID_TABLE_ADDR otherwise. Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/efi.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 14b9c7ebb5df..9bc22de0d67e 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -47,15 +47,9 @@ struct efi __read_mostly efi = { .tpm_final_log = EFI_INVALID_TABLE_ADDR, .ovmf_debug_log = EFI_INVALID_TABLE_ADDR, .mem_reserve = EFI_INVALID_TABLE_ADDR, -#ifdef CONFIG_LOAD_UEFI_KEYS .mokvar_table = EFI_INVALID_TABLE_ADDR, -#endif -#ifdef CONFIG_EFI_COCO_SECRET .coco_secret = EFI_INVALID_TABLE_ADDR, -#endif -#ifdef CONFIG_UNACCEPTED_MEMORY .unaccepted = EFI_INVALID_TABLE_ADDR, -#endif }; EXPORT_SYMBOL(efi); From 2c493ac8c347167eec9e4e985d2b58d5fa6fd420 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Sun, 11 Oct 2026 13:20:48 +0200 Subject: [PATCH 35/35] lkdtm: Add more EFI runtime tests Two bugs were found in the arm64 EFI runtime support that weren't caught by LKDTM. The existing EFI_RUNTIME_CRASH test instructs the firmware to write to kernel memory that is mapped read-only, which usually fails as expected. However, this does not exercise the runtime mapping logic in the kernel, nor the fault recovery logic when the faulting address is inside the EFI runtime memory regions itself. So add two more test cases: - a write to R/O memory test that uses the firmware's own executable memory as an output buffer - a branch to memory that is occupied by the firmware's runtime services function table, which is usually mapped non-execute. This depends on the EFI memory attributes table having been exposed by the firmware and having been taken into account by the runtime mapping code, so this is used as a prerequisite by the new tests, otherwise it produces an XFAIL. Reviewed-by: Kees Cook Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/runtime-wrappers.c | 57 +++++++++++++++++++++++++ drivers/misc/lkdtm/bugs.c | 33 ++++++++++++++ include/linux/efi.h | 10 +++++ 3 files changed, 100 insertions(+) diff --git a/drivers/firmware/efi/runtime-wrappers.c b/drivers/firmware/efi/runtime-wrappers.c index 33f256dd3d2c..eea4f32e6474 100644 --- a/drivers/firmware/efi/runtime-wrappers.c +++ b/drivers/firmware/efi/runtime-wrappers.c @@ -337,6 +337,31 @@ static void __nocfi efi_call_rts(struct work_struct *work) * function identifier into efi_rts_work->efi_rts_id */ pr_err("Requested executing invalid EFI Runtime Service.\n"); + break; + +#if IS_ENABLED(CONFIG_LKDTM) + case EFI_LKDTM_RUNTIME_RO_TEST: + /* + * Call GetTime() with the output argument pointing to + * executable memory, which should be mapped read-only in the + * EFI page tables. + */ + status = efi_call_virt(get_time, + (void *)efi.runtime->set_virtual_address_map, + NULL); + break; + case EFI_LKDTM_RUNTIME_NX_TEST: + /* + * Call efi.runtime, which points to a data object in a EFI + * runtime region, as if it were a function pointer. + */ + { + struct { efi_status_t (*f)(int); } *s = (void *)&efi.runtime; + + status = arch_efi_call_virt(s, f, 0); + } + break; +#endif } efi_call_virt_check_flags(flags, efi_rts_work.caller); @@ -624,6 +649,34 @@ static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules, return status; } +#if IS_ENABLED(CONFIG_LKDTM) +static efi_status_t efi_lkdtm_rt_test(enum efi_rts_ids id) +{ + efi_status_t status; + + if (!efi_enabled(EFI_RUNTIME_SERVICES) || !efi_enabled(EFI_MEM_ATTR)) + return EFI_NOT_READY; + + switch (id) { + case EFI_LKDTM_RUNTIME_NX_TEST: + break; + case EFI_LKDTM_RUNTIME_RO_TEST: + if (efi.get_time && + efi_rt_services_supported(EFI_RT_SUPPORTED_GET_TIME)) + break; + fallthrough; + default: + return EFI_UNSUPPORTED; + } + + if (down_interruptible(&efi_runtime_lock)) + return EFI_NOT_READY; + status = __efi_queue_work(id, NULL); + up(&efi_runtime_lock); + return status; +} +#endif + void __init efi_native_runtime_setup(void) { efi.get_time = virt_efi_get_time; @@ -640,6 +693,10 @@ void __init efi_native_runtime_setup(void) efi.query_variable_info_nonblocking = virt_efi_query_variable_info_nb; efi.update_capsule = virt_efi_update_capsule; efi.query_capsule_caps = virt_efi_query_capsule_caps; + +#if IS_ENABLED(CONFIG_LKDTM) + efi.lkdtm_rt_test = efi_lkdtm_rt_test; +#endif } #ifdef CONFIG_ACPI_PRMT diff --git a/drivers/misc/lkdtm/bugs.c b/drivers/misc/lkdtm/bugs.c index 3eca2ef64aff..8ed368ea3e8b 100644 --- a/drivers/misc/lkdtm/bugs.c +++ b/drivers/misc/lkdtm/bugs.c @@ -841,6 +841,37 @@ static void __maybe_unused lkdtm_EFI_RUNTIME_CRASH(void) status); } +static void efi_rt_test(enum efi_rts_ids id, const char *desc) +{ + if (!efi_enabled(EFI_RUNTIME_SERVICES)) { + pr_err("XFAIL: EFI runtime services have been disabled\n"); + return; + } else if (!efi_enabled(EFI_MEM_ATTR)) { + pr_err("XFAIL: EFI memory attributes table not provided by firmware\n"); + return; + } else if (!efi.lkdtm_rt_test) { + pr_err("XFAIL: Only supported with native EFI runtime services\n"); + return; + } + + efi_status_t status = efi.lkdtm_rt_test(id); + if (status == EFI_UNSUPPORTED) + pr_err("XFAIL: EFI runtime service not implemented by firmware\n"); + else if (status != EFI_ABORTED || efi_enabled(EFI_RUNTIME_SERVICES)) + pr_err("FAIL: EFI %s memory not caught (%#lx)\n", + desc, status); +} + +static void __maybe_unused lkdtm_EFI_RUNTIME_WRITE_TO_RO(void) +{ + efi_rt_test(EFI_LKDTM_RUNTIME_RO_TEST, "write to RO"); +} + +static void __maybe_unused lkdtm_EFI_RUNTIME_BRANCH_TO_NX(void) +{ + efi_rt_test(EFI_LKDTM_RUNTIME_NX_TEST, "branch to NX"); +} + static struct crashtype crashtypes[] = { CRASHTYPE(PANIC), CRASHTYPE(PANIC_STOP_IRQOFF), @@ -876,6 +907,8 @@ static struct crashtype crashtypes[] = { CRASHTYPE(CORRUPT_PAC), #ifdef CONFIG_EFI CRASHTYPE(EFI_RUNTIME_CRASH), + CRASHTYPE(EFI_RUNTIME_WRITE_TO_RO), + CRASHTYPE(EFI_RUNTIME_BRANCH_TO_NX), #endif }; diff --git a/include/linux/efi.h b/include/linux/efi.h index cd8728702e93..cc1df1577d5f 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -631,6 +631,8 @@ typedef struct { extern unsigned long __ro_after_init efi_rng_seed; /* RNG Seed table */ +enum efi_rts_ids; + /* * All runtime access to EFI goes through this structure: */ @@ -667,6 +669,10 @@ extern struct efi { efi_get_next_high_mono_count_t *get_next_high_mono_count; efi_reset_system_t *reset_system; +#if IS_ENABLED(CONFIG_LKDTM) + efi_status_t (*lkdtm_rt_test)(enum efi_rts_ids id); +#endif + struct efi_memory_map memmap; unsigned long flags; } efi; @@ -1254,6 +1260,10 @@ enum efi_rts_ids { EFI_UPDATE_CAPSULE, EFI_QUERY_CAPSULE_CAPS, EFI_ACPI_PRM_HANDLER, +#if IS_ENABLED(CONFIG_LKDTM) + EFI_LKDTM_RUNTIME_RO_TEST, + EFI_LKDTM_RUNTIME_NX_TEST, +#endif }; union efi_rts_args;