From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-1.0 required=5.0 tests=FREEMAIL_FROM, MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 10467 invoked from network); 19 May 2020 06:49:07 -0000 Received: from ns1.primenet.com.au (HELO primenet.com.au) (203.24.36.2) by inbox.vuxu.org with ESMTPUTF8; 19 May 2020 06:49:07 -0000 Received: (qmail 24124 invoked by alias); 19 May 2020 06:48:55 -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: 45843 Received: (qmail 27268 invoked by uid 1010); 19 May 2020 06:48:55 -0000 X-Qmail-Scanner-Diagnostics: from mail4.protonmail.ch by f.primenet.com.au (envelope-from , uid 7791) with qmail-scanner-2.11 (clamdscan: 0.102.3/25814. spamassassin: 3.4.4. Clear:RC:0(185.70.40.27):SA:0(-2.7/5.0):. Processed in 0.762314 secs); 19 May 2020 06:48:55 -0000 X-Envelope-From: arinerron@protonmail.com X-Qmail-Scanner-Mime-Attachments: | X-Qmail-Scanner-Zip-Files: | Received-SPF: pass (ns1.primenet.com.au: SPF record at _spf.protonmail.ch designates 185.70.40.27 as permitted sender) Date: Tue, 19 May 2020 06:48:14 +0000 To: "zsh-workers@zsh.org" From: Aaron Esau Cc: Peter Stephenson Reply-To: Aaron Esau Subject: [BUG] Two vulnerabilities in zsh Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Another win for AFL! There are two vulnerabilities in zsh <5.8. I'll request a couple CVE IDs if= that's okay. I contacted Peter about these a few days ago.= Neither have common, practical attack scenarios ("oh no! someone with a sh= ell could pop a shell!"), so I'm disclosing them here. I did a little analysis on each vulnerability. The more severe one appears = to be some form of memory corruption, but it's decently complex, and I coul= dn't find the root cause. I think it'll take someone more experienced than = me to find it. But, I was able to track down the null dereference bug. :) Frankly, I'm not a C developer, but I think I at least know how to fix the = null dereference vuln, so I added a section with a patch there. Thanks for the awesome shell! Sincerely, Aaron Esau https://aaronesau.com/ ------ #1 :: null dereference in check_colon_subscript in subst.c ------ A denial of service vulnerability exists in zsh <5.8 due to a null derefere= nce in check_colon_subscript in subst.c. ## Reproduction Steps I was able to reproduce this bug on every zsh version I tested. I am runnin= g zsh 5.8 (x86_64-pc-linux-gnu) on Arch Linux. 1. Start zsh and execute the following (including quotes): "${: :${{{\"{{i use arch btw}}" 2. Observe that the command causes a segmentation fault. The stack trace is= : $rip 0x5555555eb22e =3D> movzx eax, BYTE PTR [rax] [#1] 0x5555555ed61e =3D> prefork() [#2] 0x555555583815 =3D> mov rsi, r12 [#3] 0x555555588e01 =3D> mov rbx, QWORD PTR [rbx] [#4] 0x55555558c32f =3D> pop rcx [#5] 0x55555558c6d1 =3D> mov eax, DWORD PTR [rip+0x9e8c1] # [rip+0x9e8c1]= =3D> 0x55555562af98 [#6] 0x55555558e051 =3D> execlist() [#7] 0x55555558e564 =3D> execode() [#8] 0x5555555a43d4 =3D> loop() [#9] 0x5555555a7d36 =3D> zsh_main() ## Analysis The following code in check_colon_subscript in subst.c checks if the value = at a pointer obtained from a call to parse_subscript is NULL: *endp =3D parse_subscript(str, 0, ':'); if (!*endp) { /* No trailing colon? */ *endp =3D parse_subscript(str, 0, '\0'); if (!*endp) return NULL; } However, the pointer itself can be NULL. In parse_subscript in lex.c, if er= r !=3D 0, the returning variable, s, is set to NULL: err =3D dquote_parse(endchar, sub); ... if (err) { ... s =3D NULL; } else { s +=3D toklen; } .. return s; The function dquote_parse in lex.c returns NULL on many error-related condi= tions. ## Patch diff --git Src/subst.c Src/subst.c index 90b5fc121..ac12c6d0e 100644 --- Src/subst.c +++ Src/subst.c @@ -1571,10 +1571,10 @@ check_colon_subscript(char *str, char **endp) } *endp =3D parse_subscript(str, 0, ':'); - if (!*endp) { + if (endp && !*endp) { =09/* No trailing colon? */ =09*endp =3D parse_subscript(str, 0, '\0'); -=09if (!*endp) +=09if (endp && !*endp) =09 return NULL; } sav =3D **endp; ------ #2 :: memory corruption in ?? ------ A memory corruption vulnerability exists in zsh <5.8 which may enable arbit= rary code execution or memory disclosure via unspecified methods. ## Reproduction Steps I am running zsh 5.8 (x86_64-pc-linux-gnu) on Arch Linux, kernel version 5.= 6.11-arch1-1. 1. Execute the following PoC command: echo $'******** **********************$\\\n(>$' | zsh 2. Observe that the command causes a segmentation fault. The stack trace is= : $rip 0x5555555eb22e =3D> movzx eax, BYTE PTR [rax] [#1] 0x5555555ed61e =3D> prefork() [#2] 0x555555583815 =3D> mov rsi, r12 [#3] 0x555555588e01 =3D> mov rbx, QWORD PTR [rbx] [#4] 0x55555558c32f =3D> pop rcx [#5] 0x55555558c6d1 =3D> mov eax, DWORD PTR [rip+0x9e8c1] # [rip+0x9e8c1]= =3D> 0x55555562af98 [#6] 0x55555558e051 =3D> execlist() [#7] 0x55555558e564 =3D> execode() [#8] 0x5555555a43d4 =3D> loop() [#9] 0x5555555a7d36 =3D> zsh_main() ## Analysis The segmentation fault is caused by a mov instruction which dereferences a = $rax, a pointer to invalid memory: * 0x555555588bac: setne bl * 0x555555588baf: jmp 0x555555588bd9 * 0x555555588bb1: nop DWORD PTR [rax+0x0] =3D>0x555555588bb8: mov rdi, QWORD PTR [rax+0x10] * 0x555555588bbc: addr32 call 0x5555555f27f0 * 0x555555588bc2: test eax, eax * 0x555555588bc4: je 0x555555589bc0 * 0x555555588bca: mov rsi, QWORD PTR [rbp+0x0] * 0x555555588bce: xor edx, edx At the segfault, the \$r[a-z]{2} registers are: $rax : 0x00007fff007ffff7 $rbx : 0x00007ffff7fbe601 =3D> 0xa8772e2a3a000000 $rcx : 0x00007ffff7fbe608 =3D> 0x00007ffff7fbe5a8 =3D> 0x0000000000000000 $rdx : 0x00007ffff7fbe5a8 =3D> 0x0000000000000000 $rsp : 0x00007fffffffbef0 =3D> 0x0000000100000099 $rbp : 0x00007ffff7fbe5f0 =3D> 0x00007fff007ffff7 $rsi : 0x00007ffff7fbe578 =3D> 0x0000000000000000 $rdi : 0x00007ffff7fbe5f0 =3D> 0x00007fff007ffff7 $rip : 0x0000555555588bb8 =3D> mov rdi, QWORD PTR [rax+0x10] So, again, the register $rax points to invalid memory. I stepped backward a= nd found that the value in the register comes from the stack: =3D>0x555555588bef: mov rax, QWORD PTR [rbp+0x0] * 0x555555588bf3: test rax, rax * 0x555555588bf6: jne 0x555555588bb8 I set a breakpoint at 0x555555588bef, then set watchpoints on both $rbp and= =E2=80=94as least significant 4 bytes of the address appear to be overwritt= en=E2=80=94the address $rbp-0x4. It was really hard to trace back and find what overwrites that memory. It a= ppears as though the lower bytes of the address are overwritten before the = instruction that first triggered the second watchpoint in __memmove_avx_una= ligned_erms: =3D>0x7ffff7d4d26f: mov QWORD PTR [rdi+rdx*1-0x8], rcx However, neither execlist in exec.c nor any of the functions it calls appea= r to execute glibc's memmove function. Perhaps another glibc function inter= nally calls __memmove_avx_unaligned_erms? To be continued.