Vulnerability: User after free in mrb_vm_exec in GitHub repository mruby/mruby prior to 3.2.
Vulnerability ID: CVE-2022-1071
CWE: CWE-416
Severity score: 6.4
Project: https://github.com/mruby/mruby
Published: 2022-03-26
File: src/vm.c
Commit message: vm.c: stack may be reallocated in functions calls.

Probably due to recursive VM calls via `mrb_funcall()`.
Commit author: Yukihiro "Matz" Matsumoto

Vulnerable version (before fix):
 mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)         regs[a] = mrb_ary_entry(va, mrb_integer(vb));         break;       case MRB_TT_HASH:         regs[a] = mrb_hash_get(mrb, va, vb);         break;       case MRB_TT_STRING:         switch (mrb_type(vb)) {         case MRB_TT_INTEGER:         case MRB_TT_STRING:         case MRB_TT_RANGE:           regs[a] = mrb_str_aref(mrb, va, vb, mrb_undef_value());           break;         default:           goto getidx_fallback; mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)     }      CASE(OP_GETCONST, BB) {       regs[a] = mrb_vm_const_get(mrb, syms[b]);       NEXT;     }  mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)     }      CASE(OP_GETMCNST, BB) {       regs[a] = mrb_const_get(mrb, regs[a], syms[b]);       NEXT;     }  mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)     CASE(OP_KARG, BB) {       mrb_value k = mrb_symbol_value(syms[b]);       mrb_int kidx = mrb_ci_kidx(mrb->c->ci);       mrb_value kdict;        if (kidx < 0 || !mrb_hash_p(kdict=regs[kidx]) || !mrb_hash_key_p(mrb, kdict, k)) {         mrb_value str = mrb_format(mrb, "missing keyword: %v", k);         mrb_exc_set(mrb, mrb_exc_new_str(mrb, E_ARGUMENT_ERROR, str));         goto L_RAISE;       }       regs[a] = mrb_hash_get(mrb, kdict, k);       mrb_hash_delete_key(mrb, kdict, k);       NEXT;     }

Fixed version (after fix):
 mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)         regs[a] = mrb_ary_entry(va, mrb_integer(vb));         break;       case MRB_TT_HASH:         va = mrb_hash_get(mrb, va, vb);         regs[a] = va;         break;       case MRB_TT_STRING:         switch (mrb_type(vb)) {         case MRB_TT_INTEGER:         case MRB_TT_STRING:         case MRB_TT_RANGE:           va = mrb_str_aref(mrb, va, vb, mrb_undef_value());           regs[a] = va;           break;         default:           goto getidx_fallback; mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)     }      CASE(OP_GETCONST, BB) {       mrb_value v = mrb_vm_const_get(mrb, syms[b]);       regs[a] = v;       NEXT;     }  mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)     }      CASE(OP_GETMCNST, BB) {       mrb_value v = mrb_const_get(mrb, regs[a], syms[b]);       regs[a] = v;       NEXT;     }  mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)     CASE(OP_KARG, BB) {       mrb_value k = mrb_symbol_value(syms[b]);       mrb_int kidx = mrb_ci_kidx(mrb->c->ci);       mrb_value kdict, v;        if (kidx < 0 || !mrb_hash_p(kdict=regs[kidx]) || !mrb_hash_key_p(mrb, kdict, k)) {         mrb_value str = mrb_format(mrb, "missing keyword: %v", k);         mrb_exc_set(mrb, mrb_exc_new_str(mrb, E_ARGUMENT_ERROR, str));         goto L_RAISE;       }       v = mrb_hash_get(mrb, kdict, k);       regs[a] = v;       mrb_hash_delete_key(mrb, kdict, k);       NEXT;     }

Patch:
patch: @@ -1394,14 +1394,16 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)
         regs[a] = mrb_ary_entry(va, mrb_integer(vb));
         break;
       case MRB_TT_HASH:
-        regs[a] = mrb_hash_get(mrb, va, vb);
+        va = mrb_hash_get(mrb, va, vb);
+        regs[a] = va;
         break;
       case MRB_TT_STRING:
         switch (mrb_type(vb)) {
         case MRB_TT_INTEGER:
         case MRB_TT_STRING:
         case MRB_TT_RANGE:
-          regs[a] = mrb_str_aref(mrb, va, vb, mrb_undef_value());
+          va = mrb_str_aref(mrb, va, vb, mrb_undef_value());
+          regs[a] = va;
           break;
         default:
           goto getidx_fallback;
@@ -1423,7 +1425,8 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)
     }
 
     CASE(OP_GETCONST, BB) {
-      regs[a] = mrb_vm_const_get(mrb, syms[b]);
+      mrb_value v = mrb_vm_const_get(mrb, syms[b]);
+      regs[a] = v;
       NEXT;
     }
 
@@ -1433,7 +1436,8 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)
     }
 
     CASE(OP_GETMCNST, BB) {
-      regs[a] = mrb_const_get(mrb, regs[a], syms[b]);
+      mrb_value v = mrb_const_get(mrb, regs[a], syms[b]);
+      regs[a] = v;
       NEXT;
     }
 
@@ -2014,14 +2018,15 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *proc, const mrb_code *pc)
     CASE(OP_KARG, BB) {
       mrb_value k = mrb_symbol_value(syms[b]);
       mrb_int kidx = mrb_ci_kidx(mrb->c->ci);
-      mrb_value kdict;
+      mrb_value kdict, v;
 
       if (kidx < 0 || !mrb_hash_p(kdict=regs[kidx]) || !mrb_hash_key_p(mrb, kdict, k)) {
         mrb_value str = mrb_format(mrb, "missing keyword: %v", k);
         mrb_exc_set(mrb, mrb_exc_new_str(mrb, E_ARGUMENT_ERROR, str));
         goto L_RAISE;
       }
-      regs[a] = mrb_hash_get(mrb, kdict, k);
+      v = mrb_hash_get(mrb, kdict, k);
+      regs[a] = v;
       mrb_hash_delete_key(mrb, kdict, k);
       NEXT;
     }
path: src/vm.c