Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH 5/5] raw_getbyte(): keep fds[] and watch_fds in step when traps run
- X-seq: zsh-workers 55175
- From: Imre Péntek <imre@xxxxxxxxxx>
- To: zsh-workers@xxxxxxx
- Cc: Imre Péntek <imre@xxxxxxxxxx>, "Claude Opus 5.5" <noreply@xxxxxxxxxxxxx>
- Subject: [PATCH 5/5] raw_getbyte(): keep fds[] and watch_fds in step when traps run
- Date: Wed, 23 Sep 2026 19:08:25 +0200
- Archived-at: <https://zsh.org/workers/55175>
- In-reply-to: <20260923170825.501947-1-imre@pentek.dev>
- List-id: <zsh-workers.zsh.org>
- References: <20260923170825.501947-1-imre@pentek.dev>
A trap run while ZLE waits for input may add "zle -F" handlers
(powerlevel10k's SIGINT trap does). raw_getbyte() then copied nwatch
entries and indexed fds[] with them although fds[] was sized for the
old count; likewise the zrealloc() of fds[] can itself run queued
signals between computing nfds and filling the array
(AddressSanitizer: heap-buffer-overflow in raw_getbyte()).
Only handle as many watchers as were polled, and queue signals while
taking the copy and while resizing fds[].
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 (preceding ones applied),
AddressSanitizer reports heap-buffer-overflow in raw_getbyte().
setopt interactivecomments
# 5: a trap adding "zle -F" watchers while zle handles watchers: the
# handler loop indexes fds[] with the new, larger watcher count.
exec {z}</dev/zero
h() { local c; read -r -u $1 -k 1 c }
zle -F $z h
w() { zle -F $1; exec {1}<&- }
t() { local f; exec {f}</dev/null; zle -F $f w }
trap 't; return 130' INT
Src/Zle/zle_main.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 921ebb9ec..c044de46f 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -719,11 +719,24 @@ raw_getbyte(long do_keytmout, char *cptr, int full)
* user decides to delete one from inside the
* handler function.
*/
- int lnwatch = nwatch;
- Watch_fd lwatch_fds = zalloc(lnwatch*sizeof(struct watch_fd));
+ int lnwatch;
+ Watch_fd lwatch_fds;
+
+ /*
+ * A trap run while we were waiting may have changed
+ * watch_fds; only the ones we polled have results.
+ */
+ queue_signals();
+ lnwatch = nwatch;
+# ifdef HAVE_POLL
+ if (lnwatch > nfds - 1)
+ lnwatch = nfds - 1;
+# endif
+ lwatch_fds = zalloc(lnwatch*sizeof(struct watch_fd));
memcpy(lwatch_fds, watch_fds, lnwatch*sizeof(struct watch_fd));
for (i = 0; i < lnwatch; i++)
lwatch_fds[i].func = ztrdup(lwatch_fds[i].func);
+ unqueue_signals();
for (i = 0; i < lnwatch; i++) {
Watch_fd lwatch_fd = lwatch_fds + i;
if (
@@ -792,6 +805,7 @@ raw_getbyte(long do_keytmout, char *cptr, int full)
# ifdef HAVE_POLL
/* Function may have added or removed handlers */
+ queue_signals(); /* keep nwatch and fds[] in step */
nfds = 1 + nwatch;
if (nfds > 1) {
fds = zrealloc(fds, sizeof(struct pollfd) * nfds);
@@ -813,6 +827,7 @@ raw_getbyte(long do_keytmout, char *cptr, int full)
fds[i+1].revents = 0;
}
}
+ unqueue_signals();
# endif
}
/* If looping, need to recalculate timeout */
--
2.55.0
Messages sorted by:
Reverse Date,
Date,
Thread,
Author