CVE-2016-10210: NULL Pointer Dereference (CWE-476)
value: libyara/lexer.l in YARA 3.5.0 allows remote attackers to cause a denial of service (NULL pointer dereference and application crash) via a crafted rule that is mishandled in the yy_get_next_buffer function.
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.5
Published: 2017-04-03T05:59Z
Repository: https://github.com/VirusTotal/yara
Language: C
Files changed: libyara/re_lexer.c, libyara/re_lexer.l, tests/test-rules.c
Commit date: 2017-01-17 17:07:02 +0100
Commit message: re_lexer: Make reading escape sequences more robust (#586)

* Add test for issue #503

* re_lexer: Make reading escape sequences more robust

This commit fixes parsing incomplete escape sequences at the end of a
regular expression and parsing things like \xxy (invalid hex digits)
which before were silently turned into (char)255.

Close #503

* Update re_lexer.c

Diff: diff --git a/libyara/re_lexer.c b/libyara/re_lexer.c
index 82d4871..f0940a8 100644
--- a/libyara/re_lexer.c
+++ b/libyara/re_lexer.c
@@ -188,11 +188,11 @@ typedef size_t yy_size_t;
 #define EOB_ACT_END_OF_FILE 1
 #define EOB_ACT_LAST_MATCH 2
 
     /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires
      *       access to the local variable yy_act. Since yyless() is a macro, it would break
-     *       existing scanners that call yyless() from OUTSIDE re_yylex. 
+     *       existing scanners that call yyless() from OUTSIDE re_yylex.
      *       One obvious solution it to make yy_act a global. I tried that, and saw
      *       a 5% performance hit in a non-yylineno scanner, because yy_act is
      *       normally declared as a register variable-- so it is not worth it.
      */
     #define  YY_LESS_LINENO(n) \
@@ -264,11 +264,11 @@ struct yy_buffer_state
 	 */
 	int yy_at_bol;
 
     int yy_bs_lineno; /**< The line count. */
     int yy_bs_column; /**< The column count. */
-    
+
 	/* Whether to try to fill the input buffer when we reach the
 	 * end of it.
 	 */
 	int yy_fill_buffer;
 
@@ -904,11 +904,11 @@ yy_find_action:
 		if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] )
 			{
 			yy_size_t yyl;
 			for ( yyl = 0; yyl < yyleng; ++yyl )
 				if ( yytext[yyl] == '\n' )
-					   
+					
     do{ yylineno++;
         yycolumn=0;
     }while(0)
 ;
 			}
@@ -1123,11 +1123,11 @@ YY_RULE_SETUP
     yylval->integer = c;
     return _CHAR_;
   }
   else
   {
-    yyerror(yyscanner, lex_env, "unexpected end of buffer");
+    yyerror(yyscanner, lex_env, "illegal escape sequence");
     yyterminate();
   }
 }
 	YY_BREAK
 case 18:
@@ -1178,11 +1178,11 @@ YY_RULE_SETUP
 
   if (end == '\\')
   {
     if (!read_escaped_char(yyscanner, &end))
     {
-      yyerror(yyscanner, lex_env, "unexpected end of buffer");
+      yyerror(yyscanner, lex_env, "illegal escape sequence");
       yyterminate();
     }
   }
 
   if (end < start)
@@ -1290,11 +1290,11 @@ YY_RULE_SETUP
   {
     LEX_ENV->class_vector[c / 8] |= 1 << c % 8;
   }
   else
   {
-    yyerror(yyscanner, lex_env, "unexpected end of buffer");
+    yyerror(yyscanner, lex_env, "illegal escape sequence");
     yyterminate();
   }
 }
 	YY_BREAK
 case 27:
@@ -1761,11 +1761,11 @@ static int yy_get_next_buffer (yyscan_t yyscanner)
 	c = *(unsigned char *) yyg->yy_c_buf_p;	/* cast for 8-bit char's */
 	*yyg->yy_c_buf_p = '\0';	/* preserve yytext */
 	yyg->yy_hold_char = *++yyg->yy_c_buf_p;
 
 	if ( c == '\n' )
-		   
+		
     do{ yylineno++;
         yycolumn=0;
     }while(0)
 ;
 
@@ -2016,13 +2016,13 @@ static void re_yyensure_buffer_stack (yyscan_t yyscanner)
 		yyg->yy_buffer_stack = (struct yy_buffer_state**)re_yyalloc
 								(num_to_alloc * sizeof(struct yy_buffer_state*)
 								, yyscanner);
 		if ( ! yyg->yy_buffer_stack )
 			YY_FATAL_ERROR( "out of dynamic memory in re_yyensure_buffer_stack()" );
-								  
+
 		memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*));
-				
+
 		yyg->yy_buffer_stack_max = num_to_alloc;
 		yyg->yy_buffer_stack_top = 0;
 		return;
 	}
 
@@ -2047,11 +2047,11 @@ static void re_yyensure_buffer_stack (yyscan_t yyscanner)
 
 /** Setup the input buffer state to scan directly from a user-specified character buffer.
  * @param base the character buffer
  * @param size the size in bytes of the character buffer
  * @param yyscanner The scanner object.
- * @return the newly allocated buffer state object. 
+ * @return the newly allocated buffer state object.
  */
 YY_BUFFER_STATE re_yy_scan_buffer  (char * base, yy_size_t  size , yyscan_t yyscanner)
 {
 	YY_BUFFER_STATE b;
     
@@ -2175,11 +2175,11 @@ YY_EXTRA_TYPE re_yyget_extra  (yyscan_t yyscanner)
  * @param yyscanner The scanner object.
  */
 int re_yyget_lineno  (yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
-    
+
         if (! YY_CURRENT_BUFFER)
             return 0;
     
     return yylineno;
 }
@@ -2188,11 +2188,11 @@ int re_yyget_lineno  (yyscan_t yyscanner)
  * @param yyscanner The scanner object.
  */
 int re_yyget_column  (yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
-    
+
         if (! YY_CURRENT_BUFFER)
             return 0;
     
     return yycolumn;
 }
@@ -2363,24 +2363,24 @@ int re_yylex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals )
 
     if (ptr_yy_globals == NULL){
         errno = EINVAL;
         return 1;
     }
-	
+
     *ptr_yy_globals = (yyscan_t) re_yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts );
-	
+
     if (*ptr_yy_globals == NULL){
         errno = ENOMEM;
         return 1;
     }
-    
+
     /* By setting to 0xAA, we expose bugs in
     yy_init_globals. Leave at 0x00 for releases. */
     memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
-    
+
     re_yyset_extra (yy_user_defined, *ptr_yy_globals);
-    
+
     return yy_init_globals ( *ptr_yy_globals );
 }
 
 static int yy_init_globals (yyscan_t yyscanner)
 {
@@ -2566,23 +2566,23 @@ int read_escaped_char(
   char text[4] = {0, 0, 0, 0};
 
   text[0] = '\\';
   text[1] = RE_YY_INPUT(yyscanner);
 
-  if (text[1] == EOF)
+  if (text[1] == EOF || text[1] == 0)
     return 0;
 
   if (text[1] == 'x')
   {
     text[2] = RE_YY_INPUT(yyscanner);
 
-    if (text[2] == EOF)
+    if (!isxdigit(text[2]))
       return 0;
 
     text[3] = RE_YY_INPUT(yyscanner);
 
-    if (text[3] == EOF)
+    if (!isxdigit(text[3]))
       return 0;
   }
 
   *escaped_char = escaped_char_value(text);
 
diff --git a/libyara/re_lexer.l b/libyara/re_lexer.l
index 1b3f5aa..9e0b005 100644
--- a/libyara/re_lexer.l
+++ b/libyara/re_lexer.l
@@ -259,11 +259,11 @@ hex_digit     [0-9a-fA-F]
     yylval->integer = c;
     return _CHAR_;
   }
   else
   {
-    yyerror(yyscanner, lex_env, "unexpected end of buffer");
+    yyerror(yyscanner, lex_env, "illegal escape sequence");
     yyterminate();
   }
 }
 
 
@@ -310,11 +310,11 @@ hex_digit     [0-9a-fA-F]
 
   if (end == '\\')
   {
     if (!read_escaped_char(yyscanner, &end))
     {
-      yyerror(yyscanner, lex_env, "unexpected end of buffer");
+      yyerror(yyscanner, lex_env, "illegal escape sequence");
       yyterminate();
     }
   }
 
   if (end < start)
@@ -408,11 +408,11 @@ hex_digit     [0-9a-fA-F]
   {
     LEX_ENV->class_vector[c / 8] |= 1 << c % 8;
   }
   else
   {
-    yyerror(yyscanner, lex_env, "unexpected end of buffer");
+    yyerror(yyscanner, lex_env, "illegal escape sequence");
     yyterminate();
   }
 }
 
 
@@ -522,23 +522,23 @@ int read_escaped_char(
   char text[4] = {0, 0, 0, 0};
 
   text[0] = '\\';
   text[1] = RE_YY_INPUT(yyscanner);
 
-  if (text[1] == EOF)
+  if (text[1] == EOF || text[1] == 0)
     return 0;
 
   if (text[1] == 'x')
   {
     text[2] = RE_YY_INPUT(yyscanner);
 
-    if (text[2] == EOF)
+    if (!isxdigit(text[2]))
       return 0;
 
     text[3] = RE_YY_INPUT(yyscanner);
 
-    if (text[3] == EOF)
+    if (!isxdigit(text[3]))
       return 0;
   }
 
   *escaped_char = escaped_char_value(text);
 
diff --git a/tests/test-rules.c b/tests/test-rules.c
index 5570dbc..556e345 100644
--- a/tests/test-rules.c
+++ b/tests/test-rules.c
@@ -1017,10 +1017,16 @@ void test_re()
   assert_false_regexp("(bc+d$|ef*g.|h?i(j|k))", "bcdd");
   assert_true_regexp("(bc+d$|ef*g.|h?i(j|k))", "reffgz", "effgz");
 
   // Test case for issue #324
   assert_true_regexp("whatever|   x.   x", "   xy   x", "   xy   x");
+
+  // test case for issue #503, \x without two following hex-digits
+  assert_regexp_syntax_error("\\x0");
+  assert_regexp_syntax_error("\\x");
+
+  assert_regexp_syntax_error("\\xxy");
 }
 
 
 static void test_entrypoint()
 {

Vulnerable code:      *       existing scanners that call yyless() from OUTSIDE re_yylex. 
    
					   
    yyerror(yyscanner, lex_env, "unexpected end of buffer");
      yyerror(yyscanner, lex_env, "unexpected end of buffer");
    yyerror(yyscanner, lex_env, "unexpected end of buffer");
		   
								  
				
 * @return the newly allocated buffer state object. 
    
    
	
	
    
    
    
  if (text[1] == EOF)
    if (text[2] == EOF)
    if (text[3] == EOF)
    yyerror(yyscanner, lex_env, "unexpected end of buffer");
      yyerror(yyscanner, lex_env, "unexpected end of buffer");
    yyerror(yyscanner, lex_env, "unexpected end of buffer");
  if (text[1] == EOF)
    if (text[2] == EOF)
    if (text[3] == EOF)
Fixed code:      *       existing scanners that call yyless() from OUTSIDE re_yylex.

					
    yyerror(yyscanner, lex_env, "illegal escape sequence");
      yyerror(yyscanner, lex_env, "illegal escape sequence");
    yyerror(yyscanner, lex_env, "illegal escape sequence");
		


 * @return the newly allocated buffer state object.







  if (text[1] == EOF || text[1] == 0)
    if (!isxdigit(text[2]))
    if (!isxdigit(text[3]))
    yyerror(yyscanner, lex_env, "illegal escape sequence");
      yyerror(yyscanner, lex_env, "illegal escape sequence");
    yyerror(yyscanner, lex_env, "illegal escape sequence");
  if (text[1] == EOF || text[1] == 0)
    if (!isxdigit(text[2]))
    if (!isxdigit(text[3]))

  // test case for issue #503, \x without two following hex-digits
  assert_regexp_syntax_error("\\x0");
  assert_regexp_syntax_error("\\x");

  assert_regexp_syntax_error("\\xxy");