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"); }