Test drive native fuzzing (#7104)

This commit is contained in:
AdamKorcz 2022-01-07 14:57:02 +00:00 committed by GitHub
parent 81f4a646b2
commit ffcf9c0c0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 483 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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 })
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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()
}
}

View File

@ -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'