es_respond_flags_result does not work for system files on macOS Sonoma

Hi all,

I'm developing an app that can disallow read and/or write access to selected files. I'm doing this with es_respond_flags_result:

es_respond_result_t es_result;
if (blockingState) {
     // Don't allow any operations on path...
     es_result = es_respond_flags_result(client, msg, 0, false);
                
     // Deny writing to path...
     // es_respond_flags_result(client, msg, 0xffffffff & ~FWRITE, true);
                
     // Deny reading of path...
     // es_respond_flags_result(client, msg, 0xffffffff & ~FREAD, true);
     } else {
     // Allow everything else...
     es_result = es_respond_flags_result(client, msg, 0xffffffff, false);
}

While everything works correctly for files on the desktop, blocking fails for files in the "/System/Library/" path. Everything worked great under macOS Ventura. Now on macOS Sonoma it does not work anymore.

On macOS Sonoma I still get ES_RESPOND_RESULT_SUCCESS as the result from es_respond_flags_result but the files can still be read/written.

What has changed in macOS Sonoma? I cannot find anything about this in the change logs. Are more adjustments needed for macOS Sonoma?

Thanks for any advice!

nm196

Replies

Can you give as a specific example of the file you’re trying to block and the client that’s trying to read it?

Share and Enjoy

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

Hi Eskimo, thank you for your help.

An example file would be /System/Library/Frameworks/CoreMediaIO.framework/Versions/A/Resources/VDC.plugin/Contents/MacOS/VDC.

Following is my client code. I get the user's choice to block/unblock a file via xpc-connection from the frontend:


static NSArray *files = @[
    @"/System/Library/Frameworks/CoreMediaIO.framework/Versions/A/Resources/VDC.plugin/Contents/MacOS/VDC",
    ...
];


static dispatch_queue_t g_event_queue = NULL;

static void
init_dispatch_queue(void)
{
    dispatch_queue_attr_t queue_attrs = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, 0);
    g_event_queue = dispatch_queue_create("open_event_queue", queue_attrs);
}

#pragma mark Endpoint Security Event handlers


static void
handle_open_worker(es_client_t *client, const es_message_t *msg) {
    NSString *openFilePath = [NSString stringWithFormat: @"%s", msg->event.open.file->path.data];
    
    if ([files containsObject: openFilePath]) {
        //Block if blocking state is disbaled at Host application
        [[IPCConnectionService sharedService] getBlockingStateWithApplicationPath:[NSString stringWithUTF8String:msg->process->executable->path.data] completionHandler: ^(BOOL blockingState, NSString *source) {
    
            es_respond_result_t es_result;
            if (blockingState) {
                // Don't allow any operations on path...
                es_result = es_respond_flags_result(client, msg, 0, false);
                
                // Deny writing to path...
                // es_respond_flags_result(client, msg, 0xffffffff & ~FWRITE, true);
                
                // Deny reading of path...
                // es_respond_flags_result(client, msg, 0xffffffff & ~FREAD, true);
            } else {
                // Allow everything else...
                es_result = es_respond_flags_result(client, msg, 0xffffffff, false);
            }
            
            // Print result
            if (es_result != ES_RESPOND_RESULT_SUCCESS) {
                switch(es_result) {
                    case ES_RESPOND_RESULT_ERR_INVALID_ARGUMENT:
                        // "One or more invalid arguments were provided"
                        break;

                    case ES_RESPOND_RESULT_ERR_INTERNAL:
                        // "Communication with the ES subsystem failed"
                        break;
                        
                    case ES_RESPOND_RESULT_NOT_FOUND:
                        // "The message being responded to could not be found"
                        break;
                        
                    case ES_RESPOND_RESULT_ERR_DUPLICATE_RESPONSE:
                        // "The provided message has been responded to more than once"
                        break;
                        
                    case ES_RESPOND_RESULT_ERR_EVENT_TYPE:
                        // "Either an inappropriate response API was used for the event type (ensure using proper es_respond_auth_result or es_respond_flags_result function) or the event is notification only."
                        break;
                }
            }
            
            es_release_message(msg);
        }];
    } else {
        // Allow everything else...
        es_respond_flags_result(client, msg, 0xffffffff, true);
        es_release_message(msg);
    }
}

static void
handle_open(es_client_t *client, const es_message_t *msg)
{
    // Retains the given message, extending its lifetime until released.
    es_retain_message(msg);
    dispatch_async(g_event_queue, ^{
        handle_open_worker(client, msg);
        
        // Can't call es_release_message here because in
        // handle_open_worker() we are async with an completionHandler
        // es_release_message(msg);
    });
}

static void
handle_event(es_client_t *client, const es_message_t *msg) {
    switch (msg->event_type) {
        case ES_EVENT_TYPE_AUTH_OPEN:
            handle_open(client, msg);
            break;
        default:
            if (msg->action_type == ES_ACTION_TYPE_AUTH) {
                es_respond_auth_result(client, msg, ES_AUTH_RESULT_ALLOW, true);
            }
            break;
    }
}

int main(int argc, char *argv[])
{
    [[IPCConnectionService sharedService] startListner];
    
    init_dispatch_queue();
    
    // Create the client
    es_client_t* client = NULL;
    es_new_client_result_t newClientResult = es_new_client(&client, ^(es_client_t *c, const es_message_t *message) {
        handle_event(c, message);
    });
    
    if (newClientResult != ES_NEW_CLIENT_RESULT_SUCCESS) {
        switch(newClientResult) {
            case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED:
                // "Application requires 'com.apple.developer.endpoint-security.client' entitlement"
                break;

            case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
                // "Application lacks Transparency, Consent, and Control (TCC) approval from the user. This can be resolved by granting 'Full Disk Access' from the 'Security & Privacy' tab of System Preferences."
                break;

            case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED:
                // "Application needs to be run as root"
                break;

            default:
                // Unknown error
                break;
        }
        
        es_delete_client(client);
        return 1;
    }
    
    es_event_type_t events[] =  { ES_EVENT_TYPE_AUTH_OPEN };
    if (es_subscribe(client, events, sizeof(events) / sizeof(events[0])) != ES_RETURN_SUCCESS) {
        es_delete_client(client);
        return 1;
    }
    
    
    dispatch_main();
    
    return 0;
}

I appreciate your help!

Thank you, nm196

An example file would be …/VDC.plugin/Contents/MacOS/VDC.

OK, so that’s what’s confusing me. Earlier you wrote:

but the files can still be read/written.

but no one is trying to write this file, right?

So is this just about reads? Or is there a write failure case [1] as well?

Share and Enjoy

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

[1] Well, success case (-: Or, better, a failed-to-block case.

An example file would be …/VDC.plugin/Contents/MacOS/VDC.

Yes, in this case, it's about reads. Apps are still able to read the file, although no operations are allowed based on

es_result = es_respond_flags_result(client, msg, 0, false);

while es_result equals ES_RESPOND_RESULT_SUCCESS

Thanks, nm196

Thanks for the clarification.

I don’t have any solid answers for you here. I recommend that you file a bug about this. It would help if you attached a cut down test ES client that demos the issue, kinda like what you’ve posted above. And make sure to attach a sysdiagnose log taken shortly after reproducing it.

Please post your bug number, just for the record.

Personally, I’m in two minds about this. On the one hand, blocking read access to built-in system components seems like quite a strange thing to be doing. OTOH, I’m quite surprised we managed to break this somehow. Regardless, I think a bug report is your best path forward.

Share and Enjoy

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