Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+93
-21
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user