Re: [RFC PATCH v2 12/24] unwind_user/eh_frame: Remove .eh_frame[_hdr] section on detected corruption

From
Jens Remus <hidden>
Date
2026-09-14 12:40:32
Also in
linux-s390, sashiko-reviews
Reply to
sashiko-bot@kernel.org, 2026-08-21
On 8/21/2026 10:18 PM, sashiko-bot@kernel.org wrote:
This commit adds logic to unregister .eh_frame or .eh_frame_hdr sections on
demand when corruption is detected during lookup. This prevents repeated
attempts to parse bad unwind sections by removing them immediately upon an
error return.
quoted
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
quoted
@@ -1202,6 +1202,15 @@ int eh_frame_find(unsigned long ip, struct unwind_user_frame *frame)
 	if (!ret)
 		ret = __find_frame_row(sec, &fde, ip, frame);
 
+	/*
+	 * Unregister .eh_frame[_hdr] in case of an error,
+	 * e.g. EINVAL (corrupted) or EFAULT (inaccessible).
+	 * Keep if ENOENT (not found) or EOPNOTSUPP (unsupported CFI).
+	 */
+	if (ret && (ret != -ENOENT && ret != -EOPNOTSUPP))
+		if (eh_frame_remove_section(sec->eh_frame_hdr_start))
+			dbg("eh_frame_remove_section() failed\n");
+
[Severity: High]
Does this introduce a race condition with memory unmapping and mapping?

If a thread is delayed after retrieving sec in eh_frame_find(), another
thread could unmap the library and map a new library at the exact same
virtual address, registering it with the same eh_frame_hdr_start. When the
first thread resumes and fails to parse against the new memory, passing the
virtual address sec->eh_frame_hdr_start to eh_frame_remove_section() seems
like it would unintentionally remove the newly loaded, valid library's
section.
I don't think that is possible.  eh_frame_find() and likewise
eh_frame_remove_section() both guard using eh_frame_srcu.  So while one
thread has a reference to a section that cannot be removed and replaced.
I am missing something?
[Severity: High]
Is it safe to assume -EINVAL always means corruption?

Looking at __do_cfi_program(), it also returns -EINVAL when kernel limits
are hit, such as exceeding EH_FRAME_CFI_INSN_LIMIT or
EH_FRAME_MAX_CODE_ALIGN.

If a valid library simply has a complex function that hits these limits,
wouldn't this code globally and permanently disable unwinding for it by
removing its section?
Following are the used conventions:
- EINVAL: bad .eh_frame; section should be removed
- EOPNOTSUPP: unsupported .eh_frame
- ENOENT: no .eh_frame for IP

One can argue whether a CIE/FDE with too many CFI instructions should
be considered bad or unsupported.  For now I decided for bad, to
prevent future unwind attempts in case of crafted .eh_frame.
[Severity: High]
This is a pre-existing issue, but looking at eh_frame_remove_section()
which is called here, is there a memory leak if mas_erase() drops the lock?

eh_frame_remove_section() holds mtree_lock and iterates via mas_for_each().
It calls __eh_frame_remove_section(), which calls mas_erase().

If mas_erase() needs to allocate memory via mas_nomem(mas, GFP_KERNEL), it
can drop the tree lock. If a concurrent thread inserts a new section
covering the same range during this unlocked window, mas_erase() retries,
erases the new section, and returns it.

__eh_frame_remove_section() then sees the returned entry doesn't match sec,
returns -EINVAL, and skips call_srcu(), potentially leaking the new section.
Help!  I fail to understand how the (internal) maple tree locking and
its advanced API are then supposed to be used correctly to iterate over
the items in the tree and remove some while doing so.  Is that why other
users use an external lock (MT_FLAGS_LOCK_EXTERN, MTREE_INIT_EXT())?
See MM_MT_FLAGS.
[Severity: Critical]
This is a pre-existing issue, but looking at __eh_frame_remove_section()
called by eh_frame_remove_section(), can a memory allocation failure lead
to a use-after-free?

If mas_nomem() fails to allocate memory during mas_erase(), it sets an
error state and aborts without removing the node, but still returns the
original sec:

__eh_frame_remove_section() {
    ...
    if (mas_erase(mas) != sec) {
        return -EINVAL;
    }
    call_srcu(&eh_frame_srcu, &sec->rcu, eh_frame_free_srcu);
    ...
}

Because __eh_frame_remove_section() only checks if the return value does
not equal sec, it misses the error state completely. It then calls
call_srcu() to free the section while the node remains active in the maple
tree. Could subsequent calls to eh_frame_find() load and access this freed
memory?
This is resolved in the next version of "[RFC PATCH v2 08/24] unwind_user/
eh_frame: Store .eh_frame_hdr section data in per-mm maple tree".  See my
respective reply.
quoted
 	return ret;
 }
Thanks and 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