XProtect makes app hang when running an AppleScript

I now had the second user with 26.2. complaining about a hang in my app. The hang occurs when the first AppleScript for Mail is run. Here is the relevant section from the process analysis in Activity Monitor:

+                             2443 OSACompile  (in OpenScripting) + 52  [0x1b32b30f4]
+                               2443 SecurityPolicyTestDescriptor  (in OpenScripting) + 152  [0x1b32a2284]
+                                 2443 _SecurityPolicyTest(char const*, void const*, unsigned long)  (in OpenScripting) + 332  [0x1b32a2118]
+                                   2443 InterpreterSecurity_ScanBuffer  (in libInterpreterSecurity.dylib) + 112  [0x28c149304]
+                                     2443 -[InterpreterSecurity scanData:withSourceURL:]  (in libInterpreterSecurity.dylib) + 164  [0x28c148db4]
+                                       2443 -[XProtectScan beginAnalysisWithFeedback:]  (in XprotectFramework) + 544  [0x1d35a1e58]
+                                         2443 -[XPMalwareEvaluation initWithData:assessmentClass:]  (in XprotectFramework) + 92  [0x1d359ada4]
+                                           2443 -[XPMalwareEvaluation initWithRuleString:withExtraRules:withURL:withData:withAssessmentClass:feedback:]  (in XprotectFramework) + 36  [0x1d359b2a8]

My app is correctly signed and notarised. The first user had to completely uninstall/reinstall the app and the everything worked again.

Why does this happen? How can the problem be fixed?

Answered by DTS Engineer in 871339022

Thanks for attaching a sysdiagnose log to your bug (FB21334477). I took a look at the enclosed spin dump and I see this:

  Thread 0x222f6b    180 samples (216-395)    priority 31 (base 31)    cpu time 0.016s (43.3M cycles, 111.3M in…
  180  thread_start + 8 (libsystem_pthread.dylib + 7080) [0x19ea8cba8]
    180  _pthread_start + 136 (libsystem_pthread.dylib + 27656) [0x19ea91c08]
      180  ??? (XojoFramework + 1309844) [0x10569bc94]
        180  ??? (XojoFramework + 2624740) [0x1057dcce4]
          … redacted …
          180  -[NSAppleScript compileAndReturnError:] + 136 (Foundation + 9808488) [0x1a0c7da68]
            180  OSACompile + 52 (OpenScripting + 78068) [0x1bffa30f4]
              180  SecurityPolicyTestDescriptor + 152 (OpenScripting + 8836) [0x1bff92284]
                180  _SecurityPolicyTest(char const*, void const*, unsigned long) + 332 (OpenScripting + 8472) …
                  180  InterpreterSecurity_ScanBuffer + 112 (libInterpreterSecurity.dylib + 4868) [0x298e39304]
                    180  -[InterpreterSecurity scanData:withSourceURL:] + 164 (libInterpreterSecurity.dylib + 3…
                      180  -[XProtectScan beginAnalysisWithFeedback:] + 544 (XprotectFramework + 40536) [0x1e02…
                        180  -[XPMalwareEvaluation initWithData:assessmentClass:] + 92 (XprotectFramework + 116…
                          180  -[XPMalwareEvaluation initWithRuleString:withExtraRules:withURL:withData:withAss…
                            180  -[XPMalwareEvaluation initWithRuleString:withExtraRules:withURL:withData:force…
                              180  _dispatch_sync_f_slow + 148 (libdispatch.dylib + 71640) [0x19e8e47d8]
                                180  __DISPATCH_WAIT_FOR_QUEUE__ + 368 (libdispatch.dylib + 72736) [0x19e8e4c20]
                                  180  _dispatch_thread_event_wait_slow + 56 (libdispatch.dylib + 15116) [0x19e…
                                    180  __ulock_wait + 8 (libsystem_kernel.dylib + 10980) [0x19ea50ae4]
                                     *180  ??? (kernel.release.t8103 + 6504060) [0xfffffe0008defe7c]

Note I’ve redacted a bunch of frames because they contain your symbols and I wasn’t sure whether you were OK with me posting those details here.

There’s a couple of things I can tell from this:

  • This code is running on a secondary thread.
  • The -[XPMalwareEvaluation initWithRuleString:withExtraRules:withURL:withData:forcedRulesOnly:withAssessmentClass:feedback:] method is stuck within a dispatch_sync call.

Looking at the source for that routine, I see that it’s dispatching to the main queue. So, your stuck here because the main thread isn’t servicing the main queue.

Looking at the state of the main thread in the spin dump, I see that it’s spending a significant amount of time stuck here:

… elided …
  180  Thread.Start%%o<Thread> + 16 … redacted …
    180  ??? (XojoFramework + 2619288) [0x1057db798]
      180  ??? (XojoFramework + 2628580) [0x1057ddbe4]
        180  xojo::ConditionVariable::Wait(xojo::UniqueLock&) + 20 (XojoFramework + 1310640) [0x10569bfb0]
          180  __psynch_cvwait + 8 (libsystem_kernel.dylib + 17656) [0x19ea524f8]
           *180  ??? (pthread + 18508) [0xfffffe000bffd7ac]

I’m not sure what this is about. The Thread.Start… frame is your code, but the remaining frames are within your third-party runtime and it’s hard to tell what exactly it’s waiting on.

Regardless, the above should give you something to go on. To summarise:

  • You’re running an AppleScript.
  • Which is being scanned by XProtect.
  • Which is waiting for the main thread.
  • Which is stalled within the Xojo runtime.
  • As the result of your Thread.Start… code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Exact same problem with same stack trace for our app. We create an NSAppleScript option and it is now hanging when we call executeAndReturnError or compileAndReturnError.

Some more information:

  • My AppleScripts usually are very simple because the app does the heavy lifting. Think getting the accounts for Mail or the account type.
  • I have 2 places in the app where I use AppleScript. In the Setup the users determines which email she wants to archive from Mail. And then the user can archive the emails to a database. The Setup uses the AppleScripts in a thread. Archiving is threaded, too. But the AppleScripts are run on the main thread and before threading starts. The scripts run fine on the main thread. These are the same scripts which are used in the Setup!!!
  • I've moved the first AppleScript to osascript and now the second script hangs which is still using NSAppleScript.

The plan for today is to move more AppleScripts to osascript.

It’s hard to offer insight here without more information. Are you able to capture a sysdiagnose log while your app is hung? Well, get your user to capture that log, I guess.

See Bug Reporting > Profiles and Logs for advice on how to capture a sysdiagnose log.

Also, if your user is reluctant to send you the log, they can use Feedback Assistant to file the bug directly with Apple and then pass along the bug number for you to share here.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

After updating to the latest XProtect version I was able to see the hang, too. I've added a sysdiagnose to the ticket.

Thanks for attaching a sysdiagnose log to your bug (FB21334477). I took a look at the enclosed spin dump and I see this:

  Thread 0x222f6b    180 samples (216-395)    priority 31 (base 31)    cpu time 0.016s (43.3M cycles, 111.3M in…
  180  thread_start + 8 (libsystem_pthread.dylib + 7080) [0x19ea8cba8]
    180  _pthread_start + 136 (libsystem_pthread.dylib + 27656) [0x19ea91c08]
      180  ??? (XojoFramework + 1309844) [0x10569bc94]
        180  ??? (XojoFramework + 2624740) [0x1057dcce4]
          … redacted …
          180  -[NSAppleScript compileAndReturnError:] + 136 (Foundation + 9808488) [0x1a0c7da68]
            180  OSACompile + 52 (OpenScripting + 78068) [0x1bffa30f4]
              180  SecurityPolicyTestDescriptor + 152 (OpenScripting + 8836) [0x1bff92284]
                180  _SecurityPolicyTest(char const*, void const*, unsigned long) + 332 (OpenScripting + 8472) …
                  180  InterpreterSecurity_ScanBuffer + 112 (libInterpreterSecurity.dylib + 4868) [0x298e39304]
                    180  -[InterpreterSecurity scanData:withSourceURL:] + 164 (libInterpreterSecurity.dylib + 3…
                      180  -[XProtectScan beginAnalysisWithFeedback:] + 544 (XprotectFramework + 40536) [0x1e02…
                        180  -[XPMalwareEvaluation initWithData:assessmentClass:] + 92 (XprotectFramework + 116…
                          180  -[XPMalwareEvaluation initWithRuleString:withExtraRules:withURL:withData:withAss…
                            180  -[XPMalwareEvaluation initWithRuleString:withExtraRules:withURL:withData:force…
                              180  _dispatch_sync_f_slow + 148 (libdispatch.dylib + 71640) [0x19e8e47d8]
                                180  __DISPATCH_WAIT_FOR_QUEUE__ + 368 (libdispatch.dylib + 72736) [0x19e8e4c20]
                                  180  _dispatch_thread_event_wait_slow + 56 (libdispatch.dylib + 15116) [0x19e…
                                    180  __ulock_wait + 8 (libsystem_kernel.dylib + 10980) [0x19ea50ae4]
                                     *180  ??? (kernel.release.t8103 + 6504060) [0xfffffe0008defe7c]

Note I’ve redacted a bunch of frames because they contain your symbols and I wasn’t sure whether you were OK with me posting those details here.

There’s a couple of things I can tell from this:

  • This code is running on a secondary thread.
  • The -[XPMalwareEvaluation initWithRuleString:withExtraRules:withURL:withData:forcedRulesOnly:withAssessmentClass:feedback:] method is stuck within a dispatch_sync call.

Looking at the source for that routine, I see that it’s dispatching to the main queue. So, your stuck here because the main thread isn’t servicing the main queue.

Looking at the state of the main thread in the spin dump, I see that it’s spending a significant amount of time stuck here:

… elided …
  180  Thread.Start%%o<Thread> + 16 … redacted …
    180  ??? (XojoFramework + 2619288) [0x1057db798]
      180  ??? (XojoFramework + 2628580) [0x1057ddbe4]
        180  xojo::ConditionVariable::Wait(xojo::UniqueLock&) + 20 (XojoFramework + 1310640) [0x10569bfb0]
          180  __psynch_cvwait + 8 (libsystem_kernel.dylib + 17656) [0x19ea524f8]
           *180  ??? (pthread + 18508) [0xfffffe000bffd7ac]

I’m not sure what this is about. The Thread.Start… frame is your code, but the remaining frames are within your third-party runtime and it’s hard to tell what exactly it’s waiting on.

Regardless, the above should give you something to go on. To summarise:

  • You’re running an AppleScript.
  • Which is being scanned by XProtect.
  • Which is waiting for the main thread.
  • Which is stalled within the Xojo runtime.
  • As the result of your Thread.Start… code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Oh, one more thing. That dispatch_sync call being made by XProtect is doing some one-time initialisation. That initialisation has to be done on the main thread. So, you might be able to work around this by forcing that initialisation to run on the main thread in advance of all this secondary thread work. An easy way to do that would be to use NSAppleScript to run a trivial script from the main thread as part of your app init sequence.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the clarification, Quinn. That explains why the hang occurs and why the first AppleScript call needs to run on the main thread.

In my case, the problem is that this behavior appeared without documentation. My app legitimately automates Mail and has done so for years. Running AppleScripts from background threads is a normal pattern for this kind of software. Having the first call suddenly require main-thread initialization is a significant behavioral change.

There are some practical issues for developers:

• The change is not documented.

• The hangs are non-deterministic.

• There is no API to trigger or control the initialization.

• There is no error or diagnostic that would help identify the cause.

I understand that AppleScript can be an attack surface and that XProtect integration may be intentional. However, the current approach affects legitimate uses without giving developers a supported way forward.

If the platform requirement is that the first AppleScript runs on the main thread, Apple should state this clearly. Undocumented technical hoops do not stop attackers. They only make life harder for developers who are trying to use the system in a supported way.

I would appreciate it if you could pass along that this change has real-world impact on existing shipping software, and that documentation or a simple API would avoid a lot of confusion.

Thanks again for your time.

the problem is that this behavior appeared without documentation.

Understood. Unfortunately such is the nature of bugs. If we’d known about the developer impact of this change, we wouldn’t have shipped it. Or, if we had to ship it anyway, we would’ve called it out in the release notes.

Usually we’re able to flush out problems like this during the beta seed process, but this case is tricky because it seems to be triggered by the combination of macOS and XProtect changes )-:

If the platform requirement is that the first AppleScript runs on the main thread

I think you’ve misunderstood my previous posts. I’m not claiming that this is a change in “platform requirement”. IMO this is a bug that we should fix on our side. My suggestion to prime XProtect by running a dummy AppleScript from the main thread is a workaround, and I represented it as such (“you might be able to work around”).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Confirmed you must load on the main thread. But you can call AppleScript from any thread after. The bit in my custom class which must be initialized on main:

    init() {
        Bundle.main.loadAppleScriptObjectiveCScripts()
        let DIMBridgeClass: AnyClass = NSClassFromString("ApplescriptBridge")!
        self._DIM = DIMBridgeClass.alloc() as? DIMBridge
    }

where ApplescriptBridge is the script name and @objc(NSObject) protocol DIMBridge { .. } allows access to AppleScript variables and functions.

Oh, hey, thanks for pinging this thread, because it reminded me to take a look at the state of bwill’s bug report (FB21334477). And it seems that there have been changes in this space on macOS 26.3. Can either of you reproduce the issue on that release?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Quinn-

A user was reporting the crash which I could not reproduce locally. We are both running macOS 26.3 right after it came out (I upgraded to macOS 26 mostly because of this- rounded corners are annoying/wasteful, just saying).

Code is on GitHub (https://github.com/com-entonos/Desktop-Icon-Manager) in particular ViewController.swift, line 114. This works for the user. The previous version of that file crashes at start for the user.

The way the code crashed I could not explain (it was in a routine it should not be possible to reach at startup) unless there was UserDefaults value that changed. I had the user use 'defaults delete ...' to delete the relevant plist file, so I really don't know what the state of his machine was. If he had 'Automatically restore at start' checked, it would explain it. Default is not checked. He was at an university so who knows what IT does...

Good luck.

A user was reporting the crash which I could not reproduce locally.

A crash? bwill’s original report was about a hang. Are you using “crash” in the general sense? Or is it actually crashing? If so, please ask your user for crash report and post it see.

For advice on how to post a crash report, see Posting a Crash Report.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

No, crash. Report at end. 'atos' told me it was the 'restore' function and the line was using the script when the crash occurred. Logically, AppleScriptObjC/script should have finished loading before calling 'restore'. For the user, AppleScriptObjC had not finished loading. Crashing is fine by me, if the app was in that state.

User sent me a word document w/ copy/paste so you get just the text.

No, crash.

OK, then this is a different issue from the one that started this thread.

Rather than muddy the waters of this thread any further, I recommend that you start a new thread for this issue. Put it in App & System Services > Automation & Scripting and tag it with AppleScript. That way I’ll be sure to see it go by.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

With all due respect, I think this is the same issue. bwill found his code was hanging if he didn't load AppleScriptObjC on the main thread. He did not crash most likely because he was waiting for the load that never happened. In my case, the algorithm 'insured' AppleScriptObjC was loaded before use and so I never tested if it was actually loaded. My app requires access to an AppleScript to do anything else. It appears bwill was using AppleScript for two rather minor things.

In other words, I crash because I assumed there would be no problem loading. In my case, I think that is correct (if it doesn't load, the app is useless). I could capture the problem, but it makes more sense to crash so a Crash Report can be generated. That is, in this case the Crash Report did tell me AppleScriptObjC was still loading- something that should not have happened.

Regardless, the solution was to load AppleScriptObjC and script on the main thread. Afterwards, the script can be used from any thread.

Thanks,

Consider this:

Exception Type:    EXC_BREAKPOINT (SIGTRAP)
Exception Codes:   0x0000000000000001, 0x0000000102c799bc

This typically means that the app trapped, that is, it detected an internal error and deliberately crashed. For example, if you force unwrap an option that’s nil, you’ll trap like this.

Now look at the backtrace:

Thread 1 Crashed::  Dispatch queue: com.apple.root.utility-qos
0 DIM                     … 0x102c6c000 + 55740
1 DIM                     … 0x102c6c000 + 128212
2 libdispatch.dylib       … _dispatch_call_block_and_release + 32
3 libdispatch.dylib       … _dispatch_client_callout + 16
4 libdispatch.dylib       …  + 32
5 libdispatch.dylib       … _dispatch_root_queue_drain + 736
6 libdispatch.dylib       … _dispatch_worker_thread2 + 180
7 libsystem_pthread.dylib … _pthread_wqthread + 232
8 libsystem_pthread.dylib … start_wqthread + 8

frames 8 through 2 are Dispatch (GCD) machinery. Frame 1 is your code, which calls frame 0, which trapped. To learn more about this you need to symbolicate frames 1 and 0. See Adding identifiable symbol names to a crash report.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

That's what I did. It resolved to a line which tried to use the object to access the AppleScript. Before Apple's change, it could not reach that code.

I create the object which loads AppleScriptObjC/script on the .userInteractive queue. I then dispatch back to the main queue. There I test if the object is nil or if there is a problem calling the script. If so, I display error sheet and quit. Otherwise, UserDefaults is loaded and startup finishes accordingly. These tests to check if AppleScriptObjC/script correctly loaded failed to work.

Messy, but worked for over five years, until now.

btw, the picture of the Devices and Simulations is out-of-date along with the instructions. I did cli.

You have the crash report, GitHub has the actual source for that version in Releases. Uploaded .dSYM also. (https://github.com/com-entonos/Desktop-Icon-Manager/releases/tag/4.5.1)

Appreciate the interest,

GitHub has the actual source for that version in Releases

OK. I downloaded the app, the .dSYM, and the source code and took a look.

First, the crash report has this:

Binary Images:
       0x102c6c000 -        0x102c9ffff DIM arm64  <3e247524da7930099f63cd12ace4ab0e> …/DIM

I used dwarfdump to confirm that I have the right .dSYM:

% dwarfdump --uuid DIM.app/Contents/MacOS/DIM
UUID: 7A4DAC69-55A2-3D1B-B9E0-6A14AB2D5EEA (x86_64) DIM.app/Contents/MacOS/DIM
UUID: 3E247524-DA79-3009-9F63-CD12ACE4AB0E (arm64) DIM.app/Contents/MacOS/DIM

I then symbolicated those frames:

% atos -l 0x102c6c000 -o DIM.app.dSYM 0x102c799bc 0x102c8b4d4
closure #1 in ViewController.restore(_:) (in DIM) (ViewController.swift:207)
thunk for @escaping @callee_guaranteed () -> () (in DIM) + 28

Plugging that into the backtrace, I get this:

Thread 1 Crashed::  Dispatch queue: com.apple.root.utility-qos
0 DIM                     … 0x102c6c000 + 55740 closure #1 in ViewController.restore(_:) (in DIM) (ViewController.swift:207)
1 DIM                     … 0x102c6c000 + 128212 thunk for @escaping @callee_guaranteed () -> () (in DIM) + 28
2 libdispatch.dylib       … _dispatch_call_block_and_release + 32
3 libdispatch.dylib       … _dispatch_client_callout + 16
4 libdispatch.dylib       …  + 32
5 libdispatch.dylib       … _dispatch_root_queue_drain + 736
6 libdispatch.dylib       … _dispatch_worker_thread2 + 180
7 libsystem_pthread.dylib … _pthread_wqthread + 232
8 libsystem_pthread.dylib … start_wqthread + 8

I then loaded the app up in LLDB:

% lldb DIM.app
(lldb) target create "DIM.app"
Current executable set to '/Users/quinn/Desktop/DesktopIconManager4.5.1/DIM.app' (arm64).

and looked at the actual crashing address:

(lldb) disas -s 0x100000000+55740
DIM`Swift runtime failure: Unexpectedly found nil while unwrapping an Optional value:
DIM[0x10000d9bc] <+1288>: brk    #0x1

Note The crash report shows that 55740 is the offset from the start of the image to the crashing instruction. LLDB loads the main image at 0x100000000, that is, after the 4 GiB page zero segment. By adding these together we get the crashing address for the LLDB context.

The brk is a trap instruction for 64-bit Apple silicon code. When Swift inserts such a trap, it tries to put it at a unique address and add a symbol for that address that indicates the type of trap. Hence the nice output from LLDB.

Now let’s look at the source address indicated by the symbolication, line 207 of ViewController.swift:

203 // restore from arrangement given by name
204 func restore(_ name: String) {
205     updateUI("Restoring Icon Positions...")
206     DispatchQueue.global(qos: .utility).async { [unowned self] in
207         self.setSet(set: self.arrangements[name]!)
208         self.dim!.numOnDesktop = 0
            …
213     }
214 }

It seems most likely that self.arrangements[name] is nil [1]. This confirms my earlier assertion that this is an issue with your code rather than the system-level issue that bwill was bumping in to.

Looking further into the code, I bumped into some serious concurrency holes. You access self.arrangements from both the the main thread (via the timer scheduled in refreshTimer()) and a Dispatch global concurrent queue (via the code shown above). The Swift concurrency model considers this undefined behavior. Honestly, you’re lucky that you crashed so ‘cleanly’; concurrency bugs are often much harder to find.

I recommend that you take a step back, look at the big picture of the concurrency within this app, decide on a plan, and implement that. And if were in your shoes I’d enable the Swift 6 language mode, whereupon the compiler will help you avoid issues like this.

Oh, and I also recommend that you not use Dispatch global concurrent queues. See Avoid Dispatch Global Concurrent Queues has the full story on that front.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] It wouldn’t surprise me if the actual issue was that self.dim was nil, but self.dim has the same concurrency issues as self.arrangements.

Thank you. Particularly for the step-by-step, very informative.

The algorithm should have insured arrangements[name] is not nil well before func restore was called (therefore no test to confirm).

Appreciate your time and efforts- never fun looking at another's code. Also understand your concerns about concurrency. They are valid.

XProtect makes app hang when running an AppleScript
 
 
Q