static int osquery_ioctl( dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) { #ifdef KERNEL_TEST // Reentrant code used for testing the queue functionality. // This test-only code allows benchmarks to stress test queue handling. static unsigned int test_counter = 0; if (cmd == OSQUERY_IOCTL_TEST) { if (osquery.buffer == NULL) { return -EINVAL; } test_counter++; size_t length = 0; void *e = NULL; switch (*(int *)data) { case 0: e = osquery_cqueue_reserve( &osquery.cqueue, OSQUERY_TEST_EVENT_0, sizeof(test_event_0_data_t)); length = 4096; break; case 1: e = osquery_cqueue_reserve( &osquery.cqueue, OSQUERY_TEST_EVENT_1, sizeof(test_event_1_data_t)); length = 33; break; default: return -ENOTTY; } if (!e) { return -EINVAL; } *(int *)e = test_counter; char *s = (char *)((int *)e + 1); memset(s, 'H', length); osquery_cqueue_commit(&osquery.cqueue, e); return 0; } #endif // KERNEL_TEST int err = 0; osquery_subscription_args_t *sub = NULL; osquery_buf_sync_args_t *sync = NULL; osquery_buf_allocate_args_t *alloc = NULL; // All control should be from a single daemon. // Wrap all IOCTL API handling in locks to guarantee proper use. lck_mtx_lock(osquery.mtx); switch (cmd) { // Daemon is requesting a new subscription (e.g., monitored path). case OSQUERY_IOCTL_SUBSCRIPTION: sub = (osquery_subscription_args_t *)data; if ((err = subscribe_to_event(sub->event, sub->subscribe))) { goto error_exit; } break; // Daemon is requesting a synchronization of readable queue space. case OSQUERY_IOCTL_BUF_SYNC: // The queue buffer cannot be synchronized if it has not been allocated. if (osquery.buffer == NULL) { err = -EINVAL; goto error_exit; } // Unlock while applying update logic, re-lock on error and success. lck_mtx_unlock(osquery.mtx); sync = (osquery_buf_sync_args_t *)data; if ((err = update_user_kernel_buffer(sync->options, sync->read_offset, &(sync->max_read_offset), &(sync->drops)))) { lck_mtx_lock(osquery.mtx); goto error_exit; } lck_mtx_lock(osquery.mtx); break; // Daemon is requesting an allocation for the queue, and shared region. case OSQUERY_IOCTL_BUF_ALLOCATE: alloc = (osquery_buf_allocate_args_t *)data; if (alloc->version != OSQUERY_KERNEL_COMM_VERSION) { // Daemon tried connecting with incorrect version number. // The structure types and sizes are bound to the COMMs version. // Any non-matching daemon may not handle these structures correctly. err = -EINVAL; goto error_exit; } if (osquery.buffer != NULL) { // There is only a single shared buffer. err = -EINVAL; goto error_exit; } // Attempt to allocation and set up the circular queue. if ((err = allocate_user_kernel_buffer(alloc->size, &(alloc->buffer)))) { goto error_exit; } dbg_printf( "IOCTL alloc: size %lu, location %p\n", alloc->size, alloc->buffer); break; default: err = -ENOTTY; goto error_exit; break; } error_exit: // Unlock and return a status to the daemon. lck_mtx_unlock(osquery.mtx); return err; }
// All control should be from a single consumer, so we wrap all these calls // in locks to guarantee proper use. static int osquery_ioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) { #ifdef KERNEL_TEST // Reentrant code used for testing the queue functionality. static unsigned int test_counter = 0; if (cmd == OSQUERY_IOCTL_TEST) { if (osquery.buffer == NULL) { return -EINVAL; } test_counter++; size_t length = 0; void *e = NULL; switch (*(int *)data) { case 0: e = osquery_cqueue_reserve(&osquery.cqueue, OSQUERY_TEST_EVENT_0, sizeof(test_event_0_data_t)); length = 4096; break; case 1: e = osquery_cqueue_reserve(&osquery.cqueue, OSQUERY_TEST_EVENT_1, sizeof(test_event_1_data_t)); length = 33; break; default: return -ENOTTY; } if (!e) { return -EINVAL; } *(int *)e = test_counter; char *s = (char *)((int *)e + 1); memset(s, 'H', length); osquery_cqueue_commit(&osquery.cqueue, e); return 0; } #endif // KERNEL_TEST lck_mtx_lock(osquery.mtx); int err = 0; osquery_subscription_args_t *sub = NULL; osquery_buf_sync_args_t *sync = NULL; osquery_buf_allocate_args_t *alloc = NULL; switch (cmd) { case OSQUERY_IOCTL_SUBSCRIPTION: sub = (osquery_subscription_args_t *)data; if ((err = subscribe_to_event(sub->event, sub->subscribe, sub->udata))) { goto error_exit; } break; case OSQUERY_IOCTL_BUF_SYNC: sync = (osquery_buf_sync_args_t *)data; if (osquery.buffer == NULL) { err = -EINVAL; goto error_exit; } lck_mtx_unlock(osquery.mtx); if ((err = update_user_kernel_buffer(sync->options, sync->read_offset, &(sync->max_read_offset), &(sync->drops)))) { lck_mtx_lock(osquery.mtx); goto error_exit; } lck_mtx_lock(osquery.mtx); break; case OSQUERY_IOCTL_BUF_ALLOCATE: alloc = (osquery_buf_allocate_args_t *)data; if (alloc->version != OSQUERY_KERNEL_COMM_VERSION) { // Daemon tried connecting with incorrect version number. err = -EINVAL; goto error_exit; } if (osquery.buffer != NULL) { // We don't want to allocate a second buffer. err = -EINVAL; goto error_exit; } if ((err = allocate_user_kernel_buffer(alloc->size, &(alloc->buffer)))) { goto error_exit; } dbg_printf("IOCTL alloc: size %lu, location %p\n", alloc->size, alloc->buffer); break; default: err = -ENOTTY; goto error_exit; break; } error_exit: lck_mtx_unlock(osquery.mtx); return err; }