high · 7.5CVE-2026-46597Jun 25, 2026

CVE-2026-46597: golang.org/x/crypto/ssh AES-GCM Packet Decoder Integer Underflow DoS

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

golang.org/x/crypto/ssh < 0.52.0: a byte-to-uint32 arithmetic underflow in gcmCipher.readCipherPacket lets any client panic an SSH server.

Packagegolang.org/x/crypto/ssh
Ecosystemgo
Affected< 0.52.0
Fixed in0.52.0
CVE-2026-46597: golang.org/x/crypto/ssh AES-GCM Packet Decoder Integer Underflow DoS

The problem

In golang.org/x/crypto/ssh before 0.52.0, gcmCipher.readCipherPacket computes the payload slice as plain[1 : length-uint32(padding)] without first verifying that padding < length. When a crafted AES-GCM SSH packet carries a padding byte whose value exceeds the decrypted payload length, the subtraction wraps to a huge uint32, causing an out-of-bounds slice panic (CWE-681).

Because the decoder runs before authentication is complete, an unauthenticated network peer can crash any server using the affected library.

Proof of concept

bash
# Send a valid AES-GCM SSH handshake, then inject a crafted binary packet where:
# - 4-byte length field  (big-endian) = 0x00000005  (5 bytes of ciphertext payload declared)
# - After AEAD decryption the plaintext is, e.g., 5 bytes: [0xFF, 0x00, 0x00, 0x00, 0x00]
#   plain[0] = 0xFF  --> padding = 255
#   len(plain) = 5   --> length - uint32(padding) = 5 - 255 wraps to 0xFFFFFF06 on uint32
# The slice plain[1 : 0xFFFFFF06] panics immediately with a runtime index out of range.
#
# Minimal Go reproducer (against a local test server):
package main

import (
    "net"
    "golang.org/x/crypto/ssh"
)

func main() {
    config := &ssh.ClientConfig{
        User:            "x",
        Auth:            []ssh.AuthMethod{ssh.Password("x")},
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }
    // Server panics during key-exchange / packet decode
    ssh.Dial("tcp", "target:22", config)
}

The root cause (CWE-681 / incorrect type conversion) is in cipher.go: the expression `plain[1 : length-uint32(padding)]` performs unsigned arithmetic before any bounds check. If `padding` (a byte from the decrypted packet) is larger than `length` (the uint32 wire length), the subtraction underflows to a near-maximal uint32, and Go's runtime slice-bounds check panics the process.

The patch adds `if int(padding+1) >= len(plain) { return nil, fmt.Errorf(...) }` *before* the slice expression, performing a signed integer comparison that safely catches both the underflow case and the zero-payload case. Because the decoder is invoked during key exchange, the panic is reachable pre-authentication.

The fix

Upgrade golang.org/x/crypto to v0.52.0 or later (`go get golang.org/x/crypto@v0.52.0`). The fix is in ssh/cipher.go via Gerrit CL 781620; it adds an explicit bounds check on the padding value before computing the payload slice in gcmCipher.readCipherPacket.

Reported by Maciej Kawka.

References: [1][2][3][4][5][6]