Skip to content

Commit a368adc

Browse files
author
Iestyn C. Elfick
committed
fixes for #69
1 parent ac2f466 commit a368adc

File tree

3 files changed

+73
-9
lines changed

3 files changed

+73
-9
lines changed

header.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var (
1616
SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'}
1717
SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'}
1818

19+
ErrCantReadVersion1Header = errors.New("proxyproto: can't read version 1 header")
1920
ErrVersion1HeaderTooLong = errors.New("proxyproto: version 1 header must be 107 bytes or less")
2021
ErrLineMustEndWithCrlf = errors.New("proxyproto: version 1 header is invalid, must end with \\r\\n")
2122
ErrCantReadProtocolVersionAndCommand = errors.New("proxyproto: can't read proxy protocol version and command")

v1.go

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package proxyproto
33
import (
44
"bufio"
55
"bytes"
6+
"fmt"
67
"net"
78
"strconv"
89
"strings"
@@ -22,17 +23,79 @@ func initVersion1() *Header {
2223
}
2324

2425
func parseVersion1(reader *bufio.Reader) (*Header, error) {
25-
// Read until LF shows up, otherwise fail.
26-
// At this point, can't be sure CR precedes LF which will be validated next.
27-
line, err := reader.ReadString('\n')
28-
if err != nil {
29-
return nil, ErrLineMustEndWithCrlf
30-
}
31-
if !strings.HasSuffix(line, crlf) {
26+
//The header cannot be more than 107 bytes long. Per spec:
27+
//
28+
// (...)
29+
// - worst case (optional fields set to 0xff) :
30+
// "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
31+
// => 5 + 1 + 7 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 107 chars
32+
//
33+
// So a 108-byte buffer is always enough to store all the line and a
34+
// trailing zero for string processing.
35+
//
36+
// It must also be CRLF terminated, as above. The header does not otherwise
37+
// contain a CR or LF byte.
38+
//
39+
// ISSUE #69
40+
// We can't use Peek here as it will block trying to fill the buffer, which
41+
// will never happen if the header is TCP4 or TCP6 (max. 56 and 104 bytes
42+
// respectively) and the server is expected to speak first.
43+
//
44+
// Similarly, we can't use ReadString or ReadBytes as these will keep reading
45+
// until the delimiter is found; an abusive client could easily disrupt a
46+
// server by sending a large amount of data that do not contain a LF byte.
47+
// Another means of attack would be to start connections and simply not send
48+
// data after the initial PROXY signature bytes, accumulating a large
49+
// number of blocked goroutines on the server. ReadSlice will also block for
50+
// a delimiter when the internal buffer does not fill up.
51+
//
52+
// A plain Read is also problematic since we risk reading past the end of the
53+
// header without being able to easily put the excess bytes back into the reader's
54+
// buffer (with the current implementation's design).
55+
//
56+
// So we use a ReadByte loop, which solves the overflow problem and avoids
57+
// reading beyond the end of the header. However, we need one more trick to harden
58+
// against partial header attacks (slow loris) - per spec:
59+
//
60+
// (..) The sender must always ensure that the header is sent at once, so that
61+
// the transport layer maintains atomicity along the path to the receiver. The
62+
// receiver may be tolerant to partial headers or may simply drop the connection
63+
// when receiving a partial header. Recommendation is to be tolerant, but
64+
// implementation constraints may not always easily permit this.
65+
//
66+
// We are subject to such implementation constraints. So we return an error if
67+
// the header cannot be fully extracted with a single read of the underlying
68+
// reader.
69+
buf := make([]byte, 0, 107)
70+
for {
71+
b, err := reader.ReadByte()
72+
if err != nil {
73+
return nil, fmt.Errorf(ErrCantReadVersion1Header.Error()+": %v", err)
74+
}
75+
buf = append(buf, b)
76+
if b == '\n' {
77+
// End of header found
78+
break
79+
}
80+
if len(buf) == 107 {
81+
// No delimiter in first 107 bytes
82+
return nil, ErrVersion1HeaderTooLong
83+
}
84+
if reader.Buffered() == 0 {
85+
// Header was not buffered in a single read. Since we can't
86+
// differentiate between genuine slow writers and DoS agents,
87+
// we abort. On healthy networks, this should never happen.
88+
return nil, ErrCantReadVersion1Header
89+
}
90+
}
91+
92+
// Check for CR before LF.
93+
if len(buf) < 2 || buf[len(buf)-2] != '\r' {
3294
return nil, ErrLineMustEndWithCrlf
3395
}
96+
3497
// Check full signature.
35-
tokens := strings.Split(line[:len(line)-2], separator)
98+
tokens := strings.Split(string(buf[:len(buf)-2]), separator)
3699

37100
// Expect at least 2 tokens: "PROXY" and the transport protocol.
38101
if len(tokens) < 2 {

v1_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ var invalidParseV1Tests = []struct {
6464
{
6565
desc: "incomplete signature TCP4",
6666
reader: newBufioReader([]byte("PROXY TCP4 " + IPv4AddressesAndPorts)),
67-
expectedError: ErrLineMustEndWithCrlf,
67+
expectedError: ErrCantReadVersion1Header,
6868
},
6969
{
7070
desc: "TCP6 with IPv4 addresses",

0 commit comments

Comments
 (0)