CVE-2022-35173 (published 2022-08-18T06:15Z)

An issue was discovered in Nginx NJS v0.7.5. The JUMP offset for a break instruction was not set to a correct offset during code generation, leading to a segmentation violation.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-754
cwe_name: Improper Check for Unusual or Exceptional Conditions
description: The product does not check or incorrectly checks for unusual or exceptional conditions that are not expected to occur frequently during day to day operation of the product.
extended_description: 
            The programmer may assume that certain events or conditions will never occur or do not need to be worried about, such as low memory conditions, lack of access to resources due to restrictive permissions, or misbehaving clients or components. However, attackers may intentionally trigger these unusual conditions, thus violating the programmer's assumptions, possibly introducing instability, incorrect behavior, or a vulnerability.
            Note that this entry is not exclusively about the use of exceptions and exception handling, which are mechanisms for both checking and handling unusual or unexpected conditions.
         
         

Fix information:
repo_url: https://github.com/nginx/njs
commit_details: repo_url: https://github.com/nginx/njs
author: Dmitry Volyntsev
author_date: 2022-06-28 23:04:00-07:00
msg: Fixed break instruction in a try-catch block.

Previously, JUMP offset for a break instruction inside a try-catch
block was not set to a correct offset during code generation
when a return instruction was present in inner try-catch block.

The fix is to update the JUMP offset appropriately.

This closes #553 issue on Github.
repository: repo_url: https://github.com/nginx/njs
repo_name: nginx/njs
description: A subset of JavaScript language to use in nginx
repo_language: C
owner: nginx
file_changes: filename: njs_generator.c
change_type: ModificationType.MODIFY
diff: @@ -4495,6 +4495,11 @@ njs_generate_try_catch(njs_vm_t *vm, njs_generator_t *generator,
                                             NJS_GENERATOR_ALL,
                                             &ctx->try_exit_label);
 
+            /*
+             * block can be NULL when &ctx->try_exit_label is "@return"
+             * for outermost try-catch block.
+             */
+
             if (block != NULL) {
                 patch = njs_generate_make_exit_patch(vm, block,
                                                      &ctx->try_exit_label,
@@ -4503,6 +4508,26 @@ njs_generate_try_catch(njs_vm_t *vm, njs_generator_t *generator,
                 if (njs_slow_path(patch == NULL)) {
                     return NJS_ERROR;
                 }
+
+            } else {
+
+                /*
+                 * when block == NULL, we still want to patch the "finally"
+                 * instruction break_offset.
+                 */
+
+                block = njs_generate_find_block(vm, generator->block,
+                                                NJS_GENERATOR_ALL,
+                                                &no_label);
+
+                if (block != NULL) {
+                    patch = njs_generate_make_exit_patch(vm, block, &no_label,
+                                njs_code_offset(generator, finally)
+                                + offsetof(njs_vmcode_finally_t, break_offset));
+                    if (njs_slow_path(patch == NULL)) {
+                        return NJS_ERROR;
+                    }
+                }
             }
         }
     }
@@ -4669,8 +4694,7 @@ njs_generate_try_end(njs_vm_t *vm, njs_generator_t *generator,
          * outermost try-catch block.
          */
         block = njs_generate_find_block(vm, generator->block,
-                                        NJS_GENERATOR_ALL
-                                        | NJS_GENERATOR_TRY, dest_label);
+                                        NJS_GENERATOR_ALL, dest_label);
         if (block != NULL) {
             patch = njs_generate_make_exit_patch(vm, block, dest_label,
                             njs_code_offset(generator, finally)
@@ -4678,6 +4702,19 @@ njs_generate_try_end(njs_vm_t *vm, njs_generator_t *generator,
             if (njs_slow_path(patch == NULL)) {
                 return NJS_ERROR;
             }
+
+        } else {
+
+            block = njs_generate_find_block(vm, generator->block,
+                                            NJS_GENERATOR_ALL, &no_label);
+            if (block != NULL) {
+                patch = njs_generate_make_exit_patch(vm, block, &no_label,
+                                njs_code_offset(generator, finally)
+                                + offsetof(njs_vmcode_finally_t, break_offset));
+                if (njs_slow_path(patch == NULL)) {
+                    return NJS_ERROR;
+                }
+            }
         }
     }
 

programming_language: SQL
filename: njs_unit_test.c
change_type: ModificationType.MODIFY
diff: @@ -3411,20 +3411,52 @@ static njs_unit_test_t  njs_test[] =
       njs_str("a,2") },
 
     { njs_str("function f(n) { "
-                 "  var r1 = 0, r2 = 0, r3 = 0;"
-                 "  a:{ try { try { "
-                 "              if (n == 0) { break a; } "
-                 "              if (n == 1) { throw 'a'; } "
-                 "            } "
-                 "            catch (e) { break a; } finally { r1++; } } "
-                 "      catch (e) {} "
-                 "      finally { r2++; } "
-                 "      r3++;  "
-                 "  }; "
-                 "return [r1, r2, r3]"
-                 "}; njs.dump([f(0), f(1), f(3)])"),
+              "  var r1 = 0, r2 = 0, r3 = 0;"
+              "  a:{ try { try { "
+              "              if (n == 0) { break a; } "
+              "              if (n == 1) { throw 'a'; } "
+              "            } "
+              "            catch (e) { break a; } finally { r1++; } } "
+              "      catch (e) {} "
+              "      finally { r2++; } "
+              "      r3++;  "
+              "  }; "
+              "return [r1, r2, r3]"
+              "}; njs.dump([f(0), f(1), f(3)])"),
       njs_str("[[1,1,0],[1,1,0],[1,1,1]]") },
 
+
+    { njs_str("function f(n) {"
+              "    while (1)"
+              "           try {"
+              "              if (n == 0) { break; }"
+              "              if (n == 1) { throw 'a'; }"
+              ""
+              "              try { return 42; }"
+              "              catch (a) {}"
+              ""
+              "            } catch (b) { return b; }"
+              "};"
+              "njs.dump([f(0), f(1), f(2)])"),
+      njs_str("[undefined,'a',42]") },
+
+    { njs_str("function f(n, r) {"
+              "    while (1)"
+              "           try {"
+              "              if (n == 0) { break; }"
+              "              if (n == 1) { throw 'a'; }"
+              ""
+              "              try { return 42; }"
+              "              catch (a) {}"
+              "              finally { r.push('in');}"
+              ""
+              "            } catch (b) { return b; }"
+              "            finally { r.push('out'); }"
+              "};"
+              "function g(n) { var r = []; return [f(n, r), r]}"
+              "njs.dump([g(0), g(1), g(2)])"),
+      njs_str("[[undefined,['out']],['a',['out']],[42,['in','out']]]") },
+
     /**/
 
     { njs_str("function f() { Object.prototype.toString = 1; };"

programming_language: JavaScript