istio

Форк
0
/
crd_roundtrip_fuzzer.go 
210 строк · 5.8 Кб
1
// Copyright Istio Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package fuzz
16

17
import (
18
	"bytes"
19
	"encoding/hex"
20
	"fmt"
21
	"reflect"
22
	"strings"
23
	"sync"
24

25
	fuzz "github.com/AdaLogics/go-fuzz-headers"
26
	"github.com/davecgh/go-spew/spew"
27
	legacyproto "github.com/golang/protobuf/proto" // nolint: staticcheck
28
	"github.com/google/go-cmp/cmp"
29
	"github.com/google/go-cmp/cmp/cmpopts"
30
	"google.golang.org/protobuf/testing/protocmp"
31
	apimeta "k8s.io/apimachinery/pkg/api/meta"
32
	"k8s.io/apimachinery/pkg/runtime"
33
	"k8s.io/apimachinery/pkg/runtime/schema"
34
	"k8s.io/apimachinery/pkg/runtime/serializer/json"
35

36
	clientextensions "istio.io/client-go/pkg/apis/extensions/v1alpha1"
37
	clientnetworkingalpha "istio.io/client-go/pkg/apis/networking/v1alpha3"
38
	clientnetworkingbeta "istio.io/client-go/pkg/apis/networking/v1beta1"
39
	clientsecurity "istio.io/client-go/pkg/apis/security/v1beta1"
40
	clienttelemetry "istio.io/client-go/pkg/apis/telemetry/v1alpha1"
41
	"istio.io/istio/pkg/config/schema/collections"
42
)
43

44
var (
45
	scheme  = runtime.NewScheme()
46
	initter sync.Once
47
)
48

49
func initRoundTrip() {
50
	clientnetworkingalpha.AddToScheme(scheme)
51
	clientnetworkingbeta.AddToScheme(scheme)
52
	clientsecurity.AddToScheme(scheme)
53
	clientextensions.AddToScheme(scheme)
54
	clienttelemetry.AddToScheme(scheme)
55
}
56

57
// FuzzRoundtrip tests whether the pilot CRDs
58
// can be encoded and decoded.
59
func FuzzCRDRoundtrip(data []byte) int {
60
	initter.Do(initRoundTrip)
61
	if len(data) < 100 {
62
		return 0
63
	}
64

65
	// select a target:
66
	r := collections.Pilot.All()[int(data[0])%len(collections.Pilot.All())]
67
	gvk := r.GroupVersionKind()
68
	kgvk := schema.GroupVersionKind{
69
		Group:   gvk.Group,
70
		Version: gvk.Version,
71
		Kind:    gvk.Kind,
72
	}
73
	object, err := scheme.New(kgvk)
74
	if err != nil {
75
		return 0
76
	}
77

78
	typeAcc, err := apimeta.TypeAccessor(object)
79
	if err != nil {
80
		panic(fmt.Sprintf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableInternalTypes: %v\n", kgvk, err))
81
	}
82
	f := fuzz.NewConsumer(data[1:])
83
	err = f.GenerateStruct(object)
84
	if err != nil {
85
		return 0
86
	}
87
	err = checkForNilValues(object)
88
	if err != nil {
89
		return 0
90
	}
91
	typeAcc.SetKind(kgvk.Kind)
92
	typeAcc.SetAPIVersion(kgvk.GroupVersion().String())
93

94
	roundTrip(json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false), object)
95
	return 1
96
}
97

98
// roundTrip performs the roundtrip of the object.
99
func roundTrip(codec runtime.Codec, object runtime.Object) {
100
	printer := spew.ConfigState{DisableMethods: true}
101

102
	// deep copy the original object
103
	object = object.DeepCopyObject()
104
	name := reflect.TypeOf(object).Elem().Name()
105

106
	// encode (serialize) the deep copy using the provided codec
107
	data, err := runtime.Encode(codec, object)
108
	if err != nil {
109
		return
110
	}
111

112
	// encode (serialize) a second time to verify that it was not varying
113
	secondData, err := runtime.Encode(codec, object)
114
	if err != nil {
115
		panic("This should not fail since we are encoding for the second time")
116
	}
117

118
	// serialization to the wire must be stable to ensure that we don't write twice to the DB
119
	// when the object hasn't changed.
120
	if !bytes.Equal(data, secondData) {
121
		panic(fmt.Sprintf("%v: serialization is not stable: %s\n", name, printer.Sprintf("%#v", object)))
122
	}
123

124
	// decode (deserialize) the encoded data back into an object
125
	obj2, err := runtime.Decode(codec, data)
126
	if err != nil {
127
		panic(fmt.Sprintf("%v: %v\nCodec: %#v\nData: %s\nSource: %#v\n", name, err, codec, dataAsString(data), printer.Sprintf("%#v", object)))
128
	}
129

130
	// decode the encoded data into a new object (instead of letting the codec
131
	// create a new object)
132
	obj3 := reflect.New(reflect.TypeOf(object).Elem()).Interface().(runtime.Object)
133
	if err := runtime.DecodeInto(codec, data, obj3); err != nil {
134
		panic(fmt.Sprintf("%v: %v\n", name, err))
135
	}
136

137
	// ensure that the object produced from decoding the encoded data is equal
138
	// to the original object
139
	if diff := cmp.Diff(obj2, obj3, protocmp.Transform(), cmpopts.EquateNaNs()); diff != "" {
140
		panic("These should not be different: " + diff)
141
	}
142
}
143

144
// dataAsString is a simple helper.
145
func dataAsString(data []byte) string {
146
	dataString := string(data)
147
	if !strings.HasPrefix(dataString, "{") {
148
		dataString = "\n" + hex.Dump(data)
149
		legacyproto.NewBuffer(make([]byte, 0, 1024)).DebugPrint("decoded object", data)
150
	}
151
	return dataString
152
}
153

154
// checkForNilValues is a helper to check for nil
155
// values in the runtime objects.
156
// This part only converts the interface to a reflect.Value.
157
func checkForNilValues(targetStruct any) error {
158
	v := reflect.ValueOf(targetStruct)
159
	e := v.Elem()
160
	err := checkForNil(e)
161
	if err != nil {
162
		return err
163
	}
164
	return nil
165
}
166

167
// Checks for nil values in a reflect.Value.
168
func checkForNil(e reflect.Value) error {
169
	switch e.Kind() {
170
	case reflect.Struct:
171
		for i := 0; i < e.NumField(); i++ {
172
			err := checkForNil(e.Field(i))
173
			if err != nil {
174
				return err
175
			}
176
		}
177
	case reflect.Array, reflect.Slice:
178
		for i := 0; i < e.Len(); i++ {
179
			err := checkForNil(e.Index(i))
180
			if err != nil {
181
				return err
182
			}
183
		}
184
	case reflect.Map:
185
		if e.IsNil() {
186
			return fmt.Errorf("field is nil")
187
		}
188
		for _, k := range e.MapKeys() {
189
			if e.IsNil() {
190
				return fmt.Errorf("field is nil")
191
			}
192
			err := checkForNil(e.MapIndex(k))
193
			if err != nil {
194
				return err
195
			}
196
		}
197
	case reflect.Ptr:
198
		if e.IsNil() {
199
			return fmt.Errorf("field is nil")
200
		}
201

202
		err := checkForNil(e.Elem())
203
		if err != nil {
204
			return err
205
		}
206
	default:
207
		return nil
208
	}
209
	return nil
210
}
211

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.