Apple replaced the very simple (i.e. function fits in a cache line; inputs fit in a single dword) BSD user/group/other filesystem privileges system, with a Lisp interpreter (or maybe compiler? not sure) executing some security DSL[1][2].
[1]
https://wiki.mozilla.org/Sandbox/OS_X_Rule_Set
[2]
https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sand...
This capabilities-ruleset interpreter is what Apple uses the term "Gatekeeper" to refer to, mostly. It had already been put in charge of authorizing most Cocoa-land system interactions as of 10.12. But the capabilities-ruleset interpreter wasn't in the code-path for any
BSD-land code until 10.15.
A capabilities-ruleset "program" for this interpreter
can be very simple (and thus quick to execute), or arbitrarily complex. In terms of how complex a ruleset
can get—i.e. what the interpreter's runtime allows it to take into consideration in a single grant evaluation—it knows about all the filesystem bitflags BSD used to,
plus Gatekeeper-level grants (e.g. the things you do in Preferences; the "com.apple.quarantine" xattr),
plus external system-level capabilities "hotfixes" (i.e. the same sort of "rewrite the deployed code after the fact" fixes that GPU makers deploy to make games run better, but for security instead of performance),
plus some stuff (that I don't honestly know too much about) that can require it to contact Apple's servers during the ruleset execution.
I'm not sure whether it's the implementation (an in-kernel VM doesn't imply slowness; see eBPF) or the particular checks that need to be done, but either way, it adds up to a bit of synchronous slowness per call.
The real killer that makes you
notice the problem, though, is how the naive/classic BSD approach to using syscalls to walk a filesystem
interacts with the Mach message-passing-IPC step in-between the client and the sandboxd server.
The security framework was pretty obviously structured to expect that applications would only send it O(1) capability-grant requests, since the idiomatic thing to do when writing a macOS Cocoa-userland application, if you want to work with a directory's contents, is to get a capability on a whole directory-tree from a folder-picker, and then use that capability to interact with the files.
Under such an approach, the sandbox system would never be asked too many questions at a time, and so implementing it as a Mach IPC server that has a message inbox that processes send to, makes sense.
Under an approach where you've got BSD processes that spam individual fopen(2)-triggered capability requests to sandboxd, though, that single message inbox becomes a bottleneck. Especially a concurrency bottleneck. (It's very much like the kind of process-inbox bottlenecks you see in Erlang, that are solved by using process pools or ETS tables.)
Either Apple should have rethought the IPC architecture of sandboxing in 10.15, but skipped on this; or they should have made their BSD libc transparently handle "push down" of capabilities to descendent requests, but skipped on that.