From 727dd2e21035c7a303b83266c8ad33a5ba0b4d4d Mon Sep 17 00:00:00 2001 From: Rodrigo Bertoldo de Sousa Martins Date: Sun, 13 Sep 2026 21:24:09 -0300 Subject: [PATCH] fixed4 --- xdp/xdp_wg_mtu_optimizer.c | 45 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/xdp/xdp_wg_mtu_optimizer.c b/xdp/xdp_wg_mtu_optimizer.c index 012944d..72f6c20 100644 --- a/xdp/xdp_wg_mtu_optimizer.c +++ b/xdp/xdp_wg_mtu_optimizer.c @@ -43,15 +43,6 @@ struct { __type(value, __u16); // MTU conhecido } path_mtu_map SEC(".maps"); -// Barreira de compilador: impede o clang de "provar" (via -O2) que um -// ponteiro já está implicitamente dentro de um range válido por -// transitividade com outro ponteiro checado antes, e por isso eliminar do -// bytecode um check de bounds que na prática é redundante *matematicamente* -// mas que o verificador do kernel exige ver explicitamente antes de cada -// dereferência. Gotcha clássico de eBPF — aparece até nos selftests do -// próprio kernel para parsing de opções TCP. -#define barrier_var(var) asm volatile("" : "+r"(var)) - // Atualização incremental de checksum de 16 bits (RFC 1071/1624) — a mesma // técnica usada pelo csum_replace2() do kernel. Necessária porque XDP não // tem acesso a helpers de checksum baseados em skb (bpf_l4_csum_replace só @@ -84,19 +75,27 @@ static __always_inline int clamp_tcp_mss(struct tcphdr *tcp, void *data_end, __u if (tcp_hdr_len <= sizeof(struct tcphdr)) return 0; // Sem opções TCP + // Bytes de opções restantes como CONTADOR ESCALAR, não como um segundo + // ponteiro (opt_end) comparado contra data_end. Isso é de propósito: + // se o limite do loop fosse outro ponteiro também verificado contra + // data_end, o clang consegue provar por transitividade que checar + // "opt+N > data_end" é redundante (já que opt < opt_end <= data_end) e + // REMOVE esse check do bytecode — o verificador do kernel então rejeita + // o programa porque, sem o check explícito no bytecode, não há prova + // de que a leitura é segura (viu-se isso na prática: nem barrier_var + // impediu, porque a eliminação é por análise de range de valores, não + // por CSE simples). Um contador inteiro não carrega esse tipo de + // relação com data_end, então cada dereferência só fica "provada seguro" + // pelo check explícito que a precede — que é exatamente o que o + // verificador exige ver. + __u8 opt_bytes_left = tcp_hdr_len - sizeof(struct tcphdr); __u8 *opt = (__u8 *)(tcp + 1); - __u8 *opt_end = (__u8 *)tcp + tcp_hdr_len; - if ((void *)opt_end > data_end) - return 0; - // Loop limitado (verificador do BPF exige bound estático): cabeçalho - // TCP tem no máximo 40 bytes de opções, então 10 iterações cobrem até - // opções de 4 bytes cada — mais que suficiente para chegar no MSS, - // que por convenção vem logo no início das opções. #pragma unroll for (int i = 0; i < 10; i++) { - barrier_var(opt); - if ((void *)(opt + 1) > data_end || opt >= opt_end) + if (opt_bytes_left < 1) + break; + if ((void *)(opt + 1) > data_end) break; __u8 kind = opt[0]; @@ -104,18 +103,19 @@ static __always_inline int clamp_tcp_mss(struct tcphdr *tcp, void *data_end, __u break; if (kind == 1) { // NOP opt += 1; + opt_bytes_left -= 1; continue; } - barrier_var(opt); - if ((void *)(opt + 2) > data_end || opt + 1 >= opt_end) + if (opt_bytes_left < 2) + break; + if ((void *)(opt + 2) > data_end) break; __u8 opt_len = opt[1]; - if (opt_len < 2 || (void *)(opt + opt_len) > data_end || opt + opt_len > opt_end) + if (opt_len < 2 || opt_len > opt_bytes_left) break; if (kind == 2 && opt_len == 4) { // Maximum Segment Size - barrier_var(opt); if ((void *)(opt + 4) > data_end) break; __be16 *mss_ptr = (__be16 *)(opt + 2); @@ -131,6 +131,7 @@ static __always_inline int clamp_tcp_mss(struct tcphdr *tcp, void *data_end, __u } opt += opt_len; + opt_bytes_left -= opt_len; } return 0; }