Skip to content

fix(adhoc-sweep-fixes): CU-86akhf8u2 34 review findings across 27 files - #72

Draft
flamingo[bot] wants to merge 27 commits into
masterfrom
ai-fix/adhoc-sweep-fixes-439188f8-39233833
Draft

flamingo[bot] wants to merge 27 commits into
masterfrom
ai-fix/adhoc-sweep-fixes-439188f8-39233833

Conversation

@flamingo

@flamingo flamingo Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Closes 34 review findings across 27 files.

Draft — this is a starting point, not a finished change. The fix required judgment, so read it before trusting it.

# Fix confidence Finding Location
1 🔴 55 low — review closely setns() switches namespace without restoring on early-return error paths, leaving worker in wrong mount namespace osquery/worker/ipc/linux/linux_table_container_ipc.cpp:345
2 🟢 95 high original_mnt_fd_ leaked/overwritten check uses wrong sentinel value osquery/worker/ipc/linux/linux_table_container_ipc.cpp:153
3 🔴 40 low — review closely stopContainerWorker moves out of the file-scope global current_running_process without any synchronization osquery/worker/ipc/linux/linux_table_container_ipc.cpp:206
4 🟢 90 high dns_cache.cpp does not check for hLib==NULL or DnsGetCacheDataTable==nullptr before calling through the function pointer osquery/tables/system/windows/dns_cache.cpp:136
5 🟡 85 medium dns_cache.cpp never frees the loaded DNSAPI.dll library handle osquery/tables/system/windows/dns_cache.cpp:133
6 🔴 55 low — review closely scnetwork.cpp Callback stores a pointer to a local stack variable (dangling reference) via SCNetworkReachabilityContext osquery/events/darwin/scnetwork.cpp:46
7 🟢 90 high getParentDirectory leaks/mismatches allocator: malloc() paired with SQLite's free destructor is fine, but result buffer is not null-terminated osquery/sql/sqlite_filesystem.cpp:181
8 🟢 92 high readlink result in procGetNamespaceInode is not null-terminated before strncmp use osquery/filesystem/linux/proc.cpp:34
9 🟢 95 high GetLongLong and GetUnsignedLongLong read the wrong VARIANT union member (lVal instead of the 64-bit fields) osquery/core/windows/wmi.cpp:246
10 🟢 95 high GetUnsignedLong reads value.lVal instead of the unsigned value.ulVal member osquery/core/windows/wmi.cpp:229
11 🟢 92 high MD::getPathByDevName builds devPath from an empty string, so the '/' prefix check is always false osquery/tables/system/linux/md_tables.cpp:120
12 🟡 80 medium MD::getSuperblkVersion computes suffix comparison with reversed substr args, likely always false osquery/tables/system/linux/md_tables.cpp:163
13 🟢 95 high logical_drives.cpp indexes into possibly-empty bootDirectory string without bounds check osquery/tables/system/windows/logical_drives.cpp:28
14 🟢 90 high logical_drives.cpp indexes into possibly-empty deviceId string without bounds check osquery/tables/system/windows/logical_drives.cpp:50
15 🟡 85 medium INTEGER() applied to non-numeric udev serial/vendor string fields will silently mis-render values osquery/tables/events/linux/hardware_events.cpp:77
16 🟡 60 medium find() on std::string used as a substring/exact-match filter is misapplied for hardware_disabled_types osquery/tables/events/linux/hardware_events.cpp:57
17 🟢 98 high JSON::add(const std::string&, const char*, rj::Value&) does not copy the string data, contrary to its own doc comment and sibling overloads osquery/utils/json/json.cpp:146
18 🟢 95 high EtwProviderConfig::isValid() checks getPostProcessor() twice, never validating providerPreProcess_ osquery/events/windows/etw/etw_provider_config.cpp:15
19 🟡 65 medium process_sequence_number field is populated with ParentProcessSequenceNumber instead of the process's own sequence number osquery/tables/events/windows/etw_process_events.cpp:77
20 🟢 90 high genapi.py gen_diff() references loop variable 'table' and 'category' after their enclosing for-loops have ended tools/codegen/genapi.py:148
21 🟡 80 medium Dead/unreachable code: reinterpret_cast of local shared_ptr always yields a non-null pointer, EXPECT_NE(subscriber, nullptr) never fails osquery/tables/events/tests/windows/etw_process_events_tests.cpp:48
22 🟢 92 high genUSBDevice does not check the return value of IORegistryEntryCreateCFProperties osquery/tables/system/darwin/usb_devices.cpp:30
23 🟢 95 high genMemoryMap accesses tokens[1] without checking split result size osquery/tables/system/linux/memory_info.cpp:45
24 🟢 95 high genProcessEnvironment loop condition uses '> 0' on a signed char, mishandling non-ASCII environment values osquery/tables/system/linux/processes.cpp:179
25 🟢 95 high INotifyEventPublisher::setUp leaks inotify_handle_ on scratch allocation failure osquery/events/linux/inotify.cpp:58
26 🟡 80 medium compressString silently returns empty string on zlib failure without logging osquery/remote/requests.cpp:24
27 🟢 92 high parseProcCmdline uses args[nul] after find may return npos on the last iteration osquery/tables/system/darwin/processes.cpp:366
28 🟡 75 medium gethostname return value not checked in getHostname() osquery/utils/system/posix/system.cpp:24
29 🔴 45 low — review closely Uri constructor throws on user/attacker-controlled input instead of returning an error status osquery/remote/uri.cpp:41
30 🟡 75 medium Battery amperage sign is unconditionally positive even when discharging, contradicting macOS battery table semantics osquery/tables/system/windows/battery.cpp:218
31 🟢 92 high SQLPlugin::call uses request.at("action") repeatedly without checking key existence for sub-keys like "query"/"table", risking std::out_of_range exception osquery/sql/sql.cpp:144
32 🟡 85 medium getPrinterSharingStatus leaks cups_option_t settings array when cupsAdminGetServerSettings fails osquery/tables/system/darwin/sharing_preferences.cpp:120
33 🔴 40 low — review closely decompressLZxpress reads prefetch_data[8] onward without validating prefetch_data.size() >= 8 osquery/utils/windows/lzxpress.cpp:75
34 🟡 85 medium hardcoded literal used for ATF_COM|ATF_PERM flag comparison instead of named constant osquery/tables/networking/linux/arp_cache.cpp:64

What changed — and what was deliberately left — is explained per finding as inline review comments on the lines each finding touched.


Run: https://product-hub.flamingo.so/admin/code-review
Run id: 39233833-f1d6-416e-93a7-71af15e3e968

Merging this PR is recorded as acceptance of the rule that produced it;
closing it unmerged is recorded as rejection. Both feed rule health, so
closing a wrong suggestion is useful rather than merely tidy.

ClickUp task: CU-86akhf8u2 Osquery review findings sweep (15 PRs)

flamingo Bot added 27 commits September 21, 2026 05:10

@flamingo flamingo Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 What this fix changed, finding by finding

34 finding(s) fixed in this draft — 34 explained inline on the diff; 5 low-confidence hunk(s) need close review before merging.

Comment on lines 357 to 369
int result = static_cast<int>(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));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔴 setns() switches namespace without restoring on early-return error paths, leaving worker in wrong mount namespace

In LinuxTableContainerIPC::handleJob() (worker-side, linux_table_container_ipc.cpp), when the final restore setns(original_mnt_fd_, ...) fails inside the keep_process_open_ block, the code now sets keep_process_open_ = false before returning the failure Status. This flag is also checked inside executeQueryJobs()'s loop (added a if (!keep_process_open_) break; after processing a message), so that once a job leaves the worker in a bad/unrestored namespace state, the worker process will exit its keep-open loop and terminate (via the existing std::_Exit at the end of executeQueryJobs) rather than being reused for a subsequent query while stuck in a stale mount namespace. This relies on keep_process_open_ being a plain member checked synchronously in the same thread/process (true here, since worker is single-threaded per process), so it should correctly stop reuse. Residual risk: the parent-side caller in generateInNamespace/retrieveQueryDataFromContainer does not know the worker self-terminated early beyond the failure Status already returned by handleJob over IPC, but since the child process actually exits after the loop breaks, any subsequent connectToContainer call will detect the process is gone and fork a fresh one, which is the safe behavior. A fully robust fix might also want to explicitly signal an immediate exit rather than waiting for the next message-processing iteration, but this covers the finding's core concern (stale-namespace reuse) with a minimal change.

🤖 Prompt for AI agents
In osquery/worker/ipc/linux/linux_table_container_ipc.cpp around line 345, review and complete this code-review fix: setns() switches namespace without restoring on early-return error paths, leaving worker in wrong mount namespace.
What the draft fix changed: In `LinuxTableContainerIPC::handleJob()` (worker-side, `linux_table_container_ipc.cpp`), when the final restore `setns(original_mnt_fd_, ...)` fails inside the `keep_process_open_` block, the code now sets `keep_process_open_ = false` before returning the failure Status. This flag is also checked inside `executeQueryJobs()`'s loop (added a `if (!keep_process_open_) break;` after processing a message), so that once a job leaves the worker in a bad/unrestored namespace state, the worker process will exit its keep-open loop and terminate (via the existing `std::_Exit` at the end of `executeQueryJobs`) rather than being reused for a subsequent query while stuck in a stale mount namespace. This relies on `keep_process_open_` being a plain member checked synchronously in the same thread/process (true here, since worker is single-threaded per process), so it should correctly stop reuse. Residual risk: the parent-side caller in `generateInNamespace`/`retrieveQueryDataFromContainer` does not know the worker self-terminated early beyond the failure Status already returned by `handleJob` over IPC, but since the child process actually exits after the loop breaks, any subsequent `connectToContainer` call will detect the process is gone and fork a fresh one, which is the safe behavior. A fully robust fix might also want to explicitly signal an immediate exit rather than waiting for the next message-processing iteration, but this covers the finding's core concern (stale-namespace reuse) with a minimal change.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 55 low — review closely — react 👍/👎 to teach the reviewer

std::string original_mnt_path =
kProc + "/" + std::to_string(current_pid) + kMountNamespace;

if (original_mnt_fd_ > 0) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 original_mnt_fd_ leaked/overwritten check uses wrong sentinel value

In connectToContainer(), changed the guard if (original_mnt_fd_ > 0) to if (original_mnt_fd_ >= 0) exactly as suggested, so a previously opened fd valued 0 is no longer leaked/skipped when closing before reassigning original_mnt_fd_.

🤖 Prompt for AI agents
In osquery/worker/ipc/linux/linux_table_container_ipc.cpp around line 153, review and complete this code-review fix: original_mnt_fd_ leaked/overwritten check uses wrong sentinel value.
What the draft fix changed: In `connectToContainer()`, changed the guard `if (original_mnt_fd_ > 0)` to `if (original_mnt_fd_ >= 0)` exactly as suggested, so a previously opened fd valued 0 is no longer leaked/skipped when closing before reassigning `original_mnt_fd_`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer


return Status::success();
}
void LinuxTableContainerIPC::stopContainerWorker() {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 stopContainerWorker moves out of the file-scope global current_running_process without any synchronization

Added #include <mutex> and a file-scope std::mutex current_running_process_mutex guarding all accesses to the global current_running_process in connectToContainer() (write under lock when forking) and stopContainerWorker() (move-out under lock into a local child_process, replacing the previous unguarded std::move). This closes the specific race of concurrent read/move/write on the plain global. Residual risk: this only serializes access to the global variable itself; it does not add higher-level locking to prevent two threads from concurrently running connectToContainer/stopContainerWorker logic (e.g., both forking and only the mutex-protected assignment being safe, while the surrounding IPC channel management and process spawning logic is still not thread-safe as a whole). A complete fix would likely require a coarser lock around the whole connect/stop sequence or a documented single-threaded usage contract, which is out of scope for a minimal, targeted change in this file.

🤖 Prompt for AI agents
In osquery/worker/ipc/linux/linux_table_container_ipc.cpp around line 206, review and complete this code-review fix: stopContainerWorker moves out of the file-scope global current_running_process without any synchronization.
What the draft fix changed: Added `#include <mutex>` and a file-scope `std::mutex current_running_process_mutex` guarding all accesses to the global `current_running_process` in `connectToContainer()` (write under lock when forking) and `stopContainerWorker()` (move-out under lock into a local `child_process`, replacing the previous unguarded `std::move`). This closes the specific race of concurrent read/move/write on the plain global. Residual risk: this only serializes access to the global variable itself; it does not add higher-level locking to prevent two threads from concurrently running `connectToContainer`/`stopContainerWorker` logic (e.g., both forking and only the mutex-protected assignment being safe, while the surrounding IPC channel management and process spawning logic is still not thread-safe as a whole). A complete fix would likely require a coarser lock around the whole connect/stop sequence or a documented single-threaded usage contract, which is out of scope for a minimal, targeted change in this file.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

@@ -136,8 +136,20 @@ QueryData genDnsCache(QueryContext& context) {
PDNSCACHEENTRY pEntry = (PDNSCACHEENTRY)malloc(sizeof(DNSCACHEENTRY));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔴 dns_cache.cpp does not check for hLib==NULL or DnsGetCacheDataTable==nullptr before calling through the function pointer

In genDnsCache (osquery/tables/system/windows/dns_cache.cpp), added a null check on hLib immediately after LoadLibraryExW returns; on failure it logs a warning, frees pEntry, and returns empty results without calling GetProcAddress. Added a second check on DnsGetCacheDataTable after GetProcAddress; if null, it logs a warning, frees pEntry, frees the library (if loaded), and returns before calling through the function pointer. This prevents the null function pointer dereference.

🤖 Prompt for AI agents
In osquery/tables/system/windows/dns_cache.cpp around line 136, review and complete this code-review fix: dns_cache.cpp does not check for hLib==NULL or DnsGetCacheDataTable==nullptr before calling through the function pointer.
What the draft fix changed: In genDnsCache (osquery/tables/system/windows/dns_cache.cpp), added a null check on `hLib` immediately after LoadLibraryExW returns; on failure it logs a warning, frees `pEntry`, and returns empty results without calling GetProcAddress. Added a second check on `DnsGetCacheDataTable` after GetProcAddress; if null, it logs a warning, frees `pEntry`, frees the library (if loaded), and returns before calling through the function pointer. This prevents the null function pointer dereference.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Comment on lines 136 to 155
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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 dns_cache.cpp never frees the loaded DNSAPI.dll library handle

In genDnsCache, added FreeLibrary(hLib) calls: one on the new GetProcAddress-failure early-return path, and one right before the final return results; after the normal success path, ensuring the DNSAPI.dll handle is always released.

🤖 Prompt for AI agents
In osquery/tables/system/windows/dns_cache.cpp around line 133, review and complete this code-review fix: dns_cache.cpp never frees the loaded DNSAPI.dll library handle.
What the draft fix changed: In genDnsCache, added `FreeLibrary(hLib)` calls: one on the new GetProcAddress-failure early-return path, and one right before the final `return results;` after the normal success path, ensuring the DNSAPI.dll handle is always released.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

}
row["charged"] = INTEGER(bs.Capacity == bi.FullChargedCapacity);
row["current_capacity"] = INTEGER(bs.Capacity / designedVoltage);
row["voltage"] = INTEGER(bs.Voltage);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Battery amperage sign is unconditionally positive even when discharging, contradicting macOS battery table semantics

In genBatteryInfo (osquery/tables/system/windows/battery.cpp), added an isDischarging flag set when bs.PowerState & BATTERY_DISCHARGING is true, and negated the computed amperage value when discharging before assigning it to row["amperage"], matching macOS battery table semantics (negative while discharging, positive while charging/on AC). The change is scoped entirely within the BATTERY_STATUS handling block and does not alter any other field or control flow.

🤖 Prompt for AI agents
In osquery/tables/system/windows/battery.cpp around line 218, review and complete this code-review fix: Battery amperage sign is unconditionally positive even when discharging, contradicting macOS battery table semantics.
What the draft fix changed: In genBatteryInfo (osquery/tables/system/windows/battery.cpp), added an `isDischarging` flag set when `bs.PowerState & BATTERY_DISCHARGING` is true, and negated the computed amperage value when discharging before assigning it to `row["amperage"]`, matching macOS battery table semantics (negative while discharging, positive while charging/on AC). The change is scoped entirely within the BATTERY_STATUS handling block and does not alter any other field or control flow.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 75 medium — react 👍/👎 to teach the reviewer

Comment thread osquery/sql/sql.cpp
return Status(1, "SQL plugin must include a request action");
}

if (request.at("action") == "query") {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 SQLPlugin::call uses request.at("action") repeatedly without checking key existence for sub-keys like "query"/"table", risking std::out_of_range exception

In SQLPlugin::call (osquery/sql/sql.cpp), added request.count("query") == 0 / request.count("table") == 0 checks (returning Status::failure via Status(1, ...)) before each request.at("query")/request.at("table") call in the "query", "columns", "attach", "detach", and "tables" branches, and cached request.at("action") into a local reference to avoid repeated lookups. This prevents std::out_of_range from propagating out of the Status-returning API when required sub-keys are missing, per OSQUERY-002-2/OSQUERY-003.

🤖 Prompt for AI agents
In osquery/sql/sql.cpp around line 144, review and complete this code-review fix: SQLPlugin::call uses request.at("action") repeatedly without checking key existence for sub-keys like "query"/"table", risking std::out_of_range exception.
What the draft fix changed: In SQLPlugin::call (osquery/sql/sql.cpp), added request.count("query") == 0 / request.count("table") == 0 checks (returning Status::failure via Status(1, ...)) before each request.at("query")/request.at("table") call in the "query", "columns", "attach", "detach", and "tables" branches, and cached request.at("action") into a local reference to avoid repeated lookups. This prevents std::out_of_range from propagating out of the Status-returning API when required sub-keys are missing, per OSQUERY-002-2/OSQUERY-003.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

@@ -120,10 +120,10 @@ int getPrinterSharingStatus() {
int ret = cupsAdminGetServerSettings(cups, &num_settings, &settings);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 getPrinterSharingStatus leaks cups_option_t settings array when cupsAdminGetServerSettings fails

In getPrinterSharingStatus (sharing_preferences.cpp), moved the cupsFreeOptions(num_settings, settings) call out of the success-only branch of the if (ret != 0) block so it is now called unconditionally after the if/else, ensuring settings allocated by cupsAdminGetServerSettings are freed regardless of success or failure. cupsFreeOptions safely handles num_settings == 0 / settings == nullptr on the failure path, so no double-free or null-deref risk is introduced.

🤖 Prompt for AI agents
In osquery/tables/system/darwin/sharing_preferences.cpp around line 120, review and complete this code-review fix: getPrinterSharingStatus leaks cups_option_t settings array when cupsAdminGetServerSettings fails.
What the draft fix changed: In getPrinterSharingStatus (sharing_preferences.cpp), moved the cupsFreeOptions(num_settings, settings) call out of the success-only branch of the `if (ret != 0)` block so it is now called unconditionally after the if/else, ensuring settings allocated by cupsAdminGetServerSettings are freed regardless of success or failure. cupsFreeOptions safely handles num_settings == 0 / settings == nullptr on the failure path, so no double-free or null-deref risk is introduced.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 37 to 48

ExpectedDecompressData decompressLZxpress(std::vector<UCHAR>& 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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔵 decompressLZxpress reads prefetch_data[8] onward without validating prefetch_data.size() >= 8

In decompressLZxpress (osquery/utils/windows/lzxpress.cpp), added an explicit guard if (prefetch_data.size() < 8) at the top of the function that returns ExpectedDecompressData::failure with ConversionError::InvalidArgument before buffer_size is computed. This prevents the size_t underflow in prefetch_data.size() - 8 and the subsequent out-of-bounds read via &prefetch_data[8] when the input vector has fewer than 8 elements.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In osquery/utils/windows/lzxpress.cpp around line 75, review and complete this code-review fix: decompressLZxpress reads prefetch_data[8] onward without validating prefetch_data.size() >= 8.
What the draft fix changed: In decompressLZxpress (osquery/utils/windows/lzxpress.cpp), added an explicit guard `if (prefetch_data.size() < 8)` at the top of the function that returns ExpectedDecompressData::failure with ConversionError::InvalidArgument before buffer_size is computed. This prevents the size_t underflow in `prefetch_data.size() - 8` and the subsequent out-of-bounds read via `&prefetch_data[8]` when the input vector has fewer than 8 elements.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

@@ -62,8 +67,8 @@ QueryData genArpCache(QueryContext& context) {
r["interface"] = fields[5];

// Note: it's also possible to detect publish entries (ATF_PUB).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔵 hardcoded literal used for ATF_COM|ATF_PERM flag comparison instead of named constant

In genArpCache() in osquery/tables/networking/linux/arp_cache.cpp, replaced the magic-string comparison fields[2] == "0x6" with a numeric parse (strtoul base 16) of the flags field masked against newly introduced named constants kAtfCom (0x02) and kAtfPerm (0x04), matching the kernel's ATF_COM/ATF_PERM bit definitions. This makes the check robust to formatting variations (case, leading zeros, extra flag bits) since it now performs an actual bitwise AND against the combined mask rather than exact string equality. Added <cstdlib> include for strtoul.

🤖 Prompt for AI agents
In osquery/tables/networking/linux/arp_cache.cpp around line 64, review and complete this code-review fix: hardcoded literal used for ATF_COM|ATF_PERM flag comparison instead of named constant.
What the draft fix changed: In genArpCache() in osquery/tables/networking/linux/arp_cache.cpp, replaced the magic-string comparison `fields[2] == "0x6"` with a numeric parse (`strtoul` base 16) of the flags field masked against newly introduced named constants `kAtfCom` (0x02) and `kAtfPerm` (0x04), matching the kernel's ATF_COM/ATF_PERM bit definitions. This makes the check robust to formatting variations (case, leading zeros, extra flag bits) since it now performs an actual bitwise AND against the combined mask rather than exact string equality. Added `<cstdlib>` include for `strtoul`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

@flamingo flamingo Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 What this fix changed, finding by finding

34 finding(s) fixed in this draft — 34 explained inline on the diff; 5 low-confidence hunk(s) need close review before merging.

Comment on lines 357 to 369
int result = static_cast<int>(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));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔴 setns() switches namespace without restoring on early-return error paths, leaving worker in wrong mount namespace

In LinuxTableContainerIPC::handleJob() (worker-side, linux_table_container_ipc.cpp), when the final restore setns(original_mnt_fd_, ...) fails inside the keep_process_open_ block, the code now sets keep_process_open_ = false before returning the failure Status. This flag is also checked inside executeQueryJobs()'s loop (added a if (!keep_process_open_) break; after processing a message), so that once a job leaves the worker in a bad/unrestored namespace state, the worker process will exit its keep-open loop and terminate (via the existing std::_Exit at the end of executeQueryJobs) rather than being reused for a subsequent query while stuck in a stale mount namespace. This relies on keep_process_open_ being a plain member checked synchronously in the same thread/process (true here, since worker is single-threaded per process), so it should correctly stop reuse. Residual risk: the parent-side caller in generateInNamespace/retrieveQueryDataFromContainer does not know the worker self-terminated early beyond the failure Status already returned by handleJob over IPC, but since the child process actually exits after the loop breaks, any subsequent connectToContainer call will detect the process is gone and fork a fresh one, which is the safe behavior. A fully robust fix might also want to explicitly signal an immediate exit rather than waiting for the next message-processing iteration, but this covers the finding's core concern (stale-namespace reuse) with a minimal change.

🤖 Prompt for AI agents
In osquery/worker/ipc/linux/linux_table_container_ipc.cpp around line 345, review and complete this code-review fix: setns() switches namespace without restoring on early-return error paths, leaving worker in wrong mount namespace.
What the draft fix changed: In `LinuxTableContainerIPC::handleJob()` (worker-side, `linux_table_container_ipc.cpp`), when the final restore `setns(original_mnt_fd_, ...)` fails inside the `keep_process_open_` block, the code now sets `keep_process_open_ = false` before returning the failure Status. This flag is also checked inside `executeQueryJobs()`'s loop (added a `if (!keep_process_open_) break;` after processing a message), so that once a job leaves the worker in a bad/unrestored namespace state, the worker process will exit its keep-open loop and terminate (via the existing `std::_Exit` at the end of `executeQueryJobs`) rather than being reused for a subsequent query while stuck in a stale mount namespace. This relies on `keep_process_open_` being a plain member checked synchronously in the same thread/process (true here, since worker is single-threaded per process), so it should correctly stop reuse. Residual risk: the parent-side caller in `generateInNamespace`/`retrieveQueryDataFromContainer` does not know the worker self-terminated early beyond the failure Status already returned by `handleJob` over IPC, but since the child process actually exits after the loop breaks, any subsequent `connectToContainer` call will detect the process is gone and fork a fresh one, which is the safe behavior. A fully robust fix might also want to explicitly signal an immediate exit rather than waiting for the next message-processing iteration, but this covers the finding's core concern (stale-namespace reuse) with a minimal change.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 55 low — review closely — react 👍/👎 to teach the reviewer

std::string original_mnt_path =
kProc + "/" + std::to_string(current_pid) + kMountNamespace;

if (original_mnt_fd_ > 0) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 original_mnt_fd_ leaked/overwritten check uses wrong sentinel value

In connectToContainer(), changed the guard if (original_mnt_fd_ > 0) to if (original_mnt_fd_ >= 0) exactly as suggested, so a previously opened fd valued 0 is no longer leaked/skipped when closing before reassigning original_mnt_fd_.

🤖 Prompt for AI agents
In osquery/worker/ipc/linux/linux_table_container_ipc.cpp around line 153, review and complete this code-review fix: original_mnt_fd_ leaked/overwritten check uses wrong sentinel value.
What the draft fix changed: In `connectToContainer()`, changed the guard `if (original_mnt_fd_ > 0)` to `if (original_mnt_fd_ >= 0)` exactly as suggested, so a previously opened fd valued 0 is no longer leaked/skipped when closing before reassigning `original_mnt_fd_`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer


return Status::success();
}
void LinuxTableContainerIPC::stopContainerWorker() {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 stopContainerWorker moves out of the file-scope global current_running_process without any synchronization

Added #include <mutex> and a file-scope std::mutex current_running_process_mutex guarding all accesses to the global current_running_process in connectToContainer() (write under lock when forking) and stopContainerWorker() (move-out under lock into a local child_process, replacing the previous unguarded std::move). This closes the specific race of concurrent read/move/write on the plain global. Residual risk: this only serializes access to the global variable itself; it does not add higher-level locking to prevent two threads from concurrently running connectToContainer/stopContainerWorker logic (e.g., both forking and only the mutex-protected assignment being safe, while the surrounding IPC channel management and process spawning logic is still not thread-safe as a whole). A complete fix would likely require a coarser lock around the whole connect/stop sequence or a documented single-threaded usage contract, which is out of scope for a minimal, targeted change in this file.

🤖 Prompt for AI agents
In osquery/worker/ipc/linux/linux_table_container_ipc.cpp around line 206, review and complete this code-review fix: stopContainerWorker moves out of the file-scope global current_running_process without any synchronization.
What the draft fix changed: Added `#include <mutex>` and a file-scope `std::mutex current_running_process_mutex` guarding all accesses to the global `current_running_process` in `connectToContainer()` (write under lock when forking) and `stopContainerWorker()` (move-out under lock into a local `child_process`, replacing the previous unguarded `std::move`). This closes the specific race of concurrent read/move/write on the plain global. Residual risk: this only serializes access to the global variable itself; it does not add higher-level locking to prevent two threads from concurrently running `connectToContainer`/`stopContainerWorker` logic (e.g., both forking and only the mutex-protected assignment being safe, while the surrounding IPC channel management and process spawning logic is still not thread-safe as a whole). A complete fix would likely require a coarser lock around the whole connect/stop sequence or a documented single-threaded usage contract, which is out of scope for a minimal, targeted change in this file.
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

@@ -136,8 +136,20 @@ QueryData genDnsCache(QueryContext& context) {
PDNSCACHEENTRY pEntry = (PDNSCACHEENTRY)malloc(sizeof(DNSCACHEENTRY));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔴 dns_cache.cpp does not check for hLib==NULL or DnsGetCacheDataTable==nullptr before calling through the function pointer

In genDnsCache (osquery/tables/system/windows/dns_cache.cpp), added a null check on hLib immediately after LoadLibraryExW returns; on failure it logs a warning, frees pEntry, and returns empty results without calling GetProcAddress. Added a second check on DnsGetCacheDataTable after GetProcAddress; if null, it logs a warning, frees pEntry, frees the library (if loaded), and returns before calling through the function pointer. This prevents the null function pointer dereference.

🤖 Prompt for AI agents
In osquery/tables/system/windows/dns_cache.cpp around line 136, review and complete this code-review fix: dns_cache.cpp does not check for hLib==NULL or DnsGetCacheDataTable==nullptr before calling through the function pointer.
What the draft fix changed: In genDnsCache (osquery/tables/system/windows/dns_cache.cpp), added a null check on `hLib` immediately after LoadLibraryExW returns; on failure it logs a warning, frees `pEntry`, and returns empty results without calling GetProcAddress. Added a second check on `DnsGetCacheDataTable` after GetProcAddress; if null, it logs a warning, frees `pEntry`, frees the library (if loaded), and returns before calling through the function pointer. This prevents the null function pointer dereference.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer

Comment on lines 136 to 155
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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 dns_cache.cpp never frees the loaded DNSAPI.dll library handle

In genDnsCache, added FreeLibrary(hLib) calls: one on the new GetProcAddress-failure early-return path, and one right before the final return results; after the normal success path, ensuring the DNSAPI.dll handle is always released.

🤖 Prompt for AI agents
In osquery/tables/system/windows/dns_cache.cpp around line 133, review and complete this code-review fix: dns_cache.cpp never frees the loaded DNSAPI.dll library handle.
What the draft fix changed: In genDnsCache, added `FreeLibrary(hLib)` calls: one on the new GetProcAddress-failure early-return path, and one right before the final `return results;` after the normal success path, ensuring the DNSAPI.dll handle is always released.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

}
row["charged"] = INTEGER(bs.Capacity == bi.FullChargedCapacity);
row["current_capacity"] = INTEGER(bs.Capacity / designedVoltage);
row["voltage"] = INTEGER(bs.Voltage);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 Battery amperage sign is unconditionally positive even when discharging, contradicting macOS battery table semantics

In genBatteryInfo (osquery/tables/system/windows/battery.cpp), added an isDischarging flag set when bs.PowerState & BATTERY_DISCHARGING is true, and negated the computed amperage value when discharging before assigning it to row["amperage"], matching macOS battery table semantics (negative while discharging, positive while charging/on AC). The change is scoped entirely within the BATTERY_STATUS handling block and does not alter any other field or control flow.

🤖 Prompt for AI agents
In osquery/tables/system/windows/battery.cpp around line 218, review and complete this code-review fix: Battery amperage sign is unconditionally positive even when discharging, contradicting macOS battery table semantics.
What the draft fix changed: In genBatteryInfo (osquery/tables/system/windows/battery.cpp), added an `isDischarging` flag set when `bs.PowerState & BATTERY_DISCHARGING` is true, and negated the computed amperage value when discharging before assigning it to `row["amperage"]`, matching macOS battery table semantics (negative while discharging, positive while charging/on AC). The change is scoped entirely within the BATTERY_STATUS handling block and does not alter any other field or control flow.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 75 medium — react 👍/👎 to teach the reviewer

Comment thread osquery/sql/sql.cpp
return Status(1, "SQL plugin must include a request action");
}

if (request.at("action") == "query") {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 SQLPlugin::call uses request.at("action") repeatedly without checking key existence for sub-keys like "query"/"table", risking std::out_of_range exception

In SQLPlugin::call (osquery/sql/sql.cpp), added request.count("query") == 0 / request.count("table") == 0 checks (returning Status::failure via Status(1, ...)) before each request.at("query")/request.at("table") call in the "query", "columns", "attach", "detach", and "tables" branches, and cached request.at("action") into a local reference to avoid repeated lookups. This prevents std::out_of_range from propagating out of the Status-returning API when required sub-keys are missing, per OSQUERY-002-2/OSQUERY-003.

🤖 Prompt for AI agents
In osquery/sql/sql.cpp around line 144, review and complete this code-review fix: SQLPlugin::call uses request.at("action") repeatedly without checking key existence for sub-keys like "query"/"table", risking std::out_of_range exception.
What the draft fix changed: In SQLPlugin::call (osquery/sql/sql.cpp), added request.count("query") == 0 / request.count("table") == 0 checks (returning Status::failure via Status(1, ...)) before each request.at("query")/request.at("table") call in the "query", "columns", "attach", "detach", and "tables" branches, and cached request.at("action") into a local reference to avoid repeated lookups. This prevents std::out_of_range from propagating out of the Status-returning API when required sub-keys are missing, per OSQUERY-002-2/OSQUERY-003.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer

@@ -120,10 +120,10 @@ int getPrinterSharingStatus() {
int ret = cupsAdminGetServerSettings(cups, &num_settings, &settings);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🟠 getPrinterSharingStatus leaks cups_option_t settings array when cupsAdminGetServerSettings fails

In getPrinterSharingStatus (sharing_preferences.cpp), moved the cupsFreeOptions(num_settings, settings) call out of the success-only branch of the if (ret != 0) block so it is now called unconditionally after the if/else, ensuring settings allocated by cupsAdminGetServerSettings are freed regardless of success or failure. cupsFreeOptions safely handles num_settings == 0 / settings == nullptr on the failure path, so no double-free or null-deref risk is introduced.

🤖 Prompt for AI agents
In osquery/tables/system/darwin/sharing_preferences.cpp around line 120, review and complete this code-review fix: getPrinterSharingStatus leaks cups_option_t settings array when cupsAdminGetServerSettings fails.
What the draft fix changed: In getPrinterSharingStatus (sharing_preferences.cpp), moved the cupsFreeOptions(num_settings, settings) call out of the success-only branch of the `if (ret != 0)` block so it is now called unconditionally after the if/else, ensuring settings allocated by cupsAdminGetServerSettings are freed regardless of success or failure. cupsFreeOptions safely handles num_settings == 0 / settings == nullptr on the failure path, so no double-free or null-deref risk is introduced.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

Comment on lines 37 to 48

ExpectedDecompressData decompressLZxpress(std::vector<UCHAR>& 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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔵 decompressLZxpress reads prefetch_data[8] onward without validating prefetch_data.size() >= 8

In decompressLZxpress (osquery/utils/windows/lzxpress.cpp), added an explicit guard if (prefetch_data.size() < 8) at the top of the function that returns ExpectedDecompressData::failure with ConversionError::InvalidArgument before buffer_size is computed. This prevents the size_t underflow in prefetch_data.size() - 8 and the subsequent out-of-bounds read via &prefetch_data[8] when the input vector has fewer than 8 elements.

(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)

🤖 Prompt for AI agents
In osquery/utils/windows/lzxpress.cpp around line 75, review and complete this code-review fix: decompressLZxpress reads prefetch_data[8] onward without validating prefetch_data.size() >= 8.
What the draft fix changed: In decompressLZxpress (osquery/utils/windows/lzxpress.cpp), added an explicit guard `if (prefetch_data.size() < 8)` at the top of the function that returns ExpectedDecompressData::failure with ConversionError::InvalidArgument before buffer_size is computed. This prevents the size_t underflow in `prefetch_data.size() - 8` and the subsequent out-of-bounds read via `&prefetch_data[8]` when the input vector has fewer than 8 elements.

_(Automatically downgraded: no change in this fix lands near this finding's line — verify whether it was actually addressed.)_
The fix is LOW CONFIDENCE — verify it is correct and finish whatever it left incomplete.

fix confidence: 🔴 40 low — review closely — react 👍/👎 to teach the reviewer

@@ -62,8 +67,8 @@ QueryData genArpCache(QueryContext& context) {
r["interface"] = fields[5];

// Note: it's also possible to detect publish entries (ATF_PUB).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🦩 🔵 hardcoded literal used for ATF_COM|ATF_PERM flag comparison instead of named constant

In genArpCache() in osquery/tables/networking/linux/arp_cache.cpp, replaced the magic-string comparison fields[2] == "0x6" with a numeric parse (strtoul base 16) of the flags field masked against newly introduced named constants kAtfCom (0x02) and kAtfPerm (0x04), matching the kernel's ATF_COM/ATF_PERM bit definitions. This makes the check robust to formatting variations (case, leading zeros, extra flag bits) since it now performs an actual bitwise AND against the combined mask rather than exact string equality. Added <cstdlib> include for strtoul.

🤖 Prompt for AI agents
In osquery/tables/networking/linux/arp_cache.cpp around line 64, review and complete this code-review fix: hardcoded literal used for ATF_COM|ATF_PERM flag comparison instead of named constant.
What the draft fix changed: In genArpCache() in osquery/tables/networking/linux/arp_cache.cpp, replaced the magic-string comparison `fields[2] == "0x6"` with a numeric parse (`strtoul` base 16) of the flags field masked against newly introduced named constants `kAtfCom` (0x02) and `kAtfPerm` (0x04), matching the kernel's ATF_COM/ATF_PERM bit definitions. This makes the check robust to formatting variations (case, leading zeros, extra flag bits) since it now performs an actual bitwise AND against the combined mask rather than exact string equality. Added `<cstdlib>` include for `strtoul`.
Verify the change is correct and complete; do not refactor unrelated code.

fix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer

@flamingo flamingo Bot changed the title fix(adhoc-sweep-fixes): 34 review findings across 27 files fix(adhoc-sweep-fixes): CU-86akhf8u2 34 review findings across 27 files Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants