LD37 - Zombie Room Mac OS

Posted on  by

Free download Zombie Area! Zombie Area!puts you in the role of one man alone against the raving hordes of zombies. Apple fans shouldn't get too smug about this because the firewall which shipped with Mac OS X Tiger is just as bad. Jay Beale discovered that even if you enable the “ Block UDP Traffic ” box in the firewall GUI, packets from port 67 (DHCP) and 5,353 (Zeroconf) pass right through. Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. Testing conducted by Apple in October 2020 using preproduction Mac mini systems with Apple M1 chip, and production 3.6GHz quad‑core Intel Core i3‑based Mac mini systems, all configured with 16GB of RAM and 2TB SSD. Open source project built with prerelease Xcode 12.2 with Apple Clang 12.0.0, Ninja 1.10.0.git, and CMake 3.16.5. Business chats, one-click conference calls and shared documents – all protected with end-to-end encryption. Also available for personal use.

  1. Ld37 - Zombie Room Mac Os Catalina
  2. Ld37 - Zombie Room Mac Os Pro
Posted at 2014-11-07 16:04 RSS feed (Full text feed) Blog Index
Next article: Friday Q&A 2015-01-23: Let's Build Swift Notifications
Previous article: A Brief Pause
Tags: braaaiiiinnnssssfridayqnamemoryzombie

Zombies are a valuable tool for debugging memory management problems. I previously discussed the implementation of zombies, and today I'm going to go one step further and build them from scratch, a topic suggested by Шпирко Алексей.

Review
Zombies detect memory management errors. Specifically, they detect the scenario where an Objective-C object is deallocated and then a message is sent using a pointer to where that object used to be. This is a specific case of the general 'use after free' error.

In normal operation, this results in a message being sent to memory that may have been overwritten or returned to the kernel. This results in a crash if the memory has been returned to the kernel, and can result in a crash if the memory has been overwritten. In the case where the memory was overwritten with a new Objective-C object, then the message is sent to that new object which is probably completely unrelated to the original one, which can cause exceptions thrown due to unrecognized selectors or can even cause bizarre misbehaviors if the message is one the object actually responds to.

It's also possible that the memory hasn't been touched and still contains the original object, in a post-dealloc state. This can lead to other interesting and bizarre failures. For example, if the object contains a UNIX file handle, it may call close on a file descriptor twice, which can end up closing a file descriptor owned by some other part of the program, causing a failure far away from the bug.

ARC has greatly reduced the frequency of these errors, but it hasn't eliminated them altogether. These problems can still occur due to problems with multithreading, interactions with non-ARC code, mismatched method declarations, or type system abuse that strips or changes ARC storage modifiers.

Zombies hook into object deallocation. Instead of freeing the underlying memory as the last step in object deallocation, zombies change the object to a new zombie class which intercepts all messages sent to it. Any message sent to a zombie object results in a diagnostic error message instead of the bizarre behavior you get in normal operation. There is also a mode where it rewrites the class and then frees the memory anyway, but this is typically much less useful since the memory will typically get reused quickly, and I'll ignore that option here.

To write our own zombies implementation, we need to hook object deallocation and build the appropriate zombie classes. Let's get started!

Catching All Messages
If we make a root class without any methods, then any message sent to an instance of that class will go into the runtime's forwarding machinery. This would seem to make forwardInvocation: a natural point to catch messages. However, that one happens a bit too late. Before forwardInvocation: can run, the runtime needs a method signature to construct an NSInvocation object, and that means that methodSignatureForSelector: runs first. This, then, is the override point for catching messages sent to a zombie object.

Dynamically Allocated Classes
In addition to the selector that was sent, zombies also remember the original class of the object. However, there may not be any room in the object's memory to store a reference to that original class. If the original class had no additional instance variables, then there's no space that can be repurposed for storage. The original class must therefore be stored in the zombie class rather than in the zombie object, and that means the zombie class needs to be dynamically allocated. Each class which has an instance that becomes a zombie will get its own zombie class.

The next question is where to store the reference to the original class. It's possible to allocate a class with some extra storage for things like this, but it's somewhat inconvenient to use. An easier way is to simply use the class name. Since Objective-C classes all live in one big namespace, the class name is sufficient to uniquely identify it within a process. By sticking a prefix on the original class name to generate the zombie class name, we end up with something that's both descriptive on its own and can be used to recover the original class name. We'll use MAZombie_ as the prefix.

Method Implementations
Note that all of the code here is built without ARC, since ARC memory management calls really get in the way here.

Let's start off with a simple method implementation, which is an empty one:

It turns out that the Objective-C runtime assumes that every class implements +initialize. This is sent to a class before the first message sent to the class to allow it to do any setup it needs. If it's not implemented, the runtime sends it anyway and hits the forwarding machinery instead, which isn't helpful here. Adding an empty implementation of +initialize avoids that problem. EmptyIMP will be used as the implementation of +initialize on zombie classes.

The implementation of -methodSignatureForSelector: is a bit more interesting:

It retrieves the class of the object and that class's name. This is the name of the zombie class:

The original class name can be retrieved by stripping off the prefix:

Then it logs the error and calls abort() to make sure you're paying attention:

Creating the Classes
The ZombifyClass function takes a normal class and returns a zombie class, creating it if necessary:

The zombie class name is useful both for checking to see if a zombie class exists and for creating it if it doesn't:

The existence of the zombie class can be checked using NSClassFromString. This also provides the zombie class so it can be returned immediately if it exists:

Note that there's a race condition here: if two instances of the same class are zombified from two threads simultaneously, they'll both try to create the zombie class. In real code, you'd need to wrap this whole chunk of code in a lock to ensure that doesn't happen.

A call to the objc_allocateClassPair function allocates the zombie class:

We add the implementation of -methodSignatureForSelector: using the class_addMethod function. The signature of '@@::' means that it returns an object, and takes three parameters: an object (self), a selector (_cmd), and another selector (the explicit selector parameter):

The empty method is also added as the implementation of +initialize. There's no separate function for adding class methods. Instead, we add a method to the class's class, which is the metaclass:

Now that the class is set up, it can be registered with the runtime and returned:

Ld37 - Zombie Room Mac Os Catalina

Zombifying Objects
In order to turn objects into zombies, we'll replace the implementation of NSObject's dealloc method. Subclasses' dealloc methods will still run, but once they go up the chain to NSObject, the zombie code will run. This will prevent the object from being destroyed, and provides a place to set the object's class to the zombie class. This operation gets wrapped up into a function to enable zombies:

We can then put a call to EnableZombies at the top of main() or similar, and the rest takes care of itself. The implementation of ZombieDealloc is straightforward. It calls ZombifyClass to obtain the zombie class for the object being deallocated, then uses object_setClass to change the class of the object to the zombie class:

Testing
Let's make sure it works:

I chose NSIndexSet semi-arbitrarily, as a convenient class that doesn't hit CoreFoundation bridging weirdness. Running this code with zombies enabled produces:

Success!

Conclusion
Zombies are fairly simple to implement in the end. By dynamically allocating classes, we can easily keep track of the original class without needing to rely on storage within the zombie object. methodSignatureForSelector: provides a convenient choke point for intercepting messages sent to the zombie object. A quick hook on -[NSObject dealloc] lets us turn objects into zombies instead of destroying them when their retain count goes to zero.

That's it for today. Come back next time for more frightening tales. Until then, keep sending in your suggestions for topics.

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle. Click here for more information.
Comments:
Cool. Sounds like there's a couple of weak points, though: 1. The CF bridging weirdness you mentioned. 2. Zombify requires a class' dealloc implementation to call super. Could one swizzle dealloc instead? You'd need a place to catch the loading/registering of every class to swizzle its dealloc, though.
Also, this article is a week late. October is for zombies... Now we need articles on NSTurkey. Or NSPumpkinSpiceLatte, or something... :)
If you want to see a different implementation of zombies, including a treadmill, one that has been hardened by shipping it for years, check out the Chromium version:
https://chromium.googlesource.com/chromium/src/+/master/chrome/common/mac/objc_zombie.mm
Disclosure: I'm on the Chrome team, though I never worked on the zombie implementation.
'If the original class had no additional instance variables, then there's no space that can be repurposed for storage.'
Technically speaking, the granularity of malloc() on Darwin is 16 bytes, which is a minimum space occupied by an Obj-C object (barring evil custom +alloc implementations, of course). The isa variable on a 64-bit system occupies 8 bytes, which still leaves 8 bytes for a pointer to the original class. In other words, that's a great opportunity for optimization for 99.9% cases on all currently supported Apple systems. Just my 2 cents.
Rick: The CF stuff is a big problem, and one that NSZombie actually shared for many years. The requirement to call super is related, and I don't think NSZombie will catch that either. Once you start doing custom allocators it's hard to hook stuff reliably. You could swizzle dealloc, but how will you stop the memory from being deallocated? You still need to run the rest of the dealloc code for things to work properly. And you're right, I originally meant to get this article out last week but didn't quite make it!
Konstantin Anoshkin: You're right, we could safely assume there's enough storage for another pointer in the real world. But I didn't like to make that assumption, partly just because it's messy, and partly because there's nothing that says Apple couldn't decide to use a more fine-grained allocator for objects that don't have any extra ivars. In a real implementation, assuming you're only running it for debugging and you check with malloc_size before you start overwriting stuff, it would be fine.
Rather than dynamically installing +initialize and -methodSignatureForSelector:, couldn't you just write an MAZombie base class and have all of your individual zombie classes subclass it? It seems like that would reduce the amount of runtime magic you need to write for each zombie class.
Also, I believe this implementation of zombies won't call -.cxx_destruct, which is used by ARC. Would it be a good idea to call that in ZombieDealloc?
I enjoyed the tags ;]
I wonder if it would be useful to create a zombie that doesn't prevent deallocation -- it would still catch incorrect message sends for a while, up until the memory gets overwritten. And since it doesn't leak, it can be used in production. What do you think?
Comments RSS feed for this pageZombie

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Ld37 - Zombie Room Mac Os Pro

JavaScript is required to submit comments due to anti-spam measures. Please enable JavaScript and reload the page.

Code syntax highlighting thanks to Pygments.

How to disable an optical drive 13 comments Create New Account
Click here to return to the 'How to disable an optical drive' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.

I wonder if just using the kext unload command in the terminal would do it. ?? Hmm. interesting hint, thanks, this could also be good to secure your computer in lab setups.

Maybe I'm just being Mr. Obvious here, but wouldn't it be a heck of a lot easier to shut the machine down, open it up, and disconnect the ATA cable from the drive? Beats messing around with the OS.

How obvious, how could he have missed that taking apart an iBook or PowerBook is so much simpler than dragging and dropping a file into a folder.

Oh wait... 'and I didn't want to take apart the laptop and disconnect it'. Thank you Captain Obvious.

v

That's a powerbook,that's not use to disconnect the CABLE
thx for the author's tips
similar problem ever come to me,
but i didn't solve it,
now 3q for your guide

It Did not work for my MacBook Pro, any more Ideas?

Great hint. I tried it just to save battery on my Macbook since I never use the optical drive. But of course, now I need to use the optical drive and I'm having a heck of a time enabling it again. I moved the IODVDStorageFamily.kext back into the proper Extensions folder. Then I did:
sudo chown -R root:wheel IODVDStorageFamily.kext
sudo chmod -R 755 root:wheel IODVDStorageFamily.kext
sudo kextload IODVDStorageFamily.kext

Then I rebooted, and my system still can't see the optical drive. I think I need to somehow load the kext before login every time?

Nevermind, I found the solution. I simply had to plug a flash drive in and the optical drive was immediately recognized.

Just found out how to do this tweak by a more 'cleaner' manner : you just need to unload all the kext dependencies of IOCDStorageFamily, then unload IODVDStorageFamily itself :
sudo kextunload IOSCSIArchitectureModelFamily.kext/Contents/PlugIns/IOSCSIMultimediaCommandsDevice.kext
sudo kextunload IOBDStorageFamily.kext
sudo kextunload IODVDStorageFamily.kext
sudo kextunload IOCDStorageFamily.kext
You can actually see all the dependancies by just looking at the command 'kextstat'.

G4 Titanium PB 1Ghz DVI Superdrive. Superdrive failed, eating my hardware test disk. I opened it up and took the disk out. Upon boot, the newly-empty Superdrive started cycling endlessly, annoyingly. Looked online, saw this post. Tried deleting IODVDStorageFamily.kext to no avail; upon restart, drive kept up the annoying cycling. Dragged and dropped IODVDStorageFamily.kext back into System > Library> Extensions. Upon restart, my external Firewire DVD drive is no longer recognized; upon opening DVD Player, I get an error message that IODVDStorageFamily.kext was installed improperly, and I should contact the vendor. Also, no DVD drive recognized.
Oh, boy. So, my Superdrive is cycling endlessly, and I'd like to stop that. I'm fine with opening the PB up and pulling a plug. Which one? Can I remove the Superdrive entirely? I'm assuming not, as the modem and inverter mount on it, but how can I disable it or stop it from spazzing out?
Worse, I cannot now play a DVD from my (recently) working FW DVD drive.
A little help, please! And thanks in advance...

Okay, I went back in and unplugged the Superdrive, so that's that. And now when I insert a DVD in the FW drive, it shows up in System Profiler, DVD appears on the desktop with the right name... But when I try to play it with DVD player, I get 'There was an initialization error. A valid DVD drive could not be found. [-70012]. Help!
Ironically enough, I appear to have disabled my optical drive, all right...

So DVD Player will not run if the Superdrive is unplugged, despite the fact that you can read the DVD from a Firewire external and open it. So it's a DVD Player problem, at this point. My 'Super' drive is physically broken and cycling really annoyingly, so I'd like to unplug it, but can't watch DVD's in DVD Player like that. Maybe another player? Otherwise, issues are solved, sort of.

I tried this, but when I wanted to un-do it I just moved the files back (I did the CD and the DVD one). But now it just comes up with an error:
System extension cannot be used
The system extension '/System/Library/Extensions/IOCDStorageFamily.kext' was installed improperly and cannot be used. Please try reinstalling it, or contact the product's vendor for an update.
Is there something I can do?

In reply to my previous comment, I got my cd drive working again by loading Disk Utility, choosing my hard drive and selecting 'Repair permissions.' It popped up one more time saying it was still not working, but then I restarted and it is all working great ;).
P.S. In the original disabling, you do have to restart before anything really notices that the drive is gone.