Skip to content

Security relevant heap corruption while garbage collecting bound functions (001) #1970

Closed
@corporateshill

Description

@corporateshill

There's an exploitable heap corruption bug, where the garbage collector runs off the end of a bound function object and enters whatever else is after that. Owing to a sanity check on the arguments, this is super exploitable although I don't have an exploit for it.

On Ubuntu 16.04.2,

$ python tools/build.py --compile-flag=-m32 --clean --jerry-libc=OFF --system-allocator ON --compile-flag=-ggdb3 --debug --link-lib mcheck

$ cat x.js

Function.prototype.bind({0:0},30000000000);

$ cat mcheck.gdb
break main
commands
call mcheck(0)
c
end
run

$ MALLOC_CHECK_=1 gdb -q -x mcheck.gdb --args ./build/bin/jerry x.js
Reading symbols from ./build/bin/jerry...done.
Breakpoint 1 at 0x80ac9a8: file /afl/jerryscript/jerry-main/main-unix.c, line 410.

Breakpoint 1, main (argc=0x2, argv=0xffffd654) at /afl/jerryscript/jerry-main/main-unix.c:410
410 {
$1 = 0x0

Program received signal SIGSEGV, Segmentation fault.
0x080a8d7f in ecma_gc_set_object_visited.lto_priv.626 (object_p=0x93939390, is_visited=0x1) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:115
115 object_p->type_flags_refs = (uint16_t) (object_p->type_flags_refs & ~ECMA_OBJECT_FLAG_GC_VISITED);
(gdb) where
#0 0x080a8d7f in ecma_gc_set_object_visited.lto_priv.626 (object_p=0x93939390, is_visited=0x1) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:115
#1 0x080a2731 in ecma_gc_mark (object_p=0x837c150) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:348
#2 0x080a3054 in ecma_gc_run (severity=JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:732
#3 0x080a3220 in ecma_free_unused_memory (severity=JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:828
#4 0x0808be2e in jmem_run_free_unused_memory_callbacks (severity=JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW) at /afl/jerryscript/jerry-core/jmem/jmem-allocator.c:148
#5 0x0808c0b2 in jmem_heap_gc_and_alloc_block (size=0x8, ret_null_on_error=0x0) at /afl/jerryscript/jerry-core/jmem/jmem-heap.c:359
#6 0x0808c175 in jmem_heap_alloc_block (size=0x8) at /afl/jerryscript/jerry-core/jmem/jmem-heap.c:408
#7 0x0808bec9 in jmem_pools_alloc (size=0x8) at /afl/jerryscript/jerry-core/jmem/jmem-poolman.c:102
#8 0x0809bfb6 in ecma_alloc_number () at /afl/jerryscript/jerry-core/ecma/base/ecma-alloc.c:83
#9 0x0809b2fd in ecma_create_float_number (ecma_number=300000000) at /afl/jerryscript/jerry-core/ecma/base/ecma-helpers-value.c:384
#10 0x0809b961 in ecma_copy_value (value=0x837c0f1) at /afl/jerryscript/jerry-core/ecma/base/ecma-helpers-value.c:639
#11 0x0809ba79 in ecma_copy_value_if_not_object (value=0x837c0f1) at /afl/jerryscript/jerry-core/ecma/base/ecma-helpers-value.c:683
#12 0x08074bd2 in ecma_builtin_function_prototype_object_bind (this_arg=0x837bb93, arguments_list_p=0xffffd38c, arguments_number=0x2)
at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.c:273
#13 0x08074513 in ecma_builtin_function_prototype_dispatch_routine (builtin_routine_id=0x25, this_arg_value=0x837bb93, arguments_list=0xffffd38c, arguments_number=0x2)
at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.inc.h:43
#14 0x080996c3 in ecma_builtin_dispatch_routine (builtin_object_id=ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE, builtin_routine_id=0x25, this_arg_value=0x837bb93, arguments_list=0xffffd38c,
arguments_number=0x2) at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtins.inc.h:108
#15 0x08099a87 in ecma_builtin_dispatch_call (obj_p=0x837beb0, this_arg_value=0x837bb93, arguments_list_p=0xffffd38c, arguments_list_len=0x2)
at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtins.c:844
#16 0x08091f9e in ecma_op_function_call (func_obj_p=0x837beb0, this_arg_value=0x837bb93, arguments_list_p=0xffffd38c, arguments_list_len=0x2)
at /afl/jerryscript/jerry-core/ecma/operations/ecma-function-object.c:458
#17 0x08085ce4 in opfunc_call.lto_priv.408 (frame_ctx_p=0xffffd3b4) at /afl/jerryscript/jerry-core/vm/vm.c:411
#18 0x0807db36 in vm_execute (frame_ctx_p=0xffffd3b4, arg_p=0x0, arg_list_len=0x0) at /afl/jerryscript/jerry-core/vm/vm.c:2746
#19 0x0807dcfe in vm_run (bytecode_header_p=0x837bfe8, this_binding_value=0x837bafb, lex_env_p=0x837bb30, is_eval_code=0x0, arg_list_p=0x0, arg_list_len=0x0)
at /afl/jerryscript/jerry-core/vm/vm.c:2826
#20 0x08085939 in vm_run_global (bytecode_p=0x837bfe8) at /afl/jerryscript/jerry-core/vm/vm.c:231
#21 0x080ae327 in jerry_run (func_val=0x837be0b) at /afl/jerryscript/jerry-core/api/jerry.c:425
#22 0x080ad1ce in main (argc=0x2, argv=0xffffd654) at /afl/jerryscript/jerry-main/main-unix.c:691
(gdb)

The garbage collector is expecting a bound function to have an argument list, and it doesn't. Might have been an errant copy-paste between the compilation vs. the garbage collector.

More details from a similar debugging session:

Program received signal SIGSEGV, Segmentation fault.
0x080a8d7b in ecma_gc_set_object_visited.lto_priv.626 (object_p=0x93939390, is_visited=true) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:111
111 object_p->type_flags_refs = (uint16_t) (object_p->type_flags_refs | ECMA_OBJECT_FLAG_GC_VISITED);
(gdb) where
#0 0x080a8d7b in ecma_gc_set_object_visited.lto_priv.626 (object_p=0x93939390, is_visited=true) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:111
#1 0x080a2740 in ecma_gc_mark (object_p=0x82d7cf8) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:348
#2 0x080a3063 in ecma_gc_run (severity=JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:732
#3 0x080a322f in ecma_free_unused_memory (severity=JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW) at /afl/jerryscript/jerry-core/ecma/base/ecma-gc.c:828
#4 0x0808be3d in jmem_run_free_unused_memory_callbacks (severity=JMEM_FREE_UNUSED_MEMORY_SEVERITY_LOW) at /afl/jerryscript/jerry-core/jmem/jmem-allocator.c:148
#5 0x0808c0c1 in jmem_heap_gc_and_alloc_block (size=8, ret_null_on_error=false) at /afl/jerryscript/jerry-core/jmem/jmem-heap.c:359
#6 0x0808c184 in jmem_heap_alloc_block (size=8) at /afl/jerryscript/jerry-core/jmem/jmem-heap.c:408
#7 0x0808bed8 in jmem_pools_alloc (size=8) at /afl/jerryscript/jerry-core/jmem/jmem-poolman.c:102
#8 0x0809bfc5 in ecma_alloc_number () at /afl/jerryscript/jerry-core/ecma/base/ecma-alloc.c:83
#9 0x0809b30c in ecma_create_float_number (ecma_number=0.10000000000000001) at /afl/jerryscript/jerry-core/ecma/base/ecma-helpers-value.c:384
#10 0x0809b970 in ecma_copy_value (value=137201113) at /afl/jerryscript/jerry-core/ecma/base/ecma-helpers-value.c:639
#11 0x0809ba88 in ecma_copy_value_if_not_object (value=137201113) at /afl/jerryscript/jerry-core/ecma/base/ecma-helpers-value.c:683
#12 0x08074baa in ecma_builtin_function_prototype_object_bind (this_arg=137186523, arguments_list_p=0xffffd00c, arguments_number=158)
at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.c:265
#13 0x08074522 in ecma_builtin_function_prototype_dispatch_routine (builtin_routine_id=37, this_arg_value=137186523, arguments_list=0xffffd00c, arguments_number=158)
at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtin-function-prototype.inc.h:43
#14 0x080996d2 in ecma_builtin_dispatch_routine (builtin_object_id=ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE, builtin_routine_id=37, this_arg_value=137186523, arguments_list=0xffffd00c,
arguments_number=158) at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtins.inc.h:108
#15 0x08099a96 in ecma_builtin_dispatch_call (obj_p=0x82d5730, this_arg_value=137186523, arguments_list_p=0xffffd00c, arguments_list_len=158)
at /afl/jerryscript/jerry-core/ecma/builtin-objects/ecma-builtins.c:844
#16 0x08091fad in ecma_op_function_call (func_obj_p=0x82d5730, this_arg_value=137186523, arguments_list_p=0xffffd00c, arguments_list_len=158)
at /afl/jerryscript/jerry-core/ecma/operations/ecma-function-object.c:458
#17 0x08085cf3 in opfunc_call.lto_priv.408 (frame_ctx_p=0xffffd3c4) at /afl/jerryscript/jerry-core/vm/vm.c:411
#18 0x0807db45 in vm_execute (frame_ctx_p=0xffffd3c4, arg_p=0x0, arg_list_len=0) at /afl/jerryscript/jerry-core/vm/vm.c:2746
#19 0x0807dd0d in vm_run (bytecode_header_p=0x82d6278, this_binding_value=137183339, lex_env_p=0x82d40a0, is_eval_code=false, arg_list_p=0x0, arg_list_len=0)
at /afl/jerryscript/jerry-core/vm/vm.c:2826
#20 0x08085948 in vm_run_global (bytecode_p=0x82d6278) at /afl/jerryscript/jerry-core/vm/vm.c:231
#21 0x080ae336 in jerry_run (func_val=137188227) at /afl/jerryscript/jerry-core/api/jerry.c:425
#22 0x080ad1dd in main (argc=2, argv=0xffffd634) at /afl/jerryscript/jerry-main/main-unix.c:691

ecma_gc_mark (object_p=0x82d7cf8). Well this is easier:

(gdb) print args_p[i]
$7 = 0x93939393

(gdb) print *(ecma_extended_object_t *)(0x82d7cf8)
$10 = {object = {type_flags_refs = 0x75, gc_next_cp = 0x0, property_list_or_bound_object_cp = 0x0, prototype_or_outer_reference_cp = 0x82d4100}, u = {built_in = {id = 0xd8,
length_and_bitset_size = 0x4c, routine_id = 0x82d, instantiated_bitset = {0x9e}}, class_prop = {class_id = 0x4cd8, u = {value = 0x9e, length = 0x9e}}, function = {scope_cp = 0x82d4cd8,
bytecode_cp = 0x9e}, array = {length = 0x82d4cd8, length_prop = 0x9e}, pseudo_array = {type = 0xd8, extra_info = 0x4c, u1 = {length = 0x82d, class_id = 0x82d}, u2 = {lex_env_cp = 0x9e,
arraybuffer = 0x9e}}, bound_function = {target_function = 0x82d4cd8, args_length = 0x9e}, external_handler_cb = 0x82d4cd8}}

So the 0x9e arguments list has come right out of the bytecode:

Breakpoint 7, opfunc_call.lto_priv.408 (frame_ctx_p=0xffffd3c4) at /afl/jerryscript/jerry-core/vm/vm.c:366
366 uint8_t opcode = frame_ctx_p->byte_code_p[0];
(gdb) print opcode
$27 = 0x0
(gdb) step
369 if (opcode >= CBC_CALL0)
(gdb) print opcode
$28 = 0xb2
(gdb) step
375 arguments_list_len = frame_ctx_p->byte_code_p[1];
(gdb) print frame_ctx_p
$29 = (vm_frame_ctx_t ) 0xffffd3c4
(gdb) print frame_ctx_p
$30 = {
bytecode_header_p = 0x82d6278,
byte_code_p = 0x82d678e "\262\236(\025\004\062b
\022\020\t
\r\t'\f\tC)\023\024\062b2\b)\f\t2b(\033\062",
byte_code_start_p = 0x82d6394 "
\001\002\003(\005\274\274\274", <incomplete sequence \314>,
registers_p = 0xffffd000,
stack_top_p = 0xffffd284,
literal_start_p = 0x82d6284,
lex_env_p = 0x82d40a0,
prev_context_p = 0x0,
this_binding = 0x82d406b,
call_block_result = 0x38,
context_depth = 0x0,
is_eval_code = 0x0,
call_operation = 0x1
}
(gdb) print frame_ctx_p->byte_code_p[1];
Invalid character ';' in expression.
(gdb) print frame_ctx_p->byte_code_p[1]
$31 = 0x9e
(gdb)

So now let's watch that... watch ((vm_frame_ctx_t *) 0xffffd3c4)->byte_code_p[1]

Finally some progress:

Hardware watchpoint 4: *0x82d6519

Old value = 0x93939393
New value = 0x9393939e
parser_post_processing (context_p=0xffffd2b8) at /afl/jerryscript/jerry-core/parser/js/js-parser.c:1784
1784 real_offset++;
(gdb)

The value for the arguments length is passed in via the bytecode - it's an opcode (the next one!) and it's stored in js-parser.c, on line 1711:

1710 /* Storing the opcode */
1711 *dst_p++ = opcode;

(gdb) print opcode
$32 = CBC_CALL_PROP_PUSH_RESULT

Okay, so it's something intrinsic with how the types match up between the compiler and the garbage collector.

Other triggers for the same bug:

Function.prototype.bind({0:0},300000000000000000000000,{0:function(){(0)}});

and

l=eval.bind(.1,function(){},800-9900,function(){},909,910,function(){},900,9000,990,908==901)

and

try{String(Number.MAX_VALUE)}catch(r){}assert("a"=="".replace(/$/,"a")),assert("a"=="".replace(/^/,"a"));var eval=eval.bind(.1,0==function(){})

and

y=assert(isNaN(RegExp("")));eval.bind(.1,0,function Error(){},8==9.18,99.100-998399.100,97100,9998,99.100,998,99.100-9980,989,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.18,99.100,99,999,6998,9100,9998,99.100,99,999,9998.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.100-100,99839999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,eval.bind(.1,0,function Error(){},8==9.18,99.100-998399.100,9998,99.100,function x(){},99.100,989,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.18,99.100,99,999,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.100-100,99839999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.18,99.100,99,999,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.100-100,998399-100,9998,99.100,99,999,6998,99.100,898,99.+00,999.100,999.100,98==9100,9998,93.100,99,994,9998,99.100,9988,99.10000,9998998,99.100,9988,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.1098399-100,998399-100,9998100,999.100,998,99.100-100,99839999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,998399-100,9998,99.100,99,999,6998,99.100,998,99.1100,98.100,99,9998,99.100,9),99.18,99.100,99,999,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.100-100,998399-100,9998,99.100,99,999,6998,99.100,898,99.+00,999.100,999.100,98==9100,9998,99.100,99,994,9998,99.100,9988,99.10000,9998998,99.100,9988,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.1098399-100,998399-100,9998100,999.100,999.100,function x(){},999.100,99910,99,9998,99.10098,99.100,999.1,999.1998399-100,998399-100,9998,99.100,99,999,6998,99.100,998,99.1100,98.100,99,9998,99.100,9),9998,99.100,function x(){},999.100,999100,99,9999.100,9998,9999.100,9998,99.100,function x(){},999.100,99910,99,9998,99.100,999.100,9998,99.100,998399-100,998399-100,9998,99.1009,9998,99.100,999.100,9998,99.18,99.100,99,999,6998,9100,9998,99.100,99,999,9998.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.100-100,99839999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,eval.bind(.1,0,function Error(){},8==9.18,99.100-998399.100,9998,99.100,function x(){},99.100,989,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.18,99.100,99,999,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.100-100,99839999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.18,99.100,99,999,6998,9100,9998,99.100,99,999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.100-100,998399-100,9998,99.100,99,999,6998,99.100,898,99.+00,999.100,999.100,98==9100,9998,93.100,99,994,9998,99.100,9988,99.10000,9998998,99.100,9988,99.10000,9998,99.100,function x(){},999.100,999100,99,9998,99.100,999.100,9998,99.1098399-100,998399-100,9998100,999.100,998,99.100-100,99839999,9998,99.100,998,99.10000,9998,99.100,function x(){},999.100,998399-100,9998,99.100,99,999,6998,99.100,998,99.1100,98.100,99,9998,99.100,9),99.18,99(999,6998,99.100,998,99.100,999.99,9998,99.100,function x(){},999.100,999100,99,9,9998,99.100,99,994,1)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugUndesired behaviourcriticalRaises security concerns

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions