Subject: [bug#32663] [PATCH 2/2] gnu: ghostscript: Update replacement to 9.24 [security fixes].

From: Marius Bakke <[email-masked]>
Date: Sat, 8 Sep 2018 13:10:58 +0200

The following CVEs are fixed with this release: CVE-2018-15908,
CVE-2018-15909, CVE-2018-15910, CVE-2018-15911, CVE-2018-16509,
CVE-2018-16510, CVE-2018-16511, CVE-2018-16513, CVE-2018-16539,
CVE-2018-16540, CVE-2018-16541, CVE-2018-16542, CVE-2018-16543.

* gnu/packages/patches/ghostscript-CVE-2018-10194.patch: Delete file.
* gnu/packages/patches/ghostscript-CVE-2018-16509.patch: New file.
* gnu/local.mk (dist_patch_DATA): Adjust accordingly.
* gnu/packages/ghostscript.scm (ghostscript/fixed): Update to 9.24.
[source](patches): Remove 'ghostscript-CVE-2018-10194.patch' and
'ghostscript-runpath.patch'.  Add 'ghostscript-CVE-2018-16509.patch'.
[arguments]: Add LDFLAGS to #:configure-flags, and a phase to create output
directory.
---
 gnu/local.mk                                  |   2 +-
 gnu/packages/ghostscript.scm                  |  36 +++-
 .../patches/ghostscript-CVE-2018-10194.patch  |  52 -----
 .../patches/ghostscript-CVE-2018-16509.patch  | 193 ++++++++++++++++++
 4 files changed, 227 insertions(+), 56 deletions(-)
 delete mode 100644 gnu/packages/patches/ghostscript-CVE-2018-10194.patch
 create mode 100644 gnu/packages/patches/ghostscript-CVE-2018-16509.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index 1924ae946..617af81a8 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -726,7 +726,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/geoclue-config.patch			\
   %D%/packages/patches/ghc-8.0-fall-back-to-madv_dontneed.patch \
   %D%/packages/patches/ghc-dont-pass-linker-flags-via-response-files.patch	\
-  %D%/packages/patches/ghostscript-CVE-2018-10194.patch		\
+  %D%/packages/patches/ghostscript-CVE-2018-16509.patch		\
   %D%/packages/patches/ghostscript-no-header-id.patch		\
   %D%/packages/patches/ghostscript-no-header-uuid.patch		\
   %D%/packages/patches/ghostscript-no-header-creationdate.patch \
diff --git a/gnu/packages/ghostscript.scm b/gnu/packages/ghostscript.scm
index 1240b1dc1..a4f756bb7 100644
--- a/gnu/packages/ghostscript.scm
+++ b/gnu/packages/ghostscript.scm
@@ -7,6 +7,7 @@
 ;;; Copyright © 2017 Efraim Flashner <[email-masked]>
 ;;; Copyright © 2017 Leo Famulari <[email-masked]>
 ;;; Copyright © 2018 Tobias Geerinckx-Rice <[email-masked]>
+;;; Copyright © 2018 Marius Bakke <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -38,8 +39,10 @@
   #:use-module ((guix licenses) #:prefix license:)
   #:use-module (guix packages)
   #:use-module (guix download)
+  #:use-module (guix utils)
   #:use-module (guix build-system gnu)
-  #:use-module (guix build-system trivial))
+  #:use-module (guix build-system trivial)
+  #:use-module (srfi srfi-1))
 
 (define-public lcms
   (package
@@ -255,11 +258,38 @@ output file formats and printers.")
   (hidden-package
     (package
       (inherit ghostscript)
+      (version "9.24")
       (source
         (origin
           (inherit (package-source ghostscript))
-          (patches (append (origin-patches (package-source ghostscript))
-                           (search-patches "ghostscript-CVE-2018-10194.patch"))))))))
+          (uri (string-append "https://github.com/ArtifexSoftware/"
+                              "ghostpdl-downloads/releases/download/gs"
+                              (string-delete #\. version)
+                              "/ghostscript-" version ".tar.xz"))
+          (sha256
+           (base32
+            "1mk922rnml93w2g42yxiyn8xqanc50cm65irrgh0b6lp4kgifjfl"))
+          (patches (search-patches "ghostscript-CVE-2018-16509.patch"
+                                   "ghostscript-no-header-creationdate.patch"
+                                   "ghostscript-no-header-id.patch"
+                                   "ghostscript-no-header-uuid.patch"))))
+      (arguments
+       (substitute-keyword-arguments (package-arguments ghostscript)
+         ((#:configure-flags flags)
+          ;; Notice that we removed the 'ghostscript-runpath' patch above.
+          ;; The reason is that it conflicts with an upstream change that
+          ;; takes LDFLAGS into account.
+          `(cons (string-append "LDFLAGS=-Wl,-rpath="
+                                (assoc-ref %outputs "out") "/lib")
+                 ,flags))
+         ((#:phases phases)
+          `(modify-phases ,phases
+             (add-before 'configure 'create-output-directory
+               (lambda* (#:key outputs #:allow-other-keys)
+                 ;; Unfortunately the configure script refuses to function if
+                 ;; the directory specified as -rpath does not already exist.
+                 (mkdir-p (string-append (assoc-ref outputs "out") "/lib"))
+                 #t)))))))))
 
 (define-public ghostscript/x
   (package/inherit ghostscript
diff --git a/gnu/packages/patches/ghostscript-CVE-2018-10194.patch b/gnu/packages/patches/ghostscript-CVE-2018-10194.patch
deleted file mode 100644
index 242e57c27..000000000
--- a/gnu/packages/patches/ghostscript-CVE-2018-10194.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-Fix CVE-2018-10194:
-
-https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-10194
-https://bugs.ghostscript.com/show_bug.cgi?id=699255
-
-Patch copied from upstream source repository:
-
-https://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=39b1e54b2968620723bf32e96764c88797714879
-
-From 39b1e54b2968620723bf32e96764c88797714879 Mon Sep 17 00:00:00 2001
-From: Ken Sharp <[email-masked]>
-Date: Wed, 18 Apr 2018 15:46:32 +0100
-Subject: [PATCH] pdfwrite - Guard against trying to output an infinite number
-
-Bug #699255 " Buffer overflow on pprintg1 due to mishandle postscript file data to pdf"
-
-The file uses an enormous parameter to xyxhow, causing an overflow in
-the calculation of text positioning (value > 1e39).
-
-Since this is basically a nonsense value, and PostScript only supports
-real values up to 1e38, this patch follows the same approach as for
-a degenerate CTM, and treats it as 0.
-
-Adobe Acrobat Distiller throws a limitcheck error, so we could do that
-instead if this approach proves to be a problem.
----
- devices/vector/gdevpdts.c | 7 ++++++-
- 1 file changed, 6 insertions(+), 1 deletion(-)
-
-diff --git a/devices/vector/gdevpdts.c b/devices/vector/gdevpdts.c
-index 848ad781f..172fe6bc3 100644
---- a/devices/vector/gdevpdts.c
-+++ b/devices/vector/gdevpdts.c
-@@ -103,9 +103,14 @@ append_text_move(pdf_text_state_t *pts, double dw)
- static int
- set_text_distance(gs_point *pdist, double dx, double dy, const gs_matrix *pmat)
- {
--    int code = gs_distance_transform_inverse(dx, dy, pmat, pdist);
-+    int code;
-     double rounded;
- 
-+    if (dx > 1e38 || dy > 1e38)
-+        code = gs_error_undefinedresult;
-+    else
-+        code = gs_distance_transform_inverse(dx, dy, pmat, pdist);
-+
-     if (code == gs_error_undefinedresult) {
-         /* The CTM is degenerate.
-            Can't know the distance in user space.
--- 
-2.18.0
-
diff --git a/gnu/packages/patches/ghostscript-CVE-2018-16509.patch b/gnu/packages/patches/ghostscript-CVE-2018-16509.patch
new file mode 100644
index 000000000..50ffa3cb9
--- /dev/null
+++ b/gnu/packages/patches/ghostscript-CVE-2018-16509.patch
@@ -0,0 +1,193 @@
+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");
+ }
-- 
2.18.0