This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-13 21:24:09 -03:00
1 parent 6cfca41b07
commit 727dd2e210
1 file changed
+23 -22
+23 -22
View File
@@ -43,15 +43,6 @@ struct {
__type(value, __u16); // MTU conhecido __type(value, __u16); // MTU conhecido
} path_mtu_map SEC(".maps"); } 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 // 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 // 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ó // 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)) if (tcp_hdr_len <= sizeof(struct tcphdr))
return 0; // Sem opções TCP 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 = (__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 #pragma unroll
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
barrier_var(opt); if (opt_bytes_left < 1)
if ((void *)(opt + 1) > data_end || opt >= opt_end) break;
if ((void *)(opt + 1) > data_end)
break; break;
__u8 kind = opt[0]; __u8 kind = opt[0];
@@ -104,18 +103,19 @@ static __always_inline int clamp_tcp_mss(struct tcphdr *tcp, void *data_end, __u
break; break;
if (kind == 1) { // NOP if (kind == 1) { // NOP
opt += 1; opt += 1;
opt_bytes_left -= 1;
continue; continue;
} }
barrier_var(opt); if (opt_bytes_left < 2)
if ((void *)(opt + 2) > data_end || opt + 1 >= opt_end) break;
if ((void *)(opt + 2) > data_end)
break; break;
__u8 opt_len = opt[1]; __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; break;
if (kind == 2 && opt_len == 4) { // Maximum Segment Size if (kind == 2 && opt_len == 4) { // Maximum Segment Size
barrier_var(opt);
if ((void *)(opt + 4) > data_end) if ((void *)(opt + 4) > data_end)
break; break;
__be16 *mss_ptr = (__be16 *)(opt + 2); __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 += opt_len;
opt_bytes_left -= opt_len;
} }
return 0; return 0;
} }