Developer PSA: Check Your Receipt Validation Code

• Chris Liscio

There is a bug in macOS that triggers a nasty failure in the receipt validation code that you probably ship with your Mac app right now, and you need to fix it.

Mac apps that are sold on the App Store include code that verifies that the app was obtained legitimately from Apple. Part of this verification process ensures that the receipt was generated specifically for the Mac that is currently running the software.

This code requires a unique identifier from the machine, which—on the Mac—happens to be the MAC address of the primary network interface , en0. Apple helpfully provides developers with sample code to obtain this. I've reproduced the code in a gist so that I can reference it line-by-line as I describe the failure.

The System Bug

While playing audio using AirPlay 2, there are two devices that have a BSD Name equal to en0. Both the actual en0 interface, and a new device called IOTimeSyncEthernetModernInterfaceAdapter (or IOTimeSyncWiFiInterfaceAdapter, depending on your Mac) that is only present while AirPlay is active.

If you want to see this in action for yourself, type the following command at the Terminal:

ioreg | grep -C10 en0

Then, start some audio playing over AirPlay 2, and repeat the above command. You'll notice that the tree underneath en0 has expanded to include a new "time sync" device.

Now, type the following command (substituting IOTimeSyncWiFiInterfaceAdapter if your Mac only has built-in Wi-Fi):

ioreg -r -d 1 -n IOTimeSyncEthernetModernInterfaceAdapter

And notice that the output contains the following entry:

"BSD Name" = "en0"

Well that's quite unexpected, wouldn't you agree? I filed FB7369072 with Apple to track this.

The Sample Code Bug

In the presence of the bug above, the matchingDict created on line 20 will result in two iterations of the loop on line 32.

On the first iteration of the while loop, the MAC address is obtained successfully.

However, on the second iteration, line 38 clears out the MAC address, and line 40 returns a NULL CFDataRef!

The Consequence

When copy_mac_address returns NULL, your receipt validation code fails, and probably results in your application calling exit(173) to ask the App Store for a new receipt. Unfortunately, a new receipt isn’t going to help, and you get stuck in a very strange loop.

For me, this resulted in the app repeatedly appearing & disappearing in the Dock for quite some time. Eventually, the system finally gave up and told me the app was “Damaged” and needed to be re-downloaded.

The Workaround

As far as I can tell, the only end-user workaround requires that AirPlay playback is not running when launching apps that are affected by this bug.

In some cases, the bug will persist after AirPlay is stopped, and a restart may be required to clear this up. This is the state that I found myself in when I first discovered the issue.

Fixing Your Code

The quick fix is to change the while loop on line 32 to an if statement, and skip the 2nd go-around in the loop. But I don't like this approach at all, and don't recommend it.

Instead, I decided to go with the same approach that Apple uses themselves in a few spots:

The keys to this alternate approach are as follows:

  1. It specifies the kIOEthernetInterfaceClass, which only matches network interfaces. (Before you ask—yes, this includes Wi-Fi adapters.)
  2. It also specifies that kIOPrimaryInterface is set to true, which matches the built-in, primary network interface.

I also don’t bother with a while loop on the returned iterator for primary interfaces. The code I ended up with looks most similar to the DirectoryService code linked above.

Questions & Answers

How widespread is this?

I don’t think it’s very common at all, which is probably why this bug slipped through the cracks this long. Also, it requires that someone is streaming something over AirPlay.

In addition, it appears that this only affects users that use wired built-in network adapters. In my case, I use the wired ethernet adapter in my iMac Pro. I don’t know whether merely having the wired Ethernet adapter causes the issue, or whether you have to use the wired adapter to stream to an AirPlay device. An exercise left up to the reader, I guess.

At any rate, I have verified on my MacBook Pro that the consequences of the bug are not as bad. While the strange "time sync" device still appears with a BSD Name of en0, and the while loop is executed twice, the parent device (which is en0 itself) unexpectedly carries a (correct!) value for the MAC address.

So the “weird behavior” (a second, unexpected en0 entry) still persists, but the correct MAC address is not overwritten during the second iteration of the while loop.

If it's so rare, then why should I bother?

First and foremost, the consequences are horrible. A customer that downloaded your software—in some cases having just paid for it—will be unable to launch your app while their Mac is in this state.

It won't crash on them, no. Instead, the system tells your customer that your application is damaged! They'll re-download it as instructed, and nothing will change the outcome. At this point, your customer is fed up. They'll demand a refund, and tell everyone they know that your software is junk.

Sadly, their sentiment is not entirely incorrect. Put yourself in their shoes, and you'd feel exactly the same way—especially if money changed hands.

Now, the second reason you should care is that the bug could become more widespread. While the Wi-Fi-only Macs currently obtain the MAC address successfully, it's a complete fluke! The original code asks the parent of the en0 interface for the MAC address, because the en0 device is not expected to carry a value for the MAC address.

It's a total fluke that Apple's sample code still works on these Macs, and so this behavior could change on upcoming hardware or software updates, and cause the issue to appear more frequently in the future. I don't know about you, but I wouldn't risk it.

The third and final reason? It affects me! I listen to music from my Mac over AirPlay frequently, and it pisses me off that a few of the apps that I purchased from the App Store refuse to launch until I disconnect from AirPlay.

Will this approach actually work?

If—like me—you’re not fully convinced about kIOPrimaryInterface being singular, or the use of a while loop, you can read Apple’s code for yourself. Search for isPrimaryInterface to see how the kIOPrimaryInterface property is set. It’s assigned to the built-in device with a unit number equal to 0. Or, to put it another way, en0.

You may also notice that the DirectoryService code doesn’t bother with a while loop, but the webdavfs code does. However, despite the pluralized FindPrimaryEthernetInterfaces function name in the latter example, the comments seem to indicate that they expect only a single device to be returned.

Given the combination of querying only the kIOEthernetInterfaceClass devices, and the kIOPrimaryInterface flag, you'll only ever get exactly the device you're interested in.

What are the risks?

Any half-decent developer is justified in feeling some worry about making changes to their receipt validation code. I definitely did for a short while.

Apple tells us to “use this exact same code” to get the Mac’s device ID. If we implement our device ID code differently from Apple, and a weird bug pops up, then our receipt validation should be equally as broken as theirs! So everything should just match up!

Fortunately, there are a few factors working against that argument:

  1. Apple’s code that participates in the receipt generation does not appear to fail in the same circumstance. Everything works fine for me when I implement the suggested fixes above, so Apple is clearly not using that exact same approach to obtain the MAC address.
  2. Apple’s own code (that I linked to above) uses this same method in a few places. Frankly, code that actually ships as a part of the OS is a far more reliable source than code appearing in Apple’s often-neglected documentation.
  3. The fact that the BSD Name en0 can be mis-applied to another device—one that doesn’t even represent a network interface!—is a pretty clear indication that Apple’s documented method is fragile.

Is anyone else already doing it this way?

Yes. I have received verification from a very small handful of developers (shipping quite-popular apps) that the same mechanism is working for them in the field.

As of a few days ago, Capo shipped using this alternate approach as well.

Maybe go download it, check it out, and let me know if you have trouble launching it?