[Fix #2446] Consolidate namedPipe into socketExists (#2712)

This commit is contained in:
Teddy Reed 2016-11-16 19:32:22 -08:00 committed by GitHub
parent 1a0aa988f1
commit 4fdea34a9d
6 changed files with 128 additions and 159 deletions

View File

@ -35,24 +35,20 @@ namespace osquery {
// Millisecond latency between initalizing manager pings.
const size_t kExtensionInitializeLatency = 20;
#ifdef __APPLE__
#define MODULE_EXTENSION ".dylib"
#define EXT_EXTENSION ".ext"
#elif defined(WIN32)
#define MODULE_EXTENSION ".dll"
#define EXT_EXTENSION ".exe"
#else
#define MODULE_EXTENSION ".so"
#define EXT_EXTENSION ".ext"
#endif
enum ExtenableTypes {
enum class ExtendableType {
EXTENSION = 1,
MODULE = 2,
};
const std::map<ExtenableTypes, std::string> kExtendables = {
{EXTENSION, EXT_EXTENSION}, {MODULE, MODULE_EXTENSION},
using ExtendableTypeSet = std::map<ExtendableType, std::string>;
const std::map<PlatformType, ExtendableTypeSet> kFileExtensions{
{PlatformType::TYPE_WINDOWS,
{{ExtendableType::EXTENSION, ".exe"}, {ExtendableType::MODULE, ".dll"}}},
{PlatformType::TYPE_LINUX,
{{ExtendableType::EXTENSION, ".ext"}, {ExtendableType::MODULE, ".so"}}},
{PlatformType::TYPE_OSX,
{{ExtendableType::EXTENSION, ".ext"}, {ExtendableType::MODULE, ".dylib"}}},
};
CLI_FLAG(bool, disable_extensions, false, "Disable extension API");
@ -60,12 +56,12 @@ CLI_FLAG(bool, disable_extensions, false, "Disable extension API");
CLI_FLAG(string,
extensions_socket,
OSQUERY_SOCKET "osquery.em",
"Path to the extensions UNIX domain socket")
"Path to the extensions UNIX domain socket");
CLI_FLAG(string,
extensions_autoload,
OSQUERY_HOME "/extensions.load",
"Optional path to a list of autoloaded & managed extensions")
"Optional path to a list of autoloaded & managed extensions");
CLI_FLAG(string,
extensions_timeout,
@ -75,12 +71,12 @@ CLI_FLAG(string,
CLI_FLAG(string,
extensions_interval,
"3",
"Seconds delay between connectivity checks")
"Seconds delay between connectivity checks");
CLI_FLAG(string,
modules_autoload,
OSQUERY_HOME "/modules.load",
"Optional path to a list of autoloaded registry modules")
"Optional path to a list of autoloaded registry modules");
SHELL_FLAG(string, extension, "", "Path to a single extension to autoload");
@ -102,31 +98,6 @@ EXTENSION_FLAG_ALIAS(socket, extensions_socket);
EXTENSION_FLAG_ALIAS(timeout, extensions_timeout);
EXTENSION_FLAG_ALIAS(interval, extensions_interval);
#ifdef WIN32
// Time to wait for a busy named pipe, if it exists
#define NAMED_PIPE_WAIT 500
/**
* We cannot use existing methods to determine the lifespan of the
* extensions/extensions manager socket. On Windows, the Thrift install is
* brittle and does not like a quick connect and disconnect. To compensate, we
* use WaitNamedPipe to determine the existence of a named pipe. If the named
* pipe does not exist, WaitNamedPipe should error with ERROR_BAD_PATHNAME.
*/
static Status isNamedPipePathValid(const std::string& path) {
if (!boost::starts_with(path, OSQUERY_SOCKET)) {
return Status(1, "Bad named pipe name prefix");
}
if ((::WaitNamedPipeA(path.c_str(), NAMED_PIPE_WAIT) == 0) &&
(::GetLastError() == ERROR_BAD_PATHNAME)) {
return Status(1, "Named pipe path is invalid");
}
return Status(0, "OK");
}
#endif
Status applyExtensionDelay(std::function<Status(bool& stop)> predicate) {
// Make sure the extension manager path exists, and is writable.
size_t delay = 0;
@ -153,23 +124,16 @@ Status applyExtensionDelay(std::function<Status(bool& stop)> predicate) {
Status extensionPathActive(const std::string& path, bool use_timeout = false) {
return applyExtensionDelay(([path, &use_timeout](bool& stop) {
#ifdef WIN32
// This makes sure the pipe exists in some capacity (could be busy at the
// moment)
if (namedPipeExists(path).ok()) {
return Status(0, "OK");
}
#else
if (pathExists(path) && isWritable(path)) {
if (socketExists(path)) {
try {
ExtensionStatus status;
auto client = EXManagerClient(path);
client.get()->ping(status);
return Status(0, "OK");
} catch (const std::exception& e) {
} catch (const std::exception& /* e */) {
// Path might exist without a connected extension or extension manager.
}
}
#endif
// Only check active once if this check does not allow a timeout.
if (!use_timeout) {
stop = true;
@ -221,26 +185,18 @@ void ExtensionWatcher::watch() {
// This does NOT use pingExtension to avoid the latency checks applied.
ExtensionStatus status;
bool core_sane = true;
#ifdef WIN32
// Check to see if the pipe name is a valid named pipe
if (!namedPipeExists(path_).ok()) {
core_sane = false;
}
#else
if (isWritable(path_)) {
if (socketExists(path_)) {
try {
auto client = EXManagerClient(path_);
// Ping the extension manager until it goes down.
client.get()->ping(status);
} catch (const std::exception& e) {
} catch (const std::exception& /* e */) {
core_sane = false;
}
} else {
// The previously-writable extension socket is not usable.
core_sane = false;
}
#endif
if (!core_sane) {
LOG(INFO) << "Extension watcher ending: osquery core has gone away";
@ -261,33 +217,24 @@ void ExtensionManagerWatcher::watch() {
ExtensionStatus status;
for (const auto& uuid : uuids) {
auto path = getExtensionSocket(uuid);
#ifdef WIN32
// Check to see if the pipe name is a valid named pipe
if (!namedPipeExists(path).ok()) {
LOG(INFO) << "Extension UUID " << uuid << " ping failed";
auto exists = socketExists(path);
// Immediate fail non-writable paths.
failures_[uuid] += 1;
}
#else
// The manager first checks writeability of the extension socket.
auto writable = isWritable(path);
if (!writable && failures_[uuid] == 0) {
if (!exists.ok() && failures_[uuid] == 0) {
// If there was never a failure then this is the first attempt.
// Allow the extension to be latent and respect the autoload timeout.
VLOG(1) << "Extension UUID " << uuid << " initial check failed";
writable = extensionPathActive(path, true);
exists = extensionPathActive(path, true);
}
// All extensions will have a single failure (and odd use of the counting).
// If failures get to 2 then the extension will be removed.
failures_[uuid] = 1;
if (writable) {
if (exists.ok()) {
try {
auto client = EXClient(path);
// Ping the extension until it goes down.
client.get()->ping(status);
} catch (const std::exception& e) {
} catch (const std::exception& /* e */) {
failures_[uuid] += 1;
continue;
}
@ -303,7 +250,6 @@ void ExtensionManagerWatcher::watch() {
} else {
failures_[uuid] = 1;
}
#endif
}
for (const auto& uuid : failures_) {
@ -315,28 +261,6 @@ void ExtensionManagerWatcher::watch() {
}
}
Status socketWritable(const fs::path& path) {
if (pathExists(path).ok()) {
if (!isWritable(path).ok()) {
return Status(1, "Cannot write extension socket: " + path.string());
}
if (!osquery::remove(path).ok()) {
return Status(1, "Cannot remove extension socket: " + path.string());
}
} else {
if (!pathExists(path.parent_path()).ok()) {
return Status(1, "Extension socket directory missing: " + path.string());
}
if (!isWritable(path.parent_path()).ok()) {
return Status(1, "Cannot create extension socket: " + path.string());
}
}
return Status(0, "OK");
}
void loadExtensions() {
// Disabling extensions will disable autoloading.
if (FLAGS_disable_extensions) {
@ -365,10 +289,11 @@ void loadModules() {
}
}
static bool isFileSafe(std::string& path, ExtenableTypes type) {
static bool isFileSafe(std::string& path, ExtendableType type) {
boost::trim(path);
// A 'type name' may be used in verbose log output.
std::string type_name = ((type == EXTENSION) ? "extension" : "module");
std::string type_name =
((type == ExtendableType::EXTENSION) ? "extension" : "module");
if (path.size() == 0 || path[0] == '#' || path[0] == ';') {
return false;
}
@ -378,8 +303,15 @@ static bool isFileSafe(std::string& path, ExtenableTypes type) {
VLOG(1) << "Cannot autoload " << type_name << " from directory: " << path;
return false;
}
// The extendables will force an appropriate file path extension.
auto& ext = kExtendables.at(type);
std::string ext;
if (isPlatform(PlatformType::TYPE_LINUX)) {
ext = kFileExtensions.at(PlatformType::TYPE_LINUX).at(type);
} else if (isPlatform(PlatformType::TYPE_OSX)) {
ext = kFileExtensions.at(PlatformType::TYPE_OSX).at(type);
} else {
ext = kFileExtensions.at(PlatformType::TYPE_WINDOWS).at(type);
}
// Only autoload file which were safe at the time of discovery.
// If the binary later becomes unsafe (permissions change) then it will fail
@ -413,7 +345,7 @@ Status loadExtensions(const std::string& loadfile) {
if (readFile(loadfile, autoload_paths).ok()) {
for (auto& path : osquery::split(autoload_paths, "\n")) {
if (isFileSafe(path, EXTENSION)) {
if (isFileSafe(path, ExtendableType::EXTENSION)) {
// After the path is sanitized the watcher becomes responsible for
// forking and executing the extension binary.
Watcher::addExtensionPath(path);
@ -430,7 +362,7 @@ Status loadModules(const std::string& loadfile) {
std::string autoload_paths;
if (readFile(loadfile, autoload_paths).ok()) {
for (auto& path : osquery::split(autoload_paths, "\n")) {
if (isFileSafe(path, MODULE)) {
if (isFileSafe(path, ExtendableType::MODULE)) {
RegistryModuleLoader loader(path);
loader.init();
} else {
@ -511,15 +443,10 @@ Status startExtension(const std::string& manager_path,
return Status(1, "Extension register failed: " + std::string(e.what()));
}
// Now that the uuid is known, try to clean up stale socket paths.
// Now that the UUID is known, try to clean up stale socket paths.
auto extension_path = getExtensionSocket(ext_status.uuid, manager_path);
#ifdef WIN32
status = isNamedPipePathValid(extension_path);
#else
status = socketWritable(extension_path);
#endif
status = socketExists(extension_path, true);
if (!status) {
return status;
}
@ -723,13 +650,9 @@ Status startExtensionManager() {
}
Status startExtensionManager(const std::string& manager_path) {
// Check if the socket location exists.
#ifdef WIN32
auto status = isNamedPipePathValid(manager_path);
#else
auto status = socketWritable(manager_path);
#endif
// Check if the socket location is ready for a new Thrift server.
// We expect the path to be invalid or a removal attempt to succeed.
auto status = socketExists(manager_path, true);
if (!status.ok()) {
return status;
}

View File

@ -259,11 +259,11 @@ void ExtensionRunnerCore::startServer(TProcessorRef processor) {
transport_ = TServerTransportRef(new TPlatformServerSocket(path_));
#ifndef WIN32
// Before starting and after stopping the manager, remove stale sockets.
// This is not relevant in Windows
removeStalePaths(path_);
#endif
if (!isPlatform(PlatformType::TYPE_WINDOWS)) {
// Before starting and after stopping the manager, remove stale sockets.
// This is not relevant in Windows
removeStalePaths(path_);
}
// Construct the service's transport, protocol, thread pool.
auto transport_fac = TTransportFactoryRef(new TBufferedTransportFactory());

View File

@ -35,21 +35,19 @@ const int kTimeout = 3000;
class ExtensionsTest : public testing::Test {
protected:
void SetUp() override {
#ifdef WIN32
socket_path = OSQUERY_SOCKET;
#else
socket_path = kTestWorkingDirectory;
#endif
if (isPlatform(PlatformType::TYPE_WINDOWS)) {
socket_path = OSQUERY_SOCKET;
} else {
socket_path = kTestWorkingDirectory;
}
socket_path += "testextmgr" + std::to_string(rand());
#ifdef WIN32
if (namedPipeExists(socket_path).ok()) {
#else
remove(socket_path);
if (pathExists(socket_path).ok()) {
#endif
throw std::domain_error("Cannot test sockets: " + socket_path);
if (!isPlatform(PlatformType::TYPE_WINDOWS)) {
remove(socket_path);
if (pathExists(socket_path)) {
throw std::domain_error("Cannot test sockets: " + socket_path);
}
}
}
@ -57,9 +55,9 @@ class ExtensionsTest : public testing::Test {
Dispatcher::stopServices();
Dispatcher::joinServices();
#ifndef WIN32
remove(socket_path);
#endif
if (!isPlatform(PlatformType::TYPE_WINDOWS)) {
remove(socket_path);
}
}
bool ping(int attempts = 3) {
@ -109,15 +107,11 @@ class ExtensionsTest : public testing::Test {
return extensions;
}
bool socketExists(const std::string& _socket_path) {
bool socketExistsLocal(const std::string& socket_path) {
// Wait until the runnable/thread created the socket.
int delay = 0;
while (delay < kTimeout) {
#ifdef WIN32
if (namedPipeExists(_socket_path).ok()) {
#else
if (pathExists(_socket_path).ok() && isReadable(_socket_path).ok()) {
#endif
if (osquery::socketExists(socket_path).ok()) {
return true;
}
sleepFor(kDelay);
@ -135,14 +129,14 @@ TEST_F(ExtensionsTest, test_manager_runnable) {
auto status = startExtensionManager(socket_path);
EXPECT_TRUE(status.ok());
// Call success if the Unix socket was created.
EXPECT_TRUE(socketExists(socket_path));
EXPECT_TRUE(socketExistsLocal(socket_path));
}
TEST_F(ExtensionsTest, test_extension_runnable) {
auto status = startExtensionManager(socket_path);
EXPECT_TRUE(status.ok());
// Wait for the extension manager to start.
EXPECT_TRUE(socketExists(socket_path));
EXPECT_TRUE(socketExistsLocal(socket_path));
// Test the extension manager API 'ping' call.
EXPECT_TRUE(ping());
@ -151,7 +145,7 @@ TEST_F(ExtensionsTest, test_extension_runnable) {
TEST_F(ExtensionsTest, test_extension_start) {
auto status = startExtensionManager(socket_path);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(socketExists(socket_path));
EXPECT_TRUE(socketExistsLocal(socket_path));
// Now allow duplicates (for testing, since EM/E are the same).
Registry::allowDuplicates(true);
@ -170,7 +164,7 @@ TEST_F(ExtensionsTest, test_extension_start) {
RouteUUID uuid = (RouteUUID)stoi(status.getMessage(), nullptr, 0);
// We can test-wait for the extensions's socket to open.
EXPECT_TRUE(socketExists(socket_path + "." + std::to_string(uuid)));
EXPECT_TRUE(socketExistsLocal(socket_path + "." + std::to_string(uuid)));
// Then clean up the registry modifications.
Registry::removeBroadcast(uuid);
@ -194,7 +188,7 @@ CREATE_REGISTRY(ExtensionPlugin, "extension_test");
TEST_F(ExtensionsTest, test_extension_broadcast) {
auto status = startExtensionManager(socket_path);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(socketExists(socket_path));
EXPECT_TRUE(socketExistsLocal(socket_path));
// This time we're going to add a plugin to the extension_test registry.
Registry::add<TestExtensionPlugin>("extension_test", "test_item");
@ -224,7 +218,7 @@ TEST_F(ExtensionsTest, test_extension_broadcast) {
}
auto ext_socket = socket_path + "." + std::to_string(uuid);
EXPECT_TRUE(socketExists(ext_socket));
EXPECT_TRUE(socketExistsLocal(ext_socket));
// Make sure the EM registered the extension (called in start extension).
auto extensions = registeredExtensions();

View File

@ -106,13 +106,9 @@ const PlatformHandle kInvalidHandle = (PlatformHandle)-1;
* Provides a platform agnostic enumeration for file seek operations. These
* are translated to the appropriate flags for the underlying platform.
*/
enum SeekMode { PF_SEEK_BEGIN = 0, PF_SEEK_CURRENT, PF_SEEK_END };
#ifdef WIN32
/// Checks for the existence of a named pipe socket
Status namedPipeExists(const std::string& path);
/// Takes a Windows FILETIME object and returns seconds since epoch
LONGLONG filetimeToUnixtime(const FILETIME& ft);
@ -307,6 +303,25 @@ bool platformIsatty(FILE* f);
boost::optional<FILE*> platformFopen(const std::string& filename,
const std::string& mode);
/**
* @brief Checks for the existence of a named pipe or UNIX socket.
*
* This method is overloaded to perform two actions. If removal is requested
* the success is determined based on the non-existence or successful removal
* of the socket path. Otherwise the result is straightforward.
*
* The removal action is only used when extensions or the extension manager
* is first starting.
*
* @param path The filesystem path to a UNIX socket or Windows named pipe.
* @param remove_socket Attempt to remove the socket if it exists.
*
* @return Success if the socket exists and removal was not requested. False
* if the socket exists and removal was requested (and the attempt to remove
* had failed).
*/
Status socketExists(const fs::path& path, bool remove_socket = false);
/**
* @brief Returns the OS root system directory.
*

View File

@ -340,6 +340,30 @@ boost::optional<FILE*> platformFopen(const std::string& filename,
return fp;
}
Status socketExists(const fs::path& path, bool remove_socket) {
// This implies that the socket is writable.
if (pathExists(path).ok()) {
if (!isWritable(path).ok()) {
return Status(1, "Cannot write extension socket: " + path.string());
} else if (remove_socket && !osquery::remove(path).ok()) {
return Status(1, "Cannot remove extension socket: " + path.string());
}
} else {
// The path does not exist.
if (!pathExists(path.parent_path()).ok()) {
return Status(1, "Extension socket directory missing: " + path.string());
} else if (!isWritable(path.parent_path()).ok()) {
return Status(1, "Cannot create extension socket: " + path.string());
}
// If we are not requesting to remove the socket then this is a failure.
if (!remove_socket) {
return Status(1, "Socket does not exist");
}
}
return Status(0);
}
fs::path getSystemRoot() {
return fs::path("/");
}

View File

@ -1249,13 +1249,26 @@ boost::optional<FILE*> platformFopen(const std::string& filename,
return fp;
}
Status namedPipeExists(const std::string& path) {
// Wait 500ms for the named pipe status
if (::WaitNamedPipeA(path.c_str(), 500) == 0) {
/**
* @brief The windows implementation introduces a 500ms max wait.
*
* We cannot use existing methods to determine the lifespan of the
* extensions/extensions manager socket. On Windows, the Thrift install is
* brittle and does not like a quick connect and disconnect. To compensate, we
* use WaitNamedPipe to determine the existence of a named pipe. If the named
* pipe does not exist, WaitNamedPipe should error with ERROR_BAD_PATHNAME.
*/
Status socketExists(const fs::path& path, bool remove_socket) {
DWORD timeout = (remove_socket) ? 0 : 500;
if (::WaitNamedPipeA(path.string().c_str(), timeout) == 0) {
DWORD error = ::GetLastError();
if (error == ERROR_BAD_PATHNAME) {
return Status(1, "Named pipe path is invalid");
} else if (error == ERROR_FILE_NOT_FOUND) {
if (remove_socket) {
return Status(0);
}
return Status(1, "Named pipe does not exist");
}
}