diff --git a/projects/testing-native-go-fuzzing/Dockerfile b/projects/testing-native-go-fuzzing/Dockerfile new file mode 100644 index 000000000..00b8d69a0 --- /dev/null +++ b/projects/testing-native-go-fuzzing/Dockerfile @@ -0,0 +1,28 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +FROM gcr.io/oss-fuzz-base/base-builder-go +RUN git clone --depth 1 https://github.com/vitessio/vitess +RUN go install golang.org/dl/gotip@latest \ + && gotip download +RUN go install github.com/AdamKorcz/go-118-fuzz-build@latest +COPY build.sh \ + native_ossfuzz_coverage_runnger.go \ + fuzzers/tablet_manager_fuzzer_test.go \ + fuzzers/parser_fuzzer_test.go \ + fuzzers/ast_fuzzer_test.go \ + $SRC/ +WORKDIR $SRC/vitess diff --git a/projects/testing-native-go-fuzzing/build.sh b/projects/testing-native-go-fuzzing/build.sh new file mode 100755 index 000000000..ffd1cb6b5 --- /dev/null +++ b/projects/testing-native-go-fuzzing/build.sh @@ -0,0 +1,182 @@ +#!/bin/bash -eu +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +cd $SRC +# Build main binary +git clone https://github.com/AdamKorcz/go-118-fuzz-build +cd go-118-fuzz-build +go build + +# Build addimport binary +cd addimport +go build + +cd $SRC/vitess + +# Remove existing non-native fuzzers to not deal with them +rm go/vt/vtgate/vindexes/fuzz.go + +# backup vtctl_fuzzer.go +cp go/test/fuzzing/vtctl_fuzzer.go /tmp/ +rm -r go/test/fuzzing/* + +mv $SRC/parser_fuzzer_test.go $SRC/vitess/go/test/fuzzing/ +mv $SRC/ast_fuzzer_test.go $SRC/vitess/go/test/fuzzing/ +mv $SRC/tablet_manager_fuzzer_test.go $SRC/vitess/go/test/fuzzing/ + + +# Disable logging for mysql conn +# This affects the mysql fuzzers +sed -i '/log.Errorf/c\\/\/log.Errorf' $SRC/vitess/go/mysql/conn.go +sed -i '/log.Warningf/c\\/\/log.Warningf' $SRC/vitess/go/vt/sqlparser/parser.go + +mv ./go/vt/vttablet/tabletmanager/vreplication/framework_test.go \ + ./go/vt/vttablet/tabletmanager/vreplication/framework_fuzz.go + +#consistent_lookup_test.go is needed for loggingVCursor +mv ./go/vt/vtgate/vindexes/consistent_lookup_test.go \ + ./go/vt/vtgate/vindexes/consistent_lookup_test_fuzz.go + +# fake_vcursor_test.go is needed for loggingVCursor +mv ./go/vt/vtgate/engine/fake_vcursor_test.go \ + ./go/vt/vtgate/engine/fake_vcursor.go + +# plan_test.go is needed for vschemaWrapper +mv ./go/vt/vtgate/planbuilder/plan_test.go \ + ./go/vt/vtgate/planbuilder/plan_test_fuzz.go + +# tabletserver fuzzer +mv ./go/vt/vttablet/tabletserver/testutils_test.go \ + ./go/vt/vttablet/tabletserver/testutils_fuzz.go + +# collation fuzzer +mv ./go/mysql/collations/uca_test.go \ + ./go/mysql/collations/uca_test_fuzz.go + +mv $SRC/vitess/go/vt/vtgate/grpcvtgateconn/suite_test.go \ + $SRC/vitess/go/vt/vtgate/grpcvtgateconn/suite_test_fuzz.go +mv $SRC/vitess/go/vt/vtgate/grpcvtgateconn/fuzz_flaky_test.go \ + $SRC/vitess/go/vt/vtgate/grpcvtgateconn/fuzz.go + +# build_go_fuzz_harness rewrites a copy of the +# fuzzer to allow for libFuzzer instrumentation +function rewrite_go_fuzz_harness() { + fuzzer_filename=$1 + + # Create a copy of the fuzzer to not modify the existing fuzzer + cp $fuzzer_filename "${fuzzer_filename}"_fuzz_.go + mv $fuzzer_filename /tmp/ + + # replace *testing.F with *go118fuzzbuildutils.F + echo "replacing *testing.F" + sed -i 's/f \*testing\.F/f \*go118fuzzbuildutils\.F/g' "${fuzzer_filename}"_fuzz_.go + + # import https://github.com/AdamKorcz/go-118-fuzz-build + # This changes the line numbers from the original fuzzer + $SRC/go-118-fuzz-build/addimport/addimport -path "${fuzzer_filename}"_fuzz_.go +} + +function compile_native_go_fuzzer() { + fuzzer=$1 + function=$2 + path=$3 + tags="-tags gofuzz" + + if [[ $SANITIZER = *coverage* ]]; then + echo "here we perform coverage build" + fuzzed_package=`go list $tags -f '{{.Name}}' $path` + abspath=`go list $tags -f {{.Dir}} $path` + cd $abspath + cp $SRC/native_ossfuzz_coverage_runnger.go ./"${function,,}"_test.go + sed -i -e 's/FuzzFunction/'$function'/' ./"${function,,}"_test.go + sed -i -e 's/mypackagebeingfuzzed/'$fuzzed_package'/' ./"${function,,}"_test.go + sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go + + # The repo is the module path/name, which is already created above in case it doesn't exist, + # but not always the same as the module path. This is necessary to handle SIV properly. + fuzzed_repo=$(go list $tags -f {{.Module}} "$path") + abspath_repo=`go list -m $tags -f {{.Dir}} $fuzzed_repo || go list $tags -f {{.Dir}} $fuzzed_repo` + # give equivalence to absolute paths in another file, as go test -cover uses golangish pkg.Dir + echo "s=$fuzzed_repo"="$abspath_repo"= > $OUT/$fuzzer.gocovpath + ls + gotip test -run Test${function}Corpus -v $tags -coverpkg $fuzzed_repo/... -c -o $OUT/$fuzzer $path + + rm ./"${function,,}"_test.go + else + $SRC/go-118-fuzz-build/go-118-fuzz-build -o $fuzzer.a -func $function $abs_file_dir + $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer + fi +} + +# build_go_fuzzer will be the api used by users +# The api is now placed in this build script +# but will be moved to the base image once it +# has reached sufficient maturity. +function build_go_fuzzer () { + path=$1 + function=$2 + fuzzer=$3 + tags="-tags gofuzz" + + # Get absolute path + abs_file_dir=$(go list $tags -f {{.Dir}} $path) + + # TODO: Get rid of "-r" flag here + fuzzer_filename=$(grep -r -l -s "$function" "${abs_file_dir}") + + # test if file contains a line with "func $function" and "testing.F" + if [ $(grep -r "func $function" $fuzzer_filename | grep "testing.F" | wc -l) -eq 1 ] + then + # we are dealing with a native harness + + # Install more dependencies + gotip get github.com/AdamKorcz/go-118-fuzz-build/utils + gotip get google.golang.org/grpc/internal/channelz@v1.39.0 + + echo "Native harness" + rewrite_go_fuzz_harness $fuzzer_filename + compile_native_go_fuzzer $fuzzer $function $abs_file_dir + # clean up + rm "${fuzzer_filename}_fuzz_.go" + mv /tmp/$(basename $fuzzer_filename) $fuzzer_filename + else + # we are dealing with a go-fuzz harness + echo "go-fuzz harness" + compile_go_fuzzer $path $function $fuzzer $tags + fi + +} + +# build native fuzzers +build_go_fuzzer vitess.io/vitess/go/test/fuzzing FuzzTabletManager_ExecuteFetchAsDba fuzz_tablet_manager_execute_fetch_as_dba +build_go_fuzzer vitess.io/vitess/go/test/fuzzing FuzzParser parser_fuzzer +build_go_fuzzer vitess.io/vitess/go/test/fuzzing FuzzIsDML is_dml_fuzzer +build_go_fuzzer vitess.io/vitess/go/test/fuzzing FuzzNormalizer normalizer_fuzzer +build_go_fuzzer vitess.io/vitess/go/test/fuzzing FuzzNodeFormat normalizer_fuzzer +build_go_fuzzer vitess.io/vitess/go/test/fuzzing FuzzSplitStatementToPieces fuzz_split_statement_to_pieces +build_go_fuzzer vitess.io/vitess/go/test/fuzzing FuzzEqualsSQLNode fuzz_equals_sql_node + +# Delete all the native fuzzers before building the go-fuzz fuzzer(s) +# this will not be necessary when Go 1.18 is released. The reason this +# is needed is because go114-fuzz-build calls "go" instead of "gotip", +# and an error will be thrown because testing.F is not recognized. +rm $SRC/vitess/go/test/fuzzing/*_test.go + +# build go-fuzz fuzzers +mv /tmp/vtctl_fuzzer.go $SRC/vitess/go/test/fuzzing/ +build_go_fuzzer vitess.io/vitess/go/test/fuzzing Fuzz vtctl_fuzzer + diff --git a/projects/testing-native-go-fuzzing/fuzzers/ast_fuzzer_test.go b/projects/testing-native-go-fuzzing/fuzzers/ast_fuzzer_test.go new file mode 100644 index 000000000..c2b35e119 --- /dev/null +++ b/projects/testing-native-go-fuzzing/fuzzers/ast_fuzzer_test.go @@ -0,0 +1,72 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package fuzzing + +import ( + fuzz "github.com/AdaLogics/go-fuzz-headers" + + "vitess.io/vitess/go/vt/sqlparser" + "testing" +) + +func FuzzEqualsSQLNode(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) < 10 { + return + } + f := fuzz.NewConsumer(data) + query1, err := f.GetSQLString() + if err != nil { + return + } + query2, err := f.GetSQLString() + if err != nil { + return + } + inA, err := sqlparser.Parse(query1) + if err != nil { + return + } + inB, err := sqlparser.Parse(query2) + if err != nil { + return + } + + // There are 3 targets in this fuzzer: + // 1) sqlparser.EqualsSQLNode + // 2) sqlparser.CloneSQLNode + // 3) sqlparser.VisitSQLNode + + // Target 1: + identical := sqlparser.EqualsSQLNode(inA, inA) + if !identical { + panic("Should be identical") + } + identical = sqlparser.EqualsSQLNode(inB, inB) + if !identical { + panic("Should be identical") + } + + // Target 2: + newSQLNode := sqlparser.CloneSQLNode(inA) + if !sqlparser.EqualsSQLNode(inA, newSQLNode) { + panic("These two nodes should be identical") + } + + // Target 3: + _ = sqlparser.VisitSQLNode(inA, func(node sqlparser.SQLNode) (bool, error) { return false, nil }) + }) +} diff --git a/projects/testing-native-go-fuzzing/fuzzers/parser_fuzzer_test.go b/projects/testing-native-go-fuzzing/fuzzers/parser_fuzzer_test.go new file mode 100644 index 000000000..1bbb4c746 --- /dev/null +++ b/projects/testing-native-go-fuzzing/fuzzers/parser_fuzzer_test.go @@ -0,0 +1,73 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package fuzzing + +import ( + "testing" + querypb "vitess.io/vitess/go/vt/proto/query" + "vitess.io/vitess/go/vt/sqlparser" + + fuzz "github.com/AdaLogics/go-fuzz-headers" +) + +func FuzzIsDML(f *testing.F) { + f.Fuzz(func(t *testing.T, data string) { + _ = sqlparser.IsDML(data) + }) +} + +func FuzzNormalizer(f *testing.F) { + f.Fuzz(func(t *testing.T, data string) { + stmt, reservedVars, err := sqlparser.Parse2(data) + if err != nil { + return + } + bv := make(map[string]*querypb.BindVariable) + sqlparser.Normalize(stmt, sqlparser.NewReservedVars("bv", reservedVars), bv) + }) +} + +func FuzzParser(f *testing.F) { + f.Fuzz(func(t *testing.T, data string) { + _, _ = sqlparser.Parse(data) + }) +} + +func FuzzNodeFormat(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + f := fuzz.NewConsumer(data) + query, err := f.GetSQLString() + if err != nil { + return + } + node, err := sqlparser.Parse(query) + if err != nil { + return + } + buf := &sqlparser.TrackedBuffer{} + err = f.GenerateStruct(buf) + if err != nil { + return + } + node.Format(buf) + }) +} + +func FuzzSplitStatementToPieces(f *testing.F) { + f.Fuzz(func(t *testing.T, data string) { + _, _ = sqlparser.SplitStatementToPieces(data) + }) +} diff --git a/projects/testing-native-go-fuzzing/fuzzers/tablet_manager_fuzzer_test.go b/projects/testing-native-go-fuzzing/fuzzers/tablet_manager_fuzzer_test.go new file mode 100644 index 000000000..69cde008c --- /dev/null +++ b/projects/testing-native-go-fuzzing/fuzzers/tablet_manager_fuzzer_test.go @@ -0,0 +1,47 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package fuzzing + +import ( + "context" + "testing" + + "vitess.io/vitess/go/mysql" + "vitess.io/vitess/go/mysql/fakesqldb" + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/dbconfigs" + "vitess.io/vitess/go/vt/mysqlctl/fakemysqldaemon" + "vitess.io/vitess/go/vt/vttablet/tabletmanager" + "vitess.io/vitess/go/vt/vttablet/tabletservermock" +) + +func FuzzTabletManager_ExecuteFetchAsDba(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + ctx := context.Background() + cp := mysql.ConnParams{} + db := fakesqldb.New(t) + db.AddQueryPattern(".*", &sqltypes.Result{}) + daemon := fakemysqldaemon.NewFakeMysqlDaemon(db) + + dbName := "dbname" + tm := &tabletmanager.TabletManager{ + MysqlDaemon: daemon, + DBConfigs: dbconfigs.NewTestDBConfigs(cp, cp, dbName), + QueryServiceControl: tabletservermock.NewController(), + } + _, _ = tm.ExecuteFetchAsDba(ctx, data, dbName, 10, false, false) + }) +} diff --git a/projects/testing-native-go-fuzzing/native_ossfuzz_coverage_runnger.go b/projects/testing-native-go-fuzzing/native_ossfuzz_coverage_runnger.go new file mode 100644 index 000000000..b7399e191 --- /dev/null +++ b/projects/testing-native-go-fuzzing/native_ossfuzz_coverage_runnger.go @@ -0,0 +1,71 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mypackagebeingfuzzed + +import ( + "io/ioutil" + "os" + "runtime/pprof" + "testing" + "github.com/AdamKorcz/go-118-fuzz-build/utils" +) + +func TestFuzzCorpus(t *testing.T) { + dir := os.Getenv("FUZZ_CORPUS_DIR") + if dir == "" { + t.Logf("No fuzzing corpus directory set") + return + } + infos, err := ioutil.ReadDir(dir) + if err != nil { + t.Logf("Not fuzzing corpus directory %s", err) + return + } + filename := "" + defer func() { + if r := recover(); r != nil { + t.Error("Fuzz panicked in "+filename, r) + } + }() + profname := os.Getenv("FUZZ_PROFILE_NAME") + if profname != "" { + f, err := os.Create(profname + ".cpu.prof") + if err != nil { + t.Logf("error creating profile file %s\n", err) + } else { + _ = pprof.StartCPUProfile(f) + } + } + for i := range infos { + filename = dir + infos[i].Name() + data, err := ioutil.ReadFile(filename) + if err != nil { + t.Error("Failed to read corpus file", err) + } + fuzzerF := &utils.F{Data:data, T:&testing.T{}} + FuzzFunction(fuzzerF) + } + if profname != "" { + pprof.StopCPUProfile() + f, err := os.Create(profname + ".heap.prof") + if err != nil { + t.Logf("error creating heap profile file %s\n", err) + } + if err = pprof.WriteHeapProfile(f); err != nil { + t.Logf("error writing heap profile file %s\n", err) + } + f.Close() + } +} diff --git a/projects/testing-native-go-fuzzing/project.yaml b/projects/testing-native-go-fuzzing/project.yaml new file mode 100644 index 000000000..711ec4e73 --- /dev/null +++ b/projects/testing-native-go-fuzzing/project.yaml @@ -0,0 +1,10 @@ +homepage: "https://github.com/vitessio/vitess" +primary_contact: "adam@adalogics.com" +auto_ccs : + - "david@adalogics.com" +language: go +fuzzing_engines: + - libfuzzer +sanitizers: + - address +main_repo: 'https://github.com/vitessio/vitess'