Skip to content

Instantly share code, notes, and snippets.

@schani
Created May 27, 2015 17:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save schani/059d7aa74ee8e4fe86b6 to your computer and use it in GitHub Desktop.
Save schani/059d7aa74ee8e4fe86b6 to your computer and use it in GitHub Desktop.

We're generating code that calls the managed allocator with an object size of 0. The managed allocator doesn't check for that case and therefore the object allocated after that overwrites that zero-sized allocation.

Here's a debugger session:

Breakpoint 1, major_alloc_heap (nursery_size=4194304, nursery_align=4194304, the_nursery_bits=22) at sgen-marksweep.c:233
233		if (nursery_align)
(gdb) finish
Run till exit from #0  major_alloc_heap (nursery_size=4194304, nursery_align=4194304, the_nursery_bits=22) at sgen-marksweep.c:233
Current language:  auto; currently minimal
0x000000010035a65f in alloc_nursery () at sgen-gc.c:1421
1421		data = major_collector.alloc_heap (alloc_size, alloc_size, DEFAULT_NURSERY_BITS);
Value returned is $1 = (void *) 0x102000000

We've allocated the nursery at 0x102000000.

(gdb) watch *(long*)0x1022de6a0
Hardware watchpoint 2: *(long *) 4331529888

This is a magic number that learned from previous debugging sessions. It's where in many cases the zero-sized object will be allocated.

(gdb) cont
Continuing.
Reading symbols for shared libraries warning: UUID mismatch detected between:
	/Users/schani/Work/mono/installation/lib/mono/4.5/mscorlib.dll.dylib
	/Users/schani/Work/mono/installation/lib/mono/4.5/mscorlib.dll.dylib.dSYM/Contents/Resources/DWARF/mscorlib.dll.dylib...
warning: Could not find object file "/var/folders/1h/7ghts1ys53xdk8y1czrjzdmw0000gn/T/mono_aot_LHzZKh.o" - no debug information available for "mscorlib.dll".

. done
Reading symbols for shared libraries warning: UUID mismatch detected between:
	/Users/schani/Work/mono/installation/lib/mono/4.5/mscorlib.dll.dylib
	/Users/schani/Work/mono/installation/lib/mono/4.5/mscorlib.dll.dylib.dSYM/Contents/Resources/DWARF/mscorlib.dll.dylib...
warning: Could not find object file "/var/folders/1h/7ghts1ys53xdk8y1czrjzdmw0000gn/T/mono_aot_LHzZKh.o" - no debug information available for "mscorlib.dll".

. done
NUnit version 2.4.8
Copyright (C) 2002-2007 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.

Runtime Environment - 
   OS Version: Unix 13.4.0.0
  CLR Version: 4.0.30319.17020 ( 4.0.1 (mono-4.0.0-branch/d64e196 Tue May 26 18:09:16 PDT 2015) )

.[Switching to process 27999 thread 0x400b]
Hardware watchpoint 2: *(long *) 4331529888

Old value = 0
New value = 4372641952
0x0000000105f6b007 in ?? ()
(gdb) p mono_pmip(0x0000000105f6b007)
$2 = 0x105c000b0 " (wrapper alloc) object:AllocSmall (intptr,intptr) + 0x67 (0x105f6afa0 0x105f6b039) [0x101144750 - domain-Test.dll]"

The watchpoint triggered in the managed allocator, so the new value at that address will be the vtable of the zero-sized object.

(gdb) p *((MonoVTable*)4372641952)->klass
$3 = {
  element_class = 0x1049c5888, 
  cast_class = 0x1049c5888, 
  supertypes = 0x1049c5998, 
  idepth = 4, 
  rank = 0 '\0', 
  instance_size = 80, 
  inited = 1, 
  init_pending = 0, 
  size_inited = 1, 
  valuetype = 0, 
  enumtype = 0, 
  blittable = 0, 
  unicode = 0, 
  wastypebuilder = 0, 
  min_align = 8 '\b', 
  packing_size = 0, 
  ghcimpl = 0, 
  has_finalize = 1, 
  marshalbyref = 0, 
  contextbound = 0, 
  delegate = 0, 
  gc_descr_inited = 1, 
  has_cctor = 0, 
  has_references = 1, 
  has_static_refs = 0, 
  no_special_static_fields = 0, 
  is_com_object = 0, 
  nested_classes_inited = 0, 
  interfaces_inited = 1, 
  simd_type = 0, 
  is_generic = 0, 
  is_inflated = 1, 
  has_finalize_inited = 1, 
  fields_inited = 1, 
  setup_fields_called = 1, 
  exception_type = 0 '\0', 
  parent = 0x104987aa8, 
  nested_in = 0x0, 
  image = 0x104997000, 
  name = 0x104819140 "ICrasher_Crash", 
  name_space = 0x104819150 "Castle.Proxies.Invocations", 
  type_token = 33554434, 
  vtable_size = 23, 
  interface_count = 0, 
  interface_id = 0, 
  max_interface_id = 1150, 
  interface_offsets_count = 2, 
  interfaces_packed = 0x1049c59c8, 
  interface_offsets_packed = 0x1049c59d8, 
  interface_bitmap = 0x1049c59e0 " ", 
  interfaces = 0x1049c59c8, 
  sizes = {
    class_size = 0, 
    element_size = 0, 
    generic_param_token = 0
  }, 
  flags = 8193, 
  field = {
    first = 0, 
    count = 0
  }, 
  method = {
    first = 0, 
    count = 0
  }, 
  ref_info_handle = 0, 
  marshal_info = 0x0, 
  fields = 0x1049c5b28, 
  methods = 0x0, 
  this_arg = {
    data = {
      klass = 0x1049c5830, 
      type = 0x1049c5830, 
      array = 0x1049c5830, 
      method = 0x1049c5830, 
      generic_param = 0x1049c5830, 
      generic_class = 0x1049c5830
    }, 
    attrs = 0, 
    type = MONO_TYPE_GENERICINST, 
    num_mods = 0, 
    byref = 1, 
    pinned = 0, 
    modifiers = 0x1049c594c
  }, 
  byval_arg = {
    data = {
      klass = 0x1049c5830, 
      type = 0x1049c5830, 
      array = 0x1049c5830, 
      method = 0x1049c5830, 
      generic_param = 0x1049c5830, 
      generic_class = 0x1049c5830
    }, 
    attrs = 0, 
    type = MONO_TYPE_GENERICINST, 
    num_mods = 0, 
    byref = 0, 
    pinned = 0, 
    modifiers = 0x1049c595c
  }, 
  generic_class = 0x1049c5830, 
  generic_container = 0x0, 
  gc_descr = 0x5fa, 
  runtime_info = 0x1048e9620, 
  next_class_cache = 0x0, 
  vtable = 0x1049c5a70, 
  ext = 0x0
}

It's always this class, Castle.Proxies.Invocations.ICrasher_Crash. Its instance_size is 80.

(gdb) p mono_thread_info_current ()
$4 = (MonoThreadInfo *) 0x1017949e0
(gdb) p *(SgenThreadInfo*)$4
$5 = {
  info = {
    node = {
      next = 0x101002060, 
      key = 4422897664
    }, 
    small_id = 69, 
    native_handle = 16395, 
    thread_state = 1, 
    runtime_thread = 1, 
    tools_thread = 0, 
    suspend_semaphore = 18179, 
    suspend_count = 0, 
    finish_resume_semaphore = 18691, 
    resume_semaphore = 18435, 
    suspend_state = {
      ctx = {
        rax = 0, 
        rbx = 0, 
        rcx = 0, 
        rdx = 0, 
        rbp = 0, 
        rsp = 0, 
        rsi = 0, 
        rdi = 0, 
        r8 = 0, 
        r9 = 0, 
        r10 = 0, 
        r11 = 0, 
        r12 = 0, 
        r13 = 0, 
        r14 = 0, 
        r15 = 0, 
        rip = 0
      }, 
      unwind_data = {0x0, 0x0, 0x0}, 
      valid = 0
    }, 
    async_target = 0, 
    user_data = 0x0, 
    inside_critical_region = 0, 
    is_async_context = 0, 
    create_suspended = 0, 
    create_suspended_sem = 18947, 
    tls = {0x0, 0x1011adfe0, 0x101144750, 0x0, 0x0, 0x0, 0x0, 0x0, 0x107a01910}, 
    handle = 0x117, 
    service_requests = 0
  }, 
  skip = 0, 
  in_critical_region = 0, 
  gc_disabled = 0, 
  stack_end = 0x107a00e74, 
  stack_start = 0x0, 
  stack_start_limit = 0x107801000, 
  tlab_next_addr = 0x101794c28, 
  tlab_start_addr = 0x101794c20, 
  tlab_temp_end_addr = 0x101794c30, 
  tlab_real_end_addr = 0x101794c38, 
  runtime_data = 0x0, 
  stopped_ip = 0x0, 
  stopped_domain = 0x101144750, 
  ctx = {
    rax = 0, 
    rbx = 0, 
    rcx = 0, 
    rdx = 0, 
    rbp = 0, 
    rsp = 0, 
    rsi = 0, 
    rdi = 0, 
    r8 = 0, 
    r9 = 0, 
    r10 = 0, 
    r11 = 0, 
    r12 = 0, 
    r13 = 0, 
    r14 = 0, 
    r15 = 0, 
    rip = 0
  }, 
  tlab_start = 0x1022dd8c0 "0\002\f\005\001", 
  tlab_next = 0x1022de6a0 "?8?\004\001", 
  tlab_temp_end = 0x1022de8c0 "", 
  tlab_real_end = 0x1022de8c0 ""
}

At this point in the object allocation, tlab_next should have already been advanced, but it wasn't. It's still at the point at where this current object is allocated.

(gdb) disassemble 0x105f6afa0 0x105f6b039
Dump of assembler code from 0x105f6afa0 to 0x105f6b039:
0x0000000105f6afa0:	sub    $0x28,%rsp
0x0000000105f6afa4:	mov    %rbx,(%rsp)
0x0000000105f6afa8:	mov    %r12,0x8(%rsp)
0x0000000105f6afad:	mov    %r14,0x10(%rsp)
0x0000000105f6afb2:	mov    %r15,0x18(%rsp)
0x0000000105f6afb7:	mov    %rdi,%r14
0x0000000105f6afba:	mov    %rsi,%r15
0x0000000105f6afbd:	mov    %r15,0x20(%rsp)
0x0000000105f6afc2:	mov    %gs:0x860,%rax
0x0000000105f6afcb:	mov    0x180(%rax),%r12
0x0000000105f6afd2:	mov    %r12,%rax
0x0000000105f6afd5:	mov    (%rax),%rbx
0x0000000105f6afd8:	mov    %rbx,%rax
0x0000000105f6afdb:	mov    %r15,%rcx
0x0000000105f6afde:	mov    %rax,%r15
0x0000000105f6afe1:	add    %rcx,%r15
0x0000000105f6afe4:	mov    %r15,%rax
0x0000000105f6afe7:	mov    %gs:0x860,%rcx
0x0000000105f6aff0:	mov    0x250(%rcx),%rcx
0x0000000105f6aff7:	cmp    %rcx,%rax
0x0000000105f6affa:	jae    0x105f6b022
0x0000000105f6b000:	mov    %r15,(%r12)
0x0000000105f6b004:	mov    %r14,(%rbx)
0x0000000105f6b007:	mov    %rbx,%rax
0x0000000105f6b00a:	mov    (%rsp),%rbx
0x0000000105f6b00e:	mov    0x8(%rsp),%r12
0x0000000105f6b013:	mov    0x10(%rsp),%r14
0x0000000105f6b018:	mov    0x18(%rsp),%r15
0x0000000105f6b01d:	add    $0x28,%rsp
0x0000000105f6b021:	retq   
0x0000000105f6b022:	mov    %r14,%rdi
0x0000000105f6b025:	mov    0x20(%rsp),%rsi
0x0000000105f6b02a:	mov    $0x1036b5890,%r11
0x0000000105f6b034:	callq  *%r11
0x0000000105f6b037:	jmp    0x105f6b00a
End of assembler dump.

This is the managed allocator. Note that the object size is passed as the second argument, in %rsi.

(gdb) p/x $rsi
$6 = 0x0

This is the problem.

(gdb) display/i $pc
1: x/i $pc  0x105f6b007:	mov    %rbx,%rax
(gdb) si
0x0000000105f6b00a in ?? ()
1: x/i $pc  0x105f6b00a:	mov    (%rsp),%rbx
(gdb) 
0x0000000105f6b00e in ?? ()
1: x/i $pc  0x105f6b00e:	mov    0x8(%rsp),%r12
(gdb) 
0x0000000105f6b013 in ?? ()
1: x/i $pc  0x105f6b013:	mov    0x10(%rsp),%r14
(gdb) 
0x0000000105f6b018 in ?? ()
1: x/i $pc  0x105f6b018:	mov    0x18(%rsp),%r15
(gdb) 
0x0000000105f6b01d in ?? ()
1: x/i $pc  0x105f6b01d:	add    $0x28,%rsp
(gdb) 
0x0000000105f6b021 in ?? ()
1: x/i $pc  0x105f6b021:	retq   
(gdb) 
0x0000000106fbf726 in ?? ()
1: x/i $pc  0x106fbf726:	mov    0x70(%rsp),%r9
(gdb) p mono_pmip(0x106fbf726)
$7 = 0x101189fa0 " Castle.Proxies.ICrasherProxy:Crash<T> () + 0xf6 (0x106fbf630 0x106fbf7f6) [0x101144750 - domain-Test.dll]"

This is the method that called the managed allocator.

(gdb) disassemble 0x106fbf630 0x106fbf7f6
Dump of assembler code from 0x106fbf630 to 0x106fbf7f6:
0x0000000106fbf630:	sub    $0x78,%rsp
0x0000000106fbf634:	mov    %rbx,(%rsp)
0x0000000106fbf638:	mov    %r13,0x8(%rsp)
0x0000000106fbf63d:	mov    %r14,0x10(%rsp)
0x0000000106fbf642:	mov    %r15,0x18(%rsp)
0x0000000106fbf647:	mov    %r10,0x40(%rsp)
0x0000000106fbf64c:	mov    %rdi,%r15
0x0000000106fbf64f:	movq   $0x0,0x50(%rsp)
0x0000000106fbf658:	mov    %r15,0x58(%rsp)
0x0000000106fbf65d:	mov    0x10(%r15),%r15
0x0000000106fbf661:	mov    0x40(%rsp),%rdi
0x0000000106fbf666:	mov    $0x100679fb0,%r11
0x0000000106fbf670:	callq  *%r11
0x0000000106fbf673:	mov    %rax,0x38(%rsp)
0x0000000106fbf678:	mov    0x38(%rsp),%rax
0x0000000106fbf67d:	mov    %rax,0x28(%rsp)
0x0000000106fbf682:	mov    $0x1017803d8,%rax
0x0000000106fbf68c:	mov    %rax,0x30(%rsp)
0x0000000106fbf691:	mov    0x30(%rsp),%rax
0x0000000106fbf696:	mov    %rax,0x20(%rsp)
0x0000000106fbf69b:	mov    0x20(%rsp),%rsi
0x0000000106fbf6a0:	mov    0x28(%rsp),%rdi
0x0000000106fbf6a5:	mov    $0x106fbf850,%r11
0x0000000106fbf6af:	callq  *%r11
0x0000000106fbf6b2:	mov    %rax,%rbx
0x0000000106fbf6b5:	test   %rbx,%rbx
0x0000000106fbf6b8:	je     0x106fbf6df
0x0000000106fbf6be:	mov    (%rbx),%rax
0x0000000106fbf6c1:	mov    (%rax),%rax
0x0000000106fbf6c4:	mov    0x10(%rax),%rax
0x0000000106fbf6c8:	mov    0x18(%rax),%rax
0x0000000106fbf6cc:	mov    $0x100829340,%rcx
0x0000000106fbf6d6:	cmp    %rcx,%rax
0x0000000106fbf6d9:	jne    0x106fbf7df
0x0000000106fbf6df:	mov    $0x1050979e0,%rdi
0x0000000106fbf6e9:	xor    %esi,%esi
0x0000000106fbf6eb:	mov    $0x105f6b480,%r11
0x0000000106fbf6f5:	callq  *%r11
0x0000000106fbf6f8:	mov    %rax,0x48(%rsp)
0x0000000106fbf6fd:	mov    %rax,0x70(%rsp)
0x0000000106fbf702:	mov    0x40(%rsp),%rdi
0x0000000106fbf707:	mov    $0x100679f70,%r11
0x0000000106fbf711:	callq  *%r11
0x0000000106fbf714:	mov    %rax,%rdi
0x0000000106fbf717:	xor    %esi,%esi
0x0000000106fbf719:	mov    $0x105f6afa0,%r11
0x0000000106fbf723:	callq  *%r11

This is the call in question. Note that %esi is zeroed here.

0x0000000106fbf726:	mov    0x70(%rsp),%r9
0x0000000106fbf72b:	mov    %rax,0x68(%rsp)
0x0000000106fbf730:	mov    %rax,%rdi
0x0000000106fbf733:	mov    0x50(%rsp),%rsi
0x0000000106fbf738:	mov    0x58(%rsp),%rdx
0x0000000106fbf73d:	mov    %r15,%rcx
0x0000000106fbf740:	mov    %rbx,%r8
0x0000000106fbf743:	mov    $0x106fbf812,%r11
0x0000000106fbf74d:	callq  *%r11
0x0000000106fbf750:	mov    0x68(%rsp),%rax
0x0000000106fbf755:	mov    %rax,%r14
0x0000000106fbf758:	mov    $0x1050c68f0,%rdi
0x0000000106fbf762:	mov    $0x1,%esi
0x0000000106fbf767:	mov    $0x105f6b454,%r11
0x0000000106fbf771:	callq  *%r11
0x0000000106fbf774:	mov    %rax,%r13
0x0000000106fbf777:	mov    %rax,0x60(%rsp)
0x0000000106fbf77c:	mov    0x40(%rsp),%rdi
0x0000000106fbf781:	mov    $0x100679f30,%r11
0x0000000106fbf78b:	callq  *%r11
0x0000000106fbf78e:	mov    %rax,%rdx
0x0000000106fbf791:	mov    0x60(%rsp),%rax
0x0000000106fbf796:	mov    %rax,%rdi
0x0000000106fbf799:	xor    %esi,%esi
0x0000000106fbf79b:	mov    (%rax),%rax
0x0000000106fbf79e:	callq  *0xf8(%rax)
0x0000000106fbf7a4:	mov    %r14,%rdi
0x0000000106fbf7a7:	mov    %r13,%rsi
0x0000000106fbf7aa:	mov    $0x106fbf804,%r11
0x0000000106fbf7b4:	callq  *%r11
0x0000000106fbf7b7:	mov    %r14,%rdi
0x0000000106fbf7ba:	mov    $0x106fbf7f6,%r11
0x0000000106fbf7c4:	callq  *%r11
0x0000000106fbf7c7:	mov    (%rsp),%rbx
0x0000000106fbf7cb:	mov    0x8(%rsp),%r13
0x0000000106fbf7d0:	mov    0x10(%rsp),%r14
0x0000000106fbf7d5:	mov    0x18(%rsp),%r15
0x0000000106fbf7da:	add    $0x78,%rsp
0x0000000106fbf7de:	retq   
0x0000000106fbf7df:	mov    $0x11d,%esi
0x0000000106fbf7e4:	mov    $0x86b,%edi
0x0000000106fbf7e9:	mov    $0x100679b60,%r11
0x0000000106fbf7f3:	callq  *%r11
End of assembler dump.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment