Loading HuntDB...

Double free of filename after codegen error

S
shopify-scripts
Submitted None
Reported by titanous

Vulnerability Details

Technical details and impact analysis

Memory Corruption - Generic
The following program causes a double free of `irep->filename` after a codgen error is triggered. I've poked at it a bit and it doesn't seem exploitable because the second free happens near the end of the program and there don't appear to be any overflows or useful heap control available. However, I'm not particularly skilled at this, so I may be wrong. ```ruby def b def c a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a>>=a end end ``` This is what it looks like when run by mruby-engine: ```text bin/sandbox:20:in `sandbox_eval': user memory error (state: 0x00000101848010, chunk: 0x00000101875680) (MRubyEngine::EngineInternalError) 0x00000101738e2d : (me_host_internal_error_new+0x189) [0x00000101738e2d] 0x00000101735722 : (mspace_free+0x1634) [0x00000101735722] 0x00000101739599 : (mruby_engine_allocf+0x57) [0x00000101739599] 0x0000010175c756 : (mrb_irep_free+0x342) [0x0000010175c756] 0x0000010175c726 : (mrb_irep_free+0x294) [0x0000010175c726] 0x00000101775901 : (mrb_generate_code+0x161) [0x00000101775901] 0x00000101739845 : (generate_code+0x245) [0x00000101739845] 0x0000010173972b : (me_mruby_engine_generate_code+0x75) [0x0000010173972b] 0x00000101737f32 : (ext_mruby_engine_eval+0x114) [0x00000101737f32] 0x000001010a55a4 : (vm_call_cfunc+0x1764) [0x000001010a55a4] 0x000001010a4692 : (vm_call_method+0x882) [0x000001010a4692] 0x00000101088820 : (vm_exec_core+0x13920) [0x00000101088820] 0x00000101098811 : (vm_exec+0x129) [0x00000101098811] 0x00000101099918 : (rb_iseq_eval_main+0x504) [0x00000101099918] 0x00000100f55434 : (ruby_exec_internal+0x148) [0x00000100f55434] 0x00000100f5535e : (ruby_run_node+0x78) [0x00000100f5535e] 0x00000100f0a06f : (main+0x79) [0x00000100f0a06f] from bin/sandbox:20:in `<main>' bin/sandbox: unexpected return ``` Here's the ASAN report: ```text codegen error:-:1: too complex expression ================================================================= ==22527==ERROR: AddressSanitizer: attempting double-free on 0x60200000db90 in thread T0: #0 0x4c4520 in free (/vagrant/bin/mruby+0x4c4520) #1 0x5bfc0b in mrb_default_allocf /vagrant/src/state.c:56:5 #2 0x551427 in mrb_free /vagrant/src/gc.c:268:3 #3 0x5c043f in mrb_irep_free /vagrant/src/state.c:162:3 #4 0x5bfe8a in mrb_irep_decref /vagrant/src/state.c:133:5 #5 0x5c0338 in mrb_irep_free /vagrant/src/state.c:158:5 #6 0x5bfe8a in mrb_irep_decref /vagrant/src/state.c:133:5 #7 0x6a732b in mrb_generate_code /vagrant/mrbgems/mruby-compiler/core/codegen.c:2960:5 #8 0x673102 in mrb_load_exec /vagrant/mrbgems/mruby-compiler/core/parse.y:5732:10 #9 0x674815 in mrb_load_file_cxt /vagrant/mrbgems/mruby-compiler/core/parse.y:5764:10 #10 0x4f3af5 in main /vagrant/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c:232:11 #11 0x7fc4a028cf44 in __libc_start_main /build/eglibc-oGUzwX/eglibc-2.19/csu/libc-start.c:287 #12 0x41a505 in _start (/vagrant/bin/mruby+0x41a505) 0x60200000db90 is located 0 bytes inside of 2-byte region [0x60200000db90,0x60200000db92) freed by thread T0 here: #0 0x4c4520 in free (/vagrant/bin/mruby+0x4c4520) #1 0x5bfc0b in mrb_default_allocf /vagrant/src/state.c:56:5 #2 0x551427 in mrb_free /vagrant/src/gc.c:268:3 #3 0x5c043f in mrb_irep_free /vagrant/src/state.c:162:3 #4 0x5bfe8a in mrb_irep_decref /vagrant/src/state.c:133:5 #5 0x5c0338 in mrb_irep_free /vagrant/src/state.c:158:5 #6 0x5bfe8a in mrb_irep_decref /vagrant/src/state.c:133:5 #7 0x5c0338 in mrb_irep_free /vagrant/src/state.c:158:5 #8 0x5bfe8a in mrb_irep_decref /vagrant/src/state.c:133:5 #9 0x6a732b in mrb_generate_code /vagrant/mrbgems/mruby-compiler/core/codegen.c:2960:5 #10 0x673102 in mrb_load_exec /vagrant/mrbgems/mruby-compiler/core/parse.y:5732:10 #11 0x674815 in mrb_load_file_cxt /vagrant/mrbgems/mruby-compiler/core/parse.y:5764:10 #12 0x4f3af5 in main /vagrant/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c:232:11 #13 0x7fc4a028cf44 in __libc_start_main /build/eglibc-oGUzwX/eglibc-2.19/csu/libc-start.c:287 previously allocated by thread T0 here: #0 0x4c4c0d in realloc (/vagrant/bin/mruby+0x4c4c0d) #1 0x5bfc25 in mrb_default_allocf /vagrant/src/state.c:60:12 #2 0x550336 in mrb_realloc_simple /vagrant/src/gc.c:201:8 #3 0x550984 in mrb_realloc /vagrant/src/gc.c:215:8 #4 0x5512c3 in mrb_malloc /vagrant/src/gc.c:236:10 #5 0x5f963d in sym_intern /vagrant/src/symbol.c:81:17 #6 0x5f8ea6 in mrb_intern /vagrant/src/symbol.c:95:10 #7 0x5f97fb in mrb_intern_cstr /vagrant/src/symbol.c:107:10 #8 0x5147fe in mrb_define_method /vagrant/src/class.c:402:32 #9 0x5914c3 in mrb_init_numeric /vagrant/src/numeric.c:1281:3 #10 0x6a23a4 in mrb_init_core /vagrant/src/init.c:46:3 #11 0x5bfbc5 in mrb_open_core /vagrant/src/state.c:47:3 #12 0x5bfd6c in mrb_open_allocf /vagrant/src/state.c:107:20 #13 0x5bfd3a in mrb_open /vagrant/src/state.c:99:20 #14 0x4f29d3 in main /vagrant/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c:172:20 #15 0x7fc4a028cf44 in __libc_start_main /build/eglibc-oGUzwX/eglibc-2.19/csu/libc-start.c:287 SUMMARY: AddressSanitizer: double-free (/vagrant/bin/mruby+0x4c4520) in free ==22527==ABORTING ``` The `MRB_CATCH` block in `mrb_generate_code` tries to avoid this by nulling the filename, but it doesn't take into account that ireps can be nested and so the filename in the nested irep gets freed even though it was not allocated for the irep. Here is a patch that fixes the issue, and a patch that fixes a memory leak due to `s->iseq` not being cleaned up by the error handler: ```diff From 44e50aa439acf092af28b3f5edd4ad3314c87106 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg <[email protected]> Date: Fri, 23 Dec 2016 18:22:43 -0500 Subject: [PATCH 1/2] Fix double free of irep->filename after codegen error --- include/mruby/irep.h | 1 + mrbgems/mruby-compiler/core/codegen.c | 4 +--- src/state.c | 5 ++++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/mruby/irep.h b/include/mruby/irep.h index 8922f4b7..35ae2bba 100644 --- a/include/mruby/irep.h +++ b/include/mruby/irep.h @@ -39,6 +39,7 @@ typedef struct mrb_irep { struct mrb_locals *lv; /* debug info */ + mrb_bool own_filename; const char *filename; uint16_t *lines; struct mrb_irep_debug_info* debug_info; diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index fc54a064..3bae67c6 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -2847,6 +2847,7 @@ scope_finish(codegen_scope *s) memcpy(fname, s->filename, fname_len); fname[fname_len] = '\0'; irep->filename = fname; + irep->own_filename = TRUE; } irep->nlocals = s->nlocals; @@ -2954,9 +2955,6 @@ mrb_generate_code(mrb_state *mrb, parser_state *p) return proc; } MRB_CATCH(&scope->jmp) { - if (scope->filename == scope->irep->filename) { - scope->irep->filename = NULL; - } mrb_irep_decref(mrb, scope->irep); mrb_pool_close(scope->mpool); return NULL; diff --git a/src/state.c b/src/state.c index 1259ac3a..11b71dd6 100644 --- a/src/state.c +++ b/src/state.c @@ -159,7 +159,9 @@ mrb_irep_free(mrb_state *mrb, mrb_irep *irep) } mrb_free(mrb, irep->reps); mrb_free(mrb, irep->lv); - mrb_free(mrb, (void *)irep->filename); + if (irep->own_filename) { + mrb_free(mrb, (void *)irep->filename); + } mrb_free(mrb, irep->lines); mrb_debug_info_free(mrb, irep->debug_info); mrb_free(mrb, irep); @@ -261,6 +263,7 @@ mrb_add_irep(mrb_state *mrb) irep = (mrb_irep *)mrb_malloc(mrb, sizeof(mrb_irep)); *irep = mrb_irep_zero; irep->refcnt = 1; + irep->own_filename = FALSE; return irep; } -- 2.11.0 ``` ```diff From ee8cfffe308f40aa31fb78a8b8f624b1439a7a7f Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg <[email protected]> Date: Fri, 23 Dec 2016 18:23:27 -0500 Subject: [PATCH 2/2] Fix leak of state->iseq during codegen error --- mrbgems/mruby-compiler/core/codegen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index 3bae67c6..b808cfb7 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -93,6 +93,7 @@ codegen_error(codegen_scope *s, const char *message) if (!s) return; while (s->prev) { codegen_scope *tmp = s->prev; + mrb_free(s->mrb, s->iseq); mrb_pool_close(s->mpool); s = tmp; } -- 2.11.0 ```

Report Details

Additional information and metadata

State

Closed

Substate

Resolved

Bounty

$200.00

Submitted

Weakness

Memory Corruption - Generic