Vulnerability: parser/js/js-scanner.c in JerryScript 2.2.0 mishandles errors during certain out-of-memory conditions, as demonstrated by a scanner_reverse_info_list NULL pointer dereference and a scanner_scan_all assertion failure.
Vulnerability ID: CVE-2020-13649
CWE: CWE-476
Severity score: 2.9
Project: https://github.com/jerryscript-project/jerryscript
Published: 2020-05-28
File: jerry-core/parser/js/js-scanner.c
Commit message: Fix error handling in scanner when in case of OOM (#3793)

This patch fixes #3786 and fixes #3788.

JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu
Commit author: Robert Fancsik

Vulnerable version (before fix):
 scanner_scan_all (parser_context_t *context_p, /**< context */   }   PARSER_CATCH   {     /* Ignore the errors thrown by the lexer. */     if (context_p->error != PARSER_ERR_OUT_OF_MEMORY)     {       context_p->error = PARSER_ERR_NO_ERROR;     }  #if ENABLED (JERRY_ES2015)     while (scanner_context.active_binding_list_p != NULL)     {       scanner_pop_binding_list (&scanner_context);     } #endif /* ENABLED (JERRY_ES2015) */      /* The following code may allocate memory, so it is enclosed in a try/catch. */     PARSER_TRY (context_p->try_buffer)     { #if ENABLED (JERRY_ES2015)       if (scanner_context.status_flags & SCANNER_CONTEXT_THROW_ERR_ASYNC_FUNCTION)       {         JERRY_ASSERT (scanner_context.async_source_p != NULL);          scanner_info_t *info_p;         info_p = scanner_insert_info (context_p, scanner_context.async_source_p, sizeof (scanner_info_t));         info_p->type = SCANNER_TYPE_ERR_ASYNC_FUNCTION;       } #endif /* ENABLED (JERRY_ES2015) */        while (scanner_context.active_literal_pool_p != NULL)       {         scanner_pop_literal_pool (context_p, &scanner_context);       }     }     PARSER_CATCH     {       JERRY_ASSERT (context_p->error == PARSER_ERR_NO_ERROR);        while (scanner_context.active_literal_pool_p != NULL)       {         scanner_literal_pool_t *literal_pool_p = scanner_context.active_literal_pool_p; scanner_scan_all (parser_context_t *context_p, /**< context */         parser_list_free (&literal_pool_p->literal_pool);         scanner_free (literal_pool_p, sizeof (scanner_literal_pool_t));       }     }     PARSER_TRY_END  #if ENABLED (JERRY_ES2015)     context_p->status_flags &= (uint32_t) ~PARSER_IS_GENERATOR_FUNCTION; #endif /* ENABLED (JERRY_ES2015) */   }   PARSER_TRY_END 

Fixed version (after fix):
 scanner_scan_all (parser_context_t *context_p, /**< context */   }   PARSER_CATCH   { #if ENABLED (JERRY_ES2015)     while (scanner_context.active_binding_list_p != NULL)     {       scanner_pop_binding_list (&scanner_context);     } #endif /* ENABLED (JERRY_ES2015) */      if (JERRY_UNLIKELY (context_p->error != PARSER_ERR_OUT_OF_MEMORY))     {       /* Ignore the errors thrown by the lexer. */       context_p->error = PARSER_ERR_NO_ERROR;        /* The following code may allocate memory, so it is enclosed in a try/catch. */       PARSER_TRY (context_p->try_buffer)       {   #if ENABLED (JERRY_ES2015)         if (scanner_context.status_flags & SCANNER_CONTEXT_THROW_ERR_ASYNC_FUNCTION)         {           JERRY_ASSERT (scanner_context.async_source_p != NULL);            scanner_info_t *info_p;           info_p = scanner_insert_info (context_p, scanner_context.async_source_p, sizeof (scanner_info_t));           info_p->type = SCANNER_TYPE_ERR_ASYNC_FUNCTION;         }   #endif /* ENABLED (JERRY_ES2015) */          while (scanner_context.active_literal_pool_p != NULL)         {           scanner_pop_literal_pool (context_p, &scanner_context);         }       }       PARSER_CATCH       {         JERRY_ASSERT (context_p->error == PARSER_ERR_OUT_OF_MEMORY);       }       PARSER_TRY_END     }      JERRY_ASSERT (context_p->error == PARSER_ERR_NO_ERROR || context_p->error == PARSER_ERR_OUT_OF_MEMORY);      if (context_p->error == PARSER_ERR_OUT_OF_MEMORY)     {       while (scanner_context.active_literal_pool_p != NULL)       {         scanner_literal_pool_t *literal_pool_p = scanner_context.active_literal_pool_p; scanner_scan_all (parser_context_t *context_p, /**< context */         parser_list_free (&literal_pool_p->literal_pool);         scanner_free (literal_pool_p, sizeof (scanner_literal_pool_t));       }        parser_stack_free (context_p);       return;     }   }   PARSER_TRY_END 

Patch:
patch: @@ -3159,42 +3159,48 @@ scanner_scan_all (parser_context_t *context_p, /**< context */
   }
   PARSER_CATCH
   {
-    /* Ignore the errors thrown by the lexer. */
-    if (context_p->error != PARSER_ERR_OUT_OF_MEMORY)
-    {
-      context_p->error = PARSER_ERR_NO_ERROR;
-    }
-
 #if ENABLED (JERRY_ES2015)
     while (scanner_context.active_binding_list_p != NULL)
     {
       scanner_pop_binding_list (&scanner_context);
     }
 #endif /* ENABLED (JERRY_ES2015) */
 
-    /* The following code may allocate memory, so it is enclosed in a try/catch. */
-    PARSER_TRY (context_p->try_buffer)
+    if (JERRY_UNLIKELY (context_p->error != PARSER_ERR_OUT_OF_MEMORY))
     {
-#if ENABLED (JERRY_ES2015)
-      if (scanner_context.status_flags & SCANNER_CONTEXT_THROW_ERR_ASYNC_FUNCTION)
+      /* Ignore the errors thrown by the lexer. */
+      context_p->error = PARSER_ERR_NO_ERROR;
+
+      /* The following code may allocate memory, so it is enclosed in a try/catch. */
+      PARSER_TRY (context_p->try_buffer)
       {
-        JERRY_ASSERT (scanner_context.async_source_p != NULL);
+  #if ENABLED (JERRY_ES2015)
+        if (scanner_context.status_flags & SCANNER_CONTEXT_THROW_ERR_ASYNC_FUNCTION)
+        {
+          JERRY_ASSERT (scanner_context.async_source_p != NULL);
 
-        scanner_info_t *info_p;
-        info_p = scanner_insert_info (context_p, scanner_context.async_source_p, sizeof (scanner_info_t));
-        info_p->type = SCANNER_TYPE_ERR_ASYNC_FUNCTION;
-      }
-#endif /* ENABLED (JERRY_ES2015) */
+          scanner_info_t *info_p;
+          info_p = scanner_insert_info (context_p, scanner_context.async_source_p, sizeof (scanner_info_t));
+          info_p->type = SCANNER_TYPE_ERR_ASYNC_FUNCTION;
+        }
+  #endif /* ENABLED (JERRY_ES2015) */
 
-      while (scanner_context.active_literal_pool_p != NULL)
+        while (scanner_context.active_literal_pool_p != NULL)
+        {
+          scanner_pop_literal_pool (context_p, &scanner_context);
+        }
+      }
+      PARSER_CATCH
       {
-        scanner_pop_literal_pool (context_p, &scanner_context);
+        JERRY_ASSERT (context_p->error == PARSER_ERR_OUT_OF_MEMORY);
       }
+      PARSER_TRY_END
     }
-    PARSER_CATCH
-    {
-      JERRY_ASSERT (context_p->error == PARSER_ERR_NO_ERROR);
 
+    JERRY_ASSERT (context_p->error == PARSER_ERR_NO_ERROR || context_p->error == PARSER_ERR_OUT_OF_MEMORY);
+
+    if (context_p->error == PARSER_ERR_OUT_OF_MEMORY)
+    {
       while (scanner_context.active_literal_pool_p != NULL)
       {
         scanner_literal_pool_t *literal_pool_p = scanner_context.active_literal_pool_p;
@@ -3204,12 +3210,10 @@ scanner_scan_all (parser_context_t *context_p, /**< context */
         parser_list_free (&literal_pool_p->literal_pool);
         scanner_free (literal_pool_p, sizeof (scanner_literal_pool_t));
       }
-    }
-    PARSER_TRY_END
 
-#if ENABLED (JERRY_ES2015)
-    context_p->status_flags &= (uint32_t) ~PARSER_IS_GENERATOR_FUNCTION;
-#endif /* ENABLED (JERRY_ES2015) */
+      parser_stack_free (context_p);
+      return;
+    }
   }
   PARSER_TRY_END
path: jerry-core/parser/js/js-scanner.c