The "secinit subsystem" (if we may call it that) has two parts:
-
/usr/libexec/secinitd
-- a system daemon that (according to itsman
page) "initializes the runtime security policies for processes". A more precise and accurate description is that it serves requests to configure and turn on the App Sandbox. -
/usr/lib/system/libsystem_secinit.dylib
-- a component of/usr/lib/libSystem.dylib
whose code runs automatically aslibSystem.dylib
is initialized, or in other words as every Mach binary starts up.
Part of libsystem_secinit.dylib
's initialization code
(_libsecinit_setup_secinitd_client()
) calls
xpc_copy_entitlements_for_pid()
to find out if the current process has
any entitlements. If it doesn't, or if the entitlements don't include
one of the following three keys set to "true", then nothing more
happens.
com.apple.security.app-sandbox
com.apple.security.app-sandbox.optional
com.apple.security.app-protection
If any of these keys is "true", that means the current process wants
to use the
App Sandbox.
This is a high level way to configure and enable Apple's sandbox,
using
entitlements.
In this case _libsecinit_setup_secinitd_client()
goes on to call
xpc_pipe_routine()
to send a message to "com.apple.secinitd" via XPC
(in other words to the secinitd
system daemon). The message
contains (among other things) the process's entitlements. Normally
secinitd
will respond, and its response will appear in
xpc_pipe_routine()
's 'reply' parameter. Among other things, this
reply will include a blob of Scheme byte code compiled from the
sandbox-specific parts of the current process's entitlements. Then
_libsecinit_setup_app_sandbox()
will call __mac_syscall()
with
'call' set to '0' and 'arg' pointing (among other things) to the blob
of Scheme byte code. This initializes and turns on Apple's sandbox.
(For some reason this doesn't happen on macOS 13 and above. I haven't
yet figured out why.)
On the server side, secinitd
runs as the currently logged in user
and sits waiting for requests from "secinitd clients". When a new
client process makes a connection, secinitd
calls
xpc_connection_set_event_handler()
to set an event handler for it,
and the handler gets called on the first message -- the one sent via
xpc_pipe_routine()
from the client. The handler checks if a
"container" has already been created for the client. If so, it sends
back the contents using xpc_connection_send_message()
.
Containers are stored in directories under ~/Library/Containers
,
each named for an application that uses the App Sandbox (for example
"com.apple.calculator"). If a directory doesn't exist, secinitd
needs to (re)generate it. Among other things this involves a call to
sandbox_compile_entitlements()
.
The hook library in Examples/secinit
gets
loaded into whichever application you're testing with, and also into
secinitd
(via HC_ADDKIDS
). In this kind of environment it's best
to configure it to redirect its output to a virtual serial port like
PySerialPortLogger,
here.
Multiple instances of secinitd
may already be running, each serving
a different purpose. So first you need to identify the one you'll use
in these tests -- the one for the domain of the user you're currently
logged in as.
The first step is to (re)start secinitd
for your domain. The command
will return the pid
of the new instance. Then you'll kill this
instance, so that running Calculator will cause it to be restarted yet
again with the hook library loaded.
% launchctl kickstart -p user/${UID}/com.apple.secinitd
service spawned with pid: [pid]
% kill -9 [pid]
Now run the Calculator app (or whichever app you're testing with):
HC_ADDKIDS=/usr/libexec/secinitd HC_INSERT_LIBRARY=/full/path/to/hook.dylib open /System/Applications/Calculator.app
To see sandbox_compile_entitlements()
in action, first delete the
secinitd
client's Containers directory. For example:
rm -rf ~/Library/Containers/com.apple.calculator
It's also possible to do all the above "automatically" via a shell script, as follows:
HC_ADDKIDS=/usr/libexec/secinitd HC_INSERT_LIBRARY=/full/path/to/hook.dylib ./runtest.sh
Also test with other applications that may or may not be secinitd
clients, to see what happens. Examples of applications that have
entitlements and use the App Sandbox are Calculator and Notes. Firefox
and Google Chrome have entitlements but don't use the App Sandbox --
instead they use Apple's sandbox at a lower level by calling
sandbox_init_with_parameters()
. Safari also used to do this, but now
uses the App Sandbox.
When you're done testing, quit all the applications containing the
hook library and restart secinitd
for your domain one more time by
doing the following. This latest instance of secinitd
will no longer
have the hook library loaded into it.
% launchctl kickstart -p user/${UID}/com.apple.secinitd
service spawned with pid: [pid]
% kill -9 [pid]