TEST_F(VirtualTableTests, test_table_cache) { // Get a database connection. Registry::add<cacheTablePlugin>("table", "cache"); auto dbc = SQLiteDBManager::getUnique(); { auto cache = std::make_shared<cacheTablePlugin>(); attachTableInternal("cache", cache->columnDefinition(), dbc); } QueryData results; // Run a query with a join within. std::string statement = "SELECT c2.data as data FROM cache c1, cache c2;"; auto status = queryInternal(statement, results, dbc->db()); dbc->clearAffectedTables(); EXPECT_TRUE(status.ok()); ASSERT_EQ(results.size(), 1U); EXPECT_EQ(results[0]["data"], "more_awesome_data"); // Run the query again, the virtual table cache should have been expired. results.clear(); statement = "SELECT data from cache c1"; queryInternal(statement, results, dbc->db()); ASSERT_EQ(results.size(), 1U); ASSERT_EQ(results[0]["data"], "awesome_data"); }
TEST_F(ResultsTests, test_adding_duplicate_rows_to_query_data) { Row r1, r2, r3; r1["foo"] = "bar"; r1["baz"] = "boo"; r2["foo"] = "baz"; r2["baz"] = "bop"; r3["foo"] = "baz"; r3["baz"] = "bop"; QueryData q; bool s; s = addUniqueRowToQueryData(q, r1); EXPECT_TRUE(s); EXPECT_EQ(q.size(), 1); s = addUniqueRowToQueryData(q, r2); EXPECT_TRUE(s); EXPECT_EQ(q.size(), 2); s = addUniqueRowToQueryData(q, r3); EXPECT_FALSE(s); EXPECT_EQ(q.size(), 2); }
void jsonPrint(const QueryData& q) { printf("[\n"); for (size_t i = 0; i < q.size(); ++i) { std::string row_string; if (serializeRowJSON(q[i], row_string).ok()) { row_string.pop_back(); printf(" %s", row_string.c_str()); if (i < q.size() - 1) { printf(",\n"); } } } printf("\n]\n"); }
TEST_F(LaunchdTests, test_parse_launchd_item) { QueryData results; genLaunchdItem(kTestDataPath + "test_launchd.plist", results); Row expected = { {"path", kTestDataPath + "test_launchd.plist"}, {"name", "test_launchd.plist"}, {"label", "com.apple.mDNSResponder"}, {"run_at_load", ""}, {"keep_alive", ""}, {"on_demand", "false"}, {"disabled", ""}, {"username", "_mdnsresponder"}, {"groupname", "_mdnsresponder"}, {"stdout_path", ""}, {"stderr_path", ""}, {"start_interval", ""}, {"program_arguments", "/usr/sbin/mDNSResponder"}, {"program", ""}, {"watch_paths", ""}, {"queue_directories", ""}, {"inetd_compatibility", ""}, {"start_on_mount", ""}, {"root_directory", ""}, {"working_directory", ""}, {"process_type", ""}, }; ASSERT_EQ(results.size(), 1); for (const auto& column : expected) { EXPECT_EQ(results[0][column.first], column.second); } }
TEST_F(AppsTests, test_sanity_check) { // Test beyond units, that there's at least 1 application on the built host. std::set<std::string> apps; genApplicationsFromPath("/Applications", apps); ASSERT_GT(apps.size(), 0); // Parse each application searching for a parsed Safari. bool found_safari = false; for (const auto& path : apps) { pt::ptree tree; if (osquery::parsePlist(path, tree).ok()) { QueryData results; genApplication(tree, path, results); // No asserts about individual Application parsing, expect edge cases. if (results.size() > 0 && results[0].count("bundle_identifier") > 0 && results[0].at("bundle_identifier") == "com.apple.Safari") { // Assume Safari is installed on the build host. found_safari = true; break; } } } EXPECT_TRUE(found_safari); }
TEST_F(SQLTests, test_sql_sha256) { QueryData d; query("select sha256('test') as test;", d); EXPECT_EQ(d.size(), 1U); EXPECT_EQ(d[0]["test"], "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); }
std::map<std::string, int> computeQueryDataLengths(const QueryData& q) { std::map<std::string, int> results; if (q.size() == 0) { return results; } for (const auto& it : q.front()) { results[it.first] = it.first.size(); } for (const auto& row : q) { for (const auto& it : row) { try { if (it.second.size() > results[it.first]) { results[it.first] = it.second.size(); } } catch (const std::out_of_range& e) { LOG(ERROR) << "Error retreiving the \"" << it.first << "\" key in computeQueryDataLength: " << e.what(); } } } return results; }
TEST_F(AppsTests, test_parse_info_plist) { QueryData results; // Generate a set of results/single row using an example tree. auto tree = getInfoPlistTree(); genApplication(tree, "/Applications/Foobar.app/Contents/Info.plist", results); ASSERT_EQ(results.size(), 1); ASSERT_EQ(results[0].count("name"), 1); Row expected = { {"name", "Foobar.app"}, {"path", "/Applications/Foobar.app"}, {"bundle_executable", "Photo Booth"}, {"bundle_identifier", "com.apple.PhotoBooth"}, {"bundle_name", ""}, {"bundle_short_version", "6.0"}, {"bundle_version", "517"}, {"bundle_package_type", "APPL"}, {"environment", ""}, {"element", ""}, {"compiler", "com.apple.compilers.llvm.clang.1_0"}, {"development_region", "English"}, {"display_name", ""}, {"info_string", ""}, {"minimum_system_version", "10.7.0"}, {"category", "public.app-category.entertainment"}, {"applescript_enabled", ""}, {"copyright", ""}, }; // We could compare the entire map, but iterating the columns will produce // better error text as most likely parsing for a certain column/type changed. for (const auto& column : expected) { EXPECT_EQ(results[0][column.first], column.second); } }
TEST_F(SQLTests, test_sql_base64_conditional_encode) { QueryData d; query("select conditional_to_base64('test') as test;", d); EXPECT_EQ(d.size(), 1U); EXPECT_EQ(d[0]["test"], "test"); QueryData d2; query("select conditional_to_base64('悪因悪果') as test;", d2); EXPECT_EQ(d2.size(), 1U); EXPECT_EQ(d2[0]["test"], "5oKq5Zug5oKq5p6c"); }
TEST_F(VirtualTableTests, test_sqlite3_table_joins) { // Get a database connection. auto dbc = SQLiteDBManager::get(); QueryData results; // Run a query with a join within. std::string statement = "SELECT p.pid FROM osquery_info oi, processes p WHERE oi.pid=p.pid"; auto status = queryInternal(statement, results, dbc.db()); EXPECT_TRUE(status.ok()); EXPECT_EQ(results.size(), 1U); }
static void tryVacuum(sqlite3* db) { std::string q = "SELECT (sum(s1.pageno + 1 == s2.pageno) * 1.0 / count(*)) < 0.01 as v " " FROM " "(SELECT pageno FROM dbstat ORDER BY path) AS s1," "(SELECT pageno FROM dbstat ORDER BY path) AS s2 WHERE " "s1.rowid + 1 = s2.rowid; "; QueryData results; sqlite3_exec(db, q.c_str(), getData, &results, nullptr); if (results.size() > 0 && results[0]["v"].back() == '1') { sqlite3_exec(db, "vacuum;", nullptr, nullptr, nullptr); } }
Status SQLiteDatabasePlugin::get(const std::string& domain, const std::string& key, std::string& value) const { QueryData results; char* err = nullptr; std::string q = "select value from " + domain + " where key = '" + key + "';"; sqlite3_exec(db_, q.c_str(), getData, &results, &err); if (err != nullptr) { sqlite3_free(err); } // Only assign value if the query found a result. if (results.size() > 0) { value = std::move(results[0]["value"]); return Status(0); } return Status(1); }
TEST_F(VirtualTableTests, test_json_extract) { // Get a database connection. Registry::add<jsonTablePlugin>("table", "json"); auto dbc = SQLiteDBManager::get(); { auto json = std::make_shared<jsonTablePlugin>(); attachTableInternal("json", json->columnDefinition(), dbc->db()); } QueryData results; // Run a query with a join within. std::string statement = "SELECT JSON_EXTRACT(data, '$.test') AS test FROM json;"; auto status = queryInternal(statement, results, dbc->db()); EXPECT_TRUE(status.ok()); ASSERT_EQ(results.size(), 1U); EXPECT_EQ(results[0]["test"], "1"); }
TEST_F(SQLiteUtilTests, test_reset) { auto internal_db = SQLiteDBManager::get()->db(); ASSERT_NE(nullptr, internal_db); sqlite3_exec(internal_db, "create view test_view as select 'test';", nullptr, nullptr, nullptr); SQLiteDBManager::resetPrimary(); auto instance = SQLiteDBManager::get(); QueryData results; queryInternal("select * from test_view", results, instance); // Assume the internal (primary) database we reset and recreated. EXPECT_EQ(results.size(), 0U); }
TEST_F(LaunchdTests, test_parse_launchd_item) { // Read the contents of our testing launchd plist. pt::ptree tree; auto launchd_path = kTestDataPath + "test_launchd.plist"; auto status = osquery::parsePlist(launchd_path, tree); ASSERT_TRUE(status.ok()); // Parse the contents into a launchd table row. QueryData results; genLaunchdItem(tree, launchd_path, results); ASSERT_EQ(results.size(), 1U); Row expected = { {"path", kTestDataPath + "test_launchd.plist"}, {"name", "test_launchd.plist"}, {"label", "com.apple.mDNSResponder"}, {"run_at_load", ""}, {"keep_alive", ""}, {"on_demand", "0"}, {"disabled", ""}, {"username", "_mdnsresponder"}, {"groupname", "_mdnsresponder"}, {"stdout_path", ""}, {"stderr_path", ""}, {"start_interval", ""}, {"program_arguments", "/usr/sbin/mDNSResponder"}, {"program", ""}, {"watch_paths", ""}, {"queue_directories", ""}, {"inetd_compatibility", ""}, {"start_on_mount", ""}, {"root_directory", ""}, {"working_directory", ""}, {"process_type", ""}, }; // We could compare the entire map, but iterating the columns will produce // better error text as most likely parsing for a certain column/type changed. for (const auto& column : expected) { EXPECT_EQ(results[0][column.first], column.second); } }
void prettyPrint(const QueryData& results, const std::vector<std::string>& columns, std::map<std::string, size_t>& lengths) { if (results.size() == 0) { return; } // Call a final compute using the column names as minimum lengths. computeRowLengths(results.front(), lengths, true); // Output a nice header wrapping the column names. auto separator = generateToken(lengths, columns); auto header = separator + generateHeader(lengths, columns) + separator; printf("%s", header.c_str()); // Iterate each row and pretty print. for (const auto& row : results) { printf("%s", generateRow(row, lengths, columns).c_str()); } printf("%s", separator.c_str()); }
std::string beautify(const QueryData& q, const std::vector<std::string>& order) { auto lengths = computeQueryDataLengths(q); if (q.size() == 0) { return std::string(); } auto separator = generateSeparator(lengths, order); std::ostringstream results; results << "\n"; results << separator; results << generateHeader(lengths, order); results << separator; for (const auto& r : q) { results << generateRow(r, lengths, order); } results << separator; return results.str(); }
TEST_F(Time, test_sanity) { QueryData data = execute_query("select * from time"); ASSERT_EQ(data.size(), 1ul); ValidatatioMap row_map = { {"weekday", NonEmptyString}, {"year", IntType}, {"month", IntMinMaxCheck(1, 12)}, {"day", IntMinMaxCheck(1, 31)}, {"hour", IntMinMaxCheck(0, 24)}, {"minutes", IntMinMaxCheck(0, 59)}, {"seconds", IntMinMaxCheck(0, 59)}, {"timezone", NonEmptyString}, {"local_time", NonNegativeInt}, {"local_timezone", NonEmptyString}, {"unix_time", NonNegativeInt}, {"timestamp", NonEmptyString}, {"datetime", NonEmptyString}, {"iso_8601", NonEmptyString}, }; validate_rows(data, row_map); }
TEST_F(RegistryTablesTest, test_registry_non_existing_key) { QueryData results; auto ret = queryKey(kInvalidTestKey, results); EXPECT_TRUE(ret.ok()); EXPECT_TRUE(results.size() == 0); }
TEST_F(SQLTests, test_sql_base64_decode) { QueryData d; query("select from_base64('dGVzdA==') as test;", d); EXPECT_EQ(d.size(), 1U); EXPECT_EQ(d[0]["test"], "test"); }
TEST_F(SQLTests, test_sql_md5) { QueryData d; query("select md5('test') as test;", d); EXPECT_EQ(d.size(), 1U); EXPECT_EQ(d[0]["test"], "098f6bcd4621d373cade4e832627b4f6"); }
TEST_F(SQLTests, test_sql_sha1) { QueryData d; query("select sha1('test') as test;", d); EXPECT_EQ(d.size(), 1U); EXPECT_EQ(d[0]["test"], "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"); }
TEST_F(SQLTests, test_sql_base64_encode) { QueryData d; query("select to_base64('test') as test;", d); EXPECT_EQ(d.size(), 1U); EXPECT_EQ(d[0]["test"], "dGVzdA=="); }
void getDrivesForArray(const std::string& arrayName, MDInterface& md, QueryData& data) { std::string path(md.getPathByDevName(arrayName)); if (path.empty()) { LOG(ERROR) << "Could not get file path for " << arrayName; return; } mdu_array_info_t array; if (!md.getArrayInfo(path, array)) { return; } /* Create a vector of with all expected slot positions. As we work through * the RAID disks, we remove discovered slots */ std::vector<size_t> missingSlots(array.raid_disks); std::iota(missingSlots.begin(), missingSlots.end(), 0); /* Keep track of index in QueryData that have removed slots since we can't * make safe assumptions about it's original slot position if disk_number >= * total_disk and we're unable to deteremine total number of missing slots * until we walk thru all MD_SB_DISKS */ std::vector<size_t> removedSlots; size_t qdPos = data.size(); for (size_t i = 0; i < MD_SB_DISKS; i++) { mdu_disk_info_t disk; disk.number = i; if (!md.getDiskInfo(path, disk)) { continue; } if (disk.major > 0) { Row r; r["md_device_name"] = arrayName; r["drive_name"] = md.getDevName(disk.major, disk.minor); r["state"] = getDiskStateStr(disk.state); if (disk.raid_disk >= 0) { r["slot"] = INTEGER(disk.raid_disk); missingSlots.erase( std::remove( missingSlots.begin(), missingSlots.end(), disk.raid_disk), missingSlots.end()); /* We assume that if the disk number is less than the total disk count * of the array, then it assumes its original slot position; If the * number is greater than the disk count, then it's not safe to make * that assumption. We do this check here b/c if a recovery is targeted * for the same slot, we potentially miss identifying the original slot * position of the bad disk. */ } else if (disk.raid_disk < 0 && disk.number < array.raid_disks) { r["slot"] = std::to_string(disk.number); missingSlots.erase( std::remove(missingSlots.begin(), missingSlots.end(), disk.number), missingSlots.end()); /* Mark QueryData position as a removedSlot to handle later*/ } else { removedSlots.push_back(qdPos); } qdPos++; data.push_back(r); } } /* Handle all missing slots. See `scattered_faulty_and_removed` unit test in * `./tests/md_tables_tests.cpp`*/ for (const auto& slot : missingSlots) { if (!removedSlots.empty()) { data[removedSlots[0]]["slot"] = INTEGER(slot); removedSlots.erase(removedSlots.begin()); } else { Row r; r["md_device_name"] = arrayName; r["drive_name"] = "unknown"; r["state"] = "removed"; r["slot"] = std::to_string(slot); data.push_back(r); } } }