Prefer /etc/os-release for Linux os_version (#2667)

This commit is contained in:
Teddy Reed 2016-10-22 16:58:32 -07:00 committed by GitHub
parent 5bb5ae1030
commit df25f27efb
3 changed files with 81 additions and 40 deletions

View File

@ -8,6 +8,7 @@
*
*/
#include <map>
#include <string>
#include <boost/xpressive/xpressive.hpp>
@ -23,38 +24,80 @@ namespace xp = boost::xpressive;
namespace osquery {
namespace tables {
#if defined(REDHAT_BASED)
const std::string kLinuxOSRelease = "/etc/redhat-release";
const std::string kLinuxOSRegex =
"(?P<name>[\\w+\\s]+) .* "
"(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)\\.?(?P<patch>\\w+)?";
#elif defined(DEBIAN)
const std::string kLinuxOSRelease = "/etc/os-release";
const std::string kLinuxOSRegex =
"PRETTY_NAME=\"(?P<name>[\\w \\/]*) "
"(?P<major>[0-9]+)[\\.]{0,1}(?P<minor>[0-9]*)[\\.]{0,1}(?P<patch>[0-9]*).*"
"\"";
#else
const std::string kLinuxOSRelease = "/etc/os-release";
#ifdef UBUNTU_XENIAL
const std::string kLinuxOSRegex =
"VERSION=\"(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)[\\.]{0,1}(?P<patch>[0-9]+)"
"?.* \\((?P<name>[\\w ]*)\\)\"$";
#else
const std::string kLinuxOSRegex =
"VERSION=\"(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)[\\.]{0,1}(?P<patch>[0-9]+)"
"?.*, (?P<name>[\\w ]*)\"$";
#endif
#endif
const std::string kOSRelease = "/etc/os-release";
const std::string kRedhatRelease = "/etc/redhat-release";
QueryData genOSVersion(QueryContext& context) {
const std::map<std::string, std::string> kOSReleaseColumns = {
{"NAME", "name"},
{"VERSION", "version"},
{"BUILD_ID", "build"},
{"ID", "platform"},
{"ID_LIKE", "platform_like"},
{"VERSION_CODENAME", "codename"},
{"VERSION_ID", "_id"},
};
QueryData genOSRelease() {
// This will parse /etc/os-version according to the systemd manual.
std::string content;
if (!forensicReadFile(kLinuxOSRelease, content).ok()) {
if (!readFile(kOSRelease, content).ok()) {
return {};
}
Row r;
auto rx = xp::sregex::compile(kLinuxOSRegex);
for (const auto& line : osquery::split(content, "\n")) {
auto fields = osquery::split(line, "=", 1);
if (fields.size() != 2) {
continue;
}
auto column = std::ref(kOSReleaseColumns.at("VERSION_CODENAME"));
if (kOSReleaseColumns.count(fields[0]) != 0) {
column = std::ref(kOSReleaseColumns.at(fields[0]));
} else if (fields[0].find("CODENAME") == std::string::npos) {
// Some distros may attach/invent their own CODENAME field.
continue;
}
r[column] = std::move(fields[1]);
if (!r.at(column).empty() && r.at(column)[0] == '"') {
// This is quote-enclosed string, make it pretty!
r[column] = r[column].substr(1, r.at(column).size() - 2);
}
if (column.get() == "_id") {
auto parts = osquery::split(r.at(column), ".", 2);
switch (parts.size()) {
case 3:
r["patch"] = parts[2];
case 2:
r["minor"] = parts[1];
case 1:
r["major"] = parts[0];
break;
}
}
}
return {r};
}
QueryData genOSVersion(QueryContext& context) {
if (isReadable(kOSRelease)) {
return genOSRelease();
}
std::string content;
if (!isReadable(kRedhatRelease) || !readFile(kRedhatRelease, content).ok()) {
// This is an unknown Linux OS.
return {};
}
Row r;
// This is an older version of a Redhat-based OS.
auto rx = xp::sregex::compile(
"(?P<name>[\\w+\\s]+) .* "
"(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)\\.?(?P<patch>\\w+)?");
xp::smatch matches;
for (const auto& line : osquery::split(content, "\n")) {
if (xp::regex_search(line, matches, rx)) {
@ -67,6 +110,9 @@ QueryData genOSVersion(QueryContext& context) {
}
}
r["version"] = content;
r["platform_like"] = "rhel";
// No build name.
r["build"] = "";
return {r};

View File

@ -26,21 +26,12 @@ class SystemsTablesTests : public testing::Test {};
TEST_F(SystemsTablesTests, test_os_version) {
auto results = SQL("select * from os_version");
// Issue #2564: There is no os_version on Windows.
if (!isPlatform(PlatformType::TYPE_WINDOWS)) {
EXPECT_EQ(results.rows().size(), 1U);
EXPECT_EQ(results.rows().size(), 1U);
// Make sure major and minor have data (a missing value of -1 is an error).
EXPECT_FALSE(results.rows()[0].at("major").empty());
// Debian does not define a minor.
#if !defined(DEBIAN)
EXPECT_FALSE(results.rows()[0].at("minor").empty());
#endif
// The OS name should be filled in too.
EXPECT_FALSE(results.rows()[0].at("name").empty());
}
// Make sure major and minor have data (a missing value of -1 is an error).
EXPECT_FALSE(results.rows()[0].at("major").empty());
// The OS name should be filled in too.
EXPECT_FALSE(results.rows()[0].at("name").empty());
}
TEST_F(SystemsTablesTests, test_process_info) {

View File

@ -2,10 +2,14 @@ table_name("os_version")
description("A single row containing the operating system name and version.")
schema([
Column("name", TEXT, "Distribution or product name"),
Column("version", TEXT, "Pretty, suitable for presentation, OS version"),
Column("major", INTEGER, "Major release version"),
Column("minor", INTEGER, "Minor release version"),
Column("patch", INTEGER, "Optional patch release"),
Column("build", TEXT, "Optional build-specific or variant string"),
Column("platform", TEXT, "OS Platform or ID"),
Column("platform_like", TEXT, "Closely related platforms"),
Column("codename", TEXT, "OS version codename"),
])
implementation("system/os_version@genOSVersion")
fuzz_paths([