Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH 1/5] dotrap(): keep the trap's Eprog alive across dont_queue_signals()
- X-seq: zsh-workers 55171
- From: Imre Péntek <imre@xxxxxxxxxx>
- To: zsh-workers@xxxxxxx
- Cc: Imre Péntek <imre@xxxxxxxxxx>, "Claude Opus 5.5" <noreply@xxxxxxxxxxxxx>
- Subject: [PATCH 1/5] dotrap(): keep the trap's Eprog alive across dont_queue_signals()
- Date: Wed, 23 Sep 2026 19:08:21 +0200
- Archived-at: <https://zsh.org/workers/55171>
- In-reply-to: <20260923170825.501947-1-imre@pentek.dev>
- List-id: <zsh-workers.zsh.org>
- References: <20260923170825.501947-1-imre@pentek.dev>
dotrap() fetches siglists[sig] and then calls dont_queue_signals(),
which runs any signals queued meanwhile. If one of those is the same
signal, the nested dotrap() runs the trap to completion; should that
trap replace itself (e.g. a function it calls has an EXIT trap doing
"trap '...' INT", as powerlevel10k does), the old Eprog is freed by the
nested execode() and the outer dotrap() then executes freed memory.
Take a reference with useeprog() before signals can run and drop it
when done.
Found by holding down Ctrl-C at a powerlevel10k prompt (segfault);
AddressSanitizer reports heap-use-after-free in execode() called from
dotrapargs(), freed by a nested execode() reached via
zhandler() -> dotrap() -> dont_queue_signals().
Co-Authored-By: Claude Opus 5.5 <noreply@xxxxxxxxxxxxx>
---
Notes:
Reproducer: start "zsh -f", paste the lines below, then hold down
Ctrl-C for a few seconds. Without this patch,
AddressSanitizer reports heap-use-after-free in execode() (stock builds usually survive this one).
setopt interactivecomments
# 1: dotrap() runs queued signals before using the trap's Eprog; a nested
# run of the same trap replaces (frees) it via a function's EXIT trap.
T='f; return 130'
f() { trap : INT; trap "trap ${(q)T} INT" EXIT; repeat 50 : }
g() { f; : ${(l:2000000::ab:)}; return 0 }
functions -M g
setopt promptsubst
PS1='${$((g()))+}%# '
precmd() { trap "$T" INT }
Src/signals.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/Src/signals.c b/Src/signals.c
index ff9797f35..87493bbf1 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -1245,6 +1245,7 @@ void
dotrap(int sig)
{
void *funcprog;
+ Eprog heldprog = NULL;
int q = queue_signal_level();
if (sigtrapped[sig] & ZSIG_FUNC) {
@@ -1257,16 +1258,25 @@ dotrap(int sig)
#endif
funcprog = NULL;
}
- } else
+ } else {
funcprog = siglists[sig];
+ /*
+ * dont_queue_signals() below may run queued signals, whose
+ * traps may replace and free this one; keep it alive.
+ */
+ heldprog = (Eprog) funcprog;
+ useeprog(heldprog);
+ }
/*
* Copied from dotrapargs().
* (In fact, the gain from duplicating this appears to be virtually
* zero. Not sure why it's here.)
*/
- if ((sigtrapped[sig] & ZSIG_IGNORED) || !funcprog || errflag)
+ if ((sigtrapped[sig] & ZSIG_IGNORED) || !funcprog || errflag) {
+ freeeprog(heldprog);
return;
+ }
dont_queue_signals();
@@ -1278,6 +1288,7 @@ dotrap(int sig)
if (sig == SIGEXIT)
--in_exit_trap;
+ freeeprog(heldprog);
restore_queue_signals(q);
}
--
2.55.0
Messages sorted by:
Reverse Date,
Date,
Thread,
Author