Browse Source

Merge pull request #384 from Zariel/fuzz

fuzz fixes
Ben Hood 10 năm trước cách đây
mục cha
commit
aa933093a7
4 tập tin đã thay đổi với 131 bổ sung7 xóa
  1. 4 0
      .gitignore
  2. 52 7
      frame.go
  3. 42 0
      frame_test.go
  4. 33 0
      fuzz.go

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+gocql-fuzz
+fuzz-corpus
+fuzz-work
+gocql.test

+ 52 - 7
frame.go

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"net"
+	"runtime"
 	"sync"
 	"time"
 )
@@ -328,6 +329,10 @@ func (f *framer) trace() {
 
 // reads a frame form the wire into the framers buffer
 func (f *framer) readFrame(head *frameHeader) error {
+	if head.length < 0 {
+		return fmt.Errorf("frame body length can not be less than 0: %d", head.length)
+	}
+
 	if cap(f.readBuffer) >= head.length {
 		f.rbuf = f.readBuffer[:head.length]
 	} else {
@@ -356,7 +361,7 @@ func (f *framer) readFrame(head *frameHeader) error {
 	return nil
 }
 
-func (f *framer) parseFrame() (frame, error) {
+func (f *framer) parseFrame() (frame frame, err error) {
 	if f.header.version.request() {
 		return nil, NewErrProtocol("got a request frame from server: %v", f.header.version)
 	}
@@ -365,10 +370,14 @@ func (f *framer) parseFrame() (frame, error) {
 		f.readTrace()
 	}
 
-	var (
-		frame frame
-		err   error
-	)
+	defer func() {
+		if r := recover(); r != nil {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			}
+			err = r.(error)
+		}
+	}()
 
 	// asumes that the frame body has been read into rbuf
 	switch f.header.op {
@@ -390,7 +399,7 @@ func (f *framer) parseFrame() (frame, error) {
 		return nil, NewErrProtocol("unknown op in frame header: %s", f.header.op)
 	}
 
-	return frame, err
+	return
 }
 
 func (f *framer) parseErrorFrame() frame {
@@ -1151,18 +1160,28 @@ func (f *framer) readByte() byte {
 }
 
 func (f *framer) readInt() (n int) {
+	if len(f.rbuf) < 4 {
+		panic(fmt.Errorf("not enough bytes in buffer to read int require 4 got: %d", len(f.rbuf)))
+	}
+
 	n = int(int32(f.rbuf[0])<<24 | int32(f.rbuf[1])<<16 | int32(f.rbuf[2])<<8 | int32(f.rbuf[3]))
 	f.rbuf = f.rbuf[4:]
 	return
 }
 
 func (f *framer) readShort() (n uint16) {
+	if len(f.rbuf) < 2 {
+		panic(fmt.Errorf("not enough bytes in buffer to read short require 2 got: %d", len(f.rbuf)))
+	}
 	n = uint16(f.rbuf[0])<<8 | uint16(f.rbuf[1])
 	f.rbuf = f.rbuf[2:]
 	return
 }
 
 func (f *framer) readLong() (n int64) {
+	if len(f.rbuf) < 8 {
+		panic(fmt.Errorf("not enough bytes in buffer to read long require 8 got: %d", len(f.rbuf)))
+	}
 	n = int64(f.rbuf[0])<<56 | int64(f.rbuf[1])<<48 | int64(f.rbuf[2])<<40 | int64(f.rbuf[3])<<32 |
 		int64(f.rbuf[4])<<24 | int64(f.rbuf[5])<<16 | int64(f.rbuf[6])<<8 | int64(f.rbuf[7])
 	f.rbuf = f.rbuf[8:]
@@ -1171,6 +1190,11 @@ func (f *framer) readLong() (n int64) {
 
 func (f *framer) readString() (s string) {
 	size := f.readShort()
+
+	if len(f.rbuf) < int(size) {
+		panic(fmt.Errorf("not enough bytes in buffer to read string require %d got: %d", size, len(f.rbuf)))
+	}
+
 	s = string(f.rbuf[:size])
 	f.rbuf = f.rbuf[size:]
 	return
@@ -1178,12 +1202,21 @@ func (f *framer) readString() (s string) {
 
 func (f *framer) readLongString() (s string) {
 	size := f.readInt()
+
+	if len(f.rbuf) < size {
+		panic(fmt.Errorf("not enough bytes in buffer to read long string require %d got: %d", size, len(f.rbuf)))
+	}
+
 	s = string(f.rbuf[:size])
 	f.rbuf = f.rbuf[size:]
 	return
 }
 
 func (f *framer) readUUID() *UUID {
+	if len(f.rbuf) < 16 {
+		panic(fmt.Errorf("not enough bytes in buffer to read uuid require %d got: %d", 16, len(f.rbuf)))
+	}
+
 	// TODO: how to handle this error, if it is a uuid, then sureley, problems?
 	u, _ := UUIDFromBytes(f.rbuf[:16])
 	f.rbuf = f.rbuf[16:]
@@ -1207,6 +1240,10 @@ func (f *framer) readBytes() []byte {
 		return nil
 	}
 
+	if len(f.rbuf) < size {
+		panic(fmt.Errorf("not enough bytes in buffer to read bytes require %d got: %d", size, len(f.rbuf)))
+	}
+
 	// we cant make assumptions about the length of the life of the supplied byte
 	// slice so we defensivly copy it out of the underlying buffer. This has the
 	// downside of increasing allocs per read but will provide much greater memory
@@ -1230,11 +1267,19 @@ func (f *framer) readShortBytes() []byte {
 }
 
 func (f *framer) readInet() (net.IP, int) {
+	if len(f.rbuf) < 1 {
+		panic(fmt.Errorf("not enough bytes in buffer to read inet size require %d got: %d", 1, len(f.rbuf)))
+	}
+
 	size := f.rbuf[0]
 	f.rbuf = f.rbuf[1:]
 
 	if !(size == 4 || size == 16) {
-		panic(fmt.Sprintf("invalid IP size: %d", size))
+		panic(fmt.Errorf("invalid IP size: %d", size))
+	}
+
+	if len(f.rbuf) < 1 {
+		panic(fmt.Errorf("not enough bytes in buffer to read inet require %d got: %d", size, len(f.rbuf)))
 	}
 
 	ip := make([]byte, size)

+ 42 - 0
frame_test.go

@@ -0,0 +1,42 @@
+package gocql
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestFuzzBugs(t *testing.T) {
+	// these inputs are found using go-fuzz (https://github.com/dvyukov/go-fuzz)
+	// and should cause a panic unless fixed.
+	tests := [][]byte{
+		[]byte("00000\xa0000"),
+		[]byte("\x8000\x0e\x00\x00\x00\x000"),
+		[]byte("\x8000\x00\x00\x00\x00\t0000000000"),
+	}
+
+	for i, test := range tests {
+		t.Logf("test %d input: %q", i, test)
+
+		var bw bytes.Buffer
+
+		r := bytes.NewReader(test)
+
+		head, err := readHeader(r, make([]byte, 9))
+		if err != nil {
+			continue
+		}
+
+		framer := newFramer(r, &bw, nil, 3)
+		err = framer.readFrame(&head)
+		if err != nil {
+			continue
+		}
+
+		_, err = framer.parseFrame()
+		if err != nil {
+			continue
+		}
+
+		t.Errorf("(%d) expected to fail for input %q", i, test)
+	}
+}

+ 33 - 0
fuzz.go

@@ -0,0 +1,33 @@
+// +build gofuzz
+
+package gocql
+
+import "bytes"
+
+func Fuzz(data []byte) int {
+	var bw bytes.Buffer
+
+	r := bytes.NewReader(data)
+
+	head, err := readHeader(r, make([]byte, 9))
+	if err != nil {
+		return 0
+	}
+
+	framer := newFramer(r, &bw, nil, 3)
+	err = framer.readFrame(&head)
+	if err != nil {
+		return 0
+	}
+
+	frame, err := framer.parseFrame()
+	if err != nil {
+		return 0
+	}
+
+	if frame != nil {
+		return 1
+	}
+
+	return 2
+}