CVE-2017-8294: Out-of-bounds Read (CWE-125)
value: libyara/re.c in the regex component in YARA 3.5.0 allows remote attackers to cause a denial of service (out-of-bounds read and application crash) via a crafted rule that is mishandled in the yr_re_exec function.
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.5
Published: 2017-04-27T14:59Z
Repository: https://github.com/VirusTotal/yara
Language: C
Files changed: libyara/exec.c, libyara/include/yara/re.h, libyara/re.c, libyara/scan.c, tests/test-rules.c
Commit date: 2017-04-27 11:39:04 +0200
Commit message: Fix issue #646 (#648)

* Fix issue #646 and some edge cases with wide regexps using \b and \B

* Rename function IS_WORD_CHAR to _yr_re_is_word_char

Diff: diff --git a/libyara/exec.c b/libyara/exec.c
index 14128cf0..0b58999e 100644
--- a/libyara/exec.c
+++ b/libyara/exec.c
@@ -848,10 +848,11 @@ int yr_execute_code(
 
         r1.i = yr_re_exec(
           (uint8_t*) r2.re->code,
           (uint8_t*) r1.ss->c_string,
           r1.ss->length,
+          0,
           r2.re->flags | RE_FLAGS_SCAN,
           NULL,
           NULL) >= 0;
 
         push(r1);
diff --git a/libyara/include/yara/re.h b/libyara/include/yara/re.h
index a9645bb6..f2393064 100644
--- a/libyara/include/yara/re.h
+++ b/libyara/include/yara/re.h
@@ -92,11 +92,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define RE_FLAGS_EXHAUSTIVE             0x08
 #define RE_FLAGS_WIDE                   0x10
 #define RE_FLAGS_NO_CASE                0x20
 #define RE_FLAGS_SCAN                   0x40
 #define RE_FLAGS_DOT_ALL                0x80
-#define RE_FLAGS_NOT_AT_START          0x100
 #define RE_FLAGS_GREEDY                0x400
 #define RE_FLAGS_UNGREEDY              0x800
 
 
 typedef struct RE RE;
@@ -105,18 +104,10 @@ typedef struct RE_NODE RE_NODE;
 typedef struct RE_ERROR RE_ERROR;
 
 typedef uint8_t RE_SPLIT_ID_TYPE;
 
 
-#define CHAR_IN_CLASS(chr, cls)  \
-    ((cls)[(chr) / 8] & 1 << ((chr) % 8))
-
-
-#define IS_WORD_CHAR(chr) \
-    (isalnum(chr) || (chr) == '_')
-
-
 struct RE_NODE
 {
   int type;
 
   union {
@@ -211,20 +202,22 @@ void yr_re_node_destroy(
 
 
 int yr_re_exec(
     uint8_t* re_code,
     uint8_t* input,
-    size_t input_size,
+    size_t input_forwards_size,
+    size_t input_backwards_size,
     int flags,
     RE_MATCH_CALLBACK_FUNC callback,
     void* callback_args);
 
 
 int yr_re_fast_exec(
     uint8_t* re_code,
     uint8_t* input,
-    size_t input_size,
+    size_t input_forwards_size,
+    size_t input_backwards_size,
     int flags,
     RE_MATCH_CALLBACK_FUNC callback,
     void* callback_args);
 
 
diff --git a/libyara/re.c b/libyara/re.c
index dcb063c7..6257a816 100644
--- a/libyara/re.c
+++ b/libyara/re.c
@@ -138,10 +138,28 @@ typedef struct _RE_THREAD_STORAGE
 
 
 YR_THREAD_STORAGE_KEY thread_storage_key = 0;
 
 
+#define CHAR_IN_CLASS(chr, cls)  \
+    ((cls)[(chr) / 8] & 1 << ((chr) % 8))
+
+
+int _yr_re_is_word_char(
+    uint8_t* input,
+    int character_size)
+{
+  int result = ((isalnum(*input) || (*input) == '_'));
+
+  if (character_size == 2)
+    result = result && (*(input + 1) == 0);
+
+  return result;
+}
+
+
+
 //
 // yr_re_initialize
 //
 // Should be called by main thread before any other
 // function from this module.
@@ -358,10 +376,11 @@ int yr_re_match(
 {
   return yr_re_exec(
       re->code,
       (uint8_t*) target,
       strlen(target),
+      0,
       re->flags | RE_FLAGS_SCAN,
       NULL,
       NULL);
 }
 
@@ -1797,22 +1816,34 @@ int _yr_re_fiber_sync(
 
 
 //
 // yr_re_exec
 //
-// Executes a regular expression
+// Executes a regular expression. The specified regular expression will try to
+// match the data starting at the address specified by "input". The "input"
+// pointer can point to any address inside a memory buffer. Arguments
+// "input_forwards_size" and "input_backwards_size" indicate how many bytes
+// can be accesible starting at "input" and going forwards and backwards
+// respectively.
+//
+//   <--- input_backwards_size -->|<----------- input_forwards_size -------->
+//  |--------  memory buffer  -----------------------------------------------|
+//                                ^
+//                              input
 //
 // Args:
 //   uint8_t* re_code                 - Regexp code be executed
 //   uint8_t* input                   - Pointer to input data
-//   size_t input_size                - Input data size
+//   size_t input_forwards_size       - Number of accessible bytes starting at
+//                                      "input" and going forwards.
+//   size_t input_backwards_size      - Number of accessible bytes starting at
+//                                      "input" and going backwards
 //   int flags                        - Flags:
 //      RE_FLAGS_SCAN
 //      RE_FLAGS_BACKWARDS
 //      RE_FLAGS_EXHAUSTIVE
 //      RE_FLAGS_WIDE
-//      RE_FLAGS_NOT_AT_START
 //      RE_FLAGS_NO_CASE
 //      RE_FLAGS_DOT_ALL
 //   RE_MATCH_CALLBACK_FUNC callback  - Callback function
 //   void* callback_args              - Callback argument
 //
@@ -1823,14 +1854,16 @@ int _yr_re_fiber_sync(
 //      -2  Not enough memory
 //      -3  Too many matches
 //      -4  Too many fibers
 //      -5  Unknown fatal error
 
+
 int yr_re_exec(
     uint8_t* re_code,
     uint8_t* input_data,
-    size_t input_size,
+    size_t input_forwards_size,
+    size_t input_backwards_size,
     int flags,
     RE_MATCH_CALLBACK_FUNC callback,
     void* callback_args)
 {
   uint8_t* ip;
@@ -1856,22 +1889,27 @@ int yr_re_exec(
   #define ACTION_NONE       0
   #define ACTION_CONTINUE   1
   #define ACTION_KILL       2
   #define ACTION_KILL_TAIL  3
 
-  #define prolog if (bytes_matched >= max_bytes_matched) \
+  #define prolog { \
+      if ((bytes_matched >= max_bytes_matched) || \
+          (character_size == 2 && *(input + 1) != 0)) \
       { \
         action = ACTION_KILL; \
         break; \
-      }
+      } \
+    }
 
-  #define fail_if_error(e) switch (e) { \
+  #define fail_if_error(e) { \
+      switch (e) { \
         case ERROR_INSUFFICIENT_MEMORY: \
           return -2; \
         case ERROR_TOO_MANY_RE_FIBERS: \
           return -4; \
-      }
+      } \
+    }
 
   if (_yr_re_alloc_storage(&storage) != ERROR_SUCCESS)
     return -2;
 
   if (flags & RE_FLAGS_WIDE)
@@ -1882,18 +1920,21 @@ int yr_re_exec(
   input = input_data;
   input_incr = character_size;
 
   if (flags & RE_FLAGS_BACKWARDS)
   {
+    max_bytes_matched = (int) yr_min(input_backwards_size, RE_SCAN_LIMIT);
     input -= character_size;
     input_incr = -input_incr;
   }
-
-  max_bytes_matched = (int) yr_min(input_size, RE_SCAN_LIMIT);
+  else
+  {
+    max_bytes_matched = (int) yr_min(input_forwards_size, RE_SCAN_LIMIT);
+  }
 
   // Round down max_bytes_matched to a multiple of character_size, this way if
-  // character_size is 2 and input_size is odd we are ignoring the
+  // character_size is 2 and max_bytes_matched is odd we are ignoring the
   // extra byte which can't match anyways.
 
   max_bytes_matched = max_bytes_matched - max_bytes_matched % character_size;
   bytes_matched = 0;
 
@@ -1971,18 +2012,18 @@ int yr_re_exec(
           fiber->ip += 33;
           break;
 
         case RE_OPCODE_WORD_CHAR:
           prolog;
-          match = IS_WORD_CHAR(*input);
+          match = _yr_re_is_word_char(input, character_size);
           action = match ? ACTION_NONE : ACTION_KILL;
           fiber->ip += 1;
           break;
 
         case RE_OPCODE_NON_WORD_CHAR:
           prolog;
-          match = !IS_WORD_CHAR(*input);
+          match = !_yr_re_is_word_char(input, character_size);
           action = match ? ACTION_NONE : ACTION_KILL;
           fiber->ip += 1;
           break;
 
         case RE_OPCODE_SPACE:
@@ -2026,40 +2067,49 @@ int yr_re_exec(
           break;
 
         case RE_OPCODE_WORD_BOUNDARY:
         case RE_OPCODE_NON_WORD_BOUNDARY:
 
-          if (bytes_matched == 0 &&
-              !(flags & RE_FLAGS_NOT_AT_START) &&
-              !(flags & RE_FLAGS_BACKWARDS))
+          if (bytes_matched == 0 && input_backwards_size < character_size)
+          {
             match = TRUE;
+          }
           else if (bytes_matched >= max_bytes_matched)
+          {
             match = TRUE;
-          else if (IS_WORD_CHAR(*(input - input_incr)) != IS_WORD_CHAR(*input))
-            match = TRUE;
+          }
           else
-            match = FALSE;
+          {
+            assert(input <  input_data + input_forwards_size);
+            assert(input >= input_data - input_backwards_size);
+
+            assert(input - input_incr <  input_data + input_forwards_size);
+            assert(input - input_incr >= input_data - input_backwards_size);
+
+            match = _yr_re_is_word_char(input, character_size) != \
+                    _yr_re_is_word_char(input - input_incr, character_size);
+          }
 
           if (*ip == RE_OPCODE_NON_WORD_BOUNDARY)
             match = !match;
 
           action = match ? ACTION_CONTINUE : ACTION_KILL;
           fiber->ip += 1;
           break;
 
         case RE_OPCODE_MATCH_AT_START:
           if (flags & RE_FLAGS_BACKWARDS)
-            kill = input_size > (size_t) bytes_matched;
+            kill = input_backwards_size > (size_t) bytes_matched;
           else
-            kill = (flags & RE_FLAGS_NOT_AT_START) || (bytes_matched != 0);
+            kill = input_backwards_size > 0 || (bytes_matched != 0);
           action = kill ? ACTION_KILL : ACTION_CONTINUE;
           fiber->ip += 1;
           break;
 
         case RE_OPCODE_MATCH_AT_END:
           kill = flags & RE_FLAGS_BACKWARDS ||
-                 input_size > (size_t) bytes_matched;
+                 input_forwards_size > (size_t) bytes_matched;
           action = kill ? ACTION_KILL : ACTION_CONTINUE;
           fiber->ip += 1;
           break;
 
         case RE_OPCODE_MATCH:
@@ -2132,17 +2182,10 @@ int yr_re_exec(
           fail_if_error(error);
           fiber = next_fiber;
       }
     }
 
-    if (flags & RE_FLAGS_WIDE &&
-        bytes_matched < max_bytes_matched &&
-        *(input + 1) != 0)
-    {
-      _yr_re_fiber_kill_all(&fibers, &storage->fiber_pool);
-    }
-
     input += input_incr;
     bytes_matched += character_size;
 
     if (flags & RE_FLAGS_SCAN && bytes_matched < max_bytes_matched)
     {
@@ -2162,11 +2205,12 @@ int yr_re_exec(
 
 
 int yr_re_fast_exec(
     uint8_t* code,
     uint8_t* input_data,
-    size_t input_size,
+    size_t input_forwards_size,
+    size_t input_backwards_size,
     int flags,
     RE_MATCH_CALLBACK_FUNC callback,
     void* callback_args)
 {
   RE_REPEAT_ANY_ARGS* repeat_any_args;
@@ -2185,11 +2229,15 @@ int yr_re_fast_exec(
   int i;
   int stop;
   int input_incr;
   int sp = 0;
   int bytes_matched;
-  int max_bytes_matched = input_size;
+  int max_bytes_matched;
+
+  max_bytes_matched = flags & RE_FLAGS_BACKWARDS ?
+      input_backwards_size :
+      input_forwards_size;
 
   input_incr = flags & RE_FLAGS_BACKWARDS ? -1 : 1;
 
   if (flags & RE_FLAGS_BACKWARDS)
     input--;
diff --git a/libyara/scan.c b/libyara/scan.c
index 93fcf8d1..88a7d84a 100644
--- a/libyara/scan.c
+++ b/libyara/scan.c
@@ -526,11 +526,12 @@ int _yr_scan_match_callback(
 
 
 typedef int (*RE_EXEC_FUNC)(
     uint8_t* code,
     uint8_t* input,
-    size_t input_size,
+    size_t input_forwards_size,
+    size_t input_backwards_size,
     int flags,
     RE_MATCH_CALLBACK_FUNC callback,
     void* callback_args);
 
 
@@ -567,11 +568,12 @@ int _yr_scan_verify_re_match(
   {
     forward_matches = exec(
         ac_match->forward_code,
         data + offset,
         data_size - offset,
-        offset > 0 ? flags | RE_FLAGS_NOT_AT_START : flags,
+        offset,
+        flags,
         NULL,
         NULL);
   }
 
   if (STRING_IS_WIDE(ac_match->string) && forward_matches == -1)
@@ -579,11 +581,12 @@ int _yr_scan_verify_re_match(
     flags |= RE_FLAGS_WIDE;
     forward_matches = exec(
         ac_match->forward_code,
         data + offset,
         data_size - offset,
-        offset > 0 ? flags | RE_FLAGS_NOT_AT_START : flags,
+        offset,
+        flags,
         NULL,
         NULL);
   }
 
   switch(forward_matches)
@@ -614,10 +617,11 @@ int _yr_scan_verify_re_match(
   if (ac_match->backward_code != NULL)
   {
     backward_matches = exec(
         ac_match->backward_code,
         data + offset,
+        data_size - offset,
         offset,
         flags | RE_FLAGS_BACKWARDS | RE_FLAGS_EXHAUSTIVE,
         _yr_scan_match_callback,
         (void*) &callback_args);
 
diff --git a/tests/test-rules.c b/tests/test-rules.c
index c26a25f4..63433e08 100644
--- a/tests/test-rules.c
+++ b/tests/test-rules.c
@@ -899,10 +899,54 @@ void test_re()
 
   assert_true_rule_blob(
       "rule test { strings: $a = /a.{1,2}b/ wide condition: !a == 8 }",
       "a\0x\0x\0b\0");
 
+  assert_true_rule_blob(
+      "rule test { strings: $a = /\\babc/ wide condition: $a }",
+      "a\0b\0c\0");
+
+  assert_true_rule_blob(
+      "rule test { strings: $a = /\\babc/ wide condition: $a }",
+      "\0a\0b\0c\0");
+
+  assert_true_rule_blob(
+      "rule test { strings: $a = /\\babc/ wide condition: $a }",
+      "\ta\0b\0c\0");
+
+  assert_false_rule_blob(
+      "rule test { strings: $a = /\\babc/ wide condition: $a }",
+      "x\0a\0b\0c\0");
+
+  assert_true_rule_blob(
+      "rule test { strings: $a = /\\babc/ wide condition: $a }",
+      "x\ta\0b\0c\0");
+
+  assert_true_rule_blob(
+      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
+      "a\0b\0c\0");
+
+  assert_true_rule_blob(
+      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
+      "a\0b\0c\0\0");
+
+  assert_true_rule_blob(
+      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
+      "a\0b\0c\0\t");
+
+  assert_false_rule_blob(
+      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
+      "a\0b\0c\0x\0");
+
+  assert_true_rule_blob(
+      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
+      "a\0b\0c\0b\t");
+
+  assert_false_rule_blob(
+      "rule test { strings: $a = /\\b/ wide condition: $a }",
+      "abc");
+
   assert_regexp_syntax_error(")");
   assert_true_regexp("abc", "abc", "abc");
   assert_false_regexp("abc", "xbc");
   assert_false_regexp("abc", "axc");
   assert_false_regexp("abc", "abx");

Vulnerable code: #define RE_FLAGS_NOT_AT_START          0x100
#define CHAR_IN_CLASS(chr, cls)  \
    ((cls)[(chr) / 8] & 1 << ((chr) % 8))


#define IS_WORD_CHAR(chr) \
    (isalnum(chr) || (chr) == '_')


    size_t input_size,
    size_t input_size,
// Executes a regular expression
//   size_t input_size                - Input data size
//      RE_FLAGS_NOT_AT_START
    size_t input_size,
  #define prolog if (bytes_matched >= max_bytes_matched) \
      }
  #define fail_if_error(e) switch (e) { \
      }

  max_bytes_matched = (int) yr_min(input_size, RE_SCAN_LIMIT);
  // character_size is 2 and input_size is odd we are ignoring the
          match = IS_WORD_CHAR(*input);
          match = !IS_WORD_CHAR(*input);
          if (bytes_matched == 0 &&
              !(flags & RE_FLAGS_NOT_AT_START) &&
              !(flags & RE_FLAGS_BACKWARDS))
          else if (IS_WORD_CHAR(*(input - input_incr)) != IS_WORD_CHAR(*input))
            match = TRUE;
            match = FALSE;
            kill = input_size > (size_t) bytes_matched;
            kill = (flags & RE_FLAGS_NOT_AT_START) || (bytes_matched != 0);
                 input_size > (size_t) bytes_matched;
    if (flags & RE_FLAGS_WIDE &&
        bytes_matched < max_bytes_matched &&
        *(input + 1) != 0)
    {
      _yr_re_fiber_kill_all(&fibers, &storage->fiber_pool);
    }

    size_t input_size,
  int max_bytes_matched = input_size;
    size_t input_size,
        offset > 0 ? flags | RE_FLAGS_NOT_AT_START : flags,
        offset > 0 ? flags | RE_FLAGS_NOT_AT_START : flags,
Fixed code:           0,
    size_t input_forwards_size,
    size_t input_backwards_size,
    size_t input_forwards_size,
    size_t input_backwards_size,
#define CHAR_IN_CLASS(chr, cls)  \
    ((cls)[(chr) / 8] & 1 << ((chr) % 8))


int _yr_re_is_word_char(
    uint8_t* input,
    int character_size)
{
  int result = ((isalnum(*input) || (*input) == '_'));

  if (character_size == 2)
    result = result && (*(input + 1) == 0);

  return result;
}



      0,
// Executes a regular expression. The specified regular expression will try to
// match the data starting at the address specified by "input". The "input"
// pointer can point to any address inside a memory buffer. Arguments
// "input_forwards_size" and "input_backwards_size" indicate how many bytes
// can be accesible starting at "input" and going forwards and backwards
// respectively.
//
//   <--- input_backwards_size -->|<----------- input_forwards_size -------->
//  |--------  memory buffer  -----------------------------------------------|
//                                ^
//                              input
//   size_t input_forwards_size       - Number of accessible bytes starting at
//                                      "input" and going forwards.
//   size_t input_backwards_size      - Number of accessible bytes starting at
//                                      "input" and going backwards

    size_t input_forwards_size,
    size_t input_backwards_size,
  #define prolog { \
      if ((bytes_matched >= max_bytes_matched) || \
          (character_size == 2 && *(input + 1) != 0)) \
      } \
    }
  #define fail_if_error(e) { \
      switch (e) { \
      } \
    }
    max_bytes_matched = (int) yr_min(input_backwards_size, RE_SCAN_LIMIT);
  else
  {
    max_bytes_matched = (int) yr_min(input_forwards_size, RE_SCAN_LIMIT);
  }
  // character_size is 2 and max_bytes_matched is odd we are ignoring the
          match = _yr_re_is_word_char(input, character_size);
          match = !_yr_re_is_word_char(input, character_size);
          if (bytes_matched == 0 && input_backwards_size < character_size)
          {
          }
          {
          }
          {
            assert(input <  input_data + input_forwards_size);
            assert(input >= input_data - input_backwards_size);

            assert(input - input_incr <  input_data + input_forwards_size);
            assert(input - input_incr >= input_data - input_backwards_size);

            match = _yr_re_is_word_char(input, character_size) != \
                    _yr_re_is_word_char(input - input_incr, character_size);
          }
            kill = input_backwards_size > (size_t) bytes_matched;
            kill = input_backwards_size > 0 || (bytes_matched != 0);
                 input_forwards_size > (size_t) bytes_matched;
    size_t input_forwards_size,
    size_t input_backwards_size,
  int max_bytes_matched;

  max_bytes_matched = flags & RE_FLAGS_BACKWARDS ?
      input_backwards_size :
      input_forwards_size;
    size_t input_forwards_size,
    size_t input_backwards_size,
        offset,
        flags,
        offset,
        flags,
        data_size - offset,
  assert_true_rule_blob(
      "rule test { strings: $a = /\\babc/ wide condition: $a }",
      "a\0b\0c\0");

  assert_true_rule_blob(
      "rule test { strings: $a = /\\babc/ wide condition: $a }",
      "\0a\0b\0c\0");

  assert_true_rule_blob(
      "rule test { strings: $a = /\\babc/ wide condition: $a }",
      "\ta\0b\0c\0");

  assert_false_rule_blob(
      "rule test { strings: $a = /\\babc/ wide condition: $a }",
      "x\0a\0b\0c\0");

  assert_true_rule_blob(
      "rule test { strings: $a = /\\babc/ wide condition: $a }",
      "x\ta\0b\0c\0");

  assert_true_rule_blob(
      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
      "a\0b\0c\0");

  assert_true_rule_blob(
      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
      "a\0b\0c\0\0");

  assert_true_rule_blob(
      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
      "a\0b\0c\0\t");

  assert_false_rule_blob(
      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
      "a\0b\0c\0x\0");

  assert_true_rule_blob(
      "rule test { strings: $a = /abc\\b/ wide condition: $a }",
      "a\0b\0c\0b\t");

  assert_false_rule_blob(
      "rule test { strings: $a = /\\b/ wide condition: $a }",
      "abc");