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 11.10.1.1. 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); 11.10.1.2. 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 data.pid = vfs_context_pid((vfs_context_t)arg0); proc_name(data.pid, 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.pid, 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 } ... |