Seccomp filters: Add method to uniquely identify the seccomp configuration.

This adds a `Key()` method on `config.Options` which uniquely identifies
the ingredients that go into the seccomp filter configuration.

This is necessary for precompiling seccomp filters. At runtime, precompiled
seccomp programs will be matched according to their config key.

PiperOrigin-RevId: 583492673
This commit is contained in:
Etienne Perot
2023-11-17 14:51:12 -08:00
committed by gVisor bot
parent 51318e8a91
commit 2ef56fee94
2 changed files with 106 additions and 0 deletions
+21
View File
@@ -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 {
+85
View File
@@ -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)
}
}
})
}