After many tests I finally found a solution. Apparently the & at the end of the command is whats causing the issue in Sonoma.
So I had to replace this command:
/Applications/myapp.app/Contents/MacOS/myapp --load-system-extension &
With this one:
/usr/bin/open /Applications/myapp.app --args --load-system-extension
And now it works perfectly! Im still curious about those better ways to activate an extension.
Post
Replies
Boosts
Views
Activity
I found a solution similar to what Quinn suggested on Google's Project Santa's github. I just ported it to swift. Here it is:
let deadline = DispatchTime(uptimeNanoseconds: cMsg.pointee.deadline) - .seconds(2)
let handlerSemaphore = DispatchSemaphore(value: 0)
handlerSemaphore.signal()
let deadlineSemaphore = DispatchSemaphore(value: 0)
queue.asyncAfter(deadline: deadline) {
// Check if event handler has already responded
if handlerSemaphore.wait(timeout: .now()) == .timedOut {
// Event handler already responded so exit
return
}
// Deadline met, event handler still processing, default to allow
self.allowMessage(cMsg)
deadlineSemaphore.signal()
}
queue.async {
// Process auth event and respond
handleAuthEvent(cMsg)
// Check if deadline was met
if handlerSemaphore.wait(timeout: .now()) == .timedOut {
// Wait for deadline block to allow the message, then free
deadlineSemaphore.wait()
}
self.freeMessage(cMsg)
}
Hi, I am having the same issue that Uddalak described. But in my case this only happens when I attach Xcode's debugger to my system extension. As soon as I do it the entire system becomes unresponsive for 30 secs until my app is killed (no crash log). It doesn't matter if I use the 0x7fffffff or 0xffffffff flags or if I cache the response or not.
In my event handler block I allow all events, like this:
es_respond_flags_result(client, msg, 0x7fffffff, true)
As per Quinns request here is a more detailed version of my code
static func handleProcessExec(_ msg: UnsafePointer<es_message_t>) {
let target = msg.pointee.event.exec.target.pointee
guard msg.pointee.process.pointee.is_es_client == false else {
es_respond_auth_result(esClient.client!, msg, ES_AUTH_RESULT_ALLOW, true)
return
}
// copy message so it can be used on another thread
guard let copiedMessage = copyMessage(msg) else {
es_respond_auth_result(esClient.client!, msg, ES_AUTH_RESULT_DENY, false)
return
}
DispatchQueue.global().async {
var process: ProcessDetails? = nil
let deadline = DispatchTime(uptimeNanoseconds: copiedMessage.pointee.deadline - (2*NSEC_PER_SEC))
let semaphore = DispatchSemaphore(value: 0)
// RuleResult contains what rule matched and if we should allow/deny
var result = RuleResult()
let timestamp = copiedMessage.pointee.time.tv_sec
DispatchQueue.global().async {
process = ProcessDetails(process: copiedMessage.pointee.event.exec.target.pointee)
ESEventRulesRunner.runProcessExecRules(process: process!, &result)
semaphore.signal()
}
_ = semaphore.wait(timeout: deadline)
es_respond_auth_result(esClient.client!, copiedMessage, result.verdict, false)
if result.verdict == ES_AUTH_RESULT_DENY {
// Log event to api
let event = ESEvent(process: process!, timestamp: timestamp, ruleType: result.type)
Api.postEvent(event: event)
}
freeMessage(copiedMessage)
}
}
Ok I found out what is happening but I need help fixing it. Look at this code
// Inside function that handles process exec messages
guard let copiedMessage = copyMessage(msg) else {
es_respond_auth_result(esClient.client!, msg, ES_AUTH_RESULT_ALLOW, false)
return
}
DispatchQueue.global().async {
var process: ProcessDetails? = nil
let deadline = copiedMessage.pointee.deadline
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
// This may take some time
process = ProcessDetails(process: copiedMessage.pointee.event.exec.target.pointee)
semaphore.signal()
}
_ = semaphore.wait(timeout: DispatchTime(uptimeNanoseconds: deadline - (2 * NSEC_PER_SEC)))
es_respond_auth_result(...)
freeMessage(copiedMessage)
}
I am calling description inside my ProcessDetails class, the problem is that if the timeout of the semaphore runs out I will free the copiedMessage but the initialization of ProcessDetails is still going so that results in a crash. How could I kill that seconds thread before freeing the message? Or should I somehow set another semaphore and wait for that thread to finish (even though I already responded to the message) to free the message?
12-17-2021 Xcode Version 13.2 (13C90) MBP 13-inch,i7, 2017, macOS 11.6.1
I honestly can't believe how after all this years the bug is still there. It is extremely anoying to have to restart Xcode everytime I want to test something in playgrounds
Quinn, as you said, for apps as big as XCode this can be very slow. I understand I can cache the result but if I am doing this in the context of an ES_EVENT_TYPE_AUTH_EXEC I would be blocking the first execution of Xcode for a long time before my analysis finishes. What would you do in my case?
Turns out I was setting the exportedInterface instead of the remoteObjectInterface on the NSXPCConnection.
Another question I had about the willCompleteAfterReboot is why is this called? I want to better understand this problem I am having about extensions needing a reboot to complete an upgrade. What is preventing the system from starting my extension without the need of a reboot?
I am using a Network Extension, if I were to add Endpoint Security to my extension would something in the activation process change? I can't see antivirus software that uses this framework requiring a reboot for every update to their extension.
Would manually deactivating my System Extension before installing the new version help? I was thinking about doing that in the preinstall script of my pkg installer by executing the container app with a uninstall parameter like this:
/Applications/app/Contents/MacOS/app --unload-extension
Would this work? Just accessing the NEAppProxyFlow and returning true without having to handle the flow
// NEDNSProxyProvider
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
NSLog("DNSProxyProvider: handleFlow")
if let tcpFlow = flow as? NEAppProxyTCPFlow {
let remoteHost = (tcpFlow.remoteEndpoint as! NWHostEndpoint).hostname
let remotePort = (tcpFlow.remoteEndpoint as! NWHostEndpoint).port
// Do whatever I want with this data
} else if let udpFlow = flow as? NEAppProxyUDPFlow {
let localHost = (udpFlow.localEndpoint as! NWHostEndpoint).hostname
let localPort = (udpFlow.localEndpoint as! NWHostEndpoint).port
// Do whatever I want with this data
}
return true
}
Ok so here is what I came up with. This works fine, the only thing I have yet to see is if it has any memory leaks. I hope this helps someone in the future :) If you have any suggestions to the func please let me know.
Also, you should first convert the audit_token to pid using the function mentioned above.
func getArgs(from pid: Int32) -> [NSString]? {
var arguments: [NSString] = []
var mib: [Int32] = [0, 0, 0]
var argsMax: Int = 0
mib[0] = CTL_KERN
mib[1] = KERN_ARGMAX
var size = MemoryLayout<Int>.stride(ofValue: argsMax)
if sysctl(&mib, 2, &argsMax, &size, nil, 0) == -1 {
return nil
}
let processArgs = UnsafeMutablePointer<CChar>.allocate(capacity: argsMax)
mib[0] = CTL_KERN
mib[1] = KERN_PROCARGS2
mib[2] = pid
size = argsMax as size_t
// Get process arguments
if sysctl(&mib, 3, processArgs, &size, nil, 0) == -1 {
return nil
}
if size <= MemoryLayout<Int>.size {
return nil
}
var numberOfArgs: Int32 = 0
//Get number of args
memcpy(&numberOfArgs, processArgs, MemoryLayout.size(ofValue: numberOfArgs))
// Initialize the pointer to the start of args
var parser: UnsafeMutablePointer<CChar> = processArgs + MemoryLayout.size(ofValue: numberOfArgs)
// Iterate until NULL terminated path
while parser < &processArgs[size] {
if 0x0 == parser.pointee {
// arrived ar argv[0]
break
}
parser += 1
}
// sanity check
if parser == &processArgs[size] {
return nil
}
while parser < &processArgs[size] {
if 0x0 != parser.pointee {
break
}
parser += 1
}
// sanity check
if parser == &processArgs[size] {
return nil
}
var argStart: UnsafeMutablePointer<CChar>? = parser
// Get all args
while parser < &processArgs[size] {
if parser.pointee == CChar(0) {
if nil != argStart {
let argument = NSString(utf8String: argStart!)
if argument != nil {
arguments.append(argument!)
}
}
argStart = parser + 1
if arguments.count == numberOfArgs {
break
}
}
parser += 1
}
// Is this free necessary?
free(processArgs)
return arguments
}
Yes, I can get the remoteEndpoint populated
guard let socketFlow = flow as? NEFilterSocketFlow,
let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint,
let localEndpoint = socketFlow.localEndpoint as? NWHostEndpoint else {
return nil
}
if #available(macOS 11.0, *) {
if let hostname = socketFlow.remoteHostname {
// Access hostname
}
}
Thanks for the help!
Lastly, do you guys have a sample project using NETransparentProxyProvider because I could not find a single repo on github using this api.
create your outbound copier, and then open the flow with your inbound copier.
So no matter the direction of the flow (inbound/outbound) I need to use both inboundCopier and outboundCopier correct?