The Universal Binary of Crashiness
June 9th, 2008 by Joe RanieriWhat would you say to an application that crashes Finder to the point of being useless? One that merely has to be downloaded, and not even run? I present NukeApp.
For those who don’t feel like experiencing this firsthand, the behavior will vary depending on your Safari download settings. If you’ve set it automatically open downloaded files, Safari will crash and the world will be happy. If you have that option turned off, and you open the zip file up in Finder, Finder will enter a crash loop.
View a movie of the experience.
So, on to the technical details. I’ll start off by saying that this binary has been specifically crafted and is not a valid universal binary — it lies horribly about the number of architectures it contains.
Let’s take a look at the stack trace for where it dies (only showing the top few frames):
_LSAddExecutableFormatInfo _LSAddBundleExecutableInfo _LSRegisterDirectoryNode _LSFindOrRegisterBundleNode _LSCopyInfoForNode _LSCopyItemInfoForRefInfo
Aha, it’s LaunchServices giving us the trouble! It looks like Finder is asking it for information about the application and LaunchServices is registering the application in its database. From the looks of it, it’s trying to determine the format of the executable. A closer look at the top function reveals that it’s opening the file and reading it in:
movl 0xfffffb08(%ebp),%eax leal 0xfffffd34(%ebp),%edx movl $0x00000200,0x08(%esp) movl %edx,0xfffffafc(%ebp) movl %edx,0x04(%esp) movl %eax,(%esp) calll read$UNIX2003
You’ll notice it’s only reading 512 (0×200) bytes. A bit after this it checks the first long in the data to see which type of executable it is - Mach-O, Mach-O 64-bit, PEF, or a fat binary. If we go down the function a bit more, you’ll notice there’s some strings for each arch type (ppc, ppc64, i386, x86_64). So, what it must be doing is looping through each fat_arch that follows the fat_header. However, since we’re getting crashes, it must not be making sure that it stops reading at the end of its buffer.
Here’s an example that suffers the same issue (ignoring endian issues):
int file = open("/path/to/binary") char buffer[512]; read(buffer, sizeof(buffer), file); fat_header *header = buffer; fat_arch *archs = buffer + sizeof(fat_header); for(int i = 0; i < header->nfat_archs; i++) { // do something with archs[i] }
This situation comes up in a few diferent places… so how do they handle it?:
- CFBundle restricts nfat_archs count to the most that fits in the buffer
- the kernel loader returns error if the archs exceed the size of its buffer (one page size)
I’ll also note that this is not exploitable (it ONLY reads 512 bytes into a 512 byte buffer) and occurs in every version of OS X we could lay our hands on (10.4, 10.5, etc). You cannot inject code into Finder, the Dock, or any other process that crashes in this routine. It’s simply a crash.
We reported this to Apple February 28th (radar 5771210), and have received no response.
Post updated June 9th, with video and slightly better explanation.