Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Date: Wed, 23 Jan 2019 06:38:09 -0800
From: Tavis Ormandy <taviso@...gle.com>
To: oss-security@...ts.openwall.com
Subject: ghostscript: subroutines within pseudo-operators must themselves be pseudo-operators

Hello, I noticed ghostscript 9.26 was released, so decided to take a look
and noticed some problems. For background, this is how you define a
subroutine in postscript:

/hello {
    (hello\n) print
} def

That's simple enough, but because a subroutine is just an executable array
of commands, you need to mark it as executeonly if you're using powerful
system operators. That way, users can't peek inside and get references to
operators they shouldn't be allowed to use.

/hello {
    (hello\n) print
} executeonly def

That's still not enough though, because the routine might expose the
contents to error handlers, so you also need to make it a pseudo-operator
with odef. PostScript error handlers don't examine any deeper than the
current operator (or pseudo-operator), so won't expose any of the contents
if they stop.

/hello {
    (hello\n) print
} executeonly odef

Looks good, but it gets weirder. If you don't bind the contents, then name
resolution happens on execution, not when you define it. That means that
someone can change the dictstack (which kind of works like variable scope
in other languages) so that commands and operators do something different
than when you defined the subroutine.

Like this:

GS>/hello { (hello\n) print } executeonly odef
GS><< /print { (goodbye)= pop } >> begin
GS>hello
goodbye

This means you also need to bind the routine, and also be very aware when
you're writing it of what is and what isn't an operator at define-time
(nobody ever said writing postscript was easy, lol). So now we have this:

/hello {
    (hello\n) print
} bind executeonly odef

I think that's good enough for simple routines, but what if it's more
complicated? The way you branch in PostScript is to create an ephemeral
subroutine and pass it to the `if` or `ifelse` operators, like this:

/hello {
    time 1200 lt {
        (good morning\n) print
    } {
        (good afternoon\n) print
    } ifelse
} bind executeonly odef

Do those ephemeral routines also need to be protected? The answer is *yes*,
they're pushed on the operand stack just like everything else, so can cause
errors like /stackoverflow or /execstackoverflow and will then be exposed
to error handlers. In my opinion, this is a language specification flaw in
PostScript.

Regardless, ghostscript didn't protect a whole bunch of these ephemeral
routines, here is one example, but there were dozens:

http://git.ghostscript.com/?p=ghostpdl.git;a=blob;f=Resource/Init/pdf_draw.ps;h=79733df451c1ecc0a71b08d10e5412ac3e243a9e;hb=gs926#l1123

1123       {
1124         currentglobal pdfdict gcheck .setglobal
1125         pdfdict /.Qqwarning_issued //true .forceput
1126         .setglobal
1127         pdfformaterror
1128       } ifelse

You can see the routine itself is bound, executeonly and odef, but the
ephemeral routines inside it used for conditions and loops are not
protected.

These bugs are starting to get trickier to exploit, you have to make an
operator fail very precisely, but I made a demo that works in 9.26. This
uses the trick I described above of taking over names that couldn't be
resolved at define time by pushing a new dict on the dictstack. This gives
me a high degree of control over the routine.

$ gs -dSAFER -f ghostscript-926-forceput.ps
GPL Ghostscript 9.26 (2018-11-20)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Stage 0: PDFfile
Stage 1: q
Stage 3: oget
Stage 4: pdfemptycount
Stage 5: gput
Stage 6: resolvestream
Stage 7: pdfopdict
Stage 8: .pdfruncontext
Stage 9: pdfdict
Stage 10: /typecheck #1
Stage 10: /typecheck #2
Stage 11: Exploitation...
Should now have complete control over ghostscript, attempting to read
/etc/passwd...
(root:x:0:0:root:/root:/bin/bash)
Attempting to execute a shell command...
uid=1000(taviso) gid=1000(primarygroup)
groups=1000(primarygroup),4(adm),20(dialout),24(cdrom),25(floppy),44(video),46(plugdev),999(logindev)

This exploit should work via evince, ImageMagick, nautilus, less (just
rename it exploit.pcd), gimp, gv, etc, etc. It might require some
adjustment to work on older versions, but 9.26 and earlier are all
affected. Do not count on AppArmor protecting you, the policy is *very*
relaxed.

The patch required to protect ghostscript from attacks like this was
non-trivial, and took a significant amount of work, these patches are
required:

http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=13b0a36f8181db66a91bcc8cea139998b53a8996
http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=2db98f9c66135601efb103d8db7d020a672308db
http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=99f13091a3f309bdc95d275ea9fec10bb9f42d9a
http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=59d8f4deef90c1598ff50616519d5576756b4495
http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=2768d1a6dddb83f5c061207a7ed2813999c1b5c9
http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=49c8092da88ef6bb0aa281fe294ae0925a44b5b9

This was Project Zero issue 1729
<https://bugs.chromium.org/p/project-zero/issues/detail?id=1729>,
Ghostscript issue 700317
<https://bugs.ghostscript.com/show_bug.cgi?id=700317>, and CVE-2019-6116.

Thanks, Tavis.

p.s. I'm not regularly looking at ghostscript, this was just a random look
at the new release.

#DeprecateUntrustedPostscript

Content of type "text/html" skipped

Download attachment "ghostscript-926-forceput-typecheck-example.ps" of type "application/postscript" (2468 bytes)

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.