From af33cd98c88ccfe5ef1f0ef92bf900919fd3c559 Mon Sep 17 00:00:00 2001 From: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> Date: Tue, 22 Aug 2023 23:35:06 +0100 Subject: [PATCH] chore: improve performance of engine fuzzers (#8090) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: AdamKorcz Co-authored-by: Charles-Edouard Brétéché --- pkg/engine/fuzz_test.go | 193 ++++++++++++++++--- test/fuzz/dictionaries/fuzz.dict | 321 +++++++++++++++++++++++++++++++ test/fuzz/oss_fuzz_build.sh | 4 + 3 files changed, 495 insertions(+), 23 deletions(-) create mode 100644 test/fuzz/dictionaries/fuzz.dict diff --git a/pkg/engine/fuzz_test.go b/pkg/engine/fuzz_test.go index b164526f1e..454b5d5abd 100644 --- a/pkg/engine/fuzz_test.go +++ b/pkg/engine/fuzz_test.go @@ -1,7 +1,6 @@ package engine import ( - "bytes" "context" "encoding/json" "fmt" @@ -28,9 +27,6 @@ import ( fuzz "github.com/AdaLogics/go-fuzz-headers" ) -/* -VerifyAndPatchImage -*/ var ( fuzzCfg = config.NewDefaultConfiguration(false) fuzzMetricsCfg = config.NewDefaultMetricsConfiguration() @@ -49,16 +45,72 @@ var ( nil, "", ) + k8sKinds = map[int]string{ + 0: "Config", + 1: "ConfigMap", + 2: "CronJob", + 3: "DaemonSet", + 4: "Deployment", + 5: "EndpointSlice", + 6: "Ingress", + 7: "Job", + 8: "LimitRange", + 9: "List", + 10: "NetworkPolicy", + 11: "PersistentVolume", + 12: "PersistentVolumeClaim", + 13: "Pod", + 14: "ReplicaSet", + 15: "ReplicationController", + 16: "RuntimeClass", + 17: "Secret", + 18: "Service", + 19: "StorageClass", + 20: "VolumeSnapshot", + 21: "VolumeSnapshotClass", + 22: "VolumeSnapshotContent", + } + + kindToVersion = map[string]string{ + "Config": "v1", + "ConfigMap": "v1", + "CronJob": "batch/v1", + "DaemonSet": "apps/v1", + "Deployment": "apps/v1", + "EndpointSlice": "discovery.k8s.io/v1", + "Ingress": "networking.k8s.io/v1", + "Job": "batch/v1", + "LimitRange": "v1", + "List": "v1", + "NetworkPolicy": "networking.k8s.io/v1", + "PersistentVolume": "v1", + "PersistentVolumeClaim": "v1", + "Pod": "v1", + "ReplicaSet": "apps/v1", + "ReplicationController": "v1", + "RuntimeClass": "node.k8s.io/v1", + "Secret": "v1", + "Service": "v1", + "StorageClass": "storage.k8s.io/v1", + "VolumeSnapshot": "snapshot.storage.k8s.io/v1", + "VolumeSnapshotClass": "snapshot.storage.k8s.io/v1", + "VolumeSnapshotContent": "snapshot.storage.k8s.io/v1", + } ) -func buildFuzzContext(policy, resource, oldResource []byte) (*PolicyContext, error) { - var cpol kyverno.ClusterPolicy - err := json.Unmarshal([]byte(policy), &cpol) +func buildFuzzContext(ff *fuzz.ConsumeFuzzer) (*PolicyContext, error) { + cpSpec, err := createPolicySpec(ff) if err != nil { return nil, err } + cpol := &kyverno.ClusterPolicy{} + cpol.Spec = cpSpec - resourceUnstructured, err := kubeutils.BytesToUnstructured(resource) + if len(autogen.ComputeRules(cpol)) == 0 { + return nil, fmt.Errorf("No rules created") + } + + resourceUnstructured, err := createUnstructuredObject(ff) if err != nil { return nil, err } @@ -75,15 +127,25 @@ func buildFuzzContext(policy, resource, oldResource []byte) (*PolicyContext, err } policyContext = policyContext. - WithPolicy(&cpol). + WithPolicy(cpol). WithNewResource(*resourceUnstructured) - if !bytes.Equal(oldResource, []byte("")) { - oldResourceUnstructured, err := kubeutils.BytesToUnstructured(oldResource) + addOldResource, err := ff.GetBool() + if err != nil { + return nil, err + } + + if addOldResource { + oldResourceUnstructured, err := createUnstructuredObject(ff) if err != nil { return nil, err } + oldResource, err := json.Marshal(oldResourceUnstructured) + if err != nil { + return policyContext, nil + } + err = enginecontext.AddOldResource(policyContext.JSONContext(), oldResource) if err != nil { return nil, err @@ -95,9 +157,13 @@ func buildFuzzContext(policy, resource, oldResource []byte) (*PolicyContext, err return policyContext, nil } +/* +VerifyAndPatchImage +*/ func FuzzVerifyImageAndPatchTest(f *testing.F) { - f.Fuzz(func(t *testing.T, policy, resource, oldResource []byte) { - pc, err := buildFuzzContext(policy, resource, oldResource) + f.Fuzz(func(t *testing.T, data []byte) { + ff := fuzz.NewConsumer(data) + pc, err := buildFuzzContext(ff) if err != nil { return } @@ -346,7 +412,6 @@ func createRule(f *fuzz.ConsumeFuzzer) (*kyverno.Rule, error) { func FuzzEngineValidateTest(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { ff := fuzz.NewConsumer(data) - //ff.GenerateStruct(policy) cpSpec, err := createPolicySpec(ff) if err != nil { return @@ -375,11 +440,86 @@ func FuzzEngineValidateTest(f *testing.F) { }) } +func GetK8sString(ff *fuzz.ConsumeFuzzer) (string, error) { + allowedChars := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.") + stringLength, err := ff.GetInt() + if err != nil { + return "", err + } + var sb strings.Builder + for i := 0; i < stringLength%63; i++ { + charIndex, err := ff.GetInt() + if err != nil { + return "", err + } + sb.WriteString(string(allowedChars[charIndex%len(allowedChars)])) + } + return sb.String(), nil +} + +func getVersionAndKind(ff *fuzz.ConsumeFuzzer) (string, error) { + kindToCreate, err := ff.GetInt() + if err != nil { + return "", err + } + k := k8sKinds[kindToCreate%len(k8sKinds)] + v := kindToVersion[k] + var sb strings.Builder + sb.WriteString("\"apiVersion\": \"") + sb.WriteString(v) + sb.WriteString("\", \"kind\": \"") + sb.WriteString(k) + sb.WriteString("\"") + return sb.String(), nil +} + +func createLabels(ff *fuzz.ConsumeFuzzer) (string, error) { + var sb strings.Builder + noOfLabels, err := ff.GetInt() + if err != nil { + return "", err + } + for i := 0; i < noOfLabels%30; i++ { + key, err := GetK8sString(ff) + if err != nil { + return "", err + } + value, err := GetK8sString(ff) + if err != nil { + return "", err + } + sb.WriteString("\"") + sb.WriteString(key) + sb.WriteString("\":") + sb.WriteString("\"") + sb.WriteString(value) + sb.WriteString("\"") + if i != (noOfLabels%30)-1 { + sb.WriteString(", ") + } + } + return sb.String(), nil +} + // Creates an unstructured k8s object func createUnstructuredObject(f *fuzz.ConsumeFuzzer) (*unstructured.Unstructured, error) { + labels, err := createLabels(f) + if err != nil { + return nil, err + } + + versionAndKind, err := getVersionAndKind(f) + if err != nil { + return nil, err + } + var sb strings.Builder - sb.WriteString("{ \"apiVersion\": \"apps/v1\", \"kind\": \"Deployment\", \"metadata\": { \"creationTimestamp\": \"2020-09-21T12:56:35Z\", \"name\": \"fuzz\", \"labels\": { \"test\": \"qos\" } }, \"spec\": { ") + sb.WriteString("{ ") + sb.WriteString(versionAndKind) + sb.WriteString(", \"metadata\": { \"creationTimestamp\": \"2020-09-21T12:56:35Z\", \"name\": \"fuzz\", \"labels\": { ") + sb.WriteString(labels) + sb.WriteString(" } }, \"spec\": { ") for i := 0; i < 1000; i++ { typeToAdd, err := f.GetInt() @@ -422,14 +562,22 @@ func createUnstructuredObject(f *fuzz.ConsumeFuzzer) (*unstructured.Unstructured Mutate */ func FuzzMutateTest(f *testing.F) { - f.Fuzz(func(t *testing.T, resourceRaw, policyRaw []byte) { - var policy kyverno.ClusterPolicy - err := json.Unmarshal(policyRaw, &policy) + f.Fuzz(func(t *testing.T, data []byte) { + + ff := fuzz.NewConsumer(data) + //ff.GenerateStruct(policy) + cpSpec, err := createPolicySpec(ff) if err != nil { return } - var resource unstructured.Unstructured - err = resource.UnmarshalJSON(resourceRaw) + policy := &kyverno.ClusterPolicy{} + policy.Spec = cpSpec + + if len(autogen.ComputeRules(policy)) == 0 { + return + } + + resource, err := createUnstructuredObject(ff) if err != nil { return } @@ -437,7 +585,7 @@ func FuzzMutateTest(f *testing.F) { // create policy context pc, err := NewPolicyContext( fuzzJp, - resource, + *resource, kyverno.Create, nil, fuzzCfg, @@ -458,8 +606,7 @@ func FuzzMutateTest(f *testing.F) { ) e.Mutate( context.Background(), - pc.WithPolicy(&policy), + pc.WithPolicy(policy), ) - panic("Here") }) } diff --git a/test/fuzz/dictionaries/fuzz.dict b/test/fuzz/dictionaries/fuzz.dict new file mode 100644 index 0000000000..472c3ddd28 --- /dev/null +++ b/test/fuzz/dictionaries/fuzz.dict @@ -0,0 +1,321 @@ +"metadata" +"name" +"generateName" +"namespace" +"selfLink" +"uid" +"resourceVersion" +"generation" +"creationTimestamp" +"deletionTimestamp" +"deletionGracePeriodSeconds" +"labels" +"annotations" +"ownerReferences" +"apiVersion" +"kind" +"controller" +"blockOwnerDeletion" +"finalizers" +"managedFields" +"manager" +"operation" +"time" +"fieldsType" +"fieldsV1" +"subresource" +"spec" +"volumes" +"initContainers" +"image" +"command" +"args" +"workingDir" +"ports" +"hostPort" +"containerPort" +"protocol" +"hostIP" +"envFrom" +"prefix" +"configMapRef" +"optional" +"secretRef" +"env" +"value" +"valueFrom" +"fieldRef" +"fieldPath" +"resourceFieldRef" +"containerName" +"resource" +"divisor" +"configMapKeyRef" +"key" +"secretKeyRef" +"resources" +"limits" +"requests" +"claims" +"resizePolicy" +"resourceName" +"restartPolicy" +"volumeMounts" +"readOnly" +"mountPath" +"subPath" +"mountPropagation" +"subPathExpr" +"volumeDevices" +"devicePath" +"livenessProbe" +"initialDelaySeconds" +"timeoutSeconds" +"periodSeconds" +"successThreshold" +"failureThreshold" +"terminationGracePeriodSeconds" +"readinessProbe" +"startupProbe" +"lifecycle" +"postStart" +"exec" +"httpGet" +"path" +"port" +"host" +"scheme" +"httpHeaders" +"tcpSocket" +"preStop" +"terminationMessagePath" +"terminationMessagePolicy" +"imagePullPolicy" +"securityContext" +"capabilities" +"add" +"drop" +"privileged" +"seLinuxOptions" +"user" +"role" +"type" +"level" +"windowsOptions" +"gmsaCredentialSpecName" +"gmsaCredentialSpec" +"runAsUserName" +"hostProcess" +"runAsUser" +"runAsGroup" +"runAsNonRoot" +"readOnlyRootFilesystem" +"allowPrivilegeEscalation" +"procMount" +"seccompProfile" +"localhostProfile" +"stdin" +"stdinOnce" +"tty" +"containers" +"ephemeralContainers" +"targetContainerName" +"activeDeadlineSeconds" +"dnsPolicy" +"nodeSelector" +"serviceAccountName" +"serviceAccount" +"automountServiceAccountToken" +"nodeName" +"hostNetwork" +"hostPID" +"hostIPC" +"shareProcessNamespace" +"supplementalGroups" +"fsGroup" +"sysctls" +"fsGroupChangePolicy" +"imagePullSecrets" +"hostname" +"subdomain" +"affinity" +"nodeAffinity" +"requiredDuringSchedulingIgnoredDuringExecution" +"nodeSelectorTerms" +"matchExpressions" +"operator" +"values" +"matchFields" +"preferredDuringSchedulingIgnoredDuringExecution" +"weight" +"preference" +"podAffinity" +"labelSelector" +"matchLabels" +"namespaces" +"topologyKey" +"namespaceSelector" +"podAffinityTerm" +"podAntiAffinity" +"schedulerName" +"tolerations" +"effect" +"tolerationSeconds" +"hostAliases" +"ip" +"hostnames" +"priorityClassName" +"priority" +"dnsConfig" +"nameservers" +"searches" +"options" +"readinessGates" +"conditionType" +"runtimeClassName" +"enableServiceLinks" +"preemptionPolicy" +"overhead" +"topologySpreadConstraints" +"maxSkew" +"whenUnsatisfiable" +"minDomains" +"nodeAffinityPolicy" +"nodeTaintsPolicy" +"matchLabelKeys" +"setHostnameAsFQDN" +"os" +"hostUsers" +"schedulingGates" +"resourceClaims" +"source" +"resourceClaimName" +"resourceClaimTemplateName" +"status" +"phase" +"conditions" +"lastProbeTime" +"lastTransitionTime" +"reason" +"message" +"nominatedNodeName" +"hostIPs" +"podIP" +"podIPs" +"startTime" +"initContainerStatuses" +"state" +"waiting" +"running" +"startedAt" +"terminated" +"exitCode" +"signal" +"finishedAt" +"containerID" +"lastState" +"ready" +"restartCount" +"imageID" +"started" +"allocatedResources" +"containerStatuses" +"qosClass" +"ephemeralContainerStatuses" +"resize" +"resourceClaimStatuses" +"immutable" +"data" +"stringData" +"binaryData" +"schedule" +"timeZone" +"startingDeadlineSeconds" +"concurrencyPolicy" +"suspend" +"jobTemplate" +"parallelism" +"completions" +"podFailurePolicy" +"rules" +"action" +"onExitCodes" +"onPodConditions" +"backoffLimit" +"backoffLimitPerIndex" +"maxFailedIndexes" +"selector" +"manualSelector" +"template" +"ttlSecondsAfterFinished" +"completionMode" +"podReplacementPolicy" +"successfulJobsHistoryLimit" +"failedJobsHistoryLimit" +"active" +"lastScheduleTime" +"lastSuccessfulTime" +"updateStrategy" +"rollingUpdate" +"maxUnavailable" +"maxSurge" +"minReadySeconds" +"revisionHistoryLimit" +"currentNumberScheduled" +"numberMisscheduled" +"desiredNumberScheduled" +"numberReady" +"observedGeneration" +"updatedNumberScheduled" +"numberAvailable" +"numberUnavailable" +"collisionCount" +"replicas" +"strategy" +"paused" +"progressDeadlineSeconds" +"updatedReplicas" +"readyReplicas" +"availableReplicas" +"unavailableReplicas" +"lastUpdateTime" +"addressType" +"endpoints" +"addresses" +"serving" +"terminating" +"targetRef" +"deprecatedTopology" +"zone" +"hints" +"forZones" +"appProtocol" +"ingressClassName" +"defaultBackend" +"service" +"number" +"apiGroup" +"tls" +"hosts" +"secretName" +"http" +"paths" +"pathType" +"backend" +"loadBalancer" +"ingress" +"error" +"completionTime" +"succeeded" +"failed" +"completedIndexes" +"failedIndexes" +"uncountedTerminatedPods" +"max" +"min" +"default" +"defaultRequest" +"maxLimitRequestRatio" +"continue" +"remainingItemCount" +"items" \ No newline at end of file diff --git a/test/fuzz/oss_fuzz_build.sh b/test/fuzz/oss_fuzz_build.sh index 87ceb13292..2a3f5de71f 100755 --- a/test/fuzz/oss_fuzz_build.sh +++ b/test/fuzz/oss_fuzz_build.sh @@ -22,3 +22,7 @@ compile_native_go_fuzzer github.com/kyverno/kyverno/pkg/engine FuzzMutateTest Fu compile_native_go_fuzzer github.com/kyverno/kyverno/pkg/validation/policy FuzzValidatePolicy FuzzValidatePolicy compile_native_go_fuzzer github.com/kyverno/kyverno/pkg/engine/anchor FuzzAnchorParseTest FuzzAnchorParseTest compile_native_go_fuzzer github.com/kyverno/kyverno/pkg/engine/api FuzzEngineResponse FuzzEngineResponse + +cp $SRC/kyverno/test/fuzz/dictionaries/fuzz.dict $OUT/FuzzEngineValidateTest.dict +cp $SRC/kyverno/test/fuzz/dictionaries/fuzz.dict $OUT/FuzzMutateTest.dict +cp $SRC/kyverno/test/fuzz/dictionaries/fuzz.dict $OUT/FuzzVerifyImageAndPatchTest.dict