Post

Replies

Boosts

Views

Activity

Faulty, hard-to-understand XPC behavior with remote methods that have a reply-block
Assume this over-simplified @protocol I'm using for my XPC-service: @protocol MyMinimalProtocol <NSObject> - (void)getStatusWithReply:(void (^ _Nullable)(NSDictionary * _Nonnull))reply; @end The Client side would then NSXPCConnection *connection = [[NSXPCConnection alloc] initWithMachServiceName:myServiceLabel options:NSXPCConnectionPrivileged]; connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyMinimalProtocol)]; connection.interruptionHandler = ^{ NSLog(@"XPC: connection - interrupted"); }; connection.invalidationHandler = ^{ NSLog(@"XPC: connection - invalidated"); }; [connection resume]; [connection.remoteObjectProxy getStatusWithReply:^(NSDictionary * response) { NSLog(@"XPC service status received - %@", response); }]; So far - so good. My XPC service receives the asynchronous call, schedules it's "status gathering operation" on internal background queue, and returns. Later - when information is available, my XPC service executes the reply-block then, on the remote calling side - I see the log line with the status, as expected. BUT!!! If I add another different code-block argument to the method e.g. @protocol MyMinimalProtocol <NSObject> - (void)getStatusWithReply:(void (^ _Nullable)(NSDictionary * _Nonnull))reply andFailureBlock:(void (^ _Nullable)(NSError * _Nonnull))failureBlock; @end Then all hell breaks loose. Both XPC service and the client crash with hilarious crash reasons I can't decipher. Here's "Client side" caller crash (excerpt - forgive the methods are NOT the simplified ones above) XPC Client(Caller) side crash - excerpt while on the "XPC Service" side, crashes like these: XPC service side crash excerpt I wonder if there's something inherently wrong with having two code-block arguments for an XPC remote method? Another issue. The client XPC calls are asynchronous. They return immediately. The XPC service implementing the remote-call also returns immediately - and it executes the "reply block" far (a minute!) later, on another queue. However, if the XPC service attempts to execute the code-block MORE THAN ONCE, then the client-side code-block is only called ONCE. rest of the executions look benign in the XPC-service side - but never happen on the calling (client) side. Any idea why? can this be overcome? Any thoughts/ideas/references to documentation will be greatly appreciated. I couldn't find any good source on this. Thanks.
4
0
2.3k
Sep ’23
How to publish an XPC Service in a global daemon that employs EndpointSecurity framework?
I have a global daemon managed by launchd, whose .plist is installed in /Library/LaunchDaemons). To be correctly entitled and code-signed so it can communicate with EndpointSecurity framework, its executable resides in a normal Mac App bundle (main() will run as minimal UI when launched from UI, and as a daemon when launched by launchd). This means that the ProgramArguments.0 in its .plist looks something like /Library/PrivilegedHelperTools/MyDaemonApp.app/Contents/MacOS/MyDaemonApp Now I need this daemon to publish an XPC Service (with few control commands) so that other components of our system (UI app, a user-context launchd-daemon and another launchd global-daemon) will be able to connect to The XPC Service and control it via the published protocol. I read some answers here, and also found a working order sample code that does just this here - https://github.com/jdspoone/SampleOSXLaunchDaemon But when I apply its content to my global daemon, word for word - it doesn't work - meaning, clients cannot create a connection to The XPC Service. The daemon is up and running, and functional. its .plist is quite simple and looks like this: <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.mycompany.itm.service</string> <key>KeepAlive</key> <true/> <key>RunAtLoad</key> <true/> <key>MachServices</key> <dict> <key>com.mycompany.itm.service</key> <true/> </dict> <key>ProgramArguments</key> <array> <string>/Library/PrivilegedHelperTools/IMyDaemonApp.app/Contents/MacOS/MyDaemonApp</string> <string>-monitor</string> <string>-protectDeviceProtocol</string> <string>USB</string> </array> </dict> </plist> It creates and starts an XPC listener in MYXPCListener.h like thus: #import <Foundation/Foundation.h> #import "MYXPCProtocol.h" NS_ASSUME_NONNULL_BEGIN @interface OITPreventionXPCService : NSObject (instancetype) init; (void) start; /* Begin listening for incoming XPC connections */ (void) stop; /* Stop listening for incoming XPC connections */ @end NS_ASSUME_NONNULL_END and the implementation is: /* AppDelegate.m */ @interface MYXPCService () <NSXPCListenerDelegate, OITPreventionXPCProtocol> @property (nonatomic, strong, readwrite) NSXPCListener *listener; @property (nonatomic, readwrite) BOOL started; @end @implementation OITPreventionXPCService (instancetype) init {     if ((self = [super init]) != nil) {         _listener = [[NSXPCListener alloc] initWithMachServiceName:@"com.mycompany.itm.service"];         _listener.delegate = self;         if (_listener == nil) {             os_log_error(myLog, "XPCListener failed to initialize");         }         _started = NO;     }     return self; } (void) start {     assert(_started == NO);     [_listener resume];     os_log_info(myLog, "XPCListener resumed");     _started = YES; } (void) stop {     assert(_started == YES);     [_listener suspend];     os_log_info(myLog, "XPCListener suspended");     _started = NO; } /* NSXPCListenerDelegate implementation */ (BOOL) listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {     os_log_info(myLog, "Prevention XPCListener is bequsted a new connection");     assert(listener == _listener);     assert(newConnection != nil);     newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MYXPCProtocol)];     newConnection.exportedObject = self; &#9;&#9;[newConnection resume];     return YES; } /* Further down this implementation, I have implementations to all the methods in MYXPCProtocol. */ @end Now the client code (and I tried EVERY kind of client, signed unsigned, daemon, UI, root privileged, or user-scoped - whatever). For example, in the AppDelegate of a UI app: #import "AppDelegate.h" #import "MYXPCProtocol.h" @interface AppDelegate () @property (strong) IBOutlet NSWindow *window; @property (nonatomic, strong, readwrite) NSXPCConnection *connection; /* lazy initialized */ @end @implementation AppDelegate (NSXPCConnection *) connection {     if (_connection == nil) {         _connection = [[NSXPCConnection alloc] initWithMachServiceName:daemonLabel options:NSXPCConnectionPrivileged];         _connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MYXPCProtocol)];         _connection.invalidationHandler =  ^{             self->_connection = nil;             NSLog(@"connection has been invalidated");         };         [_connection resume];         /* New connections always start suspended */     }     return _connection; } (IBAction) getServiceStatus:(id)sender {     [self.connection.remoteObjectProxy getStatus:^(NSString * _Nonnull status) {         NSLog(@"MY XPC Service status is: %@", status);     }]; } @end but no matter what I do - I always get the "connection invalidated". The sample launchDaemon that works - is not code-signed at all!!! but mine, which is both signed and checking of which yields $ spctl --assess --verbose IMyDaemonApp.app IMyDaemonApp.app: accepted source=Notarized Developer ID   I'm at a loss - and would like to get any advice, or any helpful documentation (I've been reading TN2083, and man launchctl and man launchd.plist and many other pages - to no avail. There seems to be no real "programming guide" for XPC and no reasonable sample code on Apple developer site to fit my needs. Last - this is MacOS 10.15.7, and latest Xcode 12.3
7
0
2.8k
Oct ’22
What is the recommended way to count files recursively in a specific folder
Given a directory path (or NSURL) I need to get the total number of files/documents in that directory - recursively - as fast and light as possible. I don't need to list the files, and not filter them. All the APIs I found so far (NSFileManger, NSURL, NSDirectoryEnumerator) collect too much information, and those who are recursive - are aggregating the whole hierarchy before returning. If applied to large directory - this both implies a high CPU peak and slow action, and a huge memory impact - even if transient. My question: What API is best to use to accomplish this count, must I scan recursively the hierarchy? Is there a "lower level" API I could use that is below NSFileManager that provides better performance? One time in the middle-ages, I used old MacOS 8 (before MacOS X) file-system APIs that were immensely fast and allowed doing this without aggregating anything. I write my code in Objective-C, using latest Xcode and MacOS and of course ARC.
7
0
1.1k
Jul ’24
How to find WHY my app triggers LNP popoup on MacOS 15
My App is a rather small menu-bar status-item app with 2 informational windows. It does NOT make use of ANY of the APIs mentioned here: https://developer.apple.com/forums/thread/663874 that are bound to need "Local Network" hence trigger TCC dialog. Yet - on first run of the app, the dialog pops. App is Obj-C, and the only APIs used are Notification-Center (for scheduling local notifications to the user), XPC connections and calls to other (our) apps for gathering that information, plus normal AppKit windowing (Controls, Text-fields, etc.) nothing else. Obviously SOMETHING I do causes the thing - and I know for sure this app DOES NOT NEED access to the local network - only I do not know how to identify the specific API I need to avoid using (or change the way I'm using) Are there any specific system logs to watch for? Is there any official set of APIs that will trigger the dialog? Provided that I cannot avoid this - could this permission be granted via MDM profile payload? Our product comes with
8
1
886
Jan ’25
es_mute_path() vs. deprecated es_mute_path_literal() - incompatibility and wrong documentation
I recently upgraded a line of code in my Endpoint-Security client, to remove a deprecation warning: for (NSString *mutePath in ignoredBinaryPaths) { //(old) res = es_mute_path_literal(self.esClient, [mutePath UTF8String]); res = es_mute_path(self.esClient, [mutePath UTF8String], ES_MUTE_PATH_TYPE_TARGET_LITERAL); if (res!=ES_RETURN_SUCCESS) os_log_error(setupLog, "Failed to white-list binary:%{public}@ error:%{errno}d", mutePath, errno); } However, after this change, I started receiving tons of ES event messages, for AUTH_OPEN and AUTH_CREATE and many others, from processes/executables I explicitly and successfully muted! Since ES is so performance sensitive - I got worried. Inspecting better the new API I found incoherent documentation and even misleading and contradicting definitions. But the ES headers say differently!!! /** * @brief Suppress all events matching a path. * * @param client The es_client_t for which the path will be muted. * @param path The path to mute. * @param type Describes the type of the `path` parameter. * * @return es_return_t A value indicating whether or not the path was successfully muted. * * @note Path-based muting applies to the real and potentially firmlinked path * of a file as seen by VFS, and as available from fcntl(2) F_GETPATH. * No special provisions are made for files with multiple ("hard") links, * or for symbolic links. * In particular, when using inverted target path muting to monitor a * particular path for writing, you will need to check if the file(s) of * interest are also reachable via additional hard links outside of the * paths you are observing. * * @see es_mute_path_events * @discussion When using the path types ES_MUTE_PATH_TYPE_TARGET_PREFIX and ES_MUTE_PATH_TYPE_TARGET_LITERAL Not all events are * supported. Furthermore the interpretation of target path is contextual. For events with more than one target path (such as * exchangedata) the behavior depends on the mute inversion state Under normal muting the event is suppressed only if ALL paths * are muted When target path muting is inverted the event is selected if ANY target path is muted For example a rename will be * suppressed if and only if both the source path and destination path are muted. Supported events are listed below. For each * event the target path is defined as: * * EXEC: The file being executed * OPEN: The file being opened * MMAP: The file being memory mapped * RENAME: Both the source and destination path. * SIGNAL: The path of the process being signalled * UNLINK: The file being unlinked * CLOSE: The file being closed * CREATE: The path to the file that will be created or replaced * GET_TASK: The path of the process for which the task port is being retrieved * LINK: Both the source and destination path * SETATTRLIST: The file for which the attributes are being set * SETEXTATTR: The file for which the extended attributes are being set * SETFLAGS: The file for which flags are being set * SETMODE: The file for which the mode is being set * SETOWNER: The file for which the owner is being set * WRITE: The file being written to * READLINK: The symbolic link being resolved * TRUNCATE: The file being truncated * CHDIR: The new working directory * GETATTRLIST: The file for which the attribute list is being retrieved * STAT: The file for which the stat is being retrieved * ACCESS: The file for which access is being tested * CHROOT: The file which will become the new root * UTIMES: The file for which times are being set * CLONE: Both the source file and target path * FCNTL: The file under file control * GETEXTATTR The file for which extended attributes are being retrieved * LISTEXTATTR The file for which extended attributes are being listed * READDIR The directory for whose contents will be read * DELETEEXTATTR The file for which extended attribues will be deleted * DUP: The file being duplicated * UIPC_BIND: The path to the unix socket that will be created * UIPC_CONNECT: The file that the unix socket being connected is bound to * EXCHANGEDATA: The path of both file1 and file2 * SETACL: The file for which ACLs are being set * PROC_CHECK: The path of the process against which access is being checked * SEARCHFS: The path of the volume which will be searched * PROC_SUSPEND_RESUME: The path of the process being suspended or resumed * GET_TASK_NAME: The path of the process for which the task name port will be retrieved * TRACE: The path of the process that will be attached to * REMOTE_THREAD_CREATE: The path of the process in which the new thread is created * GET_TASK_READ: The path of the process for which the task read port will be retrieved * GET_TASK_INSPECT: The path of the process for which the task inspect port will be retrieved * COPYFILE: The path to the source file and the path to either the new file to be created or the existing file to be overwritten */ So the behavior completely changed, you can no longer specify executables (via their binary path) from which you do NOT want any events Muting effectively became reactive, not proactive. Why this change is not documented with the deprecation? Why no alternative is suggested? why find this only because it broke my software tool behavior and performance? And last: For how long can I rely on the old, deprecated APIs, should I choose to revert my change instead of devising a whole new mechanism for muting un-interesting
8
0
173
Aug ’25