/// 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.
*/
Status add(std::shared_ptr<InternalRunnable> task);
+ /// See `add`, but services are not limited to a thread poll size.
Status addService(std::shared_ptr<InternalRunnable> service);
/**
*/
void join();
+ /// See `join`, but applied to osquery services.
void joinServices();
+ /// Destroy and stop all osquery service threads and service objects.
void removeServices();
/**
* @see getThreadManager
*/
InternalThreadManagerRef thread_manager_;
+ /// The set of shared osquery service threads.
std::vector<std::shared_ptr<boost::thread> > service_threads_;
+ /// THe set of shared osquery services.
std::vector<std::shared_ptr<InternalRunnable> > services_;
};
+
+/// Sleep in a boost::thread interruptable state.
+void interruptableSleep(size_t milli);
}
#include <boost/thread/mutex.hpp>
#include <osquery/database.h>
+#include <osquery/dispatcher.h>
#include <osquery/registry.h>
#include <osquery/status.h>
#include <osquery/tables.h>
DECLARE_string(extensions_socket);
-namespace extensions {
-
/**
* @brief Helper struct for managing extenion metadata.
*
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<RouteUUID, ExtensionInfo> 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.
*
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.
bool exists(const std::string& name);
/// Maintain a map of extension UUID to metadata for tracking deregistrations.
- ExtensionList extensions_;
+ InternalExtensionList extensions_;
};
}
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;
}
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.
*
*/
std::vector<boost::filesystem::path> getHomeDirectories();
+/// Return bit-mask-style permissions.
+std::string lsperms(int mode);
+
#ifdef __APPLE__
/**
* @brief Parse a property list on disk into a property tree.
/// 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;
}
public:
RegistryHelperCore(bool auto_setup = true) : auto_setup_(auto_setup) {}
+ virtual ~RegistryHelperCore() {}
/**
* @brief Remove a registry item by its identifier.
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<RegistryType> 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<RegistryType> shared_item(item);
- items_[item_name] = shared_item;
+ items_[item_name] = item;
return Status(0, "OK");
}
* @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<RegistryType>(items_.at(item_name));
}
- const std::map<std::string, RegistryTypeRef> all() {
+ const std::map<std::string, RegistryTypeRef> all() const {
std::map<std::string, RegistryTypeRef> ditems;
for (const auto& item : items_) {
ditems[item.first] = std::dynamic_pointer_cast<RegistryType>(item.second);
return 0;
}
- auto registry = (PluginRegistryHelper*)new RegistryHelper<Type>(auto_setup);
+ PluginRegistryHelperRef registry((PluginRegistryHelper*)new RegistryHelper<Type>(auto_setup));
registry->setName(registry_name);
- PluginRegistryHelperRef shared_registry(registry);
- instance().registries_[registry_name] = shared_registry;
+ instance().registries_[registry_name] = registry;
return 0;
}
* 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.
*
*/
*
* @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
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
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
tables::ConstraintOperator op,
const std::string& expr);
- private:
+ protected:
/**
* @brief Private default constructor
*
*/
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_;
* @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;
+ }
+};
+
}
* @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.
* @return If the expression matched all constraints.
*/
template <typename T>
- bool matches(const T& expr) {
+ bool matches(const T& expr) const {
return matches(TEXT(expr));
}
*
* @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.
* @return true if any constraint exists AND matches the type expression.
*/
template <typename T>
- bool existsAndMatches(const T& expr) {
+ bool existsAndMatches(const T& expr) const {
return (exists() && matches(expr));
}
* @return true if constraint is missing or matches the type expression.
*/
template <typename T>
- bool notExistsOrMatches(const T& expr) {
+ bool notExistsOrMatches(const T& expr) const {
return (!exists() || matches(expr));
}
* @brief Helper templated function for ConstraintList::matches.
*/
template <typename T>
- bool literal_matches(const T& base_expr);
+ bool literal_matches(const T& base_expr) const;
/**
* @brief Get all expressions for a given ConstraintOperator.
* @param op the ConstraintOperator.
* @return A list of TEXT%-represented types matching the operator.
*/
- std::set<std::string> getAll(ConstraintOperator op);
+ std::set<std::string> getAll(ConstraintOperator op) const;
template<typename T>
- std::set<T> getAll(ConstraintOperator op) {
+ std::set<T> getAll(ConstraintOperator op) const {
std::set<T> literal_matches;
auto matches = getAll(op);
for (const auto& match : matches) {
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;
}
typedef map<string, string> ExtensionRoute
typedef map<string, ExtensionRoute> ExtensionRouteTable
typedef map<string, ExtensionRouteTable> ExtensionRegistry
-typedef map<ExtensionRouteUUID, InternalExtensionInfo> ExtensionList
+typedef map<ExtensionRouteUUID, InternalExtensionInfo> InternalExtensionList
enum ExtensionCode {
EXT_SUCCESS = 0,
}
service ExtensionManager extends Extension {
- ExtensionList extensions(),
+ InternalExtensionList extensions(),
ExtensionStatus registerExtension(
1:InternalExtensionInfo info,
2:ExtensionRegistry registry),
ADD_SUBDIRECTORY(core)
ADD_SUBDIRECTORY(config)
ADD_SUBDIRECTORY(dispatcher)
+ADD_SUBDIRECTORY(distributed)
ADD_SUBDIRECTORY(devtools)
ADD_SUBDIRECTORY(database)
ADD_SUBDIRECTORY(events)
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.
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<TEXT_LITERAL>(expr);
}
template <typename T>
-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);
return true;
}
-std::set<std::string> ConstraintList::getAll(ConstraintOperator op) {
+std::set<std::string> ConstraintList::getAll(ConstraintOperator op) const {
std::set<std::string> set;
for (size_t i = 0; i < constraints_.size(); ++i) {
if (constraints_[i].op == op) {
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,
} 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}});
}
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();
}
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));
}
--- /dev/null
+ADD_OSQUERY_LIBRARY(TRUE osquery_distributed distributed.cpp)
+
+ADD_OSQUERY_TEST(TRUE distributed_tests distributed_tests.cpp)
--- /dev/null
+/*
+ * 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 <sstream>
+
+#include <boost/property_tree/json_parser.hpp>
+
+#include <osquery/core.h>
+#include <osquery/database.h>
+#include <osquery/logger.h>
+
+#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<DistributedQueryRequest>& 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<DistributedQueryRequest> results;
+ for (const auto& node : tree) {
+ const auto& request_tree = node.second;
+ DistributedQueryRequest request;
+ try {
+ request.query = request_tree.get_child("query").get_value<std::string>();
+ request.id = request_tree.get_child("id").get_value<std::string>();
+ } 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<std::pair<DistributedQueryRequest, SQL> >& 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<DistributedQueryRequest> requests;
+ status = parseQueriesJSON(query_json, requests);
+ if (!status.ok()) {
+ return status;
+ }
+
+ // Run the queries
+ std::vector<std::pair<DistributedQueryRequest, SQL> > query_results;
+ std::set<std::string> 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;
+}
+}
--- /dev/null
+/*
+ * 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 <set>
+#include <vector>
+
+#include <boost/property_tree/ptree.hpp>
+
+#include <osquery/database/results.h>
+#include <osquery/sql.h>
+
+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<IDistributedProvider> 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<std::pair<DistributedQueryRequest, SQL> >& 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<DistributedQueryRequest>& requests);
+
+private:
+ // The provider used to read and write queries and results
+ std::unique_ptr<IDistributedProvider> 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<std::string> executedRequestIds_;
+};
+
+} // namespace osquery
--- /dev/null
+/*
+ * 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 <iostream>
+
+#include <boost/property_tree/json_parser.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <gtest/gtest.h>
+
+#include <osquery/core.h>
+#include <osquery/sql.h>
+
+#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<DistributedQueryRequest> 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<int>("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<int>("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<std::string>("foo0"));
+ EXPECT_EQ("bar0_val", row->second.get<std::string>("bar0"));
+ ++row;
+ EXPECT_EQ("foo1_val", row->second.get<std::string>("foo1"));
+ EXPECT_EQ("bar1_val", row->second.get<std::string>("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<int>("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<std::string>("foo0"));
+ EXPECT_EQ("bar0_val", row->second.get<std::string>("bar0"));
+ ++row;
+ EXPECT_EQ("foo1_val", row->second.get<std::string>("foo1"));
+ EXPECT_EQ("bar1_val", row->second.get<std::string>("bar1"));
+
+ EXPECT_EQ(1, tree.get<int>("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<MockDistributedProvider>
+ 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<int>("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<int>("hour"), 0);
+ EXPECT_LE(row->second.get<int>("hour"), 24);
+ EXPECT_EQ(getHostname(), row->second.get<std::string>("_source_host"));
+ }
+
+ {
+ // this query should have failed
+ EXPECT_EQ(1, tree.get<int>("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<int>("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<int>("minutes"), 0);
+ EXPECT_LE(row->second.get<int>("minutes"), 60);
+ EXPECT_EQ(getHostname(), row->second.get<std::string>("_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<MockDistributedProvider>
+ 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<int>("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<int>("hour"), 0);
+ EXPECT_LE(row->second.get<int>("hour"), 24);
+ EXPECT_EQ(getHostname(), row->second.get<std::string>("_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();
+}
#include <boost/lexical_cast.hpp>
#include <osquery/core.h>
-#include <osquery/dispatcher.h>
#include <osquery/events.h>
#include <osquery/flags.h>
#include <osquery/logger.h>
namespace osquery {
+/// Helper cooloff (ms) macro to prevent thread failure thrashing.
+#define EVENTS_COOLOFF 20
+
DEFINE_osquery_flag(bool,
disable_events,
false,
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.
namespace osquery {
-int kINotifyULatency = 200;
+int kINotifyMLatency = 200;
+
static const uint32_t BUFFER_SIZE =
(10 * ((sizeof(struct inotify_event)) + NAME_MAX + 1));
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";
p += (sizeof(struct inotify_event)) + event->len;
}
- ::usleep(kINotifyULatency);
+ osquery::interruptableSleep(kINotifyMLatency);
return Status(0, "Continue");
}
}
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;
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.
namespace osquery {
-int kUdevULatency = 200;
+int kUdevMLatency = 200;
REGISTER(UdevEventPublisher, "event_publisher", "udev");
udev_device_unref(device);
- ::usleep(kUdevULatency);
+ osquery::interruptableSleep(kUdevMLatency);
return Status(0, "Continue");
}
}
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;
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);
};
*
*/
+#include <csignal>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
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<TSocket> socket(new TSocket(path));
+ boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
+ boost::shared_ptr<TProtocol> 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<TSocket> socket(new TSocket(manager_path));
+ boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
+ boost::shared_ptr<TProtocol> 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,
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);
}
Status startExtensionManager() {
+ if (FLAGS_disable_extensions) {
+ return Status(1, "Extensions disabled");
+ }
return startExtensionManager(FLAGS_extensions_socket);
}
}
ExtensionList registeredExtensions(int attempts = 3) {
- // Open a socket to the test extension manager.
- boost::shared_ptr<TSocket> socket(new TSocket(kTestManagerSocket));
- boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
- boost::shared_ptr<TProtocol> 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;
}
}
// 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");
}
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;
+}
}
#include <exception>
#include <map>
+#include <regex>
#include <vector>
+#include <linux/limits.h>
#include <unistd.h>
-#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <osquery/filesystem.h>
const std::string kLinuxProcPath = "/proc";
Status procProcesses(std::vector<std::string>& 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());
}
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 {
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();
}
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);
}
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);
}
/// 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;
/// 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");
}
return Status(0, "OK");
}
+#define UNUSED(x) (void)(x)
+
TEST_F(RegistryTests, test_registry_api) {
auto AutoWidgetRegistry = TestCoreRegistry::create<WidgetPlugin>("widgets");
+ UNUSED(AutoWidgetRegistry);
+
TestCoreRegistry::add<SpecialWidget>("widgets", "special");
// Test route info propogation, from item to registry, to broadcast.
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<std::string> SQL::getTableNames() {
std::vector<std::string> results;
for (const auto& name : Registry::names("table")) {
class TestTable : public tables::TablePlugin {
private:
- tables::TableColumns columns() {
+ tables::TableColumns columns() const {
return {{"test_int", "INTEGER"}, {"test_text", "TEXT"}};
}
{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);
}
}
-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";
}
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;
}
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;
}
#pragma once
+#include <map>
+#include <mutex>
+
#include <sqlite3.h>
+#include <boost/thread/mutex.hpp>
+#include <boost/noncopyable.hpp>
+
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<boost::mutex> lock_;
+};
+
/**
* @brief A map of SQLite status codes to their corresponding message string
*
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);
class SQLiteUtilTests : public testing::Test {};
-sqlite3* createTestDB() {
- sqlite3* db = createDB();
+SQLiteDBInstance getTestDBC() {
+ SQLiteDBInstance dbc = SQLiteDBManager::getUnique();
char* err = nullptr;
std::vector<std::string> queries = {
"CREATE TABLE test_table ("
"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);
}
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);
}
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<sqlite3, decltype(sqlite3_close)*> db_managed(createDB(),
- sqlite3_close);
- sqlite3* db = db_managed.get();
+ auto dbc = getTestDBC();
std::string query;
Status status;
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")),
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")),
results[1]);
query = "SELECT * FROM foo";
- status = getQueryColumnsInternal(query, results, db);
+ status = getQueryColumnsInternal(query, results, dbc.db());
ASSERT_FALSE(status.ok());
}
}
afinite = boost::lexical_cast<int>(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") {
afinite = boost::lexical_cast<long long int>(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);
}
// sample plugin used on tests
class sampleTablePlugin : public TablePlugin {
private:
- TableColumns columns() {
+ TableColumns columns() const {
return {
{"foo", "INTEGER"}, {"bar", "TEXT"},
};
TEST_F(VirtualTableTests, test_sqlite3_attach_vtable) {
auto table = std::make_shared<sampleTablePlugin>();
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<sampleTablePlugin>("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);
}
}
}
*
*/
+#include <regex>
+
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <boost/algorithm/string/split.hpp>
-#include <boost/regex.hpp>
#include <osquery/core.h>
#include <osquery/filesystem.h>
}
// 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<std::string, std::string> socket_inodes;
for (const auto& process : processes) {
std::map<std::string, std::string> 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;
}
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)"),
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"),
])
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."),
])
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"),
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")
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),
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")
--- /dev/null
+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")
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")
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"),
])
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"),
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")
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")
status = osquery::listFilesInDirectory(table_path, child_tables);
if (status.ok()) {
for (const auto& child_table : child_tables) {
- genACPITable(table, results);
+ genACPITable(child_table, results);
}
}
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;
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;
}
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);
#include <pwd.h>
#include <osquery/core.h>
+#include <osquery/filesystem.h>
#include <osquery/logger.h>
#include <osquery/tables.h>
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" : "";
class {{table_name_cc}}TablePlugin : public TablePlugin {
private:
- TableColumns columns() {
+ TableColumns columns() const {
return {
{% for column in schema %}\
{"{{column.name}}", "{{column.type.affinity}}"}\
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) {
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;
#include <osquery/hash.h>
#include <osquery/tables.h>
+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;
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;
// 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);
}
}
#include <osquery/config.h>
#include <osquery/core.h>
+#include <osquery/extensions.h>
#include <osquery/flags.h>
#include <osquery/logger.h>
#include <osquery/sql.h>
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;
}
r["config_path"] = Flag::get().getValue("config_path");
+ r["extensions"] =
+ (pingExtension(FLAGS_extensions_socket).ok()) ? "active" : "inactive";
results.push_back(r);
return results;
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.