ash: reset tokpushback before prompting while parsing heredoc
authorChristoph Schulz <develop@kristov.de>
Tue, 20 Nov 2018 16:45:52 +0000 (17:45 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Tue, 20 Nov 2018 16:45:52 +0000 (17:45 +0100)
commit03ad7ae08189ed88dd7e0fcb6c6001fbf3b12efb
tree8cadf600f6d43bfc4f98ca5a006fb982667a4e60
parent32511da87ddaea0824801eafebd27e11409bf444
ash: reset tokpushback before prompting while parsing heredoc

The parser reads from an already freed memory location, thereby causing
unpredictable results, in the following situation:

- ENABLE_ASH_EXPAND_PRMT is enabled
- heredoc is being parsed
- command substitution is used within heredoc

Examples where this bug crops up are (PS2 is set to "> "):

$ cat <<EOF
> `echo abc`
> EOF
-sh: O: not found

$ cat <<EOF
> $(echo abc)
> EOF
-sh: {garbage}: not found

The presumable reason is that setprompt_if() causes a nested expansion when
ENABLE_ASH_EXPAND_PRMT is enabled, therefore leaving "wordtext" in an unusable
state. However, when parseheredoc() is called, "tokpushback" is non-zero, which
causes the next call to xxreadtoken() to return TWORD, causing the caller to
use the invalid "wordtoken" instead of reading the next valid token.

The call chain is:

list()
-> peektoken() [sets tokpushback to 1]
-> parseheredoc()
   -> setprompt_if()
      -> pushstackmark()
      -> expandstr()
         -> readtoken1()
            [sets lasttoken to TWORD, wordtoken points to expanded prompt]
      -> popstackmark() [invalidates wordtoken, leaves lasttoken as is]
   -> readtoken1()
      -> ...parsebackq
         -> list()
            -> andor()
               -> pipeline()
                  -> readtoken()
                     -> xxreadtoken()
                        [tokpushback non-zero, reuse lasttoken and wordtext]

Note that in almost all other contexts, each call to setprompt_if() is preceded
by setting "tokpushback" to zero. One exception is "oldstyle" backquote parsing
in readtoken1(), but there "tokpushback" is reset afterwards. The other
exception is nlprompt(), but this function is only used within readtoken1()
(but in contexts where no nested calls to xxreadtoken() occur) and xxreadtoken()
(where "tokpushback" is guaranteed to be zero).

function                                             old     new   delta
parseheredoc                                         124     131      +7

Signed-off-by: Christoph Schulz <develop@kristov.de>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/ash.c