Skip to content

Commit

Permalink
Merge pull request #17 from hashicorp/fix/read-race
Browse files Browse the repository at this point in the history
Fix logical race caused by read-buffer sharing
  • Loading branch information
banks authored Jan 23, 2023
2 parents 5c103f2 + 1cab18f commit da16d5b
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 3 deletions.
6 changes: 4 additions & 2 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ type Codec interface {

// Decode a log from the passed byte slice into the log entry pointed to. This
// allows the caller to manage allocation and re-use of the bytes and log
// entry.
// entry. The resulting raft.Log MUST NOT reference data in the input byte
// slice since the input byte slice may be re-used for
Decode([]byte, *raft.Log) error
}

Expand Down Expand Up @@ -148,7 +149,8 @@ func (d *decoder) bytes() []byte {
d.err = io.ErrShortBuffer
return nil
}
bs := d.buf[:n]
bs := make([]byte, n)
copy(bs, d.buf[:n])
d.buf = d.buf[n:]
return bs
}
Expand Down
33 changes: 33 additions & 0 deletions codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,36 @@ func TestBinaryCodecFuzz(t *testing.T) {
require.Equal(t, log, log2)
}
}

func TestBinaryCodecCopysOnDecode(t *testing.T) {
var in, out raft.Log

in.Index = 1234
in.Term = 2
in.Type = raft.LogCommand
in.Data = []byte("foo")
in.Extensions = []byte("ext")

c := BinaryCodec{}
var buf bytes.Buffer
require.NoError(t, c.Encode(&in, &buf))

rawBytes := buf.Bytes()

require.NoError(t, c.Decode(rawBytes, &out))

// Make sure the decoded data is the same
require.Equal(t, string(out.Data), "foo")
require.Equal(t, string(out.Extensions), "ext")

// Intentionally mangle the buffer contents
for i := 0; i < len(rawBytes); i++ {
rawBytes[i] = 'x'
}

// Make sure the decoded data is still the same (i.e. didn't refer to the
// underlying bytes)
require.Equal(t, string(out.Data), "foo")
require.Equal(t, string(out.Extensions), "ext")

}
15 changes: 14 additions & 1 deletion wal_stubs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,21 @@ func (s *testSegment) GetLog(idx uint64) (*types.PooledBuffer, error) {
if !ok {
return nil, ErrNotFound
}

// We make a pooled buffer with it's own copy of the log which we then
// invalidate when it's Closed to make sure that any test code that reads data
// from a buffer that was freed fails to read what it expected. This ensures
// our logic about returning slices to users that we might re-use is correct.
buf := make([]byte, len(log.Data))
copy(buf, log.Data)
pb := &types.PooledBuffer{
Bs: log.Data,
Bs: buf,
CloseFn: func() {
closed := []byte{'c', 'l', 'o', 's', 'e', 'd', ' ', 'b', 'u', 'f', 'f', 'e', 'r', '!'}
for i := 0; i < len(buf); i++ {
buf[i] = closed[i%len(closed)]
}
},
}
return pb, nil
}
Expand Down
3 changes: 3 additions & 0 deletions wal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,9 @@ func TestConcurrentReadersAndWriter(t *testing.T) {
if log.Index != idx {
panic(fmt.Errorf("wrong %s log want=%d got=%d", name, idx, log.Index))
}
if string(log.Data) != fmt.Sprintf("Log entry %d", log.Index) {
panic(fmt.Errorf("wrong data in log %d: %s", log.Index, log.Data))
}
return 1
}

Expand Down

0 comments on commit da16d5b

Please sign in to comment.