Compare commits

...

4 Commits

Author SHA1 Message Date
Leonardo Grasso
abdf5512c8 fix(userspace): add portable compat wrappers for gmtime_r, localtime_r, strerror_r
The previous commits used gmtime_r/localtime_r (unavailable on Windows)
and assumed the GNU strerror_r variant returning char* (only on glibc).
This broke macOS, musl, WASM, and Win32 builds.

Add userspace/engine/compat.h with portable inline wrappers:
- falco_gmtime_r / falco_localtime_r: use gmtime_s/localtime_s on Win32
- falco_strerror_r: returns const char* on all platforms, detecting
  glibc via __GLIBC__ (not _GNU_SOURCE alone, since musl defines
  _GNU_SOURCE but provides the XSI variant returning int)

Also fixes a pre-existing bug in create_signal_handlers.cpp where
the GNU strerror_r return value was incorrectly compared to 0 and
the actual error string was discarded.

Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2026-04-09 16:57:04 +02:00
Leonardo Grasso
26daebb1a9 fix(userspace): replace strerror() with thread-safe strerror_r()
strerror() returns a pointer to a shared static buffer, making it
unsafe when called concurrently from different threads. Replace all
call sites with strerror_r() using stack-local buffers, consistent
with the existing pattern in create_signal_handlers.cpp.

Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2026-04-09 16:56:58 +02:00
Leonardo Grasso
bb96f26fce fix(userspace/engine): replace non-thread-safe random() with thread-local RNG
random() uses internal static state that is not thread-safe. Since
should_drop_evt() can be called concurrently from per-source event
processing threads, replace it with a thread_local std::mt19937
seeded by std::random_device. Remove the now-unused srandom() seed
and Windows compat defines.

Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2026-04-09 16:56:49 +02:00
Leonardo Grasso
5e91db569a fix(userspace): replace gmtime/localtime with reentrant variants
gmtime() and localtime() return pointers to a shared static buffer,
making them unsafe in multi-threaded contexts. Replace all call sites
with gmtime_r() and localtime_r() which use caller-provided buffers.

Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2026-04-09 16:56:43 +02:00
10 changed files with 118 additions and 33 deletions

59
userspace/engine/compat.h Normal file
View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2025 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <ctime>
#include <cstring>
// Portable gmtime_r: Windows provides gmtime_s with reversed arg order.
inline struct tm* falco_gmtime_r(const time_t* timer, struct tm* buf) {
#ifdef _WIN32
return gmtime_s(buf, timer) == 0 ? buf : nullptr;
#else
return gmtime_r(timer, buf);
#endif
}
// Portable localtime_r: Windows provides localtime_s with reversed arg order.
inline struct tm* falco_localtime_r(const time_t* timer, struct tm* buf) {
#ifdef _WIN32
return localtime_s(buf, timer) == 0 ? buf : nullptr;
#else
return localtime_r(timer, buf);
#endif
}
// Portable strerror_r: returns const char* on all platforms.
//
// - glibc with _GNU_SOURCE: returns char* that may point to buf or a static string
// - musl/macOS/WASM (XSI): returns int, always writes to buf
// - Windows: no strerror_r, uses strerror_s instead
//
// We check __GLIBC__ (not _GNU_SOURCE alone) because musl defines _GNU_SOURCE
// but always provides the XSI variant.
inline const char* falco_strerror_r(int errnum, char* buf, size_t len) {
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(errnum, buf, len);
#elif defined(_WIN32)
strerror_s(buf, len, errnum);
return buf;
#else
strerror_r(errnum, buf, len);
return buf;
#endif
}

View File

@@ -15,15 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#include <cstdlib>
#ifndef _WIN32
#include <unistd.h>
#else
#include <stdlib.h>
#include <io.h>
#define srandom srand
#define random rand
#endif
#include <random>
#include <string>
#include <fstream>
#include <functional>
@@ -52,7 +49,7 @@ const std::string falco_engine::s_default_ruleset = "falco-default-ruleset";
using namespace falco;
falco_engine::falco_engine(bool seed_rng):
falco_engine::falco_engine(bool /* seed_rng */):
m_syscall_source(NULL),
m_syscall_source_idx(SIZE_MAX),
m_rule_reader(std::make_shared<rule_loader::reader>()),
@@ -62,10 +59,6 @@ falco_engine::falco_engine(bool seed_rng):
m_min_priority(falco_common::PRIORITY_DEBUG),
m_sampling_ratio(1),
m_sampling_multiplier(0) {
if(seed_rng) {
srandom((unsigned)getpid());
}
m_default_ruleset_id = find_ruleset_id(s_default_ruleset);
fill_engine_state_funcs(m_engine_state);
@@ -1007,6 +1000,8 @@ inline bool falco_engine::should_drop_evt() const {
return false;
}
double coin = (random() * (1.0 / RAND_MAX));
thread_local std::mt19937 rng(std::random_device{}());
std::uniform_real_distribution<double> dist(0.0, 1.0);
double coin = dist(rng);
return (coin >= (1.0 / (m_sampling_multiplier * m_sampling_ratio)));
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "compat.h"
#include "formats.h"
#include "falco_engine.h"
@@ -98,7 +99,9 @@ std::string falco_formats::format_event(sinsp_evt *evt,
char time_ns[12]; // sizeof ".sssssssssZ"
std::string iso8601evttime;
strftime(time_sec, sizeof(time_sec), "%FT%T", gmtime(&evttime));
struct tm tm_buf;
falco_gmtime_r(&evttime, &tm_buf);
strftime(time_sec, sizeof(time_sec), "%FT%T", &tm_buf);
snprintf(time_ns, sizeof(time_ns), ".%09luZ", evt->get_ts() % 1000000000);
iso8601evttime = time_sec;
iso8601evttime += time_ns;

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include <ctime>
#include "compat.h"
#include "logger.h"
#include "falco_common.h"
@@ -122,14 +123,16 @@ void falco_logger::log(falco_logger::level priority, const std::string&& msg) {
std::time_t result = std::time(nullptr);
if(falco_logger::time_format_iso_8601) {
char buf[sizeof "YYYY-MM-DDTHH:MM:SS-0000"];
const struct tm* gtm = std::gmtime(&result);
if(gtm != NULL && (strftime(buf, sizeof(buf), "%FT%T%z", gtm) != 0)) {
struct tm gtm;
if(falco_gmtime_r(&result, &gtm) != NULL &&
(strftime(buf, sizeof(buf), "%FT%T%z", &gtm) != 0)) {
fprintf(stderr, "%s: %s", buf, copy.c_str());
}
} else {
const struct tm* ltm = std::localtime(&result);
struct tm ltm;
falco_localtime_r(&result, &ltm);
char tstr[std::size("WWW MMM DD HH:mm:ss YYYY")];
std::strftime(std::data(tstr), std::size(tstr), "%a %b %d %H:%M:%S %Y", ltm);
std::strftime(std::data(tstr), std::size(tstr), "%a %b %d %H:%M:%S %Y", &ltm);
fprintf(stderr, "%s: %s", tstr, copy.c_str());
}
}

View File

@@ -18,6 +18,7 @@ limitations under the License.
#include <functional>
#include "actions.h"
#include "compat.h"
#include "../app.h"
#include "../signals.h"
@@ -49,12 +50,10 @@ bool create_handler(int sig, void (*func)(int), run_result& ret) {
#ifdef __linux__
if(signal(sig, func) == SIG_ERR) {
char errbuf[1024];
if(strerror_r(errno, errbuf, sizeof(errbuf)) != 0) {
snprintf(errbuf, sizeof(errbuf) - 1, "Errno %d", errno);
}
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
ret = run_result::fatal(std::string("Could not create signal handler for ") +
strsignal(sig) + ": " + errbuf);
strsignal(sig) + ": " + errstr);
}
#endif
return ret.success;

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include "actions.h"
#include "compat.h"
#include "helpers.h"
#include "../app.h"
#include <fstream>
@@ -36,10 +37,13 @@ falco::app::run_result falco::app::actions::print_kernel_version(const falco::ap
std::ifstream input_file("/proc/version");
if(!input_file.is_open()) {
// We don't want to fail, we just need to log something
falco_logger::log(
falco_logger::level::INFO,
"Cannot read under '/proc/version' (err_message: '" + std::string(strerror(errno)) +
"', err_code: " + std::to_string(errno) + "). No info provided, go on.");
int saved_errno = errno;
char errbuf[256];
const char* errstr = falco_strerror_r(saved_errno, errbuf, sizeof(errbuf));
falco_logger::log(falco_logger::level::INFO,
"Cannot read under '/proc/version' (err_message: '" +
std::string(errstr) + "', err_code: " +
std::to_string(saved_errno) + "). No info provided, go on.");
return run_result::ok();
}

View File

@@ -20,6 +20,7 @@ limitations under the License.
#else
#include <windows.h>
#endif
#include "compat.h"
#include <iostream>
#include "actions.h"
@@ -95,7 +96,9 @@ falco::app::run_result falco::app::actions::print_support(falco::app::state& s)
nlohmann::json support;
if(get_sysinfo(support) != 0) {
return run_result::fatal(std::string("Could not get system info: ") + strerror(errno));
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
return run_result::fatal(std::string("Could not get system info: ") + errstr);
}
const falco::versions_info infos(s.offline_inspector);

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include "falco_outputs.h"
#include "compat.h"
#include "config_falco.h"
#include "formats.h"
@@ -181,7 +182,9 @@ void falco_outputs::handle_msg(uint64_t ts,
char time_ns[12]; // sizeof ".sssssssssZ"
std::string iso8601evttime;
strftime(time_sec, sizeof(time_sec), "%FT%T", gmtime(&evttime));
struct tm tm_buf;
falco_gmtime_r(&evttime, &tm_buf);
strftime(time_sec, sizeof(time_sec), "%FT%T", &tm_buf);
snprintf(time_ns, sizeof(time_ns), ".%09luZ", ts % 1000000000);
iso8601evttime = time_sec;
iso8601evttime += time_ns;

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include "outputs_program.h"
#include "compat.h"
#include "logger.h"
#include <stdio.h>
#include <cerrno>
@@ -26,9 +27,11 @@ void falco::outputs::output_program::open_pfile() {
m_pfile = popen(m_oc.options["program"].c_str(), "w");
if(m_pfile == nullptr) {
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
falco_logger::log(falco_logger::level::ERR,
"Failed to open program output: " + m_oc.options["program"] +
" (error: " + std::string(std::strerror(errno)) + ")");
" (error: " + errstr + ")");
return;
}
@@ -38,7 +41,7 @@ void falco::outputs::output_program::open_pfile() {
}
}
void falco::outputs::output_program::output(const message *msg) {
void falco::outputs::output_program::output(const message* msg) {
open_pfile();
if(m_pfile != nullptr) {

View File

@@ -24,6 +24,7 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "compat.h"
#include "falco_common.h"
#include "stats_writer.h"
#include "logger.h"
@@ -71,7 +72,9 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
memset(&handler, 0, sizeof(handler));
handler.sa_handler = &timer_handler;
if(sigaction(SIGALRM, &handler, NULL) == -1) {
err = std::string("Could not set up signal handler for periodic timer: ") + strerror(errno);
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up signal handler for periodic timer: ") + errstr;
return false;
}
@@ -93,7 +96,9 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
memset(&handler, 0, sizeof(handler));
handler.sa_handler = &timer_handler;
if(sigaction(SIGALRM, &handler, NULL) == -1) {
err = std::string("Could not set up signal handler for periodic timer: ") + strerror(errno);
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up signal handler for periodic timer: ") + errstr;
return false;
}
@@ -119,7 +124,9 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
memset(&handler, 0, sizeof(handler));
handler.sa_handler = &timer_handler;
if(sigaction(SIGALRM, &handler, NULL) == -1) {
err = std::string("Could not set up signal handler for periodic timer: ") + strerror(errno);
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up signal handler for periodic timer: ") + errstr;
return false;
}
@@ -131,14 +138,18 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
// delete any previously set timer
if(s_timerid_exists) {
if(timer_delete(s_timerid) == -1) {
err = std::string("Could not delete previous timer: ") + strerror(errno);
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not delete previous timer: ") + errstr;
return false;
}
s_timerid_exists = false;
}
if(timer_create(CLOCK_MONOTONIC, &sev, &s_timerid) == -1) {
err = std::string("Could not create periodic timer: ") + strerror(errno);
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not create periodic timer: ") + errstr;
return false;
}
s_timerid_exists = true;
@@ -148,7 +159,9 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
timer.it_interval = timer.it_value;
if(timer_settime(s_timerid, 0, &timer, NULL) == -1) {
err = std::string("Could not set up periodic timer: ") + strerror(errno);
char errbuf[256];
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up periodic timer: ") + errstr;
return false;
}