This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+93 -21
View File
@@ -5,10 +5,28 @@ import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
)
// MaxV2HeaderSize is the maximum accepted value of a v2 header's 16-bit length
// field, i.e. the number of bytes following the fixed 16-byte prefix (address
// block plus TLVs).
//
// The spec allows up to 65535, but parseVersion2 allocates this many bytes
// before reading, so a lower limit mitigates memory-allocation DoS from
// untrusted peers while allowing real-world legitimate headers.
// PP2_SUBTYPE_SSL_CLIENT_CERT (a DER-encoded certificate) is typically between
// 1 and 2KiB, so the 4KiB default leaves room for other TLVs; deployments
// expecting larger headers may raise it.
//
// Like DefaultReadHeaderTimeout, this is a package-level variable to keep it
// easy to override. Set it at program init; it must not be modified
// concurrently with parsing.
var MaxV2HeaderSize uint16 = 4096
var (
lengthUnspec = uint16(0)
lengthV4 = uint16(12)
@@ -62,9 +80,9 @@ type _addrUnix struct {
func parseVersion2(reader *bufio.Reader) (header *Header, err error) {
// Skip first 12 bytes (signature)
for i := 0; i < 12; i++ {
for range 12 {
if _, err = reader.ReadByte(); err != nil {
return nil, ErrCantReadProtocolVersionAndCommand
return nil, fmt.Errorf("%w: %w", ErrCantReadProtocolVersionAndCommand, err)
}
}
@@ -74,7 +92,7 @@ func parseVersion2(reader *bufio.Reader) (header *Header, err error) {
// Read the 13th byte, protocol version and command
b13, err := reader.ReadByte()
if err != nil {
return nil, ErrCantReadProtocolVersionAndCommand
return nil, fmt.Errorf("%w: %w", ErrCantReadProtocolVersionAndCommand, err)
}
header.Command = ProtocolVersionAndCommand(b13)
if _, ok := supportedCommand[header.Command]; !ok {
@@ -84,20 +102,60 @@ func parseVersion2(reader *bufio.Reader) (header *Header, err error) {
// Read the 14th byte, address family and protocol
b14, err := reader.ReadByte()
if err != nil {
return nil, ErrCantReadAddressFamilyAndProtocol
return nil, fmt.Errorf("%w: %w", ErrCantReadAddressFamilyAndProtocol, err)
}
header.TransportProtocol = AddressFamilyAndProtocol(b14)
// UNSPEC is only supported when LOCAL is set.
// Per spec (section 2.2) only the listed address family / transport protocol
// combinations are defined; "other values are unspecified and must not be
// emitted in version 2 of this protocol and must be rejected as invalid by
// receivers". That mandate has no command carve-out, so it applies to LOCAL
// too: the family byte is ignored when interpreting a LOCAL address block,
// but an undefined byte still makes the frame invalid. Rejecting bytes that
// share a known family with an undefined transport — e.g. 0x13 (IPv4
// family, transport 3) — also matters for PROXY: such a byte passes the
// IsIPv4/IsIPv6 checks below, has an address struct read for it, and then
// yields a nil net.Addr from newIPAddr. That nil is stored as
// SourceAddr/DestinationAddr and panics any caller that does
// RemoteAddr().String() (e.g. logging), a remotely triggerable crash.
if !supportedTransportProtocol[header.TransportProtocol] {
return nil, ErrUnsupportedAddressFamilyAndProtocol
}
// UNSPEC carries no address block to trust. For LOCAL it is the family the
// spec expects senders to use; for PROXY the spec leaves it to the receiver
// to accept or reject, and this library rejects it.
if header.TransportProtocol == UNSPEC && header.Command != LOCAL {
return nil, ErrUnsupportedAddressFamilyAndProtocol
}
// Make sure there are bytes available as specified in length
var length uint16
if err := binary.Read(io.LimitReader(reader, 2), binary.BigEndian, &length); err != nil {
return nil, ErrCantReadLength
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
return nil, fmt.Errorf("%w: %w", ErrCantReadLength, err)
}
if !header.validateLength(length) {
// A LOCAL connection is one the proxy opened itself (e.g. health
// checks). Per spec (section 2.2) the receiver must use the real
// connection endpoints (Conn.RemoteAddr/LocalAddr short-circuit on
// IsLocal), must skip exactly `length` bytes, and "must not assume zero
// is presented for LOCAL connections". So a LOCAL frame whose length
// does not fit its declared family's address-block layout — e.g.
// LOCAL + TCPv4 + length 0 — is still valid: skip the block, which the
// spec says to discard, and normalize the header to UNSPEC so it stays
// serializable via Format/WriteTo. A LOCAL frame whose length does fit
// the family layout is instead decoded exactly like a PROXY frame
// below, preserving the (informational) addresses and any trailing
// TLVs, so the header round-trips byte for byte.
if header.Command == LOCAL {
if length > MaxV2HeaderSize {
return nil, ErrInvalidLength
}
if _, err := reader.Discard(int(length)); err != nil {
return nil, fmt.Errorf("%w: %w", ErrInvalidLength, err)
}
header.TransportProtocol = UNSPEC
return header, nil
}
return nil, ErrInvalidLength
}
@@ -107,7 +165,7 @@ func parseVersion2(reader *bufio.Reader) (header *Header, err error) {
return header, nil
}
if _, err := reader.Peek(int(length)); err != nil {
if length > MaxV2HeaderSize {
return nil, ErrInvalidLength
}
@@ -121,26 +179,26 @@ func parseVersion2(reader *bufio.Reader) (header *Header, err error) {
if header.TransportProtocol.IsIPv4() {
var addr _addr4
if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil {
return nil, ErrInvalidAddress
return nil, fmt.Errorf("%w: %w", ErrInvalidAddress, err)
}
header.SourceAddr = newIPAddr(header.TransportProtocol, addr.Src[:], addr.SrcPort)
header.DestinationAddr = newIPAddr(header.TransportProtocol, addr.Dst[:], addr.DstPort)
} else if header.TransportProtocol.IsIPv6() {
var addr _addr6
if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil {
return nil, ErrInvalidAddress
return nil, fmt.Errorf("%w: %w", ErrInvalidAddress, err)
}
header.SourceAddr = newIPAddr(header.TransportProtocol, addr.Src[:], addr.SrcPort)
header.DestinationAddr = newIPAddr(header.TransportProtocol, addr.Dst[:], addr.DstPort)
} else if header.TransportProtocol.IsUnix() {
var addr _addrUnix
if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil {
return nil, ErrInvalidAddress
return nil, fmt.Errorf("%w: %w", ErrInvalidAddress, err)
}
network := "unix"
network := networkUnix
if header.TransportProtocol.IsDatagram() {
network = "unixgram"
network = networkUnixgram
}
header.SourceAddr = &net.UnixAddr{
@@ -160,6 +218,10 @@ func parseVersion2(reader *bufio.Reader) (header *Header, err error) {
return nil, err
}
if payloadReader.N != 0 {
return nil, ErrInvalidLength
}
return header, nil
}
@@ -196,7 +258,11 @@ func (header *Header) formatVersion2() ([]byte, error) {
addrSrc = sourceIP.To16()
addrDst = destIP.To16()
} else if header.TransportProtocol.IsUnix() {
buf.Write(lengthUnixBytes)
hdrLen, err := addTLVLen(lengthUnixBytes, len(header.rawTLVs))
if err != nil {
return nil, err
}
buf.Write(hdrLen)
sourceAddr, destAddr, ok := header.UnixAddrs()
if !ok {
return nil, ErrInvalidAddress
@@ -212,11 +278,16 @@ func (header *Header) formatVersion2() ([]byte, error) {
buf.Write(addrDst)
if sourcePort, destPort, ok := header.Ports(); ok {
if sourcePort < 0 || sourcePort > math.MaxUint16 || destPort < 0 || destPort > math.MaxUint16 {
return nil, ErrInvalidPortNumber
}
portBytes := make([]byte, 2)
//nolint:gosec // Bounds are checked above.
binary.BigEndian.PutUint16(portBytes, uint16(sourcePort))
buf.Write(portBytes)
//nolint:gosec // Bounds are checked above.
binary.BigEndian.PutUint16(portBytes, uint16(destPort))
buf.Write(portBytes)
}
@@ -253,6 +324,7 @@ func addTLVLen(cur []byte, tlvLen int) ([]byte, error) {
return nil, errUint16Overflow
}
a := make([]byte, 2)
//nolint:gosec // newLen bounds are validated above.
binary.BigEndian.PutUint16(a, uint16(newLen))
return a, nil
}
@@ -260,19 +332,19 @@ func addTLVLen(cur []byte, tlvLen int) ([]byte, error) {
func newIPAddr(transport AddressFamilyAndProtocol, ip net.IP, port uint16) net.Addr {
if transport.IsStream() {
return &net.TCPAddr{IP: ip, Port: int(port)}
} else if transport.IsDatagram() {
return &net.UDPAddr{IP: ip, Port: int(port)}
} else {
return nil
}
if transport.IsDatagram() {
return &net.UDPAddr{IP: ip, Port: int(port)}
}
return nil
}
func parseUnixName(b []byte) string {
i := bytes.IndexByte(b, 0)
if i < 0 {
before, _, ok := bytes.Cut(b, []byte{0})
if !ok {
return string(b)
}
return string(b[:i])
return string(before)
}
func formatUnixName(name string) []byte {