Hi,
I'm adding a Content Filtering (FilterDataProvider) on macOS to an existing app and using MDM to avoid user interaction.
I start by pushing the following payloads to my machine:
com.apple.system-extension-policy
com.apple.webcontent-filter
And then installing notarized pkg containing my app and the NE.
Inspecting the system logs shows the following error:
neagent Failed to find a com.apple.networkextension.filter-data extension inside of app com.company_name.app_name.daemon
And calling
submit(request: .activationRequest(forExtensionWithIdentifier: bundleId, queue: queue))
results in:
Missing entitlement com.apple.developer.system-extension.install
Installing from Xcode on a SIP disabled machine works fine and both NE and CF are working as expected.
I followed the steps mentioned here https://developer.apple.com/forums/thread/737894 however the embedded entitlements already contained -systemextension suffix so I'm not sure if re signing and the subsequent steps are needed.
I also double checked that com.apple.developer.system-extension.install is present, certificates are not expired and that get-task-allow is not present in the embedded profile.
Here is what my release entitlement file looks like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>content-filter-provider-systemextension</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>com.company_name.app_name.network-extension.content-filter</string>
</array>
</dict>
and my release app entitlement:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.endpoint-security.client</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>content-filter-provider-systemextension</string>
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
</dict>
</plist>
redacted logs
@eskimo may I ask for your help here!
System Extensions
RSS for tagInstall and manage user space code that extends the capabilities of macOS using System Extensions.
Posts under System Extensions tag
126 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi All, I'm working on a camera system extension where the main app is supposed to transfer a video stream using IOSurface memory sharing to the cam extension.
I have built a sample app that does contains all the logic, but without a camera extension. So I'm essentially using IOSurface to render a video in one SwiftUI view and show the result in another SwiftUI view. Just for testing purposes. And everything works fine so far.
Now, when moving the receiver code to the camera extensions, I'm having problems in accessing the IOSurface via ID. I am sharing the IOSurface ID via UserDefaults. I know from the logs the ID is correctly transferred.
Here is the code that uses IOSurfaceLookup to get the IOSurface. But this fails with the given message. The error message prints the surface ID which is the correct one. I know this from the main app where I get the ID and print it as well.
private var surfaceId: Int = -1 {
didSet {
logger.info("surfaceId has changed")
if surfaceId == -1 {
stopReceivingFrames()
ioSurface = nil
} else {
guard let surface = IOSurfaceLookup(IOSurfaceID(surfaceId)) else {
logger.error("failed to lookup IOSurface with ID: \(self.surfaceId)")
return
}
self.ioSurface = surface
logger.info("surface set, now starting receiving frames")
startReceivingFrames()
}
}
}
My gut feeling says that this issue might be related to some missing entitlement, sandboxing. In general, I have a working camera extension. I'm just not able to render a video in the main app, and send it over to the camera extension to overlay another web cam.
Both, the main app and camera extension are in the same XCode workspace and share the same AppGroup.
In short, my actual questions are:
Is there any entitlement required for using IOSurface between app and camera system extension?
Is using IOSurface actually possible in system extensions?
Is there any specific setting/requirement that I need to handle to make this work?
We want to ressolve dns for predefined sets of private app domains.
We've added this rule:
NENetworkRule(destinationHost: NWHostEndpoint(hostname: Private Domain1(example.com), port: 53), protocol: .UDP)
As per apple documentation: A rule that matches all DNS queries/responses for hosts in the example.com domain.
do you think it will work i.e it will forward DNS requests UDP flow to transparent provider in all the cases?
or do you think the text is a bit misleading. it should instead say: "A rule that matches all DNS queries/responses for nameservers in the example.com domain"?
This rule that look for port 53 of that domain only works if the system really asks a nameserver of that specific domain, right?
So, what if a local DNS server or a different nameserver are taking care of the resolution?
Whenever I'm working on my content filter for macOS, I usually keep SIP disabled and with developer mode on (systemextensionsctl) as a convenience.
The issue: content filter stopped receiving any kind of traffic when SIP is disabled. I don't see any log lines in Console for new flows, and the filter can't block anything, since it doesn't get any flows. Issue started yesterday.
I tried several things and did some investigation, here are some findings:
Reboot: rebooting did not fix the issue (while keeping SIP disabled).
Reenabling SIP fixes the issue for both App Store and Xcode builds.
Code: latest published version also stopped working with SIP disabled. This version is stable and confirmed to work as reported by users.
Clean Xcode + rebuild did not fix the issue.
Lastly, I inspected the logs and did not see any errors standing out. I noticed the filter does get started (startFilter is called) and registered, but after that there are no errors/new flows or anything, just silence (logs below).
com.apple.networkextension default 15:22:22.270746-0300 : Calling startFilterWithCompletionHandler
com.extension.MyExtension info 15:22:22.270998-0300 Success applying filter settings
com.apple.networkextension debug 15:22:22.272705-0300 NESMFilterSession[My Extension:B9F3F30E-E0E0-4E53-8B32-EFC285E3CF6A]: Checking providerBundleIdentifier com.extension.MyExtension for pluginClass 4
com.apple.networkextension debug 15:22:22.272717-0300 Checking for com.extension.MyExtension - com.apple.networkextension.filter-data
com.apple.networkextension default 15:22:22.272728-0300 Found 1 registrations for com.extension.MyExtension (com.apple.networkextension.filter-data)
com.apple.networkextension debug 15:22:22.272778-0300 NESMFilterSession[My Extension:B9F3F30E-E0E0-4E53-8B32-EFC285E3CF6A]: com.extension.MyExtension is registered for pluginClass 4
Here are some additional info about my system:
macOS 15.1
Between yesterday and today, the only new Installation is XProtectPlistConfigData at 12:10AM
Thanks!
I'm working on a system extension leveraging endpoint security entitlement. However, while in development, is there a way to continue working and testing locally without having the endpoint security entitlement approved or needing the extension signed.
I got these errors running a build:
Provisioning profile "Mac Team Provisioning Profile: "com.xxxxx.extension" doesn't include the com.apple.developer.endpoint-security.client entitlement.
Hi,
I try to use NSUserDefaults to share some parameter values between the container app and the system extension. I have added the App Group in Signing & Capabilities in both apps. I set it in the container app and read it in the system extension app, but the information I read from the system extension is nil. I tested that I can read the information directly from the container app. Is the system extension running in the sandbox not allowed to read other app information? But the information I see should be OK, as shown below:
The container app code is as follows:
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.yourcompany.shared"];
[sharedDefaults setObject:@"Sample Data" forKey:@"SharedData"];
[sharedDefaults synchronize];
The system expansion reading code is as follows:
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.yourcompany.shared"];
NSString *data = [sharedDefaults objectForKey:@"SharedData"];
os_log_debug(logHandle, "NSUserDefaults: %{public}@", data);
I am working on developing a Mac app (WireGuard Apple VPN) that will be distributed outside the App Store.
I have added the network extension which is included in the system extension with packet tunneling capability.
I have created a build following these steps here: https://developer.apple.com/forums/thread/737894
as per your suggestions in my accepted post: https://developer.apple.com/forums/thread/761251
It works fine in this case when the machine has SIP disabled and systemextensionsctl developer enabled.
As soon as I have made changes on the machine to disable systemextensionsctl developer and enable SIP, it loads the system extension and also asks for network extension permission. But it does not connect to the VPN.
I have copied the app to the "/Applications" directory before opening it.
This issue is specific to macOS 15.1. It works fine for macOS 14.* and 13.*. Speaking of macOS 15.0, it didn't work in both cases with SIP enabled or disabled. So, it seems that it must be a bug in macOS 15.0 and it seems that this bug was partially fixed in macOS 15.1. Is that right?
I am currently planning to distribute the app to testers for final testing before rolling it out to a wider audience.
Am I missing something? Thanks in advance.
I have a need to do a coverage test for the networkextension function code implemented by the system extension, but I don't know how to implement this method.
For example, how do you use gtest or how do you use xctest to achieve these capabilities?
If you know, please let me know. Thanks
Starting on macOS Sequoia, flows originated in Firefox have an empty sourceAppAuditToken. Other apps contain a valid token.
Background: in order to fetch process info for a certain NEFilterFlow, my content filter extension uses sourceAppAuditToken, audit_token_to_pid() and proc_* (as recommended in #126820). When that fails, we use SecCodeCopyGuestWithAttributes, recommended in some other thread as a better alternative. Both approaches break when the sourceAppAuditToken is empty since they need the pid.
Debugging:
My logging shows audit token is empty for Firefox
Typical logs from com.apple.networkextension also indicate it fails to fetch the same info I'm looking for:
com.apple.networkextension debug 11:22:07.024588-0300 Fetching appInfo from cache for pid: 948 uuid: 5C40B765-C6C9-3641-A822-2BC44D264361 bundle id: (null)
com.apple.networkextension debug 11:22:07.024657-0300 Calling delegate lookup handler with pid: 948, uuid: 5C40B765-C6C9-3641-A822-2BC44D264361, bundleID: (null)
com.apple.networkextension debug 11:22:07.025856-0300 Could not look up appInfo for pid: 948 bundle id: (null) uuid: 5C40B765-C6C9-3641-A822-2BC44D264361
com.apple.networkextension error 11:22:07.025897-0300 Could not find app info, return the original flow without filling in app info
Handling new flow:
identifier = D89B5B5D-793C-4940-D992-4E90F2AD1900
procPID = 953
eprocPID = 948
direction = outbound
inBytes = 0
outBytes = 0
signature = {length = 32, bytes = 0x4afeafde b484aa0c c5cb8698 0567343d ... 7cdee33e 135666dd }
socketID = 19adf2904e92d9
localEndpoint = 0.0.0.0:0
remoteEndpoint = 17.33.202.170:443
protocol = 6
family = 2
type = 1
procUUID = 0C68E603-967E-3643-B225-378BD2A655F7
eprocUUID = 5C40B765-C6C9-3641-A822-2BC44D264361
Perhaps there's a bug when generating the audit token or could it be something with the Firefox signature?
I double-checked Firefox and it seems fine:
$ codesign --verify --verbose /Applications/Firefox.app
/Applications/Firefox.app: valid on disk
/Applications/Firefox.app: satisfies its Designated Requirement
Not sure if relevant, but codesign with -dv showed different flags in CodeDirectory when compared to chrome:
codesign -dv /Applications/Firefox.app
...
CodeDirectory v=20500 size=863 flags=0x10000(runtime) hashes=18+5
...
Versus chrome
CodeDirectory v=20500 size=1821 flags=0x12a00(kill,restrict,library-validation,runtime) hashes=46+7 location=embedded
Hi,
I developed a system extension that uses the content filter providers of the network extension. When I am using a VPN, I turn on the network extension and the VPN is disconnected. Can this problem be avoided? How to prevent VPN disconnection
Trying to flesh out an idea for an application which would rely on Endpoint Security Framework and Network Extension Framework, where intend the application to:
Forward certain ESF events to a backend (on a separate server)
Forward certain Unified logs to a backend (on a separate server)
Forwarding various DNS queries and responses (on a separate server)
Retrieve configuration from the backend to set Network Extension Filters
Are there any limitations and/or reasons not to bundle all this functionality into a single system extension?
I know of other applications where system extension is very thin and main application (daemon) communicates over xpc with the system extension, would this be considered best practice?
We’re encountering issues with a system extension that subscribes to multiple events. Some users are experiencing performance problems when running our extension alongside other system extensions like Microsoft Defender and Crowdstrike, which seem to generate a high volume of events. However, on certain Macs with an identical setup, there are no performance issues, making it difficult to pinpoint the cause.
Has anyone found ways to improve compatibility with other system extensions? Currently, we’re ignoring and caching events from other extensions to avoid unnecessary processing.
The specific ES events contributing to the issue seem to be:
• ES_EVENT_TYPE_AUTH_EXEC
• ES_EVENT_TYPE_AUTH_OPEN
I realize this is a broad question, but the documentation for endpoint security extensions is quite limited. Any insights or suggestions would be greatly appreciated!
We have network system extension which is fundamental part of our application and needs to be installed before the application can run.
In many cases we need the installation to be automated, i.e. without logged-in user (with the help of MDM solution like JAMF).
Is there a way to activate the extension fully automated without logged-in users?
I tried to call 'open -W -a /Application/' from the package's post install script. But seems launch fails if no user is logged in.
I try to mix content filter and endpoint security in one system extension, but get error below when the program invoke es_new_client(returned ES_NEW_CLIENTRESULT_ERR_INTERNAL).
Failed to open services: 0xe00002e2: Caller was denied connecting to the ES subsystem, possibly due to a sandbox violation.
how to solve this error while keeping two functionalities in one system extension?
or I have to seperate them?
The kernel sends SIGKILL to application if it handles ES_EVENT_TYPE_AUTH_OPEN and lldb is attached to this process.
App:
int main(int /*argc*/, char** /*argv*/)
{
es_client_t *pEpClient = nullptr;
es_new_client_result_t result = es_new_client(&pEpClient, ^(es_client_t *pClient, const es_message_t *pMessage)
{
switch (pMessage->event_type)
{
case ES_EVENT_TYPE_AUTH_OPEN:
{
uint32_t authorizedFlags = pMessage->event.open.fflag;
if ((authorizedFlags & FREAD) || (authorizedFlags & FWRITE))
{
std::filesystem::path filePath = std::string(pMessage->event.open.file->path.data, pMessage->event.open.file->path.length);
std::string fileName = filePath.filename();
if (fileName == "test.txt")
{
std::cout << "blocked fileName: " << filePath.filename() << std::endl;
authorizedFlags &= ~FWRITE;
authorizedFlags &= ~FREAD;
}
}
if (es_respond_flags_result(pClient, pMessage, authorizedFlags, false) != ES_RESPOND_RESULT_SUCCESS)
{
std::cout << "es_respond_flags_result() failed with error " << std::endl;
}
}
break;
default:
break;
}
});
if (result != ES_NEW_CLIENT_RESULT_SUCCESS)
{
std::cout << "es_new_client() failed." << std::endl;
return 1;
}
es_event_type_t eventsList[] =
{
ES_EVENT_TYPE_AUTH_OPEN
};
if (es_subscribe(pEpClient, eventsList, 1) == ES_RETURN_ERROR)
{
std::cout << "es_subscribe() failed." << std::endl;
}
// wait
int i = 0;
std::cin >> i;
if (es_delete_client(pEpClient) == ES_RETURN_ERROR)
{
std::cout << "es_delete_client() failed." << std::endl;
}
return 0;
}
(lldb) process attach --pid 61127
....
(lldb) c
Process 61127 resuming
Process 61127 exited with status = 9 (0x00000009) Terminated due to signal 9
System log:
Allowing set_exception_ports from [debugserver] on [ep_sample] for entitled process/debugger
Client did not respond in appropriate amount of time (client pid: 61127), sent SIGKILL
Hello everybody,
Since macOS 15, the systemextension allow in changed as switch style and put in the "Login items & Extensions". I know the URL navigating to here, which is:
x-apple.systempreferences:com.apple.LoginItems-Settings.extension
But the extension options we need to scroll deep down and we need to click the "!" to open it.
I want to open the finally window for user can easily see it and enable it. Please tell me how. Appreciate!!
NETransparentProxyProvider having these two methods:
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool
override func handleNewUDPFlow(
_ flow: NEAppProxyUDPFlow,
initialRemoteEndpoint remoteEndpoint: NWEndpoint
) -> Bool
During initial days when NETransparentProxyProvider was introduced,
We used handleNewFlow to handle NEAppProxyTCPFlow and handleNewUDPFlow to handle NEAppProxyUDPFlow .
Since handleNewUDPFlow is now deprecated, is it just okay to use handleNewFlow to handle both NEAppProxyTCPFlow & NEAppProxyUDPFlow?
will this works always or there are some scenario where keeping handleNewUDPFlow will be usefull?
I'm trying to create a network extension packaged as a system extension on macOS,
let request = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: "com.example.Desktop.PacketTunnelDesktop",
queue: DispatchQueue.main)
request.delegate = delegate
// Submit the request to the system.
let extensionManager = OSSystemExtensionManager.shared
extensionManager.submitRequest(request)
The application is installed in /Applications, I have also turned off SIP and systemextensionsctl developer on
I'm not getting any breakpoint hits on my request delegate, but I am getting some logs in the console app:
making activation decision for extension with teamID teamID("XXXXXX"), identifier com.example.Desktop.PacketTunnelDesktop
no related kext found for sysex `com.example.Desktop.PacketTunnelDesktop`
extension XXXXXXX com.example.Desktop.PacketTunnelDesktop (1.0/1) advancing state from validating to validating_by_category
validate: category: com.apple.system_extension.network_extension, extension: com.example.Desktop.PacketTunnelDesktop
waiting for external validation of extension with identifier com.example.Desktop.PacketTunnelDesktop
It seems to stop here, and running systemsextensionsctl list shows:
[validating by category] as the status.
I'm trying to find some barebones example code for a network extension packaged as system extension but couldn't find any. Any ideas where to go from here?
Hello Team,
I want to know if there's a way to uninstall System Extension without prompting the user for authorisation.
These are ways I found to uninstall System Extension
The deactivationRequest api prompts the user for uninstalling System extension.
If I use Apple script to drag and drop the application[which is embedded with System Extension] to trash also prompts the user.
The only workaround that doesn't prompt is by disabling SIP and using the systemextensionsctl uninstall command.
I want to know if there's any other solution that can uninstall System Extension without prompting the user for authorisation.
Thanks!
Hi,
We are currently working on porting our PacketTunnelProvider app extension to run as a system extension. Things are mostly working great, but we're now testing upgrades from the existing app extension to a system extension.
We have an existing configuration that gets created and runs perfectly fine with the app extension. Then, when we go and upgrade to the system extension, and attempt to connect using the same existing configuration.
We see this error in the nesessionmanager logs:
10:00:57.717694-0700 nesessionmanager Signature check failed: code failed to satisfy specified code requirement(s) error
10:00:57.717914-0700 nesessionmanager Rejecting agent com.agentBundleID.bundleID because it does not satisfy the code signature requirements error
10:00:57.717937-0700 nesessionmanager Failed to launch com.agentBundleID.bundleID
If we create a new configuration profile in our upgraded app w/system extension it works fine. The problem only occurs with existing profiles.
Our app doesn't even get any notification about this error,
startVPNTunnelWithOptions:andReturnError: doesn't return any error that we can work with.
My gut tells me this has to do with the ProviderDesignatedRequirement not being correct, but I really have no way to confirm this at all. The NETunnelProviderProtocol has no way to specify that in its API. Our providerBundleIdentifier was unchanged between the two extensions.
Is there anything that we can do here? Or are we stuck re-creating the configuration profile after an upgrade?