Created attachment 27213 [details] Stack buffer overflow XPS file. Hi! Most of my previous bugs were buffer over or underreads but this one is a write overflow which enables RCE. I compiled commit 9f281ad42c8f6ffad42c46827c5ac508d4ed5687 with these commands: ``` export CFLAGS="-fsanitize=address -g3 -O0" export CXXFLAGS="-fsanitize=address -g3 -O0" export CC=clang export CXX=clang++ export LDFLAGS="-lz -lpthread -lcrypt -lz" # Linker flag stuff... ./autogen.sh \ --enable-freetype --enable-fontconfig \ --enable-cups --with-ijs --with-jbig2dec CC=$CC CXX=$CXX CFLAGS=$CFLAGS CXXFLAGS=$CXXFLAGS LDFLAGS=$LDFLAGS make -j100 sanitize ``` and then running `./sanbin/gxps -dSAFER -sOutputFile=out.pdf -sDEVICE=pdfwrite pwn.xps` should yield something like this: ``` ================================================================= ==249585==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7bfff4c4f2c0 at pc 0x5555570e7bd1 bp 0x7fffffffb1b0 sp 0x7fffffffb1a8 WRITE of size 1 at 0x7bfff4c4f2c0 thread T0 [Detaching after fork from child process 249588] #0 0x5555570e7bd0 in xps_unpredict_tiff /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpstiff.c:533:17 #1 0x5555570e27e5 in xps_decode_tiff_strips /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpstiff.c:808:13 #2 0x5555570dee19 in xps_decode_tiff /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpstiff.c:1178:13 #3 0x5555571120fb in xps_decode_image /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpsimage.c:154:17 #4 0x555557110ada in xps_parse_image_brush /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpsimage.c:449:12 #5 0x5555570fb7f3 in xps_parse_brush /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpscommon.c:48:20 #6 0x55555710873d in xps_parse_path /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpspath.c:1359:16 #7 0x5555570fb969 in xps_parse_element /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpscommon.c:66:16 #8 0x5555570f89ec in xps_parse_canvas /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpspage.c:99:16 #9 0x5555570fb9dd in xps_parse_element /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpscommon.c:70:16 #10 0x5555570f9ca0 in xps_parse_fixed_page /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpspage.c:290:16 #11 0x5555570efe04 in xps_read_and_process_page_part /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpszip.c:576:12 #12 0x5555570ee80a in xps_process_file /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpszip.c:849:16 #13 0x55555595bd98 in xps_impl_process_file /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpstop.c:325:12 #14 0x5555570b3031 in pl_process_file /home/oof/newghost/newest/pocversion/ghostpdl/./pcl/pl/pltop.c:117:16 #15 0x5555572b64eb in pl_main_run_file_utf8 /home/oof/newghost/newest/pocversion/ghostpdl/./pcl/pl/plmain.c:1036:16 #16 0x5555572b2a1d in pl_main_process_options /home/oof/newghost/newest/pocversion/ghostpdl/./pcl/pl/plmain.c:2877:24 #17 0x5555572af0a9 in pl_main_init_with_args /home/oof/newghost/newest/pocversion/ghostpdl/./pcl/pl/plmain.c:327:12 #18 0x5555570b3d44 in gsapi_init_with_args /home/oof/newghost/newest/pocversion/ghostpdl/./pcl/pl/plapi.c:114:12 #19 0x5555572ae8d9 in main /home/oof/newghost/newest/pocversion/ghostpdl/./pcl/pl/realmain.c:58:12 #20 0x7ffff782a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 #21 0x7ffff782a28a in __libc_start_main csu/../csu/libc-start.c:360:3 #22 0x55555586ed84 in _start (/home/oof/newghost/newest/pocversion/ghostpdl/sanbin/gxps+0x31ad84) (BuildId: d9aab294da45c04a63de82d5af21345bdd1446f1) Address 0x7bfff4c4f2c0 is located in stack of thread T0 at offset 64 in frame #0 0x5555570e7aaf in xps_unpredict_tiff /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpstiff.c:528 This frame has 1 object(s): [32, 64) 'left' (line 529) <== Memory access at offset 64 overflows this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: stack-buffer-overflow /home/oof/newghost/newest/pocversion/ghostpdl/./xps/xpstiff.c:533:17 in xps_unpredict_tiff Shadow bytes around the buggy address: 0x7bfff4c4f000: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f080: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f100: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f180: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f200: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 =>0x7bfff4c4f280: f1 f1 f1 f1 00 00 00 00[f3]f3 f3 f3 f3 f3 f3 f3 0x7bfff4c4f300: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f380: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f400: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f480: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 0x7bfff4c4f500: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==249585==ABORTING ``` Looking at the code the cause seems clear to me as the samplesperpixel value passed to the vulnerable function isn't checked in any way: ``` static void xps_unpredict_tiff(byte *line, int width, int comps, int bits) { byte left[32]; int i, k, v; for (k = 0; k < comps; k++) left[k] = 0; for (i = 0; i < width; i++) { for (k = 0; k < comps; k++) { v = getcomp(line, i * comps + k, bits); v = v + left[k]; v = v % (1 << bits); putcomp(line, i * comps + k, bits, v); left[k] = v; } } } // ... // ... // ... xps_unpredict_tiff(p, image->width, tiff->samplesperpixel, image->bits); ``` therefore an attacker can overwrite as many bytes as one wants. I compiled a version without address sanitizer and I am able to get instruction pointer control: ``` export CFLAGS="-g3 -O3" export CXXFLAGS="-g3 -O3" export CC=clang export CXX=clang++ export LDFLAGS="-lz -lpthread -lcrypt -lz" # Linker flag stuff... ./autogen.sh \ --enable-freetype --enable-fontconfig \ --enable-cups --with-ijs --with-jbig2dec CC=$CC CXX=$CXX CFLAGS=$CFLAGS CXXFLAGS=$CXXFLAGS LDFLAGS=$LDFLAGS make -j100 gpdl gxps ``` and then: ``` Starting program: /home/oof/newghost/newest/pocversion/ghostpdl/bin/gxps -dSAFER -sOutputFile=out.pdf -sDEVICE=pdfwrite tiffpwn.xps This GDB supports auto-downloading debuginfo from the following URLs: <https://debuginfod.ubuntu.com> Enable debuginfod for this session? (y or [n]) n Debuginfod has been disabled. To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit. [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". warning: could not find '.gnu_debugaltlink' file for /lib/x86_64-linux-gnu/libbrotlidec.so.1 warning: could not find '.gnu_debugaltlink' file for /lib/x86_64-linux-gnu/libbrotlicommon.so.1 warning: could not find '.gnu_debugaltlink' file for /lib/x86_64-linux-gnu/libcap.so.2 Program received signal SIGSEGV, Segmentation fault. 0x0000555555bf8847 in xps_decode_tiff (ctx=<optimized out>, buf=<optimized out>, len=<optimized out>, image=<optimized out>) at ./xps/xpstiff.c:1206 1206 } (gdb) where #0 0x0000555555bf8847 in xps_decode_tiff (ctx=<optimized out>, buf=<optimized out>, len=<optimized out>, image=<optimized out>) at ./xps/xpstiff.c:1206 #1 0xcececececececece in ?? () #2 0xcececececececece in ?? () #3 0xcececececececece in ?? () #4 0xcececececececece in ?? () #5 0xcececececececece in ?? () #6 0xcececececececece in ?? () #7 0xcececececececece in ?? () #8 0xcececececececece in ?? () #9 0xcececececececece in ?? () #10 0xcececececececece in ?? () #11 0xcececececececece in ?? () #12 0xcececececececece in ?? () #13 0xcececececececece in ?? () #14 0xcececececececece in ?? () #15 0xcececececececece in ?? () #16 0xcececececececece in ?? () #17 0xcececececececece in ?? () #18 0xcececececececece in ?? () #19 0xcececececececece in ?? () #20 0xcececececececece in ?? () #21 0xcececececececece in ?? () #22 0xcececececececece in ?? () #23 0xcececececececece in ?? () #24 0xcececececececece in ?? () #25 0xcececececececece in ?? () #26 0xcececececececece in ?? () #27 0xcececececececece in ?? () --Type <RET> for more, q to quit, c to continue without paging--q Quit (gdb) i r rax 0x0 0 rbx 0xcececececececece -3544668469065756978 rcx 0x4438 17464 rdx 0x555556d7bac8 93825017559752 rsi 0x555556d7bba8 93825017559976 rdi 0x555556c45fb8 93825016291256 rbp 0xcececececececece 0xcececececececece rsp 0x7fffffffc238 0x7fffffffc238 r8 0x555556d87928 93825017608488 r9 0x555556d87938 93825017608504 r10 0x0 0 r11 0x555556d3e9a0 93825017309600 r12 0xcececececececece -3544668469065756978 r13 0xcececececececece -3544668469065756978 r14 0xcececececececece -3544668469065756978 r15 0xcececececececece -3544668469065756978 rip 0x555555bf8847 0x555555bf8847 <xps_decode_tiff+103> eflags 0x10202 [ IF RF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 fs_base 0x7ffff6b93a80 140737332722304 gs_base 0x0 0 (gdb) x/10wx $rsp 0x7fffffffc238: 0xcececece 0xcececece 0xcececece 0xcececece 0x7fffffffc248: 0xcececece 0xcececece 0xcececece 0xcececece 0x7fffffffc258: 0xcececece 0xcececece (gdb) ``` and it shows that we are about to pop an invalid value to the RIP register from the stack with the `ret` instruction. Of course an attacker has to go through the rigmarole of crafting a ROP chain etc etc etc to get remote command execution. The fix is to just check the samples per pixel value accordingly. Is this bug under bounty?
> Is this bug under bounty? If you want to claim it as an RCE bounty then we'll need to see something which actually executes code on the host, using a reasonably normal build of Ghostscript.
I've pushed this commit 99727069197d548a8db69ba5d63f766bff40eaab to address this, so we can discuss what happens next at leisure.
Hi! You need to define what a "normal" build is more specifically. If the binary has ASLR and/or stack canaries, then this bug by itself is not exploitable, since you would need to chain it with an information leak bug to get the base address and/or the stack canary. Executable stack (the absence of the NX bit) makes exploitation easier, but is not strictly necessary, but disabling ASLR and stack canaries are mandatory to exploit this bug as far as I can tell. In the meantime I will start developing an exploit for a build of gxps which has aslr disabled and no stack canaries but does not have executable stack. Of course this bug is still dangerous since this bug is still the "trigger" of an RCE exploit, but you lack the required information to exploit it in an ASLR environment or with stack canaries so you would have to chain it with other bugs. If an exploit needs to work with aslr and stack canaries, then this bug does not classify as RCE, so in this case feel free to classify this as a non-rce bug. Thanks in advance!
(In reply to Elias Myllymäki from comment #3) > You need to define what a "normal" build is more specifically. I did say 'reasonably' normal, essentially not modifying the source code or build environment to disable protections, or including 3rd party libraries other than the versions we ship. > NX bit) makes exploitation easier, but is not strictly necessary, but > disabling ASLR and stack canaries are mandatory to exploit this bug as far > as I can tell. OK so you'd need to turn off protections which would (IMO) normally be present. At least these days, anyway, I am aware that wasn't always true. > In the meantime I will start developing an exploit for a > build of gxps which has aslr disabled and no stack canaries but does not > have executable stack. Please don't go to lots of effort. From what you say this wouldn't be classified by us as a remote code execution bug (I may be misunderstanding you, obviously I am not a security researcher) > If an exploit needs to work with aslr and stack canaries, then this bug does > not classify as RCE, so in this case feel free to classify this as a non-rce > bug. OK I can go ahead and add this to my list, and now that we've classified it, and finished the release, I can put that list forward for approval. I need to take this up with some people in the US, but you should expect to hear from me by private email in the near future; there are some steps we (the company, not me specifically) need to go through in order to pay bounties. Obviously we'll need banking details, we have money laundering hoops to jump through and I think that we need a declaration from non-US citizens in order to avoid the IRS taking a cut. I'm assuming you are not a US citizen.... Please do not post anything here, obviously, or direct to me, I'll make sure the accounts people contact you, and I'll alert you beforehand who to expect email from.
Hi! Whoops, I meant that this vulnerability would not cause remote command execution in isolation. This vulnerability is usually considered a remote command execution one, but you would need to chain it with other info leak bugs in order for it to work. The best analogy I can come up with is that this bug is the round and the information leak bug(s) is the gun. The round isn't by itself deadly and can at most cause the program to just crash, but combined with the information leak bug(s) it becomes dangerous. Even though the round itself is not deadly, it doesn't erase the fact that without it the gun is useless and it is the most important part when trying to shoot. What I am trying to say is that the buffer overflow is the most important part of such an exploit chain and basically the linchpin of binary exploitation attacks. A stack buffer overflow is one of the most common ways an attacker can gain control of the instruction pointer register (RIP) which is the beating heart of binary exploitation. Practically all binary exploits first overwrite the stack or heap such that instead of returning back to the original caller, the program "returns" to an attacker controlled address. Now, the difficulty is in knowing which address to "return" to. This is the reason why such a bug is chained with an information leak bug in order to leak an address off of the heap or stack to figure out the "base address" of the module which then is used in a so called "ROP-chain" in order to piece small pieces of machine code called gadgets in a chain which ultimately call some function like "system" with "/bin/sh" as the argument to get a shell or execute arbitrary code. Of course one also needs to circumvent the stack canary protection with a similar information leak bug in order to write the correct canary byte such that the stack protector doesn't kill your exploit attempt but I digress. My main point is that this is the main bug needed for remote command execution, but you need extra information achieved via other bugs in order to exploit it. Without such a bug, command execution can not be achieved. So since this needs to be chained with other bugs to get remote command execution, I guess the final classification depends on if you consider each bug separately or together, since combined with other bugs you actually can achieve remote command execution. If separately, then this could lead to a situation where a group of bugs are all considered not remote command execution even though they would cause RCE in combination. So it depends on your definitions. Thanks in advance!
Also, If I find such an infoleak and I am then able to exploit this vulnerability in combination with such a bug and actually cause RCE on a machine with ASLR, stack canaries and NX bit all enabled, then does this bounty apply retroactively? This complicates things since then you would have to consider such an infoleak as a "part" of this bug (maybe?) so I am not sure.
(In reply to Elias Myllymäki from comment #6) > Also, If I find such an infoleak and I am then able to exploit this > vulnerability in combination with such a bug and actually cause RCE on a > machine with ASLR, stack canaries and NX bit all enabled, then does this > bounty apply retroactively? This complicates things since then you would > have to consider such an infoleak as a "part" of this bug (maybe?) so I am > not sure. All the remote code execution bugs reported to date have been 'stand-alone', none have required any other bug to be present in order to be demonstrated. Nor have they required us to build Ghostscript in particular ways. In general my feeling (and this is just me) is that a RCE bug needs to be stand-alone. But I'd take each case on its merits, I wouldn't want this to be taken as an absolute position. Which isn't to say that I don't think this is serious. While I haven't rated it as highly as the previous RCE bug reports, I have rated it more highly than your other reports, and well up the scale that we are able to afford. I've started the process of getting all the accumulated reports to date approved, but I'm not certain if our CEO is currently in Korea or the US, if Korea then this will take a little longer to filter through the email chain.
I've asked one of my colleagues to request a CVE for this bug report. Unfortunately it is taking a long time for MITRE to assign us CVEs at the moment, we are still waiting for several others.