Skip to content

Commit

Permalink
THRIFT-5294: Fix panic in go TSimpleJSONProtocol
Browse files Browse the repository at this point in the history
Client: go

In go library's TSimpleJSONProtocol and TJSONProtocol implementations,
we use slices as stacks for context info, but didn't do proper boundary
check when peeking/popping, result in it might panic with using -1 as
slice index in certain cases of calling Write*End without matching
Write*Begin before.

Refactor the code to properly implement the stack, and return a
TProtocolException instead on those cases.

Also add unit tests for all protocols. The unit tests shown that
TCompactProtocol.[Read|Write]StructEnd would also panic with unmatched
Begin calls, so fix them as well.
  • Loading branch information
fishy committed Oct 14, 2020
1 parent daf6209 commit 64c2a4b
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 61 deletions.
7 changes: 7 additions & 0 deletions lib/go/thrift/compact_protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package thrift
import (
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
Expand Down Expand Up @@ -158,6 +159,9 @@ func (p *TCompactProtocol) WriteStructBegin(ctx context.Context, name string) er
// this as an opportunity to pop the last field from the current struct off
// of the field stack.
func (p *TCompactProtocol) WriteStructEnd(ctx context.Context) error {
if len(p.lastField) <= 0 {
return NewTProtocolExceptionWithType(INVALID_DATA, errors.New("WriteStructEnd called without matching WriteStructBegin call before"))
}
p.lastFieldId = p.lastField[len(p.lastField)-1]
p.lastField = p.lastField[:len(p.lastField)-1]
return nil
Expand Down Expand Up @@ -386,6 +390,9 @@ func (p *TCompactProtocol) ReadStructBegin(ctx context.Context) (name string, er
// this struct from the field stack.
func (p *TCompactProtocol) ReadStructEnd(ctx context.Context) error {
// consume the last field we read off the wire.
if len(p.lastField) <= 0 {
return NewTProtocolExceptionWithType(INVALID_DATA, errors.New("ReadStructEnd called without matching ReadStructBegin call before"))
}
p.lastFieldId = p.lastField[len(p.lastField)-1]
p.lastField = p.lastField[:len(p.lastField)-1]
return nil
Expand Down
4 changes: 2 additions & 2 deletions lib/go/thrift/json_protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ type TJSONProtocol struct {
// Constructor
func NewTJSONProtocol(t TTransport) *TJSONProtocol {
v := &TJSONProtocol{TSimpleJSONProtocol: NewTSimpleJSONProtocol(t)}
v.parseContextStack = append(v.parseContextStack, int(_CONTEXT_IN_TOPLEVEL))
v.dumpContext = append(v.dumpContext, int(_CONTEXT_IN_TOPLEVEL))
v.parseContextStack.push(_CONTEXT_IN_TOPLEVEL)
v.dumpContext.push(_CONTEXT_IN_TOPLEVEL)
return v
}

Expand Down
4 changes: 4 additions & 0 deletions lib/go/thrift/json_protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,7 @@ func TestWriteJSONProtocolMap(t *testing.T) {
}
trans.Close()
}

func TestTJSONProtocolUnmatchedBeginEnd(t *testing.T) {
UnmatchedBeginEndProtocolTest(t, NewTJSONProtocolFactory())
}
89 changes: 89 additions & 0 deletions lib/go/thrift/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ func ReadWriteProtocolTest(t *testing.T, protocolFactory TProtocolFactory) {
ReadWriteByte(t, p, trans)
trans.Close()
}

t.Run("UnmatchedBeginEnd", func(t *testing.T) {
UnmatchedBeginEndProtocolTest(t, protocolFactory)
})
}

func ReadWriteBool(t testing.TB, p TProtocol, trans TTransport) {
Expand Down Expand Up @@ -515,3 +519,88 @@ func ReadWriteBinary(t testing.TB, p TProtocol, trans TTransport) {
}
}
}

func UnmatchedBeginEndProtocolTest(t *testing.T, protocolFactory TProtocolFactory) {
// NOTE: not all protocol implementations do strict state check to
// return an error on unmatched Begin/End calls.
// This test is only meant to make sure that those unmatched Begin/End
// calls won't cause panic. There's no real "test" here.
trans := NewTMemoryBuffer()
t.Run("Read", func(t *testing.T) {
t.Run("Message", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.ReadMessageEnd(context.Background())
p.ReadMessageEnd(context.Background())
})
t.Run("Struct", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.ReadStructEnd(context.Background())
p.ReadStructEnd(context.Background())
})
t.Run("Field", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.ReadFieldEnd(context.Background())
p.ReadFieldEnd(context.Background())
})
t.Run("Map", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.ReadMapEnd(context.Background())
p.ReadMapEnd(context.Background())
})
t.Run("List", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.ReadListEnd(context.Background())
p.ReadListEnd(context.Background())
})
t.Run("Set", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.ReadSetEnd(context.Background())
p.ReadSetEnd(context.Background())
})
})
t.Run("Write", func(t *testing.T) {
t.Run("Message", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.WriteMessageEnd(context.Background())
p.WriteMessageEnd(context.Background())
})
t.Run("Struct", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.WriteStructEnd(context.Background())
p.WriteStructEnd(context.Background())
})
t.Run("Field", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.WriteFieldEnd(context.Background())
p.WriteFieldEnd(context.Background())
})
t.Run("Map", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.WriteMapEnd(context.Background())
p.WriteMapEnd(context.Background())
})
t.Run("List", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.WriteListEnd(context.Background())
p.WriteListEnd(context.Background())
})
t.Run("Set", func(t *testing.T) {
trans.Reset()
p := protocolFactory.GetProtocol(trans)
p.WriteSetEnd(context.Background())
p.WriteSetEnd(context.Background())
})
})
trans.Close()
}
Loading

0 comments on commit 64c2a4b

Please sign in to comment.