NSDocumentController recentDocuments Sandbox (weird) issue

684 views Asked by At

I'm on Objective-C, Xcode 11, macOS not iOS, sandboxed application.

I need to manually update recent documents menu. I store the URLs in bookmarks so i can access them according to sandbox.

What is weird is that code A works but B does not. Does anyone have an explanation for this?

// Code A
NSURL* bookmarkURL = (some valid URL from bookmark);
[bookmarkURL startAccessingSecurityScopedResource]; <- returns TRUE
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:bookmarkURL];
//[bookmarkURL stopAccessingSecurityScopedResource]; <-- Without closing access it works

// Code B
NSURL* bookmarkURL = (some valid URL from bookmark);
[bookmarkURL startAccessingSecurityScopedResource]; <- returns TRUE
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:bookmarkURL];
[bookmarkURL stopAccessingSecurityScopedResource];

B doesn't work (with closing security access)! It feels wrong to not close security access. For case B the following error is thrown

Insert failed for list identifier com.apple.LSSharedFileList.ApplicationRecentDocuments Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" (Restricted by sandbox) UserInfo={NSDebugDescription=Restricted by sandbox}

Without starting security access at all same error is thrown (obviously).

1

There are 1 answers

0
marczellm On

It seems that the application should hold onto the security scoped permission as long as the document is in the (manually managed) recent list.

In my case the error manifested in that

  • the Clear Menu menu item in File / Open Recent stayed grayed out
  • the recent list did not persist across app launches
  • the recent list did not appear on the Dock.
  • I got errors in Console.app saying

    Insert failed for list identifier com.apple.LSSharedFileList.ApplicationRecentDocuments Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" (Access to list denied) UserInfo={NSDebugDescription=Access to list denied}

It seems that internally the recent documents list is managed by a separate thread, which in turn communicates with the system process sharedfilelistd. If I stopAccessingSecurityScopedResource, I can observe a sandboxd violation in Console.app with the following call stack (abbreviated):

Sandbox: MyApp(24093) deny(1) file-read-data /path/to/recent/document

Thread 1 (id: 364320):
0   libsystem_kernel.dylib          0x00007ff81b2265b2 mach_msg2_trap + 10
1   libsystem_kernel.dylib          0x00007ff81b22d5e4 mach_msg_overwrite + 692
2   libsystem_kernel.dylib          0x00007ff81b22689a mach_msg + 19
3   libdispatch.dylib               0x00007ff81b0dce6f _dispatch_mach_send_and_wait_for_reply + 518
4   libdispatch.dylib               0x00007ff81b0dd273 dispatch_mach_send_with_result_and_wait_for_reply + 50
5   libxpc.dylib                    0x00007ff81afacb97 xpc_connection_send_message_with_reply_sync + 238
6   Foundation                      0x00007ff81c18b423 __NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ + 9
7   Foundation                      0x00007ff81c1893c1 -[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:] + 3215
8   Foundation                      0x00007ff81c188723 -[NSXPCConnection _sendInvocation:withProxy:] + 91
9   CoreFoundation                  0x00007ff81b3234c6 ___forwarding___ + 671
10  CoreFoundation                  0x00007ff81b323198 _CF_forwarding_prep_0 + 120
11  SharedFileList                  0x00007ff823244145 __44-[SFLGenericList _insertItem:atIndex:error:]_block_invoke + 695
12  libsystem_trace.dylib           0x00007ff81afeaa0e _os_activity_initiate_impl + 51
13  SharedFileList                  0x00007ff823243ddc -[SFLGenericList _insertItem:atIndex:error:] + 301
14  SharedFileList                  0x00007ff823243ba5 __46-[SFLGenericList _insertItem:afterItem:error:]_block_invoke + 378
15  libsystem_trace.dylib           0x00007ff81afeaa0e _os_activity_initiate_impl + 51
16  SharedFileList                  0x00007ff823243961 -[SFLGenericList _insertItem:afterItem:error:] + 316
17  SharedFileList                  0x00007ff8232433b2 -[SFLGenericList insertItem:afterItem:error:] + 100
18  SharedFileList                  0x00007ff82323c085 +[SFLList(LSSharedFileListSupport) itemByInsertingAfterItem:name:URL:propertiesToSet:propertiesToClear:list:] + 1370
19  SharedFileList                  0x00007ff82323f3f7 LSSharedFileListInsertItemURL + 183
20  AppKit                          0x00007ff81e80868f -[_NSRecentItemsMenuController _notePendingRecentDocumentURLsForKey:documentsSnapshot:] + 677
21  Foundation                      0x00007ff81c1ada71 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
22  Foundation                      0x00007ff81c1ad969 -[NSBlockOperation main] + 98
23  Foundation                      0x00007ff81c1ad902 __NSOPERATION_IS_INVOKING_MAIN__ + 17
24  Foundation                      0x00007ff81c1acc02 -[NSOperation start] + 782
25  Foundation                      0x00007ff81c1ac8e8 __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 17
26  Foundation                      0x00007ff81c1ac7b6 __NSOQSchedule_f + 182
27  libdispatch.dylib               0x00007ff81b0d1771 _dispatch_block_async_invoke2 + 83
...

What this shows is there is a thread started by the system, performing some asynchronously dispatched operation called [_NSRecentItemsMenuController _notePendingRecentDocumentURLs...], which in turn issues XPC calls to a SFL (shared file list), which somehow results in an attempt to read the specified document, which is blocked by App Sandbox.

We can therefore conclude that after calling noteNewRecentDocumentURL, the application still has to hold the permission to access the file because the actual management of the recent list happens on another thread asynchronously, and that thread has to be able to check that the file exists or read it for some other reason.

I rewrote my application so that it holds onto the security scoped resources as long as the associated files are in the recent list, and stops accessing them when they are removed from the list. Internet wisdom says there are a few thousand such resources available, see What are the current kernel resource limits on security-scoped bookmarks?. Since my recent list never grows to such a size, my application will never run out of kernel resources.

See also https://developer.apple.com/forums/thread/710278