From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-1.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FROM,MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE autolearn=ham autolearn_force=no version=3.4.2 Received: from primenet.com.au (ns1.primenet.com.au [203.24.36.2]) by inbox.vuxu.org (OpenSMTPD) with ESMTP id 552dc6a7 for ; Sat, 18 May 2019 10:32:26 +0000 (UTC) Received: (qmail 5925 invoked by alias); 18 May 2019 10:32:11 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: List-Unsubscribe: X-Seq: 44319 Received: (qmail 15311 invoked by uid 1010); 18 May 2019 10:32:11 -0000 X-Qmail-Scanner-Diagnostics: from park01.gkg.net by f.primenet.com.au (envelope-from , uid 7791) with qmail-scanner-2.11 (clamdscan: 0.101.2/25447. spamassassin: 3.4.2. Clear:RC:0(205.235.26.22):SA:0(-1.9/5.0):. Processed in 4.2707 secs); 18 May 2019 10:32:11 -0000 X-Envelope-From: SRS0=Hojs=TS=yahoo.co.uk=okiddle@bounces.park01.gkg.net X-Qmail-Scanner-Mime-Attachments: | X-Qmail-Scanner-Zip-Files: | Received-SPF: pass (ns1.primenet.com.au: SPF record at bounces.park01.gkg.net designates 205.235.26.22 as permitted sender) X-Virus-Scanned: by amavisd-new at gkg.net Authentication-Results: amavisd4.gkg.net (amavisd-new); dkim=pass (2048-bit key) header.d=yahoo.co.uk X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.co.uk; s=s2048; t=1558175486; bh=4A6xYif1g3RZ1K/EPGWDpv/kApAjfsYlNnzCEFhce2o=; h=From:References:To:Subject:Date:From:Subject; b=PdfL9sNDD+hHcMRshPERMHvPhu8ywzAgHCKqx8jQ01DEDGhPGJQqNRbb+PLPE0Zh3Qp1HRh721tq+pbB89xrK4pePFpQE1B8V2aEnTpJSAU1mM5PEBJXX365bY6f59iilUXkRRq2rXAjNV26U/gTpqjqyF6hFaAu9SiauR+qEi2WbB6jF7L6LHQj6WOPFdKR8dQzUDtz0gTPdnAVBabZNqUL9rlM5QCmcldvBg7FmbSA2NETETUSCUB5AOUzHK9ubWl3CNtyCQ9B+8MNTX/nZRwqKb0rtmueAlcqcVk513rD7bP1l8Eq5BtvU/4h47sfNS1vd4vsGtDqgoVQ/MBxWw== X-YMail-OSG: 31H_HvQVM1kDDuwYWcycYvBsCvJ2wIaHCSBXMgh33.1XrTlOPlSbZ5s5bS8QvF6 ahYoqGURUJDiesoztBsF6OTGtIgRQ4Dlx9QqeCzP6wlZb0_xstwv2jmDFaZx_QIAqMprWyL6TRNw x8DO0prkW3nRaVQxOZ_blmkLwE2eqS.eqHGFsBFgvTe9vSEOtlnaboCE_z9N28SBKSXWCMD1p9yw lXEV3Z3I1waWCrdGE3iToF1zh9ANAXPhLFBJUPfrQ1RWVNotlcOlbvvsRCsedOxaxmD1VbQnmoAA ZqINDojRHTXdhCyatSTjTMNkfaBc6s3a_w9goZl53NFbm2wHVx_ZYdjJn_mKB_z24g1pqZOs8Sqp wQHeaZYUZajntzS9tV.rya1Pr7zmPzcVjAUJawn9DCxprkZGpQHl3i5uKbHD3VYbrr54125jqCBc 945Xp_wG9tsvdemIedwQsFHWy.xbP_ykfqVaomQZqIaZHxNdDuHXSQ85x9jENSsyDkpUWeb1JKar hTXpp5jvNAuH84VgX4Dt9pZNTxgmKpsx26Cokrj06ZBuHW23FRuOKl1Gxvv13aRq3Wc6mILw2_CT 040IjXZSsjD_cJgaAsC6JshBLldjPJu6e241uKoiloeQJnSRj4oEWiYP9Ej59zQeb4MMIxJiNXQs Bp3myynhnC5YouikFIWJsD79aDBNuE5oEsT5jCqjWYkYrRa6FtHHMCxi3rM6CPcE0nAQLMpiFJXZ hUuQzMtI.QoAuuiAQ2QxOyp0Bo6a.3ODft2ZQAsIpZBrCm6a5S7E.6WHOqEg5mxAiHSDQzaABcUb zdZTRtipucOWcb5xne6eMM8WbL2bqvCPOtwiJbudM.gugocHIbhtdVht.nL69RDyKZxBRrbj8sB1 cpO2nDvvF2l.SlRzkHRlt5Kx4WNd8eu5XtYXkQFEQGxZJ9o3HgQBQ8WZeh1HzbvsZ2YF1kmFId7O q71d8feyeR_Rz.4nASZdReJKDUy8W8RFga8qYE3N3iXfV7EReEOxSUtHxkty4He2jdVRGL8UaWuC CM0pE97HxNzvBfFfxLuFeSoJnY2p6i.ApZp2hyhZC7_8mO_vyx4CKA.7I7DF28llzNQwSmIk8Oxo CMxiKUdfy5kXNsyy7JTpL.DWVaFZrV.y4ejCK61CjPbpMgMzgKDm.ngfSRFH0rHoKDrmPqN0d9HH MpstDxNpVwfs47I3sQ22tyw-- In-reply-to: From: Oliver Kiddle References: <21436-1557865831.121649@2P7I.HAU9.QsaG> <889eb5518ad0f98899ba24c2f3e95a87f7cc3df6.camel@ntlworld.com> To: zsh-workers@zsh.org Subject: Re: Zsh - Multiple DoS Vulnerabilities MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <38327.1558175135.0@hydra> Date: Sat, 18 May 2019 12:31:24 +0200 Message-ID: <38446-1558175484.765409@Nwft.Kz_v.zVUJ> ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <38327.1558175135.1@hydra> 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 "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 ------- =_aaaaaaaaaa0 Content-Type: text/plain; name="wordcode.py"; charset="us-ascii" Content-ID: <38327.1558175135.2@hydra> # 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<", ">|", ">>", ">>|", "&>", ">&|", ">>&", ">>&|", "<>", "<", "<<", "<<-", "<<<", "<&n", ">&n", ">&-, <&-", "< <(...)", "> >(...)" ][data & 0x1f] def wc_sublist_flags(self, data): return '|'.join(map(itemgetter(1), filter(lambda (i,s): data & (1<", "-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) ------- =_aaaaaaaaaa0--