zsh-workers
 help / color / mirror / code / Atom feed
From: Oliver Kiddle <okiddle@yahoo.co.uk>
To: zsh-workers@zsh.org
Subject: Re: Zsh - Multiple DoS Vulnerabilities
Date: Sat, 18 May 2019 12:31:24 +0200	[thread overview]
Message-ID: <38446-1558175484.765409@Nwft.Kz_v.zVUJ> (raw)
In-Reply-To: <CAHYJk3SZquBSFVgjH3K3hnoiaGGPZDtoT8ejzHJxJHb8XBUXFA@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 3270 bytes --]

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
code.

  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),
	    string
	    Cond(-eq, skip=0),
	    string
	    string
	List(type=SYNC, skip=0),
	SubList(type=AND, skip=3),
	    Pipe(type=end, line=5),
	    Simple(argc=1),
	    string
	SubList(type=END, flags=NOT, skip=0),
	List(type=SYNC|END, skip=0),
	SubList(type=END, skip=3),
	    Pipe(type=end, line=6),
	    Simple(argc=1),
	    string
  End

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.

Oliver


[-- Attachment #2: wordcode.py --]
[-- Type: text/plain, Size: 5091 bytes --]

# 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):
        try:
            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

gdb.pretty_printers.append(zsh)

  parent reply	other threads:[~2019-05-18 10:32 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-05-10 15:03 David Wells
2019-05-10 16:37 ` Bart Schaefer
2019-05-12 16:21   ` Stephane Chazelas
2019-05-13 16:29     ` David Wells
2019-05-13 22:02       ` Bart Schaefer
2019-05-14 18:10       ` Stephane Chazelas
2019-05-14 21:24         ` Daniel Shahaf
2019-05-14 21:38           ` Bart Schaefer
2019-05-14 21:39         ` Daniel Shahaf
2019-05-14 22:25           ` Bart Schaefer
2019-05-15 10:48             ` Daniel Shahaf
2019-05-31 12:05     ` [PATCH] [doc] [repost] warnings about restricted shell (Was: Zsh - Multiple DoS Vulnerabilities) Stephane Chazelas
2019-06-03  9:35       ` Peter Stephenson
2019-06-04  2:39       ` dana
2019-06-04  7:34         ` dana
2019-05-10 20:27 ` Zsh - Multiple DoS Vulnerabilities Bart Schaefer
2019-05-11  1:45   ` #7 (typeset -Tp) (was Re: Zsh - Multiple DoS Vulnerabilities) Oliver Kiddle
2019-05-13  9:01     ` Peter Stephenson
2019-05-13 21:11   ` PATCH: #6 negative job id (Re: " Oliver Kiddle
2019-05-13 21:44   ` Zsh - Multiple DoS Vulnerabilities Oliver Kiddle
2019-05-13 22:36   ` #3 typeset and braces (Re: Zsh - Multiple DoS Vulnerabilities) Oliver Kiddle
2019-05-14  0:13     ` Mikael Magnusson
2019-05-14  5:38       ` Bart Schaefer
2019-05-14 10:50     ` Peter Stephenson
2019-05-14 16:38   ` Zsh - Multiple DoS Vulnerabilities Peter Stephenson
2019-05-14 20:30   ` Oliver Kiddle
2019-05-15 16:50     ` Mikael Magnusson
2019-05-16 20:37     ` Peter Stephenson
2019-05-17 13:41       ` Mikael Magnusson
2019-05-17 13:51         ` Mikael Magnusson
2019-05-17 14:28           ` Mikael Magnusson
2019-05-18 10:31           ` Oliver Kiddle [this message]
2019-05-21 14:43             ` Oliver Kiddle
     [not found]               ` <CGME20190521154256eucas1p1f0816d2467abd8bf4a0c31058af2983a@eucas1p1.samsung.com>
2019-05-21 15:42                 ` Peter Stephenson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=38446-1558175484.765409@Nwft.Kz_v.zVUJ \
    --to=okiddle@yahoo.co.uk \
    --cc=zsh-workers@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).