For a long time, we have been controlling access to files and applications at our computers using Discretionary Access Control (DAC). Usually, this approach looks like a combination of a user with restricted privileges having access to a number of strictly defined resources (files, applications, etc.) and an administrator with full access to all system resources.
Generally, this approach seems to be mostly sufficient, and — for users — sometimes even excessive, which is confirmed by a great number of users working with administrator privileges at their computers.
However, a lot of situations occur where DAC is insufficient not only for corporate but also for home users. A typical situation for corporate users is deployment of DLP (Data Loss Prevention) solution where users, even with administrator privileges, should not be permitted to manage it. Another example of insufficiency of DAC for both corporate and home users is anti-virus systems and firewalls, e.g. a well known Trojan — Flashback.C OS X — that simply removed the built-in Apple anti-virus from startup which was mainly possible due to default administrator priviliges of a MAC OS X user (i.e. sudoer by default).
Actually, the idea of this article emerged from a communication with a DLP solution developer. This company wanted to protect their product from removal by users with administrator privileges on Mac OS X computers.
Happily, this task has an easy solution in MAC OS X as it has built-in tools for deployment of a MAC (Mandatory Access Control) approach. Generally, MAC approach assumes assigning security attributes to objects with similarly attributed information and granting access to these objects for requests from users with sufficient permissions. So in case of MAC, no matter whether a regular user or an administrator attempts to access the resource, there is a fine option to restrict such attempt and terminate it if necessary.
There is a variety of MAC implementations for different platforms, e.g. SELinux and AppArmor for Linux, Solaris Trusted Extensions for Solaris, and TrustedBSD for BSD or Mac OS X. In this article, we are talking only about TrustedBSD for Mac OS X.
TrustedBSD framework for Mac OS X was created long ago, and support for most interfaces was introduced since Mac OS X 10.5. The framework itself is actively employed by Apple both in Mac OS X and iOS to ‘sandbox’ (i.e. to isolate) applications. Unfortunately, the existing sandbox does not protect an application from a user as it is intended to protect users from applications, and is not suitable for addressing the above problem. However, it doesn’t prevent us from using TrustedBSD if we write an extension for it.
INFO: Apple documentation for TrustedBSD is rather poor, while FreeBSD Architecture Handbook is much more informative on TrustedBSD and is more helpful in searching the information. Generally, the TrustedBSD interfaces for Mac OS X and FreeBSD are similar, but their names are slightly different, e.g. mpo_vnode_check_open callback function (which will be used in the example below) in case of FreeBSD is named mpo_check_vnode_open. This hinders your work with the framework to some extent as you have to guess the interface names, but it enables you to get a more detailed picture of the situation.
With TrustedBSD, you can filter nearly all events on application work. For our purpose, the filtered events can be divided into several groups:
- Control over launching the processes and switching the process into debug mode
- Filtering the network activity
- Filtering the work with file system
- Filtering the access to I/O Kit devices
- Filtering the work with pipes
- Filtering the access to Mach ports
- Filtering the access to synchronization primitives
- Filtering some critical system functions, such as mprotect, setaudit, setlcid, wait
- Filtering the access to System V message queue
Though not comprehensive, this list covers most TrustedBSD functions and gives an idea of the framework capabilities.
Now we can go to our practical part and implement a TrustedBSD extension with access control to certain files. Creating this extension does not require any special knowledge or skills; you can just create a template driver with Xcode (TrustedBSD extensions are just kext modules) and implement a couple of handlers.
INFO: This article does not cover issues related to creating, loading, and unloading drivers for Mac OS X or providing interaction between the driver and user space. If these issues are new for you, read Kernel Extension Programming Topics — a great introduction to driver development for Mac OS X available at developers.apple.com website.
The work with TrustedBSD framework is very simple. The point is that only two functions are available to the developer: mac_policy_register and mac_policy_unregister which are responsible for registering and deregistering the policies, respectively.
You should fill two following structures for registering the policies: mac_policy_conf and mac_policy_ops. The mac_policy_ops structure contains the pointers to user callback functions used for handling occuring events. The mac_policy_conf structure contains the policy configuration information: policy name, pointer to the mac_policy_conf structure, flags for unloading the policy, etc.
The most convenient way is to initialize the policies from driver entry point and deregister them upon completion of the driver’s work. So, the initialization code in this particular example will be quite simple.
static struct mac_policy_ops mac_ops ={
.mpo_policy_initbsd = mac_policy_initbsd, .mpo_vnode_check_open = mac_policy_open, .mpo_vnode_check_unlink = mac_policy_unlink, };
static struct mac_policy_conf mac_policy_conf =
{
.mpc_name = "protect_demo",
.mpc_fullname = "Protect demo!",
.mpc_labelnames = NULL,
.mpc_labelname_count = 0,
.mpc_ops = &mac_ops,
.mpc_loadtime_flags = MPC_LOADTIME_FLAG_UNLOADOK, .mpc_field_off = NULL, .mpc_runtime_flags = 0
};
static mac_policy_handle_t mac_handle; int PolicyKext_start(void * d)
{
return mac_policy_register(&mac_policy_conf, &mac_handle, d);
}
Deregistering the policy is even simplier
int PolicyKext _stop()
{
return mac_policy_unregister(mac_handle); }
Please give more attention to the mac_policy_initbsd callback. This function is called after policy initilization and load. In our case, it is just a stub. If you need to put additional logic into mac_policy_initbsd, remember that when loading the policies at the OS startup, this function will be called before kernel_task process starting (the very first process; its PID is always 0). Calling “wait” function from mac_policy_initbsd is forbidden and you should do other calls more carefully.
We should also discuss such important question as unloading the policies. While development, the policies should certainly be unloadable, and after that, in final product version – not unloadable (with few exceptions). To make the policy unloadable, upon the policy registration you should set MPC_LOADTIME_FLAG_UNLOADOK value in the mpc_loadtime_flags field of the mac_policy_conf structure. To forbid unloading the policy, set this field to 0. Other flag values are also available, for more information please refer to the documentation.
Now, we’re in the home stretch and have to create the following handlers for events associated with file opening and deletion.
- typedef int mpo_vnode_check_open_t(kauth_cred_t cred, struct vnode *vp, struct label *label, int acc_mode );
- typedef int mpo_vnode_check_unlink_t(kauth_cred_t cred, struct vnode *dvp, struct label *dlabel, struct vnode *vp, struct label *label, struct componentname *cnp);
In both cases, the most interesting thing is vnode – vp structure with information on the object to which access attempt is made. You can as well get a lot of useful information from kauth_cred_t structure, e.g. effective and real user IDs and user groups IDs.
For final step, i.e. getting information on the current process name, PID, and accessed file name, you will need the following functions:
- proc_selfname – getting the name of the process attempting to access the object.
- proc_selfpid – getting the PID of the process attempting to access the object.
- vnode_getname – getting the name of the object to which an access attempt is made.
It’s time to make a sample implementation of the handlers described above:
static int is_file_accessible(struct vnode *vp)
{
const char *vname = NULL;
char cbuf[MAXCOMLEN+1];
int retvalue = 0;
if (vp == NULL) { return (retvalue);
}
vname = vnode_getname(vp);
if(vname) {
if(strcasecmp(vname, "com.apple.xprotectupdater.plist") == 0)
{
proc_selfname(cbuf, sizeof(cbuf));
if (strcasecmp(cbuf,"XProtectUpdater"))
{
retvalue = EPERM; }
vnode_putname(vname); }
}
return(retvalue);
}
static int mac_policy_open(
kauth_cred_t cred,
struct vnode *vp, struct label *label,
int acc_mode)
{
return is_file_accessible(vp);
}
static int mac_policy_unlink(
kauth_cred_t cred,
struct vnode *dvp, struct label *dlabel, struct vnode *vp,
struct label *label, struct componentname *cnp)
{
return is_file_accessible(vp);
}
The above examples show that implementation of TrustedBSD extension is neither a complex nor tricky process, while such extension ensures an easy control of almost all aspects of process launch on the computer.
I have been working in software development more than 12 years. My main working areas are low level and networking applications, crosspaltform software development in C and C++; also I’m programming in Objective-C and Java for past few years. A great while (about 6 years) my main working platform was Microsoft Windows, but at the moment Windows is completely displaced by UNIX-like operation systems, mostly Mac OS X and Linux. I’m also keen on programming in such exotic languages Scala and Rust.
Please visit http://sysdev.me/about/ for more details.