selinux_settings: New table that presents effective SELinux settings (#6118)

* selinux_settings: New table that presents effective SELinux settings

* selinux_settings: Use the SELinux root path from the mounted fs

The code that was originally directly implemented inside the
`mounts` table has been moved outside so that it can be reused
by the selinux_settings table.

This also updates the code to use getmntent_r instead of getmntent.
This commit is contained in:
Alessandro Gario 2020-01-27 15:03:52 +01:00 committed by GitHub
parent 0b2aa61a7d
commit 8d9059f914
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 588 additions and 37 deletions

View File

@ -25,6 +25,7 @@ osquery_cxx_library(
LINUX,
[
"linux/proc.h",
"linux/mounts.h",
],
),
],
@ -55,6 +56,7 @@ osquery_cxx_library(
[
"linux/mem.cpp",
"linux/proc.cpp",
"linux/mounts.cpp",
],
),
(
@ -74,6 +76,7 @@ osquery_cxx_library(
osquery_target("osquery/utils/conversions:conversions"),
osquery_target("osquery/utils/status:status"),
osquery_target("osquery/utils/system:env"),
osquery_target("osquery/utils/system:filepath"),
osquery_tp_target("boost"),
osquery_tp_target("libarchive"),
osquery_tp_target("zstd"),

View File

@ -36,6 +36,7 @@ function(generateOsqueryFilesystem)
list(APPEND source_files
linux/mem.cpp
linux/proc.cpp
linux/mounts.cpp
)
elseif(DEFINED PLATFORM_WINDOWS)
@ -55,6 +56,7 @@ function(generateOsqueryFilesystem)
osquery_utils_conversions
osquery_utils_status
osquery_utils_system_env
osquery_utils_system_filepath
thirdparty_boost
thirdparty_libarchive
thirdparty_zstd
@ -68,6 +70,7 @@ function(generateOsqueryFilesystem)
if(DEFINED PLATFORM_LINUX)
list(APPEND public_header_files
linux/proc.h
linux/mounts.h
)
endif()

View File

@ -0,0 +1,113 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed in accordance with the terms specified in
* the LICENSE file found in the root directory of this source tree.
*/
#include <mntent.h>
#include <sys/vfs.h>
#include <osquery/filesystem/linux/mounts.h>
#include <osquery/logger.h>
#include <osquery/utils/system/filepath.h>
namespace osquery {
namespace {
const std::string kMountsPseudoFile{"/proc/mounts"};
struct MountDataDeleter final {
void operator()(FILE* ptr) {
if (ptr == nullptr) {
return;
}
endmntent(ptr);
}
};
using MountData = std::unique_ptr<FILE, MountDataDeleter>;
Status getMountData(MountData& obj) {
obj = {};
auto mount_data = setmntent(kMountsPseudoFile.c_str(), "r");
if (mount_data == nullptr) {
return Status::failure("Failed to open the '" + kMountsPseudoFile +
"' pseudo file");
}
obj.reset(mount_data);
return Status::success();
}
} // namespace
Status getMountedFilesystemMap(MountedFilesystemMap& mounted_fs_info) {
mounted_fs_info = {};
MountData mount_data;
auto status = getMountData(mount_data);
if (!status.ok()) {
return status;
}
std::vector<char> string_buffer(4096);
for (;;) {
mntent ent = {};
if (getmntent_r(mount_data.get(),
&ent,
string_buffer.data(),
string_buffer.size()) == nullptr) {
if (errno != ENOENT) {
LOG(ERROR) << "getmntent_r failed with errno " << std::to_string(errno);
}
break;
}
MountInformation mount_info = {};
mount_info.type = ent.mnt_type;
mount_info.device = ent.mnt_fsname;
mount_info.device_alias = canonicalize_file_name(ent.mnt_fsname);
mount_info.path = ent.mnt_dir;
mount_info.flags = ent.mnt_opts;
if (mount_info.type == "autofs") {
VLOG(1) << "Skipping statfs information for autofs mount: "
<< mount_info.path;
} else {
struct statfs stats = {};
if (statfs(mount_info.path.c_str(), &stats) == 0) {
MountInformation::StatFsInfo statfs_info = {};
statfs_info.block_size = static_cast<std::uint32_t>(stats.f_bsize);
statfs_info.block_count = static_cast<std::uint32_t>(stats.f_blocks);
statfs_info.free_block_count =
static_cast<std::uint32_t>(stats.f_bfree);
statfs_info.unprivileged_free_block_count =
static_cast<std::uint32_t>(stats.f_bavail);
statfs_info.inode_count = static_cast<std::uint32_t>(stats.f_files);
statfs_info.free_inode_count =
static_cast<std::uint32_t>(stats.f_ffree);
mount_info.optional_statfs_info = std::move(statfs_info);
} else {
LOG(ERROR) << "statfs failed with errno " << std::to_string(errno)
<< " on path " << mount_info.path;
}
}
mounted_fs_info.insert({mount_info.path, std::move(mount_info)});
}
return Status::success();
}
} // namespace osquery

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed in accordance with the terms specified in
* the LICENSE file found in the root directory of this source tree.
*/
#include <boost/optional.hpp>
#include <unordered_map>
#include <osquery/core.h>
#include <osquery/filesystem/filesystem.h>
namespace osquery {
// Information about a single mounted filesystem
struct MountInformation final {
struct StatFsInfo final {
// Optimal transfer block size (statfs::f_bsize)
std::uint32_t block_size{0U};
// Total data blocks in file system (statfs::f_blocks)
std::uint32_t block_count{0U};
// Free blocks in filesystem (statfs::f_bfree)
std::uint32_t free_block_count{0U};
// Free blocks available to unprivileged user (statfs::f_bavail)
std::uint32_t unprivileged_free_block_count{0U};
// Total file nodes in filesystem (statfs::f_files)
std::uint32_t inode_count{0U};
// Free file nodes in filesystem (statfs::f_ffree)
std::uint32_t free_inode_count{0U};
};
// Filesystem type
std::string type;
// Device path
std::string device;
// Canonicalized device path
std::string device_alias;
// Mount path
std::string path;
// Mount options
std::string flags;
// statfs information; may not be set if the statfs operation
// has failed
boost::optional<StatFsInfo> optional_statfs_info;
};
// Information about all mounted filesystems
using MountedFilesystemMap = std::unordered_map<std::string, MountInformation>;
Status getMountedFilesystemMap(MountedFilesystemMap& mounted_fs_info);
} // namespace osquery

View File

@ -157,6 +157,7 @@ osquery_cxx_library(
"linux/process_open_files.cpp",
"linux/processes.cpp",
"linux/rpm_packages.cpp",
"linux/selinux_settings.cpp",
"linux/shadow.cpp",
"linux/shared_memory.cpp",
"linux/smbios_tables.cpp",

View File

@ -78,6 +78,7 @@ function(generateOsqueryTablesSystemSystemtable)
linux/usb_devices.cpp
linux/user_groups.cpp
linux/users.cpp
linux/selinux_settings.cpp
)
elseif(DEFINED PLATFORM_MACOS)

View File

@ -6,57 +6,58 @@
* the LICENSE file found in the root directory of this source tree.
*/
#include <mntent.h>
#include <sys/vfs.h>
#include <set>
#include <osquery/core.h>
#include <osquery/filesystem/filesystem.h>
#include <osquery/filesystem/linux/mounts.h>
#include <osquery/logger.h>
#include <osquery/tables.h>
#include <osquery/utils/system/filepath.h>
namespace osquery {
namespace tables {
std::set<std::string> kMountStatBlacklist = {
"autofs",
};
QueryData genMounts(QueryContext& context) {
QueryData results;
FILE* mounts = setmntent("/proc/mounts", "r");
if (mounts == nullptr) {
MountedFilesystemMap mounted_fs_map{};
auto status = getMountedFilesystemMap(mounted_fs_map);
if (!status.ok()) {
LOG(ERROR) << "Failed to list the system mounts: " << status.getMessage();
return {};
}
struct mntent* ent = nullptr;
while ((ent = getmntent(mounts))) {
Row r;
QueryData results;
r["type"] = std::string(ent->mnt_type);
r["device"] = std::string(ent->mnt_fsname);
r["device_alias"] = canonicalize_file_name(ent->mnt_fsname);
r["path"] = std::string(ent->mnt_dir);
r["flags"] = std::string(ent->mnt_opts);
for (const auto& p : mounted_fs_map) {
Row r = {};
// Check type against blacklist before running statfs.
if (kMountStatBlacklist.find(r["type"]) == kMountStatBlacklist.end()) {
struct statfs st;
if (!statfs(ent->mnt_dir, &st)) {
r["blocks_size"] = BIGINT(st.f_bsize);
r["blocks"] = BIGINT(st.f_blocks);
r["blocks_free"] = BIGINT(st.f_bfree);
r["blocks_available"] = BIGINT(st.f_bavail);
r["inodes"] = BIGINT(st.f_files);
r["inodes_free"] = BIGINT(st.f_ffree);
}
const auto& mount_info = p.second;
r["type"] = mount_info.type;
r["device"] = mount_info.device;
r["device_alias"] = mount_info.device_alias;
r["path"] = mount_info.path;
r["flags"] = mount_info.flags;
// optional::has_value is not present in Boost 1.66 (which is
// what we have when compiling with BUCK)
if (mount_info.optional_statfs_info != boost::none) {
const auto& statfs_info = mount_info.optional_statfs_info.value();
r["blocks_size"] = BIGINT(statfs_info.block_size);
r["blocks"] = BIGINT(statfs_info.block_count);
r["blocks_free"] = BIGINT(statfs_info.free_block_count);
r["blocks_available"] = BIGINT(statfs_info.unprivileged_free_block_count);
r["inodes"] = BIGINT(statfs_info.inode_count);
r["inodes_free"] = BIGINT(statfs_info.free_inode_count);
} else {
r["blocks_size"] = BIGINT(0);
r["blocks"] = BIGINT(0);
r["blocks_free"] = BIGINT(0);
r["blocks_available"] = BIGINT(0);
r["inodes"] = BIGINT(0);
r["inodes_free"] = BIGINT(0);
}
results.push_back(std::move(r));
}
endmntent(mounts);
return results;
}

View File

@ -0,0 +1,274 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed in accordance with the terms specified in
* the LICENSE file found in the root directory of this source tree.
*/
#include <osquery/core.h>
#include <osquery/filesystem/filesystem.h>
#include <osquery/filesystem/linux/mounts.h>
#include <osquery/logger.h>
#include <osquery/tables.h>
#include <boost/algorithm/string.hpp>
namespace osquery {
namespace tables {
Status keyNameFromFilePath(std::string& key_name,
const std::string& selinuxfs_path,
const std::string& file_path);
Status translateBooleanKeyValue(std::string& value,
const std::string& raw_value);
namespace {
Status getSelinuxfsMountPath(std::string& path) {
path = {};
MountedFilesystemMap mounted_fs_map{};
auto status = getMountedFilesystemMap(mounted_fs_map);
if (!status.ok()) {
return status;
}
// clang-format off
auto selinuxfs_info_it = std::find_if(
mounted_fs_map.begin(),
mounted_fs_map.end(),
[](const std::pair<std::string, MountInformation> &p) -> bool {
const auto &fs_type = p.second.type;
return (fs_type == "selinuxfs");
}
);
// clang-format on
if (selinuxfs_info_it != mounted_fs_map.end()) {
path = selinuxfs_info_it->second.path;
}
return Status::success();
}
const std::vector<std::string> kRootKeyList = {"checkreqprot",
"deny_unknown",
"enforce",
"mls",
"policyvers",
"reject_unknown"};
const std::vector<std::string> kScopeList = {
"booleans", "policy_capabilities", "initial_contexts"};
Status generateScopeKey(Row& row,
const std::string& scope,
const std::string& selinuxfs_path,
const std::string& key) {
row = {};
row["scope"] = scope;
row["key"] = key;
auto path = selinuxfs_path + "/" + scope + "/" + key;
std::string raw_value;
auto status = readFile(path, raw_value);
if (!status.ok()) {
return Status::failure("Failed to retrieve SELinux key value: " + path +
". Error: " + status.getMessage());
}
std::string value;
if (scope == "booleans") {
status = translateBooleanKeyValue(value, raw_value);
if (!status.ok()) {
return Status::failure("Failed to retrieve SELinux key value: " + path +
". Error: " + status.getMessage());
}
} else {
value = std::move(raw_value);
}
row["value"] = std::move(value);
return Status::success();
}
Status generateScope(QueryData& row_list,
const std::string& selinuxfs_path,
const std::string& scope) {
auto scope_directory_path = selinuxfs_path + "/" + scope;
std::vector<std::string> path_list;
auto status = listFilesInDirectory(scope_directory_path, path_list, true);
if (!status.ok()) {
return Status::failure("Failed to enumerate the files in '" +
scope_directory_path + "': " + status.getMessage());
}
for (const auto& path : path_list) {
if (isDirectory(path).ok()) {
continue;
}
std::string key_name = {};
status = keyNameFromFilePath(key_name, selinuxfs_path, path);
if (!status.ok()) {
LOG(ERROR) << "Invalid SELinux key path '" + path
<< "'. Error: " << status.getMessage();
continue;
}
Row row;
status = generateScopeKey(row, scope, selinuxfs_path, key_name);
if (!status.ok()) {
LOG(ERROR) << status.getMessage();
continue;
}
row_list.push_back(std::move(row));
}
return Status::success();
}
Status generateClasses(QueryData& row_list, const std::string& selinuxfs_path) {
auto class_root_path = selinuxfs_path + "/class";
std::vector<std::string> path_list;
auto status = listFilesInDirectory(class_root_path, path_list, true);
if (!status.ok()) {
return Status::failure(
"Failed to enumerate the SELinux settings from under '" +
class_root_path + "': " + status.getMessage());
}
for (const auto& path : path_list) {
if (isDirectory(path).ok()) {
continue;
}
auto value_index = path.find_last_of('/');
if (value_index == std::string::npos) {
continue;
}
++value_index;
if (value_index >= path.size()) {
continue;
}
auto value = path.substr(value_index);
if (value == "index") {
continue;
}
auto key_index = class_root_path.size() + 1U;
auto key_length = value_index - key_index - 1U;
auto key = path.substr(key_index, key_length);
Row row = {};
row["scope"] = "class";
row["key"] = key;
row["value"] = value;
row_list.push_back(std::move(row));
}
return Status::success();
}
} // namespace
Status keyNameFromFilePath(std::string& key_name,
const std::string& selinuxfs_path,
const std::string& file_path) {
// This limit only applies to this specific case and not to the
// items in the 'class' scope
static const std::size_t kMinimumPathSize = selinuxfs_path.size() + 2U;
key_name = {};
if (file_path.find(selinuxfs_path) != 0) {
return Status::failure("The given path is outside the SELinux folder");
}
if (file_path.size() <= kMinimumPathSize) {
return Status::failure("The given path is too small");
}
auto key_name_index = file_path.find_last_of('/');
if (key_name_index == std::string::npos ||
key_name_index + 1 >= file_path.size()) {
return Status::failure("Invalid path specified");
}
++key_name_index;
key_name = file_path.substr(key_name_index);
if (key_name.empty()) {
return Status::failure("Key name is empty");
}
return Status::success();
}
Status translateBooleanKeyValue(std::string& value,
const std::string& raw_value) {
value = {};
if (raw_value == "0 0") {
value = "off";
} else if (raw_value == "1 1") {
value = "on";
} else {
return Status::failure("Invalid raw value for boolean key");
}
return Status::success();
}
QueryData genSELinuxSettings(QueryContext& context) {
std::string selinuxfs_path;
auto status = getSelinuxfsMountPath(selinuxfs_path);
if (!status.ok()) {
LOG(ERROR) << "Failed to acquire the SELinux FS path"
<< status.getMessage();
return {};
}
if (selinuxfs_path.empty()) {
return {};
}
QueryData row_list;
for (const auto& root_key : kRootKeyList) {
Row row;
status = generateScopeKey(row, "", selinuxfs_path, root_key);
if (!status.ok()) {
LOG(ERROR) << "Failed to generate SELinux root key: "
<< status.getMessage();
}
row_list.push_back(std::move(row));
}
for (const auto& scope : kScopeList) {
status = generateScope(row_list, selinuxfs_path, scope);
if (!status.ok()) {
LOG(ERROR) << "Failed to generate SELinux scope: " << status.getMessage();
}
}
status = generateClasses(row_list, selinuxfs_path);
if (!status.ok()) {
LOG(ERROR) << "failed to bla bla";
}
return row_list;
}
} // namespace tables
} // namespace osquery

View File

@ -18,6 +18,7 @@ osquery_cxx_test(
"linux/pci_devices_tests.cpp",
"linux/pcidb_tests.cpp",
"linux/portage_tests.cpp",
"linux/selinux_settings_tests.cpp"
],
),
],

View File

@ -32,6 +32,7 @@ function(generateOsqueryTablesSystemLinuxTests)
linux/pci_devices_tests.cpp
linux/pcidb_tests.cpp
linux/portage_tests.cpp
linux/selinux_settings_tests.cpp
)
target_link_libraries(osquery_tables_system_linux_tests-test PRIVATE

View File

@ -0,0 +1,79 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed in accordance with the terms specified in
* the LICENSE file found in the root directory of this source tree.
*/
#include <pwd.h>
#include <gtest/gtest.h>
#include <osquery/core.h>
#include <osquery/logger.h>
#include <osquery/system.h>
#include <osquery/utils/status/status.h>
namespace osquery {
namespace tables {
Status keyNameFromFilePath(std::string& key_name,
const std::string& selinuxfs_path,
const std::string& file_path);
bool isBooleanKey(const std::string& key_name);
Status translateBooleanKeyValue(std::string& value,
const std::string& raw_value);
class SELinuxSettingsTests : public testing::Test {};
TEST_F(SELinuxSettingsTests, keyNameFromFilePath) {
static const std::string kSelinuxFsPath{"/sys/fs/selinux"};
struct TestCase final {
bool expected_status{false};
std::string expected_key_name;
std::string file_path;
};
const std::vector<TestCase> test_case_list = {
{false, "", kSelinuxFsPath},
{false, "", kSelinuxFsPath + "/"},
{true, "devnull", kSelinuxFsPath + "/initial_contexts/devnull"},
{true, "name_bind", kSelinuxFsPath + "/class/smc_socket/perms/name_bind"},
{true,
"nnp_nosuid_transition",
kSelinuxFsPath + "/policy_capabilities/nnp_nosuid_transition"}};
for (const auto& test_case : test_case_list) {
std::string key_name;
auto status =
keyNameFromFilePath(key_name, kSelinuxFsPath, test_case.file_path);
CHECK_EQ(key_name, test_case.expected_key_name);
ASSERT_EQ(status.ok(), test_case.expected_status);
}
}
TEST_F(SELinuxSettingsTests, translateBooleanKeyValue) {
struct TestCase final {
bool expected_status{false};
std::string expected_value;
std::string raw_value;
};
const std::vector<TestCase> test_case_list = {
{true, "on", "1 1"}, {true, "off", "0 0"}, {false, "", "0"}};
for (const auto& test_case : test_case_list) {
std::string translated_value;
auto status =
translateBooleanKeyValue(translated_value, test_case.raw_value);
ASSERT_EQ(status.ok(), test_case.expected_status);
ASSERT_EQ(translated_value, test_case.expected_value);
}
}
} // namespace tables
} // namespace osquery

View File

@ -162,6 +162,7 @@ function(generateNativeTables)
"linux/elf_dynamic.table:linux"
"linux/ec2_instance_metadata.table:linux"
"linux/elf_sections.table:linux"
"linux/selinux_settings.table:linux"
"linwin/intel_me_info.table:linux,windows"
"lldpd/lldp_neighbors.table:linux,macos,freebsd"
"kernel_info.table:linux,macos,windows"

View File

@ -0,0 +1,11 @@
table_name("selinux_settings")
description("Track active SELinux settings.")
schema([
Column("scope", TEXT, "Where the key is located inside the SELinuxFS mount point.", index=True),
Column("key", TEXT, "Key or class name.", index=True),
Column("value", TEXT, "Active value."),
])
implementation("system/selinux_settings@genSELinuxSettings")
examples([
"SELECT * FROM selinux_settings WHERE key = 'enforce'",
])