Commit message: Clear unused stack region that may refer freed objects; fix #3596
CVE: CVE-2017-9527
CWE: CWE-416
Vulnerability description: The mark_context_stack function in gc.c in mruby through 1.2.0 allows attackers to cause a denial of service (heap-based use-after-free and application crash) or possibly have unspecified other impact via a crafted .rb file.

Project: mruby
File: gc.c

final_marking_phase(mrb_state *mrb, mrb_gc *gc)
{
  mark_context_stack(mrb, mrb->root_c);
  gc_mark_gray_list(mrb, gc);
  mrb_assert(gc->gray_list == NULL);
  gc->gray_list = gc->atomic_gray_list;
  gc->atomic_gray_list = NULL;
  gc_mark_gray_list(mrb, gc);
  mrb_assert(gc->gray_list == NULL);
}

Project: mruby
File: gc.c

gc_each_objects(mrb_state *mrb, mrb_gc *gc, mrb_each_object_callback *callback, void *data)
{
  mrb_heap_page* page = gc->heaps;

  while (page != NULL) {
    RVALUE *p, *pend;

    p = objects(page);
    pend = p + MRB_HEAP_PAGE_SIZE;
    for (;p < pend; p++) {
      (*callback)(mrb, &p->as.basic, data);
    }

    page = page->next;
  }
}

Project: mruby
File: gc.c

gc_mark_gray_list(mrb_state *mrb, mrb_gc *gc) {
  while (gc->gray_list) {
    if (is_gray(gc->gray_list))
      gc_mark_children(mrb, gc, gc->gray_list);
    else
      gc->gray_list = gc->gray_list->gcnext;
  }
}

Project: mruby
File: gc.c

gc_protect(mrb_state *mrb, mrb_gc *gc, struct RBasic *p)
{
#ifdef MRB_GC_FIXED_ARENA
  if (gc->arena_idx >= MRB_GC_ARENA_SIZE) {
    /* arena overflow error */
    gc->arena_idx = MRB_GC_ARENA_SIZE - 4; /* force room in arena */
    mrb_exc_raise(mrb, mrb_obj_value(mrb->arena_err));
  }
#else
  if (gc->arena_idx >= gc->arena_capa) {
    /* extend arena */
    gc->arena_capa = (int)(gc->arena_capa * 1.5);
    gc->arena = (struct RBasic**)mrb_realloc(mrb, gc->arena, sizeof(struct RBasic*)*gc->arena_capa);
  }
#endif
  gc->arena[gc->arena_idx++] = p;
}

Project: mruby
File: gc.c

gc_step_ratio_get(mrb_state *mrb, mrb_value obj)
{
  return mrb_fixnum_value(mrb->gc.step_ratio);
}

Project: mruby
File: gc.c

gc_step_ratio_set(mrb_state *mrb, mrb_value obj)
{
  mrb_int ratio;

  mrb_get_args(mrb, "i", &ratio);
  mrb->gc.step_ratio = ratio;
  return mrb_nil_value();
}

Project: mruby
File: gc.c

mrb_gc_destroy(mrb_state *mrb, mrb_gc *gc)
{
  free_heap(mrb, gc);
#ifndef MRB_GC_FIXED_ARENA
  mrb_free(mrb, gc->arena);
#endif
}

Project: mruby
File: gc.c

test_incremental_gc(void)
{
  mrb_state *mrb = mrb_open();
  size_t max = ~0, live = 0, total = 0, freed = 0;
  RVALUE *free;
  mrb_heap_page *page;
  mrb_gc *gc = &mrb->gc;

  puts("test_incremental_gc");
  change_gen_gc_mode(mrb, gc, FALSE);

  puts("  in mrb_full_gc");
  mrb_full_gc(mrb);

  mrb_assert(gc->state == MRB_GC_STATE_ROOT);
  puts("  in MRB_GC_STATE_ROOT");
  incremental_gc(mrb, gc, max);
  mrb_assert(gc->state == MRB_GC_STATE_MARK);
  puts("  in MRB_GC_STATE_MARK");
  incremental_gc_until(mrb, gc, MRB_GC_STATE_SWEEP);
  mrb_assert(gc->state == MRB_GC_STATE_SWEEP);

  puts("  in MRB_GC_STATE_SWEEP");
  page = gc->heaps;
  while (page) {
    RVALUE *p = objects(page);
    RVALUE *e = p + MRB_HEAP_PAGE_SIZE;
    while (p<e) {
      if (is_black(&p->as.basic)) {
        live++;
      }
      if (is_gray(&p->as.basic) && !is_dead(gc, &p->as.basic)) {
        printf("%p\n", &p->as.basic);
      }
      p++;
    }
    page = page->next;
    total += MRB_HEAP_PAGE_SIZE;
  }

  mrb_assert(gc->gray_list == NULL);

  incremental_gc(mrb, gc, max);
  mrb_assert(gc->state == MRB_GC_STATE_SWEEP);

  incremental_gc(mrb, gc, max);
  mrb_assert(gc->state == MRB_GC_STATE_ROOT);

  free = (RVALUE*)gc->heaps->freelist;
  while (free) {
   freed++;
   free = (RVALUE*)free->as.free.next;
  }

  mrb_assert(gc->live == live);
  mrb_assert(gc->live == total-freed);

  puts("test_incremental_gc(gen)");
  incremental_gc_until(mrb, gc, MRB_GC_STATE_SWEEP);
  change_gen_gc_mode(mrb, gc, TRUE);

  mrb_assert(gc->full == FALSE);
  mrb_assert(gc->state == MRB_GC_STATE_ROOT);

  puts("  in minor");
  mrb_assert(is_minor_gc(gc));
  mrb_assert(gc->majorgc_old_threshold > 0);
  gc->majorgc_old_threshold = 0;
  mrb_incremental_gc(mrb);
  mrb_assert(gc->full == TRUE);
  mrb_assert(gc->state == MRB_GC_STATE_ROOT);

  puts("  in major");
  mrb_assert(is_major_gc(gc));
  do {
    mrb_incremental_gc(mrb);
  } while (gc->state != MRB_GC_STATE_ROOT);
  mrb_assert(gc->full == FALSE);

  mrb_close(mrb);
}