From fcc2cfd098bbd215e2c8f060130042d18a4927e2 Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Fri, 24 May 2019 19:34:48 +0300 Subject: [PATCH] Fuzz: initial commit * Add fuzz.go and FuzzRecordLayer func * Add Makefile * Update contributors * Update EXCLUDE_DIRECTORIES with "fuzz" * Fix found index out of range errors --- .editorconfig | 21 +++++++++++ .../lint-disallowed-functions-in-library.sh | 2 +- .gitignore | 2 ++ Makefile | 6 ++++ README.md | 1 + cipher_suite.go | 3 ++ compression_method.go | 3 ++ extension.go | 10 +++++- fuzz.go | 36 +++++++++++++++++++ handshake_message_certificate_request.go | 7 +++- handshake_message_client_hello.go | 19 ++++++++++ handshake_message_client_key_exchange.go | 3 ++ handshake_message_hello_verify_request.go | 6 ++++ handshake_message_server_hello.go | 12 +++++-- handshake_message_server_key_exchange.go | 6 ++++ record_layer.go | 3 ++ record_layer_header.go | 3 ++ 17 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 fuzz.go diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d2b3206 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +[*.go] +indent_style = tab +indent_size = 4 + +[{*.yml,*.yaml}] +indent_style = space +indent_size = 2 + +# Makefiles always use tabs for indentation +[Makefile] +indent_style = tab diff --git a/.github/lint-disallowed-functions-in-library.sh b/.github/lint-disallowed-functions-in-library.sh index 32c6aa0..c69f716 100755 --- a/.github/lint-disallowed-functions-in-library.sh +++ b/.github/lint-disallowed-functions-in-library.sh @@ -3,7 +3,7 @@ set -e # Disallow usages of functions that cause the program to exit in the library code SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -EXCLUDE_DIRECTORIES="--exclude-dir=examples --exclude-dir=.git --exclude-dir=.github --exclude-dir=ccm " +EXCLUDE_DIRECTORIES="--exclude-dir=examples --exclude-dir=.git --exclude fuzz\.go --exclude-dir=vendor --exclude-dir=.github --exclude-dir=ccm " DISALLOWED_FUNCTIONS=('os.Exit(' 'panic(' 'Fatal(' 'Fatalf(' 'Fatalln(' 'fmt.Println(' 'fmt.Printf(' 'log.Print(' 'log.Println(' 'log.Printf(') diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e52f2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor +*-fuzz.zip diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1df38b2 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +fuzz-build-record-layer: fuzz-prepare + go-fuzz-build -tags gofuzz -func FuzzRecordLayer +fuzz-run-record-layer: + go-fuzz -bin dtls-fuzz.zip -workdir fuzz +fuzz-prepare: + @GO111MODULE=on go mod vendor diff --git a/README.md b/README.md index 3c098fa..57e80ce 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu * [Jin Lei](https://github.com/jinleileiking) - *Logging* * [Hugo Arregui](https://github.com/hugoArregui) * [Lander Noterman](https://github.com/LanderN) +* [Aleksandr Razumov](https://github.com/ernado) - *Fuzzing* ### License MIT License - see [LICENSE](LICENSE) for full text diff --git a/cipher_suite.go b/cipher_suite.go index c7abb83..162d51c 100644 --- a/cipher_suite.go +++ b/cipher_suite.go @@ -69,6 +69,9 @@ func decodeCipherSuites(buf []byte) ([]cipherSuite, error) { cipherSuitesCount := int(binary.BigEndian.Uint16(buf[0:])) / 2 rtrn := []cipherSuite{} for i := 0; i < cipherSuitesCount; i++ { + if len(buf) < (i*2 + 4) { + return nil, errBufferTooSmall + } id := CipherSuiteID(binary.BigEndian.Uint16(buf[(i*2)+2:])) if c := cipherSuiteForID(id); c != nil { rtrn = append(rtrn, c) diff --git a/compression_method.go b/compression_method.go index 55c255c..15d7aec 100644 --- a/compression_method.go +++ b/compression_method.go @@ -25,6 +25,9 @@ func decodeCompressionMethods(buf []byte) ([]*compressionMethod, error) { compressionMethodsCount := int(buf[0]) c := []*compressionMethod{} for i := 0; i < compressionMethodsCount; i++ { + if len(buf) <= i+1 { + return nil, errBufferTooSmall + } id := compressionMethodID(buf[i+1]) if compressionMethod, ok := compressionMethods[id]; ok { c = append(c, compressionMethod) diff --git a/extension.go b/extension.go index b187e21..a140275 100644 --- a/extension.go +++ b/extension.go @@ -22,6 +22,9 @@ type extension interface { } func decodeExtensions(buf []byte) ([]extension, error) { + if len(buf) < 2 { + return nil, errBufferTooSmall + } declaredLen := binary.BigEndian.Uint16(buf) if len(buf)-2 != int(declaredLen) { return nil, errLengthMismatch @@ -38,6 +41,9 @@ func decodeExtensions(buf []byte) ([]extension, error) { } for offset := 2; offset < len(buf); { + if len(buf) < (offset + 2) { + return nil, errBufferTooSmall + } var err error switch extensionValue(binary.BigEndian.Uint16(buf[offset:])) { case extensionSupportedEllipticCurvesValue: @@ -49,7 +55,9 @@ func decodeExtensions(buf []byte) ([]extension, error) { if err != nil { return nil, err } - + if len(buf) < (offset + 4) { + return nil, errBufferTooSmall + } extensionLength := binary.BigEndian.Uint16(buf[offset+2:]) offset += (4 + int(extensionLength)) } diff --git a/fuzz.go b/fuzz.go new file mode 100644 index 0000000..71568af --- /dev/null +++ b/fuzz.go @@ -0,0 +1,36 @@ +// +build gofuzz + +package dtls + +import "fmt" + +func partialHeaderMismatch(a, b recordLayerHeader) bool { + // Ignoring content length for now. + a.contentLen = b.contentLen + return a != b +} + +func FuzzRecordLayer(data []byte) int { + var r recordLayer + if err := r.Unmarshal(data); err != nil { + return 0 + } + buf, err := r.Marshal() + if err != nil { + return 1 + } + if len(buf) == 0 { + panic("zero buff") + } + var nr recordLayer + if err = nr.Unmarshal(data); err != nil { + panic(err) + } + if partialHeaderMismatch(nr.recordLayerHeader, r.recordLayerHeader) { + panic(fmt.Sprintf("header mismatch: %+v != %+v", + nr.recordLayerHeader, r.recordLayerHeader, + )) + } + + return 1 +} diff --git a/handshake_message_certificate_request.go b/handshake_message_certificate_request.go index 8d5da19..a429daa 100644 --- a/handshake_message_certificate_request.go +++ b/handshake_message_certificate_request.go @@ -62,7 +62,9 @@ func (h *handshakeMessageCertificateRequest) Unmarshal(data []byte) error { } } offset += certificateTypesLength - + if len(data) < offset+2 { + return errBufferTooSmall + } signatureHashAlgorithmsLength := int(binary.BigEndian.Uint16(data[offset:])) offset += 2 @@ -71,6 +73,9 @@ func (h *handshakeMessageCertificateRequest) Unmarshal(data []byte) error { } for i := 0; i < signatureHashAlgorithmsLength; i += 2 { + if len(data) < (offset + i + 2) { + return errBufferTooSmall + } hash := HashAlgorithm(data[offset+i]) signature := signatureAlgorithm(data[offset+i+1]) diff --git a/handshake_message_client_hello.go b/handshake_message_client_hello.go index baf1892..d68ba9d 100644 --- a/handshake_message_client_hello.go +++ b/handshake_message_client_hello.go @@ -58,6 +58,10 @@ func (h *handshakeMessageClientHello) Marshal() ([]byte, error) { } func (h *handshakeMessageClientHello) Unmarshal(data []byte) error { + if len(data) < 2+handshakeRandomLength { + return errBufferTooSmall + } + h.version.major = data[0] h.version.minor = data[1] @@ -70,23 +74,38 @@ func (h *handshakeMessageClientHello) Unmarshal(data []byte) error { currOffset += int(data[currOffset]) + 1 // SessionID currOffset++ + if len(data) < currOffset { + return errBufferTooSmall + } h.cookie = append([]byte{}, data[currOffset:currOffset+int(data[currOffset-1])]...) currOffset += len(h.cookie) // Cipher Suites + if len(data) < currOffset { + return errBufferTooSmall + } cipherSuites, err := decodeCipherSuites(data[currOffset:]) if err != nil { return err } h.cipherSuites = cipherSuites + if len(data) < currOffset+2 { + return errBufferTooSmall + } currOffset += int(binary.BigEndian.Uint16(data[currOffset:])) + 2 // Compression Methods + if len(data) < currOffset { + return errBufferTooSmall + } compressionMethods, err := decodeCompressionMethods(data[currOffset:]) if err != nil { return err } h.compressionMethods = compressionMethods + if len(data) < currOffset { + return errBufferTooSmall + } currOffset += int(data[currOffset]) + 1 // Extensions diff --git a/handshake_message_client_key_exchange.go b/handshake_message_client_key_exchange.go index 7ece67a..e2bea32 100644 --- a/handshake_message_client_key_exchange.go +++ b/handshake_message_client_key_exchange.go @@ -13,6 +13,9 @@ func (h *handshakeMessageClientKeyExchange) Marshal() ([]byte, error) { } func (h *handshakeMessageClientKeyExchange) Unmarshal(data []byte) error { + if len(data) < 1 { + return errBufferTooSmall + } publicKeyLength := int(data[0]) if len(data) <= publicKeyLength { return errBufferTooSmall diff --git a/handshake_message_hello_verify_request.go b/handshake_message_hello_verify_request.go index 749f504..453947f 100644 --- a/handshake_message_hello_verify_request.go +++ b/handshake_message_hello_verify_request.go @@ -41,9 +41,15 @@ func (h *handshakeMessageHelloVerifyRequest) Marshal() ([]byte, error) { } func (h *handshakeMessageHelloVerifyRequest) Unmarshal(data []byte) error { + if len(data) < 3 { + return errBufferTooSmall + } h.version.major = data[0] h.version.minor = data[1] cookieLength := data[2] + if len(data) < (int(cookieLength) + 3) { + return errBufferTooSmall + } h.cookie = make([]byte, cookieLength) copy(h.cookie, data[3:3+cookieLength]) diff --git a/handshake_message_server_hello.go b/handshake_message_server_hello.go index daf9130..4837abc 100644 --- a/handshake_message_server_hello.go +++ b/handshake_message_server_hello.go @@ -59,6 +59,10 @@ func (h *handshakeMessageServerHello) Marshal() ([]byte, error) { } func (h *handshakeMessageServerHello) Unmarshal(data []byte) error { + if len(data) < 2+handshakeRandomLength { + return errBufferTooSmall + } + h.version.major = data[0] h.version.minor = data[1] @@ -68,14 +72,18 @@ func (h *handshakeMessageServerHello) Unmarshal(data []byte) error { currOffset := handshakeMessageServerHelloVariableWidthStart currOffset += int(data[currOffset]) + 1 // SessionID - + if len(data) < (currOffset + 2) { + return errBufferTooSmall + } if c := cipherSuiteForID(CipherSuiteID(binary.BigEndian.Uint16(data[currOffset:]))); c != nil { h.cipherSuite = c currOffset += 2 } else { return errInvalidCipherSuite } - + if len(data) < currOffset { + return errBufferTooSmall + } if compressionMethod, ok := compressionMethods[compressionMethodID(data[currOffset])]; ok { h.compressionMethod = compressionMethod currOffset++ diff --git a/handshake_message_server_key_exchange.go b/handshake_message_server_key_exchange.go index 1a27dd0..debf11f 100644 --- a/handshake_message_server_key_exchange.go +++ b/handshake_message_server_key_exchange.go @@ -34,6 +34,9 @@ func (h *handshakeMessageServerKeyExchange) Marshal() ([]byte, error) { } func (h *handshakeMessageServerKeyExchange) Unmarshal(data []byte) error { + if len(data) < 1 { + return errBufferTooSmall + } if _, ok := ellipticCurveTypes[ellipticCurveType(data[0])]; ok { h.ellipticCurveType = ellipticCurveType(data[0]) } else { @@ -44,6 +47,9 @@ func (h *handshakeMessageServerKeyExchange) Unmarshal(data []byte) error { if _, ok := namedCurves[h.namedCurve]; !ok { return errInvalidNamedCurve } + if len(data) < 4 { + return errBufferTooSmall + } publicKeyLength := int(data[3]) offset := 4 + publicKeyLength diff --git a/record_layer.go b/record_layer.go index fa2c8ee..f51e2af 100644 --- a/record_layer.go +++ b/record_layer.go @@ -43,6 +43,9 @@ func (r *recordLayer) Marshal() ([]byte, error) { } func (r *recordLayer) Unmarshal(data []byte) error { + if len(data) < recordLayerHeaderSize { + return errBufferTooSmall + } if err := r.recordLayerHeader.Unmarshal(data); err != nil { return err } diff --git a/record_layer_header.go b/record_layer_header.go index 8f98f08..466bd95 100644 --- a/record_layer_header.go +++ b/record_layer_header.go @@ -42,6 +42,9 @@ func (r *recordLayerHeader) Marshal() ([]byte, error) { } func (r *recordLayerHeader) Unmarshal(data []byte) error { + if len(data) < 12 { + return errBufferTooSmall + } r.contentType = contentType(data[0]) r.protocolVersion.major = data[1] r.protocolVersion.minor = data[2]