iOS apps are translocated when run in macOS

M1 Macs come with some unique features, including the ability to run iOS and iPadOS apps natively. Although I’m sure that others have already examined how a macOS system can run software built for iOS, I haven’t yet come across any good explanation. Over the coming days and perhaps weeks I’m going to try to understand that, and see what it tells us about the future of macOS. This introductory article explains one strange behaviour: whenever you run an iOS/iPadOS app on an M1 Mac, it’s run in app translocation, a feature introduced in 2016 for security.

Prior to the appearance of M1 Macs, app translocation was an additional security measure applied to some apps which were being launched for the first time on that Mac. Instead of being run from their current location, the app is copied complete into a folder deep in the bowels of the hidden /var folder, and run from there. This is intended to disrupt any attempts by a malicious app to exploit relative file paths, as it breaks them.

The requirements for app translocation have been explained by Jeff Johnson, and are:

  • The quarantine flag on the app has been set.
  • The app must be opened by LaunchServices, typically by opening it in the Finder.
  • The app must be launched from its original location, and not moved from that using the Finder.

Since its introduction in Sierra, app translocation has caused the occasional glitch for users, sometimes with products like Little Snitch which approves outgoing connections for apps according to their paths. Most of us no longer notice it, or routinely install all new apps in the Applications folder to circumvent it.

When you run an iOS/iPadOS app on an M1 Mac, if it has been downloaded from the App Store (currently the only supported method, as sideloading is forbidden), it doesn’t have a quarantine flag. Not only that, but app translocation has only occurred with apps undergoing their first run: once that flag has been unset, further translocations don’t occur. Thus, under the original rules for app translocation, there’s no way that it should occur in this case.

I’m going to look in more detail at how macOS launches and runs iOS/iPadOS apps in future articles, but here I’ll show some relevant log entries which demonstrate what happens, including the translocation.

Early announcement that an iOS app has been launched comes from LaunchServices:
1.041699 LAUNCH: Application being launched is PLATFORM_IOS, so returning kLSLaunchNeedsToGoThruHelperErr to force it to launch tru CSUI.
1.041719 LAUNCH: Local launch attempt failed with kLSLaunchNeedsToGoThruHelperErr ; retrying with CoreServicesUIAgent

A little later, com.apple.syspolicy records:
1.054198 MIS validation result: 0
1.054202 Valid app wrapper: <private>
1.054230 appWrapperPolicyResult: <private>, AWPolicyResult: 1,1,0

which appears to be what determines LaunchServices’ next entry declaring the translocation:
1.055056 LAUNCH: translocate to <private> from <private>

The translocation path appears similar to those used for regular macOS apps which have been translocated, /var/folders/x4/[characters]/X/[UUID]/d/Wrapper/. The item named with a UUID is a mounted volume, and the Wrapper directory contains a copy of the app, together with two Property Lists, BundleMetadata.plist and iTunesMetadata.plist.

This translocation is further confirmed by LaunchServices when it declares information such as
1.073487 NotifyAboutLaunchedApplication: {rbExitNotification=true, modifiers={LSLaunchAsync=true, LSLaunchStoppedTemporarily=true, }, session=100024, pid=9863U, asn=401506U, executableFD=FD:, command=600, info={LSLaunchBeforeTranslocationLaunchBundlePathDeviceIDKey=16777232, CFBundleExecutablePathDeviceID=771751943, LSBundlePathDeviceID=771751943, LSExecutableFormat="LSExecutableMachOFormat", LSLaunchBeforeTranslocationLaunchBundlePathKey="/Applications/FreeFlight6.app", CFBundlePackageType="APPL", pid=9863, LSLaunchBeforeTranslocationLaunchBundlePathINodeKey=6930570, LSFrontApplicationSeed=164, LSWantsToComeForwardAtRegistrationTimeKey=true, LSStoppedState=true, LSASN={com.apple.coreservices.asn.hi=0U, com.apple.coreservices.asn=UUID:$c96172a0ce744d62, com.apple.coreservices.asn.lo=401506U, }, CFBundleName="FreeFlight6", CFBundleExecutablePathINode=6928371, CFBundleExecutablePath="/private/var/folders/x4/x00kny5x0_5dsnmmxhtw6hc80000gn/X/9EC30D87-F519-5996-9E92-F8BD7E4DAFAF/d/Wrapper/FreeFlight6.app/FreeFlight6", LSLaunchBeforeTranslocation<…>

Of the two Property Lists, iTunesMetadata.plist is by far the more interesting, as it contains extensive information from the App Store about the app, its purchase and download. Included among these are values for keys such as sideLoadedDeviceBasedVPP and is-purchased-redownload which could be used to detect attempts at sideloading.

Quite why Big Sur is translocating iOS/iPadOS apps every time they’re launched isn’t clear. If it was just to prevent sideloading, then it’s hard to see why this is repeated after the first full run. And we all hope that Apple doesn’t see this as being necessary for macOS security.

Postscript

I am extremely grateful for the comment you can read below, which points out that this translocation is performed to a path which contains no whitespace characters, in order to work around an issue in which some iOS/iPadOS may fail to parse paths containing whitespace characters. Never having developed an iOS app that wouldn’t have occurred to me as a potential problem.