zsh-workers
 help / color / mirror / code / Atom feed
* access already freed memory when resize window
@ 2016-03-20 13:57 comic fans
  2016-03-20 17:19 ` Bart Schaefer
  0 siblings, 1 reply; 4+ messages in thread
From: comic fans @ 2016-03-20 13:57 UTC (permalink / raw)
  To: zsh-workers

when resizing,

zle_main.c:  in function reexpandprompt
will call
free(lpromptbuf);    ------> already free
then call
lpromptbuf = promptexpand ....

but call stack will then reach zle_refresh.c
resetvideo line 754
call
countprompt (lpromptbuf, ....   ----------> access invalid memory


I've confirm this bug in version 5.2 and latest git version


address sanitizer report

==26994==ERROR: AddressSanitizer: heap-use-after-free on address
0x61100079d8c0 at pc 0x513f5f bp 0x7ffcfb207820 sp 0x7ffcfb207810
READ of size 1 at 0x61100079d8c0 thread T0
    #0 0x513f5e in countprompt
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/prompt.c:1082
    #1 0x7ff8e48e9ee4 in resetvideo
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_refresh.c:754
    #2 0x7ff8e48f24b5 in zrefresh
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_refresh.c:1151
    #3 0x7ff8e48d4e74 in zle_main_entry
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:1994
    #4 0x49856f in zleentry
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/init.c:1531
    #5 0x51ee49 in zhandler
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/signals.c:654
    #6 0x7ff8e7e6f44f (/lib64/libc.so.6+0x3344f)
    #7 0x7ff8e7e6f785 in __sigsuspend (/lib64/libc.so.6+0x33785)
    #8 0x51d7b5 in signal_suspend
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/signals.c:384
    #9 0x4a47b8 in waitforpid
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/jobs.c:1401
    #10 0x45f6f7 in getoutput
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/exec.c:4149
    #11 0x52da9d in stringsubst
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/subst.c:324
    #12 0x536ef8 in prefork
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/subst.c:85
    #13 0x537e77 in singsub
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/subst.c:428
    #14 0x51ad01 in promptexpand
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/prompt.c:187
    #15 0x7ff8e48d0664 in reexpandprompt
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:1870
    #16 0x7ff8e48d07f8 in zle_resetprompt
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:1895
    #17 0x7ff8e48d4d24 in zle_main_entry
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:1990
    #18 0x49856f in zleentry
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/init.c:1531
    #19 0x548a55 in adjustwinsize
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/utils.c:1933
    #20 0x51ee49 in zhandler
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/signals.c:654
    #21 0x7ff8e7e6f44f (/lib64/libc.so.6+0x3344f)
    #22 0x7ff8e7f1829f in read (/lib64/libc.so.6+0xdc29f)
    #23 0x7ff8e89765f6 in __interceptor_read
(/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/libasan.so.1+0x2a5f6)
    #24 0x7ff8e48cccf9 in read /usr/include/bits/unistd.h:44
    #25 0x7ff8e48cccf9 in raw_getbyte
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:819
    #26 0x7ff8e48cccf9 in getbyte
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:854
    #27 0x7ff8e48cb086 in getkeybuf
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_keymap.c:1660
    #28 0x7ff8e48cb086 in getkeymapcmd
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_keymap.c:1578
    #29 0x7ff8e48cb66e in getkeycmd
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_keymap.c:1689
    #30 0x7ff8e48cfc33 in zlecore
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:1083
    #31 0x7ff8e48d17cc in zleread
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:1292
    #32 0x49856f in zleentry
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/init.c:1531
    #33 0x49bddd in inputline
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/input.c:293
    #34 0x49bddd in ingetc
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/input.c:226
    #35 0x488a88 in ihgetc
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/hist.c:391
    #36 0x4b1790 in gettok
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/lex.c:611
    #37 0x4b1790 in zshlex
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/lex.c:275
    #38 0x4feba6 in parse_event
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/parse.c:570
    #39 0x490a85 in loop
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/init.c:146
    #40 0x4995f8 in zsh_main
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/init.c:1687
    #41 0x7ff8e7e5c59f in __libc_start_main (/lib64/libc.so.6+0x2059f)
    #42 0x412b18 in _start (/bin/zsh+0x412b18)

0x61100079d8c0 is located 0 bytes inside of 256-byte region
[0x61100079d8c0,0x61100079d9c0)
freed by thread T0 here:
    #0 0x7ff8e89a355f in __interceptor_free
(/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/libasan.so.1+0x5755f)
    #1 0x7ff8e48d0625 in reexpandprompt
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/Zle/zle_main.c:1869

previously allocated by thread T0 here:
    #0 0x7ff8e89a37d7 in malloc
(/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/libasan.so.1+0x577d7)
    #1 0x4c3c9e in zshcalloc
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/mem.c:974
    #2 0xfff9f64152d (+0xdff0f64a52d)

SUMMARY: AddressSanitizer: heap-use-after-free
/usr/src/debug/app-shells/zsh-9999/zsh-9999/Src/prompt.c:1082
countprompt
Shadow bytes around the buggy address:
  0x0c22800ebac0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c22800ebad0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c22800ebae0: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
  0x0c22800ebaf0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c22800ebb00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x0c22800ebb10: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd
  0x0c22800ebb20: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c22800ebb30: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
  0x0c22800ebb40: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c22800ebb50: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c22800ebb60: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Contiguous container OOB:fc
  ASan internal:           fe
==26994==ABORTING


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: access already freed memory when resize window
  2016-03-20 13:57 access already freed memory when resize window comic fans
@ 2016-03-20 17:19 ` Bart Schaefer
  2016-03-21 10:43   ` Peter Stephenson
  0 siblings, 1 reply; 4+ messages in thread
From: Bart Schaefer @ 2016-03-20 17:19 UTC (permalink / raw)
  To: zsh-workers

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--;
 }
 


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: access already freed memory when resize window
  2016-03-20 17:19 ` Bart Schaefer
@ 2016-03-21 10:43   ` Peter Stephenson
  2016-03-21 15:10     ` Bart Schaefer
  0 siblings, 1 reply; 4+ messages in thread
From: Peter Stephenson @ 2016-03-21 10:43 UTC (permalink / raw)
  To: zsh-workers

On Sun, 20 Mar 2016 10:19:10 -0700
Bart Schaefer <schaefer@brasslantern.com> wrote:
> It doesn't even help to queue signals, because waiting for the command
> substitution to finish must temporarily unqueue them.

Right, but shouldn't there at least be some attempt to tame the points
at which recursion can happen?  Or is that already happening, and all
the effects we're seeing are out at the top level after the signal
handler has returned?

pws


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: access already freed memory when resize window
  2016-03-21 10:43   ` Peter Stephenson
@ 2016-03-21 15:10     ` Bart Schaefer
  0 siblings, 0 replies; 4+ messages in thread
From: Bart Schaefer @ 2016-03-21 15:10 UTC (permalink / raw)
  To: zsh-workers

On Mar 21, 10:43am, Peter Stephenson wrote:
} Subject: Re: access already freed memory when resize window
}
} On Sun, 20 Mar 2016 10:19:10 -0700
} Bart Schaefer <schaefer@brasslantern.com> wrote:
} > It doesn't even help to queue signals, because waiting for the command
} > substitution to finish must temporarily unqueue them.
} 
} Right, but shouldn't there at least be some attempt to tame the points
} at which recursion can happen?  Or is that already happening, and all
} the effects we're seeing are out at the top level after the signal
} handler has returned?

Generically, the problem is that reexpandprompt() was doing

	free(GlobalPointer);
	GlobalPointer = maybe_reference_GlobalPointer();

The fact that the reference is from a signal handler is an instantiation
of "maybe".  So at the very least we have to change the order of the
function call and the free.

An added complication is that we have two interdependent global pointers
that were both being treated that way.

To "tame the recursion" we could do something like this:

 #ifdef SIGWINCH
     case SIGWINCH:
+	winch_block();
         adjustwinsize(1);  /* check window size and adjust */
 	(void) handletrap(SIGWINCH);
+	winch_unblock();
         break;
 #endif
 
But then we might stop adjusting our idea of the window size before the
user stops resizing the window.  (Also due to the way we manage signal
queuing we don't know the current state of winch_block() at that point,
so it might not be correct to block/unblock.  The whole winch-handling
scheme would have to be revised to be more like queue_signals().)


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2016-03-21 15:10 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-03-20 13:57 access already freed memory when resize window comic fans
2016-03-20 17:19 ` Bart Schaefer
2016-03-21 10:43   ` Peter Stephenson
2016-03-21 15:10     ` Bart Schaefer

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).