gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/container/serialization_test.go (about) 1 // Copyright 2018 The gVisor 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 serialization_test 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "reflect" 21 "strings" 22 "testing" 23 24 "google.golang.org/protobuf/proto" 25 "gvisor.dev/gvisor/runsc/container" 26 "gvisor.dev/gvisor/runsc/sandbox" 27 ) 28 29 // ignoreList is a list of field names that are ignored from the 30 // serializability check in this file. 31 // No gVisor-related field named should be here; instead, you can tag 32 // fields that are meant to be unserializable with `nojson:"true"`. 33 var ignoreList = map[string]struct{}{ 34 // Part of the OCI runtime spec, it uses an `interface{}` type which it 35 // promises is JSON-serializable in the comments. 36 "Container.Spec.Windows.CredentialSpec": struct{}{}, 37 } 38 39 // implementsSerializableInterface returns true if the given type implements 40 // an interface which inherently provides serialization. 41 func implementsSerializableInterface(typ reflect.Type) bool { 42 jsonMarshaler := reflect.TypeOf((*json.Marshaler)(nil)).Elem() 43 jsonUnmarshaler := reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() 44 if typ.Implements(jsonMarshaler) && typ.Implements(jsonUnmarshaler) { 45 return true 46 } 47 protoMessage := reflect.TypeOf((*proto.Message)(nil)).Elem() 48 if typ.Implements(protoMessage) { 49 return true 50 } 51 return false 52 } 53 54 // checkSerializable verifies that the given type is serializable. 55 func checkSerializable(typ reflect.Type, fieldName []string) error { 56 if implementsSerializableInterface(typ) || implementsSerializableInterface(reflect.PointerTo(typ)) { 57 return nil 58 } 59 field := func(s string) []string { 60 return append(append(([]string)(nil), fieldName...), s) 61 } 62 fieldPath := strings.Join(fieldName, ".") 63 if _, ignored := ignoreList[fieldPath]; ignored { 64 return nil 65 } 66 switch typ.Kind() { 67 case reflect.Bool: 68 return nil 69 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 70 return nil 71 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 72 return nil 73 case reflect.Float32, reflect.Float64: 74 return nil 75 case reflect.Complex64, reflect.Complex128: 76 return nil 77 case reflect.String: 78 return nil 79 case reflect.UnsafePointer: 80 return fmt.Errorf("unsafe pointer %q not allowed in serializable struct", fieldPath) 81 case reflect.Chan: 82 return fmt.Errorf("channel %q not allowed in serializable struct", fieldPath) 83 case reflect.Func: 84 return fmt.Errorf("function %q not allowed in serializable struct", fieldPath) 85 case reflect.Interface: 86 return fmt.Errorf("interface %q not allowed in serializable struct", fieldPath) 87 case reflect.Array: 88 return fmt.Errorf("fixed-size array %q not allowed in serializable struct (use a slice instead)", fieldPath) 89 case reflect.Slice: 90 return checkSerializable(typ.Elem(), field("[]")) 91 case reflect.Map: 92 // We only allow a small subset of types as valid map key type. 93 switch typ.Key().Kind() { 94 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 95 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 96 case reflect.String: 97 default: 98 return fmt.Errorf("map key type %v of %q not allowed", typ.Key().Kind(), fieldPath) 99 } 100 // But all the value types are allowed. 101 return checkSerializable(typ.Elem(), field("{}")) 102 case reflect.Struct: 103 for i := 0; i < typ.NumField(); i++ { 104 f := typ.Field(i) 105 if _, noJSON := f.Tag.Lookup("nojson"); noJSON { 106 if f.IsExported() { 107 return fmt.Errorf("struct field %q must not be exported since it is marked `nojson:\"true\"` in a serializable struct", strings.Join(field(f.Name), ".")) 108 } 109 continue 110 } 111 if !f.IsExported() { 112 return fmt.Errorf("struct field %q must be exported or marked `nojson:\"true\"` since it is in a serializable struct", strings.Join(field(f.Name), ".")) 113 } 114 if err := checkSerializable(f.Type, field(f.Name)); err != nil { 115 return err 116 } 117 } 118 return nil 119 case reflect.Pointer: 120 return checkSerializable(typ.Elem(), fieldName) 121 default: 122 return fmt.Errorf("unknown field type %v for %q", typ, fieldPath) 123 } 124 } 125 126 // TestSerialization verifies that the Container struct only contains 127 // serializable fields. 128 func TestSerializable(t *testing.T) { 129 for _, test := range []struct { 130 name string 131 obj any 132 }{ 133 {"Sandbox", sandbox.Sandbox{}}, 134 {"Container", container.Container{}}, 135 } { 136 t.Run(test.name, func(t *testing.T) { 137 if err := checkSerializable(reflect.TypeOf(test.obj), []string{test.name}); err != nil { 138 t.Errorf("struct %v must be serializable: %v", test.name, err) 139 } 140 }) 141 } 142 }