Checking (macOS) Mach Service Availability with Swift 27 Mar 2024
Testing for mach ports is useful if you have an app that talks to an XPC service,
agent or daemon and you’d like to check for availability of the endpoint before
pulling out an entire NSXPCConnection
. This would, for example, allow you
to show a progress indicator while the app waits to pull data over XPC into the UI.
Looking for a way to check if a mach service is reachable (or rather, launchd knows about it), you’ll find lots of C or Objective-C code.
Swift lets you call C functions but the trouble with Swift is that “complex macros”
are not available but task_get_bootstrap_port
and mach_task_self()
are defined as such:
1
2
#define task_get_bootstrap_port(task, port) \
(task_get_special_port((task), TASK_BOOTSTRAP_PORT, (port)))
1
#define mach_task_self() mach_task_self_
Both are “complex macros” and therefore not callable from Swift. Yikes.
Option 1
Write your own C helpers. Beware that they cannot have the name of the originals because there already are macros with the same name. You’ll also need a bridging header (which Xcode takes care of for you when you add a C file to your project).
1
2
3
4
5
6
7
8
// mach_compat.h
#include <mach/mach.h>
mach_port_t
mach_task_current(void);
kern_return_t
task_get_bs_port(task_inspect_t task, mach_port_t *special_port);
1
2
3
4
5
6
7
8
9
10
11
12
13
// mach_compat.c
#include "mach_compat.h"
mach_port_t
mach_task_current() {
return mach_task_self();
}
kern_return_t
task_get_bs_port(task_inspect_t task, mach_port_t *special_port) {
return task_get_special_port(task, TASK_BOOTSTRAP_PORT, special_port);
}
And use them like this:
1
2
mach_port_t service_port = MACH_PORT_NULL;
task_get_bs_port(mach_task_current(), &service_port)
Option 2
Simply do what the macros do.
The slightly trickier part was mach_task_self()
. XNU’s version gives you
mach_task_self_
which mach initializes with the return value of task_self_trap()
.
Using mach_task_self_
directly is possible but ugly.
So I came up with the following:
1
2
3
4
5
6
7
func machServiceAvailable(_ endpoint: String) -> Bool {
var srv_port = mach_port_t(MACH_PORT_NULL)
var bs_port = mach_port_t(MACH_PORT_NULL)
task_get_special_port(task_self_trap(), TASK_BOOTSTRAP_PORT, &bs_port)
let result = bootstrap_look_up(bs_port, endpoint, &srv_port)
return KERN_SUCCESS == result
}
Option 3
Do none of the above.
XNU actually stores the current tasks bootstrap port in bootstrap_port
:
1
2
3
4
5
func machServiceAvailable(_ endpoint: String) -> Bool {
var srv_port = mach_port_t(MACH_PORT_NULL)
let result = bootstrap_look_up(bootstrap_port, endpoint, &srv_port)
return KERN_SUCCESS == result
}
See the PoC for CVE-2018-4280 for a neat implementaion of almost the same.
For a deeper understanding of those things I recommend Technical Note 2083.