CVE-2021-23191: NULL Pointer Dereference (CWE-476)
value: A security issue was found in htmldoc v1.9.12 and before. A NULL pointer dereference in the function image_load_jpeg() in image.cxx may result in denial of service.
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.8
Published: 2022-03-02T23:15Z
Repository: https://github.com/michaelrsweet/htmldoc
Language: Other
Files changed: CHANGES.md, htmldoc/file.c, htmldoc/image.cxx, htmldoc/ps-pdf.cxx
Commit date: 2021-04-01 09:37:58 -0400
Commit message: Fix JPEG error handling (Issue #415)

Diff: diff --git a/CHANGES.md b/CHANGES.md
index faeff3b4..224cbc30 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,8 +1,9 @@
 # Changes in HTMLDOC v1.9.12
 
 - Fixed a crash bug with "data:" URIs and EPUB output (Issue #410)
+- Fixed JPEG error handling (Issue #415)
 - Fixed crash bugs with bogus table attributes (Issue #416, Issue #417)
 - Fixed a crash bug with malformed URIs (Issue #418)
 - Fixed a crash bug with malformed GIF files (Issue #423)
 - Fixed some issues reported by Coverity.
 
diff --git a/htmldoc/file.c b/htmldoc/file.c
index f2f3822c..a0c37f98 100644
--- a/htmldoc/file.c
+++ b/htmldoc/file.c
@@ -1008,12 +1008,19 @@ file_rlookup(const char *filename)	/* I - Filename */
   int		i;			/* Looping var */
   cache_t	*wc;			/* Current cache file */
 
 
   for (i = web_files, wc = web_cache; i > 0; i --, wc ++)
+  {
     if (!strcmp(wc->name, filename))
-      return (wc->url);
+    {
+      if (!strncmp(wc->url, "data:", 5))
+        return ("data URL");
+      else
+        return (wc->url);
+    }
+  }
 
   return (filename);
 }
 
 
diff --git a/htmldoc/image.cxx b/htmldoc/image.cxx
index 8f530508..74abfacc 100644
--- a/htmldoc/image.cxx
+++ b/htmldoc/image.cxx
@@ -1334,10 +1334,19 @@ image_load_gif(image_t *img,	/* I - Image pointer */
     }
   }
 }
 
 
+typedef struct hd_jpeg_err_s	// JPEG error manager extension
+{
+  struct jpeg_error_mgr	jerr;	// JPEG error manager information
+  jmp_buf	retbuf;		// setjmp() return buffer
+  char		message[JMSG_LENGTH_MAX];
+				// Last error message
+} hd_jpeg_err_t;
+
+
 /*
  * 'image_load_jpeg()' - Load a JPEG image file.
  */
 
 static int			/* O - 0 = success, -1 = fail */
@@ -1345,18 +1354,25 @@ image_load_jpeg(image_t *img,	/* I - Image pointer */
                 FILE    *fp,	/* I - File to load from */
                 int     gray,	/* I - 0 = color, 1 = grayscale */
                 int     load_data)/* I - 1 = load image data, 0 = just info */
 {
   struct jpeg_decompress_struct	cinfo;		/* Decompressor info */
-  struct jpeg_error_mgr		jerr;		/* Error handler info */
-  JSAMPROW			row;		/* Sample row pointer */
+  hd_jpeg_err_t			jerr;		// JPEG error handler
+JSAMPROW			row;		/* Sample row pointer */
 
 
-  jpeg_std_error(&jerr);
-  jerr.error_exit = jpeg_error_handler;
+  jpeg_std_error(&jerr.jerr);
+  jerr.jerr.error_exit = jpeg_error_handler;
 
-  cinfo.err = &jerr;
+  if (setjmp(jerr.retbuf))
+  {
+    progress_error(HD_ERROR_BAD_FORMAT, "%s (%s)", jerr.message,  file_rlookup(img->filename));
+    jpeg_destroy_decompress(&cinfo);
+    return (-1);
+  }
+
+  cinfo.err = (struct jpeg_error_mgr *)&jerr;
   jpeg_create_decompress(&cinfo);
   jpeg_stdio_src(&cinfo, fp);
   jpeg_read_header(&cinfo, (boolean)1);
 
   cinfo.quantize_colors = FALSE;
@@ -1795,13 +1811,21 @@ image_unload(image_t *img)	// I - Image
 /*
  * 'jpeg_error_handler()' - Handle JPEG errors by not exiting.
  */
 
 static void
-jpeg_error_handler(j_common_ptr)
+jpeg_error_handler(j_common_ptr p)	// Common JPEG data
 {
-  return;
+  hd_jpeg_err_t	*jerr = (hd_jpeg_err_t *)p->err;
+					// JPEG error handler
+
+
+  // Save the error message in the string buffer...
+  (jerr->jerr.format_message)(p, jerr->message);
+
+  // Return to the point we called setjmp()...
+  longjmp(jerr->retbuf, 1);
 }
 
 
 /*
  * 'read_word()' - Read a 16-bit unsigned integer.
diff --git a/htmldoc/ps-pdf.cxx b/htmldoc/ps-pdf.cxx
index 85257e5f..e963959b 100644
--- a/htmldoc/ps-pdf.cxx
+++ b/htmldoc/ps-pdf.cxx
@@ -1402,10 +1402,12 @@ pspdf_prepare_page(int page)		/* I - Page number */
   char	page_text[64];			/* Page number text */
   int	top;				/* Top of page */
 
 
   DEBUG_printf(("pspdf_prepare_page(%d)\n", page));
+  if (page < 0 || page >= num_pages)
+    return;
 
  /*
   * Make a page number; use roman numerals for the table of contents
   * and arabic numbers for all others...
   */
@@ -12283,10 +12285,13 @@ write_trailer(FILE  *out,		/* I - Output file */
         i += chapter_ends[0] - chapter_starts[0] + 1;
       }
 
       for (j = 1; j <= TocDocCount; j ++)
       {
+        if (chapter_starts[j] < 0)
+          continue;
+
         page  = pages + chapter_starts[j];
 	start = chapter_starts[j] - chapter_starts[1] + 1;
 	type  = 'D';
 
         prefix[0] = '\0';

Vulnerable code:       return (wc->url);
  struct jpeg_error_mgr		jerr;		/* Error handler info */
  JSAMPROW			row;		/* Sample row pointer */
  jpeg_std_error(&jerr);
  jerr.error_exit = jpeg_error_handler;
  cinfo.err = &jerr;
jpeg_error_handler(j_common_ptr)
  return;
Fixed code: - Fixed JPEG error handling (Issue #415)
  {
    {
      if (!strncmp(wc->url, "data:", 5))
        return ("data URL");
      else
        return (wc->url);
    }
  }
typedef struct hd_jpeg_err_s	// JPEG error manager extension
{
  struct jpeg_error_mgr	jerr;	// JPEG error manager information
  jmp_buf	retbuf;		// setjmp() return buffer
  char		message[JMSG_LENGTH_MAX];
				// Last error message
} hd_jpeg_err_t;


  hd_jpeg_err_t			jerr;		// JPEG error handler
JSAMPROW			row;		/* Sample row pointer */
  jpeg_std_error(&jerr.jerr);
  jerr.jerr.error_exit = jpeg_error_handler;
  if (setjmp(jerr.retbuf))
  {
    progress_error(HD_ERROR_BAD_FORMAT, "%s (%s)", jerr.message,  file_rlookup(img->filename));
    jpeg_destroy_decompress(&cinfo);
    return (-1);
  }

  cinfo.err = (struct jpeg_error_mgr *)&jerr;
jpeg_error_handler(j_common_ptr p)	// Common JPEG data
  hd_jpeg_err_t	*jerr = (hd_jpeg_err_t *)p->err;
					// JPEG error handler


  // Save the error message in the string buffer...
  (jerr->jerr.format_message)(p, jerr->message);

  // Return to the point we called setjmp()...
  longjmp(jerr->retbuf, 1);
  if (page < 0 || page >= num_pages)
    return;
        if (chapter_starts[j] < 0)
          continue;