osquery-1/osquery/dispatcher/scheduler.cpp

236 lines
8.2 KiB
C++
Raw Normal View History

/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under both the Apache 2.0 license (found in the
* LICENSE file in the root directory of this source tree) and the GPLv2 (found
* in the COPYING file in the root directory of this source tree).
* You may select, at your option, one of the above-listed licenses.
*/
2015-05-12 06:31:13 +00:00
2018-09-14 15:04:28 +00:00
#include <algorithm>
2014-07-31 00:35:19 +00:00
#include <ctime>
#include <boost/format.hpp>
2018-09-14 15:04:28 +00:00
#include <boost/io/detail/quoted_manip.hpp>
#include <osquery/config/config.h>
#include <osquery/core.h>
#include <osquery/data_logger.h>
#include <osquery/database.h>
#include <osquery/flags.h>
#include <osquery/killswitch.h>
#include <osquery/numeric_monitoring.h>
#include <osquery/process/process.h>
2018-09-14 15:04:28 +00:00
#include <osquery/profiler/profiler.h>
#include <osquery/query.h>
#include <osquery/utils/system/time.h>
2016-03-29 06:37:34 +00:00
#include "osquery/config/parsers/decorators.h"
#include "osquery/dispatcher/scheduler.h"
#include "osquery/sql/sqlite_util.h"
2014-07-31 00:35:19 +00:00
2014-08-15 07:25:30 +00:00
namespace osquery {
2014-07-31 00:35:19 +00:00
FLAG(uint64, schedule_timeout, 0, "Limit the schedule, 0 for no limit");
FLAG(uint64,
schedule_max_drift,
60,
"Max time drift in seconds. Scheduler tries to compensate the drift until "
"the drift exceed this value. After it the drift will be reseted to zero "
"and the compensation process will start from the beginning. It is needed "
"to avoid the problem of endless compensation (which is CPU greedy) after "
"a long SIGSTOP/SIGCONT pause or something similar. Set it to zero to "
"switch off a drift compensation. Default: 60");
FLAG(uint64,
schedule_reload,
300,
"Interval in seconds to reload database arenas");
FLAG(uint64, schedule_epoch, 0, "Epoch for scheduled queries");
HIDDEN_FLAG(bool, enable_monitor, true, "Enable the schedule monitor");
HIDDEN_FLAG(bool,
schedule_reload_sql,
false,
"Reload the SQL implementation during schedule reload");
/// Used to bypass (optimize-out) the set-differential of query results.
DECLARE_bool(events_optimize);
2016-08-31 22:32:20 +00:00
SQLInternal monitor(const std::string& name, const ScheduledQuery& query) {
// Snapshot the performance and times for the worker before running.
auto pid = std::to_string(PlatformProcess::getCurrentPid());
auto r0 = SQL::selectFrom({"resident_size", "user_time", "system_time"},
"processes",
"pid",
EQUALS,
pid);
auto t0 = getUnixTime();
Config::get().recordQueryStart(name);
SQLInternal sql(query.query, true);
// Snapshot the performance after, and compare.
auto t1 = getUnixTime();
auto r1 = SQL::selectFrom({"resident_size", "user_time", "system_time"},
"processes",
"pid",
EQUALS,
pid);
if (r0.size() > 0 && r1.size() > 0) {
// Always called while processes table is working.
Config::get().recordQueryPerformance(name, t1 - t0, r0[0], r1[0]);
}
return sql;
}
2018-09-14 15:04:28 +00:00
Status launchQuery(const std::string& name, const ScheduledQuery& query) {
2015-04-27 21:57:04 +00:00
// Execute the scheduled query and create a named query object.
LOG(INFO) << "Executing scheduled query " << name << ": " << query.query;
2016-03-29 06:37:34 +00:00
runDecorators(DECORATE_ALWAYS);
auto sql = monitor(name, query);
2015-01-02 05:55:10 +00:00
if (!sql.ok()) {
LOG(ERROR) << "Error executing scheduled query " << name << ": "
<< sql.getMessageString();
return Status::failure("Error executing scheduled query");
2015-01-02 05:55:10 +00:00
}
2015-04-27 21:57:04 +00:00
// Fill in a host identifier fields based on configuration or availability.
std::string ident = getHostIdentifier();
2015-04-27 21:57:04 +00:00
// A query log item contains an optional set of differential results or
// a copy of the most-recent execution alongside some query metadata.
QueryLogItem item;
item.name = name;
item.identifier = ident;
item.columns = sql.columns();
2015-04-27 21:57:04 +00:00
item.time = osquery::getUnixTime();
item.epoch = FLAGS_schedule_epoch;
2015-04-27 21:57:04 +00:00
item.calendar_time = osquery::getAsciiTime();
2016-03-29 06:37:34 +00:00
getDecorations(item.decorations);
2015-04-27 21:57:04 +00:00
if (query.options.count("snapshot") && query.options.at("snapshot")) {
// This is a snapshot query, emit results with a differential or state.
item.snapshot_results = std::move(sql.rows());
2015-04-27 21:57:04 +00:00
logSnapshotQuery(item);
return Status::success();
2015-04-27 21:57:04 +00:00
}
// Create a database-backed set of query results.
auto dbQuery = Query(name, query);
// Comparisons and stores must include escaped data.
sql.escapeResults();
2016-08-31 22:32:20 +00:00
Status status;
DiffResults& diff_results = item.results;
2015-04-27 21:57:04 +00:00
// Add this execution's set of results to the database-tracked named query.
// We can then ask for a differential from the last time this named query
// was executed by exact matching each row.
if (!FLAGS_events_optimize || !sql.eventBased()) {
status = dbQuery.addNewResults(
std::move(sql.rows()), item.epoch, item.counter, diff_results);
2016-08-31 22:32:20 +00:00
if (!status.ok()) {
std::string line = "Error adding new results to database for query " +
name + ": " + status.what();
2016-08-31 22:32:20 +00:00
LOG(ERROR) << line;
// If the database is not available then the daemon cannot continue.
Initializer::requestShutdown(EXIT_CATASTROPHIC, line);
}
} else {
diff_results.added = std::move(sql.rows());
2015-01-02 05:55:10 +00:00
}
if (query.options.count("removed") && !query.options.at("removed")) {
diff_results.removed.clear();
}
if (diff_results.added.empty() && diff_results.removed.empty()) {
2015-01-02 05:55:10 +00:00
// No diff results or events to emit.
return status;
2015-01-02 05:55:10 +00:00
}
VLOG(1) << "Found results for query: " << name;
2015-04-27 21:57:04 +00:00
status = logQueryLogItem(item);
if (!status.ok()) {
// If log directory is not available, then the daemon shouldn't continue.
std::string error = "Error logging the results of query: " + name + ": " +
status.toString();
LOG(ERROR) << error;
Initializer::requestShutdown(EXIT_CATASTROPHIC, error);
2014-07-31 00:35:19 +00:00
}
return status;
}
2015-05-06 00:09:07 +00:00
void SchedulerRunner::start() {
2015-11-02 18:33:20 +00:00
// Start the counter at the second.
auto i = osquery::getUnixTime();
for (; (timeout_ == 0) || (i <= timeout_); ++i) {
auto start_time_point = std::chrono::steady_clock::now();
Config::get().scheduledQueries(
([&i](std::string name, const ScheduledQuery& query) {
[fix #1390] query pack re-org This commit contains the features specified in #1390 as well as a refactoring of the general osquery configuration code. The API for the config plugins hasn't changed, although now there's a `genPack` method that config plugins can implement. If a plugin doesn't implement `genPack`, then the map<string, string> format cannot be used. The default config plugin, the filesystem plugin, now implements `genPack`, so existing query packs code will continue to work as it always has. Now many other config plugins can implement custom pack handling for what makes sense in their context. `genPacks` is not a pure virtual, so it doesn't have to be implemented in your plugin if you don't want to use it. Also, more importantly, all config plugins can use the standard inline pack format if they want to use query packs. Which is awesome. For more information, refer to #1390, the documentation and the doxygen comments included with this pull requests, as well as the following example config which is now supported, regardless of what config plugin you're using: ```json { "options": { "enable_monitor": "true" }, "packs": { "core_os_monitoring": { "version": "1.4.5", "discovery": [ "select pid from processes where name like '%osqueryd%';" ], "queries": { "kernel_modules": { "query": "SELECT name, size FROM kernel_modules;", "interval": 600 }, "system_controls": { "query": "SELECT * FROM system_controls;", "interval": 600, "snapshot": true, }, "usb_devices": { "query": "SELECT * FROM usb_devices;", "interval": 600 } } }, "osquery_internal_info": { "version": "1.4.5", "discovery": [ "select pid from processes where name like '%osqueryd%';" ], "queries": { "info": { "query": "select i.*, p.resident_size, p.user_time, p.system_time, time.minutes as counter from osquery_info i, processes p, time where p.pid = i.pid;", "interval": 60, "snapshot": true }, "registry": { "query": "SELECT * FROM osquery_registry;", "interval": 600, "snapshot": true }, "schedule": { "query": "select name, interval, executions, output_size, wall_time, (user_time/executions) as avg_user_time, (system_time/executions) as avg_system_time, average_memory from osquery_schedule;", "interval": 60, "snapshot": true } } } } } ``` The `osquery_packs` table was modified to remove the superfluous columns which could already have been found in `osquery_schedule`. Two more columns were added in their place, representing stats about pack's discovery query execution history. Notably, the internal API for the `osquery::Config` class has changed rather dramatically as apart of the refactoring. We think this is an improvement. While strictly adhering to the osquery config plugin interface will have avoided any compatibility errors, advanced users may notice compilation errors if they access config data directly. All internal users of the config have obviously been updated. Yet another reason to merge your code into mainline; we update it for you when we refactor!
2015-08-19 20:27:49 +00:00
if (query.splayed_interval > 0 && i % query.splayed_interval == 0) {
TablePlugin::kCacheInterval = query.splayed_interval;
TablePlugin::kCacheStep = i;
2018-09-14 15:04:28 +00:00
{
CodeProfiler codeProfiler(
(boost::format("scheduler.executing_query.%s") % name).str());
const auto status = launchQuery(name, query);
codeProfiler.appendName(status.ok() ? ".success" : ".failure");
};
[fix #1390] query pack re-org This commit contains the features specified in #1390 as well as a refactoring of the general osquery configuration code. The API for the config plugins hasn't changed, although now there's a `genPack` method that config plugins can implement. If a plugin doesn't implement `genPack`, then the map<string, string> format cannot be used. The default config plugin, the filesystem plugin, now implements `genPack`, so existing query packs code will continue to work as it always has. Now many other config plugins can implement custom pack handling for what makes sense in their context. `genPacks` is not a pure virtual, so it doesn't have to be implemented in your plugin if you don't want to use it. Also, more importantly, all config plugins can use the standard inline pack format if they want to use query packs. Which is awesome. For more information, refer to #1390, the documentation and the doxygen comments included with this pull requests, as well as the following example config which is now supported, regardless of what config plugin you're using: ```json { "options": { "enable_monitor": "true" }, "packs": { "core_os_monitoring": { "version": "1.4.5", "discovery": [ "select pid from processes where name like '%osqueryd%';" ], "queries": { "kernel_modules": { "query": "SELECT name, size FROM kernel_modules;", "interval": 600 }, "system_controls": { "query": "SELECT * FROM system_controls;", "interval": 600, "snapshot": true, }, "usb_devices": { "query": "SELECT * FROM usb_devices;", "interval": 600 } } }, "osquery_internal_info": { "version": "1.4.5", "discovery": [ "select pid from processes where name like '%osqueryd%';" ], "queries": { "info": { "query": "select i.*, p.resident_size, p.user_time, p.system_time, time.minutes as counter from osquery_info i, processes p, time where p.pid = i.pid;", "interval": 60, "snapshot": true }, "registry": { "query": "SELECT * FROM osquery_registry;", "interval": 600, "snapshot": true }, "schedule": { "query": "select name, interval, executions, output_size, wall_time, (user_time/executions) as avg_user_time, (system_time/executions) as avg_system_time, average_memory from osquery_schedule;", "interval": 60, "snapshot": true } } } } } ``` The `osquery_packs` table was modified to remove the superfluous columns which could already have been found in `osquery_schedule`. Two more columns were added in their place, representing stats about pack's discovery query execution history. Notably, the internal API for the `osquery::Config` class has changed rather dramatically as apart of the refactoring. We think this is an improvement. While strictly adhering to the osquery config plugin interface will have avoided any compatibility errors, advanced users may notice compilation errors if they access config data directly. All internal users of the config have obviously been updated. Yet another reason to merge your code into mainline; we update it for you when we refactor!
2015-08-19 20:27:49 +00:00
}
}));
2016-03-29 06:37:34 +00:00
// Configuration decorators run on 60 second intervals only.
if ((i % 60) == 0) {
2016-03-29 06:37:34 +00:00
runDecorators(DECORATE_INTERVAL, i);
}
if (FLAGS_schedule_reload > 0 && (i % FLAGS_schedule_reload) == 0) {
if (FLAGS_schedule_reload_sql) {
SQLiteDBManager::resetPrimary();
}
resetDatabase();
}
2017-04-06 22:57:44 +00:00
// GLog is not re-entrant, so logs must be flushed in a dedicated thread.
if ((i % 3) == 0) {
relayStatusLogs(true);
}
auto loop_step_duration =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start_time_point);
if (loop_step_duration + time_drift_ < interval_) {
2018-08-02 15:57:02 +00:00
pause(std::chrono::milliseconds(interval_ - loop_step_duration -
time_drift_));
time_drift_ = std::chrono::milliseconds::zero();
} else {
time_drift_ += loop_step_duration - interval_;
if (time_drift_ > max_time_drift_) {
// giving up
time_drift_ = std::chrono::milliseconds::zero();
}
}
if (interrupted()) {
break;
}
}
}
std::chrono::milliseconds SchedulerRunner::getCurrentTimeDrift() const
noexcept {
return time_drift_;
}
void startScheduler() {
startScheduler(static_cast<unsigned long int>(FLAGS_schedule_timeout), 1);
}
void startScheduler(unsigned long int timeout, size_t interval) {
Dispatcher::addService(std::make_shared<SchedulerRunner>(
timeout, interval, std::chrono::seconds{FLAGS_schedule_max_drift}));
2014-07-31 00:35:19 +00:00
}
} // namespace osquery