Blob Blame History Raw
From: Chris Liddell <chris.liddell@artifex.com>
Date: Tue, 4 Sep 2018 22:18:46 +0000 (+0100)
Subject: Bug 699714: retain .LockSafetyParams through failed .installpagedevice

Bug 699714: retain .LockSafetyParams through failed .installpagedevice

In the event that the .trysetparams fails during .installpagedevice, catch the
error, and ensure that at least the .LockSafetyParams is set.

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

From: Chris Liddell <chris.liddell@artifex.com>
Date: Wed, 5 Sep 2018 16:14:59 +0000 (+0100)
Subject: Bug 699718: Ensure stack space is available before gsrestore call out

Bug 699718: Ensure stack space is available before gsrestore call out

During a grestore, if the device is going to change, we call out to Postscript
to restore the device configuration, before returning to restore the graphics
state internally.

We have to ensure sufficient op stack space is available to complete the
operation, otherwise the device can end up an undefined state.

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

From: Chris Liddell <chris.liddell@artifex.com>
Date: Fri, 7 Sep 2018 07:07:12 +0000 (+0100)
Subject: Bug 699718(2): Improve/augment stack size checking

Bug 699718(2): Improve/augment stack size checking

Improve the rebustness of the previous solution (previously it could trigger an
error when there *was* stack capacity available).

Remove redundant check: we don't need to check if the *current* stack size is
sufficient, before checking the maximum permitted stack size.

Also check the exec stack, as execstackoverflow can also cause the
Postscript call out to fail.

Lastly, in event of failure, put the LockSafetyParams flag back in the existing
device (this is only necessary because we don't enfore JOBSERVER mode).

Note: the Postscript callout (%grestorepagedevice) never pushes any dictionaries
on the dict stack - if that changes, we should check that stack, too.

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

diff -up ghostscript-9.07/base/gserrors.h.cve-2018-16802 ghostscript-9.07/base/gserrors.h
--- ghostscript-9.07/base/gserrors.h.cve-2018-16802	2018-11-27 19:07:38.076517822 +0100
+++ ghostscript-9.07/base/gserrors.h	2018-11-27 19:09:19.116234748 +0100
@@ -25,6 +25,7 @@
 /* We use ints rather than an enum to avoid a lot of casting. */
 
 #define gs_error_unknownerror (-1)	/* unknown error */
+#define gs_error_execstackoverflow (-5)
 #define gs_error_interrupt (-6)
 #define gs_error_invalidaccess (-7)
 #define gs_error_invalidfileaccess (-9)
@@ -33,6 +34,7 @@
 #define gs_error_limitcheck (-13)
 #define gs_error_nocurrentpoint (-14)
 #define gs_error_rangecheck (-15)
+#define gs_error_stackoverflow (-16)
 #define gs_error_stackunderflow (-17)
 #define gs_error_typecheck (-20)
 #define gs_error_undefined (-21)
diff -up ghostscript-9.07/psi/zdevice2.c.cve-2018-16802 ghostscript-9.07/psi/zdevice2.c
--- ghostscript-9.07/psi/zdevice2.c.cve-2018-16802	2018-11-27 19:04:51.665627148 +0100
+++ ghostscript-9.07/psi/zdevice2.c	2018-11-27 19:04:51.696626755 +0100
@@ -250,8 +250,8 @@ z2currentgstate(i_ctx_t *i_ctx_p)
 /* ------ Wrappers for operators that reset the graphics state. ------ */
 
 /* Check whether we need to call out to restore the page device. */
-static bool
-restore_page_device(const gs_state * pgs_old, const gs_state * pgs_new)
+static int
+restore_page_device(i_ctx_t *i_ctx_p, const gs_state * pgs_old, const gs_state * pgs_new)
 {
     gx_device *dev_old = gs_currentdevice(pgs_old);
     gx_device *dev_new;
@@ -259,9 +259,10 @@ restore_page_device(const gs_state * pgs
     gx_device *dev_t2;
     bool samepagedevice = obj_eq(dev_old->memory, &gs_int_gstate(pgs_old)->pagedevice,
         &gs_int_gstate(pgs_new)->pagedevice);
+    bool LockSafetyParams = dev_old->LockSafetyParams;
 
     if ((dev_t1 = (*dev_proc(dev_old, get_page_device)) (dev_old)) == 0)
-        return false;
+        return 0;
     /* If we are going to putdeviceparams in a callout, we need to */
     /* unlock temporarily.  The device will be re-locked as needed */
     /* by putdeviceparams from the pgs_old->pagedevice dict state. */
@@ -270,23 +271,51 @@ restore_page_device(const gs_state * pgs
     dev_new = gs_currentdevice(pgs_new);
     if (dev_old != dev_new) {
         if ((dev_t2 = (*dev_proc(dev_new, get_page_device)) (dev_new)) == 0)
-            return false;
-        if (dev_t1 != dev_t2)
-            return true;
+            samepagedevice = true;
+        else if (dev_t1 != dev_t2)
+            samepagedevice = false;
+    }
+
+    if (LockSafetyParams && !samepagedevice) {
+        const int required_ops = 512;
+        const int required_es = 32;
+
+        /* The %grestorepagedevice must complete: the biggest danger
+           is operand stack overflow. As we use get/putdeviceparams
+           that means pushing all the device params onto the stack,
+           pdfwrite having by far the largest number of parameters
+           at (currently) 212 key/value pairs - thus needing (currently)
+           424 entries on the op stack. Allowing for working stack
+           space, and safety margin.....
+         */
+        if (required_ops + ref_stack_count(&o_stack) >= ref_stack_max_count(&o_stack)) {
+           gs_currentdevice(pgs_old)->LockSafetyParams = LockSafetyParams;
+           return_error(gs_error_stackoverflow);
+        }
+        /* We also want enough exec stack space - 32 is an overestimate of
+           what we need to complete the Postscript call out.
+         */
+        if (required_es + ref_stack_count(&e_stack) >= ref_stack_max_count(&e_stack)) {
+           gs_currentdevice(pgs_old)->LockSafetyParams = LockSafetyParams;
+           return_error(gs_error_execstackoverflow);
+        }
     }
     /*
      * The current implementation of setpagedevice just sets new
      * parameters in the same device object, so we have to check
      * whether the page device dictionaries are the same.
      */
-    return !samepagedevice;
+    return samepagedevice ? 0 : 1;
 }
 
 /* - grestore - */
 static int
 z2grestore(i_ctx_t *i_ctx_p)
 {
-    if (!restore_page_device(igs, gs_state_saved(igs)))
+    int code = restore_page_device(i_ctx_p, igs, gs_state_saved(igs));
+    if (code < 0) return code;
+
+    if (code == 0)
         return gs_grestore(igs);
     return push_callout(i_ctx_p, "%grestorepagedevice");
 }
@@ -296,7 +325,9 @@ static int
 z2grestoreall(i_ctx_t *i_ctx_p)
 {
     for (;;) {
-        if (!restore_page_device(igs, gs_state_saved(igs))) {
+        int code = restore_page_device(i_ctx_p, igs, gs_state_saved(igs));
+        if (code < 0) return code;
+        if (code == 0) {
             bool done = !gs_state_saved(gs_state_saved(igs));
 
             gs_grestore(igs);
@@ -327,11 +358,15 @@ z2restore(i_ctx_t *i_ctx_p)
     if (code < 0) return code;
 
     while (gs_state_saved(gs_state_saved(igs))) {
-        if (restore_page_device(igs, gs_state_saved(igs)))
+        code = restore_page_device(i_ctx_p, igs, gs_state_saved(igs));
+        if (code < 0) return code;
+        if (code > 0)
             return push_callout(i_ctx_p, "%restore1pagedevice");
         gs_grestore(igs);
     }
-    if (restore_page_device(igs, gs_state_saved(igs)))
+    code = restore_page_device(i_ctx_p, igs, gs_state_saved(igs));
+    if (code < 0) return code;
+    if (code > 0)
         return push_callout(i_ctx_p, "%restorepagedevice");
 
     code = dorestore(i_ctx_p, asave);
@@ -354,9 +389,12 @@ static int
 z2setgstate(i_ctx_t *i_ctx_p)
 {
     os_ptr op = osp;
+    int code;
 
     check_stype(*op, st_igstate_obj);
-    if (!restore_page_device(igs, igstate_ptr(op)))
+    code = restore_page_device(i_ctx_p, igs, igstate_ptr(op));
+    if (code < 0) return code;
+    if (code == 0)
         return zsetgstate(i_ctx_p);
     return push_callout(i_ctx_p, "%setgstatepagedevice");
 }
diff -up ghostscript-9.07/Resource/Init/gs_setpd.ps.cve-2018-16802 ghostscript-9.07/Resource/Init/gs_setpd.ps
--- ghostscript-9.07/Resource/Init/gs_setpd.ps.cve-2018-16802	2013-02-14 08:58:16.000000000 +0100
+++ ghostscript-9.07/Resource/Init/gs_setpd.ps	2018-11-27 19:04:51.696626755 +0100
@@ -95,27 +95,41 @@ level2dict begin
  {	% Since setpagedevice doesn't create new device objects,
         % we must (carefully) reinstall the old parameters in
         % the same device.
-   .currentpagedevice pop //null currentdevice //null .trysetparams
+   .currentpagedevice pop //null currentdevice //null
+   { .trysetparams } .internalstopped
+   {
+     //null
+   } if
    dup type /booleantype eq
     { pop pop }
-    {		% This should never happen!
+    {
       SETPDDEBUG { (Error in .trysetparams!) = pstack flush } if
-      cleartomark pop pop pop
+      {cleartomark pop pop pop} .internalstopped pop
+      % if resetting the entire device state failed, at least put back the
+      % security related key
+      currentdevice //null //false mark /.LockSafetyParams
+      currentpagedevice /.LockSafetyParams .knownget not
+      {systemdict /SAFER .knownget not {//false} } if
+      .putdeviceparamsonly
       /.installpagedevice cvx /rangecheck signalerror
     }
    ifelse pop pop
         % A careful reading of the Red Book reveals that an erasepage
         % should occur, but *not* an initgraphics.
    erasepage .beginpage
- } bind def
+ } bind executeonly def
 
 /.uninstallpagedevice
- { 2 .endpage { .currentnumcopies //false .outputpage } if
+ {
+   {2 .endpage { .currentnumcopies //false .outputpage } if} .internalstopped pop
    nulldevice
  } bind def
 
 (%grestorepagedevice) cvn
- { .uninstallpagedevice grestore .installpagedevice
+ {
+ .uninstallpagedevice
+ grestore
+ .installpagedevice
  } bind def
 
 (%grestoreallpagedevice) cvn