diff --git a/bson/decode_value_fuzz_test.go b/bson/decode_value_fuzz_test.go new file mode 100644 index 0000000000..bb8b57821f --- /dev/null +++ b/bson/decode_value_fuzz_test.go @@ -0,0 +1,69 @@ +// Copyright (C) MongoDB, Inc. 2025-present. +// +// 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 + +package bson + +import ( + "math" + "strings" + "testing" +) + +func FuzzDecodeValue(f *testing.F) { + // Seed the fuzz corpus with all BSON values from the MarshalValue test + // cases. + for _, tc := range marshalValueTestCases { + f.Add(byte(tc.bsontype), tc.bytes) + } + + // Also seed the fuzz corpus with special values that we want to test. + values := []any{ + // int32, including max and min values. + int32(0), int32(math.MaxInt32), int32(math.MinInt32), + // int64, including max and min values. + int64(0), int64(math.MaxInt64), int64(math.MinInt64), + // string, including empty and large string. + "", strings.Repeat("z", 10_000), + // map + map[string]any{"nested": []any{1, "two", map[string]any{"three": 3}}}, + // array + []any{1, 2, 3, "four"}, + } + + for _, v := range values { + typ, b, err := MarshalValue(v) + if err != nil { + f.Fatal(err) + } + f.Add(byte(typ), b) + } + + f.Fuzz(func(t *testing.T, bsonType byte, data []byte) { + var v any + if err := UnmarshalValue(Type(bsonType), data, &v); err != nil { + return + } + + // There is no value encoder for Go "nil" (nil values handled + // differently by each type encoder), so skip anything that unmarshals + // to "nil". It's not clear if MarshalValue should support "nil", but + // for now we skip it. + if v == nil { + t.Logf("data unmarshaled to nil: %v", data) + return + } + + typ, encoded, err := MarshalValue(v) + if err != nil { + t.Fatalf("failed to marshal: %v", err) + } + + var v2 any + if err := UnmarshalValue(typ, encoded, &v2); err != nil { + t.Fatalf("failed to unmarshal: %v", err) + } + }) +} diff --git a/bson/fuzz_test.go b/bson/fuzz_test.go index 18fbff56c9..8b799c1271 100644 --- a/bson/fuzz_test.go +++ b/bson/fuzz_test.go @@ -4,6 +4,11 @@ // 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 +// fuzz_test.go is used by the "oss-fuzz" integration. Use caution when +// modifying this file because it may break that integration. +// +// See https://github.com/google/oss-fuzz/tree/master/projects/mongo-go-driver + package bson import ( @@ -29,11 +34,11 @@ func FuzzDecode(f *testing.F) { encoded, err := Marshal(i) if err != nil { - t.Fatal("failed to marshal", err) + t.Fatalf("failed to marshal: %v", err) } if err := Unmarshal(encoded, i); err != nil { - t.Fatal("failed to unmarshal", err) + t.Fatalf("failed to unmarshal: %v", err) } } }) diff --git a/etc/run-fuzz.sh b/etc/run-fuzz.sh index 22d36cfc7f..78b4cfa54e 100755 --- a/etc/run-fuzz.sh +++ b/etc/run-fuzz.sh @@ -2,7 +2,13 @@ set -o errexit # Exit the script with error if any of the commands fail -FUZZTIME=10m +# Default fuzztime to 10m. +FUZZTIME=${FUZZTIME:-10m} + +if [ -z "$PROJECT_DIRECTORY" ]; then + echo "Please set PROJECT_DIRECTORY env variable." + exit 1 +fi # Change the working directory to the root of the mongo repository directory cd $PROJECT_DIRECTORY @@ -21,7 +27,7 @@ do # For each fuzz test in the file, run it for FUZZTIME. for FUNC in ${FUNCS} do - echo "Fuzzing \"${FUNC}\" in \"${FILE}\"" + echo "Fuzzing \"${FUNC}\" in \"${FILE}\" for ${FUZZTIME}" # Create a set of directories that are already in the subdirectories testdata/fuzz/$fuzzer corpus. This # set will be used to differentiate between new and old corpus files. @@ -35,7 +41,7 @@ do done fi - GOMAXPROCS=2 go test ${PARENTDIR} -run=${FUNC} -fuzz=${FUNC} -fuzztime=${FUZZTIME} || true + GOMAXPROCS=2 go test -race ${PARENTDIR} -run=${FUNC} -fuzz=^${FUNC}\$ -fuzztime=${FUZZTIME} || true # Check if any new corpus files were generated for the fuzzer. If there are new corpus files, move them # to $PROJECT_DIRECTORY/fuzz/$FUNC/* so they can be tarred up and uploaded to S3.