|
|
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 |
|