Ghostscript 9.24 was released with an incomplete fix for CVE-2018-16509:
https://nvd.nist.gov/vuln/detail/CVE-2018-16509
https://bugs.chromium.org/p/project-zero/issues/detail?id=1640#c19
https://bugs.ghostscript.com/show_bug.cgi?id=699718

The reproducers no longer work after applying these commits:

https://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=5812b1b78fc4d36fdc293b7859de69241140d590
https://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=e914f1da46e33decc534486598dc3eadf69e6efb
https://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=3e5d316b72e3965b7968bb1d96baa137cd063ac6
https://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=643b24dbd002fb9c131313253c307cf3951b3d47

This patch is a "squashed" version of those.

diff --git a/Resource/Init/gs_setpd.ps b/Resource/Init/gs_setpd.ps
index bba3c8c0e..8fa7c51df 100644
--- a/Resource/Init/gs_setpd.ps
+++ b/Resource/Init/gs_setpd.ps
@@ -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
diff --git a/psi/zdevice2.c b/psi/zdevice2.c
index 0c7080d57..159a0c0d9 100644
--- a/psi/zdevice2.c
+++ b/psi/zdevice2.c
@@ -251,8 +251,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_gstate * pgs_old, const gs_gstate * pgs_new)
+static int
+restore_page_device(i_ctx_t *i_ctx_p, const gs_gstate * pgs_old, const gs_gstate * pgs_new)
 {
     gx_device *dev_old = gs_currentdevice(pgs_old);
     gx_device *dev_new;
@@ -260,9 +260,10 @@ restore_page_device(const gs_gstate * pgs_old, const gs_gstate * pgs_new)
     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. */
@@ -271,23 +272,51 @@ restore_page_device(const gs_gstate * pgs_old, const gs_gstate * pgs_new)
     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_gstate_saved(igs)))
+    int code = restore_page_device(i_ctx_p, igs, gs_gstate_saved(igs));
+    if (code < 0) return code;
+
+    if (code == 0)
         return gs_grestore(igs);
     return push_callout(i_ctx_p, "%grestorepagedevice");
 }
@@ -297,7 +326,9 @@ static int
 z2grestoreall(i_ctx_t *i_ctx_p)
 {
     for (;;) {
-        if (!restore_page_device(igs, gs_gstate_saved(igs))) {
+        int code = restore_page_device(i_ctx_p, igs, gs_gstate_saved(igs));
+        if (code < 0) return code;
+        if (code == 0) {
             bool done = !gs_gstate_saved(gs_gstate_saved(igs));
 
             gs_grestore(igs);
@@ -328,11 +359,15 @@ z2restore(i_ctx_t *i_ctx_p)
     if (code < 0) return code;
 
     while (gs_gstate_saved(gs_gstate_saved(igs))) {
-        if (restore_page_device(igs, gs_gstate_saved(igs)))
+        code = restore_page_device(i_ctx_p, igs, gs_gstate_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_gstate_saved(igs)))
+    code = restore_page_device(i_ctx_p, igs, gs_gstate_saved(igs));
+    if (code < 0) return code;
+    if (code > 0)
         return push_callout(i_ctx_p, "%restorepagedevice");
 
     code = dorestore(i_ctx_p, asave);
@@ -355,9 +390,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");
 }
