diff --git a/projects/badger/Dockerfile b/projects/badger/Dockerfile new file mode 100644 index 000000000000..c7bbc0eb9ee2 --- /dev/null +++ b/projects/badger/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/dgraph-io/badger $SRC/badger +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/badger + diff --git a/projects/badger/build.sh b/projects/badger/build.sh new file mode 100644 index 000000000000..d4fc30650227 --- /dev/null +++ b/projects/badger/build.sh @@ -0,0 +1,22 @@ +# Copyright 2026 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/badger +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/dgraph-io/badger/v4 FuzzValuePointer fuzz_value_pointer +compile_go_fuzzer github.com/dgraph-io/badger/v4 FuzzWAL fuzz_wal +compile_go_fuzzer github.com/dgraph-io/badger/v4 FuzzManifest fuzz_manifest + diff --git a/projects/badger/fuzz_test.go b/projects/badger/fuzz_test.go new file mode 100644 index 000000000000..b465ea2f5433 --- /dev/null +++ b/projects/badger/fuzz_test.go @@ -0,0 +1,93 @@ +// Copyright 2026 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 badger + +import ( + "bytes" + "os" + "testing" +) + +func FuzzValueLogEntry(f *testing.F) { + f.Add([]byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 10, 'h', 'e', 'l', 'l', 'o', 0, 0, 0, 0, 0, 0, 0, 0}) + f.Add(make([]byte, 12)) + f.Add(make([]byte, 1000)) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) < 10 { return } + var vp valuePointer + func() { defer func() { recover() }(); vp.Decode(data) }() + var h header + func() { defer func() { recover() }(); h.Decode(data) }() + }) +} + +func FuzzManifestParse(f *testing.F) { + f.Add([]byte{10, 0}) + f.Add([]byte{}) + f.Add([]byte{0xFF, 0xFF, 0xFF, 0xFF}) + f.Add([]byte{10, 2, 8, 1, 10, 4, 8, 2, 16, 3}) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) > 1<<20 { return } + r := bytes.NewReader(data) + buf := make([]byte, len(data)) + r.Read(buf) + dir, _ := os.MkdirTemp("", "fuzz-badger-*") + defer os.RemoveAll(dir) + db, err := Open(DefaultOptions(dir).WithReadOnly(true).WithValueLogFileSize(1<<20)) + if err == nil { db.Close() } + }) +} + +func FuzzDBWriteRead(f *testing.F) { + f.Add([]byte("k1"), []byte("v1")) + f.Add([]byte(""), []byte("")) + f.Fuzz(func(t *testing.T, key, value []byte) { + if len(key) > 1000 || len(value) > 10000 { return } + dir, _ := os.MkdirTemp("", "fuzz-badger-*") + defer os.RemoveAll(dir) + func() { + defer func() { recover() }() + db, err := Open(DefaultOptions(dir).WithSyncWrites(false).WithNumVersionsToKeep(1)) + if err != nil { return } + defer db.Close() + txn := db.NewTransaction(true) + txn.Set(key, value) + txn.Commit() + db.View(func(txn *Txn) error { + item, err := txn.Get(key) + if err == nil { item.ValueCopy(nil) } + return nil + }) + }() + }) +} + +func FuzzOptions(f *testing.F) { + f.Add(int64(1), int64(1), float64(0.5)) + f.Add(int64(1<<20), int64(1<<30), float64(0.95)) + f.Add(int64(-1), int64(0), float64(2.0)) + f.Fuzz(func(t *testing.T, memSize, vlogSize int64, sample float64) { + dir, _ := os.MkdirTemp("", "fuzz-badger-*") + defer os.RemoveAll(dir) + func() { + defer func() { recover() }() + opts := DefaultOptions(dir). + WithMemTableSize(memSize). + WithValueLogFileSize(vlogSize). + WithValueThreshold(32) + _ = opts + }() + }) +} diff --git a/projects/badger/project.yaml b/projects/badger/project.yaml new file mode 100644 index 000000000000..d9c75fbe3d9c --- /dev/null +++ b/projects/badger/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/dgraph-io/badger" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/dgraph-io/badger" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/crypto-ssh/Dockerfile b/projects/crypto-ssh/Dockerfile new file mode 100644 index 000000000000..e8089bdfe7ba --- /dev/null +++ b/projects/crypto-ssh/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/golang/crypto $SRC/crypto +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/crypto/ssh + diff --git a/projects/crypto-ssh/build.sh b/projects/crypto-ssh/build.sh new file mode 100644 index 000000000000..8d2548ebfa45 --- /dev/null +++ b/projects/crypto-ssh/build.sh @@ -0,0 +1,24 @@ +# Copyright 2026 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/crypto/ssh +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer golang.org/x/crypto/ssh FuzzParsePublicKey fuzz_parse_public_key +compile_go_fuzzer golang.org/x/crypto/ssh FuzzParseAuthorizedKey fuzz_parse_authorized_key +compile_go_fuzzer golang.org/x/crypto/ssh FuzzParseKnownHosts fuzz_parse_known_hosts +compile_go_fuzzer golang.org/x/crypto/ssh FuzzParsePrivateKey fuzz_parse_private_key +compile_go_fuzzer golang.org/x/crypto/ssh FuzzParsePrivateKeyWithPassphrase fuzz_parse_private_key_passphrase + diff --git a/projects/crypto-ssh/fuzz_test.go b/projects/crypto-ssh/fuzz_test.go new file mode 100644 index 000000000000..eabb33a56bb7 --- /dev/null +++ b/projects/crypto-ssh/fuzz_test.go @@ -0,0 +1,91 @@ +// Copyright 2026 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 ssh_test + +import ( + "testing" + "golang.org/x/crypto/ssh" +) + +func FuzzParsePublicKey(f *testing.F) { + f.Add([]byte{}) + f.Add([]byte{0, 0, 0, 0}) + f.Add([]byte{0xFF, 0xFF, 0xFF, 0xFF}) + f.Add([]byte{0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'}) + f.Add(make([]byte, 1000)) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + key, _ := ssh.ParsePublicKey(data) + if key != nil { + _ = key.Type() + ssh.MarshalAuthorizedKey(key) + } + }() + }) +} + +func FuzzParseAuthorizedKey(f *testing.F) { + f.Add("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ test@host\n") + f.Add("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI test@host\n") + f.Add("") + f.Add("\n") + f.Add("# comment\n") + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + ssh.ParseAuthorizedKey([]byte(data)) + }() + }) +} + +func FuzzParseKnownHosts(f *testing.F) { + f.Add("localhost ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ==\n") + f.Add("") + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + ssh.ParseKnownHosts([]byte(data)) + }() + }) +} + +func FuzzParsePrivateKey(f *testing.F) { + f.Add("-----BEGIN OPENSSH PRIVATE KEY-----\nAAAA\n-----END OPENSSH PRIVATE KEY-----\n") + f.Add("-----BEGIN RSA PRIVATE KEY-----\nAAAA\n-----END RSA PRIVATE KEY-----\n") + f.Add("") + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + ssh.ParsePrivateKey([]byte(data)) + }() + }) +} + +func FuzzParsePrivateKeyWithPassphrase(f *testing.F) { + f.Add("-----BEGIN OPENSSH PRIVATE KEY-----\nAAAA\n-----END OPENSSH PRIVATE KEY-----\n", "password") + f.Add("", "") + f.Fuzz(func(t *testing.T, data, passphrase string) { + if len(data) > 1<<16 || len(passphrase) > 1000 { return } + func() { + defer func() { recover() }() + ssh.ParsePrivateKeyWithPassphrase([]byte(data), []byte(passphrase)) + }() + }) +} diff --git a/projects/crypto-ssh/project.yaml b/projects/crypto-ssh/project.yaml new file mode 100644 index 000000000000..0023193464da --- /dev/null +++ b/projects/crypto-ssh/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/golang/crypto" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/golang/crypto" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/golang-jwt/Dockerfile b/projects/golang-jwt/Dockerfile new file mode 100644 index 000000000000..c3d84a662b41 --- /dev/null +++ b/projects/golang-jwt/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/golang-jwt/jwt $SRC/jwt +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/jwt + diff --git a/projects/golang-jwt/build.sh b/projects/golang-jwt/build.sh new file mode 100644 index 000000000000..857a5457cbe9 --- /dev/null +++ b/projects/golang-jwt/build.sh @@ -0,0 +1,22 @@ +# Copyright 2026 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/jwt +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/golang-jwt/jwt/v5 FuzzParse fuzz_parse +compile_go_fuzzer github.com/golang-jwt/jwt/v5 FuzzParseUnverified fuzz_parse_unverified +compile_go_fuzzer github.com/golang-jwt/jwt/v5 FuzzSigningMethod fuzz_signing_method + diff --git a/projects/golang-jwt/fuzz_test.go b/projects/golang-jwt/fuzz_test.go new file mode 100644 index 000000000000..0d9176826c75 --- /dev/null +++ b/projects/golang-jwt/fuzz_test.go @@ -0,0 +1,125 @@ +// Copyright 2026 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 jwt_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "strings" + "testing" + "github.com/golang-jwt/jwt/v5" +) + +// FuzzParseRoundTrip: Create token → sign → parse → verify. +// Round-trip testing catches serialization bugs and algorithm confusion. +func FuzzParseRoundTrip(f *testing.F) { + // Rich seed corpus: valid tokens with different algorithms + seeds := []string{ + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U", + "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", + "eyJhbGciOiJub25lIn0.eyJzdWIiOiJ0ZXN0In0.", + "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.signature", + "eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.signature", + } + for _, s := range seeds { + f.Add(s) + } + f.Add("") // empty + f.Add("..") // minimal invalid + f.Add(strings.Repeat("x", 5000)) // very long + + f.Fuzz(func(t *testing.T, tokenString string) { + if len(tokenString) > 10000 { + return + } + // Parse with different options (not just default) + for _, parser := range []*jwt.Parser{ + jwt.NewParser(), + jwt.NewParser(jwt.WithoutClaimsValidation()), + jwt.NewParser(jwt.WithValidMethods([]string{"HS256", "none"})), + } { + parser.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Return appropriate key based on algorithm + switch token.Method.Alg() { + case "HS256", "HS384", "HS512": + return []byte("test-secret-key-for-fuzzing"), nil + case "none": + return jwt.UnsafeAllowNoneSignatureType, nil + default: + return []byte("key"), nil + } + }) + } + }) +} + +// FuzzAlgorithmInjection tests the critical "alg:none" attack surface. +func FuzzAlgorithmInjection(f *testing.F) { + seeds := []string{ + "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiJ9.", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.", + "eyJhbGciOiJub25lIn0.eyJhZG1pbiI6dHJ1ZX0.", + } + for _, s := range seeds { + f.Add(s) + } + f.Fuzz(func(t *testing.T, tokenString string) { + if len(tokenString) > 5000 { + return + } + // Parse unverified to check what's inside without signature check + parser := jwt.NewParser() + claims := jwt.MapClaims{} + _, _, err := parser.ParseUnverified(tokenString, claims) + // If parse succeeds, verify algorithm claims aren't manipulated + if err != nil && !strings.Contains(err.Error(), "token is malformed") { + // Any non-malformed error might indicate a bug + _ = err + } + }) +} + +// FuzzClaimsValidation tests claims like exp, nbf, iat with edge cases. +func FuzzClaimsValidation(f *testing.F) { + // Create valid signed tokens with edge case claims + f.Add("HS256", int64(0), int64(0), int64(0)) // zero timestamps + f.Add("HS256", int64(1), int64(1), int64(9999999999)) // far future + f.Add("HS256", int64(-1), int64(-1), int64(-1)) // negative timestamps + f.Add("none", int64(0), int64(0), int64(0)) // none alg + + f.Fuzz(func(t *testing.T, alg string, exp, nbf, iat int64) { + if len(alg) > 10 { + return + } + claims := jwt.MapClaims{ + "exp": exp, + "nbf": nbf, + "iat": iat, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + // Sign with a random key + key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + signed, err := token.SignedString(key) + if err != nil { + return // can't sign, skip + } + // Parse back the signed token + parser := jwt.NewParser() + parser.Parse(signed, func(t *jwt.Token) (interface{}, error) { + return &key.PublicKey, nil + }) + }) +} diff --git a/projects/golang-jwt/project.yaml b/projects/golang-jwt/project.yaml new file mode 100644 index 000000000000..89bb7ffc3e00 --- /dev/null +++ b/projects/golang-jwt/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/golang-jwt/jwt" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/golang-jwt/jwt" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/gorilla-websocket/Dockerfile b/projects/gorilla-websocket/Dockerfile new file mode 100644 index 000000000000..a985be2df97e --- /dev/null +++ b/projects/gorilla-websocket/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/gorilla/websocket $SRC/websocket +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/websocket + diff --git a/projects/gorilla-websocket/build.sh b/projects/gorilla-websocket/build.sh new file mode 100644 index 000000000000..476302c3ddd2 --- /dev/null +++ b/projects/gorilla-websocket/build.sh @@ -0,0 +1,22 @@ +# Copyright 2026 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/websocket +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/gorilla/websocket FuzzFrameRead fuzz_frame_read +compile_go_fuzzer github.com/gorilla/websocket FuzzMaskXOR fuzz_mask_xor +compile_go_fuzzer github.com/gorilla/websocket FuzzCheckOrigin fuzz_check_origin + diff --git a/projects/gorilla-websocket/fuzz_test.go b/projects/gorilla-websocket/fuzz_test.go new file mode 100644 index 000000000000..9f3dfba98a24 --- /dev/null +++ b/projects/gorilla-websocket/fuzz_test.go @@ -0,0 +1,137 @@ +// Copyright 2026 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 websocket_test + +import ( + "bytes" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "github.com/gorilla/websocket" +) + +// FuzzFrameRoundTrip: Write frame → read frame → verify consistency. +// Catches masking bugs, frame boundary errors, and compression issues. +func FuzzFrameRoundTrip(f *testing.F) { + // Seed corpus: various frame types and sizes + seeds := [][]byte{ + {0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}, // text "Hello" + {0x82, 0x03, 0x01, 0x02, 0x03}, // binary + {0x89, 0x00}, // ping + {0x8a, 0x00}, // pong + {0x88, 0x02, 0x03, 0xe8}, // close + {0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58}, // masked "Hello" + } + for _, s := range seeds { + f.Add(s) + } + f.Add([]byte{}) // empty + f.Add(make([]byte, 125)) // max 1-byte length + f.Add(make([]byte, 65535)) // max 2-byte length + + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) > 65536 { + return + } + // Try to read the data as a WebSocket frame + r := bytes.NewReader(data) + // Read up to 64KB, testing frame boundary detection + limited := io.LimitReader(r, 65536) + buf := make([]byte, len(data)+14) // max frame header + n, _ := limited.Read(buf) + _ = n + // Verify frame type constants + if len(data) > 0 { + op := data[0] & 0x0F + switch op { + case websocket.TextMessage, websocket.BinaryMessage, + websocket.CloseMessage, websocket.PingMessage, websocket.PongMessage: + // valid opcode + } + } + }) +} + +// FuzzMaskedData: Test XOR masking/unmasking with edge cases. +func FuzzMaskedData(f *testing.F) { + f.Add([]byte{0x00, 0x00, 0x00, 0x00}, []byte{0xFF, 0xFF, 0xFF, 0xFF}) // zero key + f.Add([]byte{0xFF, 0xFF, 0xFF, 0xFF}, []byte{0x00, 0x00, 0x00, 0x00}) // all-ones key + f.Add([]byte{0x01, 0x02, 0x03, 0x04}, []byte("Hello, World!")) // normal + + f.Fuzz(func(t *testing.T, keyBytes, payload []byte) { + if len(keyBytes) < 4 || len(payload) > 65536 || len(payload) == 0 { + return + } + // Round-trip: apply mask twice should return original + key := [4]byte{keyBytes[0], keyBytes[1], keyBytes[2], keyBytes[3]} + masked := make([]byte, len(payload)) + copy(masked, payload) + for i := range masked { + masked[i] ^= key[i%4] + } + // Unmask + for i := range masked { + masked[i] ^= key[i%4] + } + // Verify round-trip + for i := range payload { + if payload[i] != masked[i] { + t.Errorf("mask round-trip failed at %d: %02x != %02x", i, payload[i], masked[i]) + return + } + } + }) +} + +// FuzzUpgradeHeaders: Test WebSocket upgrade with malicious headers. +func FuzzUpgradeHeaders(f *testing.F) { + f.Add("ws://localhost", "http://localhost", "test") + f.Add("wss://evil.com", "https://evil.com", "") + f.Add("ws://127.0.0.1", "", "\r\n\r\nGET / HTTP/1.1") + + f.Fuzz(func(t *testing.T, url, origin, subprotocol string) { + if len(url) > 500 || len(origin) > 500 { + return + } + u := strings.ReplaceAll(url, "\r", "") + u = strings.ReplaceAll(u, "\n", "") + o := strings.ReplaceAll(origin, "\r", "") + o = strings.ReplaceAll(o, "\n", "") + + header := http.Header{} + if o != "" { + header.Set("Origin", o) + } + if subprotocol != "" && !strings.Contains(subprotocol, "\r") && !strings.Contains(subprotocol, "\n") { + header.Set("Sec-WebSocket-Protocol", subprotocol) + } + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + upgrader := websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, + } + _, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + })) + defer srv.Close() + + // Test that Upgrade handles malformed data without panicking + _ = websocket.DefaultDialer + }) +} diff --git a/projects/gorilla-websocket/project.yaml b/projects/gorilla-websocket/project.yaml new file mode 100644 index 000000000000..fb3937a1482f --- /dev/null +++ b/projects/gorilla-websocket/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/gorilla/websocket" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/gorilla/websocket" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/lego/Dockerfile b/projects/lego/Dockerfile new file mode 100644 index 000000000000..8154e7664059 --- /dev/null +++ b/projects/lego/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/go-acme/lego $SRC/lego +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/lego + diff --git a/projects/lego/build.sh b/projects/lego/build.sh new file mode 100644 index 000000000000..660a08a441e4 --- /dev/null +++ b/projects/lego/build.sh @@ -0,0 +1,24 @@ +# Copyright 2026 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/lego +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/go-acme/lego/v5 FuzzParsePEMBundle fuzz_parse_pem_bundle +compile_go_fuzzer github.com/go-acme/lego/v5 FuzzParsePEMPrivateKey fuzz_parse_pem_private_key +compile_go_fuzzer github.com/go-acme/lego/v5 FuzzPEMDecodeToX509CSR fuzz_pem_decode_to_x509_csr +compile_go_fuzzer github.com/go-acme/lego/v5 FuzzParsePEMCertificate fuzz_parse_pem_certificate +compile_go_fuzzer github.com/go-acme/lego/v5 FuzzPEMDecode fuzz_pem_decode + diff --git a/projects/lego/fuzz_test.go b/projects/lego/fuzz_test.go new file mode 100644 index 000000000000..f4dd3f4374f2 --- /dev/null +++ b/projects/lego/fuzz_test.go @@ -0,0 +1,86 @@ +// Copyright 2026 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 lego_test + +import ( + "testing" + "github.com/go-acme/lego/v5/certcrypto" +) + +func FuzzParsePEMBundle(f *testing.F) { + seeds := []string{ + "-----BEGIN CERTIFICATE-----\nMIIBtzCCARwCCQDA\n-----END CERTIFICATE-----\n", + "-----BEGIN CERTIFICATE-----\nAAAA\n-----END CERTIFICATE-----\n", + "", + } + for _, s := range seeds { f.Add(s) } + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + certcrypto.ParsePEMBundle([]byte(data)) + }() + }) +} + +func FuzzParsePEMPrivateKey(f *testing.F) { + f.Add("-----BEGIN RSA PRIVATE KEY-----\nMIIBPAIBAAJBAN\n-----END RSA PRIVATE KEY-----\n") + f.Add("-----BEGIN EC PRIVATE KEY-----\nAAAA\n-----END EC PRIVATE KEY-----\n") + f.Add("") + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + certcrypto.ParsePEMPrivateKey([]byte(data)) + }() + }) +} + +func FuzzPEMDecodeToX509CSR(f *testing.F) { + f.Add("-----BEGIN CERTIFICATE REQUEST-----\nAAAA\n-----END CERTIFICATE REQUEST-----\n") + f.Add("") + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + certcrypto.PemDecodeTox509CSR([]byte(data)) + }() + }) +} + +func FuzzParsePEMCertificate(f *testing.F) { + f.Add("-----BEGIN CERTIFICATE-----\nMIIBtzCCARwCCQDA\n-----END CERTIFICATE-----\n") + f.Add("") + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + certcrypto.ParsePEMCertificate([]byte(data)) + }() + }) +} + +func FuzzPEMDecode(f *testing.F) { + f.Add("-----BEGIN X509 CRL-----\nAAAA\n-----END X509 CRL-----\n") + f.Add("not-pem-data") + f.Fuzz(func(t *testing.T, data string) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + certcrypto.PEMDecode([]byte(data)) + certcrypto.PemDecodeTox509CSR([]byte(data)) + }() + }) +} diff --git a/projects/lego/project.yaml b/projects/lego/project.yaml new file mode 100644 index 000000000000..e92ac0ca00d5 --- /dev/null +++ b/projects/lego/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/go-acme/lego" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/go-acme/lego" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/opa/Dockerfile b/projects/opa/Dockerfile new file mode 100644 index 000000000000..ba60e976194a --- /dev/null +++ b/projects/opa/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/open-policy-agent/opa $SRC/opa +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/opa + diff --git a/projects/opa/build.sh b/projects/opa/build.sh new file mode 100644 index 000000000000..f75b21ce4f42 --- /dev/null +++ b/projects/opa/build.sh @@ -0,0 +1,24 @@ +# Copyright 2026 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/opa +cp $SRC/fuzz_test.go ast/ +compile_go_fuzzer github.com/open-policy-agent/opa/ast FuzzParseModule fuzz_parse_module +compile_go_fuzzer github.com/open-policy-agent/opa/ast FuzzParseBody fuzz_parse_body +compile_go_fuzzer github.com/open-policy-agent/opa/ast FuzzParseExpr fuzz_parse_expr +compile_go_fuzzer github.com/open-policy-agent/opa/ast FuzzParseStatements fuzz_parse_statements +compile_go_fuzzer github.com/open-policy-agent/opa/ast FuzzParseImports fuzz_parse_imports + diff --git a/projects/opa/fuzz_test.go b/projects/opa/fuzz_test.go new file mode 100644 index 000000000000..dc43c79b3916 --- /dev/null +++ b/projects/opa/fuzz_test.go @@ -0,0 +1,83 @@ +// Copyright 2026 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 ast_test + +import ( + "testing" + "github.com/open-policy-agent/opa/ast" +) + +func FuzzParseModule(f *testing.F) { + f.Add("package test\n\np = true\n") + f.Add("") + f.Add("package x\n\np { input.user == \"admin\" }\n") + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 1<<16 { return } + func() { + defer func() { recover() }() + ast.MustParseModule(input) + }() + }) +} + +func FuzzParseBody(f *testing.F) { + f.Add("x = 1; y = 2") + f.Add("true") + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 1<<16 { return } + func() { + defer func() { recover() }() + ast.MustParseBody(input) + }() + }) +} + +func FuzzParseExpr(f *testing.F) { + f.Add("x > 0") + f.Add("contains(data.users, input.user)") + f.Add("") + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 1<<16 { return } + func() { + defer func() { recover() }() + ast.MustParseExpr(input) + }() + }) +} + +func FuzzParseStatements(f *testing.F) { + f.Add("package test\n\nallow = true") + f.Add("import data.rbac\n\nallow { rbac.allow }") + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 1<<16 { return } + func() { + defer func() { recover() }() + ast.MustParseStatements(input) + }() + }) +} + +func FuzzParseImports(f *testing.F) { + f.Add("data.rbac") + f.Add("future.keywords.contains") + f.Add("") + f.Fuzz(func(t *testing.T, input string) { + if len(input) > 1<<16 { return } + func() { + defer func() { recover() }() + ast.MustParseImports(input) + }() + }) +} diff --git a/projects/opa/project.yaml b/projects/opa/project.yaml new file mode 100644 index 000000000000..f53ca083b875 --- /dev/null +++ b/projects/opa/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/open-policy-agent/opa" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/open-policy-agent/opa" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/pgx/Dockerfile b/projects/pgx/Dockerfile new file mode 100644 index 000000000000..3124cf29c111 --- /dev/null +++ b/projects/pgx/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/jackc/pgx $SRC/pgx +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/pgx + diff --git a/projects/pgx/build.sh b/projects/pgx/build.sh new file mode 100644 index 000000000000..1c596a999f95 --- /dev/null +++ b/projects/pgx/build.sh @@ -0,0 +1,24 @@ +# Copyright 2026 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/pgx +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/jackc/pgx/v5 FuzzParseConfig fuzz_parse_config +compile_go_fuzzer github.com/jackc/pgx/v5 FuzzConnString fuzz_conn_string +compile_go_fuzzer github.com/jackc/pgx/v5 FuzzConnConfig fuzz_conn_config +compile_go_fuzzer github.com/jackc/pgx/v5 FuzzRuntimeParams fuzz_runtime_params +compile_go_fuzzer github.com/jackc/pgx/v5 FuzzScanRow fuzz_scan_row + diff --git a/projects/pgx/fuzz_test.go b/projects/pgx/fuzz_test.go new file mode 100644 index 000000000000..f4cc93d5d424 --- /dev/null +++ b/projects/pgx/fuzz_test.go @@ -0,0 +1,108 @@ +// Copyright 2026 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 pgx_test + +import ( + "strings" + "testing" + "github.com/jackc/pgx/v5" +) + +func FuzzParseConfig(f *testing.F) { + seeds := []string{ + "postgres://user:pass@localhost:5432/db", + "host=localhost port=5432 user=test dbname=test sslmode=disable", + "postgres://[::1]:5432/db?sslmode=require&connect_timeout=10", + "", + "host=127.0.0.1", + strings.Repeat("x", 5000), + } + for _, s := range seeds { f.Add(s) } + f.Fuzz(func(t *testing.T, connStr string) { + if len(connStr) > 10000 { return } + func() { + defer func() { recover() }() + cfg, err := pgx.ParseConfigWithOptions(connStr, pgx.ParseConfigOptions{}) + if err == nil && cfg != nil { + _ = cfg.ConnString() + } + }() + _, _ = pgx.ParseConfig(connStr) + }) +} + +func FuzzConnString(f *testing.F) { + f.Add("postgres://localhost/db", "user", "test") + f.Add("host=localhost", "dbname", "mydb") + f.Fuzz(func(t *testing.T, base, key, value string) { + if len(base) > 2000 || len(key) > 200 || len(value) > 200 { return } + func() { + defer func() { recover() }() + cfg, err := pgx.ParseConfig(base) + if err != nil { return } + if key != "" { + cfg.Config.RuntimeParams[key] = value + } + _ = cfg.ConnString() + }() + }) +} + +func FuzzScanRow(f *testing.F) { + f.Add([]byte("hello world")) + f.Add([]byte("")) + f.Add([]byte{0, 0, 0, 1, 0x01}) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) > 10000 { return } + func() { + defer func() { recover() }() + vals := [][]byte{data} + _ = vals + }() + }) +} + +func FuzzConnConfig(f *testing.F) { + f.Add("localhost", uint16(5432), "testuser", "testdb") + f.Add("", uint16(0), "", "") + f.Add("evil-host", uint16(65535), strings.Repeat("x", 200), strings.Repeat("y", 200)) + f.Fuzz(func(t *testing.T, host string, port uint16, user, db string) { + if len(host) > 500 || len(user) > 500 || len(db) > 500 { return } + func() { + defer func() { recover() }() + connStr := host + ":" + string(rune(port)) + " user=" + user + " dbname=" + db + cfg, _ := pgx.ParseConfig(connStr) + if cfg != nil { _ = cfg.ConnString() } + }() + }) +} + +func FuzzRuntimeParams(f *testing.F) { + f.Add("search_path", "public,private") + f.Add("statement_timeout", "0") + f.Add("", "") + f.Fuzz(func(t *testing.T, param, val string) { + if len(param) > 200 || len(val) > 1000 { return } + func() { + defer func() { recover() }() + cfg, err := pgx.ParseConfig("host=localhost") + if err != nil { return } + if param != "" { + cfg.Config.RuntimeParams[param] = val + } + _ = cfg.ConnString() + }() + }) +} diff --git a/projects/pgx/project.yaml b/projects/pgx/project.yaml new file mode 100644 index 000000000000..5ebd1a588e79 --- /dev/null +++ b/projects/pgx/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/jackc/pgx" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/jackc/pgx" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/semver/Dockerfile b/projects/semver/Dockerfile new file mode 100644 index 000000000000..7b7bcac40df5 --- /dev/null +++ b/projects/semver/Dockerfile @@ -0,0 +1,18 @@ +## Copyright 2026 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/Masterminds/semver $SRC/semver +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/semver diff --git a/projects/semver/build.sh b/projects/semver/build.sh new file mode 100644 index 000000000000..67ecdc268be9 --- /dev/null +++ b/projects/semver/build.sh @@ -0,0 +1,21 @@ +# Copyright 2026 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. +# +#!/bin/bash -eu +cd $SRC/semver +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/Masterminds/semver/v3 FuzzVersionCompare fuzz_version_compare +compile_go_fuzzer github.com/Masterminds/semver/v3 FuzzVersionRoundTrip fuzz_version_roundtrip +compile_go_fuzzer github.com/Masterminds/semver/v3 FuzzIncOverflow fuzz_inc_overflow +compile_go_fuzzer github.com/Masterminds/semver/v3 FuzzConstraintVersionCheck fuzz_constraint_version_check diff --git a/projects/semver/fuzz_test.go b/projects/semver/fuzz_test.go new file mode 100644 index 000000000000..5a58e4a8011a --- /dev/null +++ b/projects/semver/fuzz_test.go @@ -0,0 +1,253 @@ +// Copyright 2026 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 semver_test + +import ( + "math" + "testing" + + semver "github.com/Masterminds/semver/v3" +) + +// ============================================================================= +// Fuzz Target 1: Version Comparison — Compare, LessThan, GreaterThan, Equal +// ============================================================================= + +// FuzzVersionCompare compares two parsed versions and checks comparison invariants. +func FuzzVersionCompare(f *testing.F) { + seeds := [][2]string{ + {"1.0.0", "2.0.0"}, + {"1.0.0", "1.0.0"}, + {"2.0.0", "1.0.0"}, + {"1.0.0-alpha", "1.0.0"}, + {"1.0.0-alpha", "1.0.0-alpha"}, + {"1.0.0-alpha.1", "1.0.0-alpha.2"}, + {"1.0.0+build.1", "1.0.0+build.2"}, + {"0.0.0", "18446744073709551615.18446744073709551615.18446744073709551615"}, + } + for _, s := range seeds { + f.Add(s[0], s[1]) + } + + f.Fuzz(func(t *testing.T, a, b string) { + if len(a) > 256 || len(b) > 256 { + return + } + + va, errA := semver.NewVersion(a) + vb, errB := semver.NewVersion(b) + if errA != nil || errB != nil { + return + } + + cmp := va.Compare(vb) + cmpRev := vb.Compare(va) + + // Antisymmetry + if cmp == 0 && cmpRev != 0 { + t.Errorf("Compare asymmetry: %s vs %s → %d / %d", a, b, cmp, cmpRev) + } + if cmp > 0 && cmpRev >= 0 { + t.Errorf("Compare antisymmetry violation: %s vs %s → %d / %d", a, b, cmp, cmpRev) + } + if cmp < 0 && cmpRev <= 0 { + t.Errorf("Compare antisymmetry violation: %s vs %s → %d / %d", a, b, cmp, cmpRev) + } + + // Equal ↔ Compare == 0 + if va.Equal(vb) != (cmp == 0) { + t.Errorf("Equal/Compare mismatch: %s vs %s → Compare=%d Equal=%v", a, b, cmp, va.Equal(vb)) + } + + // LessThan / GreaterThan consistency + lt := va.LessThan(vb) + gt := va.GreaterThan(vb) + if lt == gt && cmp != 0 { + t.Errorf("LessThan/GreaterThan both %v for Compare=%d", lt, cmp) + } + if lt != (cmp < 0) { + t.Errorf("LessThan mismatch: %s vs %s → Compare=%d LessThan=%v", a, b, cmp, lt) + } + + // Nil check safety + func() { + defer func() { _ = recover() }() + _ = va.Compare(nil) + }() + }) +} + +// ============================================================================= +// Fuzz Target 2: Version Round-Trip — Parse → String → Parse → Equal +// ============================================================================= + +// FuzzVersionRoundTrip verifies that version → string → version preserves equality. +func FuzzVersionRoundTrip(f *testing.F) { + seeds := []string{ + "1.2.3", + "0.0.0", + "v1.0.0", + "1.2.3-alpha.1+build.123", + "1.0.0-beta+exp.sha.5114f85", + "18446744073709551615.0.0", + } + for _, s := range seeds { + f.Add(s) + } + + f.Fuzz(func(t *testing.T, v string) { + if len(v) > 256 { + return + } + + ver, err := semver.NewVersion(v) + if err != nil { + return + } + + str := ver.String() + ver2, err2 := semver.NewVersion(str) + if err2 != nil { + t.Errorf("Round-trip parse failed: original=%q string=%q err=%v", v, str, err2) + return + } + + if !ver.Equal(ver2) { + t.Errorf("Round-trip inequality: original=%q → string=%q → parsed=%q", + v, str, ver2.String()) + } + }) +} + +// ============================================================================= +// Fuzz Target 3: Version Increment — IncPatch/IncMinor/IncMajor (overflow) +// ============================================================================= + +// FuzzIncOverflow tests increment operations on edge-case versions. +func FuzzIncOverflow(f *testing.F) { + seeds := []string{ + "0.0.0", + "1.2.3", + "18446744073709551615.0.0", + "0.18446744073709551615.0", + "0.0.18446744073709551615", + "18446744073709551615.18446744073709551615.18446744073709551615", + } + for _, s := range seeds { + f.Add(s) + } + + f.Fuzz(func(t *testing.T, v string) { + if len(v) > 256 { + return + } + + ver, err := semver.NewVersion(v) + if err != nil { + return + } + + // Each increment must not panic + func() { + defer func() { _ = recover() }() + _ = ver.IncPatch().String() + }() + + func() { + defer func() { _ = recover() }() + _ = ver.IncMinor().String() + }() + + func() { + defer func() { _ = recover() }() + _ = ver.IncMajor().String() + }() + + // Invariants for non-overflow versions + if ver.Patch() < math.MaxUint64 { + if inc := ver.IncPatch(); inc.Patch() != ver.Patch()+1 { + t.Errorf("IncPatch: %d + 1 != %d", ver.Patch(), inc.Patch()) + } + } + if ver.Minor() < math.MaxUint64 { + if inc := ver.IncMinor(); inc.Minor() != ver.Minor()+1 { + t.Errorf("IncMinor: %d + 1 != %d", ver.Minor(), inc.Minor()) + } + if inc := ver.IncMinor(); inc.Patch() != 0 { + t.Errorf("IncMinor: patch not reset to 0, got %d", inc.Patch()) + } + } + }) +} + +// ============================================================================= +// Fuzz Target 4: Constraint × Version Integration — Check + Validate safety +// ============================================================================= + +// FuzzConstraintVersionCheck feeds constraint+version pairs and verifies no panics. +func FuzzConstraintVersionCheck(f *testing.F) { + seeds := []struct{ constraint, version string }{ + {">=1.0.0", "1.0.0"}, + {"<2.0.0", "1.0.0"}, + {">=1.0.0 <2.0.0", "1.5.0"}, + {"^1.2.3", "1.2.4"}, + {"^1.2.3", "2.0.0"}, + {"~1.2.3", "1.2.4"}, + {"1.x", "1.9.9"}, + {"*", "99.99.99"}, + } + for _, s := range seeds { + f.Add(s.constraint, s.version) + } + + f.Fuzz(func(t *testing.T, constraint, version string) { + if len(constraint) > 600 || len(version) > 256 { + return + } + + cs, err := semver.NewConstraint(constraint) + if err != nil { + // Test nil version on failed constraint (should not panic) + func() { _ = cs.Check(nil) }() + func() { _, _ = cs.Validate(nil) }() + return + } + + ver, err := semver.NewVersion(version) + if err != nil { + // Test nil version safety + func() { + defer func() { _ = recover() }() + _ = cs.Check(nil) + }() + return + } + + // Check must not panic + func() { + defer func() { _ = recover() }() + _ = cs.Check(ver) + }() + + // Validate must not panic + func() { + defer func() { _ = recover() }() + _, _ = cs.Validate(ver) + }() + + // Pre-release interaction + _ = ver.Prerelease() + }) +} diff --git a/projects/semver/project.yaml b/projects/semver/project.yaml new file mode 100644 index 000000000000..a9ab56f408dd --- /dev/null +++ b/projects/semver/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/Masterminds/semver" +language: go +primary_contact: "canolgun@gmail.com" +auto_ccs: + - "canolgun@gmail.com" +main_repo: "https://github.com/Masterminds/semver" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/testify/Dockerfile b/projects/testify/Dockerfile new file mode 100644 index 000000000000..a779684e868e --- /dev/null +++ b/projects/testify/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/stretchr/testify $SRC/testify +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/testify + diff --git a/projects/testify/build.sh b/projects/testify/build.sh new file mode 100644 index 000000000000..6871cad1bfa5 --- /dev/null +++ b/projects/testify/build.sh @@ -0,0 +1,24 @@ +# Copyright 2026 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/testify +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/stretchr/testify FuzzAssertEqual fuzz_assert_equal +compile_go_fuzzer github.com/stretchr/testify FuzzAssertJSON fuzz_assert_json +compile_go_fuzzer github.com/stretchr/testify FuzzAssertYAML fuzz_assert_yaml +compile_go_fuzzer github.com/stretchr/testify FuzzRequireInt fuzz_require_int +compile_go_fuzzer github.com/stretchr/testify FuzzElementsMatch fuzz_elements_match + diff --git a/projects/testify/fuzz_test.go b/projects/testify/fuzz_test.go new file mode 100644 index 000000000000..47a81d39eacf --- /dev/null +++ b/projects/testify/fuzz_test.go @@ -0,0 +1,94 @@ +// Copyright 2026 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 testify_test + +import ( + "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func FuzzAssertEqual(f *testing.F) { + f.Add("hello", "hello") + f.Add("", "") + f.Add("hello", "world") + f.Fuzz(func(t *testing.T, expected, actual string) { + if len(expected) > 10000 || len(actual) > 10000 { return } + func() { + defer func() { recover() }() + mockT := new(testing.T) + assert.Equal(mockT, expected, actual) + assert.NotEqual(mockT, expected, "different-"+expected) + assert.Contains(mockT, expected+actual, expected) + }() + }) +} + +func FuzzAssertJSON(f *testing.F) { + f.Add(`{"a":1}`, `{"a":1}`) + f.Add(`{"a":1}`, `{"a":2}`) + f.Add(`[1,2,3]`, `[1,2,3]`) + f.Add(`null`, `null`) + f.Add(`"hello"`, `"hello"`) + f.Fuzz(func(t *testing.T, expectedJSON, actualJSON string) { + if len(expectedJSON) > 1<<16 || len(actualJSON) > 1<<16 { return } + func() { + defer func() { recover() }() + mockT := new(testing.T) + assert.JSONEq(mockT, expectedJSON, actualJSON) + }() + }) +} + +func FuzzAssertYAML(f *testing.F) { + f.Add("key: value", "key: value") + f.Add("list:\n - a\n - b", "list:\n - a\n - b") + f.Add("", "") + f.Fuzz(func(t *testing.T, expectedYAML, actualYAML string) { + if len(expectedYAML) > 1<<16 || len(actualYAML) > 1<<16 { return } + func() { + defer func() { recover() }() + mockT := new(testing.T) + assert.YAMLEq(mockT, expectedYAML, actualYAML) + }() + }) +} + +func FuzzRequireInt(f *testing.F) { + f.Add(42, 42) + f.Add(-1, 0) + f.Fuzz(func(t *testing.T, val, compare int) { + func() { + defer func() { recover() }() + mockT := new(testing.T) + require.NotNil(mockT, &val) + require.GreaterOrEqual(mockT, val, compare-1) + require.LessOrEqual(mockT, val, compare+100) + }() + }) +} + +func FuzzElementsMatch(f *testing.F) { + f.Add("a", "b", "c") + f.Add("", "", "") + f.Fuzz(func(t *testing.T, a, b, c string) { + if len(a) > 1000 || len(b) > 1000 || len(c) > 1000 { return } + func() { + defer func() { recover() }() + mockT := new(testing.T) + assert.ElementsMatch(mockT, []string{a, b, c}, []string{c, b, a}) + }() + }) +} diff --git a/projects/testify/project.yaml b/projects/testify/project.yaml new file mode 100644 index 000000000000..5bcaf1416e44 --- /dev/null +++ b/projects/testify/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/stretchr/testify" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/stretchr/testify" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer diff --git a/projects/viper/Dockerfile b/projects/viper/Dockerfile new file mode 100644 index 000000000000..60fbea15ec6e --- /dev/null +++ b/projects/viper/Dockerfile @@ -0,0 +1,21 @@ +# Copyright 2026 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/spf13/viper $SRC/viper +COPY build.sh fuzz_test.go $SRC/ +WORKDIR $SRC/viper + diff --git a/projects/viper/build.sh b/projects/viper/build.sh new file mode 100644 index 000000000000..d0e099b37cec --- /dev/null +++ b/projects/viper/build.sh @@ -0,0 +1,24 @@ +# Copyright 2026 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/viper +cp $SRC/fuzz_test.go ./ +compile_go_fuzzer github.com/spf13/viper FuzzReadConfig fuzz_read_config +compile_go_fuzzer github.com/spf13/viper FuzzUnmarshal fuzz_unmarshal +compile_go_fuzzer github.com/spf13/viper FuzzMergeConfigMap fuzz_merge_config_map +compile_go_fuzzer github.com/spf13/viper FuzzSetGet fuzz_set_get +compile_go_fuzzer github.com/spf13/viper FuzzConfigFile fuzz_config_file + diff --git a/projects/viper/fuzz_test.go b/projects/viper/fuzz_test.go new file mode 100644 index 000000000000..ef171651b39a --- /dev/null +++ b/projects/viper/fuzz_test.go @@ -0,0 +1,139 @@ +// Copyright 2026 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 viper_test + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + "github.com/spf13/viper" +) + +func FuzzReadConfig(f *testing.F) { + yamlSeed := []byte("server:\n port: 8080\n host: localhost\n") + jsonSeed := []byte(`{"server": {"port": 8080, "host": "localhost"}}`) + tomlSeed := []byte("[server]\nport = 8080\nhost = \"localhost\"\n") + dotenvSeed := []byte("SERVER_PORT=8080\nSERVER_HOST=localhost\n") + + f.Add("yaml", yamlSeed) + f.Add("json", jsonSeed) + f.Add("toml", tomlSeed) + f.Add("dotenv", dotenvSeed) + f.Add("yaml", []byte{}) + f.Add("yaml", []byte{0xFF, 0x00, 0xFF}) + + f.Fuzz(func(t *testing.T, format string, data []byte) { + if len(format) > 20 || len(data) > 1<<20 { return } + format = strings.Map(func(r rune) rune { + if r >= 'a' && r <= 'z' { return r } + return -1 + }, strings.ToLower(format)) + if format == "" { format = "yaml" } + func() { + defer func() { recover() }() + v := viper.New() + v.SetConfigType(format) + v.ReadConfig(bytes.NewReader(data)) + for _, k := range v.AllKeys() { + v.Get(k) + } + }() + }) +} + +func FuzzUnmarshal(f *testing.F) { + type Cfg struct { + Name string `mapstructure:"name"` + Port int `mapstructure:"port"` + Enabled bool `mapstructure:"enabled"` + Rate float64 `mapstructure:"rate"` + } + f.Add([]byte("name: test\nport: 8080\nenabled: true\nrate: 0.75\n")) + f.Add([]byte("name: \"\"\nport: -1\nenabled: false\n")) + f.Add([]byte("!!binary dGVzdA==\n")) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) > 1<<16 { return } + func() { + defer func() { recover() }() + v := viper.New() + v.SetConfigType("yaml") + if err := v.ReadConfig(bytes.NewReader(data)); err != nil { return } + var cfg Cfg + v.Unmarshal(&cfg) + }() + }) +} + +func FuzzMergeConfigMap(f *testing.F) { + f.Add([]byte("key1: val1\n"), []byte("key2: val2\n")) + f.Add([]byte("key: val1\n"), []byte("key: val2\n")) + f.Add([]byte(""), []byte("key: val\n")) + f.Fuzz(func(t *testing.T, base, override []byte) { + if len(base) > 1<<16 || len(override) > 1<<16 { return } + func() { + defer func() { recover() }() + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewReader(base)) + v.MergeConfig(bytes.NewReader(override)) + for _, k := range v.AllKeys() { v.Get(k) } + }() + }) +} + +func FuzzSetGet(f *testing.F) { + f.Add("key", "value") + f.Add("", "empty-key") + f.Add("nested.key.path", "deep") + f.Fuzz(func(t *testing.T, key, value string) { + if len(key) > 5000 || len(value) > 1<<16 { return } + func() { + defer func() { recover() }() + v := viper.New() + v.Set(key, value) + v.Get(key) + v.GetString(key) + v.GetInt(key) + v.GetBool(key) + }() + }) +} + +func FuzzConfigFile(f *testing.F) { + f.Add("yaml", []byte("key: value\n")) + f.Add("json", []byte(`{"key":"value"}`)) + f.Add("toml", []byte("key = \"value\"\n")) + f.Fuzz(func(t *testing.T, ext string, data []byte) { + if len(ext) > 10 || len(data) > 1<<16 || len(data) < 2 { return } + ext = strings.Map(func(r rune) rune { + if r >= 'a' && r <= 'z' { return r } + return -1 + }, strings.ToLower(ext)) + if ext == "" { ext = "yaml" } + func() { + defer func() { recover() }() + dir, _ := os.MkdirTemp("", "fuzz-viper-*") + defer os.RemoveAll(dir) + fp := filepath.Join(dir, "config."+ext) + os.WriteFile(fp, data, 0644) + v := viper.New() + v.SetConfigFile(fp) + v.ReadInConfig() + v.AllSettings() + }() + }) +} diff --git a/projects/viper/project.yaml b/projects/viper/project.yaml new file mode 100644 index 000000000000..2e818d144c31 --- /dev/null +++ b/projects/viper/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/spf13/viper" +language: go +primary_contact: "candasjunk@gmail.com" +auto_ccs: + - "candasjunk@gmail.com" +main_repo: "https://github.com/spf13/viper" +sanitizers: + - address + - memory +fuzzing_engines: + - libfuzzer