CVE-2014-9765: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: Buffer overflow in the main_get_appheader function in xdelta3-main.h in xdelta3 before 3.0.9 allows remote attackers to execute arbitrary code via a crafted input file.
CWE description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
CVSS v3 base score: 8.8
Published: 2016-04-19T21:59Z
Repository: https://github.com/jmacd/xdelta-devel
Language: C
Files changed: xdelta3/xdelta3-main.h, xdelta3/xdelta3-test.h
Commit date: 2014-10-12 05:24:22 +0000
Commit message: Add appheader tests; fix buffer overflow in main_get_appheader
Diff: diff --git a/xdelta3/xdelta3-main.h b/xdelta3/xdelta3-main.h
index 090b7d9..5146b38 100644
--- a/xdelta3/xdelta3-main.h
+++ b/xdelta3/xdelta3-main.h
@@ -2808,18 +2808,19 @@ main_get_appheader (xd3_stream *stream, main_file *ifile,
   /* Ignore failure, it only means we haven't received a header yet. */
   if (ret != 0) { return; }
 
   if (appheadsz > 0)
     {
+      const int kMaxArgs = 4;
       char *start = (char*)apphead;
       char *slash;
       int   place = 0;
-      char *parsed[4];
+      char *parsed[kMaxArgs];
 
       memset (parsed, 0, sizeof (parsed));
 
-      while ((slash = strchr (start, '/')) != NULL)
+      while ((slash = strchr (start, '/')) != NULL && place < (kMaxArgs-1))
 	{
 	  *slash = 0;
 	  parsed[place++] = start;
 	  start = slash + 1;
 	}
diff --git a/xdelta3/xdelta3-test.h b/xdelta3/xdelta3-test.h
index e9848b6..0e10251 100644
--- a/xdelta3/xdelta3-test.h
+++ b/xdelta3/xdelta3-test.h
@@ -1,7 +1,7 @@
 /* xdelta 3 - delta compression tools and library Copyright (C) 2001,
- * 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012.  
+ * 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012.
  * Joshua P. MacDonald
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
@@ -52,11 +52,11 @@ void mt_init(mtrand *mt, uint32_t seed) {
     /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
     /* In the previous versions, MSBs of the seed affect   */
     /* only MSBs of the array mt[].                        */
     /* 2002/01/09 modified by Makoto Matsumoto             */
     mt->mt_buffer_[i] =
-	(1812433253UL * (mt->mt_buffer_[i-1] ^ 
+	(1812433253UL * (mt->mt_buffer_[i-1] ^
 			 (mt->mt_buffer_[i-1] >> 30)) + i);
   }
 }
 
 uint32_t mt_random (mtrand *mt) {
@@ -67,24 +67,24 @@ uint32_t mt_random (mtrand *mt) {
 
   if (mt->mt_index_ >= MT_LEN) {
     int kk;
 
     for (kk = 0; kk < MT_LEN - MT_IA; kk++) {
-      y = (mt->mt_buffer_[kk] & UPPER_MASK) | 
+      y = (mt->mt_buffer_[kk] & UPPER_MASK) |
 	(mt->mt_buffer_[kk + 1] & LOWER_MASK);
-      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + MT_IA] ^ 
+      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + MT_IA] ^
 	(y >> 1) ^ mag01[y & 0x1UL];
     }
     for (;kk < MT_LEN - 1; kk++) {
-      y = (mt->mt_buffer_[kk] & UPPER_MASK) | 
+      y = (mt->mt_buffer_[kk] & UPPER_MASK) |
 	(mt->mt_buffer_[kk + 1] & LOWER_MASK);
-      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + (MT_IA - MT_LEN)] ^ 
+      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + (MT_IA - MT_LEN)] ^
 	(y >> 1) ^ mag01[y & 0x1UL];
     }
-    y = (mt->mt_buffer_[MT_LEN - 1] & UPPER_MASK) | 
+    y = (mt->mt_buffer_[MT_LEN - 1] & UPPER_MASK) |
       (mt->mt_buffer_[0] & LOWER_MASK);
-    mt->mt_buffer_[MT_LEN - 1] = mt->mt_buffer_[MT_IA - 1] ^ 
+    mt->mt_buffer_[MT_LEN - 1] = mt->mt_buffer_[MT_IA - 1] ^
       (y >> 1) ^ mag01[y & 0x1UL];
     mt->mt_index_ = 0;
   }
 
   y = mt->mt_buffer_[mt->mt_index_++];
@@ -164,11 +164,11 @@ static int do_cmd (xd3_stream *stream, const char *buf)
 	}
       else
 	{
 	  stream->msg = "abnormal command termination";
 	}
-      return XD3_INTERNAL;
+      return ret;
     }
   return 0;
 }
 
 static int do_fail (xd3_stream *stream, const char *buf)
@@ -255,12 +255,14 @@ int test_setup (void)
 }
 
 static int
 test_make_inputs (xd3_stream *stream, xoff_t *ss_out, xoff_t *ts_out)
 {
-  usize_t ts = (mt_random (&static_mtrand) % TEST_FILE_MEAN) + TEST_FILE_MEAN / 2;
-  usize_t ss = (mt_random (&static_mtrand) % TEST_FILE_MEAN) + TEST_FILE_MEAN / 2;
+  usize_t ts = (mt_random (&static_mtrand) % TEST_FILE_MEAN) +
+    TEST_FILE_MEAN / 2;
+  usize_t ss = (mt_random (&static_mtrand) % TEST_FILE_MEAN) +
+    TEST_FILE_MEAN / 2;
   uint8_t *buf = (uint8_t*) malloc (ts + ss), *sbuf = buf, *tbuf = buf + ss;
   usize_t sadd = 0, sadd_max = (usize_t)(ss * TEST_ADD_RATIO);
   FILE  *tf = NULL, *sf = NULL;
   usize_t i, j;
   int ret;
@@ -407,11 +409,11 @@ test_compare_files (const char* tgt, const char *rec)
 
       for (i = 0; i < oc; i += 1)
 	{
 	  if (obuf[i] != rbuf[i])
  	    {
-	      XPR(NT "byte %u (read %u @ %"Q"u) %d != %d\n", 
+	      XPR(NT "byte %u (read %u @ %"Q"u) %d != %d\n",
 		  (int)i, (int)oc, offset, obuf[i], rbuf[i]);
 	      diffs++;
 	      return XD3_INTERNAL;
 	    }
 	}
@@ -419,33 +421,39 @@ test_compare_files (const char* tgt, const char *rec)
       offset += oc;
     }
 
     fclose (orig);
     fclose (recons);
-    if (diffs != 0) 
+    if (diffs != 0)
       {
 	return XD3_INTERNAL;
       }
     return 0;
 }
 
 static int
-test_save_copy (const char *origname)
+test_copy_to (const char *from, const char *to)
 {
   char buf[TESTBUFSIZE];
   int ret;
 
-  snprintf_func (buf, TESTBUFSIZE, "cp -f %s %s", origname, TEST_COPY_FILE);
+  snprintf_func (buf, TESTBUFSIZE, "cp -f %s %s", from, to);
 
   if ((ret = system (buf)) != 0)
     {
       return XD3_INTERNAL;
     }
 
   return 0;
 }
 
+static int
+test_save_copy (const char *origname)
+{
+  return test_copy_to(origname, TEST_COPY_FILE);
+}
+
 static int
 test_file_size (const char* file, xoff_t *size)
 {
   struct stat sbuf;
   int ret;
@@ -497,11 +505,11 @@ test_read_integer_error (xd3_stream *stream, usize_t trunto, const char *msg)
  again:
 
   inp = buf->base;
   max = buf->base + buf->next - trunto;
 
-  if ((ret = xd3_read_uint32_t (stream, & inp, max, & rval)) != 
+  if ((ret = xd3_read_uint32_t (stream, & inp, max, & rval)) !=
       XD3_INVALID_INPUT ||
       !MSG_IS (msg))
     {
       ret = XD3_INTERNAL;
     }
@@ -1652,15 +1660,15 @@ test_compressed_stream_overflow (xd3_stream *stream, int ignore)
   uint8_t *buf;
 
   if ((buf = (uint8_t*) malloc (TWO_MEGS_AND_DELTA)) == NULL) { return ENOMEM; }
 
   memset (buf, 0, TWO_MEGS_AND_DELTA);
-  for (i = 0; i < (2 << 20); i += 256) 
+  for (i = 0; i < (2 << 20); i += 256)
     {
       int j;
       int off = mt_random(& static_mtrand) % 10;
-      for (j = 0; j < 256; j++) 
+      for (j = 0; j < 256; j++)
 	{
 	  buf[i + j] = j + off;
 	}
     }
 
@@ -1681,15 +1689,15 @@ test_compressed_stream_overflow (xd3_stream *stream, int ignore)
 	  goto fail;
 	}
     }
 
   /* Test transfer of exactly 32bits worth of data. */
-  if ((ret = test_streaming (stream, 
-			     buf, 
-			     buf + (1 << 20), 
-			     buf + (2 << 20), 
-			     1 << 12))) 
+  if ((ret = test_streaming (stream,
+			     buf,
+			     buf + (1 << 20),
+			     buf + (2 << 20),
+			     1 << 12)))
     {
       goto fail;
     }
  fail:
   free (buf);
@@ -1887,11 +1895,11 @@ test_recode_command2 (xd3_stream *stream, int has_source,
     {
       return ret;
     }
 
   /* First encode */
-  snprintf_func (ecmd, TESTBUFSIZE, "%s %s -f %s %s %s %s %s %s %s", 
+  snprintf_func (ecmd, TESTBUFSIZE, "%s %s -f %s %s %s %s %s %s %s",
 	    program_name, test_softcfg_str,
 	    has_adler32 ? "" : "-n ",
 	    has_apphead ? "-A=encode_apphead " : "-A= ",
 	    has_secondary ? "-S djw " : "-S none ",
 	    has_source ? "-s " : "",
@@ -1908,11 +1916,11 @@ test_recode_command2 (xd3_stream *stream, int has_source,
 
   /* Now recode */
   snprintf_func (recmd, TESTBUFSIZE,
 	    "%s recode %s -f %s %s %s %s %s", program_name, test_softcfg_str,
 	    recoded_adler32 ? "" : "-n ",
-	    !change_apphead ? "" : 
+	    !change_apphead ? "" :
 	        (recoded_apphead ? "-A=recode_apphead " : "-A= "),
 	    recoded_secondary ? "-S djw " : "-S none ",
 	    TEST_DELTA_FILE,
 	    TEST_COPY_FILE);
 
@@ -2359,10 +2367,80 @@ test_no_output (xd3_stream *stream, int ignore)
   if ((ret = do_cmd (stream, buf))) { return ret; }
   test_cleanup ();
   return 0;
 }
 
+/* This tests that the default appheader works */
+static int
+test_appheader (xd3_stream *stream, int ignore)
+{
+  int i;
+  int ret;
+  char buf[TESTBUFSIZE];
+  char bogus[TESTBUFSIZE];
+  xoff_t ssize, tsize;
+  test_setup ();
+
+  if ((ret = test_make_inputs (stream, &ssize, &tsize))) { return ret; }
+
+  snprintf_func (buf, TESTBUFSIZE, "%s -q -f -e -s %s %s %s", program_name,
+		 TEST_SOURCE_FILE, TEST_TARGET_FILE, TEST_DELTA_FILE);
+  if ((ret = do_cmd (stream, buf))) { return ret; }
+
+  if ((ret = test_copy_to (program_name, TEST_RECON2_FILE))) { return ret; }
+
+  snprintf_func (buf, TESTBUFSIZE, "chmod 0700 %s", TEST_RECON2_FILE);
+  if ((ret = do_cmd (stream, buf))) { return ret; }
+
+  if ((ret = test_save_copy (TEST_TARGET_FILE))) { return ret; }
+  if ((ret = test_copy_to (TEST_SOURCE_FILE, TEST_TARGET_FILE))) { return ret; }
+
+  if ((ret = test_compare_files (TEST_TARGET_FILE, TEST_COPY_FILE)) == 0)
+    {
+      return XD3_INVALID;  // I.e., files are different!
+    }
+
+  // Test that the target file is restored.
+  snprintf_func (buf, TESTBUFSIZE, "(cd /tmp && %s -q -f -d %s)",
+		 TEST_RECON2_FILE,
+		 TEST_DELTA_FILE);
+  if ((ret = do_cmd (stream, buf))) { return ret; }
+
+  if ((ret = test_compare_files (TEST_TARGET_FILE, TEST_COPY_FILE)) != 0)
+    {
+      return ret;
+    }
+
+  // Test a malicious string w/ entries > 4 in the appheader by having
+  // the encoder write it:
+  for (i = 0; i < TESTBUFSIZE / 4; ++i)
+    {
+      bogus[2*i] = 'G';
+      bogus[2*i+1] = '/';
+    }
+  bogus[TESTBUFSIZE/2-1] = 0;
+
+  snprintf_func (buf, TESTBUFSIZE, 
+		 "%s -q -f -A=%s -e -s %s %s %s", program_name, bogus,
+		 TEST_SOURCE_FILE, TEST_TARGET_FILE, TEST_DELTA_FILE);
+  if ((ret = do_cmd (stream, buf))) { return ret; }
+  // Then read it:
+  snprintf_func (buf, TESTBUFSIZE, "(cd /tmp && %s -q -f -d %s)",
+		 TEST_RECON2_FILE,
+		 TEST_DELTA_FILE);
+  if ((ret = do_cmd (stream, buf)) == 0) 
+    { 
+      return XD3_INVALID;  // Impossible
+    }
+  if (!WIFEXITED(ret))
+    {
+      return XD3_INVALID;  // Must have crashed!
+    }
+
+  return 0;
+}
+
 /***********************************************************************
  Source identical optimization
  ***********************************************************************/
 
 /* Computing a delta should be fastest when the two inputs are
@@ -2601,11 +2679,11 @@ test_string_matching (xd3_stream *stream, int ignore)
 	    case XD3_RUN: *rptr++ = 'R'; break;
 	    case XD3_CPY: *rptr++ = 'C'; break;
 	    default: CHECK(0);
 	    }
 
-	  snprintf_func (rptr, rbuf+TESTBUFSIZE-rptr, "%d/%d", 
+	  snprintf_func (rptr, rbuf+TESTBUFSIZE-rptr, "%d/%d",
 			 inst->pos, inst->size);
 	  rptr += strlen (rptr);
 
 	  if (inst->type == XD3_CPY)
 	    {
@@ -2846,10 +2924,11 @@ xd3_selftest (void)
 
 #if SHELL_TESTS
   DO_TEST (force_behavior, 0, 0);
   DO_TEST (stdout_behavior, 0, 0);
   DO_TEST (no_output, 0, 0);
+  DO_TEST (appheader, 0, 0);
   DO_TEST (command_line_arguments, 0, 0);
 
 #if EXTERNAL_COMPRESSION
   DO_TEST (source_decompression, 0, 0);
   DO_TEST (externally_compressed_io, 0, 0);

Vulnerable code:       char *parsed[4];
      while ((slash = strchr (start, '/')) != NULL)
 * 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012.  
	(1812433253UL * (mt->mt_buffer_[i-1] ^ 
      y = (mt->mt_buffer_[kk] & UPPER_MASK) | 
      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + MT_IA] ^ 
      y = (mt->mt_buffer_[kk] & UPPER_MASK) | 
      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + (MT_IA - MT_LEN)] ^ 
    y = (mt->mt_buffer_[MT_LEN - 1] & UPPER_MASK) | 
    mt->mt_buffer_[MT_LEN - 1] = mt->mt_buffer_[MT_IA - 1] ^ 
      return XD3_INTERNAL;
  usize_t ts = (mt_random (&static_mtrand) % TEST_FILE_MEAN) + TEST_FILE_MEAN / 2;
  usize_t ss = (mt_random (&static_mtrand) % TEST_FILE_MEAN) + TEST_FILE_MEAN / 2;
	      XPR(NT "byte %u (read %u @ %"Q"u) %d != %d\n", 
    if (diffs != 0) 
test_save_copy (const char *origname)
  snprintf_func (buf, TESTBUFSIZE, "cp -f %s %s", origname, TEST_COPY_FILE);
  if ((ret = xd3_read_uint32_t (stream, & inp, max, & rval)) != 
  for (i = 0; i < (2 << 20); i += 256) 
      for (j = 0; j < 256; j++) 
  if ((ret = test_streaming (stream, 
			     buf, 
			     buf + (1 << 20), 
			     buf + (2 << 20), 
			     1 << 12))) 
  snprintf_func (ecmd, TESTBUFSIZE, "%s %s -f %s %s %s %s %s %s %s", 
	    !change_apphead ? "" : 
	  snprintf_func (rptr, rbuf+TESTBUFSIZE-rptr, "%d/%d", 
Fixed code:       const int kMaxArgs = 4;
      char *parsed[kMaxArgs];
      while ((slash = strchr (start, '/')) != NULL && place < (kMaxArgs-1))
 * 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012.
	(1812433253UL * (mt->mt_buffer_[i-1] ^
      y = (mt->mt_buffer_[kk] & UPPER_MASK) |
      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + MT_IA] ^
      y = (mt->mt_buffer_[kk] & UPPER_MASK) |
      mt->mt_buffer_[kk] = mt->mt_buffer_[kk + (MT_IA - MT_LEN)] ^
    y = (mt->mt_buffer_[MT_LEN - 1] & UPPER_MASK) |
    mt->mt_buffer_[MT_LEN - 1] = mt->mt_buffer_[MT_IA - 1] ^
      return ret;
  usize_t ts = (mt_random (&static_mtrand) % TEST_FILE_MEAN) +
    TEST_FILE_MEAN / 2;
  usize_t ss = (mt_random (&static_mtrand) % TEST_FILE_MEAN) +
    TEST_FILE_MEAN / 2;
	      XPR(NT "byte %u (read %u @ %"Q"u) %d != %d\n",
    if (diffs != 0)
test_copy_to (const char *from, const char *to)
  snprintf_func (buf, TESTBUFSIZE, "cp -f %s %s", from, to);
static int
test_save_copy (const char *origname)
{
  return test_copy_to(origname, TEST_COPY_FILE);
}

  if ((ret = xd3_read_uint32_t (stream, & inp, max, & rval)) !=
  for (i = 0; i < (2 << 20); i += 256)
      for (j = 0; j < 256; j++)
  if ((ret = test_streaming (stream,
			     buf,
			     buf + (1 << 20),
			     buf + (2 << 20),
			     1 << 12)))
  snprintf_func (ecmd, TESTBUFSIZE, "%s %s -f %s %s %s %s %s %s %s",
	    !change_apphead ? "" :
/* This tests that the default appheader works */
static int
test_appheader (xd3_stream *stream, int ignore)
{
  int i;
  int ret;
  char buf[TESTBUFSIZE];
  char bogus[TESTBUFSIZE];
  xoff_t ssize, tsize;
  test_setup ();

  if ((ret = test_make_inputs (stream, &ssize, &tsize))) { return ret; }

  snprintf_func (buf, TESTBUFSIZE, "%s -q -f -e -s %s %s %s", program_name,
		 TEST_SOURCE_FILE, TEST_TARGET_FILE, TEST_DELTA_FILE);
  if ((ret = do_cmd (stream, buf))) { return ret; }

  if ((ret = test_copy_to (program_name, TEST_RECON2_FILE))) { return ret; }

  snprintf_func (buf, TESTBUFSIZE, "chmod 0700 %s", TEST_RECON2_FILE);
  if ((ret = do_cmd (stream, buf))) { return ret; }

  if ((ret = test_save_copy (TEST_TARGET_FILE))) { return ret; }
  if ((ret = test_copy_to (TEST_SOURCE_FILE, TEST_TARGET_FILE))) { return ret; }

  if ((ret = test_compare_files (TEST_TARGET_FILE, TEST_COPY_FILE)) == 0)
    {
      return XD3_INVALID;  // I.e., files are different!
    }

  // Test that the target file is restored.
  snprintf_func (buf, TESTBUFSIZE, "(cd /tmp && %s -q -f -d %s)",
		 TEST_RECON2_FILE,
		 TEST_DELTA_FILE);
  if ((ret = do_cmd (stream, buf))) { return ret; }

  if ((ret = test_compare_files (TEST_TARGET_FILE, TEST_COPY_FILE)) != 0)
    {
      return ret;
    }

  // Test a malicious string w/ entries > 4 in the appheader by having
  // the encoder write it:
  for (i = 0; i < TESTBUFSIZE / 4; ++i)
    {
      bogus[2*i] = 'G';
      bogus[2*i+1] = '/';
    }
  bogus[TESTBUFSIZE/2-1] = 0;

  snprintf_func (buf, TESTBUFSIZE, 
		 "%s -q -f -A=%s -e -s %s %s %s", program_name, bogus,
		 TEST_SOURCE_FILE, TEST_TARGET_FILE, TEST_DELTA_FILE);
  if ((ret = do_cmd (stream, buf))) { return ret; }
  // Then read it:
  snprintf_func (buf, TESTBUFSIZE, "(cd /tmp && %s -q -f -d %s)",
		 TEST_RECON2_FILE,
		 TEST_DELTA_FILE);
  if ((ret = do_cmd (stream, buf)) == 0) 
    { 
      return XD3_INVALID;  // Impossible
    }
  if (!WIFEXITED(ret))
    {
      return XD3_INVALID;  // Must have crashed!
    }

  return 0;
}

	  snprintf_func (rptr, rbuf+TESTBUFSIZE-rptr, "%d/%d",
  DO_TEST (appheader, 0, 0);