One of the BSD legacy security mechanisms included with OS X is OpenBSM. This is an audit mechanism. In contrast, TrustedBSD (also included with OS X) is a mandatory access control mechanism which can block system calls based on some policy. Audit doesn’t block anything, it only reports what happened. Also unlike TrustedBSD, the audit implementation is quite a full-stack offering, including user space tools to control the kernel audit system and even to parse audit records.
What does this system look like on a high-level, and how does it perform? Read on…
Say hello to audit
OS X actually has audit enabled and running by default, at least on 10.10. To see it in action, run `sudo cat /dev/auditpipe | praudit`. You may not see very much happening (we’ll see why in the next section on Configuration), but as an example here’s a snippet of what you might see:
header,100,11,mac_syscall(2),0,Wed Aug 3 14:32:54 2016, + 529 msec text,arg: Quarantine argument,3,0xb4,call subject,user1,user1,staff,user1,staff,380,100006,50331650,0.0.0.0 return,success,0 trailer,100 header,86,11,fork(2),0,Wed Aug 3 14:32:54 2016, + 535 msec argument,0,0xd49,child PID subject,user1,user1,staff,user1,staff,2962,100006,50331650,0.0.0.0 return,success,3401 trailer,86 header,97,11,mac_syscall(2),0,Wed Aug 3 14:32:54 2016, + 545 msec text,arg: Sandbox argument,3,0x2,call subject,-1,root,wheel,root,wheel,1,100000,0,0.0.0.0 return,success,0 trailer,97
You may also find event traces piling up in /var/audit/.
On OS X, audit is configured with four conf files in /etc/security, and it can be a pain to set up. The rough idea is that the audit_control file contains a “flag” identifier that specifies what set of events to audit, and individual event probes can be enabled with that identifier in the audit_event file. Although audit is enabled by default on OS X, the set of events enabled by default are quite minimal – and as we shall soon see, that may be a very good thing.
Before discussing the security and performance implications of audit, here’s a quick look at it’s architecture on OS X. Most of the audit mechanism is baked into the kernel. A user space daemon (auditd i.e. the com.apple.auditd LaunchDaemon) is responsible for reading configuration files and setting up the kernel through auditon() and other syscalls. A userspace tool, /usr/sbin/audit, sends control signals to auditd, to do things like start (audit -i) and stop (audit -t) auditing.
On OS X, audit has a reputation for being expensive in terms of performance. To quantify this, I ran a simple benchmark of running an xcodebuild for a substantial proprietary codebase that I work with. I ran 10 samples against varying audit setups:
- audit_off: audit is disabled.
- audit_5: audit is enabled with 5 probes: exit, fork, vfork, mac_syscall, and posix_spawn – basically process stuff.
- audit_25: audit is enabled with 25 probes: all the above plus a bunch more mostly to do with file system stuff.
- audit_50: audit is enabled with 50 probes: all the above plus a bunch more mostly to do with sockets stuff. Note that this will include IPC mechanisms, not just external network stuff as most people tend to think of when they think of sockets.
One can see that performance has a tendency to degrade as more probes are enabled. But more important than the count of enabled probes is what probes are enabled.
Case in point: in one instance I enabled auditing of fcntl. As a consequence of this, my machine slowed down so much that the GUI boot was broken, effectively rendering my system completely broken. I had to boot into single-user mode to undo the damage just to be able to boot again.
The performance implications here are not just an issue of user quality of life, the impact on granular performance can be so substantial that it impacts the availability of the system. Furthermore, it is something exploitable: should an adversary have even just user-level access to a system and has awareness of what audit probes are enabled, s/he could simply spam the relevant syscalls (without actually doing anything meaningful), specifically just to flood the audit system in an attempt to render the system unavailable.
To mitigate this, one may elect to enable audit with just a very limited number of event probes. But, would that be an effective security solution?
Given the performance issues, the question then becomes, can audit provide assurance or detection capabilities with just a limited number of event probes? Most likely the answer is no.
Consider a simple case of trying to degrade the security of a node by disabling the ocspd LaunchDaemon so that the certificate revocation list feature is compromised, thus softening the endpoints security posture. To achieve this the attacker may want to break the ocspd LaunchDaemon plist so that the service is suppressed on the next boot. Assuming an attacker can somehow obtain privilege to modify the contents of the System directory, what might the attacker do to the ocspd plist?
- Delete the plist (unlink() syscall)
- Modify the plist (open() syscall with write access)
- Truncate the plist (truncate() syscall)
- Create a new file overwriting the plist (creat() syscall)
- Rename the plist to something in another folder, effectively moving it out of the way (rename() syscall)
- Rename some other file to the plist file, effectively overwriting it (rename() syscall again, but this time watching for the other argument
That’s 5 different system calls that need to be probed just to detect one type of attack. It seems evident that in order to provide effective visibility, a lot of event probes must be enabled.
In my opinion, as a standalone security mechanism, audit is not super effective. For instance, to cover all persistence vectors, a large number of event probes must be enabled. Given the potentially severe impact on system performance, audit could wind up being a security problem for environments that consider loss of availability a security issue (as NIST, for instance, does).
That said, it may be the case that some parts of the audit subsystem can be effectively leveraged together with other mechanisms to provide a more effective assurance and detection capability.
But whatever you do, don’t audit fcntl() unless you remember the steps to boot into single-user mode to unbreak things!