Re: [RFC PATCH v2 14/24] unwind_user/eh_frame: Add .eh_frame[_hdr] validation option

From
Jens Remus <hidden>
Date
2026-09-14 14:32:28
Also in
linux-s390, sashiko-reviews
Reply to
sashiko-bot@kernel.org, 2026-08-21
On 8/21/2026 10:10 PM, sashiko-bot@kernel.org wrote:
Add a debug feature to validate all .eh_frame[_hdr] sections when first
loading the file rather than on demand.
quoted
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
quoted
@@ -1216,6 +1216,92 @@ int eh_frame_find(unsigned long ip, struct unwind_user_frame *frame)
 	return ret;
 }
 
+#ifdef CONFIG_EH_FRAME_VALIDATION
+
+static int eh_frame_validate_section(struct eh_frame_section *sec)
+{
[ ... ]
quoted
+	table_start_ptr = (void __user *)sec->binary_search_table_start;
+	table_size = sec->binary_search_table_end - sec->binary_search_table_start;
+
+	for (i = 0; i < sec->fde_count; i++) {
[Severity: High]
Could this unbounded loop cause CPU stalls or trigger soft lockup warnings?

Since fde_count is controlled by the size of the mapped .eh_frame_hdr VMA,
it can be extremely large for maliciously crafted ELF files, potentially
stalling the CPU for seconds or minutes.

Because mmap_read_lock is released before this is called, would it be safer
to add a cond_resched() in this loop?
According to Steven this is not required.
quoted
+		struct eh_frame_fde fde;
+		unsigned long cur;
+		unsigned long func_addr, fde_addr;
+		int ret;
+
+		cur = sec->binary_search_table_start + i * entry_size;
+
+		scoped_user_read_access_size(table_start_ptr, table_size, Efault) {
+			/* Read function start address from table */
+			ret = read_encoded_pointer(sec, &cur,
+						   sec->binary_search_table_end,
+						   table_enc, &func_addr);
[Severity: High]
Does this bypass the Spectre v1 mitigations provided by the scoped access
macro?

The scoped_user_read_access_size() macro shadows the table_start_ptr variable
to enforce usage of a masked pointer. However, the code ignores that masked
pointer and instead uses the unmasked cur pointer derived outside the scope:

    cur = sec->binary_search_table_start + i * entry_size;

When read_encoded_pointer() uses this unmasked pointer inside
unsafe_get_user(), the masking mitigations are bypassed, allowing potential
arbitrary speculative reads.
True.  Will resolve as part of resolving the next finding.

quoted
+			if (ret) {
+				dbg_sec_ehfh(cur, "table[%lu]: failed to read function start address\n", i);
[Severity: High]
Is it safe to call printk wrappers like dbg_sec_ehfh() or dbg_sec() while user
access (UACCESS) is enabled?

Calling complex functions like printk inside a scoped_user_read_access_size()
block can lead to deadlocks or kernel panics. It does internal locking, can
sleep, and may clobber architecture-specific user access state like the SMAP
AC flag.
Will resolve by introducing and using a safe_read_encoded_pointer()
variant, similar to the safe_read_{fde|fre}() in the .sframe series.
This also gets rid of the scoped user access region and thus resolves
the previous finding as well.
quoted
+				return ret;
+			}
+			if (i && func_addr <= prev_func_addr) {
+				dbg_sec(".eh_frame_hdr: table[%lu]: not sorted\n", i);
+				return -EINVAL;
+			}
Regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
Thread (58 messages) 58 messages, 3 authors, last reply 2026-09-14
Keyboard shortcuts
hback out one level
jnext message in thread / next series-diff position
kprevious message in thread / previous series-diff position
ldrill in
Escclose help / fold thread tree
?toggle this help