Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions driver/modern_bpf/helpers/base/maps_getters.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,51 @@ static __always_inline uint16_t maps__get_ppm_sc(uint16_t syscall_id) {
/*=============================== AUXILIARY MAPS ===========================*/

static __always_inline struct auxiliary_map *maps__get_auxiliary_map() {
uint32_t cpu_id = (uint32_t)bpf_get_smp_processor_id();
return (struct auxiliary_map *)bpf_map_lookup_elem(&auxiliary_maps, &cpu_id);
/* Key by `pid_tgid` rather than CPU: BPF programs are preemptible on
* kernels >= 5.11, so a per-CPU scratch buffer can be clobbered by
* another program scheduled on the same CPU. A task only ever runs on
* one CPU at a time, so `pid_tgid` uniquely identifies an in-flight
* event build (stable across the tail-call chain). See
* falcosecurity/libs#2719.
*/
uint64_t pid_tgid = bpf_get_current_pid_tgid();
struct auxiliary_map *auxmap =
(struct auxiliary_map *)bpf_map_lookup_elem(&auxiliary_maps, &pid_tgid);
if(auxmap) {
return auxmap;
}

/* First event for this task (or the entry was evicted from the LRU): we
* need to create the entry. `bpf_map_update_elem` requires a value to
* copy from; use the single-element init template (a 128 KB value cannot
* live on the BPF stack). The auxmap does not need to be zeroed (the
* header, payload_pos and lengths_pos are set explicitly by
* auxmap__preload_event_header, and param data is written before it is
* read), so the template contents are irrelevant. */
uint32_t zero = 0;
struct auxiliary_map *init =
(struct auxiliary_map *)bpf_map_lookup_elem(&auxiliary_map_init, &zero);
if(!init) {
return NULL;
}
if(bpf_map_update_elem(&auxiliary_maps, &pid_tgid, init, BPF_ANY)) {
return NULL;
}
return (struct auxiliary_map *)bpf_map_lookup_elem(&auxiliary_maps, &pid_tgid);
}

/**
* @brief Release the current task's auxiliary map entry.
*
* Called once an event has been submitted (best effort). Freeing the entry
* immediately keeps the `auxiliary_maps` LRU hash populated only with events
* that are currently being built (bounded by the CPU count), rather than one
* entry per task that has ever produced an event. Entries left behind by
* abandoned events (e.g. a failed tail call) are reclaimed by LRU eviction.
*/
static __always_inline void maps__release_auxiliary_map() {
uint64_t pid_tgid = bpf_get_current_pid_tgid();
bpf_map_delete_elem(&auxiliary_maps, &pid_tgid);
}

/*=============================== AUXILIARY MAPS ===========================*/
Expand Down
5 changes: 5 additions & 0 deletions driver/modern_bpf/helpers/store/auxmap_store_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ static __always_inline void auxmap__submit_event(struct auxiliary_map *auxmap) {
counter->n_drops_buffer++;
compute_event_types_stats(auxmap->event_type, counter);
}

/* The event has been handed to the ring buffer (or dropped); release this
* task's auxiliary map entry so the LRU hash is not filled with one entry
* per task that has ever produced an event. See falcosecurity/libs#2719. */
maps__release_auxiliary_map();
Comment on lines +152 to +156

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Release the task entry on every exit path.

maps__release_auxiliary_map() runs only after bpf_ringbuf_output(). The !rb branch at Line 127, the !counter branch at Line 132, and the oversized-event branch at Line 141 return before this block. Those paths leave the current task's entry in auxiliary_maps.

Repeated dropped events can fill the LRU and evict an entry still used by another in-flight event. Route all early returns through a common out: cleanup block, or release the entry before each return.

Proposed cleanup path
 if(!rb) {
   ...
-  return;
+  goto out;
 }

 if(!counter) {
-  return;
+  goto out;
 }

 if(auxmap->payload_pos > MAX_EVENT_SIZE) {
   ...
-  return;
+  goto out;
 }

+out:
 maps__release_auxiliary_map();
 return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@driver/modern_bpf/helpers/store/auxmap_store_params.h` around lines 152 -
156, Ensure maps__release_auxiliary_map() is executed on every exit path of the
helper, including the !rb, !counter, and oversized-event branches before their
returns. Route these branches and the post-bpf_ringbuf_output path through a
shared out cleanup block, preserving each branch’s existing return behavior.

return;
}

Expand Down
31 changes: 28 additions & 3 deletions driver/modern_bpf/maps/maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,40 @@ struct {
*/

/**
* @brief For every CPU on the system we have an auxiliary
* map where the event is temporally saved before being
* @brief Auxiliary map where the event is temporally saved before being
* pushed in the ringbuffer.
*
* This is keyed by `pid_tgid` (not CPU) and backed by an LRU hash to avoid a
* data-corruption race on preemptible kernels (Linux >= 5.11). BPF programs run
* with `migrate_disable()` but are preemptible: a per-CPU scratch buffer can be
* clobbered if a program is preempted mid-write and another program runs on the
* same CPU. Keying by `pid_tgid` gives each in-flight task its own scratch
* entry, since a task can only run on one CPU at a time. See
* falcosecurity/libs#2719.
*/
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__type(key, uint64_t);
__type(value, struct auxiliary_map);
} auxiliary_maps __weak SEC(".maps");

/**
* @brief Single-element template used to initialise a new `auxiliary_maps`
* entry.
*
* `bpf_map_update_elem` on the LRU hash requires a source value to copy from
* when creating a new entry for a task. We cannot build a 128 KB value on the
* BPF stack (512 byte limit), so we keep one element to copy from. Its contents
* are irrelevant (the auxmap is always written before it is read), so it is
* never explicitly initialised. A regular ARRAY is used because a
* BPF_MAP_TYPE_PERCPU_ARRAY element is limited to 32 KB.
*/
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, uint32_t);
__type(value, struct auxiliary_map);
} auxiliary_maps __weak SEC(".maps");
} auxiliary_map_init __weak SEC(".maps");

/**
* @brief For every CPU on the system we have a counter
Expand Down
14 changes: 12 additions & 2 deletions userspace/libpman/src/maps.c
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,18 @@ int pman_mark_single_64bit_syscall(int syscall_id, bool interesting) {
}

static int size_auxiliary_maps() {
/* We always allocate auxiliary maps from all the CPUs, even if some of them are not online. */
if(bpf_map__set_max_entries(g_state.skel->maps.auxiliary_maps, g_state.n_possible_cpus)) {
/* `auxiliary_maps` is an LRU hash keyed by `pid_tgid`, holding one scratch
* entry per event currently being built. Entries are released as soon as an
* event is submitted, so at any instant the number of live entries is
* bounded by the number of tasks building an event concurrently (at most
* one per CPU). We size it at 2x the possible CPUs (with a small floor) to
* leave headroom; the LRU reclaims any entries left behind by abandoned
* events. */
uint32_t aux_entries = g_state.n_possible_cpus * 2;
if(aux_entries < 16) {
aux_entries = 16;
}
if(bpf_map__set_max_entries(g_state.skel->maps.auxiliary_maps, aux_entries)) {
pman_print_error("unable to set max entries for 'auxiliary_maps'");
return errno;
}
Expand Down
63 changes: 63 additions & 0 deletions userspace/libsinsp/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,69 @@ void sinsp_evt_param::throw_invalid_len_error(size_t requested_length) const {
"parameter raw data: \n" + buffer_to_multiline_hex(param_data, param_len),
sinsp_logger::SEV_ERROR);

// Enhanced diagnostics: dump raw event structure to identify the
// root cause of parameter corruption (ROX-33614 investigation).
const scap_evt *raw = m_evt->get_scap_evt();
const ppm_event_info *evtinfo = m_evt->get_info();
if(raw && evtinfo) {
std::stringstream diag;
diag << "event diagnostics:"
<< " hdr_nparams=" << raw->nparams
<< " table_nparams=" << evtinfo->nparams
<< " event_len=" << raw->len
<< " event_type=" << raw->type
<< " hdr_size=" << sizeof(struct ppm_evt_hdr);
libsinsp_logger()->log(diag.str(), sinsp_logger::SEV_ERROR);

// Dump the length array from the raw event.
// Layout: [ppm_evt_hdr][len0][len1]...[lenN][data0][data1]...
// Each length entry is uint16_t (non-large) or uint32_t (large).
const char *evt_base = reinterpret_cast<const char *>(raw);
const char *len_array = evt_base + sizeof(struct ppm_evt_hdr);
bool is_large = (evtinfo->flags & EF_LARGE_PAYLOAD) != 0;
uint32_t len_entry_size = is_large ? sizeof(uint32_t) : sizeof(uint16_t);
uint32_t len_array_bytes = raw->nparams * len_entry_size;

// Dump all param lengths from the raw length array.
std::stringstream lens;
lens << "raw param lengths (" << (is_large ? "large" : "u16") << "):";
for(uint32_t i = 0; i < raw->nparams && i < PPM_MAX_EVENT_PARAMS; i++) {
uint32_t plen = 0;
if(is_large) {
memcpy(&plen, len_array + i * sizeof(uint32_t), sizeof(uint32_t));
} else {
uint16_t plen16 = 0;
memcpy(&plen16, len_array + i * sizeof(uint16_t), sizeof(uint16_t));
plen = plen16;
}
lens << " [" << i << "]=" << plen;
}
libsinsp_logger()->log(lens.str(), sinsp_logger::SEV_ERROR);

// Dump the raw event header + length array as hex.
size_t hdr_and_lens = sizeof(struct ppm_evt_hdr) + len_array_bytes;
size_t dump_len = std::min(hdr_and_lens, (size_t)256);
libsinsp_logger()->log(
"raw header+lengths (" + std::to_string(dump_len) + " bytes):\n" +
buffer_to_multiline_hex(evt_base, dump_len),
sinsp_logger::SEV_ERROR);

// Dump the first 128 bytes of the param data region.
const char *data_region = len_array + len_array_bytes;
size_t data_avail = 0;
if(raw->len > hdr_and_lens) {
data_avail = raw->len - hdr_and_lens;
}
size_t data_dump = std::min(data_avail, (size_t)128);
if(data_dump > 0) {
libsinsp_logger()->log(
"param data region (first " + std::to_string(data_dump) +
" of " + std::to_string(data_avail) + " bytes):\n" +
buffer_to_multiline_hex(data_region, data_dump),
sinsp_logger::SEV_ERROR);
Comment on lines +1750 to +1792

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate raw-event bounds before reading diagnostic fields.

The invalid-length path trusts raw->nparams and reads len_array before it verifies that the declared length array fits in raw->len. A truncated event can therefore cause memcpy() at Lines 1762-1765 or buffer_to_multiline_hex() at Lines 1775-1777 to read beyond the event buffer. This can crash the collector while it handles the malformed event.

Limit decoded entries to (raw->len - sizeof(ppm_evt_hdr)) / len_entry_size. Limit every hex dump to raw->len. Do not calculate data_region from the declared length-array size unless that full array fits in the event.

As per path instructions, focus on major issues impacting performance, readability, maintainability and security.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@userspace/libsinsp/event.cpp` around lines 1750 - 1792, Validate the raw
event bounds before diagnostic reads in the invalid-length handling block:
ensure raw->len covers the event header, cap decoded length entries by both
PPM_MAX_EVENT_PARAMS and the available length-array bytes, and avoid reading any
length entry beyond raw->len. Cap the header/length and parameter-data hex dumps
to raw->len, and only derive data_region from len_array_bytes when the complete
declared length array fits; otherwise skip that data-region calculation or use
only the verified available bytes.

Source: Path instructions

}
}

throw sinsp_exception(error_string);
}

Expand Down