Skip to content
This repository was archived by the owner on Jun 12, 2022. It is now read-only.

Commit e57f9a1

Browse files
committed
🚓 Shadowsocks 2022 TCP server: reject requests whose first payload chunk contains neither padding nor initial payload
1 parent fc207cb commit e57f9a1

File tree

3 files changed

+88
-59
lines changed

3 files changed

+88
-59
lines changed

‎service/tcp.go‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ func (s *tcpService) handleConnection(clientTCPConn tfo.Conn) {
278278
}
279279

280280
ssr := ss.NewShadowsocksReader(clientReader, cipherEntry.Cipher)
281-
tgtAddr, err := ss.ParseTCPReqHeader(ssr, cipherEntry.Cipher.Config(), ss.HeaderTypeClientStream)
281+
tgtAddr, initPayload, err := ss.ParseTCPReqHeader(ssr, cipherEntry.Cipher.Config(), ss.HeaderTypeClientStream)
282282
if err != nil {
283283
logger.Warn("Failed to parse header",
284284
zap.Stringer("clientConnLocalAddress", clientLocalAddr),
@@ -343,6 +343,20 @@ func (s *tcpService) handleConnection(clientTCPConn tfo.Conn) {
343343
return onet.NewConnectionError("ERR_CREATE_SS_WRITER", "Failed to create Shadowsocks writer", err)
344344
}
345345

346+
// Write initial payload.
347+
if len(initPayload) > 0 {
348+
_, err = tgtConn.Write(initPayload)
349+
if err != nil {
350+
logger.Warn("Failed to write initial payload client -> target",
351+
zap.Stringer("clientConnLocalAddress", clientLocalAddr),
352+
zap.Stringer("clientConnRemoteAddress", clientRemoteAddr),
353+
zap.String("targetAddress", tgtAddr),
354+
zap.Error(err),
355+
)
356+
return onet.NewConnectionError("ERR_WRITE_INIT_PAYLOAD", "Failed to write initial payload", err)
357+
}
358+
}
359+
346360
fromClientErrCh := make(chan error)
347361
go func() {
348362
_, fromClientErr := ssr.WriteTo(tgtConn)

‎shadowsocks/header.go‎

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strconv"
1212
"time"
1313

14-
"github.com/Shadowsocks-NET/outline-ss-server/slicepool"
1514
"github.com/Shadowsocks-NET/outline-ss-server/socks"
1615
)
1716

@@ -36,78 +35,79 @@ const (
3635
)
3736

3837
var (
39-
ErrBadTimestamp = errors.New("time diff is over 30 seconds")
40-
ErrTypeMismatch = errors.New("header type mismatch")
41-
ErrPaddingLengthOutOfRange = errors.New("padding length is less than 0 or greater than 900")
42-
ErrClientSaltMismatch = errors.New("client salt in response header does not match request")
43-
ErrSessionIDMismatch = errors.New("unexpected session ID")
44-
45-
tcpReqHeaderPool = slicepool.MakePool(TCPReqHeaderMaxLength)
38+
ErrIncompleteHeaderInFirstChunk = errors.New("header in first chunk is missing or incomplete")
39+
ErrPaddingExceedChunkBorder = errors.New("padding in first chunk is shorter than advertised")
40+
ErrBadTimestamp = errors.New("time diff is over 30 seconds")
41+
ErrTypeMismatch = errors.New("header type mismatch")
42+
ErrPaddingLengthOutOfRange = errors.New("padding length is less than 0 or greater than 900")
43+
ErrClientSaltMismatch = errors.New("client salt in response header does not match request")
44+
ErrSessionIDMismatch = errors.New("unexpected session ID")
4645
)
4746

48-
func ParseTCPReqHeader(r io.Reader, cipherConfig CipherConfig, htype byte) (string, error) {
49-
if !cipherConfig.IsSpec2022 {
50-
a, err := socks.AddrFromReader(r)
51-
if err != nil {
52-
return "", err
53-
}
54-
return a.String(), nil
47+
// ParseTCPReqHeader reads the first payload chunk, validates the header,
48+
// and returns the target address, initial payload, or an error.
49+
//
50+
// For Shadowsocks 2022, the first payload chunk MUST contain
51+
// either a non-zero-length padding or initial payload, or both (not recommended client behavior but allowed).
52+
// If the padding length is 0 and there's no initial payload, an error is returned.
53+
func ParseTCPReqHeader(r Reader, cipherConfig CipherConfig, htype byte) (string, []byte, error) {
54+
// Read first payload chunk.
55+
if err := r.EnsureLeftover(); err != nil {
56+
return "", nil, err
5557
}
58+
b := r.LeftoverZeroCopy()
5659

57-
lazySlice := tcpReqHeaderPool.LazySlice()
58-
b := lazySlice.Acquire()
59-
defer lazySlice.Release()
60+
var offset int
6061

61-
// Read type & timestamp
62-
_, err := io.ReadFull(r, b[:1+8])
63-
if err != nil {
64-
return "", fmt.Errorf("failed to read type and timestamp: %w", err)
65-
}
62+
if cipherConfig.IsSpec2022 {
63+
if len(b) < 1+8 {
64+
return "", nil, ErrIncompleteHeaderInFirstChunk
65+
}
6666

67-
// Verify type
68-
if b[0] != htype {
69-
return "", ErrTypeMismatch
70-
}
67+
// Verify type
68+
if b[0] != htype {
69+
return "", nil, ErrTypeMismatch
70+
}
7171

72-
// Verify timestamp
73-
epoch := int64(binary.BigEndian.Uint64(b[1 : 1+8]))
74-
nowEpoch := time.Now().Unix()
75-
diff := epoch - nowEpoch
76-
if diff < -30 || diff > 30 {
77-
return "", ErrBadTimestamp
78-
}
72+
// Verify timestamp
73+
epoch := int64(binary.BigEndian.Uint64(b[1 : 1+8]))
74+
nowEpoch := time.Now().Unix()
75+
diff := epoch - nowEpoch
76+
if diff < -30 || diff > 30 {
77+
return "", nil, ErrBadTimestamp
78+
}
7979

80-
offset := 1 + 8
80+
offset = 1 + 8
81+
}
8182

8283
// Read socks address
83-
n, err := socks.ReadAddr(b[offset:], r)
84+
socksaddr, err := socks.SplitAddr(b[offset:])
8485
if err != nil {
85-
return "", fmt.Errorf("failed to read socks address: %w", err)
86+
return "", nil, fmt.Errorf("failed to read socks address: %w", err)
8687
}
87-
socksaddr := socks.Addr(b[offset : offset+n])
88-
offset += n
88+
offset += len(socksaddr)
8989

90-
// Read padding length
91-
_, err = io.ReadFull(r, b[offset:offset+2])
92-
if err != nil {
93-
return "", fmt.Errorf("failed to read padding length: %w", err)
94-
}
90+
if cipherConfig.IsSpec2022 {
91+
// Make sure the remaining length > 2 (padding length + either padding or payload)
92+
if len(b)-offset <= 2 {
93+
return "", nil, ErrIncompleteHeaderInFirstChunk
94+
}
9595

96-
// Verify padding length
97-
paddingLen := int(binary.BigEndian.Uint16(b[offset : offset+2]))
98-
if paddingLen < MinPaddingLength || paddingLen > MaxPaddingLength {
99-
return "", ErrPaddingLengthOutOfRange
100-
}
96+
// Verify padding length
97+
paddingLen := int(binary.BigEndian.Uint16(b[offset : offset+2]))
98+
if paddingLen < MinPaddingLength || paddingLen > MaxPaddingLength {
99+
return "", nil, ErrPaddingLengthOutOfRange
100+
}
101+
offset += 2
101102

102-
// Read padding
103-
if paddingLen > 0 {
104-
_, err := io.ReadFull(r, b[offset+2:offset+2+paddingLen])
105-
if err != nil {
106-
return "", fmt.Errorf("failed to read padding: %w", err)
103+
// Skip padding.
104+
offset += paddingLen
105+
if offset >= len(b) {
106+
return "", nil, ErrPaddingExceedChunkBorder
107107
}
108108
}
109109

110-
return socksaddr.String(), nil
110+
return socksaddr.String(), b[offset:], nil
111111
}
112112

113113
func WriteTCPReqHeader(dst, socksaddr []byte, addPadding bool, cipherConfig CipherConfig) (n int) {

‎shadowsocks/stream.go‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,16 @@ type Reader interface {
443443
io.Reader
444444
io.WriterTo
445445

446+
// Salt returns the salt used by this instance to derive the subkey.
446447
Salt() []byte
448+
449+
// EnsureLeftover makes sure that the leftover slice is not nil.
450+
// If it's nil, a read is attempted and the result is returned.
451+
EnsureLeftover() error
452+
453+
// LeftoverZeroCopy returns the leftover slice without copying.
454+
// The content of the returned slice won't change until next read.
455+
LeftoverZeroCopy() []byte
447456
}
448457

449458
// readConverter adapts from ChunkReader, with source-controlled
@@ -479,8 +488,14 @@ func (c *readConverter) Salt() []byte {
479488
return c.cr.Salt()
480489
}
481490

491+
func (c *readConverter) LeftoverZeroCopy() (leftover []byte) {
492+
leftover = c.leftover
493+
c.leftover = nil
494+
return
495+
}
496+
482497
func (c *readConverter) Read(b []byte) (int, error) {
483-
if err := c.ensureLeftover(); err != nil {
498+
if err := c.EnsureLeftover(); err != nil {
484499
return 0, err
485500
}
486501
n := copy(b, c.leftover)
@@ -490,7 +505,7 @@ func (c *readConverter) Read(b []byte) (int, error) {
490505

491506
func (c *readConverter) WriteTo(w io.Writer) (written int64, err error) {
492507
for {
493-
if err = c.ensureLeftover(); err != nil {
508+
if err = c.EnsureLeftover(); err != nil {
494509
if err == io.EOF {
495510
err = nil
496511
}
@@ -508,7 +523,7 @@ func (c *readConverter) WriteTo(w io.Writer) (written int64, err error) {
508523
// Ensures that c.leftover is nonempty. If leftover is empty, this method
509524
// waits for incoming data and decrypts it.
510525
// Returns an error only if c.leftover could not be populated.
511-
func (c *readConverter) ensureLeftover() error {
526+
func (c *readConverter) EnsureLeftover() error {
512527
if len(c.leftover) > 0 {
513528
return nil
514529
}

0 commit comments

Comments
 (0)