[lib][uefi] fix a heap overflow, uninitialized con_out, and stale event list links - #531
Conversation
efi_core_new_debug_image_info_entry allocated sizeof(union EfiDebugImageInfo), the size of a single pointer, for the normal_image entry, then wrote a struct EfiDebugImageInfoNormal through it, overflowing the pool allocation by 16 bytes on every image load. Allocate the size of the structure actually stored there.
get_text_output_protocol returned a protocol struct with every member except output_string left uninitialized, so an application calling reset, clear_screen, or query_mode, or dereferencing the mode pointer, would jump through stack garbage. Zero-initialize the struct, provide benign implementations for the remaining callbacks, and point mode at a static 80x25 mode description.
process_pending_events moved ready events onto a stack-local list and invoked their callbacks with the nodes still chained to that local list head. After the function returned, each completed event's node kept pointing into the dead stack frame, so a later close_event or check_event calling delete_if_in_list would pass the non-null check and write through the dangling pointers, corrupting whatever occupied that stack memory. Pop each event off the local list, which clears its links, before invoking its callback.
There was a problem hiding this comment.
Pull request overview
This PR applies targeted memory-safety and correctness fixes in lib/uefi around debug image table allocation, console output protocol initialization, and event processing/wait behavior to prevent heap overflows, use of uninitialized function pointers/state, and stale list-node linkage.
Changes:
- Fix debug image info entry allocation to use the correct structure size.
- Fully initialize
EfiSimpleTextOutputProtocolcallbacks and provide a stablemodeobject. - Prevent stale list-node links in completed event processing and ensure
wait_for_event()stores the signaled event index.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lib/uefi/text_protocol.cpp | Initializes all Simple Text Output protocol callbacks and provides a static mode descriptor. |
| lib/uefi/events.cpp | Unlinks completed events before callbacks and sets the output index on wait completion. |
| lib/uefi/debug_support.cpp | Fixes pool allocation size for debug image info “normal” entries to avoid overflow. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| auto status = event_wait_timeout(&ev->ev, 200); | ||
| if (status == ERR_TIMED_OUT) { | ||
| continue; | ||
| } | ||
| *index = i; |
There was a problem hiding this comment.
Right — in the current single-threaded app model the only values that come back are NO_ERROR and ERR_TIMED_OUT (nothing else can destroy the event while the app is blocked here), but there's no reason to bake that assumption in. Updated: any other status now returns EFI_STATUS_DEVICE_ERROR instead of reporting a signaled event.
When event_wait_timeout completed without timing out, wait_for_event returned EFI_STATUS_SUCCESS without storing which event finished the wait, so callers read an uninitialized index. Store the index of the event that completed, and report EFI_STATUS_DEVICE_ERROR instead of success if the wait itself failed (e.g. the event was destroyed while being waited on).
fdb40e6 to
254a1f1
Compare
Four independent memory-safety fixes in lib/uefi, found while auditing the loader:
allocate the right size for debug image info entries:
efi_core_new_debug_image_info_entry()allocatedsizeof(union EfiDebugImageInfo)(8 bytes, a single pointer) for thenormal_imageentry and then wrote a 24-bytestruct EfiDebugImageInfoNormalthrough it, a 16-byte pool overflow on every image load.fully initialize the simple text output protocol:
get_text_output_protocol()returned a struct with every member exceptoutput_stringuninitialized. An application callingreset,clear_screen, orquery_mode, or dereferencingmode, jumped through stack garbage. All callbacks now have benign implementations andmodepoints at a static 80x25 description.unlink completed events before invoking callbacks:
process_pending_events()moved ready events onto a stack-local list and left the nodes chained there after returning. A laterclose_event/check_eventpasseddelete_if_in_list's non-null check and wrote through pointers into the dead stack frame. Events are now popped off the local list (which clears their links) before their callbacks run.set the output index when wait_for_event succeeds: the
event_wait_timeoutsuccess path returned without storing which event finished the wait, so callers read an uninitialized index.Validation (qemu-virt-arm64-test, Clang/LLD, WERROR=1):
helloworld_aa64.efiloads and runs (return code 0),ut allpasses 36/36, no faults in the loguefi_loadruns both succeed with PMMfree_countidentical before/after,ut all36/36The series is based on current master and is independent of #530; the two touch different hunks and apply cleanly in either order.