0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

Add fuzz tests for storage_fs (#601)

This commit uses native go fuzzing to fuzz test implementations
of storage in storage_fs.

moved fuzzing testdata for storage_fs in separate repo

added make target and script for importing fuzz data and running all fuzz tests

Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro>
This commit is contained in:
alexstan12 2022-07-27 20:37:55 +03:00 committed by GitHub
parent b5f27c5b50
commit 16e9822c7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 870 additions and 12 deletions

View file

@ -283,3 +283,14 @@ bats-metrics: binary check-skopeo $(BATS)
bats-metrics-verbose: EXTENSIONS=metrics bats-metrics-verbose: EXTENSIONS=metrics
bats-metrics-verbose: binary check-skopeo $(BATS) bats-metrics-verbose: binary check-skopeo $(BATS)
$(BATS) --trace -p --verbose-run --print-output-on-failure --show-output-of-passing-tests test/blackbox/metrics.bats $(BATS) --trace -p --verbose-run --print-output-on-failure --show-output-of-passing-tests test/blackbox/metrics.bats
.PHONY: fuzz-all
fuzz-all: fuzztime=${1}
fuzz-all:
rm -rf test-data; \
rm -rf pkg/storage/testdata; \
git clone https://github.com/project-zot/test-data.git; \
mv test-data/storage pkg/storage/testdata; \
rm -rf test-data; \
bash test/scripts/fuzzAll.sh ${fuzztime}; \
rm -rf pkg/storage/testdata; \

13
README_fuzz.md Normal file
View file

@ -0,0 +1,13 @@
# Fuzzing in Zot
This project makes use of native Go 1.18 fuzzing. An in-depth tutorial for fuzzing in Go can be found [here](https://go.dev/doc/fuzz/).
As language specifies, fuzz tests are included among unit-tests, inside the the same `*_test.go files`, in the packages they intend to fuzz. See [fuzzing for local storage](pkg/storage/local_test.go)
Zot doesn't store the test data for fuzzing in the same repo, nor it is added before fuzzing with `(*testing.F).Add` . Instead, it is stored in a separate repo called [test-data](https://github.com/project-zot/test-data).
To start fuzzing locally, one can use the Make target [fuzz-all](Makefile) .
**The default runtime for each fuzz test is 10s**, which can be overriden with the **fuzztime** variable
```
make fuzz-all fuzztime=20
```
By running this target, testdata for fuzzing gets downloaded from the previously mentioned repo and the fuzzing begins for every fuzz function that is found.

View file

@ -7,13 +7,16 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"unicode/utf8"
apexlog "github.com/apex/log" apexlog "github.com/apex/log"
guuid "github.com/gofrs/uuid" guuid "github.com/gofrs/uuid"
@ -186,6 +189,13 @@ func (is *ImageStoreLocal) Unlock(lockStart *time.Time) {
func (is *ImageStoreLocal) initRepo(name string) error { func (is *ImageStoreLocal) initRepo(name string) error {
repoDir := path.Join(is.rootDir, name) repoDir := path.Join(is.rootDir, name)
if !utf8.ValidString(name) {
is.log.Error().Msg("input is not valid UTF-8")
return zerr.ErrInvalidRepositoryName
}
// create "blobs" subdir // create "blobs" subdir
err := ensureDir(path.Join(repoDir, "blobs"), is.log) err := ensureDir(path.Join(repoDir, "blobs"), is.log)
if err != nil { if err != nil {
@ -850,6 +860,7 @@ func (is *ImageStoreLocal) NewBlobUpload(repo string) (string, error) {
if err != nil { if err != nil {
return "", zerr.ErrRepoNotFound return "", zerr.ErrRepoNotFound
} }
defer file.Close() defer file.Close()
return uid, nil return uid, nil
@ -859,6 +870,12 @@ func (is *ImageStoreLocal) NewBlobUpload(repo string) (string, error) {
func (is *ImageStoreLocal) GetBlobUpload(repo, uuid string) (int64, error) { func (is *ImageStoreLocal) GetBlobUpload(repo, uuid string) (int64, error) {
blobUploadPath := is.BlobUploadPath(repo, uuid) blobUploadPath := is.BlobUploadPath(repo, uuid)
if !utf8.ValidString(blobUploadPath) {
is.log.Error().Msg("input is not valid UTF-8")
return -1, zerr.ErrInvalidRepositoryName
}
binfo, err := os.Stat(blobUploadPath) binfo, err := os.Stat(blobUploadPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -1673,12 +1690,23 @@ func ifOlderThan(imgStore *ImageStoreLocal, repo string, delay time.Duration) ca
} }
func DirExists(d string) bool { func DirExists(d string) bool {
fi, err := os.Stat(d) if !utf8.ValidString(d) {
return false
}
fileInfo, err := os.Stat(d)
if err != nil {
if e, ok := err.(*fs.PathError); ok && errors.Is(e.Err, syscall.ENAMETOOLONG) || //nolint: errorlint
errors.Is(e.Err, syscall.EINVAL) {
return false
}
}
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(err) {
return false return false
} }
if !fi.IsDir() { if !fileInfo.IsDir() {
return false return false
} }

View file

@ -5,22 +5,26 @@ import (
"crypto/rand" "crypto/rand"
_ "crypto/sha256" _ "crypto/sha256"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
"path" "path"
"strings" "strings"
"syscall"
"testing" "testing"
"time" "time"
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
imeta "github.com/opencontainers/image-spec/specs-go"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
"github.com/rs/zerolog" "github.com/rs/zerolog"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/errors" zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/monitoring" "zotregistry.io/zot/pkg/extensions/monitoring"
"zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage" "zotregistry.io/zot/pkg/storage"
@ -40,10 +44,8 @@ func TestStorageFSAPIs(t *testing.T) {
true, log, metrics, nil) true, log, metrics, nil)
Convey("Repo layout", t, func(c C) { Convey("Repo layout", t, func(c C) {
repoName := "test"
Convey("Bad image manifest", func() { Convey("Bad image manifest", func() {
upload, err := imgStore.NewBlobUpload("test") upload, err := imgStore.NewBlobUpload(repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(upload, ShouldNotBeEmpty) So(upload, ShouldNotBeEmpty)
@ -56,17 +58,17 @@ func TestStorageFSAPIs(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(blob, ShouldEqual, buflen) So(blob, ShouldEqual, buflen)
err = imgStore.FinishBlobUpload("test", upload, buf, digest.String()) err = imgStore.FinishBlobUpload(repoName, upload, buf, digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
annotationsMap := make(map[string]string) annotationsMap := make(map[string]string)
annotationsMap[ispec.AnnotationRefName] = tag annotationsMap[ispec.AnnotationRefName] = tag
cblob, cdigest := test.GetRandomImageConfig() cblob, cdigest := test.GetRandomImageConfig()
_, clen, err := imgStore.FullBlobUpload("test", bytes.NewReader(cblob), cdigest.String()) _, clen, err := imgStore.FullBlobUpload(repoName, bytes.NewReader(cblob), cdigest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(clen, ShouldEqual, len(cblob)) So(clen, ShouldEqual, len(cblob))
hasBlob, _, err := imgStore.CheckBlob("test", cdigest.String()) hasBlob, _, err := imgStore.CheckBlob(repoName, cdigest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(hasBlob, ShouldEqual, true) So(hasBlob, ShouldEqual, true)
@ -195,7 +197,6 @@ func TestGetReferrers(t *testing.T) {
Size: int64(buflen), Size: int64(buflen),
} }
artifactManifest.Blobs = []artifactspec.Descriptor{} artifactManifest.Blobs = []artifactspec.Descriptor{}
manBuf, err := json.Marshal(artifactManifest) manBuf, err := json.Marshal(artifactManifest)
manBufLen := len(manBuf) manBufLen := len(manBuf)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -214,6 +215,711 @@ func TestGetReferrers(t *testing.T) {
}) })
} }
func FuzzNewBlobUpload(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
dir := t.TempDir()
defer os.RemoveAll(dir)
t.Logf("Input argument is %s", data)
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, log, metrics, nil)
_, err := imgStore.NewBlobUpload(data)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzPutBlobChunk(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
dir := t.TempDir()
defer os.RemoveAll(dir)
t.Logf("Input argument is %s", data)
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, log, metrics, nil)
repoName := data
uuid, err := imgStore.NewBlobUpload(repoName)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
buf := bytes.NewBuffer([]byte(data))
buflen := buf.Len()
_, err = imgStore.PutBlobChunk(repoName, uuid, 0, int64(buflen), buf)
if err != nil {
t.Error(err)
}
})
}
func FuzzPutBlobChunkStreamed(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
dir := t.TempDir()
defer os.RemoveAll(dir)
t.Logf("Input argument is %s", data)
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, log, metrics, nil)
repoName := data
uuid, err := imgStore.NewBlobUpload(repoName)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
buf := bytes.NewBuffer([]byte(data))
_, err = imgStore.PutBlobChunkStreamed(repoName, uuid, buf)
if err != nil {
t.Error(err)
}
})
}
func FuzzGetBlobUpload(f *testing.F) {
f.Fuzz(func(t *testing.T, data1 string, data2 string) {
dir := t.TempDir()
defer os.RemoveAll(dir)
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, log, metrics, nil)
_, err := imgStore.GetBlobUpload(data1, data2)
if err != nil {
if errors.Is(err, zerr.ErrUploadNotFound) || isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzTestPutGetImageManifest(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
cblob, cdigest := test.GetRandomImageConfig()
ldigest, lblob, err := newRandomBlobForFuzz(data)
if err != nil {
t.Errorf("error occurred while generating random blob, %v", err)
}
_, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(cblob), cdigest.String())
if err != nil {
t.Error(err)
}
_, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(lblob), ldigest.String())
if err != nil {
t.Error(err)
}
manifest, err := NewRandomImgManifest(data, cdigest, ldigest, cblob, lblob)
if err != nil {
t.Error(err)
}
manifestBuf, err := json.Marshal(manifest)
if err != nil {
t.Errorf("Error %v occurred while marshaling manifest", err)
}
mdigest := godigest.FromBytes(manifestBuf)
_, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf)
if err != nil && errors.Is(err, zerr.ErrBadManifest) {
t.Errorf("the error that occurred is %v \n", err)
}
_, _, _, err = imgStore.GetImageManifest(repoName, mdigest.String())
if err != nil {
t.Errorf("the error that occurred is %v \n", err)
}
})
}
func FuzzTestPutDeleteImageManifest(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
cblob, cdigest := test.GetRandomImageConfig()
ldigest, lblob, err := newRandomBlobForFuzz(data)
if err != nil {
t.Errorf("error occurred while generating random blob, %v", err)
}
_, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(cblob), cdigest.String())
if err != nil {
t.Error(err)
}
_, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(lblob), ldigest.String())
if err != nil {
t.Error(err)
}
manifest, err := NewRandomImgManifest(data, cdigest, ldigest, cblob, lblob)
if err != nil {
t.Error(err)
}
manifestBuf, err := json.Marshal(manifest)
if err != nil {
t.Errorf("Error %v occurred while marshaling manifest", err)
}
mdigest := godigest.FromBytes(manifestBuf)
_, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf)
if err != nil && errors.Is(err, zerr.ErrBadManifest) {
t.Errorf("the error that occurred is %v \n", err)
}
err = imgStore.DeleteImageManifest(repoName, mdigest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Errorf("the error that occurred is %v \n", err)
}
})
}
// no integration with PutImageManifest, just throw fuzz data.
func FuzzTestDeleteImageManifest(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
digest, _, err := newRandomBlobForFuzz(data)
if err != nil {
return
}
err = imgStore.DeleteImageManifest(string(data), digest.String())
if err != nil {
if errors.Is(err, zerr.ErrRepoNotFound) || isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzDirExists(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) { //nolint: unusedparams
_ = storage.DirExists(data)
})
}
func FuzzInitRepo(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
err := imgStore.InitRepo(data)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzInitValidateRepo(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
err := imgStore.InitRepo(data)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
_, err = imgStore.ValidateRepo(data)
if err != nil {
if errors.Is(err, zerr.ErrRepoNotFound) || errors.Is(err, zerr.ErrRepoBadVersion) || isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzGetImageTags(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
_, err := imgStore.GetImageTags(data)
if err != nil {
if errors.Is(err, zerr.ErrRepoNotFound) || isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzBlobUploadPath(f *testing.F) {
f.Fuzz(func(t *testing.T, repo, uuid string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
_ = imgStore.BlobUploadPath(repo, uuid)
})
}
func FuzzBlobUploadInfo(f *testing.F) {
f.Fuzz(func(t *testing.T, data string, uuid string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
repo := data
_, err := imgStore.BlobUploadInfo(repo, uuid)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzTestGetImageManifest(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
dir := t.TempDir()
defer os.RemoveAll(dir)
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, log, metrics, nil)
repoName := data
digest := godigest.FromBytes([]byte(data))
_, _, _, err := imgStore.GetImageManifest(repoName, digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzFinishBlobUpload(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
dir := t.TempDir()
defer os.RemoveAll(dir)
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, log, metrics, nil)
repoName := data
upload, err := imgStore.NewBlobUpload(repoName)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
content := []byte(data)
buf := bytes.NewBuffer(content)
buflen := buf.Len()
digest := godigest.FromBytes(content)
_, err = imgStore.PutBlobChunk(repoName, upload, 0, int64(buflen), buf)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
err = imgStore.FinishBlobUpload(repoName, upload, buf, digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzFullBlobUpload(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := "test"
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
ldigest, lblob, err := newRandomBlobForFuzz(data)
if err != nil {
t.Errorf("error occurred while generating random blob, %v", err)
}
_, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(lblob), ldigest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzDedupeBlob(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
blobDigest := godigest.FromString(data)
// replacement for .uploads folder, usually retrieved from BlobUploadPath
src := path.Join(imgStore.RootDir(), "src")
blob := bytes.NewReader([]byte(data))
_, _, err := imgStore.FullBlobUpload("repoName", blob, blobDigest.String())
if err != nil {
t.Error(err)
}
dst := imgStore.BlobPath("repoName", blobDigest)
err = os.MkdirAll(src, 0o755)
if err != nil {
t.Error(err)
}
err = imgStore.DedupeBlob(src, blobDigest, dst)
if err != nil {
t.Error(err)
}
})
}
func FuzzDeleteBlobUpload(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := data
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
uuid, err := imgStore.NewBlobUpload(repoName)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
err = imgStore.DeleteBlobUpload(repoName, uuid)
if err != nil {
t.Error(err)
}
})
}
func FuzzBlobPath(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := data
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
digest := godigest.FromString(data)
_ = imgStore.BlobPath(repoName, digest)
})
}
func FuzzCheckBlob(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := data
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
digest := godigest.FromString(data)
_, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
_, _, err = imgStore.CheckBlob(repoName, digest.String())
if err != nil {
t.Error(err)
}
})
}
func FuzzGetBlob(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := data
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
digest := godigest.FromString(data)
_, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
_, _, err = imgStore.GetBlob(repoName, digest.String(), "application/vnd.oci.image.layer.v1.tar+gzip")
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzDeleteBlob(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := data
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
digest := godigest.FromString(data)
_, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
err = imgStore.DeleteBlob(repoName, digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzGetIndexContent(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := data
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
digest := godigest.FromString(data)
_, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
_, err = imgStore.GetIndexContent(repoName)
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzGetBlobContent(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
repoName := data
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
digest := godigest.FromString(data)
_, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
_, err = imgStore.GetBlobContent(repoName, digest.String())
if err != nil {
if isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzGetReferrers(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
err := test.CopyFiles("../../test/data/zot-test", path.Join(dir, "zot-test"))
if err != nil {
t.Error(err)
}
digest := godigest.FromBytes([]byte(data))
buf := bytes.NewBuffer([]byte(data))
buflen := buf.Len()
err = ioutil.WriteFile(path.Join(imgStore.RootDir(), //nolint: gosec
"zot-test", "blobs", digest.Algorithm().String(), digest.Encoded()),
buf.Bytes(), 0o644)
if err != nil {
t.Error(err)
}
_, _, err = imgStore.FullBlobUpload("zot-test", buf, digest.String())
if err != nil {
t.Error(err)
}
artifactManifest := artifactspec.Manifest{}
artifactManifest.ArtifactType = data
artifactManifest.Subject = artifactspec.Descriptor{
MediaType: ispec.MediaTypeImageManifest,
Digest: digest,
Size: int64(buflen),
}
artifactManifest.Blobs = []artifactspec.Descriptor{}
manBuf, err := json.Marshal(artifactManifest)
if err != nil {
t.Error(err)
}
manDigest := godigest.FromBytes(manBuf)
_, err = imgStore.PutImageManifest("zot-test", manDigest.Encoded(), artifactspec.MediaTypeArtifactManifest, manBuf)
if err != nil {
t.Error(err)
}
_, err = imgStore.GetReferrers("zot-test", digest.String(), data)
if err != nil {
if errors.Is(err, zerr.ErrManifestNotFound) || isKnownErr(err) {
return
}
t.Error(err)
}
})
}
func FuzzRunGCRepo(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
log := &log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, *log)
dir := t.TempDir()
defer os.RemoveAll(dir)
imgStore := storage.NewImageStore(dir, true, storage.DefaultGCDelay, true, true, *log, metrics, nil)
imgStore.RunGCRepo(data)
})
}
func TestDedupeLinks(t *testing.T) { func TestDedupeLinks(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
@ -416,6 +1122,10 @@ func TestNegativeCases(t *testing.T) {
err = imgStore.InitRepo("test-dir") err = imgStore.InitRepo("test-dir")
So(err, ShouldBeNil) So(err, ShouldBeNil)
// Init repo should fail if repo is invalid UTF-8
err = imgStore.InitRepo("hi \255")
So(err, ShouldNotBeNil)
}) })
Convey("Invalid validate repo", t, func(c C) { Convey("Invalid validate repo", t, func(c C) {
@ -438,7 +1148,7 @@ func TestNegativeCases(t *testing.T) {
} }
_, err = imgStore.ValidateRepo("invalid-test") _, err = imgStore.ValidateRepo("invalid-test")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(err, ShouldEqual, errors.ErrRepoNotFound) So(err, ShouldEqual, zerr.ErrRepoNotFound)
err = os.Chmod(path.Join(dir, "invalid-test"), 0o755) // remove all perms err = os.Chmod(path.Join(dir, "invalid-test"), 0o755) // remove all perms
if err != nil { if err != nil {
@ -483,7 +1193,7 @@ func TestNegativeCases(t *testing.T) {
isValid, err = imgStore.ValidateRepo("invalid-test") isValid, err = imgStore.ValidateRepo("invalid-test")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(err, ShouldEqual, errors.ErrRepoBadVersion) So(err, ShouldEqual, zerr.ErrRepoBadVersion)
So(isValid, ShouldEqual, false) So(isValid, ShouldEqual, false)
files, err := ioutil.ReadDir(path.Join(dir, "test")) files, err := ioutil.ReadDir(path.Join(dir, "test"))
@ -676,6 +1386,27 @@ func TestNegativeCases(t *testing.T) {
ok := storage.DirExists(filePath) ok := storage.DirExists(filePath)
So(ok, ShouldBeFalse) So(ok, ShouldBeFalse)
}) })
Convey("DirExists call with invalid UTF-8 as argument", t, func(c C) {
dir := t.TempDir()
filePath := path.Join(dir, "hi \255")
ok := storage.DirExists(filePath)
So(ok, ShouldBeFalse)
})
Convey("DirExists call with name too long as argument", t, func(c C) {
var builder strings.Builder
for i := 0; i < 1025; i++ {
_, err := builder.WriteString("0")
if err != nil {
t.Fatal(err)
}
}
path := builder.String()
ok := storage.DirExists(path)
So(ok, ShouldBeFalse)
})
} }
func TestHardLink(t *testing.T) { func TestHardLink(t *testing.T) {
@ -1311,3 +2042,56 @@ func TestPutBlobChunkStreamed(t *testing.T) {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
} }
func NewRandomImgManifest(data []byte, cdigest, ldigest godigest.Digest, cblob, lblob []byte) (*ispec.Manifest, error) {
annotationsMap := make(map[string]string)
key := string(data)
val := string(data)
annotationsMap[key] = val
schemaVersion := 2
manifest := ispec.Manifest{
MediaType: "application/vnd.oci.image.manifest.v1+json",
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: cdigest,
Size: int64(len(cblob)),
},
Layers: []ispec.Descriptor{
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: ldigest,
Size: int64(len(lblob)),
},
},
Annotations: annotationsMap,
Versioned: imeta.Versioned{
SchemaVersion: schemaVersion,
},
}
return &manifest, nil
}
func newRandomBlobForFuzz(data []byte) (godigest.Digest, []byte, error) {
return godigest.FromBytes(data), data, nil
}
func isKnownErr(err error) bool {
if errors.Is(err, zerr.ErrInvalidRepositoryName) || errors.Is(err, zerr.ErrManifestNotFound) ||
errors.Is(err, zerr.ErrRepoNotFound) ||
errors.Is(err, zerr.ErrBadManifest) {
return true
}
if err, ok := err.(*fs.PathError); ok && errors.Is(err.Err, syscall.EACCES) || //nolint: errorlint
errors.Is(err.Err, syscall.ENAMETOOLONG) ||
errors.Is(err.Err, syscall.EINVAL) ||
errors.Is(err.Err, syscall.ENOENT) {
return true
}
return false
}

View file

@ -191,6 +191,10 @@ func TestStorageAPIs(t *testing.T) {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(bupload, ShouldEqual, -1) So(bupload, ShouldEqual, -1)
bupload, err = imgStore.GetBlobUpload("hi", " \255")
So(err, ShouldNotBeNil)
So(bupload, ShouldEqual, -1)
bupload, err = imgStore.GetBlobUpload("test", upload) bupload, err = imgStore.GetBlobUpload("test", upload)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(bupload, ShouldBeGreaterThanOrEqualTo, 0) So(bupload, ShouldBeGreaterThanOrEqualTo, 0)

18
test/scripts/fuzzAll.sh Normal file
View file

@ -0,0 +1,18 @@
#!/bin/bash
set -e
fuzzTime=${1:-10}
files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' .)
for file in ${files}
do
funcs=$(grep -oP 'func \K(Fuzz\w*)' $file)
for func in ${funcs}
do
echo "Fuzzing $func in $file"
parentDir=$(dirname $file)
go test $parentDir -run=$func -fuzz=$func$ -fuzztime=${fuzzTime}s -tags sync,metrics,search,scrub,ui_base,containers_image_openpgp | grep -oP -x '^(?:(?!\blevel\b).)*$'
done
done