Files
ja4-platform/services/ja4ebpf/bpf/uprobe_apache.c
Jacquin Antoine 4a41e31822 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>
2026-04-20 19:49:40 +02:00

150 lines
4.7 KiB
C

/* uprobe_apache.c — Capture HTTP depuis Apache httpd via apr_socket_recv
*
* 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.
*
* ============================================================================
*/
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_types.h"
/* 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 */
};
/* 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");
/* ============================================================================
* 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;
}
/* Récupérer les arguments depuis pt_regs (x86_64)
* rdi = sock, rsi = buf, rdx = len
*/
struct apr_socket_recv_args args = {};
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;
}
/* ============================================================================
* 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;
__u8 *enabled = bpf_map_lookup_elem(&apache_http_pid_map, &pid);
if (!enabled || *enabled == 0) {
return 0;
}
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);
/* 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;
}
/* 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(&apr_socket_recv_args_map, &pid_tgid);
return 0;
}
/* Initialiser l'événement */
e->pid_tgid = pid_tgid;
e->fd = 0;
e->src_ip = 0;
e->src_port = 0;
e->timestamp_ns = bpf_ktime_get_ns();
e->method_len = 0;
e->uri_len = 0;
e->query_len = 0;
e->body_len = 0;
e->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)) {
copy_len = sizeof(e->data);
}
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(&apr_socket_recv_args_map, &pid_tgid);
return 0;
}
char LICENSE[] SEC("license") = "GPL";