2

So I have a method that has 5 arguments. As expected, the registers state right before it's called:

$rdi: The receiver
$rsi: the selector for the method
$rdx: first arg
$rcx: second arg
$r8: third arg
$r9: fourth arg
$r10 fifth arg

Within the method, the first thing it does is call another objective-c method

This in turn calls objc_msgSend (see offset +58):

MyApp`-[GTMOAuth2WindowController webView:resource:willSendRequest:redirectResponse:fromDataSource:]:
    0x10044a1a0 <+0>:   pushq  %rbp
    0x10044a1a1 <+1>:   movq   %rsp, %rbp
    0x10044a1a4 <+4>:   subq   $0x40, %rsp
    0x10044a1a8 <+8>:   movq   0x10(%rbp), %rax
    0x10044a1ac <+12>:  movq   %rdi, -0x10(%rbp)
    0x10044a1b0 <+16>:  movq   %rsi, -0x18(%rbp)
    0x10044a1b4 <+20>:  movq   %rdx, -0x20(%rbp)
    0x10044a1b8 <+24>:  movq   %rcx, -0x28(%rbp)
    0x10044a1bc <+28>:  movq   %r8, -0x30(%rbp)
    0x10044a1c0 <+32>:  movq   %r9, -0x38(%rbp)
    0x10044a1c4 <+36>:  movq   %rax, -0x40(%rbp)
    0x10044a1c8 <+40>:  movq   -0x10(%rbp), %rax
    0x10044a1cc <+44>:  movq   -0x38(%rbp), %rdx
    0x10044a1d0 <+48>:  movq   0x2ffda9(%rip), %rsi      ; "handleCookiesForResponse:"
    0x10044a1d7 <+55>:  movq   %rax, %rdi
    0x10044a1da <+58>:  callq  0x1005839a2               ; symbol stub for: objc_msgSend

which then goes to the instructions for objc_msgSend:

libobjc.A.dylib`objc_msgSend:
->  0x7fff9084a0c0 <+0>:   testq  %rdi, %rdi
    0x7fff9084a0c3 <+3>:   je     0x7fff9084a140            ; <+128>
    0x7fff9084a0c6 <+6>:   testb  $0x1, %dil
    0x7fff9084a0ca <+10>:  jne    0x7fff9084a14b            ; <+139>
    0x7fff9084a0cd <+13>:  movabsq $0x7ffffffffff8, %r11
    0x7fff9084a0d7 <+23>:  andq   (%rdi), %r11
    0x7fff9084a0da <+26>:  movq   %rsi, %r10
    0x7fff9084a0dd <+29>:  andl   0x18(%r11), %r10d

and I sometimes crash on offset +29, when the cpu tries to dereference the %r11 register.

My question is, why is objc_msgSend dereferencing that register? According to the System V ABI that is a scratch register. But it's dereferenced everytime objc_msgSend, and I can't really figure out what it's used for.

My crash is happening when there is an invalid pointer in %r11

It looks like at +23, the %rdi register (pointer to receiver) is dereferenced and andq'd with %r11, but I don't get what that does. But perhaps if the receiver was deallocated here, %r11 would be filled with junk?

This theory is corroborated by this assembly source w/ comments

Where I think it states the %r11 is used for the isa property
"class = self->isa".

Which would mean that the object is being released because the isa property is junked

If that were the case, how could I protect against this?

Would a check to see if( self ) before calling objc_msgSend suffice?

Jester
  • 56,577
  • 4
  • 81
  • 125
A O
  • 5,516
  • 3
  • 33
  • 68
  • The very first thing `msgSend` does is the `if (self)` so doing that before calling won't help much. As you say, `self` points to garbage, but unfortunately it is not `NULL`. – Jester Dec 05 '16 at 23:41
  • Is there any way to check if the address of `self` contains a valid object, and not garbage? – A O Dec 06 '16 at 22:54

2 Answers2

5

Unfortunately, the site you linked is out of date. It doesn't explain the exact version of objc_msgSend you're using.

What you need to know to understand the disassembler output is that the Objective-C runtime now has a feature called “non-pointer isa”. Another page on that site explains non-pointer isa, but I'll summarize.

The isa field of an object was, historically, a pointer to the object's class. You don't need a full 64 bits for this pointer, because none of Apple's operating systems uses the full 64-bit address space. Many of the bits of the class's address are always zero.

Instead of wasting all those bits in every object, a non-pointer isa uses the bits for other things, like storing the object's reference count. This means that when you want the pointer to the class, you need to set those other bits back to zero to get a valid address. Computing isa & 0x7ffffffffff8 turns off (masks out) all of the non-pointer bits, so you get a valid pointer to the class…

…if the isa field hasn't been corrupted. If the isa field has been corrupted, you get garbage. If the garbage is an invalid address, you get a crash.

What's happening here is you've overwritten the memory containing the object, such that the isa field is no longer valid.

To debug the problem, read about how to find zombies. If that doesn't help, watch this WWDC video about using the address sanitizer.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks for the thorough answer and links :) The issue is that I can't reproduce this issue, it came in through HockeyApp, so I'm debugging this strictly from looking at the crash logs and assembly. Do you know how I could be overwriting memory that contains the original receiver object? I would've assumed that the system would be able to protect against that-- only writing in memory that has been released? – A O Dec 06 '16 at 06:56
  • There is no protection like that within a single process. ARC (and Swift) help you avoid memory management errors, but you can still make them. I suggest you test your app as much as possible under Address Sanitizer and see if you get any warnings. – rob mayoff Dec 06 '16 at 14:40
  • Thanks for the link. I watched the video, and had another question if you didn't mind. At 25:12-ish, she talks about Shadow Mapping. And explains how Address Sanitizer works, by adding an instruction before every memory access to check if the address is "poisoned" and then **crash** if found true. My question is, if you weren't running the Sanitizer, wouldn't the app crash at that invalid memory access anyways? – A O Dec 06 '16 at 21:31
  • Keep in mind that when an object is deallocated, the memory that was used by the object is (usually) added back to the process's “free space”, and available for use later when the process allocates another object. The address of the memory is still valid as far as the CPU is concerned, so accessing the freed memory will not necessarily crash. – rob mayoff Dec 06 '16 at 22:01
  • But if the free space is still part of the processes valid memory, wouldn't it not be marked as poisoned? So in the event that accessing freed memory wouldn't crash, would the address sanitizer be able to catch that anyways? I'm sure it can, but I'm not sure if I can conclude that from the video. I've still been debugging this bloody bug :( I'll report back with what I can find – A O Dec 06 '16 at 22:05
  • I made a follow-up question about a weird address that I don't understand: http://stackoverflow.com/questions/41006194/clarity-on-what-this-address-comes-from-on-64-bit-macos-application – A O Dec 06 '16 at 22:36
  • The heap (where Objective-C objects live) involves two levels of memory allocation. First, the language runtime allocates a big block of memory from the kernel. Then, each time your program asks the runtime to create an object, the runtime allocates a small chunk out of the big block. So the runtime has to keep track of which parts of the big block are “in use” for live objects, and which are “free”. When your program is done with an object, the runtime moves the small chunk from “in use” back to “free”. Every byte of the big block that is “free” is “poisoned” by asan. – rob mayoff Dec 06 '16 at 22:36
  • Finally got this pegged. Sanitizer didn't give me anything, but Zombies did. It ended up being a bug in Google's code. They had an OAuth NSWindowController that had a property for a `webView`. The `webView`'s ResourceLoadDelegate was set to be the WindowController. But if the WindowController was deallocated, the webView still tried to callback to the WindowController when a resource finished loading. Fix was to set the `webView.delegate = nil` in the `dealloc` – A O Dec 07 '16 at 18:35
2

As you say, %r11 at this point is the isa pointer from self. If self is garbage memory at this point, then it is not surprising that it's first word points to garbage.

To be clear, when you say "Would a check to see if( self ) before calling objc_msgSend suffice?" I assume you don't mean you're calling objc_msgSend yourself. (Never do that.) Checking self before calling this method won't help you if it's garbage. It's a non-0 pointer, so that's true. (If it were 0, we already would have bailed via nil-messaging at the top of objc_msgSend.)

You've trashed this memory somehow. Maybe over-release (though I doubt that in this case). Maybe you have C data structures and smashed your stack (that feels more likely). Maybe the object is being deallocated on another thread? It could be a lot of things.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thanks for your time ^^. It's hard to debug this because I can't reproduce it, I'm going off of crash logs from HockeyApp, and viewing the assembly in Xcode. Could you elaborate on how C data structures can smash a stack, and what that means? Or help me with a link to read? Is this SO thread what you're talking about? http://stackoverflow.com/a/1347464/2415178 – A O Dec 06 '16 at 06:58
  • I said "stack smash" when I really meant "running off the end of a C data structure, usually an array." Given the crash, it'd be more likely on the heap than on the stack. Look at Xcode's "Malloc Guard Edges" to help find that kind of mistake. The other Memory Management options in Profile>Run>Diagnostics can also be helpful for this (see rob mayoff's answer for links). – Rob Napier Dec 06 '16 at 13:56
  • (BTW, what you do know and don't know are really quite unique to me, which suggests that you're studying very hard at a quite advanced level without having a lot of background in the subject. I'm impressed, but it's making it slightly hard to answer. You have a strange mix of understanding of the ABI and reverse engineering objc_msgSend, but not how common C memory management works. Again, I'm impressed, but forgive us if we assume you know more than you might actually and answer at the wrong level.) – Rob Napier Dec 06 '16 at 14:01
  • Yeah I've never dealt with memory management, I went straight into macOS development out of undergrad. We've had crashing issues before that required me to look at the assembly, and I've also used registers to set symbolic breakpoints on `-addObserver` to break on specific places where we add a specific object as an observer. So that's how I'm familiar with that stuff. So my follow up question woul dbe, how do you run off the end of a data struct? Like if I `malloc`'d an int array with 10 bytes. Then tried to access `array[11]`? – A O Dec 06 '16 at 21:48
  • And I will never be offended if you assume I know or don't know something. It helps reinforce my knowledge if I already knew it, and I'll ask if I don't :) Thanks for your time – A O Dec 06 '16 at 21:49
  • I made a follow-up question about a weird address that I don't understand: http://stackoverflow.com/questions/41006194/clarity-on-what-this-address-comes-from-on-64-bit-macos-application – A O Dec 06 '16 at 22:36
  • Your example is exactly right. If you write into an element past the end of the array, you can corrupt some other object. – Rob Napier Dec 06 '16 at 23:01