ea5d11
From 26d93b9a3f2a47ece821defe12e9059b4a7a3bf2 Mon Sep 17 00:00:00 2001
ea5d11
From: Chris Liddell <chris.liddell@artifex.com>
ea5d11
Date: Fri, 24 Aug 2018 09:26:04 +0100
ea5d11
Subject: [PATCH] Improve restore robustness
ea5d11
ea5d11
Prompted by looking at Bug 699654:
ea5d11
ea5d11
There are two variants of the restore operator in Ghostscript: one is Level 1
ea5d11
(restoring VM), the other is Level 2+ (adding page device restoring to the
ea5d11
Level operator).
ea5d11
ea5d11
This was implemented by the Level 2+ version restoring the device in the
ea5d11
graphics state, then calling the Level 1 implementation to handle actually
ea5d11
restoring the VM state.
ea5d11
ea5d11
The problem was that the operand checking, and sanity of the save object was
ea5d11
only done by the Level 1 variant, thus meaning an invalid save object could
ea5d11
leave a (Level 2+) restore partially complete - with the page device part
ea5d11
restored, but not VM, and the page device not configured.
ea5d11
ea5d11
To solve that, this commit splits the operand and sanity checking, and the
ea5d11
core of the restore operation into separate functions, so the relevant
ea5d11
operators can validate the operand *before* taking any further action. That
ea5d11
reduces the chances of an invalid restore leaving the interpreter in an
ea5d11
unknown state.
ea5d11
ea5d11
If an error occurs during the actual VM restore it is essentially fatal, and the
ea5d11
interpreter cannot continue, but as an extra surety for security, in the event
ea5d11
of such an error, we'll explicitly preserve the LockSafetyParams of the device,
ea5d11
rather than rely on the post-restore device configuration (which won't happen
ea5d11
in the event of an error).
ea5d11
---
ea5d11
 psi/int.mak    |  4 ++--
ea5d11
 psi/isave.h    |  6 ++++++
ea5d11
 psi/zdevice2.c | 32 ++++++++++++++++++++++++++++++--
ea5d11
 psi/zvmem.c    | 56 +++++++++++++++++++++++++++++++++++++++++++++++---------
ea5d11
 4 files changed, 85 insertions(+), 13 deletions(-)
ea5d11
ea5d11
diff --git a/psi/int.mak b/psi/int.mak
ea5d11
index fbf191d..4d0f296 100644
ea5d11
--- a/psi/int.mak
ea5d11
+++ b/psi/int.mak
ea5d11
@@ -1047,8 +1047,8 @@ $(PSD)pagedev.dev : $(INT_MAK) $(ECHOGS_XE) $(pagedev_)
ea5d11
 
ea5d11
 $(PSOBJ)zdevice2.$(OBJ) : $(PSSRC)zdevice2.c $(OP) $(math__h) $(memory__h)\
ea5d11
  $(dstack_h) $(estack_h)\
ea5d11
- $(idict_h) $(idparam_h) $(igstate_h) $(iname_h) $(iutil_h) $(store_h)\
ea5d11
- $(gxdevice_h) $(gsstate_h)
ea5d11
+ $(idict_h) $(idparam_h) $(igstate_h) $(iname_h) $(isave) $(iutil_h) \
ea5d11
+ $(store_h) $(gxdevice_h) $(gsstate_h)
ea5d11
 	$(PSCC) $(PSO_)zdevice2.$(OBJ) $(C_) $(PSSRC)zdevice2.c
ea5d11
 
ea5d11
 $(PSOBJ)zmedia2.$(OBJ) : $(PSSRC)zmedia2.c $(OP) $(math__h) $(memory__h)\
ea5d11
diff --git a/psi/isave.h b/psi/isave.h
ea5d11
index f8a6f32..0914de1 100644
ea5d11
--- a/psi/isave.h
ea5d11
+++ b/psi/isave.h
ea5d11
@@ -122,4 +122,10 @@ int  font_restore(const alloc_save_t * save);
ea5d11
    express purpose of getting the library context. */
ea5d11
 gs_memory_t *gs_save_any_memory(const alloc_save_t *save);
ea5d11
 
ea5d11
+int
ea5d11
+restore_check_save(i_ctx_t *i_ctx_p, alloc_save_t **asave);
ea5d11
+
ea5d11
+int
ea5d11
+dorestore(i_ctx_t *i_ctx_p, alloc_save_t *asave);
ea5d11
+
ea5d11
 #endif /* isave_INCLUDED */
ea5d11
diff --git a/psi/zdevice2.c b/psi/zdevice2.c
ea5d11
index f391da1..2ed2a3b 100644
ea5d11
--- a/psi/zdevice2.c
ea5d11
+++ b/psi/zdevice2.c
ea5d11
@@ -26,6 +26,7 @@
ea5d11
 #include "igstate.h"
ea5d11
 #include "iname.h"
ea5d11
 #include "iutil.h"
ea5d11
+#include "isave.h"
ea5d11
 #include "store.h"
ea5d11
 #include "gxdevice.h"
ea5d11
 #include "gsstate.h"
ea5d11
@@ -306,11 +307,25 @@ z2grestoreall(i_ctx_t *i_ctx_p)
ea5d11
     }
ea5d11
     return 0;
ea5d11
 }
ea5d11
-
ea5d11
+/* This is the Level 2+ variant of restore - which adds restoring
ea5d11
+   of the page device to the Level 1 variant in zvmem.c.
ea5d11
+   Previous this restored the device state before calling zrestore.c
ea5d11
+   which validated operands etc, meaning a restore could error out
ea5d11
+   partially complete.
ea5d11
+   The operand checking, and actual VM restore are now in two functions
ea5d11
+   so they can called separately thus, here, we can do as much
ea5d11
+   checking as possible, before embarking on actual changes
ea5d11
+ */
ea5d11
 /* <save> restore - */
ea5d11
 static int
ea5d11
 z2restore(i_ctx_t *i_ctx_p)
ea5d11
 {
ea5d11
+    alloc_save_t *asave;
ea5d11
+    bool saveLockSafety = gs_currentdevice_inline(igs)->LockSafetyParams;
ea5d11
+    int code = restore_check_save(i_ctx_p, &asave);
ea5d11
+
ea5d11
+    if (code < 0) return code;
ea5d11
+
ea5d11
     while (gs_state_saved(gs_state_saved(igs))) {
ea5d11
         if (restore_page_device(igs, gs_state_saved(igs)))
ea5d11
             return push_callout(i_ctx_p, "%restore1pagedevice");
ea5d11
@@ -318,7 +333,20 @@ z2restore(i_ctx_t *i_ctx_p)
ea5d11
     }
ea5d11
     if (restore_page_device(igs, gs_state_saved(igs)))
ea5d11
         return push_callout(i_ctx_p, "%restorepagedevice");
ea5d11
-    return zrestore(i_ctx_p);
ea5d11
+
ea5d11
+    code = dorestore(i_ctx_p, asave);
ea5d11
+
ea5d11
+    if (code < 0) {
ea5d11
+        /* An error here is basically fatal, but....
ea5d11
+           restore_page_device() has to set LockSafetyParams false so it can
ea5d11
+           configure the restored device correctly - in normal operation, that
ea5d11
+           gets reset by that configuration. If we hit an error, though, that
ea5d11
+           may not happen -  at least ensure we keep the setting through the
ea5d11
+           error.
ea5d11
+         */
ea5d11
+        gs_currentdevice_inline(igs)->LockSafetyParams = saveLockSafety;
ea5d11
+    }
ea5d11
+    return code;
ea5d11
 }
ea5d11
 
ea5d11
 /* <gstate> setgstate - */
ea5d11
diff --git a/psi/zvmem.c b/psi/zvmem.c
ea5d11
index 4ad8f9a..d8de39d 100644
ea5d11
--- a/psi/zvmem.c
ea5d11
+++ b/psi/zvmem.c
ea5d11
@@ -104,19 +104,18 @@ zsave(i_ctx_t *i_ctx_p)
ea5d11
 static int restore_check_operand(os_ptr, alloc_save_t **, gs_dual_memory_t *);
ea5d11
 static int restore_check_stack(const i_ctx_t *i_ctx_p, const ref_stack_t *, const alloc_save_t *, bool);
ea5d11
 static void restore_fix_stack(i_ctx_t *i_ctx_p, ref_stack_t *, const alloc_save_t *, bool);
ea5d11
+
ea5d11
+/* Do as many up front checks of the save object as we reasonably can */
ea5d11
 int
ea5d11
-zrestore(i_ctx_t *i_ctx_p)
ea5d11
+restore_check_save(i_ctx_t *i_ctx_p, alloc_save_t **asave)
ea5d11
 {
ea5d11
     os_ptr op = osp;
ea5d11
-    alloc_save_t *asave;
ea5d11
-    bool last;
ea5d11
-    vm_save_t *vmsave;
ea5d11
-    int code = restore_check_operand(op, &asave, idmemory);
ea5d11
+    int code = restore_check_operand(op, asave, idmemory);
ea5d11
 
ea5d11
     if (code < 0)
ea5d11
         return code;
ea5d11
     if_debug2m('u', imemory, "[u]vmrestore 0x%lx, id = %lu\n",
ea5d11
-               (ulong) alloc_save_client_data(asave),
ea5d11
+               (ulong) alloc_save_client_data(*asave),
ea5d11
                (ulong) op->value.saveid);
ea5d11
     if (I_VALIDATE_BEFORE_RESTORE)
ea5d11
         ivalidate_clean_spaces(i_ctx_p);
ea5d11
@@ -125,14 +124,37 @@ zrestore(i_ctx_t *i_ctx_p)
ea5d11
     {
ea5d11
         int code;
ea5d11
 
ea5d11
-        if ((code = restore_check_stack(i_ctx_p, &o_stack, asave, false)) < 0 ||
ea5d11
-            (code = restore_check_stack(i_ctx_p, &e_stack, asave, true)) < 0 ||
ea5d11
-            (code = restore_check_stack(i_ctx_p, &d_stack, asave, false)) < 0
ea5d11
+        if ((code = restore_check_stack(i_ctx_p, &o_stack, *asave, false)) < 0 ||
ea5d11
+            (code = restore_check_stack(i_ctx_p, &e_stack, *asave, true)) < 0 ||
ea5d11
+            (code = restore_check_stack(i_ctx_p, &d_stack, *asave, false)) < 0
ea5d11
             ) {
ea5d11
             osp++;
ea5d11
             return code;
ea5d11
         }
ea5d11
     }
ea5d11
+    osp++;
ea5d11
+    return 0;
ea5d11
+}
ea5d11
+
ea5d11
+/* the semantics of restore differ slightly between Level 1 and
ea5d11
+   Level 2 and later - the latter includes restoring the device
ea5d11
+   state (whilst Level 1 didn't have "page devices" as such).
ea5d11
+   Hence we have two restore operators - one here (Level 1)
ea5d11
+   and one in zdevice2.c (Level 2+). For that reason, the
ea5d11
+   operand checking and guts of the restore operation are
ea5d11
+   separated so both implementations can use them to best
ea5d11
+   effect.
ea5d11
+ */
ea5d11
+int
ea5d11
+dorestore(i_ctx_t *i_ctx_p, alloc_save_t *asave)
ea5d11
+{
ea5d11
+    os_ptr op = osp;
ea5d11
+    bool last;
ea5d11
+    vm_save_t *vmsave;
ea5d11
+    int code;
ea5d11
+
ea5d11
+    osp--;
ea5d11
+
ea5d11
     /* Reset l_new in all stack entries if the new save level is zero. */
ea5d11
     /* Also do some special fixing on the e-stack. */
ea5d11
     restore_fix_stack(i_ctx_p, &o_stack, asave, false);
ea5d11
@@ -175,9 +197,24 @@ zrestore(i_ctx_t *i_ctx_p)
ea5d11
     /* cause an 'invalidaccess' in setuserparams. Temporarily set     */
ea5d11
     /* LockFilePermissions false until the gs_lev2.ps can do a        */
ea5d11
     /* setuserparams from the restored userparam dictionary.          */
ea5d11
+    /* NOTE: This is safe to do here, since the restore has           */
ea5d11
+    /* successfully completed - this should never come before any     */
ea5d11
+    /* operation that can trigger an error                            */
ea5d11
     i_ctx_p->LockFilePermissions = false;
ea5d11
     return 0;
ea5d11
 }
ea5d11
+
ea5d11
+int
ea5d11
+zrestore(i_ctx_t *i_ctx_p)
ea5d11
+{
ea5d11
+    alloc_save_t *asave;
ea5d11
+    int code = restore_check_save(i_ctx_p, &asave);
ea5d11
+    if (code < 0)
ea5d11
+        return code;
ea5d11
+
ea5d11
+    return dorestore(i_ctx_p, asave);
ea5d11
+}
ea5d11
+
ea5d11
 /* Check the operand of a restore. */
ea5d11
 static int
ea5d11
 restore_check_operand(os_ptr op, alloc_save_t ** pasave,
ea5d11
@@ -198,6 +235,7 @@ restore_check_operand(os_ptr op, alloc_save_t ** pasave,
ea5d11
     *pasave = asave;
ea5d11
     return 0;
ea5d11
 }
ea5d11
+
ea5d11
 /* Check a stack to make sure all its elements are older than a save. */
ea5d11
 static int
ea5d11
 restore_check_stack(const i_ctx_t *i_ctx_p, const ref_stack_t * pstack,
ea5d11
-- 
ea5d11
2.14.4
ea5d11