From: Sangwan Kwon Date: Tue, 10 Feb 2015 02:18:22 +0000 (-0800) Subject: Bump version to upstream-1.4.1 X-Git-Tag: accepted/tizen/unified/20200810.122954~244 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9475d5066800e57a6357ffcb1278e4c5f79b4e05;p=platform%2Fcore%2Fsecurity%2Fvist.git Bump version to upstream-1.4.1 - Add distributed query feature Signed-off-by: Sangwan Kwon --- diff --git a/include/osquery/dispatcher.h b/include/osquery/dispatcher.h index 07c3eff..edad9d9 100644 --- a/include/osquery/dispatcher.h +++ b/include/osquery/dispatcher.h @@ -51,8 +51,6 @@ class InternalRunnable : public apache::thrift::concurrency::Runnable { /// Check if the thread's entrypoint (run) executed, meaning thread context /// was allocated. bool hasRun() { return run_; } - /// Sleep in a boost::thread interruptable state. - void interruptableSleep(size_t milli); protected: /// Require the runnable thread define an entrypoint. @@ -115,6 +113,7 @@ class Dispatcher { */ Status add(std::shared_ptr task); + /// See `add`, but services are not limited to a thread poll size. Status addService(std::shared_ptr service); /** @@ -146,8 +145,10 @@ class Dispatcher { */ void join(); + /// See `join`, but applied to osquery services. void joinServices(); + /// Destroy and stop all osquery service threads and service objects. void removeServices(); /** @@ -247,7 +248,12 @@ class Dispatcher { * @see getThreadManager */ InternalThreadManagerRef thread_manager_; + /// The set of shared osquery service threads. std::vector > service_threads_; + /// THe set of shared osquery services. std::vector > services_; }; + +/// Sleep in a boost::thread interruptable state. +void interruptableSleep(size_t milli); } diff --git a/include/osquery/events.h b/include/osquery/events.h index e500905..de943a9 100644 --- a/include/osquery/events.h +++ b/include/osquery/events.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include diff --git a/include/osquery/extensions.h b/include/osquery/extensions.h index 37d9a27..6fa4209 100644 --- a/include/osquery/extensions.h +++ b/include/osquery/extensions.h @@ -26,8 +26,6 @@ namespace osquery { DECLARE_string(extensions_socket); -namespace extensions { - /** * @brief Helper struct for managing extenion metadata. * @@ -37,8 +35,30 @@ struct ExtensionInfo { std::string name; std::string version; std::string sdk_version; + + ExtensionInfo& operator=(const extensions::InternalExtensionInfo& iei) { + name = iei.name; + version = iei.version; + sdk_version = iei.sdk_version; + return *this; + } + + ExtensionInfo() {} + ExtensionInfo(const std::string& name) : name(name) { + version = OSQUERY_VERSION; + sdk_version = OSQUERY_VERSION; + } }; +typedef std::map ExtensionList; + +inline std::string getExtensionSocket( + RouteUUID uuid, const std::string& path = FLAGS_extensions_socket) { + return path + "." + std::to_string(uuid); +} + +namespace extensions { + /** * @brief The Thrift API server used by an osquery Extension process. * @@ -85,7 +105,7 @@ class ExtensionManagerHandler : virtual public ExtensionManagerIf, ExtensionManagerHandler() {} /// Return a list of Route UUIDs and extension metadata. - void extensions(ExtensionList& _return) { _return = extensions_; } + void extensions(InternalExtensionList& _return) { _return = extensions_; } /** * @brief Request a Route UUID and advertise a set of Registry routes. @@ -123,7 +143,7 @@ class ExtensionManagerHandler : virtual public ExtensionManagerIf, bool exists(const std::string& name); /// Maintain a map of extension UUID to metadata for tracking deregistrations. - ExtensionList extensions_; + InternalExtensionList extensions_; }; } @@ -161,7 +181,7 @@ class ExtensionRunner : public InternalRunnable { public: virtual ~ExtensionRunner(); ExtensionRunner(const std::string& manager_path, RouteUUID uuid) { - path_ = manager_path + "." + std::to_string(uuid); + path_ = getExtensionSocket(uuid, manager_path); uuid_ = uuid; } @@ -194,6 +214,16 @@ class ExtensionManagerRunner : public InternalRunnable { std::string path_; }; +/// Status get a list of active extenions. +Status getExtensions(ExtensionList& extensions); + +/// Internal getExtensions using a UNIX domain socket path. +Status getExtensions(const std::string& manager_path, + ExtensionList& extensions); + +/// Ping an extension manager or extension. +Status pingExtension(const std::string& path); + /** * @brief Call a Plugin exposed by an Extension Registry route. * diff --git a/include/osquery/filesystem.h b/include/osquery/filesystem.h index 2006744..5a3de6f 100644 --- a/include/osquery/filesystem.h +++ b/include/osquery/filesystem.h @@ -155,6 +155,9 @@ Status isDirectory(const boost::filesystem::path& path); */ std::vector getHomeDirectories(); +/// Return bit-mask-style permissions. +std::string lsperms(int mode); + #ifdef __APPLE__ /** * @brief Parse a property list on disk into a property tree. diff --git a/include/osquery/registry.h b/include/osquery/registry.h index 75f4b28..0e29c3b 100644 --- a/include/osquery/registry.h +++ b/include/osquery/registry.h @@ -111,7 +111,7 @@ class Plugin { /// The plugin may perform some tear down, release, not required. virtual void tearDown() {} /// The plugin may publish route info (other than registry type and name). - virtual RouteInfo routeInfo() { + virtual RouteInfo routeInfo() const { RouteInfo info; return info; } @@ -150,6 +150,7 @@ class RegistryHelperCore { public: RegistryHelperCore(bool auto_setup = true) : auto_setup_(auto_setup) {} + virtual ~RegistryHelperCore() {} /** * @brief Remove a registry item by its identifier. @@ -255,13 +256,11 @@ class RegistryHelper : public RegistryHelperCore { return Status(1, "Duplicate registry item exists: " + item_name); } - // Run the item's constructor, the setUp call will happen later. - auto item = (RegistryType*)new Item(); + // Cast the specific registry-type derived item as the API type of the + // registry used when created using the registry factory. + std::shared_ptr item((RegistryType*)new Item()); item->setName(item_name); - // Cast the specific registry-type derived item as the API typ the registry - // used when it was created using the registry factory. - std::shared_ptr shared_item(item); - items_[item_name] = shared_item; + items_[item_name] = item; return Status(0, "OK"); } @@ -274,11 +273,11 @@ class RegistryHelper : public RegistryHelperCore { * @param item_name An identifier for this registry plugin. * @return A std::shared_ptr of type RegistryType. */ - RegistryTypeRef get(const std::string& item_name) { + RegistryTypeRef get(const std::string& item_name) const { return std::dynamic_pointer_cast(items_.at(item_name)); } - const std::map all() { + const std::map all() const { std::map ditems; for (const auto& item : items_) { ditems[item.first] = std::dynamic_pointer_cast(item.second); @@ -326,10 +325,9 @@ class RegistryFactory : private boost::noncopyable { return 0; } - auto registry = (PluginRegistryHelper*)new RegistryHelper(auto_setup); + PluginRegistryHelperRef registry((PluginRegistryHelper*)new RegistryHelper(auto_setup)); registry->setName(registry_name); - PluginRegistryHelperRef shared_registry(registry); - instance().registries_[registry_name] = shared_registry; + instance().registries_[registry_name] = registry; return 0; } diff --git a/include/osquery/sql.h b/include/osquery/sql.h index 692937d..b83803c 100644 --- a/include/osquery/sql.h +++ b/include/osquery/sql.h @@ -3,7 +3,7 @@ * 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 + * 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. * */ @@ -50,7 +50,7 @@ class SQL { * * @return A QueryData object of the query results */ - QueryData rows(); + const QueryData& rows(); /** * @brief Accessor to switch off of when checking the success of a query @@ -60,6 +60,13 @@ class SQL { bool ok(); /** + * @brief Get the status returned by the query + * + * @return The query status + */ + Status getStatus(); + + /** * @brief Accessor for the message string indicating the status of the query * * @return The message string indicating the status of the query @@ -67,6 +74,15 @@ class SQL { std::string getMessageString(); /** + * @brief Add host info columns onto existing QueryData + * + * Use this to add columns providing host info to the query results. + * Distributed queries use this to add host information before returning + * results to the aggregator. + */ + void annotateHostInfo(); + + /** * @brief Accessor for the list of queryable tables * * @return A vector of table names @@ -96,7 +112,7 @@ class SQL { tables::ConstraintOperator op, const std::string& expr); - private: + protected: /** * @brief Private default constructor * @@ -104,7 +120,9 @@ class SQL { */ SQL(){}; - private: + // The key used to store hostname for annotateHostInfo + static const std::string kHostColumnName; + /// the internal member which holds the results of the query QueryData results_; @@ -152,4 +170,18 @@ Status query(const std::string& query, QueryData& results); * @return status indicating success or failure of the operation */ Status getQueryColumns(const std::string& q, tables::TableColumns& columns); + +/* + * @brief A mocked subclass of SQL useful for testing + */ +class MockSQL : public SQL { + public: + explicit MockSQL() : MockSQL({}) {} + explicit MockSQL(const QueryData& results) : MockSQL(results, Status()) {} + explicit MockSQL(const QueryData& results, const Status& status) { + results_ = results; + status_ = status; + } +}; + } diff --git a/include/osquery/tables.h b/include/osquery/tables.h index 4b96c44..7115f79 100644 --- a/include/osquery/tables.h +++ b/include/osquery/tables.h @@ -124,7 +124,7 @@ struct ConstraintList { * @param expr a SQL type expression of the column literal type to check. * @return If the expression matched all constraints. */ - bool matches(const std::string& expr); + bool matches(const std::string& expr) const; /** * @brief Check if an expression matches the query constraints. @@ -137,7 +137,7 @@ struct ConstraintList { * @return If the expression matched all constraints. */ template - bool matches(const T& expr) { + bool matches(const T& expr) const { return matches(TEXT(expr)); } @@ -150,7 +150,7 @@ struct ConstraintList { * * @return true if any constraint exists. */ - bool exists() { return (constraints_.size() > 0); } + bool exists() const { return (constraints_.size() > 0); } /** * @brief Check if a constrait exist AND matches the type expression. @@ -161,7 +161,7 @@ struct ConstraintList { * @return true if any constraint exists AND matches the type expression. */ template - bool existsAndMatches(const T& expr) { + bool existsAndMatches(const T& expr) const { return (exists() && matches(expr)); } @@ -176,7 +176,7 @@ struct ConstraintList { * @return true if constraint is missing or matches the type expression. */ template - bool notExistsOrMatches(const T& expr) { + bool notExistsOrMatches(const T& expr) const { return (!exists() || matches(expr)); } @@ -184,7 +184,7 @@ struct ConstraintList { * @brief Helper templated function for ConstraintList::matches. */ template - bool literal_matches(const T& base_expr); + bool literal_matches(const T& base_expr) const; /** * @brief Get all expressions for a given ConstraintOperator. @@ -195,10 +195,10 @@ struct ConstraintList { * @param op the ConstraintOperator. * @return A list of TEXT%-represented types matching the operator. */ - std::set getAll(ConstraintOperator op); + std::set getAll(ConstraintOperator op) const; template - std::set getAll(ConstraintOperator op) { + std::set getAll(ConstraintOperator op) const { std::set literal_matches; auto matches = getAll(op); for (const auto& match : matches) { @@ -273,9 +273,9 @@ typedef struct Constraint Constraint; class TablePlugin : public Plugin { protected: /// Helper method to generate the virtual table CREATE statement. - virtual std::string statement(); - virtual std::string columnDefinition(); - virtual TableColumns columns() { + virtual std::string statement() const; + virtual std::string columnDefinition() const; + virtual TableColumns columns() const { TableColumns columns; return columns; } diff --git a/osquery.thrift b/osquery.thrift index 3e3fa31..717b2ba 100644 --- a/osquery.thrift +++ b/osquery.thrift @@ -14,7 +14,7 @@ typedef i64 ExtensionRouteUUID typedef map ExtensionRoute typedef map ExtensionRouteTable typedef map ExtensionRegistry -typedef map ExtensionList +typedef map InternalExtensionList enum ExtensionCode { EXT_SUCCESS = 0, @@ -49,7 +49,7 @@ service Extension { } service ExtensionManager extends Extension { - ExtensionList extensions(), + InternalExtensionList extensions(), ExtensionStatus registerExtension( 1:InternalExtensionInfo info, 2:ExtensionRegistry registry), diff --git a/osquery/CMakeLists.txt b/osquery/CMakeLists.txt index 4ad8a2d..517816b 100644 --- a/osquery/CMakeLists.txt +++ b/osquery/CMakeLists.txt @@ -87,6 +87,7 @@ ENDMACRO(TARGET_OSQUERY_LINK_WHOLE) ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(config) ADD_SUBDIRECTORY(dispatcher) +ADD_SUBDIRECTORY(distributed) ADD_SUBDIRECTORY(devtools) ADD_SUBDIRECTORY(database) ADD_SUBDIRECTORY(events) diff --git a/osquery/core/system.cpp b/osquery/core/system.cpp index 336b7a6..0617915 100644 --- a/osquery/core/system.cpp +++ b/osquery/core/system.cpp @@ -106,13 +106,14 @@ Status checkStalePid(const std::string& content) { if (status != ESRCH) { // The pid is running, check if it is an osqueryd process by name. std::stringstream query_text; - query_text << "SELECT name FROM processes WHERE pid = " << pid << ";"; + query_text << "SELECT name FROM processes WHERE pid = " << pid + << " AND name = 'osqueryd';"; auto q = SQL(query_text.str()); if (!q.ok()) { return Status(1, "Error querying processes: " + q.getMessageString()); } - if (q.rows().size() >= 1 && q.rows().front()["name"] == "osqueryd") { + if (q.rows().size() > 0) { // If the process really is osqueryd, return an "error" status. if (FLAGS_force) { // The caller may choose to abort the existing daemon with --force. diff --git a/osquery/core/tables.cpp b/osquery/core/tables.cpp index 910418b..20f6d1d 100644 --- a/osquery/core/tables.cpp +++ b/osquery/core/tables.cpp @@ -17,7 +17,7 @@ namespace osquery { namespace tables { -bool ConstraintList::matches(const std::string& expr) { +bool ConstraintList::matches(const std::string& expr) const { // Support each SQL affinity type casting. if (affinity == "TEXT") { return literal_matches(expr); @@ -37,7 +37,7 @@ bool ConstraintList::matches(const std::string& expr) { } template -bool ConstraintList::literal_matches(const T& base_expr) { +bool ConstraintList::literal_matches(const T& base_expr) const { bool aggregate = true; for (size_t i = 0; i < constraints_.size(); ++i) { T constraint_expr = AS_LITERAL(T, constraints_[i].expr); @@ -63,7 +63,7 @@ bool ConstraintList::literal_matches(const T& base_expr) { return true; } -std::set ConstraintList::getAll(ConstraintOperator op) { +std::set ConstraintList::getAll(ConstraintOperator op) const { std::set set; for (size_t i = 0; i < constraints_.size(); ++i) { if (constraints_[i].op == op) { @@ -122,9 +122,7 @@ void TablePlugin::setRequestFromContext(const QueryContext& context, void TablePlugin::setResponseFromQueryData(const QueryData& data, PluginResponse& response) { - for (const auto& row : data) { - response.push_back(row); - } + response = std::move(data); } void TablePlugin::setContextFromRequest(const PluginRequest& request, @@ -170,7 +168,7 @@ Status TablePlugin::call(const PluginRequest& request, } else if (request.at("action") == "columns") { // "columns" returns a PluginRequest filled with column information // such as name and type. - auto column_list = columns(); + const auto& column_list = columns(); for (const auto& column : column_list) { response.push_back({{"name", column.first}, {"type", column.second}}); } @@ -183,20 +181,19 @@ Status TablePlugin::call(const PluginRequest& request, return Status(0, "OK"); } -std::string TablePlugin::columnDefinition() { +std::string TablePlugin::columnDefinition() const { const auto& column_list = columns(); std::string statement = "("; for (size_t i = 0; i < column_list.size(); ++i) { - statement += column_list[i].first + " " + column_list.at(i).second; + statement += column_list.at(i).first + " " + column_list.at(i).second; if (i < column_list.size() - 1) { statement += ", "; } } - statement += ")"; - return statement; + return statement += ")"; } -std::string TablePlugin::statement() { +std::string TablePlugin::statement() const { return "CREATE TABLE " + name_ + columnDefinition(); } diff --git a/osquery/dispatcher/dispatcher.cpp b/osquery/dispatcher/dispatcher.cpp index 20d450d..323c8ae 100644 --- a/osquery/dispatcher/dispatcher.cpp +++ b/osquery/dispatcher/dispatcher.cpp @@ -26,7 +26,7 @@ DEFINE_osquery_flag(int32, 4, "Number of work dispatch threads"); -void InternalRunnable::interruptableSleep(size_t milli) { +void interruptableSleep(size_t milli) { boost::this_thread::sleep(boost::posix_time::milliseconds(milli)); } diff --git a/osquery/distributed/CMakeLists.txt b/osquery/distributed/CMakeLists.txt new file mode 100644 index 0000000..1d49aa1 --- /dev/null +++ b/osquery/distributed/CMakeLists.txt @@ -0,0 +1,3 @@ +ADD_OSQUERY_LIBRARY(TRUE osquery_distributed distributed.cpp) + +ADD_OSQUERY_TEST(TRUE distributed_tests distributed_tests.cpp) diff --git a/osquery/distributed/distributed.cpp b/osquery/distributed/distributed.cpp new file mode 100644 index 0000000..bd7a5ca --- /dev/null +++ b/osquery/distributed/distributed.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2014, 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. + * + */ + +#include + +#include + +#include +#include +#include + +#include "osquery/distributed/distributed.h" + +namespace pt = boost::property_tree; + +namespace osquery { + +DEFINE_osquery_flag(int32, + distributed_get_queries_retries, + 3, + "Times to retry retrieving distributed queries"); + +DEFINE_osquery_flag(int32, + distributed_write_results_retries, + 3, + "Times to retry writing distributed query results"); + +Status MockDistributedProvider::getQueriesJSON(std::string& query_json) { + query_json = queriesJSON_; + return Status(); +} + +Status MockDistributedProvider::writeResultsJSON(const std::string& results) { + resultsJSON_ = results; + return Status(); +} + +Status DistributedQueryHandler::parseQueriesJSON( + const std::string& query_json, + std::vector& requests) { + // Parse the JSON into a ptree + pt::ptree tree; + try { + std::istringstream query_stream(query_json); + pt::read_json(query_stream, tree); + } + catch (const std::exception& e) { + return Status(1, std::string("Error loading query JSON: ") + e.what()); + } + + // Parse the ptree into DistributedQueryRequests + std::vector results; + for (const auto& node : tree) { + const auto& request_tree = node.second; + DistributedQueryRequest request; + try { + request.query = request_tree.get_child("query").get_value(); + request.id = request_tree.get_child("id").get_value(); + } catch (const std::exception& e) { + return Status(1, std::string("Error parsing queries: ") + e.what()); + } + results.push_back(request); + } + + requests = std::move(results); + + return Status(); +} + +SQL DistributedQueryHandler::handleQuery(const std::string& query_string) { + SQL query = SQL(query_string); + query.annotateHostInfo(); + return query; +} + +Status DistributedQueryHandler::serializeResults( + const std::vector >& results, + pt::ptree& tree) { + try { + pt::ptree& res_tree = tree.put_child("results", pt::ptree()); + for (const auto& result : results) { + DistributedQueryRequest request = result.first; + SQL sql = result.second; + pt::ptree& child = res_tree.put_child(request.id, pt::ptree()); + child.put("status", sql.getStatus().getCode()); + pt::ptree& rows_child = child.put_child("rows", pt::ptree()); + Status s = serializeQueryData(sql.rows(), rows_child); + if (!s.ok()) { + return s; + } + } + } + catch (const std::exception& e) { + return Status(1, std::string("Error serializing results: ") + e.what()); + } + return Status(); +} + +Status DistributedQueryHandler::doQueries() { + // Get and parse the queries + Status status; + std::string query_json; + int retries = 0; + do { + status = provider_->getQueriesJSON(query_json); + ++retries; + } while (!status.ok() && retries <= FLAGS_distributed_get_queries_retries); + if (!status.ok()) { + return status; + } + + std::vector requests; + status = parseQueriesJSON(query_json, requests); + if (!status.ok()) { + return status; + } + + // Run the queries + std::vector > query_results; + std::set successful_query_ids; + for (const auto& request : requests) { + if (executedRequestIds_.find(request.id) != executedRequestIds_.end()) { + // We've already successfully returned results for this request, don't + // process it again. + continue; + } + SQL query_result = handleQuery(request.query); + if (query_result.ok()) { + successful_query_ids.insert(request.id); + } + query_results.push_back({request, query_result}); + } + + // Serialize the results + pt::ptree serialized_results; + serializeResults(query_results, serialized_results); + std::string json; + try { + std::ostringstream ss; + pt::write_json(ss, serialized_results, false); + json = ss.str(); + } + catch (const std::exception& e) { + return Status(1, e.what()); + } + + // Write the results + retries = 0; + do { + status = provider_->writeResultsJSON(json); + ++retries; + } while (!status.ok() && retries <= FLAGS_distributed_write_results_retries); + if (!status.ok()) { + return status; + } + + // Only note that the queries were successfully completed if we were actually + // able to write the results. + executedRequestIds_.insert(successful_query_ids.begin(), + successful_query_ids.end()); + + return status; +} +} diff --git a/osquery/distributed/distributed.h b/osquery/distributed/distributed.h new file mode 100644 index 0000000..38b6857 --- /dev/null +++ b/osquery/distributed/distributed.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2014, 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 +#include + +#include + +#include +#include + +namespace osquery { + +/** + * @brief This is an interface for distributed query "providers" + * + * Providers implement the communication between the distributed query master + * and the individual host. A provider may utilize any communications strategy + * that supports reading and writing JSON (i.e. HTTPS requests, reading from a + * file, querying a message queue, etc.) + */ +class IDistributedProvider { +public: + virtual ~IDistributedProvider() {} + + /* + * @brief Get the JSON string containing the queries to be executed + * + * @param query_json A string to fill with the retrieved JSON + * + * @return osquery::Status indicating success or failure of the operation + */ + virtual Status getQueriesJSON(std::string& query_json) = 0; + + /* + * @brief Write the results JSON back to the master + * + * @param results A string containing the results JSON + * + * @return osquery::Status indicating success or failure of the operation + */ + virtual Status writeResultsJSON(const std::string& results) = 0; +}; + +/** + * @brief A mocked implementation of IDistributedProvider + * + * This implementation is useful for writing unit tests of the + * DistributedQueryHandler functionality. + */ +class MockDistributedProvider : public IDistributedProvider { +public: + // These methods just read/write the corresponding public members + Status getQueriesJSON(std::string& query_json) override; + Status writeResultsJSON(const std::string& results) override; + + std::string queriesJSON_; + std::string resultsJSON_; +}; + +/** + * @brief Small struct containing the query and ID information for a + * distributed query + */ +struct DistributedQueryRequest { +public: + explicit DistributedQueryRequest() {} + explicit DistributedQueryRequest(const std::string& q, const std::string& i) + : query(q), id(i) {} + std::string query; + std::string id; +}; + +/** + * @brief The main handler class for distributed queries + * + * This class is responsible for implementing the core functionality of + * distributed queries. It manages state, uses the provider to read/write from + * the master, and executes queries. + */ +class DistributedQueryHandler { +public: + /** + * @brief Construct a new handler with the given provider + * + * @param provider The provider used retrieving queries and writing results + */ + explicit DistributedQueryHandler( + std::unique_ptr provider) + : provider_(std::move(provider)) {} + + /** + * @brief Retrieve queries, run them, and write results + * + * This is the core method of DistributedQueryHandler, tying together all the + * other components to read the requests from the provider, execute the + * queries, and write the results back to the provider. + * + * @return osquery::Status indicating success or failure of the operation + */ + Status doQueries(); + + /** + * @brief Run and annotate an individual query + * + * @param query_string A string containing the query to be executed + * + * @return A SQL object containing the (annotated) query results + */ + static SQL handleQuery(const std::string& query_string); + + /** + * @brief Serialize the results of all requests into a ptree + * + * @param results The vector of requests and results + * @param tree The tree to serialize results into + * + * @return osquery::Status indicating success or failure of the operation + */ + static Status serializeResults( + const std::vector >& results, + boost::property_tree::ptree& tree); + + /** + * @brief Parse the query JSON into the individual query objects + * + * @param query_json The JSON string containing the queries + * @param requests A vector to fill with the query objects + * + * @return osquery::Status indicating success or failure of the parsing + */ + static Status parseQueriesJSON(const std::string& query_json, + std::vector& requests); + +private: + // The provider used to read and write queries and results + std::unique_ptr provider_; + + // Used to store already executed queries to avoid duplication. (Some master + // configurations may asynchronously process the results of requests, so a + // request might be seen by the host after it has already been executed.) + std::set executedRequestIds_; +}; + +} // namespace osquery diff --git a/osquery/distributed/distributed_tests.cpp b/osquery/distributed/distributed_tests.cpp new file mode 100644 index 0000000..6d5a8d4 --- /dev/null +++ b/osquery/distributed/distributed_tests.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2014, 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. + * + */ + +#include + +#include +#include +#include + +#include +#include + +#include "osquery/distributed/distributed.h" + +namespace pt = boost::property_tree; + +namespace osquery { + +class DistributedTests : public testing::Test {}; + +TEST_F(DistributedTests, test_test_distributed_provider) { + MockDistributedProvider p; + std::string query_string = "['foo']"; + std::string result_string = "['bar']"; + + p.queriesJSON_ = query_string; + std::string query_json; + Status s = p.getQueriesJSON(query_json); + ASSERT_EQ(Status(), s); + EXPECT_EQ(query_string, query_json); + + s = p.writeResultsJSON(result_string); + EXPECT_TRUE(s.ok()); + EXPECT_EQ(result_string, p.resultsJSON_); +} + +TEST_F(DistributedTests, test_parse_query_json) { + std::string request_json = R"([{"query": "foo", "id": "bar"}])"; + std::vector requests; + Status s = DistributedQueryHandler::parseQueriesJSON(request_json, requests); + ASSERT_EQ(Status(), s); + EXPECT_EQ(1, requests.size()); + EXPECT_EQ("foo", requests[0].query); + EXPECT_EQ("bar", requests[0].id); + + std::string bad_json = R"([{"query": "foo", "id": "bar"}, {"query": "b"}])"; + requests.clear(); + s = DistributedQueryHandler::parseQueriesJSON(bad_json, requests); + ASSERT_FALSE(s.ok()); + EXPECT_EQ(0, requests.size()); +} + +TEST_F(DistributedTests, test_handle_query) { + SQL query = DistributedQueryHandler::handleQuery("SELECT hour from time"); + ASSERT_TRUE(query.ok()); + QueryData rows = query.rows(); + ASSERT_EQ(1, rows.size()); + EXPECT_EQ(rows[0]["_source_host"], getHostname()); + + query = DistributedQueryHandler::handleQuery("bad query"); + ASSERT_FALSE(query.ok()); + rows = query.rows(); + ASSERT_EQ(0, rows.size()); +} + +TEST_F(DistributedTests, test_serialize_results_empty) { + DistributedQueryRequest r0("foo", "foo_id"); + MockSQL q0 = MockSQL(); + pt::ptree tree; + + DistributedQueryHandler::serializeResults({{r0, q0}}, tree); + + EXPECT_EQ(0, tree.get("results.foo_id.status")); + EXPECT_TRUE(tree.get_child("results.foo_id.rows").empty()); +} + +TEST_F(DistributedTests, test_serialize_results_basic) { + DistributedQueryRequest r0("foo", "foo_id"); + QueryData rows0 = {{{"foo0", "foo0_val"}, {"bar0", "bar0_val"}}, + {{"foo1", "foo1_val"}, {"bar1", "bar1_val"}}, }; + MockSQL q0 = MockSQL(rows0); + pt::ptree tree; + + DistributedQueryHandler::serializeResults({{r0, q0}}, tree); + + EXPECT_EQ(0, tree.get("results.foo_id.status")); + + const pt::ptree& tree_rows = tree.get_child("results.foo_id.rows"); + EXPECT_EQ(2, tree_rows.size()); + + auto row = tree_rows.begin(); + EXPECT_EQ("foo0_val", row->second.get("foo0")); + EXPECT_EQ("bar0_val", row->second.get("bar0")); + ++row; + EXPECT_EQ("foo1_val", row->second.get("foo1")); + EXPECT_EQ("bar1_val", row->second.get("bar1")); +} + +TEST_F(DistributedTests, test_serialize_results_multiple) { + DistributedQueryRequest r0("foo", "foo_id"); + QueryData rows0 = {{{"foo0", "foo0_val"}, {"bar0", "bar0_val"}}, + {{"foo1", "foo1_val"}, {"bar1", "bar1_val"}}, }; + MockSQL q0 = MockSQL(rows0); + + DistributedQueryRequest r1("bar", "bar_id"); + MockSQL q1 = MockSQL({}, Status(1, "Fail")); + + pt::ptree tree; + + DistributedQueryHandler::serializeResults({{r0, q0}, {r1, q1}}, tree); + + EXPECT_EQ(0, tree.get("results.foo_id.status")); + const pt::ptree& tree_rows = tree.get_child("results.foo_id.rows"); + EXPECT_EQ(2, tree_rows.size()); + auto row = tree_rows.begin(); + EXPECT_EQ("foo0_val", row->second.get("foo0")); + EXPECT_EQ("bar0_val", row->second.get("bar0")); + ++row; + EXPECT_EQ("foo1_val", row->second.get("foo1")); + EXPECT_EQ("bar1_val", row->second.get("bar1")); + + EXPECT_EQ(1, tree.get("results.bar_id.status")); + const pt::ptree& fail_rows = tree.get_child("results.bar_id.rows"); + EXPECT_EQ(0, fail_rows.size()); +} + +TEST_F(DistributedTests, test_do_queries) { + auto provider_raw = new MockDistributedProvider(); + provider_raw->queriesJSON_ = + R"([ + {"query": "SELECT hour FROM time", "id": "hour"}, + {"query": "bad", "id": "bad"}, + {"query": "SELECT minutes FROM time", "id": "minutes"} + ])"; + std::unique_ptr + provider(provider_raw); + DistributedQueryHandler handler(std::move(provider)); + + Status s = handler.doQueries(); + ASSERT_EQ(Status(), s); + + pt::ptree tree; + std::istringstream json_stream(provider_raw->resultsJSON_); + ASSERT_NO_THROW(pt::read_json(json_stream, tree)); + + { + EXPECT_EQ(0, tree.get("results.hour.status")); + const pt::ptree& tree_rows = tree.get_child("results.hour.rows"); + EXPECT_EQ(1, tree_rows.size()); + auto row = tree_rows.begin(); + EXPECT_GE(row->second.get("hour"), 0); + EXPECT_LE(row->second.get("hour"), 24); + EXPECT_EQ(getHostname(), row->second.get("_source_host")); + } + + { + // this query should have failed + EXPECT_EQ(1, tree.get("results.bad.status")); + const pt::ptree& tree_rows = tree.get_child("results.bad.rows"); + EXPECT_EQ(0, tree_rows.size()); + } + + { + EXPECT_EQ(0, tree.get("results.minutes.status")); + const pt::ptree& tree_rows = tree.get_child("results.minutes.rows"); + EXPECT_EQ(1, tree_rows.size()); + auto row = tree_rows.begin(); + EXPECT_GE(row->second.get("minutes"), 0); + EXPECT_LE(row->second.get("minutes"), 60); + EXPECT_EQ(getHostname(), row->second.get("_source_host")); + } +} + +TEST_F(DistributedTests, test_duplicate_request) { + auto provider_raw = new MockDistributedProvider(); + provider_raw->queriesJSON_ = + R"([ + {"query": "SELECT hour FROM time", "id": "hour"} + ])"; + std::unique_ptr + provider(provider_raw); + DistributedQueryHandler handler(std::move(provider)); + + Status s = handler.doQueries(); + ASSERT_EQ(Status(), s); + + pt::ptree tree; + std::istringstream json_stream(provider_raw->resultsJSON_); + ASSERT_NO_THROW(pt::read_json(json_stream, tree)); + + EXPECT_EQ(0, tree.get("results.hour.status")); + const pt::ptree& tree_rows = tree.get_child("results.hour.rows"); + EXPECT_EQ(1, tree_rows.size()); + auto row = tree_rows.begin(); + EXPECT_GE(row->second.get("hour"), 0); + EXPECT_LE(row->second.get("hour"), 24); + EXPECT_EQ(getHostname(), row->second.get("_source_host")); + + // The second time, 'hour' should not be executed again + s = handler.doQueries(); + ASSERT_EQ(Status(), s); + json_stream.str(provider_raw->resultsJSON_); + ASSERT_NO_THROW(pt::read_json(json_stream, tree)); + EXPECT_EQ(0, tree.get_child("results").size()); +} +} + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + osquery::initOsquery(argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/osquery/events/events.cpp b/osquery/events/events.cpp index e26e84a..7c38d36 100644 --- a/osquery/events/events.cpp +++ b/osquery/events/events.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include #include @@ -24,6 +23,9 @@ namespace osquery { +/// Helper cooloff (ms) macro to prevent thread failure thrashing. +#define EVENTS_COOLOFF 20 + DEFINE_osquery_flag(bool, disable_events, false, @@ -417,7 +419,7 @@ Status EventFactory::run(EventPublisherID& type_id) { while (!publisher->isEnding() && status.ok()) { // Can optionally implement a global cooloff latency here. status = publisher->run(); - ::usleep(20); + osquery::interruptableSleep(EVENTS_COOLOFF); } // The runloop status is not reflective of the event type's. diff --git a/osquery/events/linux/inotify.cpp b/osquery/events/linux/inotify.cpp index f91a673..12ff672 100644 --- a/osquery/events/linux/inotify.cpp +++ b/osquery/events/linux/inotify.cpp @@ -20,7 +20,8 @@ namespace osquery { -int kINotifyULatency = 200; +int kINotifyMLatency = 200; + static const uint32_t BUFFER_SIZE = (10 * ((sizeof(struct inotify_event)) + NAME_MAX + 1)); @@ -70,7 +71,7 @@ Status INotifyEventPublisher::run() { FD_ZERO(&set); FD_SET(getHandle(), &set); - struct timeval timeout = {0, kINotifyULatency}; + struct timeval timeout = {0, kINotifyMLatency}; int selector = ::select(getHandle() + 1, &set, nullptr, nullptr, &timeout); if (selector == -1) { LOG(ERROR) << "Could not read inotify handle"; @@ -111,7 +112,7 @@ Status INotifyEventPublisher::run() { p += (sizeof(struct inotify_event)) + event->len; } - ::usleep(kINotifyULatency); + osquery::interruptableSleep(kINotifyMLatency); return Status(0, "Continue"); } @@ -140,7 +141,7 @@ INotifyEventContextRef INotifyEventPublisher::createEventContextFrom( } bool INotifyEventPublisher::shouldFire(const INotifySubscriptionContextRef& sc, - const INotifyEventContextRef& ec) { + const INotifyEventContextRef& ec) const { if (!sc->recursive && sc->path != ec->path) { // Monitored path is not recursive and path is not an exact match. return false; diff --git a/osquery/events/linux/inotify.h b/osquery/events/linux/inotify.h index a0bbd32..7325687 100644 --- a/osquery/events/linux/inotify.h +++ b/osquery/events/linux/inotify.h @@ -122,7 +122,7 @@ class INotifyEventPublisher bool removeMonitor(int watch, bool force = false); /// Given a SubscriptionContext and INotifyEventContext match path and action. bool shouldFire(const INotifySubscriptionContextRef& mc, - const INotifyEventContextRef& ec); + const INotifyEventContextRef& ec) const; /// Get the INotify file descriptor. int getHandle() { return inotify_handle_; } /// Get the number of actual INotify active descriptors. diff --git a/osquery/events/linux/udev.cpp b/osquery/events/linux/udev.cpp index 74d6e50..4c3e899 100644 --- a/osquery/events/linux/udev.cpp +++ b/osquery/events/linux/udev.cpp @@ -16,7 +16,7 @@ namespace osquery { -int kUdevULatency = 200; +int kUdevMLatency = 200; REGISTER(UdevEventPublisher, "event_publisher", "udev"); @@ -76,7 +76,7 @@ Status UdevEventPublisher::run() { udev_device_unref(device); - ::usleep(kUdevULatency); + osquery::interruptableSleep(kUdevMLatency); return Status(0, "Continue"); } @@ -138,7 +138,7 @@ UdevEventContextRef UdevEventPublisher::createEventContextFrom( } bool UdevEventPublisher::shouldFire(const UdevSubscriptionContextRef& sc, - const UdevEventContextRef& ec) { + const UdevEventContextRef& ec) const { if (sc->action != UDEV_EVENT_ACTION_ALL) { if (sc->action != ec->action) { return false; diff --git a/osquery/events/linux/udev.h b/osquery/events/linux/udev.h index b289396..7ce8deb 100644 --- a/osquery/events/linux/udev.h +++ b/osquery/events/linux/udev.h @@ -113,7 +113,7 @@ class UdevEventPublisher private: /// Check subscription details. bool shouldFire(const UdevSubscriptionContextRef& mc, - const UdevEventContextRef& ec); + const UdevEventContextRef& ec) const; /// Helper function to create an EventContext using a udev_device pointer. UdevEventContextRef createEventContextFrom(struct udev_device* device); }; diff --git a/osquery/extensions/extensions.cpp b/osquery/extensions/extensions.cpp index afdaa9f..18421e9 100644 --- a/osquery/extensions/extensions.cpp +++ b/osquery/extensions/extensions.cpp @@ -8,6 +8,7 @@ * */ +#include #include #include #include @@ -271,13 +272,84 @@ Status startExtension(const std::string& manager_path, return Status(0, std::to_string(status.uuid)); } +Status pingExtension(const std::string& path) { + if (FLAGS_disable_extensions) { + return Status(1, "Extensions disabled"); + } + + // Make sure the extension path exists, and is writable. + if (!pathExists(path) || !isWritable(path)) { + return Status(1, "Extension socket not availabe: " + path); + } + + // Open a socket to the extension. + boost::shared_ptr socket(new TSocket(path)); + boost::shared_ptr transport(new TBufferedTransport(socket)); + boost::shared_ptr protocol(new TBinaryProtocol(transport)); + + ExtensionClient client(protocol); + ExtensionStatus ext_status; + try { + transport->open(); + client.ping(ext_status); + transport->close(); + } catch (const std::exception& e) { + return Status(1, "Extension call failed: " + std::string(e.what())); + } + + return Status(ext_status.code, ext_status.message); +} + +Status getExtensions(ExtensionList& extensions) { + if (FLAGS_disable_extensions) { + return Status(1, "Extensions disabled"); + } + return getExtensions(FLAGS_extensions_socket, extensions); +} + +Status getExtensions(const std::string& manager_path, + ExtensionList& extensions) { + // Make sure the extension path exists, and is writable. + if (!pathExists(manager_path) || !isWritable(manager_path)) { + return Status(1, "Extension manager socket not availabe: " + manager_path); + } + + // Open a socket to the extension. + boost::shared_ptr socket(new TSocket(manager_path)); + boost::shared_ptr transport(new TBufferedTransport(socket)); + boost::shared_ptr protocol(new TBinaryProtocol(transport)); + + ExtensionManagerClient client(protocol); + InternalExtensionList ext_list; + try { + transport->open(); + client.extensions(ext_list); + transport->close(); + } catch (const std::exception& e) { + return Status(1, "Extension call failed: " + std::string(e.what())); + } + + // Add the extension manager to the list called (core). + extensions.insert(std::make_pair(0, ExtensionInfo("core"))); + + // Convert from Thrift-internal list type to RouteUUID/ExtenionInfo type. + for (const auto& extension : ext_list) { + extensions[extension.first] = extension.second; + } + + return Status(0, "OK"); +} + Status callExtension(const RouteUUID uuid, const std::string& registry, const std::string& item, const PluginRequest& request, PluginResponse& response) { - // Not yet implemented. - return Status(0, "OK"); + if (FLAGS_disable_extensions) { + return Status(1, "Extensions disabled"); + } + return callExtension( + getExtensionSocket(uuid), registry, item, request, response); } Status callExtension(const std::string& extension_path, @@ -306,6 +378,7 @@ Status callExtension(const std::string& extension_path, return Status(1, "Extension call failed: " + std::string(e.what())); } + // Convert from Thrift-internal list type to PluginResponse type. if (ext_response.status.code == ExtensionCode::EXT_SUCCESS) { for (const auto& item : ext_response.response) { response.push_back(item); @@ -329,6 +402,9 @@ Status startExtensionWatcher(const std::string& manager_path, } Status startExtensionManager() { + if (FLAGS_disable_extensions) { + return Status(1, "Extensions disabled"); + } return startExtensionManager(FLAGS_extensions_socket); } diff --git a/osquery/extensions/extensions_tests.cpp b/osquery/extensions/extensions_tests.cpp index d724b75..30626da 100644 --- a/osquery/extensions/extensions_tests.cpp +++ b/osquery/extensions/extensions_tests.cpp @@ -73,23 +73,10 @@ class ExtensionsTest : public testing::Test { } ExtensionList registeredExtensions(int attempts = 3) { - // Open a socket to the test extension manager. - boost::shared_ptr socket(new TSocket(kTestManagerSocket)); - boost::shared_ptr transport(new TBufferedTransport(socket)); - boost::shared_ptr protocol(new TBinaryProtocol(transport)); - - ExtensionManagerClient client(protocol); - - // Calling open will except if the socket does not exist. ExtensionList extensions; for (int i = 0; i < attempts; ++i) { - try { - transport->open(); - client.extensions(extensions); - transport->close(); - } - catch (const std::exception& e) { - ::usleep(kDelayUS); + if (getExtensions(kTestManagerSocket, extensions).ok()) { + break; } } @@ -222,7 +209,8 @@ TEST_F(ExtensionsTest, test_extension_broadcast) { // Make sure the EM registered the extension (called in start extension). auto extensions = registeredExtensions(); - EXPECT_EQ(extensions.size(), 1); + // Expect two, since `getExtensions` includes the core. + EXPECT_EQ(extensions.size(), 2); EXPECT_EQ(extensions.count(uuid), 1); EXPECT_EQ(extensions.at(uuid).name, "test"); EXPECT_EQ(extensions.at(uuid).version, "0.1"); diff --git a/osquery/filesystem/filesystem.cpp b/osquery/filesystem/filesystem.cpp index 585e341..0719738 100644 --- a/osquery/filesystem/filesystem.cpp +++ b/osquery/filesystem/filesystem.cpp @@ -485,4 +485,15 @@ std::vector getHomeDirectories() { } return results; } + +std::string lsperms(int mode) { + static const char rwx[] = {'0', '1', '2', '3', '4', '5', '6', '7'}; + std::string bits; + + bits += rwx[(mode >> 9) & 7]; + bits += rwx[(mode >> 6) & 7]; + bits += rwx[(mode >> 3) & 7]; + bits += rwx[(mode >> 0) & 7]; + return bits; +} } diff --git a/osquery/filesystem/linux/proc.cpp b/osquery/filesystem/linux/proc.cpp index 3c28579..bfbd601 100644 --- a/osquery/filesystem/linux/proc.cpp +++ b/osquery/filesystem/linux/proc.cpp @@ -10,11 +10,12 @@ #include #include +#include #include +#include #include -#include #include #include @@ -25,21 +26,19 @@ namespace osquery { const std::string kLinuxProcPath = "/proc"; Status procProcesses(std::vector& processes) { - boost::regex process_filter("\\d+"); // Iterate over each process-like directory in proc. boost::filesystem::directory_iterator it(kLinuxProcPath), end; + std::regex process_filter("[0-9]+", std::regex_constants::extended); try { for (; it != end; ++it) { if (boost::filesystem::is_directory(it->status())) { - boost::smatch what; - if (boost::regex_match( - it->path().leaf().string(), what, process_filter)) { + if (std::regex_match(it->path().leaf().string(), process_filter)) { processes.push_back(it->path().leaf().string()); } } } - } catch (boost::filesystem::filesystem_error& e) { + } catch (const boost::filesystem::filesystem_error& e) { VLOG(1) << "Exception iterating Linux processes " << e.what(); return Status(1, e.what()); } @@ -71,16 +70,13 @@ Status procReadDescriptor(const std::string& process, const std::string& descriptor, std::string& result) { auto link = kLinuxProcPath + "/" + process + "/fd/" + descriptor; - auto path_max = pathconf(link.c_str(), _PC_PATH_MAX); - auto result_path = (char*)malloc(path_max); - memset(result_path, 0, path_max); - auto size = readlink(link.c_str(), result_path, path_max); + char result_path[PATH_MAX] = {0}; + auto size = readlink(link.c_str(), result_path, sizeof(result_path) - 1); if (size >= 0) { result = std::string(result_path); } - free(result_path); if (size >= 0) { return Status(0, "OK"); } else { diff --git a/osquery/main/run.cpp b/osquery/main/run.cpp index 545d911..a5a7944 100644 --- a/osquery/main/run.cpp +++ b/osquery/main/run.cpp @@ -37,28 +37,28 @@ int main(int argc, char* argv[]) { osquery::Registry::setUp(); osquery::attachEvents(); - int result = 0; if (FLAGS_delay != 0) { ::sleep(FLAGS_delay); } osquery::QueryData results; + osquery::Status status; for (int i = 0; i < FLAGS_iterations; ++i) { - printf("Executing: %s\n", FLAGS_query.c_str()); - auto status = osquery::query(FLAGS_query, results); + status = osquery::query(FLAGS_query, results); if (!status.ok()) { fprintf(stderr, "Query failed: %d\n", status.getCode()); break; - } else { - if (FLAGS_delay != 0) { - ::sleep(FLAGS_delay); - } } } + if (FLAGS_delay != 0) { + ::sleep(FLAGS_delay); + } + + // Instead of calling "shutdownOsquery" force the EF to join its threads. osquery::EventFactory::end(true); __GFLAGS_NAMESPACE::ShutDownCommandLineFlags(); - return result; + return status.getCode(); } diff --git a/osquery/registry/registry.cpp b/osquery/registry/registry.cpp index 704ec4d..882f9e1 100644 --- a/osquery/registry/registry.cpp +++ b/osquery/registry/registry.cpp @@ -60,7 +60,7 @@ Status RegistryHelperCore::call(const std::string& item_name, const PluginRequest& request, PluginResponse& response) { if (items_.count(item_name) > 0) { - return items_[item_name]->call(request, response); + return items_.at(item_name)->call(request, response); } return Status(1, "Cannot call registry item: " + item_name); } @@ -206,7 +206,7 @@ Status RegistryFactory::call(const std::string& registry_name, if (instance().registries_.count(registry_name) == 0) { return Status(1, "Unknown registry: " + registry_name); } - return instance().registries_[registry_name]->call( + return instance().registries_.at(registry_name)->call( item_name, request, response); } diff --git a/osquery/registry/registry_tests.cpp b/osquery/registry/registry_tests.cpp index a6ee7b1..1118b0c 100644 --- a/osquery/registry/registry_tests.cpp +++ b/osquery/registry/registry_tests.cpp @@ -154,7 +154,7 @@ class WidgetPlugin : public Plugin { /// to parse and format. BUT a plugin/registry item can also fill this /// information in if the plugin type/registry type exposes routeInfo as /// a virtual method. - RouteInfo routeInfo() { + RouteInfo routeInfo() const { RouteInfo info; info["name"] = name_; return info; @@ -162,7 +162,7 @@ class WidgetPlugin : public Plugin { /// Plugin types should contain generic request/response formatters and /// decorators. - std::string secretPower(const PluginRequest& request) { + std::string secretPower(const PluginRequest& request) const { if (request.count("secret_power") > 0) { return request.at("secret_power"); } @@ -183,8 +183,12 @@ Status SpecialWidget::call(const PluginRequest& request, return Status(0, "OK"); } +#define UNUSED(x) (void)(x) + TEST_F(RegistryTests, test_registry_api) { auto AutoWidgetRegistry = TestCoreRegistry::create("widgets"); + UNUSED(AutoWidgetRegistry); + TestCoreRegistry::add("widgets", "special"); // Test route info propogation, from item to registry, to broadcast. diff --git a/osquery/sql/sql.cpp b/osquery/sql/sql.cpp index 557915b..d828036 100644 --- a/osquery/sql/sql.cpp +++ b/osquery/sql/sql.cpp @@ -30,12 +30,22 @@ const std::map kSQLOperatorRepr = { SQL::SQL(const std::string& q) { status_ = query(q, results_); } -QueryData SQL::rows() { return results_; } +const QueryData& SQL::rows() { return results_; } bool SQL::ok() { return status_.ok(); } +Status SQL::getStatus() { return status_; } + std::string SQL::getMessageString() { return status_.toString(); } +const std::string SQL::kHostColumnName = "_source_host"; +void SQL::annotateHostInfo() { + std::string hostname = getHostname(); + for (Row& row : results_) { + row[kHostColumnName] = hostname; + } +} + std::vector SQL::getTableNames() { std::vector results; for (const auto& name : Registry::names("table")) { diff --git a/osquery/sql/sql_tests.cpp b/osquery/sql/sql_tests.cpp index d681146..b2c79a5 100644 --- a/osquery/sql/sql_tests.cpp +++ b/osquery/sql/sql_tests.cpp @@ -37,7 +37,7 @@ TEST_F(SQLTests, test_raw_access) { class TestTable : public tables::TablePlugin { private: - tables::TableColumns columns() { + tables::TableColumns columns() const { return {{"test_int", "INTEGER"}, {"test_text", "TEXT"}}; } diff --git a/osquery/sql/sqlite_util.cpp b/osquery/sql/sqlite_util.cpp index 2fa271e..f5bb3b1 100644 --- a/osquery/sql/sqlite_util.cpp +++ b/osquery/sql/sqlite_util.cpp @@ -52,6 +52,51 @@ const std::map kSQLiteReturnCodes = { {101, "SQLITE_DONE: sqlite3_step() has finished executing"}, }; +SQLiteDBInstance::SQLiteDBInstance() { + primary_ = false; + sqlite3_open(":memory:", &db_); + tables::attachVirtualTables(db_); +} + +SQLiteDBInstance::SQLiteDBInstance(sqlite3*& db) { + primary_ = true; + db_ = db; +} + +SQLiteDBInstance::~SQLiteDBInstance() { + if (!primary_) { + sqlite3_close(db_); + } else { + SQLiteDBManager::unlock(); + } +} + +void SQLiteDBManager::unlock() { instance().lock_.unlock(); } + +SQLiteDBInstance SQLiteDBManager::getUnique() { return SQLiteDBInstance(); } + +SQLiteDBInstance SQLiteDBManager::get() { + auto& self = instance(); + + if (!self.lock_.owns_lock() && self.lock_.try_lock()) { + if (self.db_ == nullptr) { + // Create primary sqlite DB instance. + sqlite3_open(":memory:", &self.db_); + tables::attachVirtualTables(self.db_); + } + return SQLiteDBInstance(self.db_); + } else { + // If this thread or another has the lock, return a transient db. + return SQLiteDBInstance(); + } +} + +SQLiteDBManager::~SQLiteDBManager() { + if (db_ != nullptr) { + sqlite3_close(db_); + } +} + std::string getStringForSQLiteReturnCode(int code) { if (kSQLiteReturnCodes.find(code) != kSQLiteReturnCodes.end()) { return kSQLiteReturnCodes.at(code); @@ -62,13 +107,6 @@ std::string getStringForSQLiteReturnCode(int code) { } } -sqlite3* createDB() { - sqlite3* db = nullptr; - sqlite3_open(":memory:", &db); - tables::attachVirtualTables(db); - return db; -} - int queryDataCallback(void* argument, int argc, char* argv[], char* column[]) { if (argument == nullptr) { LOG(ERROR) << "queryDataCallback received nullptr as data argument"; @@ -85,9 +123,8 @@ int queryDataCallback(void* argument, int argc, char* argv[], char* column[]) { } Status queryInternal(const std::string& q, QueryData& results) { - sqlite3* db = createDB(); - auto status = queryInternal(q, results, db); - sqlite3_close(db); + auto dbc = SQLiteDBManager::get(); + auto status = queryInternal(q, results, dbc.db()); return status; } @@ -104,9 +141,8 @@ Status queryInternal(const std::string& q, QueryData& results, sqlite3* db) { Status getQueryColumnsInternal(const std::string& q, tables::TableColumns& columns) { - sqlite3* db = createDB(); - Status status = getQueryColumnsInternal(q, columns, db); - sqlite3_close(db); + auto dbc = SQLiteDBManager::get(); + Status status = getQueryColumnsInternal(q, columns, dbc.db()); return status; } diff --git a/osquery/sql/sqlite_util.h b/osquery/sql/sqlite_util.h index 95775eb..051e0d0 100644 --- a/osquery/sql/sqlite_util.h +++ b/osquery/sql/sqlite_util.h @@ -10,9 +10,99 @@ #pragma once +#include +#include + #include +#include +#include + namespace osquery { + +/** + * @brief An RAII wrapper around an `sqlite3` object. + * + * The SQLiteDBInstance is also "smart" in that it may unlock access to a + * managed `sqlite3` resource. If there's no contention then only a single + * database is needed during the life of an osquery tool. + * + * If there is resource contention (multiple threads want access to the SQLite + * abstraction layer), then the SQLiteDBManager will provide a transient + * SQLiteDBInstance. + */ +class SQLiteDBInstance { + public: + SQLiteDBInstance(); + SQLiteDBInstance(sqlite3*& db); + ~SQLiteDBInstance(); + + /** + * @brief Accessor to the internal `sqlite3` object, do not store references + * to the object within osquery code. + */ + sqlite3* db() { return db_; } + + private: + bool primary_; + sqlite3* db_; +}; + +/** + * @brief osquery internal SQLite DB abstraction resource management. + * + * The SQLiteDBManager should be the ONLY method for accessing SQLite resources. + * The manager provides an abstraction to manage internal SQLite memory and + * resources as well as provide optimization around resource access. + */ +class SQLiteDBManager : private boost::noncopyable { + public: + static SQLiteDBManager& instance() { + static SQLiteDBManager instance; + return instance; + } + + /** + * @brief Return a fully configured `sqlite3` database object wrapper. + * + * An osquery database is basically just a SQLite3 database with several + * virtual tables attached. This method is the main abstraction for accessing + * SQLite3 databases within osquery. + * + * A RAII wrapper around the `sqlite3` database will manage attaching tables + * and freeing resources when the instance (connection per-say) goes out of + * scope. Using the SQLiteDBManager will also try to optimize the number of + * `sqlite3` databases in use by managing a single global instance and + * returning resource-safe transient databases if there's access contention. + * + * Note: osquery::initOsquery must be called before calling `get` in order + * for virtual tables to be registered. + * + * @return a SQLiteDBInstance with all virtual tables attached. + */ + static SQLiteDBInstance get(); + + /// See `get` but always return a transient DB connection (for testing). + static SQLiteDBInstance getUnique(); + + /// When the primary SQLiteDBInstance is destructed it will unlock. + static void unlock(); + + protected: + SQLiteDBManager() : db_(nullptr), lock_(mutex_, boost::defer_lock) {} + SQLiteDBManager(SQLiteDBManager const&); + void operator=(SQLiteDBManager const&); + virtual ~SQLiteDBManager(); + + private: + /// Primary (managed) sqlite3 database. + sqlite3* db_; + /// Mutex and lock around sqlite3 access. + boost::mutex mutex_; + /// Mutex and lock around sqlite3 access. + boost::unique_lock lock_; +}; + /** * @brief A map of SQLite status codes to their corresponding message string * @@ -60,20 +150,6 @@ Status getQueryColumnsInternal(const std::string& q, sqlite3* db); /** - * @brief Return a fully configured sqlite3 database object - * - * An osquery database is basically just a SQLite3 database with several - * virtual tables attached. This method is the main abstraction for creating - * SQLite3 databases within osquery. - * - * Note: osquery::initOsquery must be called before calling createDB in order - * for virtual tables to be registered. - * - * @return a SQLite3 database with all virtual tables attached - */ -sqlite3* createDB(); - -/** * @brief Get a string representation of a SQLite return code */ std::string getStringForSQLiteReturnCode(int code); diff --git a/osquery/sql/sqlite_util_tests.cpp b/osquery/sql/sqlite_util_tests.cpp index cc3331b..780a9ff 100644 --- a/osquery/sql/sqlite_util_tests.cpp +++ b/osquery/sql/sqlite_util_tests.cpp @@ -22,8 +22,8 @@ namespace osquery { class SQLiteUtilTests : public testing::Test {}; -sqlite3* createTestDB() { - sqlite3* db = createDB(); +SQLiteDBInstance getTestDBC() { + SQLiteDBInstance dbc = SQLiteDBManager::getUnique(); char* err = nullptr; std::vector queries = { "CREATE TABLE test_table (" @@ -33,29 +33,43 @@ sqlite3* createTestDB() { "INSERT INTO test_table VALUES (\"mike\", 23)", "INSERT INTO test_table VALUES (\"matt\", 24)"}; for (auto q : queries) { - sqlite3_exec(db, q.c_str(), nullptr, nullptr, &err); + sqlite3_exec(dbc.db(), q.c_str(), nullptr, nullptr, &err); if (err != nullptr) { - return nullptr; + throw std::domain_error("Cannot create testing DBC's db."); } } - return db; + return dbc; +} + +TEST_F(SQLiteUtilTests, test_sqlite_instance_manager) { + auto dbc1 = SQLiteDBManager::get(); + auto dbc2 = SQLiteDBManager::get(); + EXPECT_NE(dbc1.db(), dbc2.db()); + EXPECT_EQ(dbc1.db(), dbc1.db()); +} + +TEST_F(SQLiteUtilTests, test_sqlite_instance) { + // Don't do this at home kids. + // Keep a copy of the internal DB and let the SQLiteDBInstance go oos. + auto internal_db = SQLiteDBManager::get().db(); + // Compare the internal DB to another request with no SQLiteDBInstances + // in scope, meaning the primary will be returned. + EXPECT_EQ(internal_db, SQLiteDBManager::get().db()); } TEST_F(SQLiteUtilTests, test_simple_query_execution) { - auto db = createTestDB(); + auto dbc = getTestDBC(); QueryData results; - auto status = queryInternal(kTestQuery, results, db); - sqlite3_close(db); + auto status = queryInternal(kTestQuery, results, dbc.db()); EXPECT_TRUE(status.ok()); EXPECT_EQ(results, getTestDBExpectedResults()); } TEST_F(SQLiteUtilTests, test_passing_callback_no_data_param) { char* err = nullptr; - auto db = createTestDB(); - sqlite3_exec(db, kTestQuery.c_str(), queryDataCallback, nullptr, &err); - sqlite3_close(db); + auto dbc = getTestDBC(); + sqlite3_exec(dbc.db(), kTestQuery.c_str(), queryDataCallback, nullptr, &err); EXPECT_TRUE(err != nullptr); if (err != nullptr) { sqlite3_free(err); @@ -63,20 +77,19 @@ TEST_F(SQLiteUtilTests, test_passing_callback_no_data_param) { } TEST_F(SQLiteUtilTests, test_aggregate_query) { - auto db = createTestDB(); + auto dbc = getTestDBC(); QueryData results; - auto status = queryInternal(kTestQuery, results, db); - sqlite3_close(db); + auto status = queryInternal(kTestQuery, results, dbc.db()); EXPECT_TRUE(status.ok()); EXPECT_EQ(results, getTestDBExpectedResults()); } TEST_F(SQLiteUtilTests, test_get_test_db_result_stream) { - auto db = createTestDB(); + auto dbc = getTestDBC(); auto results = getTestDBResultStream(); for (auto r : results) { char* err_char = nullptr; - sqlite3_exec(db, (r.first).c_str(), nullptr, nullptr, &err_char); + sqlite3_exec(dbc.db(), (r.first).c_str(), nullptr, nullptr, &err_char); EXPECT_TRUE(err_char == nullptr); if (err_char != nullptr) { sqlite3_free(err_char); @@ -84,16 +97,13 @@ TEST_F(SQLiteUtilTests, test_get_test_db_result_stream) { } QueryData expected; - auto status = queryInternal(kTestQuery, expected, db); + auto status = queryInternal(kTestQuery, expected, dbc.db()); EXPECT_EQ(expected, r.second); } - sqlite3_close(db); } TEST_F(SQLiteUtilTests, test_get_query_columns) { - std::unique_ptr db_managed(createDB(), - sqlite3_close); - sqlite3* db = db_managed.get(); + auto dbc = getTestDBC(); std::string query; Status status; @@ -102,7 +112,7 @@ TEST_F(SQLiteUtilTests, test_get_query_columns) { query = "SELECT hour, minutes, seconds, version, config_md5, config_path, \ pid FROM time JOIN osquery_info"; - status = getQueryColumnsInternal(query, results, db); + status = getQueryColumnsInternal(query, results, dbc.db()); ASSERT_TRUE(status.ok()); ASSERT_EQ(7, results.size()); EXPECT_EQ(std::make_pair(std::string("hour"), std::string("INTEGER")), @@ -121,7 +131,7 @@ TEST_F(SQLiteUtilTests, test_get_query_columns) { results[6]); query = "SELECT hour + 1 AS hour1, minutes + 1 FROM time"; - status = getQueryColumnsInternal(query, results, db); + status = getQueryColumnsInternal(query, results, dbc.db()); ASSERT_TRUE(status.ok()); ASSERT_EQ(2, results.size()); EXPECT_EQ(std::make_pair(std::string("hour1"), std::string("UNKNOWN")), @@ -130,7 +140,7 @@ TEST_F(SQLiteUtilTests, test_get_query_columns) { results[1]); query = "SELECT * FROM foo"; - status = getQueryColumnsInternal(query, results, db); + status = getQueryColumnsInternal(query, results, dbc.db()); ASSERT_FALSE(status.ok()); } } diff --git a/osquery/sql/virtual_table.cpp b/osquery/sql/virtual_table.cpp index afe9706..43b77fa 100644 --- a/osquery/sql/virtual_table.cpp +++ b/osquery/sql/virtual_table.cpp @@ -129,8 +129,8 @@ int xColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) { afinite = boost::lexical_cast(value); } catch (const boost::bad_lexical_cast &e) { afinite = -1; - LOG(WARNING) << "Error casting " << column_name << " (" << value - << ") to INTEGER"; + VLOG(1) << "Error casting " << column_name << " (" << value + << ") to INTEGER"; } sqlite3_result_int(ctx, afinite); } else if (type == "BIGINT") { @@ -139,8 +139,8 @@ int xColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) { afinite = boost::lexical_cast(value); } catch (const boost::bad_lexical_cast &e) { afinite = -1; - LOG(WARNING) << "Error casting " << column_name << " (" << value - << ") to BIGINT"; + VLOG(1) << "Error casting " << column_name << " (" << value + << ") to BIGINT"; } sqlite3_result_int64(ctx, afinite); } diff --git a/osquery/sql/virtual_table_tests.cpp b/osquery/sql/virtual_table_tests.cpp index 7ae8e0b..de97471 100644 --- a/osquery/sql/virtual_table_tests.cpp +++ b/osquery/sql/virtual_table_tests.cpp @@ -24,7 +24,7 @@ class VirtualTableTests : public testing::Test {}; // sample plugin used on tests class sampleTablePlugin : public TablePlugin { private: - TableColumns columns() { + TableColumns columns() const { return { {"foo", "INTEGER"}, {"bar", "TEXT"}, }; @@ -45,24 +45,24 @@ TEST_F(VirtualTableTests, test_tableplugin_statement) { TEST_F(VirtualTableTests, test_sqlite3_attach_vtable) { auto table = std::make_shared(); table->setName("sample"); - sqlite3* db = nullptr; - sqlite3_open(":memory:", &db); + //sqlite3* db = nullptr; + //sqlite3_open(":memory:", &db); + auto dbc = SQLiteDBManager::get(); // Virtual tables require the registry/plugin API to query tables. - int rc = osquery::tables::attachTable(db, "failed_sample"); + int rc = osquery::tables::attachTable(dbc.db(), "failed_sample"); EXPECT_EQ(rc, SQLITE_ERROR); // The table attach will complete only when the table name is registered. Registry::add("table", "sample"); - rc = osquery::tables::attachTable(db, "sample"); + rc = osquery::tables::attachTable(dbc.db(), "sample"); EXPECT_EQ(rc, SQLITE_OK); std::string q = "SELECT sql FROM sqlite_temp_master WHERE tbl_name='sample';"; QueryData results; - auto status = queryInternal(q, results, db); + auto status = queryInternal(q, results, dbc.db()); EXPECT_EQ("CREATE VIRTUAL TABLE sample USING sample(foo INTEGER, bar TEXT)", results[0]["sql"]); - sqlite3_close(db); } } } diff --git a/osquery/tables/networking/linux/process_open_sockets.cpp b/osquery/tables/networking/linux/process_open_sockets.cpp index 575e6d7..6384cd9 100644 --- a/osquery/tables/networking/linux/process_open_sockets.cpp +++ b/osquery/tables/networking/linux/process_open_sockets.cpp @@ -8,11 +8,12 @@ * */ +#include + #include #include #include -#include #include #include @@ -285,15 +286,15 @@ QueryData genOpenSockets(QueryContext &context) { } // Generate a map of socket inode to process tid. - boost::regex inode_regex("[0-9]+"); + std::regex inode_regex("[0-9]+", std::regex_constants::extended); std::map socket_inodes; for (const auto& process : processes) { std::map descriptors; if (osquery::procDescriptors(process, descriptors).ok()) { for (const auto& fd : descriptors) { if (fd.second.find("socket:") != std::string::npos) { - boost::smatch inode; - boost::regex_search(fd.second, inode, inode_regex); + std::smatch inode; + std::regex_search(fd.second, inode, inode_regex); if (inode[0].str().length() > 0) { socket_inodes[inode[0].str()] = process; } diff --git a/osquery/tables/specs/linux/process_memory_map.table b/osquery/tables/specs/linux/process_memory_map.table index ee9718a..ad08a23 100644 --- a/osquery/tables/specs/linux/process_memory_map.table +++ b/osquery/tables/specs/linux/process_memory_map.table @@ -1,7 +1,7 @@ table_name("process_memory_map") description("Process memory mapped files and pseudo device/regions.") schema([ - Column("pid", INTEGER), + Column("pid", INTEGER, "Process (or thread) ID"), Column("start", TEXT, "Virtual start address (hex)"), Column("end", TEXT, "Virtual end address (hex)"), Column("permissions", TEXT, "r=read, w=write, x=execute, p=private (cow)"), diff --git a/osquery/tables/specs/x/crontab.table b/osquery/tables/specs/x/crontab.table index e12278a..b7c9cfa 100644 --- a/osquery/tables/specs/x/crontab.table +++ b/osquery/tables/specs/x/crontab.table @@ -2,11 +2,11 @@ table_name("crontab") description("Line parsed values from system and user cron/tab.") schema([ Column("event", TEXT, "The job @event name (rare)"), - Column("minute", TEXT), - Column("hour", TEXT), - Column("day_of_month", TEXT), - Column("month", TEXT), - Column("day_of_week", TEXT), + Column("minute", TEXT, "The exact minute for the job"), + Column("hour", TEXT, "The hour of the day for the job"), + Column("day_of_month", TEXT, "The day of the month for the job"), + Column("month", TEXT, "The month of the year for the job"), + Column("day_of_week", TEXT, "The day of the week for the job"), Column("command", TEXT, "Raw command string"), Column("path", TEXT, "File parsed"), ]) diff --git a/osquery/tables/specs/x/etc_services.table b/osquery/tables/specs/x/etc_services.table index 64599d4..6f2e814 100644 --- a/osquery/tables/specs/x/etc_services.table +++ b/osquery/tables/specs/x/etc_services.table @@ -1,9 +1,9 @@ table_name("etc_services") description("Line-parsed /etc/services.") schema([ - Column("name", TEXT), - Column("port", INTEGER), - Column("protocol", TEXT), + Column("name", TEXT, "Service name"), + Column("port", INTEGER, "Service port number"), + Column("protocol", TEXT, "Transport protocol (TCP/UDP)"), Column("aliases", TEXT, "Optional space separated list of other names for a service"), Column("comment", TEXT, "Optional comment for a service."), ]) diff --git a/osquery/tables/specs/x/file.table b/osquery/tables/specs/x/file.table index 59b5059..ee61bac 100644 --- a/osquery/tables/specs/x/file.table +++ b/osquery/tables/specs/x/file.table @@ -2,8 +2,9 @@ table_name("file") description("Interactive filesystem attributes and metadata.") schema([ Column("path", TEXT, "Absolute file path", required=True), + Column("directory", TEXT, "Directory of file(s)", required=True), Column("filename", TEXT, "Name portion of file path"), - Column("inode", BIGINT), + Column("inode", BIGINT, "Filesystem inode number"), Column("uid", BIGINT, "Owning user ID"), Column("gid", BIGINT, "Owning group ID"), Column("mode", TEXT, "Permission bits"), diff --git a/osquery/tables/specs/x/hash.table b/osquery/tables/specs/x/hash.table index 03e7096..3aa0670 100644 --- a/osquery/tables/specs/x/hash.table +++ b/osquery/tables/specs/x/hash.table @@ -3,8 +3,8 @@ description("Filesystem hash data.") schema([ Column("path", TEXT, "Must provide a path or directory", required=True), Column("directory", TEXT, "Must provide a path or directory", required=True), - Column("md5", TEXT), - Column("sha1", TEXT), - Column("sha256", TEXT), + Column("md5", TEXT, "MD5 hash of provided filesystem data"), + Column("sha1", TEXT, "SHA1 hash of provided filesystem data"), + Column("sha256", TEXT, "SHA256 hash of provided filesystem data"), ]) implementation("utility/hash@genHash") diff --git a/osquery/tables/specs/x/last.table b/osquery/tables/specs/x/last.table index 0bf072f..c1252d0 100644 --- a/osquery/tables/specs/x/last.table +++ b/osquery/tables/specs/x/last.table @@ -3,7 +3,7 @@ description("System logins and logouts.") schema([ Column("username", TEXT), Column("tty", TEXT), - Column("pid", INTEGER), + Column("pid", INTEGER, "Process (or thread) ID"), Column("type", INTEGER), Column("time", INTEGER), Column("host", TEXT), diff --git a/osquery/tables/specs/x/logged_in_users.table b/osquery/tables/specs/x/logged_in_users.table index 135a643..096b9a9 100644 --- a/osquery/tables/specs/x/logged_in_users.table +++ b/osquery/tables/specs/x/logged_in_users.table @@ -1,10 +1,10 @@ table_name("logged_in_users") description("Users with an active shell on the system.") schema([ - Column("user", TEXT), - Column("tty", TEXT), - Column("host", TEXT), - Column("time", INTEGER), - Column("pid", INTEGER), + Column("user", TEXT, "User login name"), + Column("tty", TEXT, "Device name"), + Column("host", TEXT, "Remote hostname"), + Column("time", INTEGER, "Time entry was made"), + Column("pid", INTEGER, "Process (or thread) ID"), ]) implementation("logged_in_users@genLoggedInUsers") diff --git a/osquery/tables/specs/x/osquery_extensions.table b/osquery/tables/specs/x/osquery_extensions.table new file mode 100644 index 0000000..984933a --- /dev/null +++ b/osquery/tables/specs/x/osquery_extensions.table @@ -0,0 +1,10 @@ +table_name("osquery_extensions") +description("List of active osquery extensions.") +schema([ + Column("uuid", BIGINT), + Column("name", TEXT), + Column("version", TEXT), + Column("sdk_version", TEXT), + Column("socket", TEXT), +]) +implementation("osquery@genOsqueryExtensions") diff --git a/osquery/tables/specs/x/osquery_info.table b/osquery/tables/specs/x/osquery_info.table index 3f1682a..cd88bff 100644 --- a/osquery/tables/specs/x/osquery_info.table +++ b/osquery/tables/specs/x/osquery_info.table @@ -4,6 +4,7 @@ schema([ Column("version", TEXT), Column("config_md5", TEXT), Column("config_path", TEXT), - Column("pid", INTEGER), + Column("pid", INTEGER, "Process (or thread) ID"), + Column("extensions", TEXT), ]) implementation("osquery@genOsqueryInfo") diff --git a/osquery/tables/specs/x/passwd_changes.table b/osquery/tables/specs/x/passwd_changes.table index e854c67..56e1632 100644 --- a/osquery/tables/specs/x/passwd_changes.table +++ b/osquery/tables/specs/x/passwd_changes.table @@ -2,7 +2,7 @@ table_name("passwd_changes") description("Track time, action changes to /etc/passwd.") schema([ Column("target_path", TEXT, "The path changed"), - Column("time", TEXT), + Column("time", TEXT, "Time of the change"), Column("action", TEXT, "Change action (UPDATE, REMOVE, etc)"), Column("transaction_id", BIGINT, "ID used during bulk update"), ]) diff --git a/osquery/tables/specs/x/process_envs.table b/osquery/tables/specs/x/process_envs.table index 76e860c..0c9226f 100644 --- a/osquery/tables/specs/x/process_envs.table +++ b/osquery/tables/specs/x/process_envs.table @@ -1,7 +1,7 @@ table_name("process_envs") description("A key/value table of environment variables for each process.") schema([ - Column("pid", INTEGER), + Column("pid", INTEGER, "Process (or thread) ID"), Column("key", TEXT, "Environment variable name"), Column("value", TEXT, "Environment variable value"), ForeignKey(column="pid", table="processes"), diff --git a/osquery/tables/specs/x/process_open_sockets.table b/osquery/tables/specs/x/process_open_sockets.table index f3e9f97..2e87c44 100644 --- a/osquery/tables/specs/x/process_open_sockets.table +++ b/osquery/tables/specs/x/process_open_sockets.table @@ -1,14 +1,14 @@ table_name("process_open_sockets") description("Processes which have open network sockets on the system.") schema([ - Column("pid", INTEGER), - Column("socket", INTEGER), - Column("family", INTEGER), - Column("protocol", INTEGER), - Column("local_address", TEXT), - Column("remote_address", TEXT), - Column("local_port", INTEGER), - Column("remote_port", INTEGER), + Column("pid", INTEGER, "Process (or thread) ID"), + Column("socket", INTEGER, "Socket descriptor number"), + Column("family", INTEGER, "Network protocol (IPv4, IPv6)"), + Column("protocol", INTEGER, "Transport protocol (TCP/UDP)"), + Column("local_address", TEXT, "Socket local address"), + Column("remote_address", TEXT, "Socket remote address"), + Column("local_port", INTEGER, "Socket local port"), + Column("remote_port", INTEGER, "Socket remote port"), ]) implementation("system/process_open_sockets@genOpenSockets") diff --git a/osquery/tables/specs/x/processes.table b/osquery/tables/specs/x/processes.table index ba18966..1c67b6d 100644 --- a/osquery/tables/specs/x/processes.table +++ b/osquery/tables/specs/x/processes.table @@ -1,21 +1,23 @@ table_name("processes") description("All running processes on the host system.") schema([ - Column("pid", INTEGER), + Column("pid", INTEGER, "Process (or thread) ID"), Column("name", TEXT, "The process path or shorthand argv[0]"), - Column("path", TEXT), + Column("path", TEXT, "Path to executed binary"), Column("cmdline", TEXT, "Complete argv"), - Column("uid", BIGINT), - Column("gid", BIGINT), - Column("euid", BIGINT), - Column("egid", BIGINT), + Column("cwd", TEXT, "Process current working directory"), + Column("root", TEXT, "Process virtual root directory"), + Column("uid", BIGINT, "Unsigned user ID"), + Column("gid", BIGINT, "Unsgiend groud ID"), + Column("euid", BIGINT, "Unsigned effective user ID"), + Column("egid", BIGINT, "Unsigned effective group ID"), Column("on_disk", TEXT, "The process path exist yes=1, no=-1"), - Column("wired_size", TEXT), - Column("resident_size", TEXT), - Column("phys_footprint", TEXT), - Column("user_time", TEXT), - Column("system_time", TEXT), - Column("start_time", TEXT), - Column("parent", INTEGER), + Column("wired_size", TEXT, "Bytes of unpagable memory used by process"), + Column("resident_size", TEXT, "Bytes of private memory used by process"), + Column("phys_footprint", TEXT, "Bytes of total physical memory used"), + Column("user_time", TEXT, "CPU time spent in user space"), + Column("system_time", TEXT, "CPU time spent in kernel space"), + Column("start_time", TEXT, "Unix timestamp of process start"), + Column("parent", INTEGER, "Process parent's PID"), ]) implementation("system/processes@genProcesses") diff --git a/osquery/tables/system/linux/acpi_tables.cpp b/osquery/tables/system/linux/acpi_tables.cpp index c7ce6b3..662926d 100644 --- a/osquery/tables/system/linux/acpi_tables.cpp +++ b/osquery/tables/system/linux/acpi_tables.cpp @@ -32,7 +32,7 @@ void genACPITable(const std::string& table, QueryData& results) { status = osquery::listFilesInDirectory(table_path, child_tables); if (status.ok()) { for (const auto& child_table : child_tables) { - genACPITable(table, results); + genACPITable(child_table, results); } } diff --git a/osquery/tables/system/linux/processes.cpp b/osquery/tables/system/linux/processes.cpp index 8c97a05..3ca594b 100644 --- a/osquery/tables/system/linux/processes.cpp +++ b/osquery/tables/system/linux/processes.cpp @@ -35,15 +35,15 @@ namespace tables { PROC_FILLCOM | PROC_FILLMEM | PROC_FILLSTATUS | PROC_FILLSTAT #endif -std::string getProcName(const proc_t* proc_info) { +inline std::string getProcName(const proc_t* proc_info) { return std::string(proc_info->cmd); } -std::string getProcAttr(const std::string& attr, const proc_t* proc_info) { +inline std::string getProcAttr(const std::string& attr, const proc_t* proc_info) { return "/proc/" + std::to_string(proc_info->tid) + "/" + attr; } -std::string readProcCMDLine(const proc_t* proc_info) { +inline std::string readProcCMDLine(const proc_t* proc_info) { auto attr = getProcAttr("cmdline", proc_info); std::string result; @@ -61,20 +61,18 @@ std::string readProcCMDLine(const proc_t* proc_info) { return result; } -std::string readProcLink(const proc_t* proc_info) { +inline std::string readProcLink(const proc_t* proc_info, + const std::string& attr) { // The exe is a symlink to the binary on-disk. - auto attr = getProcAttr("exe", proc_info); - long path_max = pathconf(attr.c_str(), _PC_PATH_MAX); - auto link_path = (char*)malloc(path_max); - memset(link_path, 0, path_max); + auto attr_path = getProcAttr("exe", proc_info); std::string result; - int bytes = readlink(attr.c_str(), link_path, path_max); + char link_path[PATH_MAX] = {0}; + auto bytes = readlink(attr_path.c_str(), link_path, sizeof(link_path) - 1); if (bytes >= 0) { result = std::string(link_path); } - free(link_path); return result; } @@ -177,23 +175,38 @@ QueryData genProcesses(QueryContext& context) { Row r; r["pid"] = INTEGER(proc_info->tid); - r["uid"] = BIGINT((unsigned int)proc_info->ruid); - r["gid"] = BIGINT((unsigned int)proc_info->rgid); - r["euid"] = BIGINT((unsigned int)proc_info->euid); - r["egid"] = BIGINT((unsigned int)proc_info->egid); + r["parent"] = INTEGER(proc_info->ppid); + r["path"] = readProcLink(proc_info, "exe"); r["name"] = getProcName(proc_info); + + // Read/parse cmdline arguments. std::string cmdline = readProcCMDLine(proc_info); boost::algorithm::trim(cmdline); r["cmdline"] = cmdline; - r["path"] = readProcLink(proc_info); + r["cwd"] = readProcLink(proc_info, "cwd"); + r["root"] = readProcLink(proc_info, "root"); + + r["uid"] = BIGINT((unsigned int)proc_info->ruid); + r["gid"] = BIGINT((unsigned int)proc_info->rgid); + r["euid"] = BIGINT((unsigned int)proc_info->euid); + r["egid"] = BIGINT((unsigned int)proc_info->egid); + + // If the path of the executable that started the process is available and + // the path exists on disk, set on_disk to 1. If the path is not + // available, set on_disk to -1. If, and only if, the path of the + // executable is available and the file does NOT exist on disk, set on_disk + // to 0. r["on_disk"] = osquery::pathExists(r["path"]).toString(); + // size/memory information + r["wired_size"] = "0"; // No support for unpagable counters in linux. r["resident_size"] = INTEGER(proc_info->vm_rss); r["phys_footprint"] = INTEGER(proc_info->vm_size); + + // time information r["user_time"] = INTEGER(proc_info->utime); r["system_time"] = INTEGER(proc_info->stime); r["start_time"] = INTEGER(proc_info->start_time); - r["parent"] = INTEGER(proc_info->ppid); results.push_back(r); standardFreeproc(proc_info); diff --git a/osquery/tables/system/linux/shared_memory.cpp b/osquery/tables/system/linux/shared_memory.cpp index 1c06125..5ae5b4d 100644 --- a/osquery/tables/system/linux/shared_memory.cpp +++ b/osquery/tables/system/linux/shared_memory.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -71,7 +72,7 @@ QueryData genSharedMemory(QueryContext &context) { r["dtime"] = BIGINT(shmseg.shm_dtime); r["ctime"] = BIGINT(shmseg.shm_ctime); - r["permissions"] = INTEGER(ipcp->mode); + r["permissions"] = lsperms(ipcp->mode); r["size"] = BIGINT(shmseg.shm_segsz); r["attached"] = INTEGER(shmseg.shm_nattch); r["status"] = (ipcp->mode & SHM_DEST) ? "dest" : ""; diff --git a/osquery/tables/templates/default.cpp.in b/osquery/tables/templates/default.cpp.in index 5255202..f7bd8b2 100644 --- a/osquery/tables/templates/default.cpp.in +++ b/osquery/tables/templates/default.cpp.in @@ -29,7 +29,7 @@ class {{class_name}} { class {{table_name_cc}}TablePlugin : public TablePlugin { private: - TableColumns columns() { + TableColumns columns() const { return { {% for column in schema %}\ {"{{column.name}}", "{{column.type.affinity}}"}\ diff --git a/osquery/tables/utility/file.cpp b/osquery/tables/utility/file.cpp index 65a1292..ea7abbf 100644 --- a/osquery/tables/utility/file.cpp +++ b/osquery/tables/utility/file.cpp @@ -18,15 +18,45 @@ namespace osquery { namespace tables { -inline std::string lsperms(int mode) { - static const char rwx[] = {'0', '1', '2', '3', '4', '5', '6', '7'}; - std::string bits; +void genFileInfo(const std::string& path, + const std::string& filename, + const std::string& dir, + QueryData& results) { + // Must provide the path, filename, directory separate from boost path->string + // helpers to match any explicit (query-parsed) predicate constraints. + struct stat file_stat, link_stat; + if (lstat(path.c_str(), &link_stat) < 0 || stat(path.c_str(), &file_stat)) { + // Path was not real, had too may links, or could not be accessed. + return; + } + + Row r; + r["path"] = path; + r["filename"] = filename; + r["directory"] = dir; + + r["inode"] = BIGINT(file_stat.st_ino); + r["uid"] = BIGINT(file_stat.st_uid); + r["gid"] = BIGINT(file_stat.st_gid); + r["mode"] = lsperms(file_stat.st_mode); + r["device"] = BIGINT(file_stat.st_rdev); + r["size"] = BIGINT(file_stat.st_size); + r["block_size"] = INTEGER(file_stat.st_blksize); + r["hard_links"] = INTEGER(file_stat.st_nlink); - bits += rwx[(mode >> 9) & 7]; - bits += rwx[(mode >> 6) & 7]; - bits += rwx[(mode >> 3) & 7]; - bits += rwx[(mode >> 0) & 7]; - return bits; + // Times + r["atime"] = BIGINT(file_stat.st_atime); + r["mtime"] = BIGINT(file_stat.st_mtime); + r["ctime"] = BIGINT(file_stat.st_ctime); + + // Type booleans + r["is_file"] = (!S_ISDIR(file_stat.st_mode)) ? "1" : "0"; + r["is_dir"] = (S_ISDIR(file_stat.st_mode)) ? "1" : "0"; + r["is_link"] = (S_ISLNK(link_stat.st_mode)) ? "1" : "0"; + r["is_char"] = (S_ISCHR(file_stat.st_mode)) ? "1" : "0"; + r["is_block"] = (S_ISBLK(file_stat.st_mode)) ? "1" : "0"; + + results.push_back(r); } QueryData genFile(QueryContext& context) { @@ -35,40 +65,30 @@ QueryData genFile(QueryContext& context) { auto paths = context.constraints["path"].getAll(EQUALS); for (const auto& path_string : paths) { boost::filesystem::path path = path_string; + genFileInfo(path_string, + path.filename().string(), + path.parent_path().string(), + results); + } - Row r; - r["path"] = path.string(); - r["filename"] = path.filename().string(); - - struct stat file_stat, link_stat; - if (lstat(path.string().c_str(), &link_stat) < 0 || - stat(path.string().c_str(), &file_stat)) { - // Path was not real, had too may links, or could not be accessed. + // Now loop through constraints using the directory column constraint. + auto directories = context.constraints["directory"].getAll(EQUALS); + for (const auto& directory_string : directories) { + boost::filesystem::path directory = directory_string; + if (!boost::filesystem::is_directory(directory)) { continue; } - r["inode"] = BIGINT(file_stat.st_ino); - r["uid"] = BIGINT(file_stat.st_uid); - r["gid"] = BIGINT(file_stat.st_gid); - r["mode"] = std::string(lsperms(file_stat.st_mode)); - r["device"] = BIGINT(file_stat.st_rdev); - r["size"] = BIGINT(file_stat.st_size); - r["block_size"] = INTEGER(file_stat.st_blksize); - r["hard_links"] = INTEGER(file_stat.st_nlink); - - // Times - r["atime"] = BIGINT(file_stat.st_atime); - r["mtime"] = BIGINT(file_stat.st_mtime); - r["ctime"] = BIGINT(file_stat.st_ctime); - - // Type booleans - r["is_file"] = (!S_ISDIR(file_stat.st_mode)) ? "1" : "0"; - r["is_dir"] = (S_ISDIR(file_stat.st_mode)) ? "1" : "0"; - r["is_link"] = (S_ISLNK(link_stat.st_mode)) ? "1" : "0"; - r["is_char"] = (S_ISCHR(file_stat.st_mode)) ? "1" : "0"; - r["is_block"] = (S_ISBLK(file_stat.st_mode)) ? "1" : "0"; - - results.push_back(r); + // Iterate over the directory and generate a hash for each regular file. + boost::filesystem::directory_iterator begin(directory), end; + for (; begin != end; ++begin) { + if (boost::filesystem::is_regular_file(begin->status())) { + genFileInfo(begin->path().string(), + begin->path().filename().string(), + directory_string, + results); + } + } } return results; diff --git a/osquery/tables/utility/hash.cpp b/osquery/tables/utility/hash.cpp index ad5bec7..6b152d7 100644 --- a/osquery/tables/utility/hash.cpp +++ b/osquery/tables/utility/hash.cpp @@ -14,12 +14,31 @@ #include #include +namespace fs = boost::filesystem; + namespace osquery { namespace tables { +void genHashForFile(const std::string& path, + const std::string& dir, + QueryData& results) { + // Must provide the path, filename, directory separate from boost path->string + // helpers to match any explicit (query-parsed) predicate constraints. + Row r; + r["path"] = path; + r["directory"] = dir; + r["md5"] = osquery::hashFromFile(HASH_TYPE_MD5, path); + r["sha1"] = osquery::hashFromFile(HASH_TYPE_SHA1, path); + r["sha256"] = osquery::hashFromFile(HASH_TYPE_SHA256, path); + results.push_back(r); +} + QueryData genHash(QueryContext& context) { QueryData results; + // The query must provide a predicate with constratins including path or + // directory. We search for the parsed predicate constraints with the equals + // operator. auto paths = context.constraints["path"].getAll(EQUALS); for (const auto& path_string : paths) { boost::filesystem::path path = path_string; @@ -27,15 +46,10 @@ QueryData genHash(QueryContext& context) { continue; } - Row r; - r["path"] = path.string(); - r["directory"] = path.parent_path().string(); - r["md5"] = osquery::hashFromFile(HASH_TYPE_MD5, path.string()); - r["sha1"] = osquery::hashFromFile(HASH_TYPE_SHA1, path.string()); - r["sha256"] = osquery::hashFromFile(HASH_TYPE_SHA256, path.string()); - results.push_back(r); + genHashForFile(path_string, path.parent_path().string(), results); } + // Now loop through constraints using the directory column constraint. auto directories = context.constraints["directory"].getAll(EQUALS); for (const auto& directory_string : directories) { boost::filesystem::path directory = directory_string; @@ -46,17 +60,9 @@ QueryData genHash(QueryContext& context) { // Iterate over the directory and generate a hash for each regular file. boost::filesystem::directory_iterator begin(directory), end; for (; begin != end; ++begin) { - Row r; - r["path"] = begin->path().string(); - r["directory"] = directory_string; if (boost::filesystem::is_regular_file(begin->status())) { - r["md5"] = osquery::hashFromFile(HASH_TYPE_MD5, begin->path().string()); - r["sha1"] = - osquery::hashFromFile(HASH_TYPE_SHA1, begin->path().string()); - r["sha256"] = - osquery::hashFromFile(HASH_TYPE_SHA256, begin->path().string()); + genHashForFile(begin->path().string(), directory_string, results); } - results.push_back(r); } } diff --git a/osquery/tables/utility/osquery.cpp b/osquery/tables/utility/osquery.cpp index c15e7ed..a1e6a52 100644 --- a/osquery/tables/utility/osquery.cpp +++ b/osquery/tables/utility/osquery.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,27 @@ QueryData genOsqueryFlags(QueryContext& context) { return results; } +QueryData genOsqueryExtensions(QueryContext& context) { + QueryData results; + + ExtensionList extensions; + if (!getExtensions(extensions).ok()) { + return {}; + } + + for (const auto& extenion : extensions) { + Row r; + r["uuid"] = TEXT(extenion.first); + r["name"] = extenion.second.name; + r["version"] = extenion.second.version; + r["sdk_version"] = extenion.second.sdk_version; + r["socket"] = getExtensionSocket(extenion.first); + results.push_back(r); + } + + return results; +} + QueryData genOsqueryInfo(QueryContext& context) { QueryData results; @@ -65,6 +87,8 @@ QueryData genOsqueryInfo(QueryContext& context) { } r["config_path"] = Flag::get().getValue("config_path"); + r["extensions"] = + (pingExtension(FLAGS_extensions_socket).ok()) ? "active" : "inactive"; results.push_back(r); return results; diff --git a/packaging/osquery.spec b/packaging/osquery.spec index 8a4901b..e7fbc5d 100644 --- a/packaging/osquery.spec +++ b/packaging/osquery.spec @@ -1,5 +1,5 @@ Name: osquery -Version: 1.4.0 +Version: 1.4.1 Release: 0 License: Apache-2.0 and GPLv2 Summary: A SQL powered operating system instrumentation, monitoring framework.