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  }