mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 09:58:54 +00:00
Process Operations - osquery/core Integration (#2087)
This integrates the process abstraction operations within osquery core.
This commit is contained in:
parent
484cf9c919
commit
15d1455957
@ -14,9 +14,12 @@
|
||||
#include <thread>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <syslog.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifndef WIN32
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@ -31,6 +34,7 @@
|
||||
#include <osquery/registry.h>
|
||||
|
||||
#include "osquery/core/watcher.h"
|
||||
#include "osquery/core/process.h"
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#include <sys/resource.h>
|
||||
@ -97,13 +101,16 @@ enum {
|
||||
namespace {
|
||||
extern "C" {
|
||||
static inline bool hasWorkerVariable() {
|
||||
return (getenv("OSQUERY_WORKER") != nullptr);
|
||||
return ::osquery::getEnvVar("OSQUERY_WORKER").is_initialized();
|
||||
}
|
||||
|
||||
volatile std::sig_atomic_t kHandledSignal{0};
|
||||
|
||||
static inline bool isWatcher() { return (osquery::Watcher::getWorker() > 0); }
|
||||
static inline bool isWatcher() {
|
||||
return (osquery::Watcher::getWorker().isValid());
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
void signalHandler(int num) {
|
||||
// Inform exit status of main threads blocked by service joins.
|
||||
if (kHandledSignal == 0) {
|
||||
@ -157,6 +164,7 @@ void signalHandler(int num) {
|
||||
// managed extension processes.
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,7 +172,7 @@ namespace osquery {
|
||||
|
||||
using chrono_clock = std::chrono::high_resolution_clock;
|
||||
|
||||
#ifndef __APPLE__
|
||||
#if !defined(__APPLE__) && !defined(WIN32)
|
||||
CLI_FLAG(bool, daemonize, false, "Run as daemon (osqueryd only)");
|
||||
#endif
|
||||
|
||||
@ -226,7 +234,11 @@ Initializer::Initializer(int& argc, char**& argv, ToolType tool)
|
||||
try {
|
||||
boost::filesystem::path::codecvt();
|
||||
} catch (const std::runtime_error& e) {
|
||||
#ifdef WIN32
|
||||
setlocale(LC_ALL, "C");
|
||||
#else
|
||||
setenv("LC_ALL", "C", 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
// osquery implements a custom help/usage output.
|
||||
@ -285,6 +297,7 @@ Initializer::Initializer(int& argc, char**& argv, ToolType tool)
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
// All tools handle the same set of signals.
|
||||
// If a daemon process is a watchdog the signal is passed to the worker,
|
||||
// unless the worker has not yet started.
|
||||
@ -294,6 +307,7 @@ Initializer::Initializer(int& argc, char**& argv, ToolType tool)
|
||||
std::signal(SIGHUP, signalHandler);
|
||||
std::signal(SIGALRM, signalHandler);
|
||||
std::signal(SIGUSR1, signalHandler);
|
||||
#endif
|
||||
|
||||
// If the caller is checking configuration, disable the watchdog/worker.
|
||||
if (FLAGS_config_check) {
|
||||
@ -304,7 +318,8 @@ Initializer::Initializer(int& argc, char**& argv, ToolType tool)
|
||||
initStatusLogger(binary_);
|
||||
if (tool != OSQUERY_EXTENSION) {
|
||||
if (isWorker()) {
|
||||
VLOG(1) << "osquery worker initialized [watcher=" << getppid() << "]";
|
||||
VLOG(1) << "osquery worker initialized [watcher="
|
||||
<< PlatformProcess::getLauncherProcess()->pid() << "]";
|
||||
} else {
|
||||
VLOG(1) << "osquery initialized [version=" << kVersion << "]";
|
||||
}
|
||||
@ -319,7 +334,7 @@ void Initializer::initDaemon() const {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
#if !defined(__APPLE__) && !defined(WIN32)
|
||||
// OS X uses launchd to daemonize.
|
||||
if (osquery::FLAGS_daemonize) {
|
||||
if (daemon(0, 0) == -1) {
|
||||
@ -328,9 +343,11 @@ void Initializer::initDaemon() const {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef WIN32
|
||||
// Print the version to SYSLOG.
|
||||
syslog(
|
||||
LOG_NOTICE, "%s started [version=%s]", binary_.c_str(), kVersion.c_str());
|
||||
#endif
|
||||
|
||||
// Check if /var/osquery exists
|
||||
if ((Flag::isDefault("pidfile") || Flag::isDefault("database_path")) &&
|
||||
@ -350,7 +367,8 @@ void Initializer::initDaemon() const {
|
||||
FLAGS_watchdog_level >= WATCHDOG_LEVEL_DEFAULT &&
|
||||
FLAGS_watchdog_level != WATCHDOG_LEVEL_DEBUG) {
|
||||
// Set CPU scheduling I/O limits.
|
||||
setpriority(PRIO_PGRP, 0, 10);
|
||||
setToBackgroundPriority();
|
||||
|
||||
#ifdef __linux__
|
||||
// Using: ioprio_set(IOPRIO_WHO_PGRP, 0, IOPRIO_CLASS_IDLE);
|
||||
syscall(SYS_ioprio_set, IOPRIO_WHO_PGRP, 0, IOPRIO_CLASS_IDLE);
|
||||
@ -413,7 +431,8 @@ void Initializer::initWorker(const std::string& name) const {
|
||||
|
||||
// Start a 'watcher watcher' thread to exit the process if the watcher exits.
|
||||
// In this case the parent process is called the 'watcher' process.
|
||||
Dispatcher::addService(std::make_shared<WatcherWatcherRunner>(getppid()));
|
||||
Dispatcher::addService(std::make_shared<WatcherWatcherRunner>(
|
||||
PlatformProcess::getLauncherProcess()));
|
||||
}
|
||||
|
||||
void Initializer::initWorkerWatcher(const std::string& name) const {
|
||||
@ -452,7 +471,7 @@ void Initializer::initActivePlugin(const std::string& type,
|
||||
}
|
||||
// The plugin is not local and is not active, wait and retry.
|
||||
delay += kExtensionInitializeLatencyUS;
|
||||
::usleep(kExtensionInitializeLatencyUS);
|
||||
sleepFor(kExtensionInitializeLatencyUS);
|
||||
} while (delay < timeout);
|
||||
|
||||
LOG(ERROR) << "Cannot activate " << name << " " << type
|
||||
@ -531,12 +550,14 @@ void Initializer::start() const {
|
||||
}
|
||||
initLogger(binary_);
|
||||
|
||||
#ifndef WIN32
|
||||
// Initialize the distributed plugin, if necessary
|
||||
if (!FLAGS_disable_distributed) {
|
||||
if (Registry::exists("distributed", FLAGS_distributed_plugin)) {
|
||||
initActivePlugin("distributed", FLAGS_distributed_plugin);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start event threads.
|
||||
osquery::attachEvents();
|
||||
@ -559,7 +580,9 @@ void Initializer::requestShutdown(int retcode) {
|
||||
// Stop thrift services/clients/and their thread pools.
|
||||
kExitCode = retcode;
|
||||
if (std::this_thread::get_id() != kMainThreadId) {
|
||||
#ifndef WIN32
|
||||
raise(SIGUSR1);
|
||||
#endif
|
||||
} else {
|
||||
// The main thread is requesting a shutdown, meaning in almost every case
|
||||
// it is NOT waiting for a shutdown.
|
||||
@ -572,3 +595,4 @@ void Initializer::requestShutdown(int retcode) {
|
||||
|
||||
void Initializer::shutdown(int retcode) { ::exit(retcode); }
|
||||
}
|
||||
|
||||
|
@ -56,13 +56,13 @@ std::shared_ptr<PlatformProcess> PlatformProcess::getLauncherProcess() {
|
||||
}
|
||||
|
||||
std::shared_ptr<PlatformProcess> PlatformProcess::launchWorker(
|
||||
const std::string& exec_path, const std::string& name) {
|
||||
const std::string& exec_path, int argc, char** argv) {
|
||||
auto worker_pid = ::fork();
|
||||
if (worker_pid < 0) {
|
||||
return std::shared_ptr<PlatformProcess>();
|
||||
} else if (worker_pid == 0) {
|
||||
setEnvVar("OSQUERY_WORKER", std::to_string(::getpid()).c_str());
|
||||
::execle(exec_path.c_str(), name.c_str(), nullptr, ::environ);
|
||||
::execve(exec_path.c_str(), argv, ::environ);
|
||||
|
||||
// Code should never reach this point
|
||||
LOG(ERROR) << "osqueryd could not start worker process";
|
||||
|
@ -27,6 +27,7 @@
|
||||
namespace osquery {
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
/// Unfortunately, pid_t is not defined in Windows, however, DWORD is the
|
||||
/// most appropriate alternative since process ID on Windows are stored in
|
||||
/// a DWORD.
|
||||
@ -57,8 +58,7 @@ enum ProcessState {
|
||||
* @brief Platform-agnostic process object.
|
||||
*
|
||||
* PlatformProcess is a specialized, platform-agnostic class that handles the
|
||||
* process operation needs
|
||||
* of osquery.
|
||||
* process operation needs of osquery.
|
||||
*/
|
||||
class PlatformProcess : private boost::noncopyable {
|
||||
public:
|
||||
@ -101,11 +101,12 @@ class PlatformProcess : private boost::noncopyable {
|
||||
/**
|
||||
* @brief Creates a new worker process.
|
||||
*
|
||||
* Launches a worker process given a worker executable path and a worker name.
|
||||
* Any double quotes in the worker name will be stripped out.
|
||||
* Launches a worker process given a worker executable path, number of
|
||||
* arguments, and an array of arguments. All double quotes within each entry
|
||||
* in the array of arguments will be supplanted with a preceding blackslash.
|
||||
*/
|
||||
static std::shared_ptr<PlatformProcess> launchWorker(
|
||||
const std::string& exec_path, const std::string& name);
|
||||
const std::string& exec_path, int argc, char** argv);
|
||||
|
||||
/**
|
||||
* @brief Creates a new extension process.
|
||||
|
@ -34,8 +34,10 @@ extern const char *kOsqueryTestModuleName;
|
||||
|
||||
/// These are the expected arguments for our test worker process.
|
||||
extern const char *kExpectedWorkerArgs[];
|
||||
extern const size_t kExpectedWorkerArgsCount;
|
||||
|
||||
/// These are the expected arguments for our test extensions process.
|
||||
extern const char *kExpectedExtensionArgs[];
|
||||
extern const size_t kExpectedExtensionArgsCount;
|
||||
}
|
||||
|
||||
|
@ -105,16 +105,20 @@ TEST_F(ProcessTests, test_getpid) {
|
||||
TEST_F(ProcessTests, test_envVar) {
|
||||
auto val = getEnvVar("GTEST_OSQUERY");
|
||||
EXPECT_FALSE(val);
|
||||
EXPECT_FALSE(val.is_initialized());
|
||||
|
||||
EXPECT_TRUE(setEnvVar("GTEST_OSQUERY", "true"));
|
||||
|
||||
val = getEnvVar("GTEST_OSQUERY");
|
||||
EXPECT_FALSE(!val);
|
||||
EXPECT_TRUE(val.is_initialized());
|
||||
EXPECT_EQ(*val, "true");
|
||||
|
||||
EXPECT_TRUE(unsetEnvVar("GTEST_OSQUERY"));
|
||||
|
||||
val = getEnvVar("GTEST_OSQUERY");
|
||||
EXPECT_FALSE(val);
|
||||
EXPECT_FALSE(val.is_initialized());
|
||||
}
|
||||
|
||||
TEST_F(ProcessTests, test_launchExtension) {
|
||||
@ -136,9 +140,23 @@ TEST_F(ProcessTests, test_launchExtension) {
|
||||
|
||||
TEST_F(ProcessTests, test_launchWorker) {
|
||||
{
|
||||
std::vector<char *> argv;
|
||||
for (size_t i = 0; i < kExpectedWorkerArgsCount; i++) {
|
||||
char *entry = new char[strlen(kExpectedWorkerArgs[i]) + 1];
|
||||
EXPECT_NE(entry, nullptr);
|
||||
memset(entry, '\0', strlen(kExpectedWorkerArgs[i]) + 1);
|
||||
memcpy(entry, kExpectedWorkerArgs[i], strlen(kExpectedWorkerArgs[i]));
|
||||
argv.push_back(entry);
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
std::shared_ptr<osquery::PlatformProcess> process =
|
||||
osquery::PlatformProcess::launchWorker(kProcessTestExecPath.c_str(),
|
||||
kExpectedWorkerArgs[0]);
|
||||
osquery::PlatformProcess::launchWorker(
|
||||
kProcessTestExecPath.c_str(), kExpectedWorkerArgsCount, &argv[0]);
|
||||
for (size_t i = 0; i < argv.size(); i++) {
|
||||
delete argv[i];
|
||||
}
|
||||
|
||||
EXPECT_TRUE(process.get());
|
||||
|
||||
int code = 0;
|
||||
@ -164,19 +182,6 @@ TEST_F(ProcessTests, test_launchExtensionQuotes) {
|
||||
EXPECT_EQ(code, EXTENSION_SUCCESS_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ProcessTests, test_launchWorkerQuotes) {
|
||||
{
|
||||
std::shared_ptr<osquery::PlatformProcess> process =
|
||||
osquery::PlatformProcess::launchWorker(kProcessTestExecPath.c_str(),
|
||||
"worker\"-test");
|
||||
EXPECT_TRUE(process.get());
|
||||
|
||||
int code = 0;
|
||||
EXPECT_TRUE(getProcessExitCode(*process, code));
|
||||
EXPECT_EQ(code, WORKER_SUCCESS_CODE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,10 @@
|
||||
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
|
||||
#ifndef WIN32
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@ -21,6 +24,7 @@
|
||||
#include <osquery/sql.h>
|
||||
|
||||
#include "osquery/core/watcher.h"
|
||||
#include "osquery/core/process.h"
|
||||
|
||||
extern char** environ;
|
||||
|
||||
@ -70,9 +74,9 @@ void Watcher::resetExtensionCounters(const std::string& extension,
|
||||
state.last_respawn_time = respawn_time;
|
||||
}
|
||||
|
||||
std::string Watcher::getExtensionPath(pid_t child) {
|
||||
std::string Watcher::getExtensionPath(const PlatformProcess& child) {
|
||||
for (const auto& extension : extensions()) {
|
||||
if (extension.second == child) {
|
||||
if (*extension.second == child) {
|
||||
return extension.first;
|
||||
}
|
||||
}
|
||||
@ -85,8 +89,8 @@ void Watcher::removeExtensionPath(const std::string& extension) {
|
||||
instance().extension_states_.erase(extension);
|
||||
}
|
||||
|
||||
PerformanceState& Watcher::getState(pid_t child) {
|
||||
if (child == instance().worker_) {
|
||||
PerformanceState& Watcher::getState(const PlatformProcess& child) {
|
||||
if (child == *instance().worker_) {
|
||||
return instance().state_;
|
||||
} else {
|
||||
return instance().extension_states_[getExtensionPath(child)];
|
||||
@ -97,13 +101,14 @@ PerformanceState& Watcher::getState(const std::string& extension) {
|
||||
return instance().extension_states_[extension];
|
||||
}
|
||||
|
||||
void Watcher::setExtension(const std::string& extension, pid_t child) {
|
||||
void Watcher::setExtension(const std::string& extension,
|
||||
const std::shared_ptr<PlatformProcess>& child) {
|
||||
WatcherLocker locker;
|
||||
instance().extensions_[extension] = child;
|
||||
}
|
||||
|
||||
void Watcher::reset(pid_t child) {
|
||||
if (child == instance().worker_) {
|
||||
void Watcher::reset(const PlatformProcess& child) {
|
||||
if (child == *instance().worker_) {
|
||||
instance().worker_ = 0;
|
||||
resetWorkerCounters(0);
|
||||
return;
|
||||
@ -111,15 +116,15 @@ void Watcher::reset(pid_t child) {
|
||||
|
||||
// If it was not the worker pid then find the extension name to reset.
|
||||
for (const auto& extension : extensions()) {
|
||||
if (extension.second == child) {
|
||||
setExtension(extension.first, 0);
|
||||
if (*extension.second == child) {
|
||||
setExtension(extension.first, std::make_shared<PlatformProcess>());
|
||||
resetExtensionCounters(extension.first, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Watcher::addExtensionPath(const std::string& path) {
|
||||
setExtension(path, 0);
|
||||
setExtension(path, std::make_shared<PlatformProcess>());
|
||||
resetExtensionCounters(path, 0);
|
||||
}
|
||||
|
||||
@ -132,7 +137,7 @@ bool Watcher::hasManagedExtensions() {
|
||||
// Setting this counter to 0 will prevent the worker from waiting for missing
|
||||
// dependent config plugins. Otherwise, its existence, will cause a worker to
|
||||
// wait for missing plugins to broadcast from managed extensions.
|
||||
return (getenv("OSQUERY_EXTENSIONS") != nullptr);
|
||||
return getEnvVar("OSQUERY_EXTENSIONS").is_initialized();
|
||||
}
|
||||
|
||||
bool WatcherRunner::ok() {
|
||||
@ -142,7 +147,7 @@ bool WatcherRunner::ok() {
|
||||
return false;
|
||||
}
|
||||
// Watcher is OK to run if a worker or at least one extension exists.
|
||||
return (Watcher::getWorker() >= 0 || Watcher::hasManagedExtensions());
|
||||
return (Watcher::getWorker().isValid() || Watcher::hasManagedExtensions());
|
||||
}
|
||||
|
||||
void WatcherRunner::start() {
|
||||
@ -163,7 +168,7 @@ void WatcherRunner::start() {
|
||||
// Loop over every managed extension and check sanity.
|
||||
std::vector<std::string> failing_extensions;
|
||||
for (const auto& extension : Watcher::extensions()) {
|
||||
if (!watch(extension.second)) {
|
||||
if (!watch(*extension.second)) {
|
||||
if (!createExtension(extension.first)) {
|
||||
failing_extensions.push_back(extension.first);
|
||||
}
|
||||
@ -177,18 +182,19 @@ void WatcherRunner::start() {
|
||||
} while (!interrupted() && ok());
|
||||
}
|
||||
|
||||
bool WatcherRunner::watch(pid_t child) {
|
||||
bool WatcherRunner::watch(const PlatformProcess& child) const {
|
||||
int status = 0;
|
||||
pid_t result = waitpid(child, &status, WNOHANG);
|
||||
|
||||
ProcessState result = checkChildProcessStatus(child, status);
|
||||
if (Watcher::fatesBound()) {
|
||||
// A signal was handled while the watcher was watching.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (child == 0 || result < 0) {
|
||||
if (!child.isValid() || result == PROCESS_ERROR) {
|
||||
// Worker does not exist or never existed.
|
||||
return false;
|
||||
} else if (result == 0) {
|
||||
} else if (result == PROCESS_STILL_ALIVE) {
|
||||
// If the inspect finds problems it will stop/restart the worker.
|
||||
if (!isChildSane(child)) {
|
||||
stopChild(child);
|
||||
@ -197,22 +203,24 @@ bool WatcherRunner::watch(pid_t child) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
if (result == PROCESS_EXITED) {
|
||||
// If the worker process existed, store the exit code.
|
||||
Watcher::instance().worker_status_ = WEXITSTATUS(status);
|
||||
Watcher::instance().worker_status_ = status;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WatcherRunner::stopChild(pid_t child) {
|
||||
kill(child, SIGKILL);
|
||||
void WatcherRunner::stopChild(const PlatformProcess& child) const {
|
||||
child.kill();
|
||||
|
||||
// Clean up the defunct (zombie) process.
|
||||
waitpid(-1, 0, WNOHANG);
|
||||
cleanupDefunctProcesses();
|
||||
}
|
||||
|
||||
bool WatcherRunner::isChildSane(pid_t child) {
|
||||
auto rows = SQL::selectAllFrom("processes", "pid", EQUALS, INTEGER(child));
|
||||
bool WatcherRunner::isChildSane(const PlatformProcess& child) const {
|
||||
auto rows =
|
||||
SQL::selectAllFrom("processes", "pid", EQUALS, INTEGER(child.pid()));
|
||||
if (rows.size() == 0) {
|
||||
// Could not find worker process?
|
||||
return false;
|
||||
@ -270,7 +278,7 @@ bool WatcherRunner::isChildSane(pid_t child) {
|
||||
|
||||
// Only make a decision about the child sanity if it is still the watcher's
|
||||
// child. It's possible for the child to die, and its pid reused.
|
||||
if (parent != getpid()) {
|
||||
if (parent != PlatformProcess::getCurrentProcess()->pid()) {
|
||||
// The child's parent is not the watcher.
|
||||
Watcher::reset(child);
|
||||
// Do not stop or call the child insane, since it is not our child.
|
||||
@ -279,13 +287,13 @@ bool WatcherRunner::isChildSane(pid_t child) {
|
||||
|
||||
if (sustained_latency > 0 &&
|
||||
sustained_latency * iv >= getWorkerLimit(LATENCY_LIMIT)) {
|
||||
LOG(WARNING) << "osqueryd worker (" << child
|
||||
LOG(WARNING) << "osqueryd worker (" << child.pid()
|
||||
<< ") system performance limits exceeded";
|
||||
return false;
|
||||
}
|
||||
// Check if the private memory exceeds a memory limit.
|
||||
if (footprint > 0 && footprint > getWorkerLimit(MEMORY_LIMIT) * 1024 * 1024) {
|
||||
LOG(WARNING) << "osqueryd worker (" << child
|
||||
LOG(WARNING) << "osqueryd worker (" << child.pid()
|
||||
<< ") memory limits exceeded: " << footprint;
|
||||
return false;
|
||||
}
|
||||
@ -295,6 +303,7 @@ bool WatcherRunner::isChildSane(pid_t child) {
|
||||
if (use_worker_) {
|
||||
relayStatusLogs();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -315,7 +324,11 @@ void WatcherRunner::createWorker() {
|
||||
}
|
||||
|
||||
// Get the path of the current process.
|
||||
auto qd = SQL::selectAllFrom("processes", "pid", EQUALS, INTEGER(getpid()));
|
||||
auto qd =
|
||||
SQL::selectAllFrom("processes",
|
||||
"pid",
|
||||
EQUALS,
|
||||
INTEGER(PlatformProcess::getCurrentProcess()->pid()));
|
||||
if (qd.size() != 1 || qd[0].count("path") == 0 || qd[0]["path"].size() == 0) {
|
||||
LOG(ERROR) << "osquery watcher cannot determine process path for worker";
|
||||
Initializer::requestShutdown(EXIT_FAILURE);
|
||||
@ -325,7 +338,7 @@ void WatcherRunner::createWorker() {
|
||||
// Set an environment signaling to potential plugin-dependent workers to wait
|
||||
// for extensions to broadcast.
|
||||
if (Watcher::hasManagedExtensions()) {
|
||||
setenv("OSQUERY_EXTENSIONS", "true", 1);
|
||||
setEnvVar("OSQUERY_EXTENSIONS", "true");
|
||||
}
|
||||
|
||||
// Get the complete path of the osquery process binary.
|
||||
@ -339,26 +352,18 @@ void WatcherRunner::createWorker() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto worker_pid = fork();
|
||||
if (worker_pid < 0) {
|
||||
auto worker = PlatformProcess::launchWorker(exec_path.string(), argc_, argv_);
|
||||
if (worker == nullptr) {
|
||||
// Unrecoverable error, cannot create a worker process.
|
||||
LOG(ERROR) << "osqueryd could not create a worker process";
|
||||
Initializer::shutdown(EXIT_FAILURE);
|
||||
return;
|
||||
} else if (worker_pid == 0) {
|
||||
// This is the new worker process, no watching needed.
|
||||
setenv("OSQUERY_WORKER", std::to_string(getpid()).c_str(), 1);
|
||||
execve(exec_path.string().c_str(), argv_, environ);
|
||||
// Code should never reach this point.
|
||||
LOG(ERROR) << "osqueryd could not start worker process";
|
||||
Initializer::shutdown(EXIT_CATASTROPHIC);
|
||||
return;
|
||||
}
|
||||
|
||||
Watcher::setWorker(worker_pid);
|
||||
Watcher::setWorker(worker);
|
||||
Watcher::resetWorkerCounters(getUnixTime());
|
||||
VLOG(1) << "osqueryd watcher (" << getpid() << ") executing worker ("
|
||||
<< worker_pid << ")";
|
||||
VLOG(1) << "osqueryd watcher (" << PlatformProcess::getCurrentProcess()->pid()
|
||||
<< ") executing worker (" << worker->pid() << ")";
|
||||
}
|
||||
|
||||
bool WatcherRunner::createExtension(const std::string& extension) {
|
||||
@ -382,44 +387,34 @@ bool WatcherRunner::createExtension(const std::string& extension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ext_pid = fork();
|
||||
if (ext_pid < 0) {
|
||||
auto ext_process =
|
||||
PlatformProcess::launchExtension(exec_path.string(),
|
||||
extension,
|
||||
Flag::getValue("extensions_socket"),
|
||||
Flag::getValue("extensions_timeout"),
|
||||
Flag::getValue("extensions_interval"),
|
||||
Flag::getValue("verbose"));
|
||||
if (ext_process == nullptr) {
|
||||
// Unrecoverable error, cannot create an extension process.
|
||||
LOG(ERROR) << "Cannot create extension process: " << extension;
|
||||
Initializer::shutdown(EXIT_FAILURE);
|
||||
} else if (ext_pid == 0) {
|
||||
// Pass the current extension socket and a set timeout to the extension.
|
||||
setenv("OSQUERY_EXTENSION", std::to_string(getpid()).c_str(), 1);
|
||||
// Execute extension with very specific arguments.
|
||||
execle(exec_path.string().c_str(),
|
||||
("osquery extension: " + extension).c_str(),
|
||||
"--socket",
|
||||
Flag::getValue("extensions_socket").c_str(),
|
||||
"--timeout",
|
||||
Flag::getValue("extensions_timeout").c_str(),
|
||||
"--interval",
|
||||
Flag::getValue("extensions_interval").c_str(),
|
||||
(Flag::getValue("verbose") == "true") ? "--verbose" : (char*)nullptr,
|
||||
(char*)nullptr,
|
||||
environ);
|
||||
// Code should never reach this point.
|
||||
VLOG(1) << "Could not start extension process: " << extension;
|
||||
Initializer::shutdown(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
Watcher::setExtension(extension, ext_pid);
|
||||
Watcher::setExtension(extension, ext_process);
|
||||
Watcher::resetExtensionCounters(extension, getUnixTime());
|
||||
VLOG(1) << "Created and monitoring extension child (" << ext_pid
|
||||
VLOG(1) << "Created and monitoring extension child (" << ext_process->pid()
|
||||
<< "): " << extension;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WatcherWatcherRunner::start() {
|
||||
while (!interrupted()) {
|
||||
if (getppid() != watcher_) {
|
||||
if (isLauncherProcessDead(*watcher_)) {
|
||||
// Watcher died, the worker must follow.
|
||||
VLOG(1) << "osqueryd worker (" << getpid()
|
||||
<< ") detected killed watcher (" << watcher_ << ")";
|
||||
VLOG(1) << "osqueryd worker ("
|
||||
<< PlatformProcess::getCurrentProcess()->pid()
|
||||
<< ") detected killed watcher (" << watcher_->pid() << ")";
|
||||
// The watcher watcher is a thread. Do not join services after removing.
|
||||
Initializer::requestShutdown();
|
||||
break;
|
||||
@ -443,3 +438,4 @@ size_t getWorkerLimit(WatchdogLimitType name, int level) {
|
||||
return kWatchdogLimits.at(name).at(level);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,17 @@
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <osquery/dispatcher.h>
|
||||
#include <osquery/flags.h>
|
||||
|
||||
#include "osquery/core/process.h"
|
||||
|
||||
/// Define a special debug/testing watchdog level.
|
||||
#define WATCHDOG_LEVEL_DEBUG 3
|
||||
/// Define the default watchdog level, level below are considered permissive.
|
||||
@ -27,6 +31,8 @@
|
||||
|
||||
namespace osquery {
|
||||
|
||||
using ExtensionMap = std::map<std::string, std::shared_ptr<PlatformProcess>>;
|
||||
|
||||
DECLARE_bool(disable_watchdog);
|
||||
DECLARE_int32(watchdog_level);
|
||||
|
||||
@ -113,12 +119,10 @@ class Watcher : private boost::noncopyable {
|
||||
static void unlock() { instance().lock_.unlock(); }
|
||||
|
||||
/// Accessor for autoloadable extension paths.
|
||||
static const std::map<std::string, pid_t>& extensions() {
|
||||
return instance().extensions_;
|
||||
}
|
||||
static const ExtensionMap& extensions() { return instance().extensions_; }
|
||||
|
||||
/// Lookup extension path from pid.
|
||||
static std::string getExtensionPath(pid_t child);
|
||||
static std::string getExtensionPath(const PlatformProcess& child);
|
||||
|
||||
/// Remove an autoloadable extension path.
|
||||
static void removeExtensionPath(const std::string& extension);
|
||||
@ -127,20 +131,23 @@ class Watcher : private boost::noncopyable {
|
||||
static void addExtensionPath(const std::string& path);
|
||||
|
||||
/// Get state information for a worker or extension child.
|
||||
static PerformanceState& getState(pid_t child);
|
||||
static PerformanceState& getState(const PlatformProcess& child);
|
||||
static PerformanceState& getState(const std::string& extension);
|
||||
|
||||
/// Accessor for the worker process.
|
||||
static pid_t getWorker() { return instance().worker_; }
|
||||
static PlatformProcess& getWorker() { return *instance().worker_; }
|
||||
|
||||
/// Setter for worker process.
|
||||
static void setWorker(pid_t child) { instance().worker_ = child; }
|
||||
static void setWorker(const std::shared_ptr<PlatformProcess>& child) {
|
||||
instance().worker_ = child;
|
||||
}
|
||||
|
||||
/// Setter for an extension process.
|
||||
static void setExtension(const std::string& extension, pid_t child);
|
||||
static void setExtension(const std::string& extension,
|
||||
const std::shared_ptr<PlatformProcess>& child);
|
||||
|
||||
/// Reset pid and performance counters for a worker or extension process.
|
||||
static void reset(pid_t child);
|
||||
static void reset(const PlatformProcess& child);
|
||||
|
||||
/// Count the number of worker restarts.
|
||||
static size_t workerRestartCount() { return instance().worker_restarts_; }
|
||||
@ -166,7 +173,9 @@ class Watcher : private boost::noncopyable {
|
||||
private:
|
||||
/// Do not request the lock until extensions are used.
|
||||
Watcher()
|
||||
: worker_(-1), worker_restarts_(0), lock_(mutex_, std::defer_lock) {}
|
||||
: worker_(std::make_shared<PlatformProcess>()),
|
||||
worker_restarts_(0),
|
||||
lock_(mutex_, std::defer_lock) {}
|
||||
Watcher(Watcher const&);
|
||||
|
||||
void operator=(Watcher const&);
|
||||
@ -185,13 +194,13 @@ class Watcher : private boost::noncopyable {
|
||||
|
||||
private:
|
||||
/// Keep the single worker process/thread ID for inspection.
|
||||
std::atomic<int> worker_{-1};
|
||||
std::shared_ptr<PlatformProcess> worker_;
|
||||
|
||||
/// Number of worker restarts NOT induced by a watchdog process.
|
||||
size_t worker_restarts_{0};
|
||||
|
||||
/// Keep a list of resolved extension paths and their managed pids.
|
||||
std::map<std::string, pid_t> extensions_;
|
||||
ExtensionMap extensions_;
|
||||
|
||||
/// Paths to autoload extensions.
|
||||
std::vector<std::string> extensions_paths_;
|
||||
@ -255,9 +264,9 @@ class WatcherRunner : public InternalRunnable {
|
||||
/// Boilerplate function to sleep for some configured latency
|
||||
bool ok();
|
||||
/// Begin the worker-watcher process.
|
||||
bool watch(pid_t child);
|
||||
bool watch(const PlatformProcess& child) const;
|
||||
/// Inspect into the memory, CPU, and other worker/extension process states.
|
||||
bool isChildSane(pid_t child);
|
||||
bool isChildSane(const PlatformProcess& child) const;
|
||||
|
||||
private:
|
||||
/// Fork and execute a worker process.
|
||||
@ -265,7 +274,7 @@ class WatcherRunner : public InternalRunnable {
|
||||
/// Fork an extension process.
|
||||
bool createExtension(const std::string& extension);
|
||||
/// If a worker/extension has otherwise gone insane, stop it.
|
||||
void stopChild(pid_t child);
|
||||
void stopChild(const PlatformProcess& child) const;
|
||||
|
||||
private:
|
||||
/// Keep the invocation daemon's argc to iterate through argv.
|
||||
@ -279,16 +288,18 @@ class WatcherRunner : public InternalRunnable {
|
||||
/// The WatcherWatcher is spawned within the worker and watches the watcher.
|
||||
class WatcherWatcherRunner : public InternalRunnable {
|
||||
public:
|
||||
explicit WatcherWatcherRunner(pid_t watcher) : watcher_(watcher) {}
|
||||
explicit WatcherWatcherRunner(const std::shared_ptr<PlatformProcess>& watcher)
|
||||
: watcher_(watcher) {}
|
||||
|
||||
/// Runnable thread's entry point.
|
||||
void start();
|
||||
|
||||
private:
|
||||
/// Parent, or watchdog, process ID.
|
||||
pid_t watcher_{-1};
|
||||
std::shared_ptr<PlatformProcess> watcher_;
|
||||
};
|
||||
|
||||
/// Get a performance limit by name and optional level.
|
||||
size_t getWorkerLimit(WatchdogLimitType limit, int level = -1);
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ std::shared_ptr<PlatformProcess> PlatformProcess::getCurrentProcess() {
|
||||
HANDLE handle =
|
||||
::OpenProcess(PROCESS_ALL_ACCESS, FALSE, ::GetCurrentProcessId());
|
||||
if (handle == NULL) {
|
||||
return std::shared_ptr<PlatformProcess>();
|
||||
return std::make_shared<PlatformProcess>();
|
||||
}
|
||||
|
||||
return std::make_shared<PlatformProcess>(handle);
|
||||
@ -88,7 +88,7 @@ std::shared_ptr<PlatformProcess> PlatformProcess::getCurrentProcess() {
|
||||
std::shared_ptr<PlatformProcess> PlatformProcess::getLauncherProcess() {
|
||||
auto launcher_handle = getEnvVar("OSQUERY_LAUNCHER");
|
||||
if (!launcher_handle) {
|
||||
return std::shared_ptr<PlatformProcess>();
|
||||
return std::make_shared<PlatformProcess>();
|
||||
}
|
||||
|
||||
// Convert the environment variable into a HANDLE (the value from environment
|
||||
@ -101,26 +101,27 @@ std::shared_ptr<PlatformProcess> PlatformProcess::getLauncherProcess() {
|
||||
std::stoull(*launcher_handle, nullptr, 16)));
|
||||
}
|
||||
catch (std::invalid_argument e) {
|
||||
return std::shared_ptr<PlatformProcess>();
|
||||
return std::make_shared<PlatformProcess>();
|
||||
}
|
||||
catch (std::out_of_range e) {
|
||||
return std::shared_ptr<PlatformProcess>();
|
||||
return std::make_shared<PlatformProcess>();
|
||||
}
|
||||
|
||||
if (handle == NULL || handle == INVALID_HANDLE_VALUE) {
|
||||
return std::shared_ptr<PlatformProcess>();
|
||||
return std::make_shared<PlatformProcess>();
|
||||
}
|
||||
|
||||
return std::make_shared<PlatformProcess>(handle);
|
||||
}
|
||||
|
||||
std::shared_ptr<PlatformProcess> PlatformProcess::launchWorker(
|
||||
const std::string &exec_path, const std::string &name) {
|
||||
const std::string &exec_path, int argc, char **argv) {
|
||||
::STARTUPINFOA si = {0};
|
||||
::PROCESS_INFORMATION pi = {0};
|
||||
|
||||
si.cb = sizeof(si);
|
||||
|
||||
std::stringstream argv_stream;
|
||||
std::stringstream handle_stream;
|
||||
|
||||
// The HANDLE exposed to the child process is currently limited to only having
|
||||
@ -158,15 +159,29 @@ std::shared_ptr<PlatformProcess> PlatformProcess::launchWorker(
|
||||
// Since Windows does not accept a char * array for arguments, we have to
|
||||
// build one as a string. Therefore, we need to make sure that special
|
||||
// characters are not present that would obstruct the parsing of arguments.
|
||||
// For now, we strip out all double quotes.
|
||||
// For now, we strip out all double quotes. If the an entry in argv has
|
||||
// spaces, we will put double-quotes around the entry.
|
||||
//
|
||||
// NOTE: This is extremely naive and will break the moment complexities are
|
||||
// involved... Windows command line argument parsing is extremely
|
||||
// nitpicky and is different in behavior than POSIX argv parsing.
|
||||
//
|
||||
// We don't directly use argv.c_str() as the value for lpCommandLine in
|
||||
// CreateProcess since that argument requires a modifiable buffer. So,
|
||||
// instead, we off-load the contents of argv into a vector which will have its
|
||||
// backing memory as modifiable.
|
||||
auto argv =
|
||||
std::string("\"") + boost::replace_all_copy(name, "\" ", " ") + "\"";
|
||||
std::vector<char> mutable_argv(argv.begin(), argv.end());
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
std::string component(argv[i]);
|
||||
if (component.find(" ") != std::string::npos) {
|
||||
boost::replace_all(component, "\"", "\\\"");
|
||||
argv_stream << "\"" << component << "\" ";
|
||||
} else {
|
||||
argv_stream << component << " ";
|
||||
}
|
||||
}
|
||||
|
||||
std::string cmdline = argv_stream.str();
|
||||
std::vector<char> mutable_argv(cmdline.begin(), cmdline.end());
|
||||
mutable_argv.push_back('\0');
|
||||
|
||||
BOOL status = ::CreateProcessA(exec_path.c_str(),
|
||||
|
@ -35,13 +35,18 @@ std::string kProcessTestExecPath;
|
||||
const char *kOsqueryTestModuleName = "osquery_tests.exe";
|
||||
|
||||
/// These are the expected arguments for our test worker process.
|
||||
const char *kExpectedWorkerArgs[] = {"worker-test"};
|
||||
const char *kExpectedWorkerArgs[] = {"worker-test", "--socket",
|
||||
"fake-socket", nullptr};
|
||||
const size_t kExpectedWorkerArgsCount =
|
||||
(sizeof(osquery::kExpectedWorkerArgs) / sizeof(char *)) - 1;
|
||||
|
||||
/// These are the expected arguments for our test extensions process.
|
||||
const char *kExpectedExtensionArgs[] = {
|
||||
"osquery extension: extension-test", "--socket", "socket-name",
|
||||
"--timeout", "100", "--interval",
|
||||
"5", "--verbose"};
|
||||
"osquery extension: extension-test", "--socket", "socket-name",
|
||||
"--timeout", "100", "--interval",
|
||||
"5", "--verbose", nullptr};
|
||||
const size_t kExpectedExtensionArgsCount =
|
||||
(sizeof(osquery::kExpectedExtensionArgs) / sizeof(char *)) - 1;
|
||||
|
||||
static bool compareArguments(char *result[],
|
||||
unsigned int result_nelms,
|
||||
@ -69,8 +74,7 @@ int workerMain(int argc, char *argv[]) {
|
||||
if (!osquery::compareArguments(argv,
|
||||
argc,
|
||||
osquery::kExpectedWorkerArgs,
|
||||
sizeof(osquery::kExpectedWorkerArgs) /
|
||||
sizeof(const char *))) {
|
||||
osquery::kExpectedWorkerArgsCount)) {
|
||||
return ERROR_COMPARE_ARGUMENT;
|
||||
}
|
||||
|
||||
@ -106,8 +110,7 @@ int extensionMain(int argc, char *argv[]) {
|
||||
if (!osquery::compareArguments(argv,
|
||||
argc,
|
||||
osquery::kExpectedExtensionArgs,
|
||||
sizeof(osquery::kExpectedExtensionArgs) /
|
||||
sizeof(const char *))) {
|
||||
osquery::kExpectedExtensionArgsCount)) {
|
||||
return ERROR_COMPARE_ARGUMENT;
|
||||
}
|
||||
return EXTENSION_SUCCESS_CODE;
|
||||
|
Loading…
Reference in New Issue
Block a user