Bug 708819 - Stack-buffer-overwrite when parsing a malicious tiff file in XPS file
Summary: Stack-buffer-overwrite when parsing a malicious tiff file in XPS file
Status: RESOLVED FIXED
Alias: None
Product: Ghostscript
Classification: Unclassified
Component: Security (public) (show other bugs)
Version: unspecified
Hardware: PC Linux
: P2 normal
Assignee: Chris Liddell (chrisl)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2025-09-08 16:35 UTC by Elias Myllymäki
Modified: 2025-09-12 18:00 UTC (History)
9 users (show)

See Also:
Customer:
Word Size: ---


Attachments
Stack buffer overflow XPS file. (415.91 KB, application/zip)
2025-09-08 16:35 UTC, Elias Myllymäki
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Elias Myllymäki 2025-09-08 16:35:03 UTC
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?
Comment 1 Ken Sharp 2025-09-08 18:05:41 UTC
> 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.
Comment 2 Ken Sharp 2025-09-09 09:14:24 UTC
I've pushed this commit 99727069197d548a8db69ba5d63f766bff40eaab to address this, so we can discuss what happens next at leisure.
Comment 3 Elias Myllymäki 2025-09-10 11:37:49 UTC
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!
Comment 4 Ken Sharp 2025-09-10 12:43:24 UTC
(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.
Comment 5 Elias Myllymäki 2025-09-10 13:32:21 UTC
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!
Comment 6 Elias Myllymäki 2025-09-10 13:39:11 UTC
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.
Comment 7 Ken Sharp 2025-09-10 13:51:02 UTC
(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.
Comment 8 Ken Sharp 2025-09-12 18:00:56 UTC
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.