diff --git a/runsc/boot/filter/config/config.go b/runsc/boot/filter/config/config.go index 327e3e604..44e5c84e9 100644 --- a/runsc/boot/filter/config/config.go +++ b/runsc/boot/filter/config/config.go @@ -17,6 +17,9 @@ package config import ( + "fmt" + "strings" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/seccomp" "gvisor.dev/gvisor/pkg/sentry/devices/accel" @@ -36,6 +39,24 @@ type Options struct { ControllerFD int } +// ConfigKey returns a unique string representing this set of options. +// This is used for matching a set of `Options` at seccomp precompile +// time with the same set of `Options` at runtime. +// As such, it should encompass all fields that change the structure of +// the seccomp rules, but should not encompass fields that are only known +// at runtime (e.g. `ControllerFD`). +func (opt Options) ConfigKey() string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Platform=%q ", opt.Platform.ConfigKey())) + sb.WriteString(fmt.Sprintf("HostNetwork=%t ", opt.HostNetwork)) + sb.WriteString(fmt.Sprintf("HostNetworkRawSockets=%t ", opt.HostNetworkRawSockets)) + sb.WriteString(fmt.Sprintf("HostFilesystem=%t ", opt.HostFilesystem)) + sb.WriteString(fmt.Sprintf("ProfileEnable=%t ", opt.ProfileEnable)) + sb.WriteString(fmt.Sprintf("NVProxy=%t ", opt.NVProxy)) + sb.WriteString(fmt.Sprintf("TPUProxy=%t ", opt.TPUProxy)) + return strings.TrimSpace(sb.String()) +} + // Warnings returns a set of warnings that may be useful to display to the // user when the given options are used. func Warnings(opt Options) []string { diff --git a/runsc/boot/filter/config/config_test.go b/runsc/boot/filter/config/config_test.go index 06d9b6ec1..bbb25f140 100644 --- a/runsc/boot/filter/config/config_test.go +++ b/runsc/boot/filter/config/config_test.go @@ -16,6 +16,7 @@ package config import ( "fmt" + "reflect" "testing" "golang.org/x/sys/unix" @@ -74,3 +75,87 @@ func TestIoctlFirstArgumentIsNonNegativeFD(t *testing.T) { }) } } + +// TestOptionsConfigKey verifies the behavior of `Options.ConfigKey`. +func TestOptionsConfigKey(t *testing.T) { + // mutateFn mutates the value of a specific Options field. + type mutateFn func(opt *Options) + + defaultOpt := Options{ + Platform: (&systrap.Systrap{}).SeccompInfo(), + } + + // Map of `Options` struct field names mapped to a function to mutate them. + // This should only contain fields which influence the configuration; + // calling the mutation function of these should change the value of + // `Options.Key`. + var configFields = map[string]mutateFn{ + "Platform": func(opt *Options) { + if defaultOpt.Platform.ConfigKey() == opt.Platform.ConfigKey() { + opt.Platform = (&kvm.KVM{}).SeccompInfo() + } else { + opt.Platform = (&systrap.Systrap{}).SeccompInfo() + } + }, + "HostNetwork": func(opt *Options) { opt.HostNetwork = !opt.HostNetwork }, + "HostNetworkRawSockets": func(opt *Options) { opt.HostNetworkRawSockets = !opt.HostNetworkRawSockets }, + "HostFilesystem": func(opt *Options) { opt.HostFilesystem = !opt.HostFilesystem }, + "ProfileEnable": func(opt *Options) { opt.ProfileEnable = !opt.ProfileEnable }, + "NVProxy": func(opt *Options) { opt.NVProxy = !opt.NVProxy }, + "TPUProxy": func(opt *Options) { opt.TPUProxy = !opt.TPUProxy }, + } + + // Map of `Options` struct field names mapped to a function to mutate them. + // This should only contain fields which are used as variables during + // filter generation; calling the mutation function of these should *not* + // change the value of `Options.Key`. + var varsFields = map[string]mutateFn{ + "ControllerFD": func(opt *Options) { opt.ControllerFD++ }, + } + + t.Run("fields are exhaustive", func(t *testing.T) { + for i := 0; i < reflect.ValueOf(defaultOpt).NumField(); i++ { + f := reflect.TypeOf(defaultOpt).Field(i) + found := false + for name := range configFields { + if f.Name == name { + found = true + break + } + } + for name := range varsFields { + if f.Name == name { + found = true + break + } + } + if !found { + t.Fatalf("field `Options.%s` is not known to TestOptionsKey; please add it", f.Name) + } + } + }) + + t.Run("mutating config fields causes ConfigKey to change", func(t *testing.T) { + opt := defaultOpt // Make a copy, as we're about to mutate it. + key := opt.ConfigKey() + for name, mutateFn := range configFields { + mutateFn(&opt) + newKey := opt.ConfigKey() + if key == newKey { + t.Fatalf("mutating config field %q did not cause the ConfigKey to change: %q", name, key) + } + key = newKey + } + }) + + t.Run("mutating vars fields does not cause ConfigKey to change", func(t *testing.T) { + opt := defaultOpt // Make a copy, as we're about to mutate it. + key := opt.ConfigKey() + for name, mutateFn := range varsFields { + mutateFn(&opt) + if newKey := opt.ConfigKey(); key != newKey { + t.Fatalf("mutating var field %q caused the ConfigKey to change: %q -> %q", name, key, newKey) + } + } + }) +}