Invalid memory write caused by incorrect upper bound in array_copy
Low
S
shopify-scripts
Submitted None
Actions:
Reported by
haquaman
Vulnerability Details
Technical details and impact analysis
Ok, here is a weird one!
It only crashes in mruby, and can't be made to crash in mruby-engine as max memory possible to extend to is 256M which this requires more.
The file is:
```
values = [3,5,8]
test = [1,6]
results,= [1.2]
values.each do |value|
case value
when *test
results << value
when *test*= results <<=value
end
end
```
```
$ ./dev/bin/mruby crash.rb
Segmentation fault: 11
```
```
$ lldb ./dev/bin/mruby crash.rb
(lldb) target create "./dev/bin/mruby"
Current executable set to './dev/bin/mruby' (x86_64).
(lldb) settings set -- target.run-args "crash.rb"
(lldb) r
Process 93838 launched: './dev/bin/mruby' (x86_64)
Process 93838 stopped
* thread #1: tid = 0x78effc5, 0x000000010000222d mruby`array_copy + 61, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x16f000000)
frame #0: 0x000000010000222d mruby`array_copy + 61
mruby`array_copy:
-> 0x10000222d <+61>: movq %rdx, (%rax)
0x100002230 <+64>: movq 0x8(%rcx), %rcx
0x100002234 <+68>: movq %rcx, 0x8(%rax)
0x100002238 <+72>: movl -0x18(%rbp), %eax
(lldb) bt
* thread #1: tid = 0x78effc5, 0x000000010000222d mruby`array_copy + 61, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x16f000000)
* frame #0: 0x000000010000222d mruby`array_copy + 61
frame #1: 0x0000000100004437 mruby`mrb_ary_times + 295
frame #2: 0x000000010003fc13 mruby`mrb_vm_exec + 6739
frame #3: 0x000000010003e1a7 mruby`mrb_vm_run + 135
frame #4: 0x0000000100046604 mruby`mrb_top_run + 100
frame #5: 0x0000000100071adf mruby`load_exec + 1183
frame #6: 0x0000000100071623 mruby`mrb_load_file_cxt + 67
frame #7: 0x00000001000017d8 mruby`main + 904
frame #8: 0x00007fff8a9db5ad libdyld.dylib`start + 1
(lldb) register read
General Purpose Registers:
rax = 0x000000016f000000
rbx = 0x0000000000000000
rcx = 0x000000010082a600
rdx = 0x0000000000000001
rdi = 0x000000016eff6800
rsi = 0x0000000100820e00
rbp = 0x00007fff5fbfc9d0
rsp = 0x00007fff5fbfc9d0
r8 = 0x0000000100110000
r9 = 0x0000000000000003
r10 = 0x0000000000000000
r11 = 0x0000000000000246
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x000000010000222d mruby`array_copy + 61
rflags = 0x0000000000010206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
(lldb) q
Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] y
```
A lldb process that we have symbols compiled in:
```
$ lldb ./mruby/bin/mruby crash.rb
(lldb) target create "./mruby/bin/mruby"
Current executable set to './mruby/bin/mruby' (x86_64).
(lldb) settings set -- target.run-args "crash.rb"
(lldb) r
Process 30719 launched: './mruby/bin/mruby' (x86_64)
Process 30719 stopped
* thread #1: tid = 0x78f948b, 0x0000000100001c5d mruby`array_copy(dst=0x000000016e7f6800, src=0x0000000101825c00, size=5184) + 61 at array.c:74, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x16e800000)
frame #0: 0x0000000100001c5d mruby`array_copy(dst=0x000000016e7f6800, src=0x0000000101825c00, size=5184) + 61 at array.c:74
71 mrb_int i;
72
73 for (i = 0; i < size; i++) {
-> 74 dst[i] = src[i];
75 }
76 }
77
(lldb) up
frame #1: 0x0000000100003e67 mruby`mrb_ary_times(mrb=0x0000000100300390, self=mrb_value @ 0x00007fff5fbfca10) + 295 at array.c:350
347 a2 = ary_new_capa(mrb, a1->len * times);
348 ptr = a2->ptr;
349 while (times--) {
-> 350 array_copy(ptr, a1->ptr, a1->len);
351 ptr += a1->len;
352 a2->len += a1->len;
353 }
(lldb) p times
(mrb_int) $0 = 51781
(lldb) bt
* thread #1: tid = 0x78f948b, 0x0000000100001c5d mruby`array_copy(dst=0x000000016e7f6800, src=0x0000000101825c00, size=5184) + 61 at array.c:74, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x16e800000)
frame #0: 0x0000000100001c5d mruby`array_copy(dst=0x000000016e7f6800, src=0x0000000101825c00, size=5184) + 61 at array.c:74
* frame #1: 0x0000000100003e67 mruby`mrb_ary_times(mrb=0x0000000100300390, self=mrb_value @ 0x00007fff5fbfca10) + 295 at array.c:350
frame #2: 0x000000010003f643 mruby`mrb_vm_exec(mrb=0x0000000100300390, proc=0x0000000101004df0, pc=0x000000010060f530) + 6739 at vm.c:1165
frame #3: 0x000000010003dbd7 mruby`mrb_vm_run(mrb=0x0000000100300390, proc=0x0000000101004e80, self=mrb_value @ 0x00007fff5fbfd6b0, stack_keep=0) + 135 at vm.c:766
frame #4: 0x0000000100046034 mruby`mrb_top_run(mrb=0x0000000100300390, proc=0x0000000101004e80, self=mrb_value @ 0x00007fff5fbfd720, stack_keep=0) + 100 at vm.c:2458
frame #5: 0x000000010007150f mruby`load_exec(mrb=0x0000000100300390, p=0x0000000101811420, c=0x000000010060ed90) + 1183 at parse.y:5747
frame #6: 0x0000000100071053 mruby`mrb_load_file_cxt(mrb=0x0000000100300390, f=0x00007fff7cda6050, c=0x000000010060ed90) + 67 at parse.y:5756
frame #7: 0x0000000100001208 mruby`main(argc=2, argv=0x00007fff5fbfdb40) + 904 at mruby.c:226
frame #8: 0x00007fff8a9db5ad libdyld.dylib`start + 1
(lldb) q
Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] y
```
So this is showing that we are trying to write to `0x16e800000`, which is within the bounds of the allocation. I added the following patch temporarily to further debug:
```
diff --git a/src/array.c b/src/array.c
index 106353c..a880c61 100644
--- a/src/array.c
+++ b/src/array.c
@@ -346,6 +346,7 @@ mrb_ary_times(mrb_state *mrb, mrb_value self)
}
a2 = ary_new_capa(mrb, a1->len * times);
ptr = a2->ptr;
+ printf("ptr = %p, ptr + len*times = %p\n", ptr, ptr + a1->len * times);
while (times--) {
array_copy(ptr, a1->ptr, a1->len);
ptr += a1->len;
```
which gives the output of `ptr = 0x106aee000, ptr + len*times = 0x2732ee000` just before the segfault, which clearly shows that the allocation includes the address it is failing on.
So here is where I get lost, as I was confused why realloc didn't fail when it couldn't allocate the memory... BUT I figured out what was wrong. When checking possible size, we are using the `ARY_MAX_SIZE` macro, which is set to either `SIZE_MAX / sizeof(mrb_value)` or `MRB_INT_MAX`, whichever is smallest. Now you can see here that one divides by the size of `mrb_value`, and one doesn't. On my system (64bit), the MRB_INT_MAX is smaller, so it is using that. I suggest the following patch to fix this, by dividing that by the size of `mrb_value` as well.
```
diff --git a/src/array.c b/src/array.c
index 106353c..ed2b346 100644
--- a/src/array.c
+++ b/src/array.c
@@ -14,7 +14,8 @@
#define ARY_DEFAULT_LEN 4
#define ARY_SHRINK_RATIO 5 /* must be larger than 2 */
#define ARY_C_MAX_SIZE (SIZE_MAX / sizeof(mrb_value))
-#define ARY_MAX_SIZE ((ARY_C_MAX_SIZE < (size_t)MRB_INT_MAX) ? (mrb_int)ARY_C_MAX_SIZE : MRB_INT_MAX-1)
+#define ARY_INT_MAX_SIZE ((size_t)MRB_INT_MAX / sizeof(mrb_value))
+#define ARY_MAX_SIZE ((ARY_C_MAX_SIZE < ARY_INT_MAX_SIZE) ? (mrb_int)ARY_C_MAX_SIZE : (mrb_int)ARY_INT_MAX_SIZE)
static struct RArray*
ary_new_capa(mrb_state *mrb, mrb_int capa)
```
Applying this patch no longer crashes, and instead complains that the array size is too big.
```
trace:
[0] crash.rb:8:in Object.call
[1] /Users/<snip>/mruby-engine/mruby/mrblib/array.rb:17:in Array.each
[2] crash.rb:4
crash.rb:8: array size too big (ArgumentError)
```
Cheers,
Hugh
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Bounty
$1000.00
Submitted
Weakness
Uncontrolled Resource Consumption