Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update linux block_device and disk_encryption source data to simple sysfs implementation #8182

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
168 changes: 19 additions & 149 deletions osquery/tables/system/linux/block_devices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,169 +7,39 @@
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/

#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include <blkid/blkid.h>
#include <libudev.h>
#include <unistd.h>

#include <osquery/core/core.h>
#include <osquery/core/tables.h>
#include <osquery/filesystem/filesystem.h>
#include <osquery/logger/logger.h>

extern "C" {
#include <lvm2app.h>
#include <sys/sysmacros.h>
}
#include <osquery/utils/linux/block_device_enumeration.h>

namespace osquery {
namespace tables {

void populatePVChildren(lvm_t lvm,
const std::string& devname,
const std::string& pvid,
std::map<std::string, std::string>& lvm_lv2pv) {
const auto pvid_copy = boost::erase_all_copy(pvid, "-");
const char* vg_name = lvm_vgname_from_pvid(lvm, pvid_copy.c_str());
if (vg_name == nullptr) {
return;
}

vg_t vg = lvm_vg_open(lvm, vg_name, "r", 0);
if (vg == nullptr) {
return;
}
struct dm_list* lvs = lvm_vg_list_lvs(vg);
if (lvs != nullptr) {
lv_list_t* lv = nullptr;
dm_list_iterate_items(lv, lvs) {
struct lvm_property_value kernel_major = lvm_lv_get_property(
lv->lv, "lv_kernel_major"),
kernel_minor = lvm_lv_get_property(
lv->lv, "lv_kernel_minor");
const uint64_t major = kernel_major.value.integer,
minor = kernel_minor.value.integer;
dev_t devno = makedev(major, minor);
char* const child_devname = blkid_devno_to_devname(devno);
if (child_devname != nullptr) {
lvm_lv2pv[child_devname] = devname;
}
free(child_devname);
}
}
lvm_vg_close(vg);
}

static void getBlockDevice(struct udev_device* dev,
QueryData& results,
std::map<std::string, std::string>& lvm_lv2pv) {
Row r;
const char *name = udev_device_get_devnode(dev);
if (name == nullptr) {
// Cannot get devnode information from UDEV.
return;
}

// The device name may be blank but will have a string value.
r["name"] = name;

struct udev_device *subdev =
udev_device_get_parent_with_subsystem_devtype(dev, "block", nullptr);
if (subdev != nullptr) {
r["parent"] = udev_device_get_devnode(subdev);
} else if (lvm_lv2pv.count(name)) {
r["parent"] = lvm_lv2pv[name];
}

const char *size = udev_device_get_sysattr_value(dev, "size");
if (size != nullptr) {
r["size"] = size;
}

const char* block_size =
udev_device_get_sysattr_value(dev, "queue/logical_block_size");
if (block_size != nullptr) {
r["block_size"] = block_size;
}

subdev = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", nullptr);
if (subdev != nullptr) {
const char *model = udev_device_get_sysattr_value(subdev, "model");
std::string model_string = std::string(model);
boost::algorithm::trim(model_string);
r["model"] = model_string;

model = udev_device_get_sysattr_value(subdev, "vendor");
model_string = std::string(model);
boost::algorithm::trim(model_string);
r["vendor"] = model_string;
}

blkid_probe pr = blkid_new_probe_from_filename(name);
if (pr != nullptr) {
blkid_probe_enable_superblocks(pr, 1);
blkid_probe_set_superblocks_flags(
pr, BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | BLKID_SUBLKS_TYPE);

if (!blkid_do_safeprobe(pr)) {
const char *blk_value = nullptr;
if (!blkid_probe_lookup_value(pr, "TYPE", &blk_value, nullptr)) {
r["type"] = blk_value;
}
if (!blkid_probe_lookup_value(pr, "UUID", &blk_value, nullptr)) {
r["uuid"] = blk_value;
}
if (!blkid_probe_lookup_value(pr, "LABEL", &blk_value, nullptr)) {
r["label"] = blk_value;
}
if (boost::algorithm::starts_with(r["type"], "LVM")) {
lvm_t lvm = lvm_init(nullptr);
if (lvm != nullptr) {
populatePVChildren(lvm, name, r["uuid"], lvm_lv2pv);
lvm_quit(lvm);
}
}
}
blkid_free_probe(pr);
}

results.push_back(r);
}

QueryData genBlockDevs(QueryContext &context) {
QueryData genBlockDevs(QueryContext& context) {
if (getuid() || geteuid()) {
VLOG(1) << "Not running as root, LVM and other column data not available";
}

QueryData results;
auto query_context = context.constraints.find("name")->second.getAll(EQUALS);
auto block_devices = enumerateBlockDevices(query_context, false);

struct udev *udev = udev_new();
if (udev == nullptr) {
return {};
}

struct udev_enumerate* enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "block");
udev_enumerate_scan_devices(enumerate);
for (const auto& block_device : block_devices) {
Row r;
r["name"] = block_device.name;
r["parent"] = block_device.parent;
r["vendor"] = block_device.vendor;
r["model"] = block_device.model;
r["serial"] = block_device.serial;
r["size"] = block_device.size;
r["block_size"] = block_device.block_size;
r["uuid"] = block_device.uuid;
r["type"] = block_device.type;
r["label"] = block_device.label;

std::map<std::string, std::string> lvm_lv2pv;
struct udev_list_entry *devices, *dev_list_entry;
devices = udev_enumerate_get_list_entry(enumerate);
udev_list_entry_foreach(dev_list_entry, devices) {
const char* path = udev_list_entry_get_name(dev_list_entry);
struct udev_device* dev = udev_device_new_from_syspath(udev, path);
if (path != nullptr && dev != nullptr) {
getBlockDevice(dev, results, lvm_lv2pv);
}
udev_device_unref(dev);
results.push_back(r);
}

udev_enumerate_unref(enumerate);
udev_unref(udev);

return results;
}
}
}
} // namespace tables
} // namespace osquery
107 changes: 40 additions & 67 deletions osquery/tables/system/linux/disk_encryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/

#include <unistd.h>

#include <vector>

#include <osquery/core/core.h>
#include <osquery/core/tables.h>
#include <osquery/logger/logger.h>
#include <osquery/sql/sql.h>
#include <osquery/utils/conversions/join.h>
#include <osquery/utils/linux/block_device_enumeration.h>

extern "C" {
#include <libcryptsetup.h>
Expand All @@ -29,19 +25,15 @@ const std::string kEncryptionStatusNotEncrypted = "not encrypted";

namespace osquery {
namespace tables {
void genFDEStatusForBlockDevice(const Row& block_device,
std::map<std::string, Row>& block_devices,
std::map<std::string, Row>& encrypted_rows) {
const auto name = block_device.at("name");
const auto parent_name =
(block_device.count("parent") > 0 ? block_device.at("parent") : "");

void genFDEStatusForBlockDevice(const BlockDevice& block_device,
std::set<BlockDevice>& block_devices,
std::map<std::string, Row>& encryption_status) {
Row r;
r["name"] = name;
r["uuid"] = (block_device.count("uuid") > 0) ? block_device.at("uuid") : "";
r["name"] = block_device.name;
r["uuid"] = block_device.uuid;

struct crypt_device* cd = nullptr;
auto ci = crypt_status(cd, name.c_str());
auto ci = crypt_status(cd, r["name"].c_str());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: for performance, prefer using the value you have copied from (block_device.name) instead of fetching it back from the std::map.


switch (ci) {
case CRYPT_ACTIVE:
Expand All @@ -50,15 +42,16 @@ void genFDEStatusForBlockDevice(const Row& block_device,
r["encryption_status"] = kEncryptionStatusEncrypted;
r["type"] = "";

auto crypt_init = crypt_init_by_name_and_header(&cd, name.c_str(), nullptr);
auto crypt_init =
crypt_init_by_name_and_header(&cd, r["name"].c_str(), nullptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

if (crypt_init < 0) {
VLOG(1) << "Unable to initialize crypt device for " << name;
VLOG(1) << "Unable to initialize crypt device for " << r["name"];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

break;
}

struct crypt_active_device cad;
if (crypt_get_active_device(cd, name.c_str(), &cad) < 0) {
VLOG(1) << "Unable to get active device for " << name;
if (crypt_get_active_device(cd, r["name"].c_str(), &cad) < 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

VLOG(1) << "Unable to get active device for " << r["name"];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

break;
}

Expand All @@ -83,39 +76,33 @@ void genFDEStatusForBlockDevice(const Row& block_device,
r["type"] = osquery::join(items, "-");
break;
}
// If there's no good crypt status, use the parent device's crypt status.
default:
// If there is no parent, we are likely at the root of the block device
// tree. Since no good crypt status has been found, we set the empty status
// and exit. All children of this block device will inherit this status if
// they aren't encrypted.
if (parent_name.empty()) {
r["encryption_status"] = kEncryptionStatusNotEncrypted;
r["encrypted"] = "0";
r["type"] = "";
break;
}
r["encryption_status"] = kEncryptionStatusNotEncrypted;
r["encrypted"] = "0";
r["type"] = "";

// If there is a parent, let's generate and use its crypt status for this
// device.
if (!encrypted_rows.count(parent_name)) {
genFDEStatusForBlockDevice(
block_devices[parent_name], block_devices, encrypted_rows);
// Set crypt status from parent block device.
if (!block_device.parent.empty()) {
// Since `genFDEStatusForBlockDevice` is recursive, ensure no duplicates.
if (!encryption_status.count(block_device.parent)) {
if (auto parent = block_devices.find(block_device.parent);
parent != block_devices.end()) {
genFDEStatusForBlockDevice(*parent, block_devices, encryption_status);
}
}

auto parent_row = encryption_status[block_device.parent];
r["encryption_status"] = parent_row["encryption_status"];
r["encrypted"] = parent_row["encrypted"];
r["type"] = parent_row["type"];
}

// The recursive calls return back, and each child device takes the
// encryption values of their parent.
auto parent_row = encrypted_rows[parent_name];
r["encryption_status"] = parent_row["encryption_status"];
r["encrypted"] = parent_row["encrypted"];
r["type"] = parent_row["type"];
}

encrypted_rows[name] = r;

if (cd != nullptr) {
crypt_free(cd);
}

encryption_status[r["name"]] = r;
}

QueryData genFDEStatus(QueryContext& context) {
Expand All @@ -126,32 +113,18 @@ QueryData genFDEStatus(QueryContext& context) {
return results;
}

// When a block device doesn't have sufficient crypt status, it needs to be
// able to inherit the crypt status of its parent.
// To do this, we utilize `block_devices` and `encrypted_rows` to cache block
// devices at two different points. The first is when it's queried, and the
// second is after setting crypt status. This helps us avoid O(N^2) issues.
// We can also skip sorting nodes by using recursion.
std::map<std::string, Row> block_devices;
std::map<std::string, Row> encrypted_rows;

// Ultimately we want to have proper query context here. There are underlying
// issues with udev child->parent relationship on LVM volumes. See #8152.
auto data = SQL::selectAllFrom("block_devices");
for (const auto& row : data) {
if (row.count("name") > 0) {
block_devices[row.at("name")] = row;
}
}
std::map<std::string, Row> encryption_status;
auto query_context = context.constraints.find("name")->second.getAll(EQUALS);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just access the value with [] here, since if whatever reason the value doesn't exist, it's undefined behavior when dereferencing the end iterator. Even better would actually be checking that the element actually exists, but we have using [] as a bit of a pattern it seems, because either we assume it's always there, and/or because std::map specifically will create an empty element if that key doesn't exist.
It's not ideal to rely on it though.

auto block_devices = enumerateBlockDevices(query_context, true);

// Generate and add an encryption row result for each queried block device.
for (const auto& pair : block_devices) {
if (!encrypted_rows.count(pair.first)) {
genFDEStatusForBlockDevice(pair.second, block_devices, encrypted_rows);
for (const auto& block_device : block_devices) {
// Since `genFDEStatusForBlockDevice` is recursive, ensure no duplicates.
if (!encryption_status.count(block_device.name)) {
genFDEStatusForBlockDevice(
block_device, block_devices, encryption_status);
}

// Copy encrypted rows back to results.
results.push_back(encrypted_rows[pair.first]);
results.push_back(encryption_status[block_device.name]);
}

return results;
Expand Down
4 changes: 4 additions & 0 deletions osquery/utils/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ endfunction()
function(generateOsqueryUtilsLinux)
set(public_header_files
dpkg/idpkgquery.h

block_device/block_device_enumeration.h
)

add_osquery_library(osquery_utils_linux EXCLUDE_FROM_ALL
Expand All @@ -21,6 +23,8 @@ function(generateOsqueryUtilsLinux)

dpkg/dpkgquery.h
dpkg/dpkgquery.cpp

block_device/block_device_enumeration.cpp
)

target_link_libraries(osquery_utils_linux PUBLIC
Expand Down