diff --git a/wal/decoder.go b/wal/decoder.go index f2f01fd881c..d007fe1c748 100644 --- a/wal/decoder.go +++ b/wal/decoder.go @@ -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 @@ -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 { diff --git a/wal/wal.go b/wal/wal.go index 0ca9946ebe8..1aced50bdd1 100644 --- a/wal/wal.go +++ b/wal/wal.go @@ -56,13 +56,15 @@ var ( plog = capnslog.NewPackageLogger("go.etcd.io/etcd", "wal") - 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") - ErrDecoderNotFound = errors.New("wal: decoder 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") + ErrDecoderNotFound = errors.New("wal: decoder not found") + crcTable = crc32.MakeTable(crc32.Castagnoli) ) // WAL is a logical representation of the stable storage. @@ -447,8 +449,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 diff --git a/wal/wal_test.go b/wal/wal_test.go index 254ddfbc376..9bdec2b14e5 100644 --- a/wal/wal_test.go +++ b/wal/wal_test.go @@ -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