iOS 8 notification action: "Access to item attempted while keychain is locked" error when accessing keychain in "didFinishLaunchingWithOptions"
Asked Answered
C

2

25

I am observing a keychain error in the device console thrown by SecItemCopyMatching when acting on an iOS 8 push notification on a locked phone. The detailed repro steps are as follows:

  1. Uninstall all previous versions of the app. Build an Appstore version of the app onto device. Force quit the app.
  2. Increment build number, and build a newer version onto device. This simulates an app update flow. Force quit the app (In real life, the app might get killed by the OS due to memory pressure. Force quitting simulates this behavior).
  3. Send a push notification to the app when the phone is locked.
  4. With the phone locked, swipe left to see the action buttons and press one of the action buttons.
  5. App gets woken up, didFinishLaunchingWithOptions gets called, which tries to access a keychain item. While running SecItemCopyMatching, an error Access to item attempted while keychain is locked shows up in the device console.

The complete error log is shown below. The last line gives the app specific error message.

ReportCrash[32481] <Error>: task_set_exception_ports(B07, 400, D03, 0, 0) failed with error (4: (os/kern) invalid argument)
ReportCrash[32481] <Notice>: ReportCrash acting against PID 31423
diagnosticd[32258] <Error>: error evaluating process info - pid: 31423, punique: 131317
ReportCrash[32481] <Notice>: Formulating crash report for process cfprefsd[31423]
com.apple.xpc.launchd[1] (com.apple.cfprefsd.xpc.daemon[31423]) <Notice>: Service exited due to signal: Bus error: 10
My App[32480] <Error>: assertion failed: 12F70: libxpc.dylib + 71768 [B870B51D-AA85-3686-A7D9-ACD48C5FE153]: 0x7d
Unknown[32480] <Error>: 
ReportCrash[32481] <Notice>: Saved report to /Library/Logs/CrashReporter/cfprefsd_2015-07-02-150139_Xianjing-Hus-iPhone.ips
securityd[32279] <Error>:  s3dl_query_row decode genp,rowid=8099 failed (-25308): The operation couldn’t be completed. (OSStatus error -25308 - ks_crypt: e00002e2 failed to     unwrap item (class 6, bag: 0) Access to item attempted while keychain is locked.)
securityd[32279] <Error>:  securityd_xpc_dictionary_handler Okta Verify[32480] copy_matching The operation couldn’t be completed. (OSStatus error -25308 - ks_crypt:     e00002e2 failed to unwrap item (class 6, bag: 0) Access to item attempted while keychain is locked.)
My App[32480] <Error>:  SecOSStatusWith error:[-25308] The operation couldn’t be completed. (OSStatus error -25308 - Remote error : The operation couldn't be completed. (OSStatus error -25308 - ks_crypt: e00002e2 failed to unwrap item (class 6, bag: 0) Access to item attempted while keychain is locked.))

A few things:

  • The keychain item's accessibility is set to kSecAttrAccessibleAlways.
  • As can be seen in the device log above, there is always a cfprefsd process crash prior to the issue.
  • This issue only occurs on Appstore builds, not on debug builds.
  • This issue only occurs when trying to act on notification on a locked phone.
  • This issue only occurs when the app is newly updated, as described in the repro steps above.
  • Because I force quit the app, when the push notification arrives and I pressed on the action button, my app will be launched in background. didFinishLaunchingWithOptions gets called, and inside this delegate method, I am doing my keychain access which throws the error.

Has anyone seen a similar error and if so, how did you resolve the issue? Any help is appreciated.

Update: Attaching cfprefsd crash log

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: unknown at 0x00000001007d4000
Triggered by Thread:  2

Thread 0 name:  Dispatch queue: com.apple.libdispatch-manager
Thread 0:
0   libsystem_kernel.dylib          0x0000000197d88c24 kevent64 + 8
1   libdispatch.dylib               0x0000000197c6de6c _dispatch_mgr_invoke + 272
2   libdispatch.dylib               0x0000000197c5f998 _dispatch_mgr_thread + 48

Thread 1 name:  Dispatch queue: com.apple.root.default-qos.overcommit
Thread 1:
0   libsystem_kernel.dylib          0x0000000197da3984 __sigsuspend_nocancel + 8
1   libdispatch.dylib               0x0000000197c6921c _dispatch_sigsuspend + 24
2   libdispatch.dylib               0x0000000197c69200 _dispatch_sig_thread + 44

Thread 2 name:  Dispatch queue: src
Thread 2 Crashed:
0   libsystem_platform.dylib        0x0000000197e35300 _platform_memmove + 176
1   libxpc.dylib                    0x0000000197e6567c xpc_data_create + 84
2   CoreFoundation                  0x0000000185d5a9b8 -[CFPDSource acceptMessage:] + 1956
3   CoreFoundation                  0x0000000185dc0da8 __handle_synchronize_message_block_invoke103 + 172
4   CoreFoundation                  0x0000000185d57c58 __88+[CFPDSource withSourceForDomain:inContainer:user:byHost:managed:synchronously:perform:]_block_invoke_2 + 24
5   CoreFoundation                  0x0000000185d5955c __25-[CFPDSource lockedSync:]_block_invoke + 44
6   libdispatch.dylib               0x0000000197c5d950 _dispatch_client_callout + 12
7   libdispatch.dylib               0x0000000197c671e0 _dispatch_barrier_sync_f_invoke + 72
8   CoreFoundation                  0x0000000185d59520 -[CFPDSource lockedSync:] + 80
9   CoreFoundation                  0x0000000185d57c0c __88+[CFPDSource withSourceForDomain:inContainer:user:byHost:managed:synchronously:perform:]_block_invoke + 504
10  libdispatch.dylib               0x0000000197c5d950 _dispatch_client_callout + 12
11  libdispatch.dylib               0x0000000197c671e0 _dispatch_barrier_sync_f_invoke + 72
12  CoreFoundation                  0x0000000185d576fc +[CFPDSource withSourceForDomain:inContainer:user:byHost:managed:synchronously:perform:] + 364
13  CoreFoundation                  0x0000000185dc0508 handle_message + 1312
14  CoreFoundation                  0x0000000185dc081c __handle_multi_message_block_invoke_2 + 124
15  libxpc.dylib                    0x0000000197e657c0 xpc_array_apply + 76
16  CoreFoundation                  0x0000000185dc05f8 handle_message + 1552
17  CoreFoundation                  0x0000000185dbffd4 ____CFXPreferencesDaemon_main_block_invoke_5 + 132
18  libxpc.dylib                    0x0000000197e64cc8 _xpc_connection_call_event_handler + 64
19  libxpc.dylib                    0x0000000197e62bcc _xpc_connection_mach_event + 2156
20  libdispatch.dylib               0x0000000197c5da24 _dispatch_client_callout4 + 12
21  libdispatch.dylib               0x0000000197c6113c _dispatch_mach_msg_invoke + 488
22  libdispatch.dylib               0x0000000197c682d0 _dispatch_queue_drain + 2004
23  libdispatch.dylib               0x0000000197c60664 _dispatch_mach_invoke + 132
24  libdispatch.dylib               0x0000000197c6a314 _dispatch_root_queue_drain + 716
25  libdispatch.dylib               0x0000000197c6bc48 _dispatch_worker_thread3 + 104
26  libsystem_pthread.dylib         0x0000000197e3d228 _pthread_wqthread + 812
27  libsystem_pthread.dylib         0x0000000197e3ceec start_wqthread + 0
Coastward answered 3/7, 2015 at 3:3 Comment(24)
Quick search yielded something: #10537359 for keychain access background (in google), but I have no familiarity. This question is interesting and I may eventually run into.Dropforge
Thank you @StephenJ, I have read that post. My keychain's accessibility is already explicitly set to kSecAttrAccessibleAlways, so something else is causing the issue. Thanks though!Coastward
@Coastward Do you have data protection enabled?Wellspoken
Also please post cfprefsd crash.Wellspoken
@LeoNatan Hi Leo, I don't have data protection enabled. I just appended the cfprefsd crash log at the end of my post. Thank you!Coastward
If you don't get an answer, I'd suggest you file a radar (bugreport.apple.com)Accuse
cfprefsd seems to be unrelated to keychain, so I doubt that is what is causing issues here, but who knows. You mention that this only happens on an upgrade flow. What is different then? Also, what happens if you delay the key chain read one runloop forward by dispatch_async(dispatch_get_main_queue()?Wellspoken
In the docs it recommends kSecAttrAccessibleAfterFirstUnlock, have you tried changing it to that?Accuse
@LeoNatan To test this issue I didn't make any changes between the two updates. The only difference between the two updates are the build numbers. I will try delaying the keychain read now and let you know the resultCoastward
@SantaClaus I was originally using kSecAttrAccessibleAfterFirstUnlock which was not working. Since the error indicates this is a keychain lock error, I changed it to the loosest permission level which is kSecAttrAccessibleAlways. But as a matter of fact, no matter what permission I changed it to, the error is always the same. Thanks though!Coastward
You mention you've changed the accessibility. Did you delete the key before trying to read it? Try using a different name and see if that works.Wellspoken
@LeoNatan so I put the keychain access in the dispatch_async in didFinishLaunchingWithOptions, however I am seeing the same error. Regarding the accessibility, I did a migration just to update it: in applicationDidBecomeActive, I set the kSecAttrAccessible to the new value and performed a SecItemUpdate, and I got the noErr status, indicating the keychain accessibility update is successful. After the migration which runs only once, I think I don't need to update it in future reads.Coastward
I am not sure if migration works. Try to change the key name and see if helps.Wellspoken
@LeoNatan ok thanks, I'll try that and let you knowCoastward
@LeoNatan So the keychain code is actually already using a unique key name every time it tries to save. We are using the Google OTP library, saving of the keychain is done in this link. The unique name is set in this line name = [NSString stringWithFormat:@"%@.%ld", self.name, random()]; It keeps trying new strings for the name until it finds one that has never been used.Coastward
So you modified the code from that library to add the kSecAttrAccessibleAlways attribute, correct? There are many places in that file where you have to add that attribute (where the keychain item is saved, copied, updated, etc), and its possible you missed one.Accuse
If you choose to submit a bug report, I'd suggest you create a concise example program that demonstrates the issue (i.e. not using the google OTP library)Accuse
Kind of thinking your app's unique id, beyond the non-changing bundle, has some sort of security so "new md5's" or "new apps" as far as sandboxing is concerned aren't allowed to access areas like keychains. But since you've been preapproved via previous version, think it's bug also.Dropforge
Do you have another App that accesses the keychain with the same identifier? i.e. the same app but with different version with a different bundle id? Sometimes theres a dev build and a live build on the same device and would cause this error if they access the same keychain - deleting one of the apps from the device should solve the problem.Flotation
@SantaClaus I did a global search and changed all of them. The thing is, even without changing it, it should work because AfterFirstUnlock and Always really aren't that different, the first just requires there is at least one unlock after reboot. They should both work...Coastward
@SeanDev I have made sure that all debug builds were deleted from my phone, so it's just the appstore buildCoastward
does it happen on every lock or only when the phone is rebooted and was never unlockedIndaba
@Mousam: Are you able to reproduce this issue in a sample application which does not use google OTP library?Indaba
@Indaba the phone never goes to the reboot flow, it had been unlocked many times before testingCoastward
Z
10

This error is definitely caused by trying to access an item that is kSecAttrAccessibleWhenUnlocked while device is still locked. You can tell this just by looking at the following line of the log you've provided:

securityd[32279] <Error>:  securityd_xpc_dictionary_handler Okta Verify[32480] copy_matching The operation couldn’t be completed. (OSStatus error -25308 - ks_crypt:     e00002e2 failed to unwrap item (class 6, bag: 0) Access to item attempted while keychain is locked.)

Class 6 is kSecAttrAccessibleWhenUnlocked (and kSecAttrAccessibleAlways is class 8) – see slide 15 of this deck for more details – so the behaviour you're seeing is expected.

The real question now is why the item ends up as kSecAttrAccessibleWhenUnlocked while you think it's kSecAttrAccessibleAlways. It is hard to tell without seeing more code and/or having more information, but here are few things to consider:

  • Keychain items are not removed when app is uninstalled – they do survive app reinstallation/upgrade. So if an earlier version of the app created an item as kSecAttrAccessibleWhenUnlocked it could have just carried on. Try removing the item and creating it again (and check return values of SecItemDelete() and SecItemAdd() to be sure it's done).
  • Double check that kSecAttrAccessibleAlways is passed to SecItemAdd() so that iOS doesn't apply any defaults on its own.
  • Note that accessibility class must be passed when creating item (i.e. to SecItemAdd()) and not when retrieving it (i.e. not to SecItemCopyMatching()). This is kind of obvious but it never hurts to reiterate.

If none of the above helps please post relevant code showing how the item is created and then how it's read.

Zoara answered 11/7, 2015 at 9:58 Comment(3)
Hi Andrey, on app startup I am doing a SecItemUpdate that retrieves the item, then updates accessibility. the operation finishes with a return code of noErr. Does SecItemUpdate ever work? Or must I use delete + add every time I want to update?Coastward
In general, SecItemUpdate() can change class only of the item can be read (i.e. SecItemCopyMatching() returns the item). This is because changing item's class requires that item to be decrypted first. This means SecItemUpdate() won't change kSecAttrAccessibleWhenUnlocked to kSecAttrAccessibleAlways unless device is unlocked. Think about it: if one could change classes as easy then what would be the point of having them at all? :)Zoara
I put SecItemUpdate in didBecomeActive, which only gets called if the app is in foreground, at which time the device must already be unlocked, and access should be granted. This update only needs to be run once which happened long ago, after that I was able to do actions on the device when it's locked, even now. The only confusion is, it looks like after an app update, no matter what the permission was set to in pervious versions of apps, it'll start as kSecAttrAccessibleWhenUnlocked which is the default, then update it to the correct value when I explicitly ask it to using delete + addCoastward
P
0

Access to the keychain is blocked when you have a password on your device and the device is locked. So you can't do any actions with the keychain from lock screen if you have a password.

Predicate answered 11/7, 2015 at 8:38 Comment(1)
This is true if the keychain access permission is set to kSecAttrAccessibleWhenUnlocked, but as stated in the question, my keychain access permission is kSecAttrAccessibleAlways. This should always work (it does work most of the time, just the upgrade flow is having access issues)Coastward

© 2022 - 2024 — McMap. All rights reserved.