Skip to content

Commit

Permalink
Merge pull request #11793 from gyuho/fix
Browse files Browse the repository at this point in the history
wal: check out of range slice in "ReadAll", entry limits in "decodeRecord"
  • Loading branch information
spzala committed Apr 23, 2020
2 parents 2369cb3 + c716d76 commit f4b650b
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 8 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG-3.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
- Remove [redundant storage restore operation to shorten the startup time](https://github.com/etcd-io/etcd/pull/11779).
- With 40 million key test data,it can shorten the startup time from 5 min to 2.5 min.


### Package `embed`

- Remove [`embed.Config.Debug`](https://github.com/etcd-io/etcd/pull/10947).
Expand Down Expand Up @@ -133,6 +132,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
### Package `wal`

- Add [`etcd_wal_write_bytes_total`](https://github.com/etcd-io/etcd/pull/11738).
- Handle [out-of-range slice bound in `ReadAll` and entry limit in `decodeRecord`](https://github.com/etcd-io/etcd/pull/11793).

### etcdctl v3

Expand Down
8 changes: 8 additions & 0 deletions wal/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ func (d *decoder) decode(rec *walpb.Record) error {
return d.decodeRecord(rec)
}

// raft max message size is set to 1 MB in etcd server
// assume projects set reasonable message size limit,
// thus entry size should never exceed 10 MB
const maxWALEntrySizeLimit = int64(10 * 1024 * 1024)

func (d *decoder) decodeRecord(rec *walpb.Record) error {
if len(d.brs) == 0 {
return io.EOF
Expand All @@ -79,6 +84,9 @@ func (d *decoder) decodeRecord(rec *walpb.Record) error {
}

recBytes, padBytes := decodeFrameSize(l)
if recBytes >= maxWALEntrySizeLimit-padBytes {
return ErrMaxWALEntrySizeLimitExceeded
}

data := make([]byte, recBytes+padBytes)
if _, err = io.ReadFull(d.brs[0], data); err != nil {
Expand Down
23 changes: 16 additions & 7 deletions wal/wal.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ var (
// so that tests can set a different segment size.
SegmentSizeBytes int64 = 64 * 1000 * 1000 // 64MB

ErrMetadataConflict = errors.New("wal: conflicting metadata found")
ErrFileNotFound = errors.New("wal: file not found")
ErrCRCMismatch = errors.New("wal: crc mismatch")
ErrSnapshotMismatch = errors.New("wal: snapshot mismatch")
ErrSnapshotNotFound = errors.New("wal: snapshot not found")
crcTable = crc32.MakeTable(crc32.Castagnoli)
ErrMetadataConflict = errors.New("wal: conflicting metadata found")
ErrFileNotFound = errors.New("wal: file not found")
ErrCRCMismatch = errors.New("wal: crc mismatch")
ErrSnapshotMismatch = errors.New("wal: snapshot mismatch")
ErrSnapshotNotFound = errors.New("wal: snapshot not found")
ErrSliceOutOfRange = errors.New("wal: slice bounds out of range")
ErrMaxWALEntrySizeLimitExceeded = errors.New("wal: max entry size limit exceeded")
crcTable = crc32.MakeTable(crc32.Castagnoli)
)

// WAL is a logical representation of the stable storage.
Expand Down Expand Up @@ -411,8 +413,15 @@ func (w *WAL) ReadAll() (metadata []byte, state raftpb.HardState, ents []raftpb.
switch rec.Type {
case entryType:
e := mustUnmarshalEntry(rec.Data)
// 0 <= e.Index-w.start.Index - 1 < len(ents)
if e.Index > w.start.Index {
ents = append(ents[:e.Index-w.start.Index-1], e)
// prevent "panic: runtime error: slice bounds out of range [:13038096702221461992] with capacity 0"
up := e.Index - w.start.Index - 1
if up > uint64(len(ents)) {
// return error before append call causes runtime panic
return nil, state, nil, ErrSliceOutOfRange
}
ents = append(ents[:up], e)
}
w.enti = e.Index

Expand Down
29 changes: 29 additions & 0 deletions wal/wal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,35 @@ func TestOpenForRead(t *testing.T) {
}
}

func TestOpenWithMaxIndex(t *testing.T) {
p, err := ioutil.TempDir(os.TempDir(), "waltest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(p)
// create WAL
w, err := Create(zap.NewExample(), p, nil)
if err != nil {
t.Fatal(err)
}
defer w.Close()

es := []raftpb.Entry{{Index: uint64(math.MaxInt64)}}
if err = w.Save(raftpb.HardState{}, es); err != nil {
t.Fatal(err)
}
w.Close()

w, err = Open(zap.NewExample(), p, walpb.Snapshot{})
if err != nil {
t.Fatal(err)
}
_, _, _, err = w.ReadAll()
if err == nil || err != ErrSliceOutOfRange {
t.Fatalf("err = %v, want ErrSliceOutOfRange", err)
}
}

func TestSaveEmpty(t *testing.T) {
var buf bytes.Buffer
var est raftpb.HardState
Expand Down

0 comments on commit f4b650b

Please sign in to comment.