XPC service and mac application do not connect to each other

I am currently writing an agent for endpoint security. I cannot connect the application and the xpc service. I start the plist with launchctl and then open the application, but it does not connect and the application runs dysfunctionally. I leave the code of the ViewController in the application and the XPCConnection in the xpc service below.

Note: I create the XPC service in Xcode like a normal application and write it as a service application with "Application is background only"

ViewController.swift

    func establishConnection() {
        XPCConnection.shared.connectToDaemon(bundle: Bundle.main, delegate: self) { success in
            DispatchQueue.main.async { [self] in
                if !success {
                    controlButton.isEnabled = false
                    configMenuStatus(start: false, stop: false)
                    alertWithError(error: "Unable to start monitoring for broken connection with daemon.")
                } else {
                    Logger(.Info, "Connect to daemon successfully.")
                }
            }
        }
    }

XPCConnection.swift

    func connectToDaemon(bundle: Bundle, delegate: ClientXPCProtocol, handler: @escaping (Bool) -> Void) {
        guard connection == nil else {
            Logger(.Info, "Client already connected.")
            handler(true)
            return
        }
        guard getMachServiceName(from: bundle) == ClientBundle else {
            handler(false)
            return
        }
        
        let newConnection = NSXPCConnection(machServiceName: DaemonBundle)
        newConnection.exportedObject = delegate
        newConnection.exportedInterface = NSXPCInterface(with: ClientXPCProtocol.self)
        newConnection.remoteObjectInterface = NSXPCInterface(with: DaemonXPCProtocol.self)
        newConnection.invalidationHandler = {
            self.connection = nil
            Logger(.Info, "Daemon disconnected.")
            handler(false)
        }
        newConnection.interruptionHandler = {
            self.connection = nil
            Logger(.Error, "Daemon interrupted.")
            handler(false)
        }
        connection = newConnection
        newConnection.resume()
        
        let proxy = newConnection.remoteObjectProxyWithErrorHandler { error in
            Logger(.Error, "Failed to connect with error [\(error)]")
            self.connection?.invalidate()
            self.connection = nil
            handler(false)
        } as? DaemonXPCProtocol
        
        proxy!.connectResponse(handler)
        handler(true)
    }

This is the error photo, the application continues to work

First, I checked to see if I had made a mistake in the bundle identifier, but I could not find an error, and then I realized that I had not run the launchd service. Then I ran it, but it did not make any sense. What I am trying to do is to connect and run the network extension and endpoint security with this service, but the xpc service does not connect to each other.

Replies

I’d like to clarify your setup. You wrote:

I am currently writing an agent for endpoint security.

I believe you’re using “agent” in a generic sense here. On macOS, the term agent usually refers to a launchd agent, that is, a background process managed by launchd that runs in one or more user contexts. This is in contrast to a launchd daemon, which runs in the global context.

I create the XPC service in Xcode like a normal application

I think you’re mixing up your terms again. An XPC service is a small program bundled within an app or framework. It has a bundle structure with the extension .xpc.

I commonly see folks use the term XPC service to refer to a named XPC endpoint [1]. For more background on this term, see XPC and App-to-App Communication.

I am currently writing an agent for endpoint security.

Endpoint security generically? Or a client of the Endpoint Security framework?

If it’s the latter, there are two ways it can be packaged:

  • As a launchd daemon

  • As an Endpoint Security system extension

Your text suggests that you’re using the former but you’ve tagged your thread with System Extension which suggests that you’re using the latter. Which is it?

This matters because it affects how you start your ES client.

Share and Enjoy

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

[1] Historically I used XPC Service for the program and XPC service for the named endpoint, but I’ve since decide that that’s way too subtle (-:

Hello Quinn,

I apologize for the delay in my response. I'm a high school student currently in exam week. Thank you for your feedback.

To clarify, I'm actually working on a delegate but it looks like there might be some confusion in the terminology. Let me explain:

I created a normal mac app project in xcode and this application is launched with launchd

The xpc service is not as you say, I created a new app with a new target and made it run completely in the background. This background becomes my service and it loads the endpoint security system extension and kext. I created the endpoint security plugin using the Endpoint Security framework

According to your last question, launchd starts the program's service and loads the kext and system extension in the service.

Thank you for your help and reading this message.

Kind regards,

Bugra

According to your last question, launchd starts the program's service and loads the kext and system extension in the service.

What does this kext come into things?

Regardless, if you have an ES appex then it should publish an XPC named endpoint via the NSEndpointSecurityMachServiceName property. See the EndpointSecurity man page for the details.

Any XPC client will be able to connect to that endpoint using the ‘Mach service’ APIs. It’s best to set the ‘privileged’ option. So, if you’re using NSXPCConnection, a client would connect using this code:

let connection = NSXPCConnection(machServiceName: "SSS", options: [.privileged])

where SSS is the NSEndpointSecurityMachServiceName value.

As to how you’d communicate between other components of your product, I’m not sure of my answer because I’m still not 100% sure of what those components are. However, I can say that XPC puts strict limits on the how you can set up your IPC structure. I discuss this in same detail in XPC and App-to-App Communication.

Regardless of the exact details of your setup, you could always use your ES sysex to implement the XPC rendezvous technique. However, there may be better options depend on exactly how the various components within your product are set up.

Share and Enjoy

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

For Mac versions that do not have the kext endpoint security framework, that is, catalina and later, the system extension is loaded, while for the lower versions, kext is loaded. The program I am trying to make works as follows. Don't pay too much attention to the arrows, the program will generally work like this.

When we open an application, it connects to the service and monitors and controls the transactions.

kext and system extension do the same thing but kext uses kauth from old sdk. But the reason here is not related to kext

I have already made NSXPCConnection as you wrote. Below I will write the code of the places causing the problem.

XPCConnection.swift

    func connectToDaemon(bundle: Bundle, delegate: ClientXPCProtocol, handler: @escaping (Bool) -> Void) {
        guard connection == nil else {
            Logger(.Info, "Client already connected.")
            handler(true)
            return
        }
        guard getMachServiceName(from: bundle) == ClientBundle else {
            handler(false)
            return
        }
        
        let newConnection = NSXPCConnection(machServiceName: DaemonBundle)
        newConnection.exportedObject = delegate
        newConnection.exportedInterface = NSXPCInterface(with: ClientXPCProtocol.self)
        newConnection.remoteObjectInterface = NSXPCInterface(with: DaemonXPCProtocol.self)
        newConnection.invalidationHandler = {
            self.connection = nil
            Logger(.Info, "Daemon disconnected.")
            handler(false)
        }
        newConnection.interruptionHandler = {
            self.connection = nil
            Logger(.Error, "Daemon interrupted.")
            handler(false)
        }
        connection = newConnection
        newConnection.resume()
        
        let proxy = newConnection.remoteObjectProxyWithErrorHandler { error in
            Logger(.Error, "Failed to connect with error [\(error)]")
            self.connection?.invalidate()
            self.connection = nil
            handler(false)
        } as? DaemonXPCProtocol
        
        proxy!.connectResponse(handler)
        handler(true)
    }

XPCServer.swift

class XPCServer: NSObject {
    static let shared = XPCServer()
    var fusionlog = FusionLog()
    var listener: NSXPCListener?
    var connection: NSXPCConnection?
    private func getMachServiceName(from bundle: Bundle) -> String {
        let clientKeys = bundle.object(forInfoDictionaryKey: ClientName) as? [String: Any]
        let machServiceName = clientKeys?[MachServiceKey] as? String
        return machServiceName ?? ""
    }
    func connectToSext(bundle: Bundle, delegate: ManagerXPCProtocol, handler: @escaping (Bool) -> Void) {
        guard connection == nil else {
            Logger(.Info, "Manager already connected.")
            handler(true)
            return
        }
        guard getMachServiceName(from: bundle) == ClientBundle else {
            handler(false)
            return
        }
        
        let newConnection = NSXPCConnection(machServiceName: SextBundle)
        newConnection.exportedObject = delegate
        newConnection.exportedInterface = NSXPCInterface(with: ManagerXPCProtocol.self)
        newConnection.remoteObjectInterface = NSXPCInterface(with: SextXPCProtocol.self)
        newConnection.invalidationHandler = {
            self.connection = nil
            Logger(.Info, "System Extensions disconnected.")
            handler(false)
        }
        newConnection.interruptionHandler = {
            self.connection = nil
            Logger(.Error, "System Extensions interrupted.")
            handler(false)
        }
        connection = newConnection
        newConnection.resume()
        
        let proxy = newConnection.remoteObjectProxyWithErrorHandler { error in
            Logger(.Error, "Failed to connect with error [\(error)]")
            self.connection?.invalidate()
            self.connection = nil
            handler(false)
        } as? SextXPCProtocol
        
        proxy?.connectResponse(handler)
        handler(true)
    }
}

FusionCommon.swift

func getSignInfoFromPath(_ path: String) -> [String] {
    let fileUrl = URL(fileURLWithPath: path)
    var secCode: SecStaticCode?
    var status = SecStaticCodeCreateWithPath(fileUrl as CFURL, SecCSFlags(rawValue: 0), &secCode)
    if status != errSecSuccess || secCode == nil {
        Logger(.Warning, "Failed to create static signed code for [\(path)] with error [\(status)].")
        return []
    }
    
    var secDict: CFDictionary?
    status = SecCodeCopySigningInformation(secCode!, SecCSFlags(rawValue: kSecCSSigningInformation), &secDict)
    if status != errSecSuccess || secDict == nil {
        Logger(.Warning, "Failed to copy signed info for [\(path)] with error [\(status)].")
        return []
    }
    let signedDict = secDict! as NSDictionary
    guard let certChain = signedDict[kSecCodeInfoCertificates as NSString] as? NSArray else {
        return []
    }
    
    var signInfo = [String]()
    for cert in certChain {
        var name: CFString?
        status = SecCertificateCopyCommonName(cert as! SecCertificate, &name)
        if status != errSecSuccess || name == nil {
            continue
        }
        signInfo.append((name! as String))
    }
    
    return signInfo
}

Some of these definitions do not fit, that's why I write them

launchd service running in the background

Thanks for reading and your help. 😌

Add a Comment