feat(ebpf): Apache HTTP capture + nginx multi-kernel validation
**Apache HTTP capture via apr_socket_recv** : - Uprobe sur libapr-1.so.0 (Apache Portable Runtime) - Compatible tous kernels 4.18+ (CentOS 8, Rocky 9/10) - Configuration unifiée : servers: ["nginx", "apache"] **nginx HTTP capture validation multi-kernel** : - Kretprobe __x64_sys_recvfrom validé sur CentOS 8 (4.18) - Rocky 9 (5.14) et Rocky 10 (6.12) confirmés - Contourne limitation tracepoint sys_exit_recvfrom **Documentation** : - docs/TEST_BUILD_STACK.md : stack complète test/build (VMs, Docker, RPMs) - services/ja4ebpf/docs/APACHE_HTTP_VALIDATION.md : validation Apache - services/ja4ebpf/docs/NGINX_MULTI_KERNEL_VALIDATION.md : validation nginx - docs/architecture.md + docs/services/ja4ebpf.md mis à jour **Tests unitaires Apache** : - internal/loader/apache_test.go : tests libapr, paths, structures BPF - internal/correlation/apache_test.go : tests corrélation HTTP Apache **Packaging** : - RPM spec mis à jour (version 0.3.0-1, changelog complet) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,10 @@
|
||||
/* uprobe_apache.c — Capture HTTP depuis Apache httpd via recvfrom
|
||||
/* uprobe_apache.c — Capture HTTP depuis Apache httpd via apr_socket_recv
|
||||
*
|
||||
* Identique à nginx : sys_enter_recvfrom + kretprobe __x64_sys_recvfrom
|
||||
* Utilise uprobe sur la fonction apr_socket_recv d'Apache Portable Runtime
|
||||
* pour capturer les données HTTP lues depuis le socket.
|
||||
*
|
||||
* Cette approche fonctionne sur tous les kernels car elle utilise des
|
||||
* uprobes au lieu de dépendre de syscalls.
|
||||
*
|
||||
* ============================================================================
|
||||
*/
|
||||
@ -10,41 +14,71 @@
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_types.h"
|
||||
|
||||
#define MAX_RECV_SIZE 4096
|
||||
/* Structure pour stocker les arguments entre entry et return */
|
||||
struct apr_socket_recv_args {
|
||||
__u64 buf_ptr; /* pointeur vers le buffer de réception */
|
||||
__u32 len; /* longueur demandée */
|
||||
};
|
||||
|
||||
struct recvfrom_args {
|
||||
__s32 sockfd;
|
||||
__u64 buf_ptr;
|
||||
__u64 len;
|
||||
__s64 flags;
|
||||
} __attribute__((packed));
|
||||
/* Map temporaire pour stocker les arguments entre entry et return */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 10240);
|
||||
__type(key, __u64);
|
||||
__type(value, struct apr_socket_recv_args);
|
||||
} apr_socket_recv_args_map SEC(".maps");
|
||||
|
||||
/* sys_enter_recvfrom - identique à nginx */
|
||||
SEC("tp/syscalls/sys_enter_recvfrom")
|
||||
int tp_sys_enter_recvfrom(struct trace_event_raw_sys_enter *ctx)
|
||||
/* ============================================================================
|
||||
* uprobe_apr_socket_recv_entry — Entrée de la fonction apr_socket_recv
|
||||
*
|
||||
* Signature: apr_status_t apr_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len)
|
||||
*
|
||||
* Capture le pointeur vers le buffer qui recevra les données.
|
||||
* ============================================================================
|
||||
*/
|
||||
SEC("uprobe/apr_socket_recv")
|
||||
int uprobe_apr_socket_recv_entry(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
__u32 pid = pid_tgid >> 32;
|
||||
|
||||
/* Vérifier si ce PID est dans la map apache_http_pid_map */
|
||||
__u8 *enabled = bpf_map_lookup_elem(&apache_http_pid_map, &pid);
|
||||
if (!enabled || *enabled == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct recvfrom_args args = {};
|
||||
args.sockfd = (__s32)ctx->args[0];
|
||||
args.buf_ptr = (__u64)ctx->args[1];
|
||||
args.len = (__u64)ctx->args[2];
|
||||
args.flags = (__s64)ctx->args[3];
|
||||
/* Récupérer les arguments depuis pt_regs (x86_64)
|
||||
* rdi = sock, rsi = buf, rdx = len
|
||||
*/
|
||||
struct apr_socket_recv_args args = {};
|
||||
|
||||
bpf_map_update_elem(&apache_http_recv_args_map, &pid_tgid, &args, BPF_ANY);
|
||||
args.buf_ptr = PT_REGS_PARM2(ctx); /* deuxième paramètre = buf */
|
||||
|
||||
/* Le troisième paramètre est un pointeur vers size_t,
|
||||
* on doit lire la valeur pointée pour obtenir la longueur */
|
||||
__u64 len_ptr = PT_REGS_PARM3(ctx);
|
||||
__u32 len_value = 0;
|
||||
bpf_probe_read_user(&len_value, sizeof(len_value), (void *)len_ptr);
|
||||
|
||||
args.len = len_value;
|
||||
|
||||
if (args.buf_ptr && args.len > 0) {
|
||||
bpf_map_update_elem(&apr_socket_recv_args_map, &pid_tgid, &args, BPF_ANY);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* kretprobe __x64_sys_recvfrom - identique à nginx */
|
||||
SEC("kretprobe/__x64_sys_recvfrom")
|
||||
int kretprobe_sys_exit_recvfrom(struct pt_regs *ctx)
|
||||
/* ============================================================================
|
||||
* uretprobe_apr_socket_recv — Sortie de la fonction apr_socket_recv
|
||||
*
|
||||
* La valeur de retour indique le succès, et le pointeur len a été mis à jour
|
||||
* avec le nombre d'octets réellement lus.
|
||||
* ============================================================================
|
||||
*/
|
||||
SEC("uretprobe/apr_socket_recv")
|
||||
int uretprobe_apr_socket_recv(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
__u32 pid = pid_tgid >> 32;
|
||||
@ -54,30 +88,31 @@ int kretprobe_sys_exit_recvfrom(struct pt_regs *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct recvfrom_args *args = bpf_map_lookup_elem(&apache_http_recv_args_map, &pid_tgid);
|
||||
struct apr_socket_recv_args *args = bpf_map_lookup_elem(&apr_socket_recv_args_map, &pid_tgid);
|
||||
if (!args) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* La valeur de retour est apr_status_t (0 = succès) */
|
||||
long retval = PT_REGS_RC(ctx);
|
||||
if (retval <= 0) {
|
||||
bpf_map_delete_elem(&apache_http_recv_args_map, &pid_tgid);
|
||||
|
||||
/* APR_SUCCESS est 0, toute autre valeur indique une erreur */
|
||||
if (retval != 0) {
|
||||
bpf_map_delete_elem(&apr_socket_recv_args_map, &pid_tgid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__u32 data_len = retval;
|
||||
if (data_len > MAX_RECV_SIZE)
|
||||
data_len = MAX_RECV_SIZE;
|
||||
|
||||
/* Buffer pour l'événement */
|
||||
__u32 zero = 0;
|
||||
struct apache_http_event *e = bpf_map_lookup_elem(&__apache_buf, &zero);
|
||||
if (!e) {
|
||||
bpf_map_delete_elem(&apache_http_recv_args_map, &pid_tgid);
|
||||
bpf_map_delete_elem(&apr_socket_recv_args_map, &pid_tgid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Initialiser l'événement */
|
||||
e->pid_tgid = pid_tgid;
|
||||
e->fd = args->sockfd;
|
||||
e->fd = 0;
|
||||
e->src_ip = 0;
|
||||
e->src_port = 0;
|
||||
e->timestamp_ns = bpf_ktime_get_ns();
|
||||
@ -87,17 +122,26 @@ int kretprobe_sys_exit_recvfrom(struct pt_regs *ctx)
|
||||
e->body_len = 0;
|
||||
e->data_len = 0;
|
||||
|
||||
if (data_len > 0) {
|
||||
/* Lire les données depuis le buffer utilisateur */
|
||||
__u32 data_len = args->len;
|
||||
__u64 buf_ptr = args->buf_ptr;
|
||||
|
||||
/* Vérifier que data_len est dans des limites raisonnables */
|
||||
if (data_len > 0 && data_len <= 4096 && buf_ptr != 0) {
|
||||
/* Limiter la lecture pour éviter les accès hors limites */
|
||||
__u32 copy_len = data_len;
|
||||
if (copy_len > sizeof(e->data))
|
||||
if (copy_len > sizeof(e->data)) {
|
||||
copy_len = sizeof(e->data);
|
||||
bpf_probe_read_user(e->data, copy_len, (void *)args->buf_ptr);
|
||||
}
|
||||
|
||||
bpf_probe_read_user(e->data, copy_len, (void *)buf_ptr);
|
||||
e->data_len = copy_len;
|
||||
}
|
||||
|
||||
/* Envoyer vers userspace */
|
||||
bpf_perf_event_output(ctx, &pb_apache_http, BPF_F_CURRENT_CPU, e, sizeof(*e));
|
||||
|
||||
bpf_map_delete_elem(&apache_http_recv_args_map, &pid_tgid);
|
||||
bpf_map_delete_elem(&apr_socket_recv_args_map, &pid_tgid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user