//go:build gofuzz // +build gofuzz package handler import ( "bytes" "context" "encoding/json" "errors" "io" "mime/multipart" "net/http" "testing" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" go_fuzz_utils "github.com/trailofbits/go-fuzz-utils" "github.com/valyala/fasthttp" ) const ( fuzzSuccessExitCode = 0 fuzzFailExitCode = -1 ) func prepareStrings(tp *go_fuzz_utils.TypeProvider, count int) ([]string, error) { array := make([]string, count) var err error for i := 0; i < count; i++ { err = tp.Reset() if err != nil { return nil, err } array[i], err = tp.GetString() if err != nil { return nil, err } } return array, nil } func prepareBools(tp *go_fuzz_utils.TypeProvider, count int) ([]bool, error) { array := make([]bool, count) var err error for i := 0; i < count; i++ { err = tp.Reset() if err != nil { return nil, err } array[i], err = tp.GetBool() if err != nil { return nil, err } } return array, nil } func getRandomDeterministicPositiveIntInRange(tp *go_fuzz_utils.TypeProvider, max int) (int, error) { count, err := tp.GetInt() if err != nil { return -1, err } count = count % max if count < 0 { count += max } return count, nil } func generateHeaders(tp *go_fuzz_utils.TypeProvider, r *fasthttp.Request, params []string) error { count, err := tp.GetInt() if err != nil { return err } count = count % len(params) if count < 0 { count += len(params) } for i := 0; i < count; i++ { position, err := tp.GetInt() if err != nil { return err } position = position % len(params) if position < 0 { position += len(params) } v, err := tp.GetString() if err != nil { return err } r.Header.Set(params[position], v) } return nil } func maybeFillRandom(tp *go_fuzz_utils.TypeProvider, initValue string) (string, error) { rnd, err := tp.GetBool() if err != nil { return "", err } if rnd == true { initValue, err = tp.GetString() if err != nil { return "", err } } return initValue, nil } func upload(tp *go_fuzz_utils.TypeProvider) (context.Context, *handlerContext, cid.ID, *fasthttp.RequestCtx, string, string, string, error) { hc, err := prepareHandlerContext() if err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } aclList := []acl.Basic{ acl.Private, acl.PrivateExtended, acl.PublicRO, acl.PublicROExtended, acl.PublicRW, acl.PublicRWExtended, acl.PublicAppend, acl.PublicAppendExtended, } pos, err := getRandomDeterministicPositiveIntInRange(tp, len(aclList)) if err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } acl := aclList[pos] strings, err := prepareStrings(tp, 6) if err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } bktName := strings[0] objFileName := strings[1] valAttr := strings[2] keyAttr := strings[3] if len(bktName) == 0 { return nil, nil, cid.ID{}, nil, "", "", "", errors.New("not enought buckets") } cnrID, cnr, err := hc.prepareContainer(bktName, acl) if err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } hc.frostfs.SetContainer(cnrID, cnr) ctx := context.Background() ctx = middleware.SetNamespace(ctx, "") r := new(fasthttp.RequestCtx) utils.SetContextToRequest(ctx, r) r.SetUserValue("cid", cnrID.EncodeToString()) attributes := map[string]string{ object.AttributeFileName: objFileName, keyAttr: valAttr, } var buff bytes.Buffer w := multipart.NewWriter(&buff) fw, err := w.CreateFormFile("file", attributes[object.AttributeFileName]) if err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } content, err := tp.GetBytes() if err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } if _, err = io.Copy(fw, bytes.NewReader(content)); err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } if err = w.Close(); err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } r.Request.SetBodyStream(&buff, buff.Len()) r.Request.Header.Set("Content-Type", w.FormDataContentType()) r.Request.Header.Set("X-Attribute-"+keyAttr, valAttr) err = generateHeaders(tp, &r.Request, []string{"X-Attribute-", "X-Attribute-DupKey", "X-Attribute-MyAttribute", "X-Attribute-System-DupKey", "X-Attribute-System-Expiration-Epoch1", "X-Attribute-SYSTEM-Expiration-Epoch2", "X-Attribute-system-Expiration-Epoch3", "X-Attribute-User-Attribute", "X-Attribute-", "X-Attribute-FileName", "X-Attribute-FROSTFS", "X-Attribute-neofs", "X-Attribute-SYSTEM", "X-Attribute-System-Expiration-Duration", "X-Attribute-System-Expiration-Epoch", "X-Attribute-System-Expiration-RFC3339", "X-Attribute-System-Expiration-Timestamp", "X-Attribute-Timestamp", "X-Attribute-" + strings[4], "X-Attribute-System-" + strings[5]}) if err != nil { return nil, nil, cid.ID{}, nil, "", "", "", err } hc.Handler().Upload(r) if r.Response.StatusCode() != http.StatusOK { return nil, nil, cid.ID{}, nil, "", "", "", errors.New("error on upload") } return ctx, hc, cnrID, r, objFileName, keyAttr, valAttr, nil } func InitFuzzUpload() { } func DoFuzzUpload(input []byte) int { // FUZZER INIT if len(input) < 100 { return fuzzFailExitCode } tp, err := go_fuzz_utils.NewTypeProvider(input) if err != nil { return fuzzFailExitCode } _, _, _, _, _, _, _, err = upload(tp) if err != nil { return fuzzFailExitCode } return fuzzSuccessExitCode } func FuzzUpload(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { DoFuzzUpload(data) }) } func downloadOrHead(tp *go_fuzz_utils.TypeProvider, ctx context.Context, hc *handlerContext, cnrID cid.ID, resp *fasthttp.RequestCtx, filename string) (*fasthttp.RequestCtx, error) { var putRes putResponse defer func() { if r := recover(); r != nil { panic(resp) } }() data := resp.Response.Body() err := json.Unmarshal(data, &putRes) if err != nil { return nil, err } obj := hc.frostfs.objects[putRes.ContainerID+"/"+putRes.ObjectID] attr := object.NewAttribute() attr.SetKey(object.AttributeFilePath) filename, err = maybeFillRandom(tp, filename) if err != nil { return nil, err } attr.SetValue(filename) obj.SetAttributes(append(obj.Attributes(), *attr)...) r := new(fasthttp.RequestCtx) utils.SetContextToRequest(ctx, r) cid := cnrID.EncodeToString() cid, err = maybeFillRandom(tp, cid) if err != nil { return nil, err } oid := putRes.ObjectID oid, err = maybeFillRandom(tp, oid) if err != nil { return nil, err } r.SetUserValue("cid", cid) r.SetUserValue("oid", oid) rnd, err := tp.GetBool() if err != nil { return nil, err } if rnd == true { r.SetUserValue("download", "true") } return r, nil } func InitFuzzGet() { } func DoFuzzGet(input []byte) int { // FUZZER INIT if len(input) < 100 { return fuzzFailExitCode } tp, err := go_fuzz_utils.NewTypeProvider(input) if err != nil { return fuzzFailExitCode } ctx, hc, cnrID, resp, filename, _, _, err := upload(tp) if err != nil { return fuzzFailExitCode } r, err := downloadOrHead(tp, ctx, hc, cnrID, resp, filename) if err != nil { return fuzzFailExitCode } hc.Handler().DownloadByAddressOrBucketName(r) return fuzzSuccessExitCode } func FuzzGet(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { DoFuzzUpload(data) }) } func InitFuzzHead() { } func DoFuzzHead(input []byte) int { // FUZZER INIT if len(input) < 100 { return fuzzFailExitCode } tp, err := go_fuzz_utils.NewTypeProvider(input) if err != nil { return fuzzFailExitCode } ctx, hc, cnrID, resp, filename, _, _, err := upload(tp) if err != nil { return fuzzFailExitCode } r, err := downloadOrHead(tp, ctx, hc, cnrID, resp, filename) if err != nil { return fuzzFailExitCode } hc.Handler().HeadByAddressOrBucketName(r) return fuzzSuccessExitCode } func FuzzHead(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { DoFuzzHead(data) }) } func InitFuzzDownloadByAttribute() { } func DoFuzzDownloadByAttribute(input []byte) int { // FUZZER INIT if len(input) < 100 { return fuzzFailExitCode } tp, err := go_fuzz_utils.NewTypeProvider(input) if err != nil { return fuzzFailExitCode } ctx, hc, cnrID, _, _, attrKey, attrVal, err := upload(tp) if err != nil { return fuzzFailExitCode } cid := cnrID.EncodeToString() cid, err = maybeFillRandom(tp, cid) if err != nil { return fuzzFailExitCode } attrKey, err = maybeFillRandom(tp, attrKey) if err != nil { return fuzzFailExitCode } attrVal, err = maybeFillRandom(tp, attrVal) if err != nil { return fuzzFailExitCode } r := new(fasthttp.RequestCtx) utils.SetContextToRequest(ctx, r) r.SetUserValue("cid", cid) r.SetUserValue("attr_key", attrKey) r.SetUserValue("attr_val", attrVal) hc.Handler().DownloadByAttribute(r) return fuzzSuccessExitCode } func FuzzDownloadByAttribute(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { DoFuzzDownloadByAttribute(data) }) } func InitFuzzHeadByAttribute() { } func DoFuzzHeadByAttribute(input []byte) int { // FUZZER INIT if len(input) < 100 { return fuzzFailExitCode } tp, err := go_fuzz_utils.NewTypeProvider(input) if err != nil { return fuzzFailExitCode } ctx, hc, cnrID, _, _, attrKey, attrVal, err := upload(tp) if err != nil { return fuzzFailExitCode } cid := cnrID.EncodeToString() cid, err = maybeFillRandom(tp, cid) if err != nil { return fuzzFailExitCode } attrKey, err = maybeFillRandom(tp, attrKey) if err != nil { return fuzzFailExitCode } attrVal, err = maybeFillRandom(tp, attrVal) if err != nil { return fuzzFailExitCode } r := new(fasthttp.RequestCtx) utils.SetContextToRequest(ctx, r) r.SetUserValue("cid", cid) r.SetUserValue("attr_key", attrKey) r.SetUserValue("attr_val", attrVal) hc.Handler().HeadByAttribute(r) return fuzzSuccessExitCode } func FuzzHeadByAttribute(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { DoFuzzHeadByAttribute(data) }) } func InitFuzzDownloadZipped() { } func DoFuzzDownloadZipped(input []byte) int { // FUZZER INIT if len(input) < 100 { return fuzzFailExitCode } tp, err := go_fuzz_utils.NewTypeProvider(input) if err != nil { return fuzzFailExitCode } ctx, hc, cnrID, _, _, _, _, err := upload(tp) if err != nil { return fuzzFailExitCode } cid := cnrID.EncodeToString() cid, err = maybeFillRandom(tp, cid) if err != nil { return fuzzFailExitCode } prefix := "" prefix, err = maybeFillRandom(tp, prefix) if err != nil { return fuzzFailExitCode } r := new(fasthttp.RequestCtx) utils.SetContextToRequest(ctx, r) r.SetUserValue("cid", cid) r.SetUserValue("prefix", prefix) hc.Handler().DownloadZipped(r) return fuzzSuccessExitCode } func FuzzDownloadZipped(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { DoFuzzDownloadZipped(data) }) } func InitFuzzStoreBearerTokenAppCtx() { } func DoFuzzStoreBearerTokenAppCtx(input []byte) int { // FUZZER INIT if len(input) < 100 { return fuzzFailExitCode } tp, err := go_fuzz_utils.NewTypeProvider(input) if err != nil { return fuzzFailExitCode } prefix := "" prefix, err = maybeFillRandom(tp, prefix) if err != nil { return fuzzFailExitCode } ctx := context.Background() ctx = middleware.SetNamespace(ctx, "") r := new(fasthttp.RequestCtx) utils.SetContextToRequest(ctx, r) strings, err := prepareStrings(tp, 3) rand, err := prepareBools(tp, 2) if rand[0] == true { r.Request.Header.Set(fasthttp.HeaderAuthorization, "Bearer"+strings[0]) } else if rand[1] == true { r.Request.Header.SetCookie(fasthttp.HeaderAuthorization, "Bearer"+strings[1]) } else { r.Request.Header.Set(fasthttp.HeaderAuthorization, "Bearer"+strings[0]) r.Request.Header.SetCookie(fasthttp.HeaderAuthorization, "Bearer"+strings[1]) } tokens.StoreBearerTokenAppCtx(ctx, r) return fuzzSuccessExitCode } func FuzzStoreBearerTokenAppCtx(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { DoFuzzStoreBearerTokenAppCtx(data) }) }