App being launched while device is locked

DESCRIPTION OF PROBLEM

Logs and data from our application indicate various errors that strongly suggest that our application is being launched in a state in which the device is likely locked. We are looking for guidance on how to identify, debug, reproduce, and fix these cases.

Our application does not use any of the common mechanisms for background activity, such as Background App Refresh, Navigation, Audio, etc.

Errors we get in our logs such as "authorization denied (code: 23)" when trying to access a file in our app's container on disk (a simple disk cache for data our application uses) strongly suggest that the device is operating in a state, such as being locked, where our application lacks the requisite permissions it would normally have during operation. Furthermore, attempts to access authentication information stored in the keychain also fails. We use kSecAttrAccessibleWhenUnlocked when accessing items we store in the keychain.

We have investigated "Prewarming", as well as our notification extension that helps process incoming push notifications, but cannot find any way to recreate this behavior.

Are there any steps Apple engineers can recommend to triage and debug this?

Some additional questions that would help us:

  1. What are all of the symptoms that we can look for if prewarming escapes the intended execution context?
  2. What are all of the circumstances in which we would be unauthorized to access the app’s documents/file directories even if it works correctly in normal operation?

STEPS TO REPRODUCE

Unfortunately, we are unable to forcibly reproduce this behavior in our application, so we're looking for guidance on how we might simulate this behavior in Xcode / Instruments.

Are there tools that Apple provides that would allow us to simulate certain behaviors like prewarming to verify our application's functionality?

Are there other reasons our application might be launched while the device is locked? Are there other reasons we would receive security errors when accessing the keychain or disk that are unrelated to the device being locked?

FYI, the flow here may be a bit off as I actually wrote the section "last" but I've moved it to the top as it's really the most important point here:

We use kSecAttrAccessibleWhenUnlocked when accessing items we store in the keychain.

If you're using the "WhenUnlocked" (keychain) or "Complete" (file) protection states, then the ONLY safe time that data can be accessed is after "applicationDidBecomeActive(_:)" and before "applicationWillResignActive(_:)"/"applicationDidEnterBackground(_:)".

The issue here is that there isn't any delegate method to warn your app that it will lose access to protected data, so the only way to KNOW that the device is unlocked is to specifically be in "in the foreground", which is what applicationDidBecomeActive means.

The complication here is that apps that follow this approach can appear to work fine in the background, simply because the user happens to be using their device, preventing the device from locking.

Note that this also means that the focus on launch conditions is misplaced, as the issue isn't "Why was my app launched in the background?" but is actually "Why is your app accessing protected data when it's not in the foreground?".

Our application does not use any of the common mechanisms for background activity, such as Background App Refresh, Navigation, Audio, etc.

The implicit assumption (which our documentation tends to enforce) is that there is a relatively small and well defined set of circumstances which will cause your app to wake in the background and that if you simply avoid those APIs your app will never wake in the background. In my experience, that assumption is basically false. More specifically:

  1. The full range of APIs that can trigger background wakes is sufficiently large than any of a significant size often ends up incorporating one or more of those without realizing. I've had many conversations which started with "I don't use any background APIs..." and ended with "...except for <insert API name> which we didn't think about...".

  2. There are edge cases details of the system's behavior which can cause background wakes and which have never been formally documented. As on example, I've seen cases where the system woke the app to call "userNotificationCenter(_:willPresent:withCompletionHandler:)" to determine if the system should present the notification the user. That isn't documented behavior and could very well be a bug, but that doesn't matter very much when your app is what's breaking.

Related to that point:

We have investigated "Prewarming", as well as our notification extension that helps process incoming push notifications, but cannot find any way to recreate this behavior.

Prewarming's role in this process is much smaller than it might seem. I wrote about this dynamic here, but it's general effect is that it can introduce cases where your app will be WOKEN (NOT launched) in the background when it previously would not have been.

Logs and data from our application indicate various errors that strongly suggest that our application is being launched in a state in which the device is likely locked.

SO, there are actually two cases that can be playing out here:

  1. Yes, it's possible that, due to the factors above, your app is specifically being woken "in the background" in a way that it previously was not.

  2. As the post I referenced above outlined (at least indirectly), it's possible for a "foreground launch" to actually end up with your app running (briefly) on a locked device.

Expanding on #2, it's important to understand that the processes of "unlocking the device" and "launching/waking apps" are independant processes that occur in parallel with each other. In a cold launch case on a straightforward app, that dynamic isn't really visible because the app launch process takes longer than the unlock, so the device is already unlocked before you apps code starts executing.

In concrete terms, if it takes 2s for the device to unlock and 3s for your app to "start", then the perception will be that your app "runs" 1s after unlock, even though nothing actually synchronized those actions. However, there are two things that can change that dynamic:

  1. Your app moves work "earlier", particularly by initiating work a library load time (before-main) instead of via the system delegates.

  2. The system speeds up your apps launch, with Prewarming being one example of that dynamic.

In any case, the solution here is to ensure your app is in the foreground when it accesses protected data, which side steps all these issues.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

App being launched while device is locked
 
 
Q