Blob Blame History Raw
From: Chris Liddell <chris.liddell@artifex.com>
Date: Thu, 6 Sep 2018 08:16:22 +0000 (+0100)
Subject: Bug 699708 (part 1): 'Hide' non-replaceable error handlers for SAFER

Bug 699708 (part 1): 'Hide' non-replaceable error handlers for SAFER

We already had a 'private' dictionary for non-standard errors: gserrordict.

This now includes all the default error handlers, the dictionary is made
noaccess and all the prodedures are bound and executeonly.

When running with -dSAFER, in the event of a Postscript error, instead of
pulling the handler from errordict, we'll pull it from gserrordict - thus
malicious input cannot trigger problems by the use of custom error handlers.

errordict remains open and writeable, so files such as the Quality Logic tests
that install their own handlers will still 'work', with the exception that the
custom error handlers will not be called.

This is a 'first pass', 'sledgehammer' approach: a nice addition would to allow
an integrator to specify a list of errors that are not to be replaced (for
example, embedded applications would probably want to ensure that VMerror is
always handled as they intend).

https://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=fb713b3818b52d8a6cf62c951eba2e1795ff9624

From: Chris Liddell <chris.liddell@artifex.com>
Date: Thu, 4 Oct 2018 09:42:13 +0000 (+0100)
Subject: Bug 699832: add control over hiding error handlers.

Bug 699832: add control over hiding error handlers.

With a previous commit changing error handling in SAFER so the handler gets
passed a name object (rather than executable object), it is less critical to
hide the error handlers.

This introduces a -dSAFERERRORS option to force only use of the default error
handlers.

It also adds a .setsafererrors Postscript call, meaning a caller, without
-dSAFERERRORS, can create their own default error handlers (in errordict, as
normal), and then call .setsafererrors meaning their own handlers are always
called.

With -dSAFERERRORS or after a call to .setsafererrors, .setsafererrors is
removed.

https://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=1778db6bc10a8d60dfe986b22d2300326733ddd6

From: Chris Liddell <chris.liddell@artifex.com>
Date: Tue, 2 Oct 2018 15:02:58 +0000 (+0100)
Subject: For hidden operators, pass a name object to error handler.

For hidden operators, pass a name object to error handler.

In normal operation, Postscript error handlers are passed the object which
triggered the error: this is invariably an operator object.

The issue arises when an error is triggered by an operator which is for internal
use only, and that operator is then passed to the error handler, meaning it
becomes visible to the error handler code.

By converting to a name object, the error message is still valid, but we no
longer expose internal use only operators.

The change in gs_dps1.ps is related to the above: previously an error in
scheck would throw an error against .gcheck, but as .gcheck is now a hidden
operator, it resulted in a name object being passed to the error handler. As
scheck is a 'real' operator, it's better to use the real operator, rather than
the name of an internal, hidden one.

https://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=a6807394bd94b708be24758287b606154daaaed9
---

diff -up ghostscript-9.07/psi/interp.c.cve-2018-17183 ghostscript-9.07/psi/interp.c
--- ghostscript-9.07/psi/interp.c.cve-2018-17183	2018-11-30 14:21:33.531815665 +0100
+++ ghostscript-9.07/psi/interp.c	2018-11-30 14:43:42.520691143 +0100
@@ -650,20 +650,25 @@ again:
         return code;
     if (gs_errorname(i_ctx_p, code, &error_name) < 0)
         return code;            /* out-of-range error code! */
-    /*
-     * For greater Adobe compatibility, only the standard PostScript errors
-     * are defined in errordict; the rest are in gserrordict.
+
+    /*  We refer to gserrordict first, which is not accessible to Postcript jobs
+     *  If we're running with SAFERERRORS all the handlers are copied to gserrordict
+     *  so we'll always find the default one. If not SAFERERRORS, only gs specific
+     *  errors are in gserrordict.
      */
-    if (dict_find_string(systemdict, "errordict", &perrordict) <= 0 ||
+    if (dict_find_string(systemdict, "gserrordict", &perrordict) <= 0 ||
         (dict_find(perrordict, &error_name, &epref) <= 0 &&
-         (dict_find_string(systemdict, "gserrordict", &perrordict) <= 0 ||
+         (dict_find_string(systemdict, "errordict", &perrordict) <= 0 ||
           dict_find(perrordict, &error_name, &epref) <= 0))
         )
         return code;            /* error name not in errordict??? */
+
     doref = *epref;
     epref = &doref;
     /* Push the error object on the operand stack if appropriate. */
     if (!ERROR_IS_INTERRUPT(code)) {
+        byte buf[260], *bufptr;
+        uint rlen;
         /* Replace the error object if within an oparray or .errorexec. */
         osp++;
         if (osp >= ostop) {
@@ -672,6 +677,37 @@ again:
         }
         *osp = *perror_object;
         errorexec_find(i_ctx_p, osp);
+
+        if (!r_has_type(osp, t_string) && !r_has_type(osp, t_name)) {
+            code = obj_cvs(imemory, osp, buf + 2, 256, &rlen, (const byte **)&bufptr);
+            if (code < 0) {
+                const char *unknownstr = "--unknown--";
+                rlen = strlen(unknownstr);
+                memcpy(buf, unknownstr, rlen);
+                bufptr = buf;
+            }
+            else {
+                ref *tobj;
+                bufptr[rlen] = '\0';
+                /* Only pass a name object if the operator doesn't exist in systemdict
+                 * i.e. it's an internal operator we have hidden
+                 */
+                code = dict_find_string(systemdict, (const char *)bufptr, &tobj);
+                if (code < 0) {
+                    buf[0] = buf[1] = buf[rlen + 2] = buf[rlen + 3] = '-';
+                    rlen += 4;
+                    bufptr = buf;
+                }
+                else {
+                    bufptr = NULL;
+                }
+            }
+            if (bufptr) {
+                code = name_ref(imemory, buf, rlen, osp, 1);
+                if (code < 0)
+                    make_null(osp);
+            }
+        }
     }
     goto again;
 }
diff -up ghostscript-9.07/Resource/Init/gs_dps1.ps.cve-2018-17183 ghostscript-9.07/Resource/Init/gs_dps1.ps
--- ghostscript-9.07/Resource/Init/gs_dps1.ps.cve-2018-17183	2018-11-30 14:40:30.044460932 +0100
+++ ghostscript-9.07/Resource/Init/gs_dps1.ps	2018-11-30 14:41:03.291982495 +0100
@@ -21,7 +21,7 @@ level2dict begin
 % ------ Virtual memory ------ %
 
 /currentshared /.currentglobal load def
-/scheck /.gcheck load def
+/scheck {.gcheck} bind odef
 %****** FOLLOWING IS WRONG ******
 /shareddict currentdict /globaldict .knownget not { 20 dict } if def
 
diff -up ghostscript-9.07/Resource/Init/gs_init.ps.cve-2018-17183 ghostscript-9.07/Resource/Init/gs_init.ps
--- ghostscript-9.07/Resource/Init/gs_init.ps.cve-2018-17183	2018-11-30 14:16:19.956328097 +0100
+++ ghostscript-9.07/Resource/Init/gs_init.ps	2018-11-30 14:30:28.051123777 +0100
@@ -183,6 +183,16 @@ currentdict /DELAYSAFER known { /DELAYSA
   currentdict /PARANOIDSAFER known or	% PARANOIDSAFER is equivalent
 }
 ifelse def
+
+/SAFERERRORS
+currentdict /NOSAFERERRORS known
+{
+  //false
+}
+{
+  currentdict /SAFERERRORS known
+} ifelse def
+
 currentdict /SHORTERRORS known   /SHORTERRORS exch def
 currentdict /STRICT known   /STRICT exch def
 currentdict /TTYPAUSE known   /TTYPAUSE exch def
@@ -880,7 +890,7 @@ userdict /.currentresourcefile //null pu
        { not exch pop exit } { pop } ifelse
     }
    for exch pop .quit
- } bind def
+ } bind executeonly def
 /.errorhandler		% <command> <errorname> .errorhandler -
   {		% Detect an internal 'stopped'.
     1 .instopped { //null eq { pop pop stop } if } if
@@ -925,7 +935,7 @@ userdict /.currentresourcefile //null pu
     $error /globalmode get $error /.nosetlocal get and .setglobal
     $error /.inerror //false put
     stop
-  } bind def
+  } bind executeonly def
 % Define the standard handleerror.  We break out the printing procedure
 % (.printerror) so that it can be extended for binary output
 % if the Level 2 facilities are present.
@@ -975,7 +985,7 @@ userdict /.currentresourcefile //null pu
      ifelse	% newerror
      end
      flush
-    } bind def
+    } bind executeonly def
   /.printerror_long			% long error printout,
                                         % $error is on the dict stack
    {	% Push the (anonymous) stack printing procedure.
@@ -1052,14 +1062,14 @@ userdict /.currentresourcefile //null pu
         { (Current file position is ) print position = }
        if
 
-   } bind def
+   } bind executeonly def
 % Define a procedure for clearing the error indication.
 /.clearerror
  { $error /newerror //false put
    $error /errorname //null put
    $error /errorinfo //null put
    0 .setoserrno
- } bind def
+ } bind executeonly def
 
 % Define $error.  This must be in local VM.
 .currentglobal //false .setglobal
@@ -1085,11 +1095,15 @@ end
 /errordict ErrorNames length 3 add dict
 .forcedef		% errordict is local, systemdict is global
 .setglobal		% back to global VM
-% For greater Adobe compatibility, we put all non-standard errors in a
-%   separate dictionary, gserrordict.  It does not need to be in local VM,
-%   because PostScript programs do not access it.
+%  gserrordict contains all the default error handling methods, but unlike
+%  errordict it is noaccess after creation (also it is in global VM).
+%  When running 'SAFER', we'll ignore the contents of errordict, which
+%  may have been tampered with by the running job, and always use gserrordict
+%  gserrordict also contains any non-standard errors, for better compatibility
+%  with Adobe.
+%
 %   NOTE: the name gserrordict is known to the interpreter.
-/gserrordict 5 dict def
+/gserrordict ErrorNames length 3 add dict def
 % Register an error in errordict.  We make this a procedure because we only
 % register the Level 1 errors here: the rest are registered by "feature"
 % files.  However, ErrorNames contains all of the error names regardless of
@@ -1118,9 +1132,23 @@ errordict begin
  } bind def
 end		% errordict
 
-% Put non-standard errors in gserrordict.
 gserrordict /unknownerror errordict /unknownerror get put
 errordict /unknownerror .undef
+
+/.SAFERERRORLIST ErrorNames def
+/.setsafererrors
+{
+% Put all the requested handlers in gserrordict
+  gserrordict
+  //.SAFERERRORLIST
+  {dup errordict exch get 2 index 3 1 roll put} forall
+  noaccess pop
+  systemdict /.setsafeerrors .forceundef
+  systemdict /.SAFERERRORLIST .forceundef
+} bind executeonly odef
+
+SAFERERRORS {.setsafererrors} if
+
 % Define a stable private copy of handleerror that we will always use under
 % JOBSERVER mode.
 /.GShandleerror errordict /handleerror get def
@@ -1743,18 +1771,15 @@ currentdict /.runlibfile .undef
 
 % Bind all the operators defined as procedures.
 /.bindoperators		% binds operators in currentdict
- { % Temporarily disable the typecheck error.
-   errordict /typecheck 2 copy get
-   errordict /typecheck { pop } put	% pop the command
+ {
    currentdict
     { dup type /operatortype eq
-       { % This might be a real operator, so bind might cause a typecheck,
-         % but we've made the error a no-op temporarily.
-         .bind		% do a real bind even if NOBIND is set
+       {
+         % This might be a real operator, so bind might cause a typecheck
+         {.bind} .internalstopped pop
        }
       if pop pop
     } forall
-   put
  } def
 NOBIND DELAYBIND or not { .bindoperators } if