diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index aa76c96..62d65db 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -29,3 +29,6 @@ jobs: run: | GO111MODULE=off go get github.com/mattn/goveralls $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github + + - name: Fuzz tests + run: make fuzz diff --git a/Makefile b/Makefile index f799baa..b666e52 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,12 @@ test: go test -v -cover ./... +fuzz: + go test -fuzztime=1m -fuzz . + +clean: + find . -name "test-suite-data.json" | xargs rm -f + lint: go get -u golang.org/x/lint/golint golint -set_exit_status diff --git a/README.md b/README.md index b7fd200..4326ae3 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,19 @@ PASS coverage: 90.7% of statements ok github.com/package-url/packageurl-go 0.004s coverage: 90.7% of statements ``` + +## Fuzzing + +Fuzzing is done with standard [Go fuzzing](https://go.dev/doc/fuzz/), introduced in Go 1.18. + +Fuzz tests check for inputs that cause `FromString` to panic. + +Using `make fuzz` will run fuzz tests for one minute. + +To run fuzz tests longer: + +``` +go test -fuzztime=60m -fuzz . +``` + +Or omit `-fuzztime` entirely to run indefinitely. diff --git a/fuzz_test.go b/fuzz_test.go new file mode 100644 index 0000000..264c6db --- /dev/null +++ b/fuzz_test.go @@ -0,0 +1,36 @@ +/* +Copyright (c) the purl authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package packageurl + +import ( + "fmt" + "testing" +) + +func FuzzFromString(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + // Test that parsing doesn't panic. + _, _ = FromString(s) + fmt.Print(s) + }) +} diff --git a/packageurl.go b/packageurl.go index d319d6c..ce49f8c 100644 --- a/packageurl.go +++ b/packageurl.go @@ -249,6 +249,9 @@ func FromString(purl string) (PackageURL, error) { qualifier := remainder[index+1:] for _, item := range strings.Split(qualifier, "&") { kv := strings.Split(item, "=") + if len(kv) != 2 { + return PackageURL{}, fmt.Errorf("wanted 2 kv segments, got %d", len(kv)) + } key := strings.ToLower(kv[0]) key, err := url.PathUnescape(key) if err != nil { diff --git a/testdata/fuzz/FuzzFromString/771e938e4458e983a736261a702e27c7a414fd660a15b63034f290b146d2f217 b/testdata/fuzz/FuzzFromString/771e938e4458e983a736261a702e27c7a414fd660a15b63034f290b146d2f217 new file mode 100644 index 0000000..ee3f339 --- /dev/null +++ b/testdata/fuzz/FuzzFromString/771e938e4458e983a736261a702e27c7a414fd660a15b63034f290b146d2f217 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0") diff --git a/testdata/fuzz/FuzzFromString/d0a861fe9b7c443af2b649e08753442111b630dd29fcd570543db3f9351158aa b/testdata/fuzz/FuzzFromString/d0a861fe9b7c443af2b649e08753442111b630dd29fcd570543db3f9351158aa new file mode 100644 index 0000000..e39cf64 --- /dev/null +++ b/testdata/fuzz/FuzzFromString/d0a861fe9b7c443af2b649e08753442111b630dd29fcd570543db3f9351158aa @@ -0,0 +1,2 @@ +go test fuzz v1 +string("?A")