CVE-2022-35173: Improper Check for Unusual or Exceptional Conditions (CWE-754)
value: 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.
CWE 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.
CVSS v3 base score: 7.5
Published: 2022-08-18T06:15Z
Repository: https://github.com/nginx/njs
Language: C
Files changed: src/njs_generator.c, src/test/njs_unit_test.c
Commit date: 2022-06-28 23:04:00 -0700
Commit message: 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.

Diff: diff --git a/src/njs_generator.c b/src/njs_generator.c
index 3e0d58b6f..e9fee16d4 100644
--- a/src/njs_generator.c
+++ b/src/njs_generator.c
@@ -4493,18 +4493,43 @@ njs_generate_try_catch(njs_vm_t *vm, njs_generator_t *generator,
         if (try_block->exit != NULL) {
             block = njs_generate_find_block(vm, generator->block,
                                             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,
                             njs_code_offset(generator, finally)
                             + offsetof(njs_vmcode_finally_t, break_offset));
                 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;
+                    }
+                }
             }
         }
     }
 
     /* TODO: release exception variable index. */
@@ -4667,19 +4692,31 @@ njs_generate_try_end(njs_vm_t *vm, njs_generator_t *generator,
         /*
          * block can be NULL for "return" instruction in
          * 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)
                             + offsetof(njs_vmcode_finally_t, break_offset));
             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;
+                }
+            }
         }
     }
 
     ret = njs_generate_index_release(vm, generator, ctx->exception_index);
     if (njs_slow_path(ret != NJS_OK)) {
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index c131a9cb2..8e995bf59 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -3409,24 +3409,56 @@ static njs_unit_test_t  njs_test[] =
                  "                finally { r++; } }; "
                  "[f(), r]"),
       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; };"
               "Object.prototype.toString = f;"
               "(function () { try { 's'[{}](); } catch (e) { throw e; } })()"),

Vulnerable code:                                         NJS_GENERATOR_ALL
                                        | NJS_GENERATOR_TRY, dest_label);
                 "  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)])"),
Fixed code:             /*
             * block can be NULL when &ctx->try_exit_label is "@return"
             * for outermost try-catch block.
             */


            } 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;
                    }
                }
                                        NJS_GENERATOR_ALL, dest_label);

        } 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;
                }
            }
              "  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("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']]]") },