Subscribe to receive all latest blog updates

Mac OS X 10.4 saw the introduction of a new kernel subsystem that proves itself very useful when it comes to managing authorization from the kernel. This subsystem is called Kernel Authorization, or Kauth. It can be used to modify any authorization decisions, which proves very useful for creating anti-viruses, and other security software, as well as for general system access when needed. In this article we will show you two examples of using Kauth subsystem – blocking access to certain files and directories, and listening for mounting and unmounting of specific devices. Both examples will involve creating kernel extension that will use Kauth in order to listen for specific events.

Written by:
Artur Bulakaiev,
Software Developer of Apriorit

Blocking access to files and catalogues – typical steps

In order to block access to files and catalogues in Mac OS X, we need to take the following steps:

  1. First, we will need a kernel extension, or, simply put, a driver. We will use .kext file that will run in the OS and will do most of the work.
  2. Driver will work with Kauth, or Kernel Authorization API. Detailed description of this API can be found here
  3. Driver should listen to alerts about certain events. In our case, since we want to allow or deny access to certain events, it will be Vnode scope.
  4. When the listener is all set, kernel extension will receive a notification about each such event. We need to define conditions for denying access and return one of the three signals from the listener: allow, deny, or leave decision to other listeners. Important: voting should be unilateral. If even a single listener denies access to resource, it will stay prohibited regardless of the number of permissions granted.

Creating kernel extension

Now, let’s look at the each step in details.

  1. First, let’s create a driver in xCode. Choose a name and location of the project.
  2. This driver contains two important files: <%projectname%>.c and info.plist. Source code file currently contains only functions for starting and stopping the driver. Info.plist contains Bundle Id of the driver. It is important to make sure that Kernel Mode section of the Build Settings contains the correct Bundle Id, as well as correct start and stop method.

Creating kernel extention in Mac OS X

  1. Add links to the libraries that you will need in Info.plist inside OSBundleLibraries. These libraries should include: key com.apple.kpi.libkern, type String, value 8.0.0; key com.apple.kpi.bsd with the same type and value. Value reflects minimal version of the library, required for the driver to work. Version numbering conventions are the same as Dawrin core numbering, i.e., the first number is the number of minor OS X version plus 4, thus 8 means that minimal system requirements are OS X 10.4, where Kauth API was first introduced; second number – the number of OS X revision, e.g. for OS X 10.8.2 version number will be 12.2. You can find more examples here.

Writing a listener for blocking access to files and directories

Now, the groundwork for our kernel extension is ready. All that’s left to do is implementing a listener.

  1. Now we will need headers sys/kauth.h and sys/vnode.h from Kernel.Framework (which in actuality is not a framework in Mac OS X sense, but rather a set of includes collected in the form of a framework, thus it cannot be linked to).
  2. In order to listen for notifications in Vnode scope, we need to call the method kauth_listen_scope, its signature is stated below. Give it the name of the scope we want to listen to, which in our case it is defined by the constant KAUTH_SCOPE_VNODE, as well as a pointer to specific function that will receive our notification, and any additional information, if needed. Everything that will be returned by the method needs to be saved for future use.
kauth_listener_t kauth_listen_scope(const char *_identifier, kauth_scope_callback_t _callback, void *_idata);

The type of our callback should look like this:

typedef int (* kauth_scope_callback_t)(kauth_cred_t _credential, void *_idata, kauth_action_t _action, uintptr_t
             _arg0, uintptr_t _arg1, uintptr_t _arg2, uintptr_t _arg3);

You can check the meaning of each parameter here in Listing 1.

  1. When our vnode scope listener is registered, we start to receive notifications for our function. Next we need to apply our rules to each event and return KAUTH_RESULTS_ALLOW, KAUTH_RESULT_DEFER, or KAUTH_RESULT_DENY in order to allow, deny or defer access accordingly.
  2. We need to stop listening Vnode scope at the exit point – stop method of the driver. In order to do this, we need to call keauth_unlisten_scope and give it saved object from step 5 - kauth_listener_type.

Next, we can launch and test our driver. Example of a passive listener from Apple can be found at KauthORama.

Useful advice:

  1. Use callback from Vnode very carefully. Do not log operations from very wide areas of file systems, as it can slow the system down considerably, do not deny access to wide areas of file system (denying access to the critical system directories or files can lead to system crash). In the beginning check, whether the action is happening with the object of your interest, and only then do something. In order to get the path to the object, you can use int vn_getpath(struct vnode *vp, char *pathbuf, int *len) function.
  2. To avoid hard-coding scope, you can pass it to the driver from the outside. For example, it can be done via sysctl (as shown in the KauthORama example from Apple), through socket, or through mach.

Listen for mount and unmount of a device from kernel code

For this second example, let’s return to the point where we have a kernel extension ready and create another listener that tracks mounting and unmounting of devices from kernel code.

The idea is fairly simple:

  1. Decide on the events that we are interested in, in this case mount and unmount of a device.
  2. Register mac_policy event listener
  3. Receive callbacks from mac_policy, process event data, allow or deny execution of this event.

Additionally it is worth noting that the using mac_policy for this purpose only makes sense, if it is from a kernel code, because regular applications can use much more convenient  DiskArbitrationFramework (here is an example of such project). Although mac_policy have much more capabilities, all of which can be explored in a header file.

  1. Let’s add com.apple.kpi.dsep key with the version 9.0.0 into info.plist of OSBundleLibraries. Add the necessary headers: security/mac_policy.h, sys/mount.h, and sys/param.h. Create an instance of mac_policy_ops structure, where the necessary callbacks should be added:
static struct mac_policy_ops gMacPolicyOps;
...
gMacPolicyOps.mpo_mount_check_mount = callback_mount_check_mount_t;
gMacPolicyOps.mpo_mount_check_umount = callback_mount_check_umount_t;

These callbacks should have the following structure:

typedef int mpo_mount_check_mount_t( kauth_cred_t cred, struct vnode *vp, struct label *vlabel, struct componentname *cnp,const char *vfc_name );
typedef int mpo_mount_check_umount_t( kauth_cred_t cred, struct mount *mp, struct label *mlabel );

You can read about parameters of these callbacks in full detail here or in the mac_policy.h source code.

  1. Register mac_policy event listener. For this we need to create an instance of another structure mac_policy_conf:
static struct mac_policy_conf mpc =
   { 
       .mpc_name = "com.my-test-driver",
       .mpc_fullname = "my policy",
       .mpc_labelnames = NULL,
       .mpc_labelname_count = 0,
       .mpc_ops = &gMacPolicyOps,
       .mpc_loadtime_flags = MPC_LOADTIME_FLAG_UNLOADOK,
       .mpc_field_off = NULL,
       .mpc_runtime_flags = 0,
       .mpc_list = NULL,
       .mpc_data = NULL
   };

Here mpc_name needs to correspond to Bundle Id of your driver; mpc_ops needs to contain an instance of mac_policy_ops structure that we filled out earlier; mpc_loadtime_flags is inserted into MPC_LOADTIME_FLAG_UNLOADOK, which means that this listener can be unregistered and deleted without any problems; mpc_fullname can contain the name of your policy, an everything else should be initialized with zeroes.

Registering looks like this:

mac_policy_register(&mpc, &gMacPolicyHandle, NULL);

After this we start to receive notifications in the defined method.

  1. How to get the path via which devices was mounted or unmounted?

For mpo_mount_check_mount_t let’s use vn_getpath function:

int pathLen = MAXPATHLEN;
char path[pathLen];
vn_getpath(vp, path, &pathLen);
printf("You have mount device %s", path);
return 0;

Be mindful, that vn_getpath can return 0 if the path was received correctly or an error number in any other case. Printing unfilled path from kernel code can lead to the need for a restart. We can return 0 from this method by allowing operation or corresponding mistake from sys/errno.h.

For mpo_mount_check_unmount_t receiving process will be different. We do not receive vnode in parameters, this is why we use vfs_statfs function and give it mount point (struct mount *mp):

struct vfsstatfs* stats;
stats = vfs_statfs(mp);
printf("You have unmount device %s", stats->f_mntonname);
return 0;

Returned value can be the same as in previous callback.

Important: Generally, com.apple.kpi.dsep is not a supported Apple Kernel Programming Interface, and the themselves are talking about this. However, de-facto, header files mac_policy and their support persisting since 2008 and is present in every OS X version since 10.5.

Conclusion

Kernel development is very difficult and very specific area of expertise that requires a specialists with particular knowledge and experience. Nowadays, it can be very hard to find good engineers for your kernel development project, to the point where most companies go for third party kernel developers. As a kernel developers ourselves with extensive experience in programming for Mac OS X, we at Apriorit wish to facilitate the sharing of knowledge and expertise in this area and hope that this article will prove useful to anyone brave enough to venture into kernel development.