diff --git a/osquery/tables/system/posix/augeas.cpp b/osquery/tables/system/posix/augeas.cpp index fa8c581a7aa..d2361957faf 100644 --- a/osquery/tables/system/posix/augeas.cpp +++ b/osquery/tables/system/posix/augeas.cpp @@ -153,18 +153,40 @@ class AugeasHandle { static AugeasHandle kAugeasHandle; -// Augeas presents data as a slash seperated tree. It uses `/*` as a -// single level wildcard, and `//*` as a recursive wildcard. However, -// sqlite uses % as a wildcard. To allow for LIKE expressions, we need -// to convert. -void convertWildcards(std::string& str) { - size_t pos; - while ((pos = str.find("%%")) != std::string::npos) { - str.replace(pos, 2, "/*"); +std::string patternFromOsquery(const std::string& input, + bool isLike, + bool isPath) { + // If this is a path, then we must prepend /files. Otherwise we + // assume the caller knows what it's doing. + std::string pattern = isPath ? "/files" + input : input; + + // Augeas presents data as a slash seperated tree. It uses `/*` as a + // single level wildcard, and `//*` as a recursive wildcard. However, + // sqlite uses % as a wildcard. To allow for LIKE expressions, we need + // to convert. + if (isLike) { + size_t pos; + while ((pos = pattern.find("%%")) != std::string::npos) { + pattern.replace(pos, 2, "/*"); + } + while ((pos = pattern.find("%")) != std::string::npos) { + pattern.replace(pos, 1, "*"); + } } - while ((pos = str.find("%")) != std::string::npos) { - str.replace(pos, 1, "*"); + + // augues blurs filename and contents into the node. So when + // dealing with files, osquery must append the recuse wildcard. To + // allow a LIKE query some flexibility, and to prevent augeas + // syntax errors on extra wildcards, we only do this if there is + // not already a wildcard there. (This handles both the LIKE and + // EQUALS case) + if (isPath) { + if (strncmp(&pattern.back(), "*", 1) != 0) { + pattern.append("//*"); + } } + + return pattern; } QueryData genAugeas(QueryContext& context) { @@ -218,36 +240,22 @@ QueryData genAugeas(QueryContext& context) { if (context.hasConstraint("node", LIKE)) { auto nodes = context.constraints["node"].getAll(LIKE); - for (std::string node : nodes) { + for (const auto& node : nodes) { if (node.empty()) { continue; } - convertWildcards(node); - patterns.insert(node); + patterns.insert(patternFromOsquery(node, true, false)); } } if (context.hasConstraint("path", EQUALS)) { // Allow requests via filesystem path. auto paths = context.constraints["path"].getAll(EQUALS); - std::ostringstream pattern; - for (const auto& path : paths) { if (path.empty()) { continue; } - - pattern << "/files" << path; - patterns.insert(pattern.str()); - - pattern.clear(); - pattern.str(std::string()); - - pattern << "/files" << path << "//*"; - patterns.insert(pattern.str()); - - pattern.clear(); - pattern.str(std::string()); + patterns.insert(patternFromOsquery(path, false, true)); } } @@ -256,28 +264,11 @@ QueryData genAugeas(QueryContext& context) { // will break. if (context.hasConstraint("path", LIKE)) { auto paths = context.constraints["path"].getAll(LIKE); - std::ostringstream pattern; - - for (std::string path : paths) { + for (const auto& path : paths) { if (path.empty()) { continue; } - - convertWildcards(path); - - pattern << "/files" << path; - patterns.insert(pattern.str()); - - pattern.clear(); - pattern.str(std::string()); - - if (!strncmp(&path.back(), "*", 1)) { - pattern << "/files" << path << "//*"; - patterns.insert(pattern.str()); - } - - pattern.clear(); - pattern.str(std::string()); + patterns.insert(patternFromOsquery(path, true, true)); } } diff --git a/osquery/tables/system/tests/posix/augeas_tests.cpp b/osquery/tables/system/tests/posix/augeas_tests.cpp index 3a61101517a..917dbd9da2f 100644 --- a/osquery/tables/system/tests/posix/augeas_tests.cpp +++ b/osquery/tables/system/tests/posix/augeas_tests.cpp @@ -87,5 +87,56 @@ TEST_F(AugeasTests, select_hosts_by_node) { << "Value is not empty. Got " << results.rows()[0].at("value") << "instead"; } + +TEST_F(AugeasTests, select_augeas_load) { + auto results = SQL("select * from augeas where node = '/augeas/load'"); + ASSERT_EQ(results.rows().size(), 1U); + ASSERT_EQ(results.rows()[0].at("node"), "/augeas/load"); + ASSERT_EQ(results.rows()[0].at("label"), "load"); + ASSERT_TRUE(results.rows()[0].at("path").empty()); + ASSERT_TRUE(results.rows()[0].at("value").empty()); +} + +TEST_F(AugeasTests, select_augeas_load_wildcards) { + // Exact matches, should be 1 result + ASSERT_EQ( + SQL("select * from augeas where node LIKE '/augeas/load'").rows().size(), + 1U); + ASSERT_EQ(SQL("select * from augeas where node LIKE '/%/load'").rows().size(), + 1U); + + // Single recurse, about 200 results + ASSERT_GT(SQL("select * from augeas where node LIKE '/augeas/load/%'") + .rows() + .size(), + 100U); + + // full recuse, about 1500 results + ASSERT_GT(SQL("select * from augeas where node LIKE '/augeas/load/%%'") + .rows() + .size(), + 1000U); +} + +TEST_F(AugeasTests, select_file_wildcards) { + // These are a bit funny. Augeas doesn't do partial matches, + // and because file is a real file, you have to be carefuly + // with trailing slashes. + ASSERT_EQ( + SQL("select * from augeas where path LIKE '/etc/hosts/%'").rows().size(), + 0U); + ASSERT_EQ( + SQL("select * from augeas where path LIKE '/etc/hosts%'").rows().size(), + 0U); + ASSERT_GE( + SQL("select * from augeas where path LIKE '/etc/hosts'").rows().size(), + 1U); + ASSERT_GE( + SQL("select * from augeas where path LIKE '/etc/hosts%%'").rows().size(), + 1U); + ASSERT_GE( + SQL("select * from augeas where path LIKE '/%/hosts'").rows().size(), 1U); +} + } // namespace tables } // namespace osquery