Section 11.10. The Kauth Authorization Subsystem

11.10. The Kauth Authorization Subsystem

Beginning with Mac OS X 10.4, the kernel uses a flexible and extensible subsystem called kauth for handling file-system-related authorizations. The following are some of kauth's noteworthy features.

  • It encapsulates the evaluation of access control lists (ACLs). When the VFS layer determines that a vnode has an associated ACL, it calls kauth to determine whether the given credential has the requested rights for the vnode in light of the given ACL.

  • It is not limited to file system authorizations. It can be used to implement authorization decision makers for arbitrary types of operations in the kernel. The kauth kernel programming interface (KPI) allows third-party programmers to load such decision makers for existing or new scenarios, as we will shortly see.

  • It can be used as a notification mechanism for informing interested parties about file system operations. The prime example of an application that would use such functionality is that of an antivirus file scanner. Moreover, kauth can be used to implement a fine-grained file system activity monitor for reporting vnode-level operations. We will write such a monitor later in Section 11.10.3.

11.10.1. Kauth Concepts

Figure 1130 shows the fundamental abstractions in kauth. A scope is an authorization domainan area within which an action performed by an actor is to be authorized. An actor is identified by an associated credential. Code executing in the kernel makes an authorization request to kauth, providing it a scope, a credential, an action, and scope- or action-specific arguments. Each scope has a default listener callback and possibly other registered listeners. Kauth calls each listener in the scope while authorizing a request. A listener examines the available information and makes an authorization decision.

Figure 1130. An overview of the kauth mechanism Scopes and Actions

A kauth scope groups together a set of actions. For example, operations pertaining to the VFS layer are in one scope, whereas process-related operations are in another scope. A scope is identified by a string, which is conventionally in reverse DNS format. The kernel has several built-in scopes, each with a set of predefined actions, such as those listed here.

  • KAUTH_SCOPE_GENERIC authorizes whether the actor has superuser privileges.

  • KAUTH_SCOPE_PROCESS authorizes whether the current process can signal or trace another process.

  • KAUTH_SCOPE_VNODE authorizes operations on vnodes.

  • KAUTH_SCOPE_FILEOP is a special scope that is used not for authorization but as a notification mechanism. A listener in this scope is called when certain file system operations occur, but the "decision" returned by the listener is ignored.

Depending on the scope, an action may represent an individual operation, or it may be a bit-field representing multiple operations. The vnode scope uses bit-fieldsfor example, a single authorization request may include both read and write operations.

Figure 1131 shows the kernel's built-in scopes along with their reverse DNS names, actions, and default listener callbacks.

Figure 1131. Kauth scopes and actions

Figure 1131 also shows a third-party scope. Although it may not normally be necessary, new scopes can be registered with kauth through kauth_register_scope().

kauth_scope_t kauth_register_scope(     const char             *identifier, // the scope identifier string     kauth_scope_callback_t  callback,   // default listener callback for scope     void                   *data);      // cookie passed to the callback void kauth_deregister_scope(kauth_scope_t scope); Listeners and Authorization Decisions

A listener can return one of three values as its authorization decision: KAUTH_RESULT_ALLOW, KAUTH_RESULT_DENY, or KAUTH_RESULT_DEFER. The latter means that the listener neither allows nor denies the requestit is simply deferring the decision to other listeners. The following points regarding listeners must be noted.

  • The default listener for a scope is established when the scope is registered. It is possible for a scope to have no default listenerKAUTH_SCOPE_FILEOP is an example of such a scope. When this is the case, kauth proceeds as if there were a default listener that always returned KAUTH_RESULT_DEFER as its decision.

  • Kauth always calls all listeners for each request. The default listener is always the first to be called.

  • Only the default listener can allow a request. If any subsequent listeners return KAUTH_RESULT_ALLOW, kauth ignores their return values. In other words, any listener other than the default listener can only be more restrictive.

  • If there is no default listener for a scope, and all other listenersif anyhave returned KAUTH_RESULT_DEFER, the request is denied.

Additional listeners can be installed for a given scope through kauth_listen_scope(). Note that it is possible to install a listener for a scope that is not registered. The kernel maintains a list of such dangling listeners. Every time a new scope is registered, the kernel checks this list. If any listeners are found waiting for the scope being registered, they are moved to the new scope. Conversely, when a scope is deregistered, all its installed listeners are moved to the list of dangling listeners.

typedef int (* kauth_scope_callback_t)(     kauth_cred_t    _credential, // the actor's credentials     void           *_idata,      // cookie     kauth_action_t  _action,     // requested action     uintptr_t       _arg0,       // scope-/action-specific argument     uintptr_t       _arg1,       // scope-/action-specific argument     uintptr_t       _arg2,       // scope-/action-specific argument     uintptr_t       _arg3);      // scope-/action-specific argument kauth_listener_t kauth_listen_scope(     const char             *identifier, // the scope identifier string     kauth_scope_callback_t  callback,   // callback function for the listener     void                   *idata);     // cookie passed to the callback void kauth_unlisten_scope(kauth_listener_t listener);

11.10.2. Implementation

As Figure 1132 shows, the kernel maintains a list (a tail queue, specifically) of registered scopes, with each scope being represented by a kauth_scope structure [bsd/kern/kern_authorization.c]. Each scope includes a pointer to the default listener callback function and a list of other listenersif anyinstalled in the scope.

Figure 1132. An overview of the scope and listener data structures in the kauth mechanism

The kauth subsystem is initialized when bsd_init() [bsd/kern/bsd_init.c] calls kauth_init() [bsd/kern/kern_authorization.c]before the first BSD process is created. kauth_init() performs the following actions.

  • It initializes the lists of scopes and dangling listeners.

  • It initializes synchronization data structures.

  • It calls kauth_cred_init() [bsd/kern/kern_credentials.c] to allocate the credential hash table and associated data structures. The kauth implementation provides several kernel functions for working with credentials.

  • It calls kauth_identity_init() [bsd/kern/kern_credential.c] and kauth_groups_init() to initialize identity and group membership data structures, respectively.

  • It calls kauth_scope_init() [bsd/kern/kern_credential.c] to register the process, generic, and file operation scopes. The vnode scope is initialized during initialization of the VFS layer, when bsd_init() calls vfsinit() [bsd/vfs/vfs_init.c].

  • It calls kauth_resolver_init() [bsd/kern/kern_credential.c] to initialize work queues, mutex, and sequence number[18] for the external resolver mechanism that the kernel uses to communicate with the user-space group membership resolution daemon (memberd).

    [18] The sequence number is initialized to 31337.

Group Membership Resolution Daemon

A user ID in Mac OS X can be a member of an arbitrary number of groups. Moreover, groups can be nested. In particular, it is not necessary that the list of all groups corresponding to a user ID be available in the kernel. Therefore, the kernel uses memberd as an external (out-of-kernel) identity resolver.

memberd invokes the special system call identitysvc() to register itself as the external resolver. Thereafter, it serves the kernel by using identitysvc() to fetch work, resolving identities using Open Directory calls, and informing the kernel of completed workagain, through identitysvc(). User programs can also access memberd's services through Membership API calls.

The majority of authorization activity in the kernel occurs in the vnode scope. As Figure 1133 shows, the VFS layer, specific file systems, and system calls such as execve() and mmap() call the kauth subsystem through the vnode_authorize() function. The default listener for the vnode scopevnode_authorize_callback()performs numerous checks, depending on factors such as the type of mount, the identity of the actor, and the presence of an ACL. The following are examples of checks performed by vnode_authorize_callback().

  • It checks whether the file system is mounted in a manner that warrants denial of the request without further testsfor example, a read-only file system (MNT_READONLY) cannot be written to, and files residing on a "noexec" file system (MNT_NOEXEC) cannot be executed.

  • It calls vnode_authorize_opaque() if the file system is mounted with the indication that authorization decisions are not to be made locally. NFS is an example. Such opaque file systems may handle authorization during an actual request or may implement a reliable access() operation. Depending on the value returned by vnode_authorize_opaque(), vnode_authorize_callback() can return immediately or continue with further checks.

  • It ensures that if a file is being modified, it is mutable.

  • Unless the provided VFS context corresponds to the superuser, vnode_authorize_callback() uses file attributes to determine whether the requested access is allowed.[19] Depending on the specific request, this may result in a call to vnode_authorize_simple(), which calls the kauth subsystem to evaluate the ACL, if any, associated with the vnode.

    [19] The superuser is denied execute access to a file unless at least one execute bit is set on the file.

Figure 1133. Authorization in the vnode scope

11.10.3. A Vnode-Level File System Activity Monitor

Now that we are familiar with the kauth KPI, let us use the knowledge to implement a listener for the vnode scope. We will not use the listener to participate in authorization decisions. Our listener will always return KAUTH_RESULT_DEFER as its "decision," which will defer every request to other listeners. However, for each request, it will package the vnode information received into a data structure, placing the latter on a shared memory queue that is accessible from a user-space program. We will also write a program that retrieves information from this queue and displays details of the corresponding vnode operations. Figure 1134 shows the arrangement.

Figure 1134. The design of a simple vnode-level file system activity monitor

Examining file system activity in detail can be an effective learning tool for understanding system behavior. Unlike the fslogger program (Figure 1125), which reports only file system modifications, the activity monitor we discuss in this section reports most file system accesses.[20]

[20] In some cases, the kernel may use cached results of certain file system operations. If so, subsequent invocations of these operations will not be visible to kauth.

The kernel portion of our activity monitor is contained within a kernel extension called VnodeWatcher.kext, which implements an I/O Kit driver and an I/O Kit user client. When the user client starts, it allocates an IODataQueue object for passing data from the kernel to user space. When a user-space program calls its open() method, the user client registers a kauth listener for the vnode scope. Thereafter, the program performs the following operations:

  • Calls IODataQueueAllocateNotificationPort() to allocate a Mach port to receive notifications about availability of data on the queue

  • Calls IOConnectSetNotificationPort() to set the port as the notification port

  • Calls IOConnectMapMemory() to map the shared memory corresponding to the queue into its address space

  • Loops waiting for data to be available on the queue, calling IODataQueueDequeue() to dequeue the next available entry on the queue and displaying the dequeued entry

When the user program exits, dies, or calls the user client's close() method, the kauth listener is deregistered.

Let us look at the kernel-side source first. Figure 1135 shows the header file that is shared between the kernel extension and the user-space program. We only implement the start() method of the I/O Kit driver classwe use it to publish the "VnodeWatcher" service. Note that we will treat a predefined filename (/private/tmp/VnodeWatcher.log) specially by ignoring all vnode operations on it. This way, we can use it as a log file without causing its own file system activity to be reported by the monitor.

Figure 1135. Common header file for the vnode-level file system activity monitor

// VnodeWatcher.h #include <sys/param.h> #include <sys/kauth.h> #include <sys/vnode.h> typedef struct {     UInt32        pid;     UInt32        action;     enum vtype    v_type;     enum vtagtype v_tag;;     char          p_comm[MAXCOMLEN + 1];     char          path[MAXPATHLEN]; } VnodeWatcherData_t; enum {     kt_kVnodeWatcherUserClientOpen,     kt_kVnodeWatcherUserClientClose,     kt_kVnodeWatcherUserClientNMethods,     kt_kStopListeningToMessages = 0xff, }; #define VNW_LOG_FILE "/private/tmp/VnodeWatcher.log" #ifdef KERNEL #include <IOKit/IOService.h> #include <IOKit/IOUserClient.h> #include <IOKit/IODataQueue.h> #include <sys/types.h> #include <sys/kauth.h> // the I/O Kit driver class class com_osxbook_driver_VnodeWatcher : public IOService {     OSDeclareDefaultStructors(com_osxbook_driver_VnodeWatcher) public:     virtual bool start(IOService *provider); }; enum { kt_kMaximumEventsToHold = 512 }; // the user client class class com_osxbook_driver_VnodeWatcherUserClient : public IOUserClient {     OSDeclareDefaultStructors(com_osxbook_driver_VnodeWatcherUserClient) private:     task_t                           fClient;     com_osxbook_driver_VnodeWatcher *fProvider;     IODataQueue                     *fDataQueue;     IOMemoryDescriptor              *fSharedMemory;     kauth_listener_t                 fListener; public:     virtual bool     start(IOService *provider);     virtual void     stop(IOService *provider);     virtual IOReturn open(void);     virtual IOReturn clientClose(void);     virtual IOReturn close(void);     virtual bool     terminate(IOOptionBits options);     virtual IOReturn startLogging(void);     virtual IOReturn stopLogging(void);     virtual bool     initWithTask(                          task_t owningTask, void *securityID, UInt32 type);     virtual IOReturn registerNotificationPort(                          mach_port_t port, UInt32 type, UInt32 refCon);     virtual IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options,                                          IOMemoryDescriptor **memory);     virtual IOExternalMethod *getTargetAndMethodForIndex(IOService **target,                                                          UInt32 index); }; #endif // KERNEL

Figure 1136 shows the contents of VnodeWatcher.cpp, which implement both the driver and the user client. The following points are noteworthy.

  • The listener functionmy_vnode_authorize_callback()adds information about the process name, vnode type, and vnode tag[21] to the information packet placed on the shared queue. Moreover, the listener calls vn_getpath() to build the pathname associated with the vnode.

    [21] A tag type indicates the file system type the vnode is associated with.

  • The listener keeps track of the number of times it is invoked by using a counter whose value it atomically adjusts. In Mac OS X 10.4, when a listener is deregistered through kauth_unlisten_scope(), the latter can return even though one or more threads executing the listener may still not have returned. Therefore, any state shared by the listener must not be destroyed until all such threads have returned.

  • The user client allows only one user-space program at a time to use the monitoring service. Moreover, the listener is registered only when a client program is attached, even though the kernel extension may remain loaded.

Figure 1136. Source for the vnode-level file system activity monitor kernel extension

// VnodeWatcher.cpp #include <IOKit/IOLib.h> #include <IOKit/IODataQueueShared.h> #include <sys/proc.h> #include "VnodeWatcher.h" #define super IOService OSDefineMetaClassAndStructors(com_osxbook_driver_VnodeWatcher, IOService) static char   *gLogFilePath = NULL; static size_t  gLogFilePathLen = 0; static SInt32  gListenerInvocations = 0; bool com_osxbook_driver_VnodeWatcher::start(IOService *provider) {     if (!super::start(provider))         return false;     gLogFilePath = VNW_LOG_FILE;     gLogFilePathLen = strlen(gLogFilePath) + 1;     registerService();     return true; } #undef super #define super IOUserClient OSDefineMetaClassAndStructors(     com_osxbook_driver_VnodeWatcherUserClient, IOUserClient) static const IOExternalMethod sMethods[kt_kVnodeWatcherUserClientNMethods] = {     {         NULL,         (IOMethod)&com_osxbook_driver_VnodeWatcherUserClient::open,         kIOUCScalarIScalarO,         0,         0     },     {         NULL,         (IOMethod)&com_osxbook_driver_VnodeWatcherUserClient::close,         kIOUCScalarIScalarO,         0,         0     }, }; static int my_vnode_authorize_callback(     kauth_cred_t    credential, // reference to the actor's credentials     void           *idata,      // cookie supplied when listener is registered     kauth_action_t  action,     // requested action     uintptr_t       arg0,       // the VFS context     uintptr_t       arg1,       // the vnode in question     uintptr_t       arg2,       // parent vnode, or NULL     uintptr_t       arg3)       // pointer to an errno value {     UInt32 size;     VnodeWatcherData_t data;     int name_len = MAXPATHLEN;     (void)OSIncrementAtomic(&gListenerInvocations); // enter the listener = vfs_context_pid((vfs_context_t)arg0);     proc_name(, data.p_comm, MAXCOMLEN + 1);     data.action = action;     data.v_type = vnode_vtype((vnode_t)arg1);     data.v_tag = (enum vtagtype)vnode_tag((vnode_t)arg1);     size = sizeof(data) - sizeof(data.path);     if (vn_getpath((vnode_t)arg1, data.path, &name_len) == 0)         size += name_len;     else {         data.path[0] = '\0';         size += 1;     }     if ((name_len != gLogFilePathLen) ||         memcmp(data.path, gLogFilePath, gLogFilePathLen)) { // skip log file         IODataQueue *q = OSDynamicCast(IODataQueue, (OSObject *)idata);         q->enqueue(&data, size);     }     (void)OSDecrementAtomic(&gListenerInvocations); // leave the listener     return KAUTH_RESULT_DEFER; // defer decision to other listeners } #define c_o_d_VUC com_osxbook_driver_VnodeWatcherUserClient bool c_o_d_VUC::start(IOService *provider) {     fProvider = OSDynamicCast(com_osxbook_driver_VnodeWatcher, provider);     if (!fProvider)         return false;     if (!super::start(provider))         return false;     fDataQueue = IODataQueue::withCapacity(                      (sizeof(VnodeWatcherData_t)) * kt_kMaximumEventsToHold +                      DATA_QUEUE_ENTRY_HEADER_SIZE);     if (!fDataQueue)         return kIOReturnNoMemory;     fSharedMemory = fDataQueue->getMemoryDescriptor();     if (!fSharedMemory) {         fDataQueue->release();         fDataQueue = NULL;         return kIOReturnVMError;     }     return true; } void c_o_d_VUC::stop(IOService *provider) {     if (fDataQueue) {         UInt8 message = kt_kStopListeningToMessages;         fDataQueue->enqueue(&message, sizeof(message));     }     if (fSharedMemory) {         fSharedMemory->release();         fSharedMemory = NULL;     }     if (fDataQueue) {         fDataQueue->release();         fDataQueue = NULL;     }     super::stop(provider); } IOReturn c_o_d_VUC::open(void) {     if (isInactive())         return kIOReturnNotAttached;     if (!fProvider->open(this))         return kIOReturnExclusiveAccess; // only one user client allowed     return startLogging(); } IOReturn c_o_d_VUC::clientClose(void) {     (void)close();     (void)terminate(0);     fClient = NULL;     fProvider = NULL;     return kIOReturnSuccess; } IOReturn c_o_d_VUC::close(void) {     if (!fProvider)         return kIOReturnNotAttached;     if (fProvider->isOpen(this))         fProvider->close(this);     return kIOReturnSuccess; } bool c_o_d_VUC::terminate(IOOptionBits options) {     // if somebody does a kextunload while a client is attached     if (fProvider && fProvider->isOpen(this))         fProvider->close(this);     (void)stopLogging();     return super::terminate(options); } IOReturn c_o_d_VUC::startLogging(void) {     fListener = kauth_listen_scope(              // register our listener                     KAUTH_SCOPE_VNODE,           // for the vnode scope                     my_vnode_authorize_callback, // using this callback                     (void *)fDataQueue);         // give this cookie to callback     if (fListener == NULL)         return kIOReturnInternalError;     return kIOReturnSuccess; } IOReturn c_o_d_VUC::stopLogging(void) {     if (fListener != NULL) {         kauth_unlisten_scope(fListener); // unregister our listener         fListener = NULL;     }     do { // wait for any existing listener invocations to return         struct timespec ts = { 1, 0 }; // one second         (void)msleep(&gListenerInvocations,      // wait channel                      NULL,                       // mutex                      PUSER,                      // priority                      "c_o_d_VUC::stopLogging()", // wait message                      &ts);                       // sleep interval     } while (gListenerInvocations > 0);     return kIOReturnSuccess; } bool c_o_d_VUC::initWithTask(task_t owningTask, void *securityID, UInt32 type) {     if (!super::initWithTask(owningTask, securityID , type))         return false;     if (!owningTask)         return false;     fClient = owningTask;     fProvider = NULL;     fDataQueue = NULL;     fSharedMemory = NULL;     return true; } IOReturn c_o_d_VUC::registerNotificationPort(mach_port_t port, UInt32 type, UInt32 ref) {     if ((!fDataQueue) || (port == MACH_PORT_NULL))         return kIOReturnError;     fDataQueue->setNotificationPort(port);     return kIOReturnSuccess; } IOReturn c_o_d_VUC::clientMemoryForType(UInt32 type, IOOptionBits *options,                                IOMemoryDescriptor **memory) {     *memory = NULL;     *options = 0;     if (type == kIODefaultMemoryType) {         if (!fSharedMemory)             return kIOReturnNoMemory;         fSharedMemory->retain(); // client will decrement this reference         *memory = fSharedMemory;         return kIOReturnSuccess;     }     // unknown memory type     return kIOReturnNoMemory; } IOExternalMethod * c_o_d_VUC::getTargetAndMethodForIndex(IOService **target, UInt32 index) {     if (index >= (UInt32)kt_kVnodeWatcherUserClientNMethods)         return NULL;     switch (index) {     case kt_kVnodeWatcherUserClientOpen:     case kt_kVnodeWatcherUserClientClose:         *target = this;         break;     default:         *target = fProvider;         break;     }     return (IOExternalMethod *)&sMethods[index]; }

vn_getpath() is also used by the fcntl() system call's F_GETPATH command, which retrieves the complete path corresponding to a given file descriptor. It is important to note that this mechanism is not foolproof. For example, if an open file is deleted, the path reported by vn_getpath() will be stale.

The sources in Figures 1135 and 1136 can be used in an Xcode project of type "IOKit Driver," with the following I/O Kit personality in the kernel extension's Info.plist file.

... <key>IOKitPersonalities</key> <dict>     <key>VnodeWatcher</key>     <dict>         <key>CFBundleIdentifier</key>         <string>com.osxbook.driver.VnodeWatcher</string>         <key>IOClass</key>         <string>com_osxbook_driver_VnodeWatcher</string>         <key>IOProviderClass</key>         <string>IOResources</string>         <key>IOResourceMatch</key>         <string>IOKit</string>         <key>IOUserClientClass</key>         <string>com_osxbook_driver_VnodeWatcherUserClient</string>         </dict> </dict> ...

Finally, let us look at the source for the user program (Figure 1137). It is a reasonably lightweight client in that it does not perform much processing itselfit merely displays the information contained in the queue while printing descriptive names of action bits that are set in the reported vnode operation.

Figure 1137. Source for the user-space retrieval program for the vnode-level file system activity monitor

// vnodewatch.c #include <IOKit/IOKitLib.h> #include <IOKit/IODataQueueShared.h> #include <IOKit/IODataQueueClient.h> #include <mach/mach.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/acl.h> #include "VnodeWatcher.h" #define PROGNAME "vnodewatch" #define VNODE_WATCHER_IOKIT_CLASS "com_osxbook_driver_VnodeWatcher" #define printIfAction(action, name) \     { if (action & KAUTH_VNODE_##name) { printf("%s ", #name); } } void action_print(UInt32 action, int isdir) {     printf("{ ");     if (isdir)         goto dir;     printIfAction(action, READ_DATA);   // read contents of file     printIfAction(action, WRITE_DATA);  // write contents of file     printIfAction(action, EXECUTE);     // execute contents of file     printIfAction(action, APPEND_DATA); // append to contents of file     goto common; dir:     printIfAction(action, LIST_DIRECTORY);   // enumerate directory contents     printIfAction(action, ADD_FILE);         // add file to directory     printIfAction(action, SEARCH);           // look up specific directory item     printIfAction(action, ADD_SUBDIRECTORY); // add subdirectory in directory     printIfAction(action, DELETE_CHILD);     // delete an item in directory common:     printIfAction(action, DELETE);              // delete a file system object     printIfAction(action, READ_ATTRIBUTES);     // read standard attributes     printIfAction(action, WRITE_ATTRIBUTES);    // write standard attributes     printIfAction(action, READ_EXTATTRIBUTES);  // read extended attributes     printIfAction(action, WRITE_EXTATTRIBUTES); // write extended attributes     printIfAction(action, READ_SECURITY);       // read ACL     printIfAction(action, WRITE_SECURITY);      // write ACL     printIfAction(action, TAKE_OWNERSHIP);      // change ownership     // printIfAction(action, SYNCHRONIZE);      // unused     printIfAction(action, LINKTARGET);          // create a new hard link     printIfAction(action, CHECKIMMUTABLE);      // check for immutability     printIfAction(action, ACCESS);              // special flag     printIfAction(action, NOIMMUTABLE);         // special flag     printf("}\n"); } const char * vtype_name(enum vtype vtype) {     static const char *vtype_names[] = {         "VNON",  "VREG",  "VDIR", "VBLK", "VCHR", "VLNK",         "VSOCK", "VFIFO", "VBAD", "VSTR", "VCPLX",     };     return vtype_names[vtype]; } const char * vtag_name(enum vtagtype vtag) {     static const char *vtag_names[] = {         "VT_NON",   "VT_UFS",    "VT_NFS",    "VT_MFS",    "VT_MSDOSFS",         "VT_LFS",   "VT_LOFS",   "VT_FDESC",  "VT_PORTAL", "VT_NULL",         "VT_UMAP",  "VT_KERNFS", "VT_PROCFS", "VT_AFS",    "VT_ISOFS",         "VT_UNION", "VT_HFS",    "VT_VOLFS",  "VT_DEVFS",  "VT_WEBDAV",         "VT_UDF",   "VT_AFP",    "VT_CDDA",   "VT_CIFS",   "VT_OTHER",     };     return vtag_names[vtag]; } static IOReturn vnodeNotificationHandler(io_connect_t connection) {     kern_return_t       kr;     VnodeWatcherData_t  vdata;     UInt32              dataSize;     IODataQueueMemory  *queueMappedMemory;     vm_size_t           queueMappedMemorySize;     vm_address_t        address = nil;     vm_size_t           size = 0;     unsigned int        msgType = 1; // family-defined port type (arbitrary)     mach_port_t         recvPort;     // allocate a Mach port to receive notifications from the IODataQueue     if (!(recvPort = IODataQueueAllocateNotificationPort())) {         fprintf(stderr, "%s: failed to allocate notification port\n", PROGNAME);         return kIOReturnError;     }     // this will call registerNotificationPort() inside our user client class     kr = IOConnectSetNotificationPort(connection, msgType, recvPort, 0);     if (kr != kIOReturnSuccess) {         fprintf(stderr, "%s: failed to register notification port (%d)\n",                 PROGNAME, kr);         mach_port_destroy(mach_task_self(), recvPort);         return kr;     }     // this will call clientMemoryForType() inside our user client class     kr = IOConnectMapMemory(connection, kIODefaultMemoryType,                             mach_task_self(), &address, &size, kIOMapAnywhere);     if (kr != kIOReturnSuccess) {         fprintf(stderr, "%s: failed to map memory (%d)\n", PROGNAME, kr);         mach_port_destroy(mach_task_self(), recvPort);         return kr;     }     queueMappedMemory = (IODataQueueMemory *)address;     queueMappedMemorySize = size;     while (IODataQueueWaitForAvailableData(queueMappedMemory, recvPort) ==            kIOReturnSuccess) {         while (IODataQueueDataAvailable(queueMappedMemory)) {             dataSize = sizeof(vdata);             kr = IODataQueueDequeue(queueMappedMemory, &vdata, &dataSize);             if (kr == kIOReturnSuccess) {                 if (*(UInt8 *)&vdata == kt_kStopListeningToMessages)                     goto exit;                 printf("\"%s\" %s %s %lu(%s) ",                        vdata.path,                        vtype_name(vdata.v_type),                        vtag_name(vdata.v_tag),              ,                        vdata.p_comm);                 action_print(vdata.action, (vdata.v_type & VDIR));             } else                 fprintf(stderr, "*** error in receiving data (%d)\n", kr);         }     } exit:     kr = IOConnectUnmapMemory(connection, kIODefaultMemoryType,                               mach_task_self(), address);     if (kr != kIOReturnSuccess)         fprintf(stderr, "%s: failed to unmap memory (%d)\n", PROGNAME, kr);     mach_port_destroy(mach_task_self(), recvPort);     return kr; } #define PRINT_ERROR_AND_RETURN(msg, ret) \     { fprintf(stderr, "%s: %s\n", PROGNAME, msg); return ret; } int main(int argc, char **argv) {     kern_return_t   kr;     int             ret;     io_iterator_t   iterator;     io_service_t    serviceObject;     CFDictionaryRef classToMatch;     pthread_t       dataQueueThread;     io_connect_t    connection;     setbuf(stdout, NULL);     if (!(classToMatch = IOServiceMatching(VNODE_WATCHER_IOKIT_CLASS)))         PRINT_ERROR_AND_RETURN("failed to create matching dictionary", -1);     kr = IOServiceGetMatchingServices(kIOMasterPortDefault, classToMatch,                                       &iterator);     if (kr != kIOReturnSuccess)         PRINT_ERROR_AND_RETURN("failed to retrieve matching services", -1);     serviceObject = IOIteratorNext(iterator);     IOObjectRelease(iterator);     if (!serviceObject)         PRINT_ERROR_AND_RETURN("VnodeWatcher service not found", -1);     kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &connection);     IOObjectRelease(serviceObject);     if (kr != kIOReturnSuccess)         PRINT_ERROR_AND_RETURN("failed to open VnodeWatcher service", kr);     kr = IOConnectMethodScalarIScalarO(connection,                                        kt_kVnodeWatcherUserClientOpen, 0, 0);     if (kr != KERN_SUCCESS) {         (void)IOServiceClose(connection);        PRINT_ERROR_AND_RETURN("VnodeWatcher service is busy", kr);     }     ret = pthread_create(&dataQueueThread, (pthread_attr_t *)0,                          (void *)vnodeNotificationHandler, (void *)connection);     if (ret)         perror("pthread_create");     else         pthread_join(dataQueueThread, (void **)&kr);     (void)IOServiceClose(connection);     return 0; }

Let us now test the programs we have created in this section. We will assume that the compiled kernel extension bundle resides as /tmp/VnodeWatcher.kext.

$ gcc -Wall -o vnodewatch vnodewatch.c -framework IOKit $ sudo kextload -v /tmp/VnodeWatcher.kext kextload: extension /tmp/VnodeWatcher.kext appears to be valid kextload: loading extension /tmp/VnodeWatcher.kext kextload: /tmp/VnodeWatcher.kext loaded successfully kextload: loading personalities named: kextload:     VnodeWatcher kextload: sending 1 personality to the kernel kextload: matching started for /tmp/VnodeWatcher.kext $ ./vnodewatch ... "/Users/amit/Desktop/hello.txt" VREG VT_HFS 3898(mdimport) { READ_DATA } "/Users/amit/Desktop/hello.txt" VREG VT_HFS 3898(mdimport) { READ_ATTRIBUTES } "/Users/amit/Desktop/hello.txt" VREG VT_HFS 3898(mdimport) { READ_ATTRIBUTES } "/" VDIR VT_HFS 189(mds) { SEARCH } "/.vol" VDIR VT_VOLFS 189(mds) { SEARCH } "/Users/amit/Desktop" VDIR VT_HFS 189(mds) { SEARCH } ...

Mac OS X Internals. A Systems Approach
Mac OS X Internals: A Systems Approach
ISBN: 0321278542
EAN: 2147483647
Year: 2006
Pages: 161
Authors: Amit Singh © 2008-2017.
If you may any questions please contact us: