diff --git a/CMakeLists.txt b/CMakeLists.txt index 64096dc1..1696e202 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,9 @@ else() endif() set(OS_COMPILE_FLAGS "-std=c++11") + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(OS_COMPILE_FLAGS "${OS_COMPILE_FLAGS} -g") + endif() set(OS_WHOLELINK_PRE "-Wl,-whole-archive") set(OS_WHOLELINK_POST "-Wl,-no-whole-archive") endif() diff --git a/Makefile b/Makefile index f4445844..3c3b8d95 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,8 @@ else endif all: .setup - cd build/$(BUILD_DIR) && cmake ../.. && make --no-print-directory $(MAKEFLAGS) + cd build/$(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE= ../.. && \ + make --no-print-directory $(MAKEFLAGS) debug: .setup cd build/$(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=Debug ../../ && \ diff --git a/include/osquery/events.h b/include/osquery/events.h index 68ca9c77..8b62d470 100644 --- a/include/osquery/events.h +++ b/include/osquery/events.h @@ -12,9 +12,9 @@ #include #include -#include "osquery/status.h" #include "osquery/database.h" #include "osquery/registry.h" +#include "osquery/status.h" namespace osquery { diff --git a/osquery/devtools/shell.cpp b/osquery/devtools/shell.cpp index 58d34c49..f44f2ac0 100644 --- a/osquery/devtools/shell.cpp +++ b/osquery/devtools/shell.cpp @@ -4361,10 +4361,6 @@ int launchIntoShell(int argc, char **argv) { return rc; } } - } else { - fprintf(stderr, "%s: Error: unknown option: %s\n", Argv0, z); - fprintf(stderr, "Use -help for a list of options.\n"); - return 1; } } @@ -4431,7 +4427,10 @@ int launchIntoShell(int argc, char **argv) { sqlite3_close(data.db); } sqlite3_free(data.zFreeOnClose); - free(data.prettyPrint); + + if (data.prettyPrint != nullptr) { + delete data.prettyPrint; + } return rc; } } diff --git a/osquery/events/events.cpp b/osquery/events/events.cpp index e86fe3ec..5f1f50c0 100644 --- a/osquery/events/events.cpp +++ b/osquery/events/events.cpp @@ -9,10 +9,16 @@ #include "osquery/core.h" #include "osquery/core/conversions.h" #include "osquery/events.h" +#include "osquery/flags.h" #include "osquery/dispatcher.h" namespace osquery { +DEFINE_osquery_flag(bool, + event_pubsub, + true, + "Use (enable) the osquery eventing pub/sub.") + const std::vector kEventTimeLists = { 1 * 60, // 1 minute 1 * 60 * 60, // 1 hour @@ -375,6 +381,10 @@ Status EventFactory::deregisterEventPublishers() { namespace osquery { namespace registries { void faucet(EventPublishers ets, EventSubscribers ems) { + if (!FLAGS_event_pubsub) { + // Invocation disabled eventing. + return; + } auto& ef = osquery::EventFactory::getInstance(); for (const auto& event_pub : ets) { ef.registerEventPublisher(event_pub.second); diff --git a/osquery/tables/specs/linux/rpm_packages.table b/osquery/tables/specs/linux/rpm_packages.table index f4a07292..a1d0f29f 100644 --- a/osquery/tables/specs/linux/rpm_packages.table +++ b/osquery/tables/specs/linux/rpm_packages.table @@ -5,9 +5,7 @@ schema([ Column(name="release", type="std::string"), Column(name="source", type="std::string"), Column(name="size", type="std::string"), - Column(name="dsaheader", type="std::string"), - Column(name="rsaheader", type="std::string"), - Column(name="sha1header", type="std::string"), + Column(name="sha1", type="std::string"), Column(name="arch", type="std::string"), ]) implementation("system/rpm_packages@genRpms") diff --git a/osquery/tables/system/linux/rpm_packages.cpp b/osquery/tables/system/linux/rpm_packages.cpp index 0efdeeb4..493ceca3 100644 --- a/osquery/tables/system/linux/rpm_packages.cpp +++ b/osquery/tables/system/linux/rpm_packages.cpp @@ -12,56 +12,84 @@ #include +#include "osquery/logger.h" #include "osquery/database.h" namespace osquery { namespace tables { +/** + * @brief Return a string representation of the RPM tag type. + * + * @param header A librpm header. + * @param tag A librpm rpmTag_t name. + * @param td A librpm rpmtd. + * + * Given a librpm iterator header and a requested tag name: + * 1. Determine the type of the tag (the class of value). + * 2. Request a const pointer or cast of numerate to that class. + * 3. Lexical-cast the value for SQL. + * + * @return The string representation of the tag type. + */ +std::string getRpmAttribute(const Header& header, rpmTag tag, const rpmtd& td) { + std::string result; + + if (headerGet(header, tag, td, HEADERGET_DEFAULT) == 0) { + // Intentional check for a 0 = failure. + VLOG(3) << "Could not get RPM header flag."; + return result; + } + + if (rpmTagGetClass(tag) == RPM_NUMERIC_CLASS) { + long long int attr = rpmtdGetNumber(td); + result = boost::lexical_cast(attr); + } else if (rpmTagGetClass(tag) == RPM_STRING_CLASS) { + const char* attr = rpmtdGetString(td); + if (attr != nullptr) { + result = std::string(attr); + } + } + + return result; +} + QueryData genRpms() { QueryData results; + // The following implementation uses http://rpm.org/api/4.11.1/ Header header; rpmdbMatchIterator match_iterator; - rpmReadConfigFiles(NULL, NULL); + + rpmInitCrypto(); + if (rpmReadConfigFiles(nullptr, nullptr) != 0) { + LOG(ERROR) << "Cannot read RPM configuration files."; + return results; + } rpmts ts = rpmtsCreate(); match_iterator = rpmtsInitIterator(ts, RPMTAG_NAME, NULL, 0); while ((header = rpmdbNextIterator(match_iterator)) != NULL) { Row r; rpmtd td = rpmtdNew(); + r["name"] = getRpmAttribute(header, RPMTAG_NAME, td); + r["version"] = getRpmAttribute(header, RPMTAG_VERSION, td); + r["release"] = getRpmAttribute(header, RPMTAG_RELEASE, td); + r["source"] = getRpmAttribute(header, RPMTAG_SOURCERPM, td); + r["size"] = getRpmAttribute(header, RPMTAG_SIZE, td); + r["sha1"] = getRpmAttribute(header, RPMTAG_SHA1HEADER, td); + r["arch"] = getRpmAttribute(header, RPMTAG_ARCH, td); - headerGet(header, RPMTAG_NAME, td, HEADERGET_DEFAULT); - r["name"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_VERSION, td, HEADERGET_DEFAULT); - r["version"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_RELEASE, td, HEADERGET_DEFAULT); - r["release"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_SOURCERPM, td, HEADERGET_DEFAULT); - r["source"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_SIZE, td, HEADERGET_DEFAULT); - r["size"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_DSAHEADER, td, HEADERGET_DEFAULT); - r["dsaheader"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_RSAHEADER, td, HEADERGET_DEFAULT); - r["rsaheader"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_SHA1HEADER, td, HEADERGET_DEFAULT); - r["sha1header"] = std::string(rpmtdGetString(td)); - - headerGet(header, RPMTAG_ARCH, td, HEADERGET_DEFAULT); - r["arch"] = std::string(rpmtdGetString(td)); - + rpmtdFree(td); results.push_back(r); } rpmdbFreeIterator(match_iterator); rpmtsFree(ts); + + rpmFreeCrypto(); + rpmFreeRpmrc(); + return results; } } diff --git a/tools/profile.py b/tools/profile.py index 1e34a802..c16a6086 100755 --- a/tools/profile.py +++ b/tools/profile.py @@ -6,7 +6,13 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -import argparse +try: + import argparse +except ImportError: + print ("Cannot import argparse.") + print ("Try: sudo yum install python-argparse") + exit(1) + import json import os import psutil @@ -54,7 +60,9 @@ def queries_from_tables(path, restrict): tables.append("%s.%s" % (spec_platform, table_name)) tables = [t for t in tables if t not in restrict_tables] - queries = {t: "SELECT * FROM %s;" % t.split(".", 1)[1] for t in tables} + queries = {} + for table in tables: + queries[table] = "SELECT * FROM %s;" % table.split(".", 1)[1] return queries def get_stats(p, interval=1): @@ -68,6 +76,45 @@ def get_stats(p, interval=1): "memory": p.memory_info_ex(), } +def check_leaks(shell, query, supp_file=None): + """Run valgrind using the shell and a query, parse leak reports.""" + start_time = time.time() + suppressions = "" if supp_file is None else "--suppressions=%s" % supp_file + cmd = "valgrind --tool=memcheck %s %s --query=\"%s\"" % ( + suppressions, shell, query) + proc = subprocess.Popen(cmd, + shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = proc.communicate() + summary = { + "definitely": None, + "indirectly": None, + "possibly": None, + } + for line in stderr.split("\n"): + for key in summary: + if line.find(key) >= 0: + summary[key] = line.split(":")[1].strip() + return summary + +def profile_leaks(shell, queries, supp_file=None): + report = {} + for name, query in queries.iteritems(): + print ("Analyzing leaks in query: %s" % query) + summary = check_leaks(shell, query, supp_file) + display = [] + for key in summary: + output = summary[key] + if output is not None and output[0] != "0": + # Add some fun colored output if leaking. + if key == "definitely": + output = red(output) + if key == "indirectly": + output = yellow(output) + display.append("%s: %s" % (key, output)) + print (" %s" % "; ".join(display)) + report[name] = summary + return report + def run_query(shell, query, timeout=0, count=1): """Execute the osquery run testing wrapper with a setup/teardown delay.""" start_time = time.time() @@ -174,6 +221,10 @@ if __name__ == "__main__": help="Number of times to run each query.") parser.add_argument("--rounds", default=1, type=int, help="Run the profile for multiple rounds and use the average.") + parser.add_argument("--leaks", default=False, action="store_true", + help="Check for memory leaks instead of performance.") + parser.add_argument("--suppressions", default=None, + help="Add a suppressions files to memory leak checking.") parser.add_argument("--shell", default="./build/%s/tools/run" % (platform), help="Path to osquery run wrapper.") @@ -198,6 +249,11 @@ if __name__ == "__main__": else: queries = queries_from_tables(args.tables, args.restrict) + if args.leaks: + results = profile_leaks(args.shell, queries, + supp_file=args.suppressions) + exit(0) + # Start the profiling! results = profile(args.shell, queries, timeout=args.timeout, count=args.count, rounds=args.rounds)