Merge pull request #1327 from mofarrell/kernel-publishers

Publisher system for kernel events in the kernel extension.
This commit is contained in:
Michael O'Farrell 2015-07-13 16:54:47 -07:00
commit 9a67c18974
10 changed files with 220 additions and 93 deletions

View File

@ -26,6 +26,7 @@ if(APPLE)
# Set OS X platform-specific include paths.
include_directories("${CMAKE_SOURCE_DIR}/kernel/include")
include_directories("${CMAKE_SOURCE_DIR}/kernel/src")
include_directories("/System/Library/Frameworks/Kernel.framework/Headers")
include_directories("/Applications/Xcode.app/Contents/Developer/Platforms/\
MacOSX.platform/Developer/SDKs/MacOSX${APPLE_MIN_ABI}.sdk\
@ -59,16 +60,20 @@ endif()
# The set of platform-agnostic implementations.
set(BASE_KERNEL_SOURCES
src/osquery.cpp
src/circular_queue_kern.c
)
# TODO: Add a set of platform-specific files.
file(GLOB APPLE_KERNEL_PUBLISHER_SOURCES "src/publishers/darwin/*.c")
# Add a set of platform-specific files.
set(APPLE_KERNEL_SOURCES
src/osquery.cpp
${APPLE_KERNEL_PUBLISHER_SOURCES}
)
# Define kernel targets, each should be an extension/module.
if(APPLE)
# TODO: Remove the OS X requirement.
add_executable(base_kernel ${BASE_KERNEL_SOURCES})
add_executable(base_kernel ${APPLE_KERNEL_SOURCES} ${BASE_KERNEL_SOURCES})
set_target_properties(base_kernel PROPERTIES COMPILE_FLAGS ${KERNEL_C_FLAGS} ${KERNEL_CXX_FLAGS})
set_target_properties(base_kernel PROPERTIES LINK_FLAGS ${KERNEL_LINKER_FLAGS})
set_target_properties(base_kernel PROPERTIES EXCLUDE_FROM_ALL true)

View File

@ -23,6 +23,19 @@
#include <sys/param.h>
#endif
/** @brief Communication version between kernel and daemon.
*
* A daemon may only connect to a kernel with the same communication version.
* Bump this number when changing or adding any event structs.
*/
#define OSQUERY_KERNEL_COMMUNICATION_VERSION 1UL
#ifdef KERNEL_TEST
#define OSQUERY_KERNEL_COMM_VERSION \
(OSQUERY_KERNEL_COMMUNICATION_VERSION | (1UL << 63))
#else
#define OSQUERY_KERNEL_COMM_VERSION OSQUERY_KERNEL_COMMUNICATION_VERSION
#endif
#ifdef __cplusplus
extern "C" {
#endif
@ -36,7 +49,6 @@ extern "C" {
* a reentrant testing IOCTL call that adds test events to the cqueue structure.
*/
//
// Event feed types
//
@ -51,7 +63,7 @@ typedef enum {
OSQUERY_TEST_EVENT_1,
#endif // KERNEL_TEST
OSQUERY_EVENT_NUM_EVENTS // Number of different event types.
OSQUERY_NUM_EVENTS // Number of different event types.
} osquery_event_t;
typedef struct {
@ -122,6 +134,7 @@ typedef struct {
typedef struct {
osquery_event_t event;
int subscribe;
void *udata;
} osquery_subscription_args_t;
// Flags for buffer sync options.
@ -138,6 +151,7 @@ typedef struct {
typedef struct {
size_t size; // Size of shared user kernel buffer.
void *buffer; // (Output) Pointer to buffer location.
uint64_t version; // osquery kernel communication version.
} osquery_buf_allocate_args_t;
// TODO: Choose a proper IOCTL num.

View File

@ -12,12 +12,8 @@
#include <mach/mach_types.h>
#include <kern/debug.h>
#include <sys/vnode.h>
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/kauth.h>
#include <miscfs/devfs/devfs.h>
#include <sys/vnode.h>
@ -25,7 +21,7 @@
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOLib.h>
#include <feeds.h>
#include "publishers.h"
#include "circular_queue_kern.h"
@ -49,7 +45,6 @@ static struct {
void *devfs;
int major_number;
int open_count;
kauth_listener_t fileop_listener;
lck_grp_attr_t *lck_grp_attr;
lck_grp_t *lck_grp;
@ -65,68 +60,6 @@ static struct {
.major_number = OSQUERY_MAJOR
};
static int fileop_scope_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3) {
vnode_t vp = (vnode_t)arg0;
if (action == KAUTH_FILEOP_EXEC && vp != NULL) {
// Someone is executing a file.
int path_len = MAXPATHLEN;
osquery_process_event_t *e =
(osquery_process_event_t *)osquery_cqueue_reserve(&osquery.cqueue,
OSQUERY_PROCESS_EVENT);
if (e == NULL) {
// Failed to reserve space for the event.
return KAUTH_RESULT_DEFER;
}
e->pid = proc_selfpid();
e->ppid = proc_selfppid();
e->owner_uid = 0;
e->owner_gid = 0;
e->mode = -1;
vfs_context_t context = vfs_context_create(NULL);
if (context) {
struct vnode_attr vattr = {0};
VATTR_INIT(&vattr);
VATTR_WANTED(&vattr, va_uid);
VATTR_WANTED(&vattr, va_gid);
VATTR_WANTED(&vattr, va_mode);
VATTR_WANTED(&vattr, va_create_time);
VATTR_WANTED(&vattr, va_access_time);
VATTR_WANTED(&vattr, va_modify_time);
VATTR_WANTED(&vattr, va_change_time);
if (vnode_getattr(vp, &vattr, context) == 0) {
e->owner_uid = vattr.va_uid;
e->owner_gid = vattr.va_gid;
e->mode = vattr.va_mode;
e->create_time = vattr.va_create_time.tv_sec;
e->access_time = vattr.va_access_time.tv_sec;
e->modify_time = vattr.va_modify_time.tv_sec;
e->change_time = vattr.va_change_time.tv_sec;
}
vfs_context_rele(context);
}
e->uid = kauth_cred_getruid(credential);
e->euid = kauth_cred_getuid(credential);
e->gid = kauth_cred_getrgid(credential);
e->egid = kauth_cred_getgid(credential);
vn_getpath(vp, e->path, &path_len);
osquery_cqueue_commit(&osquery.cqueue, e);
}
return KAUTH_RESULT_DEFER;
}
static inline void setup_locks() {
/* Create locks. Cannot be done on the stack. */
osquery.lck_grp_attr = lck_grp_attr_alloc_init();
@ -150,30 +83,33 @@ static inline void teardown_locks() {
}
static void unsubscribe_all_events() {
if (osquery.fileop_listener) {
kauth_unlisten_scope(osquery.fileop_listener);
osquery.fileop_listener = NULL;
for (int i = 0; i < OSQUERY_NUM_EVENTS; i++) {
if (osquery_publishers[i]) {
osquery_publishers[i]->unsubscribe();
}
}
}
static int subscribe_to_event(osquery_event_t event, int subscribe) {
static int subscribe_to_event(osquery_event_t event,
int subscribe,
void *udata) {
if (osquery.buffer == NULL) {
return -EINVAL;
}
switch (event) {
case OSQUERY_PROCESS_EVENT:
if (subscribe && osquery.fileop_listener == NULL) {
osquery.fileop_listener =
kauth_listen_scope(KAUTH_SCOPE_FILEOP, fileop_scope_callback, NULL);
} else if (!subscribe && osquery.fileop_listener) {
kauth_unlisten_scope(osquery.fileop_listener);
osquery.fileop_listener = NULL;
}
break;
default:
if (!(OSQUERY_NULL_EVENT < event && event < OSQUERY_NUM_EVENTS)) {
return -EINVAL;
}
if (!osquery_publishers[event]) {
return -EINVAL;
}
if (subscribe) {
if (osquery_publishers[event]->subscribe(&osquery.cqueue, udata)) {
return -EINVAL;
}
} else {
osquery_publishers[event]->unsubscribe();
}
return 0;
}
@ -342,7 +278,7 @@ static int osquery_ioctl(dev_t dev, u_long cmd, caddr_t data,
switch (cmd) {
case OSQUERY_IOCTL_SUBSCRIPTION:
sub = (osquery_subscription_args_t *)data;
if ((err = subscribe_to_event(sub->event, sub->subscribe))) {
if ((err = subscribe_to_event(sub->event, sub->subscribe, sub->udata))) {
goto error_exit;
}
break;
@ -365,6 +301,12 @@ static int osquery_ioctl(dev_t dev, u_long cmd, caddr_t data,
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;

54
kernel/src/publishers.h Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <feeds.h>
#include "circular_queue_kern.h"
/** @brief Subscribe function type.
*
* This function type is called when someone subscribes to a publisher. It
* should initialize all event callbacks and start publishing events to the
* queue.
*
* @param queue The queue to publish to. A subscriber will only publish to the
* last queue (not an issue as there should only be one queue).
* @param udata Pointer to user data. This is in user space and must be
* copied down to kernel space before use. A publisher may use this for
* any additional data it wants from user space.
* @return 0 on success, negative on failure.
*/
typedef int (*osquery_subscriber_t)(osquery_cqueue_t *queue, void *udata);
/** @brief Unsubscribe function type.
*
* Functions of this type stop a publisher from publishing events to the queue
* as soon as possible.
*
* @return Void.
*/
typedef void (*osquery_unsubscriber_t)();
/** @brief A kernel publisher must provide the following function pointers.
*/
typedef struct {
osquery_subscriber_t subscribe;
osquery_unsubscriber_t unsubscribe;
} osquery_kernel_event_publisher_t;
//
// Event publisher structs defined in implementations.
//
extern osquery_kernel_event_publisher_t process_events_publisher;
/** @brief List of the kernel event publishers.
*/
static osquery_kernel_event_publisher_t
*osquery_publishers[OSQUERY_NUM_EVENTS] = {
[OSQUERY_PROCESS_EVENT] = &process_events_publisher
};

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/kauth.h>
#include <sys/vnode.h>
#include "publishers.h"
static osquery_cqueue_t *cqueue = NULL;
static kauth_listener_t fileop_listener = NULL;
static int fileop_scope_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3) {
vnode_t vp = (vnode_t)arg0;
if (action == KAUTH_FILEOP_EXEC && vp != NULL) {
// Someone is executing a file.
int path_len = MAXPATHLEN;
osquery_process_event_t *e =
(osquery_process_event_t *)osquery_cqueue_reserve(
cqueue, OSQUERY_PROCESS_EVENT);
if (e == NULL) {
// Failed to reserve space for the event.
return KAUTH_RESULT_DEFER;
}
e->pid = proc_selfpid();
e->ppid = proc_selfppid();
e->owner_uid = 0;
e->owner_gid = 0;
e->mode = -1;
vfs_context_t context = vfs_context_create(NULL);
if (context) {
struct vnode_attr vattr = {0};
VATTR_INIT(&vattr);
VATTR_WANTED(&vattr, va_uid);
VATTR_WANTED(&vattr, va_gid);
VATTR_WANTED(&vattr, va_mode);
VATTR_WANTED(&vattr, va_create_time);
VATTR_WANTED(&vattr, va_access_time);
VATTR_WANTED(&vattr, va_modify_time);
VATTR_WANTED(&vattr, va_change_time);
if (vnode_getattr(vp, &vattr, context) == 0) {
e->owner_uid = vattr.va_uid;
e->owner_gid = vattr.va_gid;
e->mode = vattr.va_mode;
e->create_time = vattr.va_create_time.tv_sec;
e->access_time = vattr.va_access_time.tv_sec;
e->modify_time = vattr.va_modify_time.tv_sec;
e->change_time = vattr.va_change_time.tv_sec;
}
vfs_context_rele(context);
}
e->uid = kauth_cred_getruid(credential);
e->euid = kauth_cred_getuid(credential);
e->gid = kauth_cred_getrgid(credential);
e->egid = kauth_cred_getgid(credential);
vn_getpath(vp, e->path, &path_len);
osquery_cqueue_commit(cqueue, e);
}
return KAUTH_RESULT_DEFER;
}
static int subscribe(osquery_cqueue_t *queue, void *udata) {
cqueue = queue;
if (fileop_listener != NULL) {
return -1;
}
fileop_listener =
kauth_listen_scope(KAUTH_SCOPE_FILEOP, fileop_scope_callback, NULL);
return 0;
}
static void unsubscribe() {
if (fileop_listener) {
kauth_unlisten_scope(fileop_listener);
fileop_listener = NULL;
}
}
osquery_kernel_event_publisher_t process_events_publisher = {
.subscribe = &subscribe,
.unsubscribe = &unsubscribe
};

View File

@ -40,7 +40,7 @@ void KernelEventPublisher::configure() {
for (const auto &sub : subscriptions_) {
if (queue_ != nullptr) {
auto sc = getSubscriptionContext(sub->context);
queue_->subscribe(sc->event_type);
queue_->subscribe(sc->event_type, sc->udata);
}
}
}

View File

@ -23,6 +23,9 @@ namespace osquery {
struct KernelSubscriptionContext : public SubscriptionContext {
/// The kernel event subscription type.
osquery_event_t event_type;
/// Data to pass to the kernel.
void *udata;
};
/**

View File

@ -27,6 +27,7 @@ CQueue::CQueue(size_t size) {
osquery_buf_allocate_args_t alloc;
alloc.size = size;
alloc.buffer = NULL;
alloc.version = OSQUERY_KERNEL_COMM_VERSION;
fd_ = open(filename, O_RDWR);
if (fd_ < 0) {
@ -50,10 +51,11 @@ CQueue::~CQueue() {
}
}
void CQueue::subscribe(osquery_event_t event) {
void CQueue::subscribe(osquery_event_t event, void *udata) {
osquery_subscription_args_t sub;
sub.event = event;
sub.subscribe = 1;
sub.udata = udata;
if (ioctl(fd_, OSQUERY_IOCTL_SUBSCRIPTION, &sub)) {
throw CQueueException("Could not subscribe to event.");

View File

@ -55,8 +55,9 @@ class CQueue {
* This sets up the event callbacks so we start hearing about the given event.
*
* @param event The event we are interested in.
* @param udata Pointer to udata for the event. This is for additional info.
*/
void subscribe(osquery_event_t event);
void subscribe(osquery_event_t event, void *udata);
/**
* @brief Dequeue's an event from the shared buffer.

View File

@ -28,6 +28,7 @@ REGISTER(ProcessEventSubscriber, "event_subscriber", "process_events");
Status ProcessEventSubscriber::init() {
auto sc = createSubscriptionContext();
sc->event_type = OSQUERY_PROCESS_EVENT;
sc->udata = NULL;
subscribe(&ProcessEventSubscriber::Callback, sc, NULL);
return Status(0, "OK");