diff --git a/osquery/core/windows/wmi.cpp b/osquery/core/windows/wmi.cpp index 89c821a97d0..c0fe0312f32 100644 --- a/osquery/core/windows/wmi.cpp +++ b/osquery/core/windows/wmi.cpp @@ -238,7 +238,7 @@ Status WmiResultItem::GetUnsignedLong(const std::string& name, VariantClear(&value); return Status::failure("Invalid data type returned."); } - ret = value.lVal; + ret = value.ulVal; VariantClear(&value); return Status::success(); } @@ -255,7 +255,7 @@ Status WmiResultItem::GetLongLong(const std::string& name, VariantClear(&value); return Status::failure("Invalid data type returned."); } - ret = value.lVal; + ret = value.llVal; VariantClear(&value); return Status::success(); } @@ -272,7 +272,7 @@ Status WmiResultItem::GetUnsignedLongLong(const std::string& name, VariantClear(&value); return Status::failure("Invalid data type returned."); } - ret = value.lVal; + ret = value.ullVal; VariantClear(&value); return Status::success(); } @@ -598,3 +598,4 @@ Status WmiRequest::ExecMethod(const WmiResultItem& object, } } // namespace osquery + diff --git a/osquery/events/darwin/scnetwork.cpp b/osquery/events/darwin/scnetwork.cpp index 9c2fa1b2b29..8731223464a 100644 --- a/osquery/events/darwin/scnetwork.cpp +++ b/osquery/events/darwin/scnetwork.cpp @@ -48,9 +48,14 @@ void SCNetworkEventPublisher::addTarget( const SCNetworkReachabilityRef& target) { targets_.push_back(target); + // Keep a stable, heap-allocated copy of the subscription context pointer + // alive for the lifetime of the callback registration. + auto sc_holder = new SCNetworkSubscriptionContextRef(sc); + subscription_refs_.push_back(sc_holder); + // Assign a context (the subscription context) to the target. SCNetworkReachabilityContext* context = new SCNetworkReachabilityContext(); - context->info = (void*)≻ + context->info = (void*)sc_holder; context->retain = nullptr; context->release = nullptr; contexts_.push_back(context); @@ -98,6 +103,11 @@ void SCNetworkEventPublisher::clearAll() { } contexts_.clear(); + for (auto& sc_holder : subscription_refs_) { + delete sc_holder; + } + subscription_refs_.clear(); + target_names_.clear(); target_addresses_.clear(); } @@ -183,3 +193,4 @@ Status SCNetworkEventPublisher::run() { return Status::success(); } }; + diff --git a/osquery/events/linux/inotify.cpp b/osquery/events/linux/inotify.cpp index 22ed7855843..596510fbec7 100644 --- a/osquery/events/linux/inotify.cpp +++ b/osquery/events/linux/inotify.cpp @@ -69,6 +69,8 @@ Status INotifyEventPublisher::setUp() { WriteLock lock(scratch_mutex_); scratch_ = (char*)malloc(kINotifyBufferSize); if (scratch_ == nullptr) { + ::close(inotify_handle_); + inotify_handle_ = -1; return Status(1, "Could not allocate scratch space"); } return Status::success(); @@ -486,3 +488,4 @@ bool INotifyEventPublisher::isPathMonitored(const std::string& path) const { return (path_iterator != path_descriptors_.end()); } } + diff --git a/osquery/events/windows/etw/etw_provider_config.cpp b/osquery/events/windows/etw/etw_provider_config.cpp index 1cd5b597e5a..85fd92c5a6b 100644 --- a/osquery/events/windows/etw/etw_provider_config.cpp +++ b/osquery/events/windows/etw/etw_provider_config.cpp @@ -22,7 +22,7 @@ Status EtwProviderConfig::isValid() const { return Status::failure("Empty list of Events to handle"); } - if (getPostProcessor() == nullptr) { + if (getPreProcessor() == nullptr) { return Status::failure("Type handlers were not provided"); } @@ -166,4 +166,4 @@ void EtwProviderConfig::addEventTypeToHandle(const EtwEventType& value) { eventTypes_.push_back(value); } -} // namespace osquery \ No newline at end of file +} // namespace osquery diff --git a/osquery/filesystem/linux/proc.cpp b/osquery/filesystem/linux/proc.cpp index 894956c8fcd..ec0871c1442 100644 --- a/osquery/filesystem/linux/proc.cpp +++ b/osquery/filesystem/linux/proc.cpp @@ -37,6 +37,17 @@ Status procGetNamespaceInode(ino_t& inode, return Status(1, "Failed to retrieve the inode for namespace " + path); } + // Ensure the buffer is null-terminated, since readlink() does not do this + // for us + link_destination[link_dest_length] = '\0'; + + // The link destination must be at least long enough to hold the + // namespace name, the ":[" separator and a closing "]" + if (static_cast(link_dest_length) < + namespace_name.size() + 3) { + return Status(1, "Invalid descriptor for namespace " + path); + } + // The link destination must be in the following form: namespace:[inode] if (std::strncmp(link_destination, namespace_name.data(), @@ -430,3 +441,4 @@ Expected getProcRSS(const std::string& process) { } } // namespace osquery + diff --git a/osquery/remote/requests.cpp b/osquery/remote/requests.cpp index 620b1c2cec0..75b77f2253c 100644 --- a/osquery/remote/requests.cpp +++ b/osquery/remote/requests.cpp @@ -12,6 +12,8 @@ #include +#include + namespace osquery { #define MOD_GZIP_ZLIB_WINDOWSIZE 15 @@ -27,6 +29,8 @@ std::string compressString(const std::string& data) { MOD_GZIP_ZLIB_WINDOWSIZE + 16, MOD_GZIP_ZLIB_CFACTOR, Z_DEFAULT_STRATEGY) != Z_OK) { + LOG(ERROR) << "compressString: deflateInit2 failed to initialize zlib " + "stream"; return std::string(); } @@ -51,9 +55,13 @@ std::string compressString(const std::string& data) { deflateEnd(&zs); if (ret != Z_STREAM_END) { + LOG(ERROR) << "compressString: deflate stream did not end cleanly, " + "zlib return code: " + << ret; return std::string(); } return output; } } + diff --git a/osquery/remote/uri.cpp b/osquery/remote/uri.cpp index 3d22a14f8c2..92c33a3ed3d 100644 --- a/osquery/remote/uri.cpp +++ b/osquery/remote/uri.cpp @@ -40,7 +40,10 @@ Uri::Uri(const std::string& str) : hasAuthority_(false), port_(0) { std::smatch match; if (!std::regex_match(str, match, uriRegex)) { - throw std::invalid_argument("Invalid URL"); + // Malformed URI (potentially attacker-controlled input); leave this + // Uri in a safe, empty default state instead of throwing so that a + // single bad remote-supplied URI cannot crash the process. + return; } scheme_ = submatch(match, 1); @@ -66,7 +69,18 @@ Uri::Uri(const std::string& str) : hasAuthority_(false), port_(0) { authority.second, authorityMatch, authorityRegex)) { - throw std::invalid_argument("Invalid URI authority"); + // Malformed authority section (potentially attacker-controlled + // input); reset to a safe, empty default state instead of throwing. + scheme_.clear(); + hasAuthority_ = false; + username_.clear(); + password_.clear(); + host_.clear(); + path_.clear(); + port_ = 0; + query_.clear(); + fragment_.clear(); + return; } std::string port(authorityMatch[4].first, authorityMatch[4].second); diff --git a/osquery/sql/sql.cpp b/osquery/sql/sql.cpp index f18f276e6f2..4de09ffb27c 100644 --- a/osquery/sql/sql.cpp +++ b/osquery/sql/sql.cpp @@ -141,10 +141,18 @@ Status SQLPlugin::call(const PluginRequest& request, PluginResponse& response) { return Status(1, "SQL plugin must include a request action"); } - if (request.at("action") == "query") { + const auto& action = request.at("action"); + + if (action == "query") { + if (request.count("query") == 0) { + return Status(1, "SQL plugin query action requires a query"); + } bool use_cache = (request.count("cache") && request.at("cache") == "1"); return this->query(request.at("query"), response, use_cache); - } else if (request.at("action") == "columns") { + } else if (action == "columns") { + if (request.count("query") == 0) { + return Status(1, "SQL plugin columns action requires a query"); + } TableColumns columns; auto status = this->getQueryColumns(request.at("query"), columns); // Convert columns to response @@ -155,12 +163,21 @@ Status SQLPlugin::call(const PluginRequest& request, PluginResponse& response) { {"o", INTEGER(static_cast(std::get<2>(column)))}}); } return status; - } else if (request.at("action") == "attach") { + } else if (action == "attach") { + if (request.count("table") == 0) { + return Status(1, "SQL plugin attach action requires a table"); + } // Attach a virtual table name using an optional included definition. return this->attach(request.at("table")); - } else if (request.at("action") == "detach") { + } else if (action == "detach") { + if (request.count("table") == 0) { + return Status(1, "SQL plugin detach action requires a table"); + } return this->detach(request.at("table")); - } else if (request.at("action") == "tables") { + } else if (action == "tables") { + if (request.count("query") == 0) { + return Status(1, "SQL plugin tables action requires a query"); + } std::vector tables; auto status = this->getQueryTables(request.at("query"), tables); if (status.ok()) { diff --git a/osquery/sql/sqlite_filesystem.cpp b/osquery/sql/sqlite_filesystem.cpp index f9a6a77546f..be50efc6457 100644 --- a/osquery/sql/sqlite_filesystem.cpp +++ b/osquery/sql/sqlite_filesystem.cpp @@ -178,8 +178,13 @@ static void getParentDirectory(sqlite3_context* context, sqlite3_result_null(context); return; } - char* result = reinterpret_cast(malloc(last_slash_pos)); + char* result = reinterpret_cast(malloc(last_slash_pos + 1)); + if (result == nullptr) { + sqlite3_result_error_nomem(context); + return; + } memcpy(result, path, last_slash_pos); + result[last_slash_pos] = '\0'; sqlite3_result_text(context, result, last_slash_pos, free); } @@ -210,3 +215,4 @@ void registerFilesystemExtensions(sqlite3* db) { nullptr); } } // namespace osquery + diff --git a/osquery/tables/events/linux/hardware_events.cpp b/osquery/tables/events/linux/hardware_events.cpp index d23b02fcad6..67798b299b2 100644 --- a/osquery/tables/events/linux/hardware_events.cpp +++ b/osquery/tables/events/linux/hardware_events.cpp @@ -10,6 +10,9 @@ #include #include +#include +#include + #include #include #include @@ -55,7 +58,17 @@ Status HardwareEventSubscriber::Callback(const ECRef& ec, const SCRef& sc) { struct udev_device* device = ec->device; r["type"] = ec->devtype; - if (FLAGS_hardware_disabled_types.find(r.at("type")) != std::string::npos) { + + std::vector disabled_types; + boost::split(disabled_types, + FLAGS_hardware_disabled_types, + boost::is_any_of(",")); + for (auto& disabled_type : disabled_types) { + boost::trim(disabled_type); + } + if (std::find(disabled_types.begin(), + disabled_types.end(), + r.at("type")) != disabled_types.end()) { return Status::success(); } @@ -74,9 +87,8 @@ Status HardwareEventSubscriber::Callback(const ECRef& ec, const SCRef& sc) { r["vendor"] = UdevEventPublisher::getValue(device, "ID_VENDOR_FROM_DATABASE"); r["vendor_id"] = INTEGER(UdevEventPublisher::getValue(device, "ID_VENDOR_ID")); - r["serial"] = - INTEGER(UdevEventPublisher::getValue(device, "ID_SERIAL_SHORT")); - r["revision"] = INTEGER(UdevEventPublisher::getValue(device, "ID_REVISION")); + r["serial"] = UdevEventPublisher::getValue(device, "ID_SERIAL_SHORT"); + r["revision"] = UdevEventPublisher::getValue(device, "ID_REVISION"); add(r); return Status(0); } diff --git a/osquery/tables/events/tests/windows/etw_process_events_tests.cpp b/osquery/tables/events/tests/windows/etw_process_events_tests.cpp index d1c08537d41..d704ac410c0 100644 --- a/osquery/tables/events/tests/windows/etw_process_events_tests.cpp +++ b/osquery/tables/events/tests/windows/etw_process_events_tests.cpp @@ -49,8 +49,8 @@ TEST_F(ETWProcessEventsTests, test_subscriber_exists) { ASSERT_TRUE(Registry::get().exists("event_subscriber", ETW_SUBSCRIBER_NAME)); auto plugin = Registry::get().plugin("event_subscriber", ETW_SUBSCRIBER_NAME); - auto* subscriber = - reinterpret_cast*>(&plugin); + auto subscriber = + std::dynamic_pointer_cast(plugin); EXPECT_NE(subscriber, nullptr); } @@ -58,8 +58,8 @@ TEST_F(ETWProcessEventsTests, test_publisher_exists) { ASSERT_TRUE(Registry::get().exists("event_publisher", ETW_PUBLISHER_NAME)); auto plugin = Registry::get().plugin("event_publisher", ETW_PUBLISHER_NAME); - auto* publisher = - reinterpret_cast*>(&plugin); + auto publisher = + std::dynamic_pointer_cast(plugin); EXPECT_NE(publisher, nullptr); } diff --git a/osquery/tables/events/windows/etw_process_events.cpp b/osquery/tables/events/windows/etw_process_events.cpp index c38fa343cc9..e28dbe5c0f1 100644 --- a/osquery/tables/events/windows/etw_process_events.cpp +++ b/osquery/tables/events/windows/etw_process_events.cpp @@ -76,7 +76,7 @@ Status EtwProcessEventSubscriber::eventCallback( newRow["token_elevation_status"] = INTEGER(eventPayload->TokenIsElevated); newRow["mandatory_label"] = SQL_TEXT(eventPayload->MandatoryLabelSid); newRow["process_sequence_number"] = - BIGINT(eventPayload->ParentProcessSequenceNumber); + BIGINT(eventPayload->ProcessSequenceNumber); newRow["parent_process_sequence_number"] = BIGINT(eventPayload->ParentProcessSequenceNumber); @@ -118,3 +118,4 @@ Status EtwProcessEventSubscriber::eventCallback( } } // namespace osquery + diff --git a/osquery/tables/networking/linux/arp_cache.cpp b/osquery/tables/networking/linux/arp_cache.cpp index 46ca3c11230..9662eff7d01 100644 --- a/osquery/tables/networking/linux/arp_cache.cpp +++ b/osquery/tables/networking/linux/arp_cache.cpp @@ -7,6 +7,7 @@ * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) */ +#include #include #include @@ -21,6 +22,10 @@ namespace tables { const std::string kLinuxArpTable = "/proc/net/arp"; +// ARP flag bits as defined by the kernel (see ). +static const unsigned long kAtfCom = 0x02; +static const unsigned long kAtfPerm = 0x04; + QueryData genArpCache(QueryContext& context) { QueryData results; @@ -62,8 +67,8 @@ QueryData genArpCache(QueryContext& context) { r["interface"] = fields[5]; // Note: it's also possible to detect publish entries (ATF_PUB). - if (fields[2] == "0x6") { - // The string representation of ATF_COM | ATF_PERM. + unsigned long flags = strtoul(fields[2].c_str(), nullptr, 16); + if ((flags & (kAtfCom | kAtfPerm)) == (kAtfCom | kAtfPerm)) { r["permanent"] = "1"; } else { r["permanent"] = "0"; diff --git a/osquery/tables/system/darwin/processes.cpp b/osquery/tables/system/darwin/processes.cpp index 8f44c670f82..1e90848bee9 100644 --- a/osquery/tables/system/darwin/processes.cpp +++ b/osquery/tables/system/darwin/processes.cpp @@ -368,6 +368,9 @@ bool parseProcCmdline(std::string& args, size_t len) { start = 0; while (nargs-- && nul != std::string::npos) { nul = args.find('\0', start); + if (nul == std::string::npos) { + break; + } args[nul] = ' '; start = nul + 1; } @@ -808,3 +811,4 @@ QueryData genProcessMemoryMap(QueryContext& context) { } } // namespace tables } // namespace osquery + diff --git a/osquery/tables/system/darwin/sharing_preferences.cpp b/osquery/tables/system/darwin/sharing_preferences.cpp index 5bc3ebd6422..27bacdd5540 100644 --- a/osquery/tables/system/darwin/sharing_preferences.cpp +++ b/osquery/tables/system/darwin/sharing_preferences.cpp @@ -120,10 +120,10 @@ int getPrinterSharingStatus() { int ret = cupsAdminGetServerSettings(cups, &num_settings, &settings); if (ret != 0) { value = cupsGetOption("_share_printers", num_settings, settings); - cupsFreeOptions(num_settings, settings); } else { VLOG(1) << "Unable to get CUPS server settings: " << cupsLastErrorString(); } + cupsFreeOptions(num_settings, settings); httpClose(cups); if (value != nullptr) { diff --git a/osquery/tables/system/darwin/usb_devices.cpp b/osquery/tables/system/darwin/usb_devices.cpp index ae20e437bfd..6de6cea775f 100644 --- a/osquery/tables/system/darwin/usb_devices.cpp +++ b/osquery/tables/system/darwin/usb_devices.cpp @@ -28,9 +28,12 @@ void genUSBDevice(const io_service_t& device, QueryData& results) { Row r; // Get the device details - CFMutableDictionaryRef details; - IORegistryEntryCreateCFProperties( + CFMutableDictionaryRef details = nullptr; + auto ret = IORegistryEntryCreateCFProperties( device, &details, kCFAllocatorDefault, kNilOptions); + if (ret != KERN_SUCCESS || details == nullptr) { + return; + } r["usb_address"] = getIOKitProperty(details, "USB Address"); r["usb_port"] = getIOKitProperty(details, "PortNum"); diff --git a/osquery/tables/system/linux/md_tables.cpp b/osquery/tables/system/linux/md_tables.cpp index 63faa5d2bde..6f10bf935a8 100644 --- a/osquery/tables/system/linux/md_tables.cpp +++ b/osquery/tables/system/linux/md_tables.cpp @@ -125,8 +125,8 @@ std::string MD::getPathByDevName(const std::string& name) { udev_device_get_property_value(device, "DEVNAME") ); if (boost::ends_with(devName, name)) { - if (!boost::starts_with(devPath, "/")) { - devPath = "/dev/" + devPath; + if (!boost::starts_with(devName, "/")) { + devPath = "/dev/" + devName; } else { devPath = devName; } @@ -162,10 +162,12 @@ std::string MD::getSuperblkVersion(const std::string& arrayName) { walkUdevDevices("block", [&](udev_device* const& device) { const char* devName = udev_device_get_property_value(device, "DEVNAME"); + std::string devNameStr(devName); - if (arrayName.compare(strlen(devName) - arrayName.length(), - std::string::npos, - devName) == 0) { + if (devNameStr.length() >= arrayName.length() && + devNameStr.compare(devNameStr.length() - arrayName.length(), + std::string::npos, + arrayName) == 0) { version = udev_device_get_property_value(device, "MD_METADATA"); return true; } diff --git a/osquery/tables/system/linux/memory_info.cpp b/osquery/tables/system/linux/memory_info.cpp index ecc55e416a4..8c5daa09b51 100644 --- a/osquery/tables/system/linux/memory_info.cpp +++ b/osquery/tables/system/linux/memory_info.cpp @@ -49,9 +49,11 @@ QueryData getMemoryInfo(QueryContext& context) { // Look for mapping for (const auto& singleMap : kMemInfoMap) { if (line.find(singleMap.second) == 0) { - auto const value_exp = tryTo(tokens[1], 10); - if (value_exp.isValue()) { - r[singleMap.first] = BIGINT(value_exp.get() * 1024l); + if (tokens.size() > 1) { + auto const value_exp = tryTo(tokens[1], 10); + if (value_exp.isValue()) { + r[singleMap.first] = BIGINT(value_exp.get() * 1024l); + } } break; } diff --git a/osquery/tables/system/linux/processes.cpp b/osquery/tables/system/linux/processes.cpp index 0d486d003db..33c8761f68b 100644 --- a/osquery/tables/system/linux/processes.cpp +++ b/osquery/tables/system/linux/processes.cpp @@ -179,7 +179,7 @@ void genProcessEnvironment(const std::string& pid, QueryData& results) { const char* variable = content.c_str(); // Stop at the end of nul-delimited string content. - while (*variable > 0) { + while (*variable != '\0') { auto buf = std::string(variable); size_t idx = buf.find_first_of("="); @@ -585,3 +585,4 @@ QueryData genProcessNamespaces(QueryContext& context) { } } // namespace tables } // namespace osquery + diff --git a/osquery/tables/system/windows/battery.cpp b/osquery/tables/system/windows/battery.cpp index 4110bd598dc..b0bf61c0852 100644 --- a/osquery/tables/system/windows/battery.cpp +++ b/osquery/tables/system/windows/battery.cpp @@ -1,264 +1,270 @@ -/** - * Copyright (c) 2014-present, The osquery authors - * - * This source code is licensed as defined by the LICENSE file found in the - * root directory of this source tree. - * - * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) - */ - -#include - -// clang-format off -#include -#include -#include -#include -#include -#include -#include -// clang-format on - -#include -#include -#include -#include -#include - -namespace osquery { -namespace tables { - -std::string batteryQueryInformationString( - HANDLE hBattery, - ULONG batteryTag, - BATTERY_QUERY_INFORMATION_LEVEL informationLevel) { - BATTERY_QUERY_INFORMATION bqi = {0}; - bqi.InformationLevel = informationLevel; - bqi.BatteryTag = batteryTag; - // 1025 characters should be way more than enough for the values retrieved - // from this function. It shouldn't overflow anyway due to providing size in - // the DeviceIoControl call. - std::wstring resWstring(1025, L'\0'); - DWORD resSize(0); - - if (!DeviceIoControl(hBattery, - IOCTL_BATTERY_QUERY_INFORMATION, - &bqi, - sizeof(bqi), - resWstring.data(), - static_cast(resWstring.size()) * sizeof(wchar_t), - nullptr, - nullptr)) { - if (ERROR_INVALID_FUNCTION == GetLastError()) { - LOG(INFO) << "Battery does not support information level " - << informationLevel; - } else { - LOG(ERROR) << "Failed to get battery information level " - << informationLevel << ": code " << GetLastError(); - } - return ""; - } - - return wstringToString(resWstring); -} - -QueryData genBatteryInfo(QueryContext& context) { - QueryData results; - - // Adapted from Microsoft example: - // https://learn.microsoft.com/en-us/windows/win32/power/enumerating-battery-devices - // Enumerate the batteries and ask each one for information. - HDEVINFO hdev = SetupDiGetClassDevs( - &GUID_DEVCLASS_BATTERY, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (hdev == INVALID_HANDLE_VALUE) { - LOG(ERROR) << "Failed to initialize handle for enumerating batteries: " - << GetLastError(); - return results; - } - auto const hdevGuard = - scope_guard::create([&]() { SetupDiDestroyDeviceInfoList(hdev); }); - - // Limit search to 100 batteries max - for (int idev = 0; idev < 100; idev++) { - SP_DEVICE_INTERFACE_DATA did = {0}; - did.cbSize = sizeof(did); - - if (!SetupDiEnumDeviceInterfaces( - hdev, 0, &GUID_DEVCLASS_BATTERY, idev, &did)) { - if (GetLastError() != ERROR_NO_MORE_ITEMS) { - // Only log if it's an unexpected error - LOG(ERROR) << "Failed to set up enumeration for batteries: " - << GetLastError(); - } - break; - } - - DWORD cbRequired = 0; - SetupDiGetDeviceInterfaceDetail(hdev, &did, 0, 0, &cbRequired, 0); - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - LOG(ERROR) - << "Failed to get buffer size for get device interface detail: " - << GetLastError(); - continue; - } - - PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = - (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, cbRequired); - if (pdidd == nullptr) { - LOG(ERROR) << "Failed to allocate buffer for device interface detail: " - << GetLastError(); - continue; - } - auto const pdiddGuard = scope_guard::create([&]() { LocalFree(pdidd); }); - - pdidd->cbSize = sizeof(*pdidd); - if (!SetupDiGetDeviceInterfaceDetail( - hdev, &did, pdidd, cbRequired, &cbRequired, 0)) { - LOG(ERROR) << "Failed to get battery device detail: " << GetLastError(); - continue; - } - - // Enumerated a battery. Ask it for information. - HANDLE hBattery = CreateFile(pdidd->DevicePath, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr); - if (hBattery == INVALID_HANDLE_VALUE) { - LOG(ERROR) << "Failed to open handle for battery device " - << wstringToString(pdidd->DevicePath) << ": " - << GetLastError(); - continue; - } - auto const hBatteryGuard = - scope_guard::create([&]() { CloseHandle(hBattery); }); - - // Ask the battery for its tag - needed for later queries - BATTERY_QUERY_INFORMATION bqi = {0}; - DWORD dwWait = 0; // do not wait for a battery, return immediately - DWORD dwOut; - if (!(DeviceIoControl(hBattery, - IOCTL_BATTERY_QUERY_TAG, - &dwWait, - sizeof(dwWait), - &bqi.BatteryTag, - sizeof(bqi.BatteryTag), - &dwOut, - nullptr) && - bqi.BatteryTag)) { - LOG(ERROR) << "Failed to get tag for battery device " - << wstringToString(pdidd->DevicePath) << ": " - << GetLastError(); - continue; - } - - BATTERY_INFORMATION bi = {0}; - bqi.InformationLevel = BatteryInformation; - if (DeviceIoControl(hBattery, - IOCTL_BATTERY_QUERY_INFORMATION, - &bqi, - sizeof(bqi), - &bi, - sizeof(bi), - &dwOut, - nullptr)) { - // Only non-UPS system batteries count - if (!(bi.Capabilities & BATTERY_SYSTEM_BATTERY) || - (bi.Capabilities & BATTERY_IS_SHORT_TERM)) { - continue; - } - - if (bi.Capabilities & BATTERY_CAPACITY_RELATIVE) { - LOG(WARNING) << "Battery is reporting in unknown (relative) units. " - "Values may not be in mAh, mA, and mV."; - } - - Row row; - - // Some possible values for chemistry, though we already have - // seen LiP which is not listed - // https://learn.microsoft.com/en-us/windows/win32/power/battery-information-str - row["chemistry"] = SQL_TEXT(bi.Chemistry); - - // Assume that 12 volts is the intended voltage for the - // battery in order to convert from the mWh units that - // Microsoft provides to match the mAh units that the battery - // table already uses for macOS. - const int designedVoltage = 12; - row["max_capacity"] = INTEGER(bi.FullChargedCapacity / designedVoltage); - row["designed_capacity"] = INTEGER(bi.DesignedCapacity / designedVoltage); - if (bi.CycleCount != 0) { - row["cycle_count"] = INTEGER(bi.CycleCount); - } - - // Query the battery power status. - BATTERY_WAIT_STATUS bws = {0}; - bws.BatteryTag = bqi.BatteryTag; - BATTERY_STATUS bs; - if (DeviceIoControl(hBattery, - IOCTL_BATTERY_QUERY_STATUS, - &bws, - sizeof(bws), - &bs, - sizeof(bs), - &dwOut, - nullptr)) { - // https://learn.microsoft.com/en-us/windows/win32/power/battery-wait-status-str - if (bs.PowerState & BATTERY_POWER_ON_LINE) { - row["state"] = "AC Power"; - row["charging"] = INTEGER((bs.PowerState & BATTERY_CHARGING) > 0); - } else if (bs.PowerState & BATTERY_DISCHARGING) { - row["state"] = "Battery Power"; - row["charging"] = INTEGER(0); - } - row["charged"] = INTEGER(bs.Capacity == bi.FullChargedCapacity); - row["current_capacity"] = INTEGER(bs.Capacity / designedVoltage); - row["voltage"] = INTEGER(bs.Voltage); - if (bs.Voltage > 0) { - row["amperage"] = INTEGER((1000 * static_cast(bs.Rate)) / - static_cast(bs.Voltage)); - } else { - LOG(WARNING) << "Battery table read a voltage of 0."; - } - if (bs.Capacity != bi.FullChargedCapacity && bs.Rate > 0) { - row["minutes_to_full_charge"] = - INTEGER(60 * (bi.FullChargedCapacity - bs.Capacity) / bs.Rate); - } - } - - SYSTEM_POWER_STATUS sps; - if (GetSystemPowerStatus(&sps)) { - if (sps.BatteryLifePercent != -1) { - row["percent_remaining"] = - INTEGER((unsigned int)sps.BatteryLifePercent); - } - if (sps.BatteryLifeTime != -1) { - row["minutes_until_empty"] = - INTEGER(sps.BatteryLifeTime / 60); // convert seconds to minutes - } - } else { - LOG(WARNING) << "Failed to get system power status"; - } - - row["manufacturer"] = batteryQueryInformationString( - hBattery, bqi.BatteryTag, BatteryManufactureName); - - row["serial_number"] = batteryQueryInformationString( - hBattery, bqi.BatteryTag, BatterySerialNumber); - - row["model"] = batteryQueryInformationString( - hBattery, bqi.BatteryTag, BatteryDeviceName); - - results.push_back(row); - } - } - - if (results.empty()) { - VLOG(1) << "Battery table did not find a system battery"; - } - return results; -} -} // namespace tables -} // namespace osquery \ No newline at end of file +/** + * Copyright (c) 2014-present, The osquery authors + * + * This source code is licensed as defined by the LICENSE file found in the + * root directory of this source tree. + * + * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) + */ + +#include + +// clang-format off +#include +#include +#include +#include +#include +#include +#include +// clang-format on + +#include +#include +#include +#include +#include + +namespace osquery { +namespace tables { + +std::string batteryQueryInformationString( + HANDLE hBattery, + ULONG batteryTag, + BATTERY_QUERY_INFORMATION_LEVEL informationLevel) { + BATTERY_QUERY_INFORMATION bqi = {0}; + bqi.InformationLevel = informationLevel; + bqi.BatteryTag = batteryTag; + // 1025 characters should be way more than enough for the values retrieved + // from this function. It shouldn't overflow anyway due to providing size in + // the DeviceIoControl call. + std::wstring resWstring(1025, L'\0'); + DWORD resSize(0); + + if (!DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_INFORMATION, + &bqi, + sizeof(bqi), + resWstring.data(), + static_cast(resWstring.size()) * sizeof(wchar_t), + nullptr, + nullptr)) { + if (ERROR_INVALID_FUNCTION == GetLastError()) { + LOG(INFO) << "Battery does not support information level " + << informationLevel; + } else { + LOG(ERROR) << "Failed to get battery information level " + << informationLevel << ": code " << GetLastError(); + } + return ""; + } + + return wstringToString(resWstring); +} + +QueryData genBatteryInfo(QueryContext& context) { + QueryData results; + + // Adapted from Microsoft example: + // https://learn.microsoft.com/en-us/windows/win32/power/enumerating-battery-devices + // Enumerate the batteries and ask each one for information. + HDEVINFO hdev = SetupDiGetClassDevs( + &GUID_DEVCLASS_BATTERY, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (hdev == INVALID_HANDLE_VALUE) { + LOG(ERROR) << "Failed to initialize handle for enumerating batteries: " + << GetLastError(); + return results; + } + auto const hdevGuard = + scope_guard::create([&]() { SetupDiDestroyDeviceInfoList(hdev); }); + + // Limit search to 100 batteries max + for (int idev = 0; idev < 100; idev++) { + SP_DEVICE_INTERFACE_DATA did = {0}; + did.cbSize = sizeof(did); + + if (!SetupDiEnumDeviceInterfaces( + hdev, 0, &GUID_DEVCLASS_BATTERY, idev, &did)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + // Only log if it's an unexpected error + LOG(ERROR) << "Failed to set up enumeration for batteries: " + << GetLastError(); + } + break; + } + + DWORD cbRequired = 0; + SetupDiGetDeviceInterfaceDetail(hdev, &did, 0, 0, &cbRequired, 0); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + LOG(ERROR) + << "Failed to get buffer size for get device interface detail: " + << GetLastError(); + continue; + } + + PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = + (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, cbRequired); + if (pdidd == nullptr) { + LOG(ERROR) << "Failed to allocate buffer for device interface detail: " + << GetLastError(); + continue; + } + auto const pdiddGuard = scope_guard::create([&]() { LocalFree(pdidd); }); + + pdidd->cbSize = sizeof(*pdidd); + if (!SetupDiGetDeviceInterfaceDetail( + hdev, &did, pdidd, cbRequired, &cbRequired, 0)) { + LOG(ERROR) << "Failed to get battery device detail: " << GetLastError(); + continue; + } + + // Enumerated a battery. Ask it for information. + HANDLE hBattery = CreateFile(pdidd->DevicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (hBattery == INVALID_HANDLE_VALUE) { + LOG(ERROR) << "Failed to open handle for battery device " + << wstringToString(pdidd->DevicePath) << ": " + << GetLastError(); + continue; + } + auto const hBatteryGuard = + scope_guard::create([&]() { CloseHandle(hBattery); }); + + // Ask the battery for its tag - needed for later queries + BATTERY_QUERY_INFORMATION bqi = {0}; + DWORD dwWait = 0; // do not wait for a battery, return immediately + DWORD dwOut; + if (!(DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_TAG, + &dwWait, + sizeof(dwWait), + &bqi.BatteryTag, + sizeof(bqi.BatteryTag), + &dwOut, + nullptr) && + bqi.BatteryTag)) { + LOG(ERROR) << "Failed to get tag for battery device " + << wstringToString(pdidd->DevicePath) << ": " + << GetLastError(); + continue; + } + + BATTERY_INFORMATION bi = {0}; + bqi.InformationLevel = BatteryInformation; + if (DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_INFORMATION, + &bqi, + sizeof(bqi), + &bi, + sizeof(bi), + &dwOut, + nullptr)) { + // Only non-UPS system batteries count + if (!(bi.Capabilities & BATTERY_SYSTEM_BATTERY) || + (bi.Capabilities & BATTERY_IS_SHORT_TERM)) { + continue; + } + + if (bi.Capabilities & BATTERY_CAPACITY_RELATIVE) { + LOG(WARNING) << "Battery is reporting in unknown (relative) units. " + "Values may not be in mAh, mA, and mV."; + } + + Row row; + + // Some possible values for chemistry, though we already have + // seen LiP which is not listed + // https://learn.microsoft.com/en-us/windows/win32/power/battery-information-str + row["chemistry"] = SQL_TEXT(bi.Chemistry); + + // Assume that 12 volts is the intended voltage for the + // battery in order to convert from the mWh units that + // Microsoft provides to match the mAh units that the battery + // table already uses for macOS. + const int designedVoltage = 12; + row["max_capacity"] = INTEGER(bi.FullChargedCapacity / designedVoltage); + row["designed_capacity"] = INTEGER(bi.DesignedCapacity / designedVoltage); + if (bi.CycleCount != 0) { + row["cycle_count"] = INTEGER(bi.CycleCount); + } + + // Query the battery power status. + BATTERY_WAIT_STATUS bws = {0}; + bws.BatteryTag = bqi.BatteryTag; + BATTERY_STATUS bs; + if (DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_STATUS, + &bws, + sizeof(bws), + &bs, + sizeof(bs), + &dwOut, + nullptr)) { + // https://learn.microsoft.com/en-us/windows/win32/power/battery-wait-status-str + bool isDischarging = false; + if (bs.PowerState & BATTERY_POWER_ON_LINE) { + row["state"] = "AC Power"; + row["charging"] = INTEGER((bs.PowerState & BATTERY_CHARGING) > 0); + } else if (bs.PowerState & BATTERY_DISCHARGING) { + row["state"] = "Battery Power"; + row["charging"] = INTEGER(0); + isDischarging = true; + } + row["charged"] = INTEGER(bs.Capacity == bi.FullChargedCapacity); + row["current_capacity"] = INTEGER(bs.Capacity / designedVoltage); + row["voltage"] = INTEGER(bs.Voltage); + if (bs.Voltage > 0) { + int amperage = (1000 * static_cast(bs.Rate)) / + static_cast(bs.Voltage); + if (isDischarging) { + amperage = -amperage; + } + row["amperage"] = INTEGER(amperage); + } else { + LOG(WARNING) << "Battery table read a voltage of 0."; + } + if (bs.Capacity != bi.FullChargedCapacity && bs.Rate > 0) { + row["minutes_to_full_charge"] = + INTEGER(60 * (bi.FullChargedCapacity - bs.Capacity) / bs.Rate); + } + } + + SYSTEM_POWER_STATUS sps; + if (GetSystemPowerStatus(&sps)) { + if (sps.BatteryLifePercent != -1) { + row["percent_remaining"] = + INTEGER((unsigned int)sps.BatteryLifePercent); + } + if (sps.BatteryLifeTime != -1) { + row["minutes_until_empty"] = + INTEGER(sps.BatteryLifeTime / 60); // convert seconds to minutes + } + } else { + LOG(WARNING) << "Failed to get system power status"; + } + + row["manufacturer"] = batteryQueryInformationString( + hBattery, bqi.BatteryTag, BatteryManufactureName); + + row["serial_number"] = batteryQueryInformationString( + hBattery, bqi.BatteryTag, BatterySerialNumber); + + row["model"] = batteryQueryInformationString( + hBattery, bqi.BatteryTag, BatteryDeviceName); + + results.push_back(row); + } + } + + if (results.empty()) { + VLOG(1) << "Battery table did not find a system battery"; + } + return results; +} +} // namespace tables +} // namespace osquery diff --git a/osquery/tables/system/windows/dns_cache.cpp b/osquery/tables/system/windows/dns_cache.cpp index 311ed6b42a3..2b24742c96a 100644 --- a/osquery/tables/system/windows/dns_cache.cpp +++ b/osquery/tables/system/windows/dns_cache.cpp @@ -136,8 +136,20 @@ QueryData genDnsCache(QueryContext& context) { PDNSCACHEENTRY pEntry = (PDNSCACHEENTRY)malloc(sizeof(DNSCACHEENTRY)); HINSTANCE hLib = LoadLibraryExW(L"DNSAPI.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hLib == NULL) { + LOG(WARNING) << "Failed to load DNSAPI.dll"; + free(pEntry); + return results; + } + DNS_GET_CACHE_DATA_TABLE DnsGetCacheDataTable = (DNS_GET_CACHE_DATA_TABLE)GetProcAddress(hLib, "DnsGetCacheDataTable"); + if (DnsGetCacheDataTable == nullptr) { + LOG(WARNING) << "Failed to resolve DnsGetCacheDataTable"; + free(pEntry); + FreeLibrary(hLib); + return results; + } int stat = DnsGetCacheDataTable(pEntry); pEntry = pEntry->pNext; @@ -152,8 +164,10 @@ QueryData genDnsCache(QueryContext& context) { pEntry = pEntry->pNext; } free(pEntry); + FreeLibrary(hLib); return results; } } // namespace tables } // namespace osquery + diff --git a/osquery/tables/system/windows/logical_drives.cpp b/osquery/tables/system/windows/logical_drives.cpp index 7c545745aa4..b8c6a6e501d 100644 --- a/osquery/tables/system/windows/logical_drives.cpp +++ b/osquery/tables/system/windows/logical_drives.cpp @@ -28,7 +28,9 @@ QueryData genLogicalDrives(QueryContext& context) { for (const auto& bootConfiguration : bootConfigurations) { std::string bootDirectory; bootConfiguration.GetString("BootDirectory", bootDirectory); - bootDeviceIds.insert(bootDirectory.at(0)); + if (!bootDirectory.empty()) { + bootDeviceIds.insert(bootDirectory.at(0)); + } } } else { LOG(WARNING) << "Failed to query BootConfiguration via WMI"; @@ -67,7 +69,8 @@ QueryData genLogicalDrives(QueryContext& context) { // return "Unknown". That behavior is preserved here. r["type"] = "Unknown"; r["device_id"] = deviceId; - r["boot_partition"] = INTEGER(bootDeviceIds.count(deviceId.at(0))); + r["boot_partition"] = + INTEGER(!deviceId.empty() && bootDeviceIds.count(deviceId.at(0))); results.push_back(std::move(r)); } @@ -75,3 +78,4 @@ QueryData genLogicalDrives(QueryContext& context) { } } // namespace tables } // namespace osquery + diff --git a/osquery/utils/json/json.cpp b/osquery/utils/json/json.cpp index ce31897011b..674277d5ef3 100644 --- a/osquery/utils/json/json.cpp +++ b/osquery/utils/json/json.cpp @@ -151,7 +151,7 @@ void JSON::add(const std::string& key, const char* value, rj::Value& obj) { } obj.AddMember(rj::Value(rj::StringRef(key), doc_.GetAllocator()).Move(), - rj::Value(value, strlen(value)).Move(), + rj::Value(value, strlen(value), doc_.GetAllocator()).Move(), doc_.GetAllocator()); } void JSON::add(const std::string& key, const char* value) { diff --git a/osquery/utils/system/posix/system.cpp b/osquery/utils/system/posix/system.cpp index 7ef40da6bc1..2e2e350e172 100644 --- a/osquery/utils/system/posix/system.cpp +++ b/osquery/utils/system/posix/system.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -24,7 +25,11 @@ std::string getHostname() { std::size_t max_size = 256; std::vector hostname(max_size, 0); - gethostname(hostname.data(), max_size); + if (gethostname(hostname.data(), max_size) != 0) { + LOG(ERROR) << "Failed to retrieve hostname with error: " + << strerror(errno); + return std::string(""); + } std::string hostname_string(hostname.data()); boost::algorithm::trim(hostname_string); diff --git a/osquery/utils/windows/lzxpress.cpp b/osquery/utils/windows/lzxpress.cpp index e83bf8ff6e6..c12a7cc5fbf 100644 --- a/osquery/utils/windows/lzxpress.cpp +++ b/osquery/utils/windows/lzxpress.cpp @@ -37,6 +37,12 @@ typedef NTSTATUS(WINAPI* RTLGETCOMPRESSIONWORKSPACESIZE)( ExpectedDecompressData decompressLZxpress(std::vector& prefetch_data, unsigned long size) { + if (prefetch_data.size() < 8) { + return ExpectedDecompressData::failure( + ConversionError::InvalidArgument, + "Prefetch data too small to decompress"); + } + RTLGETCOMPRESSIONWORKSPACESIZE RtlGetCompressionWorkSpaceSize; RTLDECOMPRESSBUFFEREX RtlDecompressBufferEx; diff --git a/osquery/worker/ipc/linux/linux_table_container_ipc.cpp b/osquery/worker/ipc/linux/linux_table_container_ipc.cpp index 2d1b269fb58..63825114ca2 100644 --- a/osquery/worker/ipc/linux/linux_table_container_ipc.cpp +++ b/osquery/worker/ipc/linux/linux_table_container_ipc.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,7 @@ const std::string kMountNamespace = "/ns/mnt"; const int kMaxNamespaceIdLinkChars = 16; PlatformProcess current_running_process; +std::mutex current_running_process_mutex; Status extractMountNamespaceId(const std::string& mount_namespace_path, std::string& mount_namespace_id) { @@ -150,7 +152,7 @@ Status LinuxTableContainerIPC::connectToContainer( std::string original_mnt_path = kProc + "/" + std::to_string(current_pid) + kMountNamespace; - if (original_mnt_fd_ > 0) { + if (original_mnt_fd_ >= 0) { close(original_mnt_fd_); } @@ -196,7 +198,10 @@ Status LinuxTableContainerIPC::connectToContainer( return Status::failure("Failed to start container worker to table " + table_name); } else { - current_running_process = PlatformProcess(pid); + { + std::lock_guard lock(current_running_process_mutex); + current_running_process = PlatformProcess(pid); + } ipc_.connectToChild(table_name, std::move(channel_ticket), pid); } } @@ -204,7 +209,11 @@ Status LinuxTableContainerIPC::connectToContainer( return Status::success(); } void LinuxTableContainerIPC::stopContainerWorker() { - PlatformProcess child_process(std::move(current_running_process)); + PlatformProcess child_process(kInvalidPid); + { + std::lock_guard lock(current_running_process_mutex); + child_process = PlatformProcess(std::move(current_running_process)); + } if (child_process.pid() == kInvalidPid) { return; @@ -348,6 +357,13 @@ Status LinuxTableContainerIPC::handleJob(QueryContext& context) { int result = static_cast(syscall(SYS_setns, original_mnt_fd_, 0)); if (result < 0) { + // We failed to restore the original mount namespace. Since this + // worker process may be reused for subsequent queries while + // keep_process_open_ is still true, force it to be treated as + // unusable so the caller will not keep it around in a stale + // namespace. + keep_process_open_ = false; + auto status = Status::failure( "Failed to restore the original mount namespace, due to error: " + std::to_string(errno)); @@ -374,6 +390,10 @@ void LinuxTableContainerIPC::executeQueryJobs() { } break; } + + if (!keep_process_open_) { + break; + } } } else { JSONMessageType message_type; diff --git a/tools/codegen/genapi.py b/tools/codegen/genapi.py index a07b271d79d..b26e5555b43 100755 --- a/tools/codegen/genapi.py +++ b/tools/codegen/genapi.py @@ -136,23 +136,25 @@ def gen_diff(api_old_path, api_new_path): columns_added = [] columns_removed = [] for name, table in new_tables.items(): + category_name = name.split(":", 1)[0] if name not in old_tables: tables_added.append(name) continue for column in table["columns"]: old_columns = [c["name"] for c in old_tables[name]["columns"]] if column["name"] not in old_columns: - columns_added.append("%s:%s:%s:%s" % (category["name"], + columns_added.append("%s:%s:%s:%s" % (category_name, table["name"], column["name"], column["type"])) for name, table in old_tables.items(): + category_name = name.split(":", 1)[0] if name not in new_tables: tables_removed.append(name) continue for column in table["columns"]: new_columns = [c["name"] for c in new_tables[name]["columns"]] if column["name"] not in new_columns: - columns_removed.append("%s:%s:%s:%s" % (category["name"], + columns_removed.append("%s:%s:%s:%s" % (category_name, table["name"], column["name"], column["type"])) # Sort then pretty print (md) the changes.