diff --git a/contrib/fuzz/archive_fuzzer.go b/contrib/fuzz/archive_fuzzer.go index 4f46061f3..54efc7bc4 100644 --- a/contrib/fuzz/archive_fuzzer.go +++ b/contrib/fuzz/archive_fuzzer.go @@ -24,6 +24,8 @@ import ( fuzz "github.com/AdaLogics/go-fuzz-headers" "github.com/containerd/containerd/archive" + "github.com/containerd/containerd/content/local" + imageArchive "github.com/containerd/containerd/images/archive" ) // FuzzApply implements a fuzzer that applies @@ -50,3 +52,25 @@ func FuzzApply(data []byte) int { } return 1 } + +// FuzzImportIndex implements a fuzzer +// that targets archive.ImportIndex() +func FuzzImportIndex(data []byte) int { + f := fuzz.NewConsumer(data) + tarBytes, err := f.TarBytes() + if err != nil { + return 0 + } + ctx := context.Background() + r := bytes.NewReader(tarBytes) + tmpdir, err := ioutil.TempDir("", "fuzzing-") + if err != nil { + return 0 + } + cs, err := local.NewStore(tmpdir) + if err != nil { + return 0 + } + _, _ = imageArchive.ImportIndex(ctx, cs, r) + return 1 +} diff --git a/contrib/fuzz/content_fuzzer.go b/contrib/fuzz/content_fuzzer.go new file mode 100644 index 000000000..b2d04ec9e --- /dev/null +++ b/contrib/fuzz/content_fuzzer.go @@ -0,0 +1,138 @@ +// +build gofuzz + +/* + Copyright The containerd Authors. + 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. +*/ + +// nolint: golint +package fuzz + +import ( + "bytes" + "context" + _ "crypto/sha256" // required by go-digest + "fmt" + "io/ioutil" + "os" + "path/filepath" + "reflect" + + fuzz "github.com/AdaLogics/go-fuzz-headers" + digest "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/content/local" +) + +// checkBlobPath performs some basic validation +func checkBlobPath(dgst digest.Digest, root string) error { + if err := dgst.Validate(); err != nil { + return err + } + path := filepath.Join(root, "blobs", dgst.Algorithm().String(), dgst.Hex()) + _, err := os.Stat(path) + if err != nil { + return err + } + return nil +} + +// generateBlobs is a helper function to create random blobs +func generateBlobs(f *fuzz.ConsumeFuzzer) (map[digest.Digest][]byte, error) { + blobs := map[digest.Digest][]byte{} + blobQty, err := f.GetInt() + if err != nil { + return blobs, err + } + maxsize := 4096 + nblobs := blobQty % maxsize + + for i := 0; i < nblobs; i++ { + digestBytes, err := f.GetBytes() + if err != nil { + return blobs, err + } + + dgst := digest.FromBytes(digestBytes) + blobs[dgst] = digestBytes + } + + return blobs, nil +} + +// checkwrite is a wrapper around content.WriteBlob() +func checkWrite(ctx context.Context, cs content.Store, dgst digest.Digest, p []byte) (digest.Digest, error) { + if err := content.WriteBlob(ctx, cs, dgst.String(), bytes.NewReader(p), + ocispec.Descriptor{Size: int64(len(p)), Digest: dgst}); err != nil { + return dgst, err + } + return dgst, nil +} + +// populateBlobStore creates a bunch of blobs +func populateBlobStore(ctx context.Context, cs content.Store, f *fuzz.ConsumeFuzzer) (map[digest.Digest][]byte, error) { + blobs, err := generateBlobs(f) + if err != nil { + return nil, err + } + + for dgst, p := range blobs { + d, err := checkWrite(ctx, cs, dgst, p) + _ = d + if err != nil { + return blobs, err + } + } + return blobs, nil +} + +// FuzzCSWalk implements a fuzzer that targets contentStore.Walk() +func FuzzCSWalk(data []byte) int { + ctx := context.Background() + expected := map[digest.Digest]struct{}{} + found := map[digest.Digest]struct{}{} + tmpdir, err := ioutil.TempDir("", "fuzzing-") + if err != nil { + return 0 + } + cs, err := local.NewStore(tmpdir) + if err != nil { + return 0 + } + + f := fuzz.NewConsumer(data) + blobs, err := populateBlobStore(ctx, cs, f) + _ = blobs + if err != nil { + return 0 + } + + for dgst := range blobs { + expected[dgst] = struct{}{} + } + + if err := cs.Walk(ctx, func(bi content.Info) error { + found[bi.Digest] = struct{}{} + err = checkBlobPath(bi.Digest, tmpdir) + if err != nil { + return err + } + return nil + }); err != nil { + return 0 + } + if !reflect.DeepEqual(expected, found) { + panic(fmt.Sprintf("%v != %v but should be equal", found, expected)) + } + return 1 +} diff --git a/contrib/fuzz/oss_fuzz_build.sh b/contrib/fuzz/oss_fuzz_build.sh index c805c4201..d6f992c93 100755 --- a/contrib/fuzz/oss_fuzz_build.sh +++ b/contrib/fuzz/oss_fuzz_build.sh @@ -14,6 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +set -o nounset +set -o pipefail +set -o errexit +set -x + cd "$(dirname "${BASH_SOURCE[0]}")" cd ../../ @@ -26,7 +31,8 @@ compile_go_fuzzer github.com/containerd/containerd/remotes/docker FuzzFetcher fu compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzFiltersParse fuzz_filters_parse compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzPlatformsParse fuzz_platforms_parse compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzApply fuzz_apply - +compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzImportIndex fuzz_import_index +compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzCSWalk fuzz_cs_walk # FuzzCreateContainer requires more setup than the fuzzers above. # We need the binaries from "make".