mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-08 18:33:54 +00:00
1116 lines
38 KiB
C++
1116 lines
38 KiB
C++
/*
|
|
* Copyright (c) 2014-present, 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.
|
|
*
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <osquery/core.h>
|
|
#include <osquery/dispatcher.h>
|
|
#include <osquery/registry.h>
|
|
#include <osquery/status.h>
|
|
#include <osquery/tables.h>
|
|
|
|
namespace osquery {
|
|
|
|
struct Subscription;
|
|
template <class SC, class EC>
|
|
class EventPublisher;
|
|
template <class PUB>
|
|
class EventSubscriber;
|
|
class EventFactory;
|
|
|
|
using EventPublisherID = const std::string;
|
|
using EventSubscriberID = const std::string;
|
|
using EventID = const std::string;
|
|
using EventContextID = uint64_t;
|
|
using EventTime = uint64_t;
|
|
using EventRecord = std::pair<EventID, EventTime>;
|
|
|
|
/**
|
|
* @brief An EventPublisher will define a SubscriptionContext for
|
|
* EventSubscriber%s to use.
|
|
*
|
|
* Most EventPublisher%s will require specific information for interacting with
|
|
* an OS to receive events. The SubscriptionContext contains information the
|
|
* EventPublisher will use to register OS API callbacks, create
|
|
* subscriptioning/listening handles, etc.
|
|
*
|
|
* Linux `inotify` should implement a SubscriptionContext that subscribes
|
|
* filesystem events based on a filesystem path. `libpcap` will subscribe on
|
|
* networking protocols at various stacks. Process creation may subscribe on
|
|
* process name, parent pid, etc.
|
|
*/
|
|
struct SubscriptionContext : private boost::noncopyable {};
|
|
|
|
/**
|
|
* @brief An EventSubscriber EventCallback method will receive an EventContext.
|
|
*
|
|
* The EventContext contains the event-related data supplied by an
|
|
* EventPublisher when the event occurs. If a subscribing EventSubscriber
|
|
* would be called for the event, the EventSubscriber%'s EventCallback is
|
|
* passed an EventContext.
|
|
*/
|
|
struct EventContext : private boost::noncopyable {
|
|
/// An unique counting ID specific to the EventPublisher%'s fired events.
|
|
EventContextID id{0};
|
|
|
|
/// The time the event occurred, as determined by the publisher.
|
|
EventTime time{0};
|
|
};
|
|
|
|
using SubscriptionRef = std::shared_ptr<Subscription>;
|
|
using BaseEventPublisher = EventPublisher<SubscriptionContext, EventContext>;
|
|
using EventPublisherRef = std::shared_ptr<BaseEventPublisher>;
|
|
using SubscriptionContextRef = std::shared_ptr<SubscriptionContext>;
|
|
using EventContextRef = std::shared_ptr<EventContext>;
|
|
using BaseEventSubscriber = EventSubscriber<BaseEventPublisher>;
|
|
using EventSubscriberRef = std::shared_ptr<EventSubscriber<BaseEventPublisher>>;
|
|
|
|
/**
|
|
* @brief EventSubscriber%s and Publishers may exist in various states.
|
|
*
|
|
* The class will move through states when osquery is initializing the
|
|
* registry, starting event publisher loops, and requesting initialization of
|
|
* each subscriber and the optional set of subscriptions it creates. If this
|
|
* initialization fails the publishers or EventFactory may eject, warn, or
|
|
* otherwise not use the subscriber's subscriptions.
|
|
*
|
|
* The supported states are:
|
|
* - None: The default state, uninitialized.
|
|
* - Setup: The Subscriber is attached and has run setup.
|
|
* - Running: Subscriber is ready for events.
|
|
* - Paused: Subscriber was initialized but is not currently accepting events.
|
|
* - Failed: Subscriber failed to initialize or is otherwise offline.
|
|
*/
|
|
enum class EventState {
|
|
EVENT_NONE = 0,
|
|
EVENT_SETUP,
|
|
EVENT_RUNNING,
|
|
EVENT_PAUSED,
|
|
EVENT_FAILED,
|
|
};
|
|
|
|
/// Use a single placeholder for the EventContextRef passed to EventCallback.
|
|
using EventCallback = std::function<Status(const EventContextRef&,
|
|
const SubscriptionContextRef&)>;
|
|
|
|
/// An EventPublisher must track every subscription added.
|
|
using SubscriptionVector = std::vector<SubscriptionRef>;
|
|
|
|
/// The set of search-time binned lookup tables.
|
|
extern const std::vector<size_t> kEventTimeLists;
|
|
|
|
/**
|
|
* @brief Details for each subscriber as it relates to the schedule.
|
|
*
|
|
* This is populated for each configuration update by scanning the schedule.
|
|
*/
|
|
struct SubscriberExpirationDetails {
|
|
public:
|
|
/// The max internal is the minimum wait time for expiring subscriber data.
|
|
size_t max_interval{0};
|
|
|
|
/// The number of queries that should run between intervals.
|
|
size_t query_count{0};
|
|
};
|
|
|
|
/**
|
|
* @brief DECLARE_PUBLISHER supplies needed boilerplate code that applies a
|
|
* string-type EventPublisherID to identify the publisher declaration.
|
|
*/
|
|
#define DECLARE_PUBLISHER(TYPE) \
|
|
public: \
|
|
EventPublisherID type() const override final { \
|
|
return TYPE; \
|
|
}
|
|
|
|
/**
|
|
* @brief A Subscription is used to configure an EventPublisher and bind a
|
|
* callback to a SubscriptionContext.
|
|
*
|
|
* A Subscription is the input to an EventPublisher when the EventPublisher
|
|
* decides on the scope and details of the events it watches/generates.
|
|
* An example includes a filesystem change event. A subscription would include
|
|
* a path with optional recursion and attribute selectors as well as a callback
|
|
* function to fire when an event for that path and selector occurs.
|
|
*
|
|
* A Subscription also functions to greatly scope an EventPublisher%'s work.
|
|
* Using the same filesystem example and the Linux inotify subsystem a
|
|
* Subscription limits the number of inode watches to only those requested by
|
|
* appropriate EventSubscriber%s.
|
|
* Note: EventSubscriber%s and Subscriptions can be configured by the osquery
|
|
* user.
|
|
*
|
|
* Subscriptions are usually created with EventFactory members:
|
|
*
|
|
* @code{.cpp}
|
|
* EventFactory::addSubscription("MyEventPublisher", my_subscription_context);
|
|
* @endcode
|
|
*/
|
|
struct Subscription : private boost::noncopyable {
|
|
public:
|
|
// EventSubscriber name.
|
|
std::string subscriber_name;
|
|
|
|
/// An EventPublisher%-specific SubscriptionContext.
|
|
SubscriptionContextRef context;
|
|
|
|
/// An EventSubscription member EventCallback method.
|
|
EventCallback callback;
|
|
|
|
explicit Subscription(EventSubscriberID& name) : subscriber_name(name){};
|
|
|
|
static SubscriptionRef create(EventSubscriberID& name) {
|
|
return std::make_shared<Subscription>(name);
|
|
}
|
|
|
|
static SubscriptionRef create(EventSubscriberID& name,
|
|
const SubscriptionContextRef& mc,
|
|
EventCallback ec = nullptr) {
|
|
auto subscription = std::make_shared<Subscription>(name);
|
|
subscription->context = mc;
|
|
subscription->callback = ec;
|
|
return subscription;
|
|
}
|
|
|
|
public:
|
|
Subscription() = delete;
|
|
};
|
|
|
|
class Eventer {
|
|
public:
|
|
/**
|
|
* @brief Request the subscriber's initialization state.
|
|
*
|
|
* When event subscribers are created (initialized) they are expected to emit
|
|
* a set of subscriptions to their publisher "type". If the subscriber fails
|
|
* to initialize then the publisher may remove any intermediate subscriptions.
|
|
*/
|
|
EventState state() const {
|
|
return state_;
|
|
}
|
|
|
|
protected:
|
|
/// Set the subscriber state.
|
|
void state(EventState state) {
|
|
state_ = state;
|
|
}
|
|
|
|
private:
|
|
/// The event subscriber's run state.
|
|
EventState state_{EventState::EVENT_NONE};
|
|
|
|
friend class EventFactory;
|
|
};
|
|
|
|
class EventPublisherPlugin : public Plugin,
|
|
public InterruptableRunnable,
|
|
public Eventer {
|
|
public:
|
|
/**
|
|
* @brief A new Subscription was added, potentially change state based on all
|
|
* subscriptions for this EventPublisher.
|
|
*
|
|
* `configure` allows the EventPublisher to optimize on the state of all
|
|
* subscriptions. An example is Linux `inotify` where multiple
|
|
* EventSubscription%s will subscription identical paths, e.g., /etc for
|
|
* config changes. Since Linux `inotify` has a subscription limit, `configure`
|
|
* can dedup paths.
|
|
*/
|
|
virtual void configure() override{};
|
|
|
|
/**
|
|
* @brief Perform handle opening, OS API callback registration.
|
|
*
|
|
* `setUp` is the event framework's EventPublisher constructor equivalent.
|
|
* This is called in the main thread before the publisher's run loop has
|
|
* started, immediately following registration.
|
|
*/
|
|
virtual Status setUp() override {
|
|
return Status(0, "Not used");
|
|
}
|
|
|
|
/**
|
|
* @brief Perform handle closing, resource cleanup.
|
|
*
|
|
* osquery is about to end, the EventPublisher should close handle descriptors
|
|
* unblock resources, and prepare to exit. This will be called from the main
|
|
* thread after the run loop thread has exited.
|
|
*/
|
|
virtual void tearDown() override {}
|
|
|
|
/**
|
|
* @brief Implement a "step" of an optional run loop.
|
|
*
|
|
* @return A SUCCESS status will immediately call `run` again. A FAILED status
|
|
* will exit the run loop and the thread.
|
|
*/
|
|
virtual Status run() {
|
|
return Status(1, "No run loop required");
|
|
}
|
|
|
|
/**
|
|
* @brief Allow the EventFactory to interrupt the run loop.
|
|
*
|
|
* Assume the main thread may ask the run loop to stop at anytime.
|
|
* Before end is called the publisher's `isEnding` is set and the EventFactory
|
|
* run loop manager will exit the stepping loop and fall through to a call
|
|
* to tearDown followed by a removal of the publisher.
|
|
*/
|
|
virtual void stop() override {}
|
|
|
|
/// This is a plugin type and must implement a call method.
|
|
Status call(const PluginRequest&, PluginResponse&) override {
|
|
return Status(0);
|
|
}
|
|
|
|
/**
|
|
* @brief A new EventSubscriber is subscribing events of this publisher type.
|
|
*
|
|
* @param subscription The Subscription context information and optional
|
|
* EventCallback.
|
|
*
|
|
* @return If the Subscription is not appropriate (mismatched type) fail.
|
|
*/
|
|
virtual Status addSubscription(const SubscriptionRef& subscription);
|
|
|
|
/// Remove all subscriptions from a named subscriber.
|
|
virtual void removeSubscriptions(const std::string& subscriber);
|
|
|
|
public:
|
|
/// Overriding the EventPublisher constructor is not recommended.
|
|
EventPublisherPlugin() {}
|
|
virtual ~EventPublisherPlugin() {}
|
|
|
|
/// Return a string identifier associated with this EventPublisher.
|
|
virtual EventPublisherID type() const {
|
|
return getName();
|
|
}
|
|
|
|
public:
|
|
/// Number of Subscription%s watching this EventPublisher.
|
|
size_t numSubscriptions() const {
|
|
return subscriptions_.size();
|
|
}
|
|
|
|
/**
|
|
* @brief The number of events fired by this EventPublisher.
|
|
*
|
|
* @return The number of events.
|
|
*/
|
|
EventContextID numEvents() const {
|
|
return next_ec_id_;
|
|
}
|
|
|
|
/// Check if the EventFactory is ending all publisher threads.
|
|
bool isEnding() const {
|
|
return ending_;
|
|
}
|
|
|
|
/// Set the ending status for this publisher.
|
|
void isEnding(bool ending) {
|
|
ending_ = ending;
|
|
}
|
|
|
|
/// Check if the publisher's run loop has started.
|
|
bool hasStarted() const {
|
|
return started_;
|
|
}
|
|
|
|
/// Set the run or started status for this publisher.
|
|
void hasStarted(bool started) {
|
|
started_ = started;
|
|
}
|
|
|
|
/// Get the number of publisher restarts.
|
|
size_t restartCount() const {
|
|
return restart_count_;
|
|
}
|
|
|
|
public:
|
|
explicit EventPublisherPlugin(EventPublisherPlugin const&) = delete;
|
|
EventPublisherPlugin& operator=(EventPublisherPlugin const&) = delete;
|
|
|
|
protected:
|
|
/**
|
|
* @brief The generic check loop to call SubscriptionContext callback methods.
|
|
*
|
|
* It is NOT recommended to override `fire`. The simple logic of enumerating
|
|
* the Subscription%s and using `shouldFire` is more appropriate.
|
|
*
|
|
* @param ec The EventContext created and fired by the EventPublisher.
|
|
* @param time The most accurate time associated with the event.
|
|
*/
|
|
virtual void fire(const EventContextRef& ec, EventTime time = 0) final;
|
|
|
|
/// The internal fire method used by the typed EventPublisher.
|
|
virtual void fireCallback(const SubscriptionRef& sub,
|
|
const EventContextRef& ec) const = 0;
|
|
|
|
/// A lock for subscription manipulation.
|
|
Mutex subscription_lock_;
|
|
|
|
/// The EventPublisher will keep track of Subscription%s that contain callins.
|
|
SubscriptionVector subscriptions_;
|
|
|
|
/// An Event ID is assigned by the EventPublisher within the EventContext.
|
|
/// This is not used to store event date in the backing store.
|
|
std::atomic<EventContextID> next_ec_id_{0};
|
|
|
|
private:
|
|
/// Set ending to True to cause event type run loops to finish.
|
|
std::atomic<bool> ending_{false};
|
|
|
|
/// Set to indicate whether the event run loop ever started.
|
|
std::atomic<bool> started_{false};
|
|
|
|
/// A lock for incrementing the next EventContextID.
|
|
Mutex ec_id_lock_;
|
|
|
|
/// A helper count of event publisher runloop iterations.
|
|
std::atomic<size_t> restart_count_{0};
|
|
|
|
private:
|
|
/// Enable event factory "callins" through static publisher callbacks.
|
|
friend class EventFactory;
|
|
|
|
private:
|
|
FRIEND_TEST(EventsTests, test_event_publisher);
|
|
FRIEND_TEST(EventsTests, test_fire_event);
|
|
};
|
|
|
|
class EventSubscriberPlugin : public Plugin, public Eventer {
|
|
public:
|
|
/**
|
|
* @brief Add Subscription%s to the EventPublisher this module will act on.
|
|
*
|
|
* When the EventSubscriber%'s `init` method is called you are assured the
|
|
* EventPublisher has `setUp` and is ready to subscription for events.
|
|
*/
|
|
virtual Status init() {
|
|
return Status(0);
|
|
}
|
|
|
|
/// This is a plugin type and must implement a call method.
|
|
Status call(const PluginRequest&, PluginResponse&) override {
|
|
return Status(0);
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* @brief Store parsed event data from an EventCallback in a backing store.
|
|
*
|
|
* Within a EventCallback the EventSubscriber has an opportunity to create
|
|
* an osquery Row element, add the relevant table data for the EventSubscriber
|
|
* and store that element in the osquery backing store. At query-time
|
|
* the added data will apply selection criteria and return these elements.
|
|
* The backing store data retrieval is optimized by time-based indexes. It
|
|
* is important to added EventTime as it relates to "when the event occurred".
|
|
*
|
|
* @param r An osquery Row element.
|
|
*
|
|
* @return Was the element added to the backing store.
|
|
*/
|
|
Status add(Row& r) {
|
|
return add(r, 0);
|
|
}
|
|
|
|
/**
|
|
* @brief Return all events added by this EventSubscriber within start, stop.
|
|
*
|
|
* This is used internally (for the most part) by EventSubscriber::genTable.
|
|
*
|
|
* @param yield The Row yield method.
|
|
* @param start Inclusive lower bound time limit.
|
|
* @param stop Inclusive upper bound time limit.
|
|
* @return Set of event rows matching time limits.
|
|
*/
|
|
virtual void get(RowYield& yield, EventTime start, EventTime stop) final;
|
|
|
|
private:
|
|
/// Overload add for tests and allow them to override the event time.
|
|
virtual Status add(Row& r, EventTime event_time) final;
|
|
|
|
private:
|
|
/*
|
|
* @brief When `get`ing event results, return EventID%s from time indexes.
|
|
*
|
|
* Used by EventSubscriber::get to retrieve EventID, EventTime indexes. This
|
|
* applies the lookup-efficiency checks for time list appropriate bins.
|
|
* If the time range in 24 hours and there is a 24-hour list bin it will
|
|
* be queried using a single backing store `Get` followed by two `Get`s of
|
|
* the most-specific boundary lists.
|
|
*
|
|
* @param index the set of index to scan.
|
|
* @param optimize if true apply optimization checks.
|
|
*
|
|
* @return List of EventID, EventTime%s
|
|
*/
|
|
std::vector<EventRecord> getRecords(const std::vector<std::string>& indexes,
|
|
bool optimize = true);
|
|
|
|
/**
|
|
* @brief Get a unique storage-related EventID.
|
|
*
|
|
* An EventID is an index/element-identifier for the backing store.
|
|
* Each EventPublisher maintains a fired EventContextID to identify the many
|
|
* events that may or may not be fired based on subscription criteria for this
|
|
* EventSubscriber. This EventContextID is NOT the same as an EventID.
|
|
* EventSubscriber development should not require use of EventID%s. If this
|
|
* indexing is required within-EventCallback consider an
|
|
* EventSubscriber%-unique indexing, counting mechanic.
|
|
*
|
|
* @return A unique ID for backing storage.
|
|
*/
|
|
EventID getEventID();
|
|
|
|
/**
|
|
* @brief Plan the best set of indexes for event record access.
|
|
*
|
|
* @param start an inclusive time to begin searching.
|
|
* @param stop an inclusive time to end searching.
|
|
* @param sort if true the indexes will be sorted.
|
|
*
|
|
* @return List of 'index.step' index strings.
|
|
*/
|
|
std::vector<std::string> getIndexes(EventTime start,
|
|
EventTime stop,
|
|
bool sort = true);
|
|
|
|
/**
|
|
* @brief Expire indexes and eventually records.
|
|
*
|
|
* @param list_type the string representation of list binning type.
|
|
* @param indexes complete set of 'index.step' indexes for the list_type.
|
|
* @param expirations of the indexes, the set to expire.
|
|
*/
|
|
void expireIndexes(const std::string& list_type,
|
|
const std::vector<std::string>& indexes,
|
|
const std::vector<std::string>& expirations);
|
|
|
|
/// Expire all datums within a bin.
|
|
void expireRecords(const std::string& list_type,
|
|
const std::string& index,
|
|
bool all);
|
|
|
|
/**
|
|
* @brief Inspect the number of events, expire those overflowing events_max.
|
|
*
|
|
* When the event manager starts, or after a checkpoint number of events,
|
|
* the EventFactory will call expireCheck for each subscriber.
|
|
*
|
|
* The subscriber must count the number of buffered records and check if
|
|
* that count exceeds the configured `events_max` limit. If an overflow
|
|
* occurs the subscriber will expire N-events_max from the end of the queue.
|
|
*/
|
|
void expireCheck();
|
|
|
|
/**
|
|
* @brief Add an EventID, EventTime pair to all matching list types.
|
|
*
|
|
* The list types are defined by time size. Based on the EventTime this pair
|
|
* is added to the list bin for each list type. If there are two list types:
|
|
* 60 seconds and 3600 seconds and `time` is 92, this pair will be added to
|
|
* list type 1 bin 4 and list type 2 bin 1.
|
|
*
|
|
* @param eid A unique EventID.
|
|
* @param time The time when this EventID%'s event occurred.
|
|
*
|
|
* @return Were the indexes recorded.
|
|
*/
|
|
Status recordEvent(EventID& eid, EventTime time);
|
|
|
|
/**
|
|
* @brief Get the expiration timeout for this event type
|
|
*
|
|
* The default implementation retrieves this value from FLAGS_events_expiry.
|
|
* This method can be overridden to allow custom event expiration timeouts in
|
|
* subclasses of EventSubscriberPlugin.
|
|
*
|
|
* @return The events expiration timeout for this event type
|
|
*/
|
|
virtual size_t getEventsExpiry();
|
|
|
|
/**
|
|
* @brief Get the max number of events for this event type
|
|
*
|
|
* The default implementation retrieves this value from FLAGS_events_max.
|
|
* This method can be overridden to allow custom max event numbers in
|
|
* subclasses of EventSubscriberPlugin.
|
|
*
|
|
* @return The max number of events for this event type
|
|
*/
|
|
virtual size_t getEventsMax();
|
|
|
|
public:
|
|
/**
|
|
* @brief A single instance requirement for static callback facilities.
|
|
*
|
|
* The EventSubscriber constructor is NOT responsible for adding
|
|
* Subscription%s. Please use `init` for adding Subscription%s as all
|
|
* EventPublisher instances will have run `setUp` and initialized their run
|
|
* loops.
|
|
*/
|
|
EventSubscriberPlugin()
|
|
: expire_events_(true), expire_time_(0), optimize_time_(0) {}
|
|
virtual ~EventSubscriberPlugin() {}
|
|
|
|
/**
|
|
* @brief Suggested entrypoint for table generation.
|
|
*
|
|
* The EventSubscriber is a convention that removes a lot of boilerplate event
|
|
* 'subscribing' and acting. The `genTable` static entrypoint is the
|
|
* suggested method for table specs.
|
|
*
|
|
* @param yield The Row yield method.
|
|
* @param ctx The query context (used for time windows).
|
|
* @return The query-time table data, retrieved from a backing store.
|
|
*/
|
|
virtual void genTable(RowYield& yield, QueryContext& ctx) USED_SYMBOL;
|
|
|
|
/// Number of Subscription%s this EventSubscriber has used.
|
|
size_t numSubscriptions() const {
|
|
return subscription_count_;
|
|
}
|
|
|
|
/// The number of events this EventSubscriber has received.
|
|
EventContextID numEvents() const {
|
|
return event_count_;
|
|
}
|
|
|
|
/// Compare the number of queries run against the queries configured.
|
|
bool executedAllQueries() const;
|
|
|
|
private:
|
|
explicit EventSubscriberPlugin(EventSubscriberPlugin const&) = delete;
|
|
EventSubscriberPlugin& operator=(EventSubscriberPlugin const&) = delete;
|
|
|
|
protected:
|
|
/**
|
|
* @brief Backing storage indexing namespace.
|
|
*
|
|
* The backing storage will accumulate events for this subscriber. A namespace
|
|
* is provided to prevent event indexing collisions between subscribers and
|
|
* publishers. The namespace is a combination of the publisher and subscriber
|
|
* registry plugin names.
|
|
*/
|
|
/// See getType for lookup rational.
|
|
virtual EventPublisherID dbNamespace() const {
|
|
return getType() + '.' + getName();
|
|
}
|
|
|
|
/// Disable event expiration for this subscriber.
|
|
void doNotExpire() {
|
|
expire_events_ = false;
|
|
}
|
|
|
|
/// Trampoline into the EventFactory and lookup the name of the publisher.
|
|
virtual EventPublisherID& getType() const = 0;
|
|
|
|
/// Get a handle to the EventPublisher.
|
|
EventPublisherRef getPublisher() const;
|
|
|
|
/// Remove all subscriptions from this subscriber.
|
|
void removeSubscriptions();
|
|
|
|
protected:
|
|
/// A helper value counting the number of fired events tracked by publishers.
|
|
EventContextID event_count_{0};
|
|
|
|
/// A helper value counting the number of subscriptions created.
|
|
size_t subscription_count_{0};
|
|
|
|
private:
|
|
Status setUp() override {
|
|
return Status(0, "Setup never used");
|
|
}
|
|
|
|
private:
|
|
/// Do not respond to periodic/scheduled/triggered event expiration requests.
|
|
bool expire_events_{false};
|
|
|
|
/// Events before the expire_time_ are invalid and will be purged.
|
|
EventTime expire_time_{0};
|
|
|
|
/// Cached value of last generated EventID.
|
|
size_t last_eid_{0};
|
|
|
|
/**
|
|
* @brief Optimize subscriber selects by tracking the last select time.
|
|
*
|
|
* Event subscribers may optimize selects when used in a daemon schedule by
|
|
* requiring an event 'time' constraint and otherwise applying a minimum time
|
|
* as the last time the scheduled query ran.
|
|
*/
|
|
EventTime optimize_time_{0};
|
|
|
|
/**
|
|
* @brief Last event ID returned while using events-optimization.
|
|
*
|
|
* A time with second precision is not sufficient, but it works for index
|
|
* retrieval. While sorting using the time optimization, discard events
|
|
* before or equal to the optimization ID.
|
|
*/
|
|
size_t optimize_eid_{0};
|
|
|
|
/// The minimum acceptable expiration, based on the query schedule.
|
|
std::atomic<size_t> min_expiration_{0};
|
|
|
|
/// The number of scheduled queries using this subscriber.
|
|
std::atomic<size_t> query_count_{0};
|
|
|
|
/// Set of queries that have used this subscriber table.
|
|
std::set<std::string> queries_;
|
|
|
|
/// Lock used when incrementing the EventID database index.
|
|
Mutex event_id_lock_;
|
|
|
|
/// Lock used when recording an EventID and time into search bins.
|
|
Mutex event_record_lock_;
|
|
|
|
/// Lock used when recording queries executing against this subscriber.
|
|
mutable Mutex event_query_record_;
|
|
|
|
private:
|
|
friend class EventFactory;
|
|
friend class EventPublisherPlugin;
|
|
|
|
private:
|
|
FRIEND_TEST(EventsDatabaseTests, test_event_module_id);
|
|
FRIEND_TEST(EventsDatabaseTests, test_record_indexing);
|
|
FRIEND_TEST(EventsDatabaseTests, test_record_range);
|
|
FRIEND_TEST(EventsDatabaseTests, test_record_expiration);
|
|
FRIEND_TEST(EventsDatabaseTests, test_gentable);
|
|
FRIEND_TEST(EventsDatabaseTests, test_expire_check);
|
|
FRIEND_TEST(EventsDatabaseTests, test_optimize);
|
|
FRIEND_TEST(EventsTests, test_event_subscriber_configure);
|
|
friend class DBFakeEventSubscriber;
|
|
friend class BenchmarkEventSubscriber;
|
|
};
|
|
|
|
/**
|
|
* @brief A factory for associating event generators to EventPublisherID%s.
|
|
*
|
|
* This factory both registers new event types and the subscriptions that use
|
|
* them. An EventPublisher is also a factory, the single event factory
|
|
* arbitrates Subscription creation and management for each associated
|
|
* EventPublisher.
|
|
*
|
|
* Since event types may be plugins, they are created using the factory.
|
|
* Since subscriptions may be configured/disabled they are also factory-managed.
|
|
*/
|
|
class EventFactory : private boost::noncopyable {
|
|
public:
|
|
/// Access to the EventFactory instance.
|
|
static EventFactory& getInstance();
|
|
|
|
/**
|
|
* @brief Add an EventPublisher to the factory.
|
|
*
|
|
* The registration is mostly abstracted using osquery's registry.
|
|
*
|
|
* @param pub If for some reason the caller needs access to the
|
|
* EventPublisher instance they can register-by-instance.
|
|
*
|
|
* Access to the EventPublisher instance is not discouraged, but using the
|
|
* EventFactory `getEventPublisher` accessor is encouraged.
|
|
*/
|
|
static Status registerEventPublisher(const PluginRef& pub);
|
|
|
|
/**
|
|
* @brief Add an EventSubscriber to the factory.
|
|
*
|
|
* The registration is mostly abstracted using osquery's registry.
|
|
*/
|
|
template <class T>
|
|
static Status registerEventSubscriber() {
|
|
auto sub = std::make_shared<T>();
|
|
return registerEventSubscriber(sub);
|
|
};
|
|
|
|
/**
|
|
* @brief Add an EventSubscriber to the factory.
|
|
*
|
|
* The registration is mostly abstracted using osquery's registry.
|
|
*
|
|
* @param sub If the caller must access the EventSubscriber instance
|
|
* control may be passed to the registry.
|
|
*
|
|
* Access to the EventSubscriber instance outside of the within-instance
|
|
* table generation method and set of EventCallback%s is discouraged.
|
|
*/
|
|
static Status registerEventSubscriber(const PluginRef& sub);
|
|
|
|
/**
|
|
* @brief Add a SubscriptionContext and EventCallback Subscription to an
|
|
* EventPublisher.
|
|
*
|
|
* Create a Subscription from a given SubscriptionContext and EventCallback
|
|
* and add that Subscription to the EventPublisher associated identifier.
|
|
*
|
|
* @param type_id ID string for an EventPublisher receiving the Subscription.
|
|
* @param name_id ID string for the EventSubscriber.
|
|
* @param sc A SubscriptionContext related to the EventPublisher.
|
|
* @param cb When the EventPublisher fires an event the SubscriptionContext
|
|
* will be evaluated, if the event matches optional specifics in the context
|
|
* this callback function will be called. It should belong to an
|
|
* EventSubscription.
|
|
*
|
|
* @return Was the SubscriptionContext appropriate for the EventPublisher.
|
|
*/
|
|
static Status addSubscription(EventPublisherID& type_id,
|
|
EventSubscriberID& name_id,
|
|
const SubscriptionContextRef& sc,
|
|
EventCallback cb = nullptr);
|
|
|
|
/// Add a Subscription using a caller Subscription instance.
|
|
static Status addSubscription(EventPublisherID& type_id,
|
|
const SubscriptionRef& subscription);
|
|
|
|
/// Get the total number of Subscription%s across ALL EventPublisher%s.
|
|
static size_t numSubscriptions(EventPublisherID& type_id);
|
|
|
|
/// Get the number of EventPublishers.
|
|
static size_t numEventPublishers() {
|
|
return EventFactory::getInstance().event_pubs_.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Halt the EventPublisher run loop.
|
|
*
|
|
* Any EventSubscriber%s with Subscription%s for this EventPublisher will
|
|
* become useless. osquery callers MUST deregister events.
|
|
* EventPublisher%s assume they can hook/trampoline, which requires cleanup.
|
|
* This will tear down and remove the publisher if the run loop did not start.
|
|
* Otherwise it will call end on the publisher and assume the run loop will
|
|
* tear down and remove.
|
|
*
|
|
* @param pub The string label for the EventPublisher.
|
|
*
|
|
* @return Did the EventPublisher deregister cleanly.
|
|
*/
|
|
static Status deregisterEventPublisher(const EventPublisherRef& pub);
|
|
|
|
/// Deregister an EventPublisher by publisher name.
|
|
static Status deregisterEventPublisher(EventPublisherID& type_id);
|
|
|
|
/// Deregister an EventSubscriber by the subscriber name.
|
|
static Status deregisterEventSubscriber(EventSubscriberID& sub);
|
|
|
|
/// Return an instance to a registered EventPublisher.
|
|
static EventPublisherRef getEventPublisher(EventPublisherID& pub);
|
|
|
|
/// Return an instance to a registered EventSubscriber.
|
|
static EventSubscriberRef getEventSubscriber(EventSubscriberID& sub);
|
|
|
|
/// Check if an event subscriber exists.
|
|
static bool exists(EventSubscriberID& sub);
|
|
|
|
/// Return a list of publisher types, these are their registry names.
|
|
static std::vector<std::string> publisherTypes();
|
|
|
|
/// Return a list of subscriber registry names,
|
|
static std::vector<std::string> subscriberNames();
|
|
|
|
/// Set log forwarding by adding a logger receiver.
|
|
static void addForwarder(const std::string& logger);
|
|
|
|
/// Optionally forward events to loggers.
|
|
static void forwardEvent(const std::string& event);
|
|
|
|
/**
|
|
* @brief The event factory, subscribers, and publishers respond to updates.
|
|
*
|
|
* This should be called by the Config instance when configuration data is
|
|
* updated. It is separate from the config parser that takes configuration
|
|
* information specific to events and acts. This allows the event factory
|
|
* to make changes relative to the schedule or packs.
|
|
*/
|
|
static void configUpdate();
|
|
|
|
public:
|
|
/// The dispatched event thread's entry-point (if needed).
|
|
static Status run(EventPublisherID& type_id);
|
|
|
|
/// An initializer's entry-point for spawning all event type run loops.
|
|
static void delay();
|
|
|
|
/// If a static EventPublisher callback wants to fire
|
|
template <typename PUB>
|
|
static void fire(const EventContextRef& ec) {
|
|
auto event_pub = getEventPublisher(getType<PUB>());
|
|
event_pub->fire(ec);
|
|
}
|
|
|
|
/**
|
|
* @brief Return the publisher registry name given a type.
|
|
*
|
|
* Subscriber initialization and runtime static callbacks can lookup the
|
|
* publisher type name, which is the registry plugin name. This allows static
|
|
* callbacks to fire into subscribers.
|
|
*/
|
|
template <class PUB>
|
|
static EventPublisherID getType() {
|
|
auto pub = std::make_shared<PUB>();
|
|
return pub->type();
|
|
}
|
|
|
|
/**
|
|
* @brief End all EventPublisher run loops and deregister.
|
|
*
|
|
* End is NOT the same as deregistration. End will call deregister on all
|
|
* publishers then either join or detach their run loop threads.
|
|
* See EventFactory::deregisterEventPublisher for actions taken during
|
|
* deregistration.
|
|
*
|
|
* @param join if true, threads will be joined
|
|
*/
|
|
static void end(bool join = false);
|
|
|
|
public:
|
|
EventFactory(EventFactory const&) = delete;
|
|
EventFactory& operator=(EventFactory const&) = delete;
|
|
|
|
private:
|
|
/// An EventFactory will exist for the lifetime of the application.
|
|
EventFactory() {}
|
|
~EventFactory() {}
|
|
|
|
private:
|
|
/// Set of registered EventPublisher instances.
|
|
std::map<EventPublisherID, EventPublisherRef> event_pubs_;
|
|
|
|
/// Set of instantiated EventSubscriber subscriptions.
|
|
std::map<EventSubscriberID, EventSubscriberRef> event_subs_;
|
|
|
|
/// Set of running EventPublisher run loop threads.
|
|
std::vector<std::shared_ptr<std::thread>> threads_;
|
|
|
|
/// Set of logger plugins to forward events.
|
|
std::vector<std::string> loggers_;
|
|
|
|
/// Factory publisher state manipulation.
|
|
Mutex factory_lock_;
|
|
};
|
|
|
|
/**
|
|
* @brief Generate OS events of a type (FS, Network, Syscall, ioctl).
|
|
*
|
|
* A 'class' of OS events is abstracted into an EventPublisher responsible for
|
|
* remaining as agile as possible given a known-set of subscriptions.
|
|
*
|
|
* The life cycle of an EventPublisher may include, `setUp`, `configure`, `run`,
|
|
* `tearDown`, and `fire`. `setUp` and `tearDown` happen when osquery starts and
|
|
* stops either as a daemon or interactive shell. `configure` is a pseudo-start
|
|
* called every time a Subscription is added. EventPublisher%s can adjust their
|
|
* scope/agility specific to each added subscription by overriding
|
|
*`addSubscription`, and/or globally in `configure`.
|
|
*
|
|
* Not all EventPublisher%s leverage pure async OS APIs, and most will require a
|
|
* run loop either polling with a timeout on a descriptor or for a change. When
|
|
* osquery initializes the EventFactory will optionally create a thread for each
|
|
* EventPublisher using `run` as the thread's entrypoint. `run` is called in a
|
|
* within-thread loop where returning a FAILED status ends the run loop and
|
|
* shuts down the thread.
|
|
*
|
|
* To opt-out of polling in a thread, consider the following run implementation:
|
|
*
|
|
* @code{.cpp}
|
|
* Status run() { return Status(1, "Not Implemented"); }
|
|
* @endcode
|
|
*
|
|
* The final life cycle component, `fire` will iterate over the EventPublisher
|
|
* Subscription%s and call `shouldFire` for each, using the EventContext fired.
|
|
* The `shouldFire` method should check the subscription-specific selectors and
|
|
* only call the Subscription%'s callback function if the EventContext
|
|
* (thus event) matches.
|
|
*/
|
|
template <typename SC, typename EC>
|
|
class EventPublisher : public EventPublisherPlugin {
|
|
public:
|
|
/// A nested helper typename for the templated SubscriptionContextRef.
|
|
using SCRef = typename std::shared_ptr<SC>;
|
|
/// A nested helper typename for the templated EventContextRef.
|
|
using ECRef = typename std::shared_ptr<EC>;
|
|
|
|
public:
|
|
EventPublisher(){};
|
|
virtual ~EventPublisher() {}
|
|
|
|
/// Up-cast a base EventContext reference to the templated ECRef.
|
|
static ECRef getEventContext(const EventContextRef& ec) {
|
|
return std::static_pointer_cast<EC>(ec);
|
|
};
|
|
|
|
/// Up-cast a base SubscriptionContext reference to the templated SCRef.
|
|
static SCRef getSubscriptionContext(const SubscriptionContextRef& sc) {
|
|
return std::static_pointer_cast<SC>(sc);
|
|
}
|
|
|
|
/// Create a EventContext based on the templated type.
|
|
static ECRef createEventContext() {
|
|
return std::make_shared<EC>();
|
|
}
|
|
|
|
/// Create a SubscriptionContext based on the templated type.
|
|
static SCRef createSubscriptionContext() {
|
|
return std::make_shared<SC>();
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* @brief The internal `fire` phase of publishing.
|
|
*
|
|
* This is a template-generated method that up-casts the generic fired
|
|
* event/subscription contexts, and calls the callback if the event should
|
|
* fire given a subscription.
|
|
*
|
|
* @param sub The SubscriptionContext and optional EventCallback.
|
|
* @param ec The event that was fired.
|
|
*/
|
|
void fireCallback(const SubscriptionRef& sub,
|
|
const EventContextRef& ec) const override {
|
|
auto pub_sc = getSubscriptionContext(sub->context);
|
|
auto pub_ec = getEventContext(ec);
|
|
if (shouldFire(pub_sc, pub_ec) && sub->callback != nullptr) {
|
|
sub->callback(pub_ec, pub_sc);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* @brief The generic `fire` will call `shouldFire` for each Subscription.
|
|
*
|
|
* @param sc A SubscriptionContext with optional specifications for events
|
|
* details.
|
|
* @param ec The event fired with event details.
|
|
*
|
|
* @return should the Subscription%'s EventCallback be called for this event.
|
|
*/
|
|
virtual bool shouldFire(const SCRef& sc, const ECRef& ec) const {
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
FRIEND_TEST(EventsTests, test_event_subscriber_subscribe);
|
|
FRIEND_TEST(EventsTests, test_event_subscriber_context);
|
|
FRIEND_TEST(EventsTests, test_fire_event);
|
|
};
|
|
|
|
/**
|
|
* @brief An interface binding Subscriptions, event response, and table
|
|
*generation.
|
|
*
|
|
* Use the EventSubscriber interface when adding event subscriptions and
|
|
* defining callin functions. The EventCallback is usually a member function
|
|
* for an EventSubscriber. The EventSubscriber interface includes a very
|
|
* important `add` method that abstracts the needed event to backing store
|
|
* interaction.
|
|
*
|
|
* Storing event data in the backing store must match a table spec for queries.
|
|
* Small overheads exist that help query-time indexing and lookups.
|
|
*/
|
|
template <class PUB>
|
|
class EventSubscriber : public EventSubscriberPlugin {
|
|
protected:
|
|
using SCRef = typename PUB::SCRef;
|
|
using ECRef = typename PUB::ECRef;
|
|
|
|
public:
|
|
/**
|
|
* @brief The registry plugin name for the subscriber's publisher.
|
|
*
|
|
* During event factory initialization the subscribers 'peek' at the registry
|
|
* plugin name assigned to publishers. The corresponding publisher name is
|
|
* interpreted as the subscriber's event 'type'.
|
|
*/
|
|
virtual EventPublisherID& getType() const override {
|
|
static EventPublisherID type = EventFactory::getType<PUB>();
|
|
return type;
|
|
};
|
|
|
|
protected:
|
|
/// Helper function to call the publisher's templated subscription generator.
|
|
SCRef createSubscriptionContext() const {
|
|
return PUB::createSubscriptionContext();
|
|
}
|
|
|
|
/**
|
|
* @brief Bind a registered EventSubscriber member function to a Subscription.
|
|
*
|
|
* @param entry A templated EventSubscriber member function.
|
|
* @param sc The subscription context.
|
|
*/
|
|
template <class T, typename E>
|
|
void subscribe(Status (T::*entry)(const std::shared_ptr<E>&, const SCRef&),
|
|
const SCRef& sc) {
|
|
using std::placeholders::_1;
|
|
using std::placeholders::_2;
|
|
using CallbackFunc =
|
|
Status (T::*)(const EventContextRef&, const SubscriptionContextRef&);
|
|
|
|
// Down-cast the pointer to the member function.
|
|
auto base_entry = reinterpret_cast<CallbackFunc>(entry);
|
|
// Up-cast the EventSubscriber to the caller.
|
|
auto sub = dynamic_cast<T*>(this);
|
|
if (base_entry != nullptr && sub != nullptr) {
|
|
// Create a callable through the member function using the instance of the
|
|
// EventSubscriber and a single parameter placeholder (the EventContext).
|
|
auto cb = std::bind(base_entry, sub, _1, _2);
|
|
// Add a subscription using the callable and SubscriptionContext.
|
|
Status stat =
|
|
EventFactory::addSubscription(sub->getType(), sub->getName(), sc, cb);
|
|
if (stat.ok()) {
|
|
subscription_count_++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
explicit EventSubscriber(bool enabled = true)
|
|
: EventSubscriberPlugin(), disabled(!enabled) {}
|
|
virtual ~EventSubscriber() {}
|
|
|
|
protected:
|
|
/**
|
|
* @brief Allow subscriber implementations to default disable themselves.
|
|
*
|
|
* A subscriber may induce latency on a system within the callback routines.
|
|
* Before the initialization and set up is performed the EventFactory can
|
|
* choose to exclude a subscriber if it is not explicitly enabled within
|
|
* the config.
|
|
*
|
|
* EventSubscriber%s that should be default-disabled should set this flag
|
|
* in their constructor or worst case before EventSubsciber::init.
|
|
*/
|
|
bool disabled{false};
|
|
|
|
private:
|
|
friend class EventFactory;
|
|
|
|
private:
|
|
FRIEND_TEST(EventsTests, test_event_sub);
|
|
FRIEND_TEST(EventsTests, test_event_sub_subscribe);
|
|
FRIEND_TEST(EventsTests, test_event_sub_context);
|
|
FRIEND_TEST(EventsTests, test_event_toggle_subscribers);
|
|
};
|
|
|
|
/// Iterate the event publisher registry and create run loops for each using
|
|
/// the event factory.
|
|
void attachEvents();
|
|
}
|