Skip to content

Commit 0942aad

Browse files
authored
fix(cws): register accepted sockets in the flow_pid map (#54663)
### What does this PR do? Registers IPv6 flows in the `flow_pid` map using the socket returned by `accept()`. ### Motivation Until Linux 7.0, the flow for such socket was classified on its first transmit in the `security_sk_classify_flow` hook, so an accepted connection on a wildcard listener was attributed to the process owning it. Linux 7.0 now skips that classification whenever the socket already holds a route, which is always the case for incoming connections, so those flows were left unattributed on that kernel version. ### Describe how you validated your changes Existing functional tests running on Ubuntu 26.04 that this PR fixes. ### Additional Notes IPv4 flows are unaffected as these are still registered as part of the `security_sk_classify_flow` hook. Co-authored-by: yoann.ghigoff <yoann.ghigoff@datadoghq.com>
1 parent 7418217 commit 0942aad

4 files changed

Lines changed: 109 additions & 5 deletions

File tree

pkg/security/ebpf/c/include/helpers/network/utils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,10 @@ __attribute__((always_inline)) void flip(struct flow_t *flow) {
1616
flow->daddr[1] = tmp;
1717
}
1818

19+
// addr holds an in6_addr read as two host-order u64: ::ffff:0:0/96 puts the 0xffff marker in the
20+
// low half of the second one
21+
__attribute__((always_inline)) u8 is_ipv4_mapped_ipv6_addr(u64 *addr) {
22+
return addr[0] == 0 && (addr[1] & 0xffffffff) == 0xffff0000;
23+
}
24+
1925
#endif

pkg/security/ebpf/c/include/hooks/network/accept.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
#define _HOOKS_ACCEPT_H_
33

44
#include "constants/offsets/network.h"
5+
#include "helpers/events.h"
6+
#include "hooks/network/flow.h"
57

68
int __attribute__((always_inline)) read_sock_and_send_event(ctx_t * ctx, struct sock * sock) {
7-
if(sock == NULL) {
8-
return 0;
9-
}
10-
119
struct accept_event_t event = {0};
1210

1311
// Extract family from the socket
@@ -39,7 +37,17 @@ int __attribute__((always_inline)) read_sock_and_send_event(ctx_t * ctx, struct
3937
HOOK_EXIT("inet_csk_accept")
4038
int hook_accept(ctx_t *ctx) {
4139
struct sock *sock = (struct sock*)CTX_PARMRET(ctx);
42-
return read_sock_and_send_event(ctx, sock);
40+
if (sock == NULL || IS_ERR(sock)) {
41+
return 0;
42+
}
43+
44+
// this hook is loaded along with the network probes so that flow_pid stays up to date, hence the
45+
// event itself is only sent when a rule asks for it
46+
if (is_event_enabled(EVENT_ACCEPT)) {
47+
read_sock_and_send_event(ctx, sock);
48+
}
49+
50+
return register_accepted_flow(sock);
4351
}
4452

4553
#endif /* _HOOKS_ACCEPT_H_ */

pkg/security/ebpf/c/include/hooks/network/flow.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define _HOOKS_NETWORK_FLOW_H_
33
#include "constants/offsets/network.h"
44
#include "constants/offsets/netns.h"
5+
#include "helpers/network/parser.h"
56
#include "helpers/network/pid_resolver.h"
67
#include "helpers/network/utils.h"
78
#include "helpers/network/flow.h"
@@ -630,4 +631,90 @@ int rethook_inet6_bind(ctx_t *ctx) {
630631
return handle_inet_bind_ret(ret);
631632
}
632633

634+
__attribute__((always_inline)) int register_connected_flow(struct sock *sk, u64 pid_tgid) {
635+
struct pid_route_t route = {};
636+
637+
route.netns = get_netns_from_sock(sk);
638+
route.l4_protocol = get_protocol_from_sock(sk);
639+
route.port = get_skc_num_from_sock_common((void *)sk);
640+
if (route.port == 0) {
641+
return 0;
642+
}
643+
644+
u16 family = get_family_from_sock_common((void *)sk);
645+
if (family == AF_INET) {
646+
bpf_probe_read(&route.addr, sizeof(sk->__sk_common.skc_rcv_saddr), &sk->__sk_common.skc_rcv_saddr);
647+
} else if (family == AF_INET6) {
648+
bpf_probe_read(&route.addr, sizeof(u64) * 2, &sk->__sk_common.skc_v6_rcv_saddr);
649+
} else {
650+
return 0;
651+
}
652+
653+
struct sock_meta_t *meta = get_sock_meta(sk);
654+
if (meta != NULL) {
655+
struct pid_route_t previous = meta->existing_route;
656+
if (previous.port != 0 || previous.addr[0] != 0 || previous.addr[1] != 0) {
657+
if (can_delete_route(&previous, sk)) {
658+
659+
#if defined(DEBUG_NETWORK_FLOW)
660+
bpf_printk("| flushing route registered before the source address was known:");
661+
print_route(&previous);
662+
#endif
663+
664+
bpf_map_delete_elem(&flow_pid, &previous);
665+
}
666+
}
667+
}
668+
669+
if (!can_delete_route(&route, sk)) {
670+
// we don't want to override the existing entry
671+
return 0;
672+
}
673+
674+
struct pid_route_entry_t value = {};
675+
value.pid = pid_tgid >> 32;
676+
value.type = FLOW_CLASSIFICATION_ENTRY;
677+
value.owner_sk = sk;
678+
bpf_map_update_elem(&flow_pid, &route, &value, BPF_ANY);
679+
680+
if (meta != NULL) {
681+
meta->existing_route = route;
682+
}
683+
684+
if (route.netns != 0) {
685+
u32 tid = (u32)pid_tgid;
686+
bpf_map_update_elem(&netns_cache, &tid, &route.netns, BPF_ANY);
687+
}
688+
689+
#if defined(DEBUG_NETWORK_FLOW)
690+
bpf_printk("register_connected_flow: @:0x%p", sk);
691+
print_route(&route);
692+
print_route_entry(&value);
693+
#endif
694+
695+
return 0;
696+
}
697+
698+
// The socket returned by accept() holds the concrete local address the connection landed on, which
699+
// the BIND_ENTRY of a wildcard listener doesn't cover. Before Linux 7.0 that IPv6 socket was classified
700+
// on its first transmit, causing security_sk_classify_flow to be called and classify the flow.
701+
// Starting with Linux 7.0 that security_sk_classify_flow call is only done on a route miss in inet6_csk_xmit,
702+
// this means that we miss the classification in the hit case.
703+
__attribute__((always_inline)) int register_accepted_flow(struct sock *sk) {
704+
// Only native IPv6 sockets lost that classification with Linux 7.0, IPv4 always reaches
705+
// security_sk_classify_flow, so the flow registration is already handled by the security_sk_classify_flow hook
706+
if (get_family_from_sock_common((void *)sk) != AF_INET6) {
707+
return 0;
708+
}
709+
710+
u64 addr[2] = {};
711+
bpf_probe_read(&addr, sizeof(addr), &sk->__sk_common.skc_v6_rcv_saddr);
712+
// ipv4 mapped addresses already go through the security_sk_classify_flow path
713+
if (is_ipv4_mapped_ipv6_addr(addr)) {
714+
return 0;
715+
}
716+
717+
return register_connected_flow(sk, bpf_get_current_pid_tgid());
718+
}
719+
633720
#endif

pkg/security/ebpf/probes/event_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func NetworkSelectors(hasCgroupSocket bool) []manager.ProbesSelector {
5151
hookFunc("hook_inet_shutdown"),
5252
hookFunc("hook_inet_bind"),
5353
hookFunc("rethook_inet_bind"),
54+
hookFunc("hook_accept"),
5455
hookFunc("hook_sk_common_release"),
5556
hookFunc("hook_path_get"),
5657
hookFunc("hook_proc_fd_link"),
@@ -663,6 +664,8 @@ func GetSelectorsPerEventType(hasFentry, haveIOURing bool) map[eval.EventType][]
663664
}}},
664665

665666
// List of probes required to capture accept events
667+
// hook_accept is also part of NetworkSelectors because it keeps flow_pid up to date, it is
668+
// kept here so that accept events are still captured when network tracking is off
666669
"accept": {
667670
&manager.AllOf{Selectors: []manager.ProbesSelector{
668671
hookFunc("hook_accept"),

0 commit comments

Comments
 (0)