CVE-2021-4110: NULL Pointer Dereference (CWE-476)
value: mruby is vulnerable to NULL Pointer Dereference
CWE description: The product dereferences a pointer that it expects to be valid but is NULL.
CVSS v3 base score: 7.5
Published: 2021-12-15T05:15Z
Repository: https://github.com/mruby/mruby
Language: C
Files changed: include/mruby/proc.h, src/class.c, src/proc.c
Commit date: 2021-12-14 13:35:18 +0900
Commit message: proc.c: add `mrb_state` argument to `mrb_proc_copy()`.

The function may invoke the garbage collection and it requires
`mrb_state` to run.

Diff: diff --git a/include/mruby/proc.h b/include/mruby/proc.h
index 1ba485783e..a6aa4ed7f6 100644
--- a/include/mruby/proc.h
+++ b/include/mruby/proc.h
@@ -90,11 +90,11 @@ struct RProc {
 
 struct RProc *mrb_proc_new(mrb_state*, const mrb_irep*);
 struct RProc *mrb_closure_new(mrb_state*, const mrb_irep*);
 MRB_API struct RProc *mrb_proc_new_cfunc(mrb_state*, mrb_func_t);
 MRB_API struct RProc *mrb_closure_new_cfunc(mrb_state *mrb, mrb_func_t func, int nlocals);
-void mrb_proc_copy(struct RProc *a, struct RProc *b);
+void mrb_proc_copy(mrb_state *mrb, struct RProc *a, struct RProc *b);
 mrb_int mrb_proc_arity(const struct RProc *p);
 
 /* following functions are defined in mruby-proc-ext so please include it when using */
 MRB_API struct RProc *mrb_proc_new_cfunc_with_env(mrb_state *mrb, mrb_func_t func, mrb_int argc, const mrb_value *argv);
 MRB_API mrb_value mrb_proc_cfunc_env_get(mrb_state *mrb, mrb_int idx);
diff --git a/src/class.c b/src/class.c
index 76a9dadf35..89bcd8b624 100644
--- a/src/class.c
+++ b/src/class.c
@@ -2567,11 +2567,11 @@ mrb_mod_define_method_m(mrb_state *mrb, struct RClass *c)
   }
   if (mrb_nil_p(blk)) {
     mrb_raise(mrb, E_ARGUMENT_ERROR, "no block given");
   }
   p = MRB_OBJ_ALLOC(mrb, MRB_TT_PROC, mrb->proc_class);
-  mrb_proc_copy(p, mrb_proc_ptr(blk));
+  mrb_proc_copy(mrb, p, mrb_proc_ptr(blk));
   p->flags |= MRB_PROC_STRICT;
   MRB_METHOD_FROM_PROC(m, p);
   mrb_define_method_raw(mrb, c, mid, m);
   mrb_method_added(mrb, c, mid);
   return mrb_symbol_value(mid);
diff --git a/src/proc.c b/src/proc.c
index c79a53399f..e11cb7fec7 100644
--- a/src/proc.c
+++ b/src/proc.c
@@ -199,22 +199,22 @@ mrb_proc_cfunc_env_get(mrb_state *mrb, mrb_int idx)
 
   return e->stack[idx];
 }
 
 void
-mrb_proc_copy(struct RProc *a, struct RProc *b)
+mrb_proc_copy(mrb_state *mrb, struct RProc *a, struct RProc *b)
 {
   if (a->body.irep) {
     /* already initialized proc */
     return;
   }
   a->flags = b->flags;
   a->body = b->body;
+  a->upper = b->upper;
   if (!MRB_PROC_CFUNC_P(a) && a->body.irep) {
-    mrb_irep_incref(NULL, (mrb_irep*)a->body.irep);
+    mrb_irep_incref(mrb, (mrb_irep*)a->body.irep);
   }
-  a->upper = b->upper;
   a->e.env = b->e.env;
   /* a->e.target_class = a->e.target_class; */
 }
 
 static mrb_value
@@ -225,11 +225,11 @@ mrb_proc_s_new(mrb_state *mrb, mrb_value proc_class)
   struct RProc *p;
 
   /* Calling Proc.new without a block is not implemented yet */
   mrb_get_args(mrb, "&!", &blk);
   p = MRB_OBJ_ALLOC(mrb, MRB_TT_PROC, mrb_class_ptr(proc_class));
-  mrb_proc_copy(p, mrb_proc_ptr(blk));
+  mrb_proc_copy(mrb, p, mrb_proc_ptr(blk));
   proc = mrb_obj_value(p);
   mrb_funcall_with_block(mrb, proc, MRB_SYM(initialize), 0, NULL, proc);
   if (!MRB_PROC_STRICT_P(p) &&
       mrb->c->ci > mrb->c->cibase && MRB_PROC_ENV(p) == mrb->c->ci[-1].u.env) {
     p->flags |= MRB_PROC_ORPHAN;
@@ -243,11 +243,11 @@ mrb_proc_init_copy(mrb_state *mrb, mrb_value self)
   mrb_value proc = mrb_get_arg1(mrb);
 
   if (!mrb_proc_p(proc)) {
     mrb_raise(mrb, E_ARGUMENT_ERROR, "not a proc");
   }
-  mrb_proc_copy(mrb_proc_ptr(self), mrb_proc_ptr(proc));
+  mrb_proc_copy(mrb, mrb_proc_ptr(self), mrb_proc_ptr(proc));
   return self;
 }
 
 /* 15.2.17.4.2 */
 static mrb_value
@@ -279,11 +279,11 @@ proc_lambda(mrb_state *mrb, mrb_value self)
     mrb_raise(mrb, E_ARGUMENT_ERROR, "not a proc");
   }
   p = mrb_proc_ptr(blk);
   if (!MRB_PROC_STRICT_P(p)) {
     struct RProc *p2 = MRB_OBJ_ALLOC(mrb, MRB_TT_PROC, p->c);
-    mrb_proc_copy(p2, p);
+    mrb_proc_copy(mrb, p2, p);
     p2->flags |= MRB_PROC_STRICT;
     return mrb_obj_value(p2);
   }
   return blk;
 }

Vulnerable code: void mrb_proc_copy(struct RProc *a, struct RProc *b);
  mrb_proc_copy(p, mrb_proc_ptr(blk));
mrb_proc_copy(struct RProc *a, struct RProc *b)
    mrb_irep_incref(NULL, (mrb_irep*)a->body.irep);
  a->upper = b->upper;
  mrb_proc_copy(p, mrb_proc_ptr(blk));
  mrb_proc_copy(mrb_proc_ptr(self), mrb_proc_ptr(proc));
    mrb_proc_copy(p2, p);
Fixed code: void mrb_proc_copy(mrb_state *mrb, struct RProc *a, struct RProc *b);
  mrb_proc_copy(mrb, p, mrb_proc_ptr(blk));
mrb_proc_copy(mrb_state *mrb, struct RProc *a, struct RProc *b)
  a->upper = b->upper;
    mrb_irep_incref(mrb, (mrb_irep*)a->body.irep);
  mrb_proc_copy(mrb, p, mrb_proc_ptr(blk));
  mrb_proc_copy(mrb, mrb_proc_ptr(self), mrb_proc_ptr(proc));
    mrb_proc_copy(mrb, p2, p);