I’ve created a Swift macOS app which uses SMJobBless
to create a helper with escalated privileges. This works fine—the helper gets installed to /Library/Privileged Helper Tools
and an accompanying LaunchDaemon gets created in /Library/LaunchDaemons
. However, the helper is unable to start successfully. Instead, it crashes with an “Illegal instruction: 4” message.
I’ve prepared the helper to respond to XML connections by implementing the NSXPCListenerDelegate
protocol. Here‘s my Helper main.swift
code:
import Foundation
class HelperDelegate: NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: HelperToolProtocol.self)
newConnection.exportedObject = HelperTool()
newConnection.resume()
return true
}
}
let delegate = HelperDelegate()
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()
The crash occurs on the last line, listener.resume()
.
I tried to launch the helper app manually from the command line (which is identical to what the LaunchDaemon does) and, again, it crashes with the above error message printed to stdout. I don’t have any more ideas on how to test this for the root cause. My implementation is more than rudimentary, following Apple’s guidlines for implementing XM services. Also, the various posts on SO regarding XML services haven’t helped me in resolving this issue. Has anyone of you tried to create a privileged helper in Swift successfully? BTW, the app is not sandboxed.
For the sake of completeness, here’s the code for the HelperTool
class referenced in my HelperDelegate
class above:
import Foundation
class HelperTool: NSObject, HelperToolProtocol {
func getVersion(withReply reply: (NSData?) -> ()) {
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString" as String) as? String ?? "<unknown version>"
let build = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "<unknown build>"
if let d = "v\(version) (\(build))".data(using: .utf8, allowLossyConversion: false) {
reply(d as NSData)
}
}
}
And finally the HelperToolProtocol
:
import Foundation
@objc(HelperToolProtocol) protocol HelperToolProtocol {
func getVersion(withReply: (NSData?) -> ())
}
Thanks for any help!
After days of testing I finally found a solution which makes my XPC helper launch correctly and respond to any messages. The problem lies in the last three lines of the
main.swift
module which currently readwhich, as put in the question, make the helper crash immediately upon the very last line.
I took these lines directly from Apple’s Creating XPC Services documentation. Here’s the documentation for the
NSXPCListener
resume()
function:The solution is to not call the
NSXPCListener.service()
singleton object but rather instantiate a newNSXPCListener
object using theinit(machServiceName:)
initializer passing the same Mach service name that is being used on the main app’s XPC connection. Asresume()
in this case would resume immediately—thus terminating the helper—you have to put it on the current run loop to have it run indeterminately. Here’s the new, working code: