From d7f64fc295d7a7378d800c37ae83caf5d0570c67 Mon Sep 17 00:00:00 2001 From: huiwq1990 Date: Wed, 10 Jan 2024 15:11:50 +0800 Subject: [PATCH] add yurt controller fuzz (#1903) Signed-off-by: huiwq1990 --- test/fuzz/Dockerfile.builder | 6 ++ test/fuzz/go.mod | 6 ++ test/fuzz/oss_fuzz_build.sh | 39 ++++++++++ test/fuzz/oss_fuzz_run.sh | 20 ++++++ test/fuzz/yurtappdaemon_fuzzer.go | 72 +++++++++++++++++++ test/fuzz/yurtappset_fuzzer.go | 116 ++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+) create mode 100644 test/fuzz/Dockerfile.builder create mode 100644 test/fuzz/go.mod create mode 100644 test/fuzz/oss_fuzz_build.sh create mode 100644 test/fuzz/oss_fuzz_run.sh create mode 100644 test/fuzz/yurtappdaemon_fuzzer.go create mode 100644 test/fuzz/yurtappset_fuzzer.go diff --git a/test/fuzz/Dockerfile.builder b/test/fuzz/Dockerfile.builder new file mode 100644 index 00000000000..21f05aa365e --- /dev/null +++ b/test/fuzz/Dockerfile.builder @@ -0,0 +1,6 @@ +FROM gcr.io/oss-fuzz-base/base-builder-go + +COPY ./ $GOPATH/src/github.com/openyurtio/openyurt/ +COPY ./test/fuzz/oss_fuzz_build.sh $SRC/build.sh + +WORKDIR $SRC \ No newline at end of file diff --git a/test/fuzz/go.mod b/test/fuzz/go.mod new file mode 100644 index 00000000000..b4f5b19bd28 --- /dev/null +++ b/test/fuzz/go.mod @@ -0,0 +1,6 @@ +module github.com/openyurtio/openyurt/test/fuzz + +// This module is used only to avoid polluting the main module +// with fuzz dependencies. + +go 1.18 diff --git a/test/fuzz/oss_fuzz_build.sh b/test/fuzz/oss_fuzz_build.sh new file mode 100644 index 00000000000..1554a49fd81 --- /dev/null +++ b/test/fuzz/oss_fuzz_build.sh @@ -0,0 +1,39 @@ +# Copyright 2022 The OpenYurt 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. + +#!/usr/bin/env bash + +set -euxo pipefail + +GOPATH="${GOPATH:-/root/go}" +GO_SRC="${GOPATH}/src" +PROJECT_PATH="github.com/openyurtio/openyurt" + +cd "${GO_SRC}" + +# Move fuzzer to their respective directories. +# This removes dependency noises from the modules' go.mod and go.sum files. +cp "${PROJECT_PATH}/test/fuzz/yurtappdaemon_fuzzer.go" "${PROJECT_PATH}/pkg/yurtmanager/controller/yurtappdaemon/yurtappdaemon_fuzzer.go" +cp "${PROJECT_PATH}/test/fuzz/yurtappset_fuzzer.go" "${PROJECT_PATH}/pkg/yurtmanager/controller/yurtappset/yurtappset_fuzzer.go" + +# compile fuzz test for the runtime module +pushd "${PROJECT_PATH}" + +go get -d github.com/AdaLogics/go-fuzz-headers +go mod vendor +go mod tidy +compile_go_fuzzer "${PROJECT_PATH}/pkg/yurtmanager/controller/yurtappdaemon/" FuzzAppDaemonReconcile fuzz_yurtappdaemon_controller +compile_go_fuzzer "${PROJECT_PATH}/pkg/yurtmanager/controller/yurtappset/" FuzzAppSetReconcile fuzz_yurtappset_controller + +popd diff --git a/test/fuzz/oss_fuzz_run.sh b/test/fuzz/oss_fuzz_run.sh new file mode 100644 index 00000000000..425f2b6b483 --- /dev/null +++ b/test/fuzz/oss_fuzz_run.sh @@ -0,0 +1,20 @@ +# Copyright 2022 The OpenYurt 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. + +#!/usr/bin/env bash + +set -euxo pipefail + +# run each fuzzer once to ensure they are working properly +find /out -type f -name "fuzz*" -exec echo {} -runs=1 \; | bash -e diff --git a/test/fuzz/yurtappdaemon_fuzzer.go b/test/fuzz/yurtappdaemon_fuzzer.go new file mode 100644 index 00000000000..47eb8766a6b --- /dev/null +++ b/test/fuzz/yurtappdaemon_fuzzer.go @@ -0,0 +1,72 @@ +//go:build gofuzz +// +build gofuzz + +/* +Copyright 2022 The OpenYurt 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. +*/ + +package yurtappdaemon + +import ( + "context" + + fuzz "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + appsv1alpha1 "github.com/openyurtio/openyurt/pkg/apis/apps/v1alpha1" + "github.com/openyurtio/openyurt/pkg/yurtmanager/controller/yurtappdaemon/workloadcontroller" +) + +var ( + fuzzCtx = context.Background() + fakeSchemeForFuzzing = runtime.NewScheme() +) + +func init() { + _ = clientgoscheme.AddToScheme(fakeSchemeForFuzzing) + _ = appsv1alpha1.AddToScheme(fakeSchemeForFuzzing) + _ = corev1.AddToScheme(fakeSchemeForFuzzing) +} + +func FuzzAppDaemonReconcile(data []byte) int { + f := fuzz.NewConsumer(data) + + appDaemon := &appsv1alpha1.YurtAppDaemon{} + if err := f.GenerateStruct(appDaemon); err != nil { + return 0 + } + + clientFake := fake.NewClientBuilder().WithScheme(fakeSchemeForFuzzing).WithObjects( + appDaemon, + ).Build() + + r := &ReconcileYurtAppDaemon{ + Client: clientFake, + scheme: fakeSchemeForFuzzing, + recorder: record.NewFakeRecorder(10000), + controls: map[appsv1alpha1.TemplateType]workloadcontroller.WorkloadControllor{ + appsv1alpha1.DeploymentTemplateType: &workloadcontroller.DeploymentControllor{Client: clientFake, Scheme: fakeSchemeForFuzzing}, + }, + } + + _, _ = r.Reconcile(fuzzCtx, reconcile.Request{NamespacedName: types.NamespacedName{Name: appDaemon.Name, Namespace: appDaemon.Namespace}}) + return 1 +} diff --git a/test/fuzz/yurtappset_fuzzer.go b/test/fuzz/yurtappset_fuzzer.go new file mode 100644 index 00000000000..2f5ec6a3958 --- /dev/null +++ b/test/fuzz/yurtappset_fuzzer.go @@ -0,0 +1,116 @@ +//go:build gofuzz +// +build gofuzz + +/* +Copyright 2022 The OpenYurt 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. +*/ + +package yurtappset + +import ( + "context" + "fmt" + + fuzz "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/yaml" + + appsv1alpha1 "github.com/openyurtio/openyurt/pkg/apis/apps/v1alpha1" + "github.com/openyurtio/openyurt/pkg/yurtmanager/controller/yurtappset/adapter" +) + +var ( + fuzzCtx = context.Background() + fakeSchemeForFuzzing = runtime.NewScheme() +) + +func init() { + _ = clientgoscheme.AddToScheme(fakeSchemeForFuzzing) + _ = appsv1alpha1.AddToScheme(fakeSchemeForFuzzing) + _ = corev1.AddToScheme(fakeSchemeForFuzzing) +} + +// helper function to crate an unstructured object. +func GetUnstructured(f *fuzz.ConsumeFuzzer) (*unstructured.Unstructured, error) { + yamlStr, err := f.GetString() + if err != nil { + return nil, err + } + obj := make(map[string]interface{}) + err = yaml.Unmarshal([]byte(yamlStr), &obj) + if err != nil { + return nil, err + } + return &unstructured.Unstructured{Object: obj}, nil +} + +func validateUnstructured(unstr *unstructured.Unstructured) error { + if _, ok := unstr.Object["kind"]; !ok { + return fmt.Errorf("invalid unstr") + } + if _, ok := unstr.Object["apiVersion"]; !ok { + return fmt.Errorf("invalid unstr") + } + if _, ok := unstr.Object["spec"]; !ok { + return fmt.Errorf("invalid unstr") + } + if _, ok := unstr.Object["status"]; !ok { + return fmt.Errorf("invalid unstr") + } + return nil +} + +func FuzzAppSetReconcile(data []byte) int { + f := fuzz.NewConsumer(data) + unstr, err := GetUnstructured(f) + if err != nil { + return 0 + } + err = validateUnstructured(unstr) + if err != nil { + return 0 + } + + appset := &appsv1alpha1.YurtAppSet{} + if err := f.GenerateStruct(appset); err != nil { + return 0 + } + + clientFake := fake.NewClientBuilder().WithScheme(fakeSchemeForFuzzing).WithObjects( + appset, + ).Build() + + r := &ReconcileYurtAppSet{ + Client: clientFake, + scheme: fakeSchemeForFuzzing, + recorder: record.NewFakeRecorder(10000), + poolControls: map[appsv1alpha1.TemplateType]ControlInterface{ + appsv1alpha1.StatefulSetTemplateType: &PoolControl{Client: clientFake, scheme: fakeSchemeForFuzzing, + adapter: &adapter.StatefulSetAdapter{Client: clientFake, Scheme: fakeSchemeForFuzzing}}, + appsv1alpha1.DeploymentTemplateType: &PoolControl{Client: clientFake, scheme: fakeSchemeForFuzzing, + adapter: &adapter.DeploymentAdapter{Client: clientFake, Scheme: fakeSchemeForFuzzing}}, + }, + } + + _, _ = r.Reconcile(fuzzCtx, reconcile.Request{NamespacedName: types.NamespacedName{Name: appset.Name, Namespace: appset.Namespace}}) + return 1 +}