CVE-2019-14934: Out-of-bounds Write (CWE-787)
value: An issue was discovered in PDFResurrect before 0.18. pdf_load_pages_kids in pdf.c doesn't validate a certain size value, which leads to a malloc failure and out-of-bounds write.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2019-08-11T22:15Z
Repository: https://github.com/enferex/pdfresurrect
Language: C
Files changed: main.c, main.h, pdf.c
Commit date: 2019-08-10 14:48:05 -0700
Commit message: Zero and sanity check all dynamic allocs.

This addresses the memory issues in Issue #6 expressed in
calloc_some.pdf and malloc_some.pdf

Diff: diff --git a/main.c b/main.c
index 9cc40f5..bde5388 100644
--- a/main.c
+++ b/main.c
@@ -70,11 +70,11 @@ static void write_version(
     start = ftell(fp);
 
     /* Create file */
     if ((c = strstr(fname, ".pdf")))
       *c = '\0';
-    new_fname = malloc(strlen(fname) + strlen(dirname) + 16);
+    new_fname = safe_calloc(strlen(fname) + strlen(dirname) + 16);
     snprintf(new_fname, strlen(fname) + strlen(dirname) + 16,
              "%s/%s-version-%d.pdf", dirname, fname, xref->version);
 
     if (!(new_fp = fopen(new_fname, "w")))
     {
@@ -211,10 +211,27 @@ static pdf_t *init_pdf(FILE *fp, const char *name)
 
     return pdf;
 }
 
 
+void *safe_calloc(size_t size) {
+  void *addr;
+
+  if (!size)
+  {
+    ERR("Invalid allocation size.\n");
+    exit(EXIT_FAILURE);
+  }
+  if (!(addr = calloc(1, size)))
+  {
+      ERR("Failed to allocate requested number of bytes, out of memory?\n");
+      exit(EXIT_FAILURE);
+  }
+  return addr;
+}
+
+
 int main(int argc, char **argv)
 {
     int         i, n_valid, do_write, do_scrub;
     char       *c, *dname, *name;
     DIR        *dir;
@@ -293,11 +310,11 @@ int main(int argc, char **argv)
           name = c + 1;
 
         if ((c = strrchr(name, '.')))
           *c = '\0';
 
-        dname = malloc(strlen(name) + 16);
+        dname = safe_calloc(strlen(name) + 16);
         sprintf(dname, "%s-versions", name);
         if (!(dir = opendir(dname)))
           mkdir(dname, S_IRWXU);
         else
         {
diff --git a/main.h b/main.h
index 30b504c..c01f70d 100644
--- a/main.h
+++ b/main.h
@@ -38,7 +38,9 @@
 
 
 #define TAG "[pdfresurrect]"
 #define ERR(...) {fprintf(stderr, TAG" -- Error -- " __VA_ARGS__);}
 
+/* Returns a zero'd buffer of 'size' bytes or exits in failure. */
+extern void *safe_calloc(size_t bytes);
 
 #endif /* MAIN_H_INCLUDE */
diff --git a/pdf.c b/pdf.c
index 81a204b..6debe68 100644
--- a/pdf.c
+++ b/pdf.c
@@ -120,26 +120,26 @@ static int get_next_eof(FILE *fp);
 pdf_t *pdf_new(const char *name)
 {
     const char *n;
     pdf_t      *pdf;
    
-    pdf = calloc(1, sizeof(pdf_t));
+    pdf = safe_calloc(sizeof(pdf_t));
 
     if (name)
     {
         /* Just get the file name (not path) */
         if ((n = strrchr(name, '/')))
           ++n;
         else
           n = name;
 
-        pdf->name = malloc(strlen(n) + 1);
+        pdf->name = safe_calloc(strlen(n) + 1);
         strcpy(pdf->name, n);
     }
     else /* !name */
     {
-        pdf->name = malloc(strlen("Unknown") + 1);
+        pdf->name = safe_calloc(strlen("Unknown") + 1);
         strcpy(pdf->name, "Unknown");
     }
 
     return pdf;
 }
@@ -214,11 +214,11 @@ int pdf_load_xrefs(FILE *fp, pdf_t *pdf)
     if (!pdf->n_xrefs)
       return 0;
 
     /* Load in the start/end positions */
     fseek(fp, 0, SEEK_SET);
-    pdf->xrefs = calloc(1, sizeof(xref_t) * pdf->n_xrefs);
+    pdf->xrefs = safe_calloc(sizeof(xref_t) * pdf->n_xrefs);
     ver = 1;
     for (i=0; i<pdf->n_xrefs; i++)
     {
         /* Seek to %%EOF */
         if ((pos = get_next_eof(fp)) < 0)
@@ -312,11 +312,11 @@ void pdf_load_pages_kids(FILE *fp, pdf_t *pdf)
             while (SAFE_F(fp, (fgetc(fp) != 't')))
                 ; /* Iterate to trailer */
 
             /* Get root catalog */
             sz = pdf->xrefs[i].end - ftell(fp);
-            buf = malloc(sz + 1);
+            buf = safe_calloc(sz + 1);
             SAFE_E(fread(buf, 1, sz, fp), sz, "Failed to load /Root.\n");
             buf[sz] = '\0';
             if (!(c = strstr(buf, "/Root")))
             {
                 free(buf);
@@ -442,11 +442,11 @@ void pdf_summarize(
     dst = NULL;
     dst_name = NULL;
 
     if (name)
     {
-        dst_name = malloc(strlen(name) * 2 + 16);
+        dst_name = safe_calloc(strlen(name) * 2 + 16);
         sprintf(dst_name, "%s/%s", name, name);
 
         if ((c = strrchr(dst_name, '.')) && (strncmp(c, ".pdf", 4) == 0))
           *c = '\0';
 
@@ -641,11 +641,11 @@ static void load_xref_from_plaintext(FILE *fp, xref_t *xref)
       else
         SAFE_E(fseek(fp, --pos, SEEK_SET), 0, "Failed seek to xref /Size.\n");
 
     SAFE_E(fread(buf, 1, 21, fp), 21, "Failed to load entry Size string.\n");
     xref->n_entries = atoi(buf + strlen("ize "));
-    xref->entries = calloc(1, xref->n_entries * sizeof(struct _xref_entry));
+    xref->entries = safe_calloc(xref->n_entries * sizeof(struct _xref_entry));
 
     /* Load entry data */
     obj_id = 0;
     fseek(fp, xref->start + strlen("xref"), SEEK_SET);
     added_entries = 0;
@@ -808,11 +808,11 @@ static pdf_creator_t *new_creator(int *n_elements)
         {"CreationDate", ""},
         {"ModDate",      ""},
         {"Trapped",      ""},
     };
 
-    daddy = malloc(sizeof(creator_template));
+    daddy = safe_calloc(sizeof(creator_template));
     memcpy(daddy, creator_template, sizeof(creator_template));
 
     if (n_elements)
       *n_elements = sizeof(creator_template) / sizeof(creator_template[0]);
 
@@ -1097,12 +1097,11 @@ static char *get_object(
 
     /* Initial allocate */
     obj_sz = 0;    /* Bytes in object */
     total_sz = 0;  /* Bytes read in   */
     n_blks = 1;
-    data = malloc(blk_sz * n_blks);
-    memset(data, 0, blk_sz * n_blks);
+    data = safe_calloc(blk_sz * n_blks);
 
     /* Suck in data */
     stream = 0;
     while ((read_sz = fread(data+total_sz, 1, blk_sz-1, fp)) && !ferror(fp))
     {
@@ -1264,22 +1263,16 @@ static int get_page(int obj_id, const xref_t *xref)
 */
 
 
 static char *get_header(FILE *fp)
 {
-    long start;
-
     /* First 1024 bytes of doc must be header (1.7 spec pg 1102) */
-    char *header;
-
-    header = calloc(1, 1024);
-    
-    start = ftell(fp);
+    char *header = safe_calloc(1024);
+    long start = ftell(fp);
     fseek(fp, 0, SEEK_SET);
     SAFE_E(fread(header, 1, 1023, fp), 1023, "Failed to load PDF header.\n");
     fseek(fp, start, SEEK_SET);
-    
     return header;
 }
 
 
 static char *decode_text_string(const char *str, size_t str_len)
@@ -1290,11 +1283,11 @@ static char *decode_text_string(const char *str, size_t str_len)
     is_hex = is_utf16be = idx = ascii_idx = 0;
 
     /* Regular encoding */
     if (str[0] == '(')
     {
-        ascii = malloc(strlen(str) + 1);
+        ascii = safe_calloc(strlen(str) + 1);
         strncpy(ascii, str, strlen(str) + 1);
         return ascii;
     }
     else if (str[0] == '<')
     {
@@ -1312,11 +1305,11 @@ static char *decode_text_string(const char *str, size_t str_len)
     }
     else
       return NULL;
 
     /* Now decode as hex */
-    ascii = malloc(str_len);
+    ascii = safe_calloc(str_len);
     for ( ; idx<str_len; ++idx)
     {
         hex_buf[0] = str[idx++];
         hex_buf[1] = str[idx++];
         hex_buf[2] = str[idx++];

Vulnerable code:     new_fname = malloc(strlen(fname) + strlen(dirname) + 16);
        dname = malloc(strlen(name) + 16);
    pdf = calloc(1, sizeof(pdf_t));
        pdf->name = malloc(strlen(n) + 1);
        pdf->name = malloc(strlen("Unknown") + 1);
    pdf->xrefs = calloc(1, sizeof(xref_t) * pdf->n_xrefs);
            buf = malloc(sz + 1);
        dst_name = malloc(strlen(name) * 2 + 16);
    xref->entries = calloc(1, xref->n_entries * sizeof(struct _xref_entry));
    daddy = malloc(sizeof(creator_template));
    data = malloc(blk_sz * n_blks);
    memset(data, 0, blk_sz * n_blks);
    long start;

    char *header;

    header = calloc(1, 1024);
    
    start = ftell(fp);
    
        ascii = malloc(strlen(str) + 1);
    ascii = malloc(str_len);
Fixed code:     new_fname = safe_calloc(strlen(fname) + strlen(dirname) + 16);
void *safe_calloc(size_t size) {
  void *addr;

  if (!size)
  {
    ERR("Invalid allocation size.\n");
    exit(EXIT_FAILURE);
  }
  if (!(addr = calloc(1, size)))
  {
      ERR("Failed to allocate requested number of bytes, out of memory?\n");
      exit(EXIT_FAILURE);
  }
  return addr;
}


        dname = safe_calloc(strlen(name) + 16);
/* Returns a zero'd buffer of 'size' bytes or exits in failure. */
extern void *safe_calloc(size_t bytes);
    pdf = safe_calloc(sizeof(pdf_t));
        pdf->name = safe_calloc(strlen(n) + 1);
        pdf->name = safe_calloc(strlen("Unknown") + 1);
    pdf->xrefs = safe_calloc(sizeof(xref_t) * pdf->n_xrefs);
            buf = safe_calloc(sz + 1);
        dst_name = safe_calloc(strlen(name) * 2 + 16);
    xref->entries = safe_calloc(xref->n_entries * sizeof(struct _xref_entry));
    daddy = safe_calloc(sizeof(creator_template));
    data = safe_calloc(blk_sz * n_blks);
    char *header = safe_calloc(1024);
    long start = ftell(fp);
        ascii = safe_calloc(strlen(str) + 1);
    ascii = safe_calloc(str_len);