Skip to content

Commit 1c5d390

Browse files
committed
feat!: isolate modern probe TOCTOU mitigation logic
Current TOCTOU mitigation implementation leverages the enter events generated by eBPF programs which are tail-called by the `sys_enter` dispatcher. As the architecture is moving towards dropping enter event generation and collection, the `sys_enter` dispatcher will be ultimately removed. This requires to isolate TOCTOU mitigation handling logic in order to make it independent from `sys_enter` dispatcher removal. This patch moves TOCTOU mitigation handling logic into separate ad-hoc eBPF tracepoint programs attached on corresponding `syscalls/sys_enter_*` hooks. For each system call `<syscall>` that already supports TOCTOU mitigation, two eBPF tracepoint programs are defined: - `ttm_<syscall>_e` - this program is responsible for tail calling the `<syscall>_e` program after having performed syscall ID normalization and applied any required sampling/filtering logic - `<syscall>_e` - this program is responsible for collecting the information needed to generate the proper enter event and sending the generated event to userspace Tail-called TOCTOU mitigation programs are inserted into a separate tail call map, called `syscall_enter_toctou_mitigation_tail_table`. As the tracepoint attachment procedure generates an `openat` exit event on `/sys/kernel/tracing/events/.../id` that would pollute the stream of events read by the probe, the implementation takes care of attaching them before attaching the `sys_exit` dispatcher. Notice that the new logic make the TOCTOU mitigation programs attachment dependent on the presence of the `sys_exit` dispatcher. BREAKING CHANGE: change prerequisites for attaching some enter progs Signed-off-by: Leonardo Di Giovanna <[email protected]>
1 parent 082157d commit 1c5d390

File tree

24 files changed

+1193
-524
lines changed

24 files changed

+1193
-524
lines changed

driver/modern_bpf/helpers/interfaces/syscalls_dispatcher.h

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,42 @@
1313
#include <helpers/base/read_from_task.h>
1414
#include <helpers/extract/extract_from_kernel.h>
1515

16+
// We don't want to send DROP_E/DROP_X events from the enter tracepoint because it would requires us
17+
// to create a dedicated tail table for the enter. It is enough to send DROP_E/DROP_X events from
18+
// the exit tracepoint.
19+
static __always_inline bool syscalls_dispatcher__sampling_logic_enter(uint32_t syscall_id) {
20+
/* If dropping mode is not enabled we don't perform any sampling
21+
* false: means don't drop the syscall
22+
* true: means drop the syscall
23+
*/
24+
if(!maps__get_dropping_mode()) {
25+
return false;
26+
}
27+
28+
uint8_t sampling_flag = maps__64bit_sampling_syscall_table(syscall_id);
29+
30+
if(sampling_flag == UF_NEVER_DROP) {
31+
return false;
32+
}
33+
34+
if(sampling_flag == UF_ALWAYS_DROP) {
35+
return true;
36+
}
37+
38+
// If we are in the sampling period we drop the event
39+
if((bpf_ktime_get_boot_ns() % SECOND_TO_NS) >= (SECOND_TO_NS / maps__get_sampling_ratio())) {
40+
return true;
41+
}
42+
43+
return false;
44+
}
45+
1646
static __always_inline bool syscalls_dispatcher__64bit_interesting_syscall(uint32_t syscall_id) {
1747
return maps__interesting_syscall_64bit(syscall_id);
1848
}
1949

20-
static __always_inline long convert_network_syscalls(struct pt_regs *regs) {
21-
int socketcall_id = (int)extract__syscall_argument(regs, 0);
22-
23-
switch(socketcall_id) {
50+
static __always_inline long convert_socketcall_call_to_syscall_id(int socketcall_call) {
51+
switch(socketcall_call) {
2452
#ifdef __NR_socket
2553
case SYS_SOCKET:
2654
return __NR_socket;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
/*
3+
* Copyright (C) 2025 The Falco Authors.
4+
*
5+
* This file is dual licensed under either the MIT or GPL 2. See MIT.txt
6+
* or GPL2.txt for full copies of the license.
7+
*/
8+
9+
#pragma once
10+
11+
#include <helpers/interfaces/syscalls_dispatcher.h>
12+
#include <helpers/interfaces/variable_size_event.h>
13+
14+
/**
15+
* @brief Tail call the TOCTOU mitigation program corresponding to the specified program code.
16+
*
17+
* @param ctx is the original program context
18+
* @param syscall_id is the original system call id that triggered the program
19+
* @param socketcall_call (socketcall-only) is the call argument provided to the socketcall system
20+
call (e.g.: SYS_CONNECT)
21+
* @param prog_code is the program code identifying the TOCTOU mitigation program to be called
22+
23+
* @return never returns in case of success; otherwise, returns 0
24+
*/
25+
static __always_inline int toctou_mitigation__call_prog(
26+
void *ctx,
27+
uint32_t syscall_id,
28+
int socketcall_call,
29+
enum sys_enter_toctou_mitigation_prog_code prog_code) {
30+
int socketcall_syscall_id = -1;
31+
32+
if(bpf_in_ia32_syscall()) {
33+
#if defined(__TARGET_ARCH_x86)
34+
if(syscall_id == __NR_ia32_socketcall) {
35+
socketcall_syscall_id = __NR_ia32_socketcall;
36+
} else {
37+
syscall_id = maps__ia32_to_64(syscall_id);
38+
// Syscalls defined only on 32 bits are dropped here.
39+
if(syscall_id == (uint32_t)-1) {
40+
return 0;
41+
}
42+
}
43+
#else
44+
return 0;
45+
#endif
46+
} else {
47+
#ifdef __NR_socketcall
48+
socketcall_syscall_id = __NR_socketcall;
49+
#endif
50+
}
51+
52+
// Convert the socketcall id into the network syscall id.
53+
// In this way the syscall will be treated exactly as the original one.
54+
if(syscall_id == socketcall_syscall_id) {
55+
syscall_id = convert_socketcall_call_to_syscall_id(socketcall_call);
56+
if(syscall_id == -1) {
57+
// We can't do anything since modern bpf filler jump table is syscall indexed.
58+
return 0;
59+
}
60+
}
61+
62+
if(!syscalls_dispatcher__64bit_interesting_syscall(syscall_id)) {
63+
return 0;
64+
}
65+
66+
if(syscalls_dispatcher__sampling_logic_enter(syscall_id)) {
67+
return 0;
68+
}
69+
70+
bpf_tail_call(ctx, &syscall_enter_toctou_mitigation_tail_table, prog_code);
71+
bpf_printk("unable to tail call into TTM prog (prog_code: %d)", prog_code);
72+
return 0;
73+
}
74+
75+
static __always_inline void toctou_mitigation__push_connect_enter_event(
76+
struct auxiliary_map *auxmap,
77+
int64_t socket_fd,
78+
unsigned long usrsockaddr,
79+
uint16_t usrsockaddr_len) {
80+
auxmap__preload_event_header(auxmap, PPME_SOCKET_CONNECT_E);
81+
82+
/*=============================== COLLECT PARAMETERS ===========================*/
83+
84+
/* Parameter 1: fd (type: PT_FD) */
85+
auxmap__store_s64_param(auxmap, socket_fd);
86+
87+
/* Parameter 2: addr (type: PT_SOCKADDR) */
88+
auxmap__store_sockaddr_param(auxmap, usrsockaddr, usrsockaddr_len);
89+
90+
/*=============================== COLLECT PARAMETERS ===========================*/
91+
92+
auxmap__finalize_event_header(auxmap);
93+
94+
auxmap__submit_event(auxmap);
95+
}

driver/modern_bpf/maps/maps.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ struct {
104104
__type(value, uint32_t);
105105
} syscall_exit_tail_table __weak SEC(".maps");
106106

107+
/**
108+
* @brief This tail table is used by the TOCTOU mitigation sys_enter_* programs.
109+
* Given a predefined tail-code (`sys_enter_toctou_mitigation_prog_code`), they call
110+
* the right bpf program to generate the proper TOCTOU mitigation event.
111+
*/
112+
struct {
113+
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
114+
__uint(max_entries, SYS_ENTER_TOCTOU_MITIGATION_PROG_CODE_MAX);
115+
__type(key, uint32_t);
116+
__type(value, uint32_t);
117+
} syscall_enter_toctou_mitigation_tail_table __weak SEC(".maps");
118+
107119
/**
108120
* @brief This tail table is used when a sys exit bpf program needs another program
109121
* to complete its execution flow.

driver/modern_bpf/programs/attached/dispatchers/syscall_enter.bpf.c

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,6 @@
88

99
#include <helpers/interfaces/syscalls_dispatcher.h>
1010

11-
// We don't want to send DROP_E/DROP_X events from the enter tracepoint because it would requires us
12-
// to create a dedicated tail table for the enter. It is enough to send DROP_E/DROP_X events from
13-
// the exit tracepoint.
14-
static __always_inline bool sampling_logic_enter(void* ctx, uint32_t id) {
15-
/* If dropping mode is not enabled we don't perform any sampling
16-
* false: means don't drop the syscall
17-
* true: means drop the syscall
18-
*/
19-
if(!maps__get_dropping_mode()) {
20-
return false;
21-
}
22-
23-
uint8_t sampling_flag = maps__64bit_sampling_syscall_table(id);
24-
25-
if(sampling_flag == UF_NEVER_DROP) {
26-
return false;
27-
}
28-
29-
if(sampling_flag == UF_ALWAYS_DROP) {
30-
return true;
31-
}
32-
33-
// If we are in the sampling period we drop the event
34-
if((bpf_ktime_get_boot_ns() % SECOND_TO_NS) >= (SECOND_TO_NS / maps__get_sampling_ratio())) {
35-
return true;
36-
}
37-
38-
return false;
39-
}
40-
4111
/* From linux tree: /include/trace/events/syscall.h
4212
* TP_PROTO(struct pt_regs *regs, long id),
4313
*/
@@ -65,20 +35,44 @@ int BPF_PROG(sys_enter, struct pt_regs* regs, long syscall_id) {
6535
#endif
6636
}
6737

68-
/* we convert it here in this way the syscall will be treated exactly as the original one */
38+
/* We convert it here in this way the syscall will be treated exactly as the original one. */
6939
if(syscall_id == socketcall_syscall_id) {
70-
syscall_id = convert_network_syscalls(regs);
40+
int socketcall_call = (int)extract__syscall_argument(regs, 0);
41+
syscall_id = convert_socketcall_call_to_syscall_id(socketcall_call);
7142
if(syscall_id == -1) {
7243
// We can't do anything since modern bpf filler jump table is syscall indexed
7344
return 0;
7445
}
7546
}
7647

48+
// The following system calls are already handled by TOCTOU mitigation programs and will not
49+
// have an entry in the syscall enter tail table, so simply return early, avoiding wasting
50+
// resources on any additional filtering logic.
51+
switch(syscall_id) {
52+
#if defined(__NR_connect) || defined(__NR_creat) || defined(__NR_open) || defined(__NR_openat)
53+
#ifdef __NR_connect
54+
case __NR_connect:
55+
#endif // __NR_connect
56+
#ifdef __NR_creat
57+
case __NR_creat:
58+
#endif // __NR_creat
59+
#ifdef __NR_open
60+
case __NR_open:
61+
#endif // __NR_open
62+
#ifdef __NR_openat
63+
case __NR_openat:
64+
#endif // __NR_openat
65+
return 0;
66+
#endif // __NR_connect ||__NR_creat || __NR_open || __NR_openat
67+
default:
68+
break;
69+
}
70+
7771
if(!syscalls_dispatcher__64bit_interesting_syscall(syscall_id)) {
7872
return 0;
7973
}
8074

81-
if(sampling_logic_enter(ctx, syscall_id)) {
75+
if(syscalls_dispatcher__sampling_logic_enter(syscall_id)) {
8276
return 0;
8377
}
8478

driver/modern_bpf/programs/attached/dispatchers/syscall_exit.bpf.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,10 @@ int BPF_PROG(sys_exit, struct pt_regs *regs, long ret) {
207207
#endif
208208
}
209209

210-
/* we convert it here in this way the syscall will be treated exactly as the original one */
210+
/* We convert it here in this way the syscall will be treated exactly as the original one. */
211211
if(syscall_id == socketcall_syscall_id) {
212-
syscall_id = convert_network_syscalls(regs);
212+
int socketcall_call = (int)extract__syscall_argument(regs, 0);
213+
syscall_id = convert_socketcall_call_to_syscall_id(socketcall_call);
213214
if(syscall_id == -1) {
214215
// We can't do anything since modern bpf filler jump table is syscall indexed
215216
return 0;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# TOCTOU mitigation programs
2+
3+
eBPF programs in this folder generates enter events providing support to exit events data for TOCTOU mitigation.
4+
5+
All programs are attached to specific `tracepoint/syscalls/sys_enter_*` hooks.
6+
7+
For each system call `<syscall>`, two eBPF tracepoint programs are defined:
8+
- `ttm_<syscall>_e` - this program is responsible for tail calling the `<syscall>_e` program after having performed
9+
syscall ID normalization and applied any required sampling/filtering logic
10+
- `<syscall>_e` - this program is responsible for collecting the information needed to generate the proper enter event
11+
and sending the generated event to userspace
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
/*
3+
* Copyright (C) 2025 The Falco Authors.
4+
*
5+
* This file is dual licensed under either the MIT or GPL 2. See MIT.txt
6+
* or GPL2.txt for full copies of the license.
7+
*/
8+
9+
#include <helpers/interfaces/toctou_mitigation.h>
10+
11+
// This struct is defined according to the following format file:
12+
// /sys/kernel/tracing/events/syscalls/sys_enter_connect/format
13+
struct sys_enter_connect_args {
14+
uint64_t pad1;
15+
16+
uint32_t __syscall_nr;
17+
uint32_t pad2;
18+
uint64_t fd;
19+
uint64_t uservaddr;
20+
uint64_t addrlen;
21+
};
22+
23+
/*=============================== ENTER DISPATCHER ===========================*/
24+
25+
SEC("tracepoint/syscalls/sys_enter_connect")
26+
int connect_e(struct sys_enter_connect_args* ctx) {
27+
return toctou_mitigation__call_prog(ctx, ctx->__syscall_nr, -1, TTM_CONNECT_E);
28+
}
29+
30+
/*=============================== ENTER DISPATCHER ===========================*/
31+
32+
/*=============================== ENTER EVENT ===========================*/
33+
34+
SEC("tracepoint/syscalls/sys_enter_connect")
35+
int ttm_connect_e(struct sys_enter_connect_args* ctx) {
36+
struct auxiliary_map* auxmap = auxmap__get();
37+
if(!auxmap) {
38+
return 0;
39+
}
40+
41+
/* Extract syscall arguments from context. */
42+
int64_t socket_fd = (int64_t)(int32_t)ctx->fd;
43+
unsigned long usrsockaddr = (unsigned long)ctx->uservaddr;
44+
uint16_t usrsockaddr_len = (uint16_t)ctx->addrlen;
45+
46+
toctou_mitigation__push_connect_enter_event(auxmap, socket_fd, usrsockaddr, usrsockaddr_len);
47+
return 0;
48+
}
49+
50+
/*=============================== ENTER EVENT ===========================*/
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
/*
3+
* Copyright (C) 2025 The Falco Authors.
4+
*
5+
* This file is dual licensed under either the MIT or GPL 2. See MIT.txt
6+
* or GPL2.txt for full copies of the license.
7+
*/
8+
9+
#include <helpers/interfaces/toctou_mitigation.h>
10+
#include <helpers/interfaces/variable_size_event.h>
11+
12+
// This struct is defined according to the following format file:
13+
// /sys/kernel/tracing/events/syscalls/sys_enter_creat/format
14+
struct sys_enter_creat_args {
15+
uint64_t pad;
16+
17+
uint32_t __syscall_nr;
18+
uint64_t filename;
19+
uint64_t mode;
20+
};
21+
22+
/*=============================== ENTER DISPATCHER ===========================*/
23+
24+
SEC("tracepoint/syscalls/sys_enter_creat")
25+
int creat_e(struct sys_enter_creat_args* ctx) {
26+
return toctou_mitigation__call_prog(ctx, ctx->__syscall_nr, -1, TTM_CREAT_E);
27+
}
28+
29+
/*=============================== ENTER DISPATCHER ===========================*/
30+
31+
/*=============================== ENTER EVENT ===========================*/
32+
33+
SEC("tracepoint/syscalls/sys_enter_creat")
34+
int ttm_creat_e(struct sys_enter_creat_args* ctx) {
35+
struct auxiliary_map* auxmap = auxmap__get();
36+
if(!auxmap) {
37+
return 0;
38+
}
39+
40+
auxmap__preload_event_header(auxmap, PPME_SYSCALL_CREAT_E);
41+
42+
/*=============================== COLLECT PARAMETERS ===========================*/
43+
44+
/* Parameter 1: name (type: PT_FSPATH) */
45+
unsigned long name_pointer = (unsigned long)ctx->filename;
46+
auxmap__store_charbuf_param(auxmap, name_pointer, MAX_PATH, USER);
47+
48+
/* Parameter 2: mode (type: PT_UINT32) */
49+
unsigned long mode = (unsigned long)ctx->mode;
50+
auxmap__store_u32_param(auxmap, open_modes_to_scap(O_CREAT, mode));
51+
52+
/*=============================== COLLECT PARAMETERS ===========================*/
53+
54+
auxmap__finalize_event_header(auxmap);
55+
56+
auxmap__submit_event(auxmap);
57+
58+
return 0;
59+
}
60+
61+
/*=============================== ENTER EVENT ===========================*/

0 commit comments

Comments
 (0)