From 0cae41b23a9669e801211dd4cf97b6dadd6dbdd7 Mon Sep 17 00:00:00 2001
From: Ken Sharp <Ken.Sharp@artifex.com>
Date: Thu, 22 May 2025 12:25:41 +0100
Subject: [PATCH] pdfwrite - avoid buffer overrun

Bug #708539 "Buffer overflow in pdf_write_cmap"

The proposed fix in the report solves the buffer overrun, but does not
tackle a number of other problems.

This commit checks the result of stream_puts() in
pdf_write_cid_system_info_to_stream() and correctly signals an error to
the caller if that fails.

In pdf_write_cid_system_info we replace a (rather small!) fixed size
buffer with a dynamically allocated one using the lengths of the strings
which pdf_write_cid_system_info_to_stream() will write, and a small
fixed overhead to deal with the keys and initial byte '/'.

Because 'buf' is used in the stream 's', if it is too small to hold all
the CIDSystemInfo then we would get an error which was simply discarded
previously.

We now should avoid the potential error by ensuring the buffer is large
enough for all the information, and if we do get an error we no longer
silently ignore it, which would write an invalid PDF file.

CVE: CVE-2025-59798
Upstream-Status: Backport [https://github.com/ArtifexSoftware/ghostpdl/commit/0cae41b23a9669e801211dd4cf97b6dadd6dbdd7]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 devices/vector/gdevpdtw.c | 52 ++++++++++++++++++++++++++++++---------
 1 file changed, 41 insertions(+), 11 deletions(-)

diff --git a/devices/vector/gdevpdtw.c b/devices/vector/gdevpdtw.c
index ced15c9b2..fe24dd73a 100644
--- a/devices/vector/gdevpdtw.c
+++ b/devices/vector/gdevpdtw.c
@@ -703,7 +703,8 @@ static int
 pdf_write_cid_system_info_to_stream(gx_device_pdf *pdev, stream *s,
                           const gs_cid_system_info_t *pcidsi, gs_id object_id)
 {
-    byte *Registry, *Ordering;
+    byte *Registry = NULL, *Ordering = NULL;
+    int code = 0;
 
     Registry = gs_alloc_bytes(pdev->pdf_memory, pcidsi->Registry.size, "temporary buffer for Registry");
     if (!Registry)
@@ -734,14 +735,19 @@ pdf_write_cid_system_info_to_stream(gx_device_pdf *pdev, stream *s,
         }
         s_arcfour_process_buffer(&sarc4, Ordering, pcidsi->Ordering.size);
     }
-    stream_puts(s, "<<\n/Registry");
+    code = stream_puts(s, "<<\n/Registry");
+    if (code < 0)
+        goto error;
     s_write_ps_string(s, Registry, pcidsi->Registry.size, PRINT_HEX_NOT_OK);
-    stream_puts(s, "\n/Ordering");
+    code = stream_puts(s, "\n/Ordering");
+    if(code < 0)
+        goto error;
     s_write_ps_string(s, Ordering, pcidsi->Ordering.size, PRINT_HEX_NOT_OK);
+error:
     pprintd1(s, "\n/Supplement %d\n>>\n", pcidsi->Supplement);
     gs_free_object(pdev->pdf_memory, Registry, "free temporary Registry buffer");
     gs_free_object(pdev->pdf_memory, Ordering, "free temporary Ordering buffer");
-    return 0;
+    return code;
 }
 
 int
@@ -786,31 +792,55 @@ pdf_write_cmap(gx_device_pdf *pdev, const gs_cmap_t *pcmap,
     *ppres = writer.pres;
     writer.pres->where_used = 0; /* CMap isn't a PDF resource. */
     if (!pcmap->ToUnicode) {
-        byte buf[200];
+        byte *buf = NULL;
+        uint64_t buflen = 0;
         cos_dict_t *pcd = (cos_dict_t *)writer.pres->object;
         stream s;
 
+        /* We use 'buf' for the stream 's' below and that needs to have some extra
+         * space for the CIDSystemInfo. We also need an extra byte for the leading '/'
+         * 100 bytes is ample for the overhead.
+         */
+        buflen = pcmap->CIDSystemInfo->Registry.size + pcmap->CIDSystemInfo->Ordering.size + pcmap->CMapName.size + 100;
+        if (buflen > max_uint)
+            return_error(gs_error_limitcheck);
+
+        buf = gs_alloc_bytes(pdev->memory, buflen, "pdf_write_cmap");
+        if (buf == NULL)
+            return_error(gs_error_VMerror);
+
         code = cos_dict_put_c_key_int(pcd, "/WMode", pcmap->WMode);
-        if (code < 0)
+        if (code < 0) {
+            gs_free_object(pdev->memory, buf, "pdf_write_cmap");
             return code;
+        }
         buf[0] = '/';
         memcpy(buf + 1, pcmap->CMapName.data, pcmap->CMapName.size);
         code = cos_dict_put_c_key_string(pcd, "/CMapName",
                         buf, pcmap->CMapName.size + 1);
-        if (code < 0)
+        if (code < 0) {
+            gs_free_object(pdev->memory, buf, "pdf_write_cmap");
             return code;
+        }
         s_init(&s, pdev->memory);
-        swrite_string(&s, buf, sizeof(buf));
+        swrite_string(&s, buf, buflen);
         code = pdf_write_cid_system_info_to_stream(pdev, &s, pcmap->CIDSystemInfo, 0);
-        if (code < 0)
+        if (code < 0) {
+            gs_free_object(pdev->memory, buf, "pdf_write_cmap");
             return code;
+        }
         code = cos_dict_put_c_key_string(pcd, "/CIDSystemInfo",
                         buf, stell(&s));
-        if (code < 0)
+        if (code < 0) {
+            gs_free_object(pdev->memory, buf, "pdf_write_cmap");
             return code;
+        }
         code = cos_dict_put_string_copy(pcd, "/Type", "/CMap");
-        if (code < 0)
+        if (code < 0) {
+            gs_free_object(pdev->memory, buf, "pdf_write_cmap");
             return code;
+        }
+        gs_free_object(pdev->memory, buf, "pdf_write_cmap");
     }
     if (pcmap->CMapName.size == 0) {
         /* Create an arbitrary name (for ToUnicode CMap). */
