From 3d9c5c6166be8db51281bcbccfacda3a0864ad53 Mon Sep 17 00:00:00 2001 From: Samuele Resca Date: Thu, 13 Oct 2022 22:17:16 +0100 Subject: [PATCH] Adding fuzz test on v3rpc interfaces. Signed-off-by: Samuele Resca Signed-off-by: Samuele Resca --- .github/workflows/fuzzing.yaml | 22 ++ Makefile | 4 + scripts/fuzzing.sh | 13 ++ .../api/v3rpc/validationfuzz_test.go | 219 ++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 .github/workflows/fuzzing.yaml create mode 100755 scripts/fuzzing.sh create mode 100644 server/etcdserver/api/v3rpc/validationfuzz_test.go diff --git a/.github/workflows/fuzzing.yaml b/.github/workflows/fuzzing.yaml new file mode 100644 index 000000000..580c413b7 --- /dev/null +++ b/.github/workflows/fuzzing.yaml @@ -0,0 +1,22 @@ +name: Fuzzing v3rpc +on: [push, pull_request] +jobs: + fuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + Target: [FuzzRangeRequest, FuzzPutRequest, FuzzDeleteRangeRequest] + env: + FUZZ_TARGET: ${{matrix.Target}} + TARGET_PATH: ./server/etcdserver/api/v3rpc + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: "1.19.1" + - run: GOARCH=amd64 CPU=4 make fuzz + - uses: actions/upload-artifact@v2 + if: failure() + with: + path: "${{env.TARGET_PATH}}/testdata/fuzz/${{env.FUZZ_TARGET}}/*" diff --git a/Makefile b/Makefile index 4c3bcfab5..c049a5d11 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,10 @@ test-e2e-release: build test-linearizability: build PASSES="linearizability" ./scripts/test.sh $(GO_TEST_FLAGS) +.PHONY: fuzz +fuzz: + ./scripts/fuzzing.sh + # Static analysis verify: verify-gofmt verify-bom verify-lint verify-dep verify-shellcheck verify-goword verify-govet verify-license-header verify-receiver-name verify-mod-tidy verify-shellcheck verify-shellws verify-proto-annotations diff --git a/scripts/fuzzing.sh b/scripts/fuzzing.sh new file mode 100755 index 000000000..d77b90b15 --- /dev/null +++ b/scripts/fuzzing.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e +source ./scripts/test_lib.sh + +GO_CMD="go" +fuzz_time=${FUZZ_TIME:-"300s"} +target_path=${TARGET_PATH:-"./server/etcdserver/api/v3rpc"} +fuzz_target=${FUZZ_TARGET:-"FuzzRangeRequest"} + +log_callout -e "\\nExecuting fuzzing with target ${fuzz_target} in $target_path with a timeout of $fuzz_time\\n" +cd "$target_path" +$GO_CMD test -fuzz "$fuzz_target" -fuzztime "$fuzz_time" +log_success -e "\\COMPLETED: fuzzing with target $fuzz_target in $target_path \\n" diff --git a/server/etcdserver/api/v3rpc/validationfuzz_test.go b/server/etcdserver/api/v3rpc/validationfuzz_test.go new file mode 100644 index 000000000..4f44ee700 --- /dev/null +++ b/server/etcdserver/api/v3rpc/validationfuzz_test.go @@ -0,0 +1,219 @@ +package v3rpc + +import ( + "context" + "testing" + + pb "go.etcd.io/etcd/api/v3/etcdserverpb" + txn "go.etcd.io/etcd/server/v3/etcdserver/txn" + "go.etcd.io/etcd/server/v3/lease" + betesting "go.etcd.io/etcd/server/v3/storage/backend/testing" + "go.etcd.io/etcd/server/v3/storage/mvcc" + "go.uber.org/zap/zaptest" +) + +func FuzzRangeRequest(f *testing.F) { + testcases := []pb.RangeRequest{ + { + Key: []byte{2}, + RangeEnd: []byte{2}, + Limit: 3, + Revision: 3, + SortOrder: 2, + SortTarget: 2, + }, + } + + for _, tc := range testcases { + soValue := pb.RangeRequest_SortOrder_value[tc.SortOrder.String()] + soTarget := pb.RangeRequest_SortTarget_value[tc.SortTarget.String()] + f.Add(tc.Key, tc.RangeEnd, tc.Limit, tc.Revision, soValue, soTarget) // Use f.Add to provide a seed corpus + } + f.Fuzz(func(t *testing.T, + key []byte, + rangeEnd []byte, + limit int64, + revision int64, + sortOrder int32, + sortTarget int32, + ) { + b, _ := betesting.NewDefaultTmpBackend(t) + defer betesting.Close(t, b) + s := mvcc.NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, mvcc.StoreConfig{}) + defer s.Close() + + // setup cancelled context + ctx, cancel := context.WithCancel(context.TODO()) + cancel() + // put some data to prevent early termination in rangeKeys + // we are expecting failure on cancelled context check + s.Put(key, []byte("bar"), lease.NoLease) + + request := &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestRange{ + RequestRange: &pb.RangeRequest{ + Key: key, + RangeEnd: rangeEnd, + Limit: limit, + SortOrder: pb.RangeRequest_SortOrder(sortOrder), + SortTarget: pb.RangeRequest_SortTarget(sortTarget), + }, + }, + }, + }, + } + errCheck := checkRangeRequest(&pb.RangeRequest{ + Key: key, + RangeEnd: rangeEnd, + Limit: limit, + SortOrder: pb.RangeRequest_SortOrder(sortOrder), + SortTarget: pb.RangeRequest_SortTarget(sortTarget), + }) + + if errCheck != nil { + t.Skip("Validation not passing. Skipping the apply.") + } + + _, _, err := txn.Txn(ctx, zaptest.NewLogger(t), request, false, s, &lease.FakeLessor{}) + if err != nil { + t.Logf("Check: %s | Apply: %s", errCheck, err) + t.Skip("Application erroring.") + } + }) +} + +func FuzzPutRequest(f *testing.F) { + testcases := []pb.PutRequest{ + { + Key: []byte{2}, + Value: []byte{2}, + Lease: 2, + PrevKv: false, + IgnoreValue: false, + IgnoreLease: false, + }, + } + + for _, tc := range testcases { + f.Add(tc.Key, tc.Value, tc.Lease, tc.PrevKv, tc.IgnoreValue, tc.IgnoreLease) // Use f.Add to provide a seed corpus + } + + f.Fuzz(func(t *testing.T, + key []byte, + value []byte, + leaseValue int64, + prevKv bool, + ignoreValue bool, + IgnoreLease bool, + ) { + b, _ := betesting.NewDefaultTmpBackend(t) + defer betesting.Close(t, b) + s := mvcc.NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, mvcc.StoreConfig{}) + defer s.Close() + + // setup cancelled context + ctx, cancel := context.WithCancel(context.TODO()) + cancel() + // put some data to prevent early termination in rangeKeys + // we are expecting failure on cancelled context check + s.Put(key, []byte("bar"), lease.NoLease) + + request := &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + Key: key, + Value: value, + Lease: leaseValue, + PrevKv: prevKv, + IgnoreValue: ignoreValue, + IgnoreLease: IgnoreLease, + }, + }, + }, + }, + } + errCheck := checkPutRequest(&pb.PutRequest{ + Key: key, + Value: value, + Lease: leaseValue, + PrevKv: prevKv, + IgnoreValue: ignoreValue, + IgnoreLease: IgnoreLease, + }) + + if errCheck != nil { + t.Skip("Validation not passing. Skipping the apply.") + } + + _, _, err := txn.Txn(ctx, zaptest.NewLogger(t), request, false, s, &lease.FakeLessor{}) + if err != nil { + t.Logf("Check: %s | Apply: %s", errCheck, err) + t.Skip("Application erroring.") + } + }) +} + +func FuzzDeleteRangeRequest(f *testing.F) { + testcases := []pb.DeleteRangeRequest{ + { + Key: []byte{2}, + RangeEnd: []byte{2}, + PrevKv: false, + }, + } + + for _, tc := range testcases { + f.Add(tc.Key, tc.RangeEnd, tc.PrevKv) // Use f.Add to provide a seed corpus + } + + f.Fuzz(func(t *testing.T, + key []byte, + rangeEnd []byte, + prevKv bool, + ) { + b, _ := betesting.NewDefaultTmpBackend(t) + defer betesting.Close(t, b) + s := mvcc.NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, mvcc.StoreConfig{}) + defer s.Close() + + // setup cancelled context + ctx, cancel := context.WithCancel(context.TODO()) + cancel() + // put some data to prevent early termination in rangeKeys + // we are expecting failure on cancelled context check + s.Put(key, []byte("bar"), lease.NoLease) + + request := &pb.TxnRequest{ + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestDeleteRange{ + RequestDeleteRange: &pb.DeleteRangeRequest{ + Key: key, + RangeEnd: rangeEnd, + PrevKv: prevKv, + }, + }, + }, + }, + } + errCheck := checkDeleteRequest(&pb.DeleteRangeRequest{ + Key: key, + RangeEnd: rangeEnd, + PrevKv: prevKv, + }) + + if errCheck != nil { + t.Skip("Validation not passing. Skipping the apply.") + } + + _, _, err := txn.Txn(ctx, zaptest.NewLogger(t), request, false, s, &lease.FakeLessor{}) + if err != nil { + t.Logf("Check: %s | Apply: %s", errCheck, err) + t.Skip("Application erroring.") + } + }) +}