Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: Zsh - Multiple DoS Vulnerabilities

Mikael Magnusson wrote:
> Played with gdb reverse debugging a bit and found that at one point
> before the crash, we have this somewhat incorrect string built up:
> (gdb) p tptr-48
> $28 = 0x6e7560 <jbuf> "if [[ m -eq y ]]; then; : && ! :; select G\305\305 in "

The point at which the code starts to go wrong is these lines in text.c:

450		    s->code = *state->pc++;
451		    s->pop = (WC_LIST_TYPE(s->code) & Z_END);

code, which is what was popped from the stack is WC_LIST(type=SYNC, skip=0) -
Z_END is not set. A comment in parse.c states:
 *     - if not (type & Z_END), followed by next WC_LIST

s->code is WC_END not WC_LIST. It doesn't seem valid to check WC_LIST_TYPE for
that. We now iterate back round the loop picking up the garbage
WC_SELECT(type=pparam, skip=27625473) value for the next code.
That explains the select text that Mikael mentions.
But I still have little idea what has led to the code doing this.

After briefly trying to manually decode wordcode values, I hacked together a
gdb pretty printer for them (attached) which makes it rather easier.

Anyway the full wordcode looks like the following. I've manually tried to
substitute codes corresponding to strings and indented for some of the skips
but am not certain: especially the first string which might be a redirection

  List(type=SYNC|END, skip=0),
  SubList(type=END, skip=19),
  Pipe(type=end, line=4),
  If(type=head, skip=17),
    If(type=if, skip=16),
	List(type=SYNC|END|SIMPLE, skip=4),
	    Cond(-eq, skip=0),
	List(type=SYNC, skip=0),
	SubList(type=AND, skip=3),
	    Pipe(type=end, line=5),
	SubList(type=END, flags=NOT, skip=0),
	List(type=SYNC|END, skip=0),
	SubList(type=END, skip=3),
	    Pipe(type=end, line=6),

Without delving further into this, I'm somewhat unsure as to the meaning of the
END types on lists and how start/end matching works along with the stack used
in gettext2(). Maybe someone else knows it better?

To enable the gdb pretty printer, just dump the file in the current
directory and at the gdb prompt, type:
  python execfile("wordcode.py")
To confirm, this worked: info pretty-printer
To get line based output it can also be useful to do:
  set print array on
And to watch the parse stage:
  watch *ecbuf@22

Note that it can't discern the placeholders for strings and I've not
tested it exhaustively so there may be errors.

There may be nicer ways to handle enabling the pretty-printer; you
may have seen this if you've ever enabled to C++ STL pretty printers.
Would it make sense to include things like this somewhere in the git
repository? It is essentially a duplicate of C code so might bitrot
relative to it. Also, I'm not especially fluent in Python, so it could
perhaps be better. But it can be useful. I also have a gdb macro for the
zle undo stack.

Another aside, playing around with bit flags reminded me of a zsh annoyance:
printing a negative number in binary gives you a minus sign followed by the
positive representation of it rather than the two's complement form. Does it
make sense to allow some form of unsigned output besides printf
(which doesn't do binary)? And perhaps a Java style >>> operator.


# Gdb pretty printer for zsh wordcode values.

# python execfile("wordcode.py")
# set print array on
# print *ecbuf@22
# note that you'll get garbage values for codes that reference strings

from operator import itemgetter

class WordcodePrinter:
    WC_END     = 0   
    WC_LIST    = 1
    WC_SUBLIST = 2
    WC_PIPE    = 3
    WC_REDIR   = 4
    WC_ASSIGN  = 5
    WC_SIMPLE  = 6
    WC_TYPESET = 7
    WC_SUBSH   = 8
    WC_CURSH   = 9
    WC_TIMED   = 10
    WC_FUNCDEF = 11
    WC_FOR     = 12
    WC_SELECT  = 13
    WC_WHILE   = 14
    WC_REPEAT  = 15
    WC_CASE    = 16
    WC_IF      = 17
    WC_COND    = 18
    WC_ARITH   = 19
    WC_AUTOFN  = 20
    WC_TRY     = 21

    WC_CODEBITS  = 5
    Z_END        = (1<<4) 
    Z_SIMPLE     = (1<<5)
    WC_LIST_FREE = (6)

    NAME = [ "End", "List", "SubList", "Pipe", "Redir", "Assign", "Simple",
             "Typeset", "Subsh", "Cursh", "Timed", "Funcdef", "For",
             "Select", "While", "Repeat", "Case", "If", "Cond", "Arith",
             "Autofn", "Try" ]
    def __init__(self, val):
        self.val = val

    def wc_code(self, code):
        return int(code) & ((1 << self.WC_CODEBITS) - 1)

    def wc_data(self, code):
        return int(code) >> self.WC_CODEBITS

    def wc_list_type(self, data):
        return '|'.join(map(itemgetter(1), filter(lambda (i,s): data & (1<<i),
            enumerate(["TIMED", "SYNC", "ASYNC", "DISOWN", "END", "SIMPLE"]))))

    def wc_sublist_type(self, data):
        return [ "END", "AND", "OR" ][data & 3]

    def wc_redir_type(self, data):
        return [">", ">|", ">>", ">>|", "&>", ">&|", ">>&", ">>&|", "<>",
            "<", "<<", "<<-", "<<<", "<&n", ">&n", ">&-, <&-", "< <(...)",
            "> >(...)" ][data & 0x1f]

    def wc_sublist_flags(self, data):
        return '|'.join(map(itemgetter(1), filter(lambda (i,s): data & (1<<i),
            enumerate(["COPROC", "NOT", "SIMPLE" ], start=2))))

    def wc_if_type(self, data):
        return [ "head", "if", "elif", "else" ][data & 3]

    def wc_for_type(self, data):
        return [ "pparam", "list", "cond" ][data & 3]

    def wc_case_type(self, data):
        return [ "head", "or", "and", "testand" ][data & 7]

    def wc_cond_type(self, data):
        return [ "!", "&&", "||", "=", "==", "!=", "<",
                 ">", "-nt", "-ot", "-ef", "-eq", "-ne", "-lt", "-gt", "-le",
                 "-ge", "=~", "MOD", "MOD(infix)" ][data & 127]

    def to_string(self):
            code = self.wc_code(self.val)
            data = self.wc_data(self.val)
            name = self.NAME[code]
            if (code == self.WC_LIST):
                name += '(type={}, skip={})'.format(self.wc_list_type(data), data >> 6)
            elif (code == self.WC_SUBLIST and data & 0x1c):
                name += '(type={}, flags={}, skip={})'.format(
                    self.wc_sublist_type(data), self.wc_sublist_flags(data), data >> 5)
            elif (code == self.WC_SUBLIST):
                name += '(type={}, skip={})'.format(self.wc_sublist_type(data),
                    data >> 5)
            elif (code == self.WC_REDIR):
              name += '(type={}{})'.format(self.wc_redir_type(data),
                  ", varid=true" if (data & 0x20) else "")
            elif (code == self.WC_ASSIGN):
              if data & 2: name += "+="
              name += "(array[{}])".format(data >> 2) if data & 1 else "(scalar)"
            elif (code == self.WC_SUBSH or code == self.WC_CURSH
                    or code == self.WC_REPEAT or code == self.WC_TRY
                    or code == self.WC_FUNCDEF):
                name += '(skip={})'.format(data)
            elif (code == self.WC_TIMED):
                name += '(type={})'.format("pipe" if data else "empty")
            elif (code == self.WC_PIPE):
                name += '(type={}, line={})'.format(
		    "mid" if (data & 1) else "end", (data >> 1))
            elif (code == self.WC_FOR):
                name += '(type={}, skip={})'.format(self.wc_for_type(data), data >> 2)
            elif (code == self.WC_SELECT):
                name += '(type={}, skip={})'.format(
		    "pparam" if (data & 1) else "list", (data >> 1))
            elif (code == self.WC_WHILE):
                if data & 1: name += '/until'
                name += '(skip={})'.format(data >> 1)
            elif (code == self.WC_CASE):
                name += '(type={}, skip={})'.format(self.wc_case_type(data), data >> 3)
            elif (code == self.WC_SIMPLE or code == self.WC_TYPESET):
                name += '(argc={})'.format(data)
            elif (code == self.WC_IF):
                name += '(type={}, skip={})'.format(self.wc_if_type(data), data >> 2)
            elif (code == self.WC_COND):
                name += '({}, skip={})'.format(self.wc_cond_type(data), data >> 7)
        except IndexError:
            name = int(code) # any error likely means it isn't a wordcode

        return name

def zsh(val):
    if str(val.type) == 'wordcode':
        return WordcodePrinter(val)
    return None


Messages sorted by: Reverse Date, Date, Thread, Author