Subject: Re: access already freed memory when resize window
From: Bart Schaefer <[email-masked]>
To: [email-masked]
Date: Sun, 20 Mar 2016 10:19:10 -0700
List-Id: Zsh Workers List <zsh-workers.zsh.org>

This appears to be happening because the PROMPTSUBST option is set and
one of the prompt variables contains a $(...) command substitution.

When the $(...) is executed during prompt expansion, zsh waits for the
command to exit, which allows a second SIGWINCH to be handled, which
in turn redisplays the prompt again before it has finished expanding.
I can trivially reproduce from zsh -f with:

torch% setopt promptsubst 
torch% PS1='$(<<<$SECONDS) '"$PS1"

and then resize the window.  The reexpandprompt() routine has a guard
against re-entering this way, but that doesn't help because the global
pointer has been freed and even if reexpandprompt() is skipped other
parts of the ZLE redraw may access it.

It doesn't even help to queue signals, because waiting for the command
substitution to finish must temporarily unqueue them.

This is especially nasty because lpromptbuf has to be correctly set in
order for rpromptbuf to be correctly computed, so if the user is wildly
dragging the window border around there is no instant at which we are
guaranteed to be able to leave the two of them in proper sync with both
the window size and each other -- unless we do something unpleasant like
attempting to sleep with SIGWINCH blocked until the user has finished
goofing around.

The following appears to be the next best thing, but I think it could
still leave us one SIGWINCH short of a full paint job.

diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 6e2bfde..104e5d6 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -1856,6 +1856,7 @@ void
 reexpandprompt(void)
 {
     static int reexpanding;
+    static int looping;
 
     if (!reexpanding++) {
 	/*
@@ -1866,15 +1867,33 @@ reexpandprompt(void)
 	int local_lastval = lastval;
 	lastval = pre_zle_status;
 
-	free(lpromptbuf);
-	lpromptbuf = promptexpand(raw_lp ? *raw_lp : NULL, 1, NULL, NULL,
-				  &pmpt_attr);
-	rpmpt_attr = pmpt_attr;
-	free(rpromptbuf);
-	rpromptbuf = promptexpand(raw_rp ? *raw_rp : NULL, 1, NULL, NULL,
-				  &rpmpt_attr);
+	do {
+	    /* A new SIGWINCH may arrive while in promptexpand(), causing
+	     * looping to increment.  This only happens when a command
+	     * substitution is used in a PROMPT_SUBST prompt, but
+	     * nevertheless keep trying until we see no more changes.
+	     */
+	    char *new_lprompt, *new_rprompt;
+	    looping = reexpanding;
+
+	    new_lprompt = promptexpand(raw_lp ? *raw_lp : NULL, 1, NULL, NULL,
+				       &pmpt_attr);
+	    free(lpromptbuf);
+	    lpromptbuf = new_lprompt;
+
+	    if (looping != reexpanding)
+		continue;
+
+	    rpmpt_attr = pmpt_attr;
+	    new_rprompt = promptexpand(raw_rp ? *raw_rp : NULL, 1, NULL, NULL,
+				       &rpmpt_attr);
+	    free(rpromptbuf);
+	    rpromptbuf = new_rprompt;
+	} while (looping != reexpanding);
+
 	lastval = local_lastval;
-    }
+    } else
+	looping = reexpanding;
     reexpanding--;
 }