diff --git a/osquery/extensions/extensions.cpp b/osquery/extensions/extensions.cpp index 019c1bac..3d25215b 100644 --- a/osquery/extensions/extensions.cpp +++ b/osquery/extensions/extensions.cpp @@ -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 kExtendables = { - {EXTENSION, EXT_EXTENSION}, {MODULE, MODULE_EXTENSION}, +using ExtendableTypeSet = std::map; + +const std::map 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 predicate) { // Make sure the extension manager path exists, and is writable. size_t delay = 0; @@ -153,23 +124,16 @@ Status applyExtensionDelay(std::function 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; } diff --git a/osquery/extensions/interface.cpp b/osquery/extensions/interface.cpp index 77e96166..d53f3b53 100644 --- a/osquery/extensions/interface.cpp +++ b/osquery/extensions/interface.cpp @@ -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()); diff --git a/osquery/extensions/tests/extensions_tests.cpp b/osquery/extensions/tests/extensions_tests.cpp index 67877e1d..6a698d10 100644 --- a/osquery/extensions/tests/extensions_tests.cpp +++ b/osquery/extensions/tests/extensions_tests.cpp @@ -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("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(); diff --git a/osquery/filesystem/fileops.h b/osquery/filesystem/fileops.h index cef76667..2368bf08 100644 --- a/osquery/filesystem/fileops.h +++ b/osquery/filesystem/fileops.h @@ -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 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. * diff --git a/osquery/filesystem/posix/fileops.cpp b/osquery/filesystem/posix/fileops.cpp index cd8271c4..f665b2be 100644 --- a/osquery/filesystem/posix/fileops.cpp +++ b/osquery/filesystem/posix/fileops.cpp @@ -340,6 +340,30 @@ boost::optional 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("/"); } diff --git a/osquery/filesystem/windows/fileops.cpp b/osquery/filesystem/windows/fileops.cpp index 85fc4ca6..aab96fd4 100644 --- a/osquery/filesystem/windows/fileops.cpp +++ b/osquery/filesystem/windows/fileops.cpp @@ -1249,13 +1249,26 @@ boost::optional 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"); } }