diff --git a/pkg/sentry/platform/kvm/kvm.go b/pkg/sentry/platform/kvm/kvm.go index d7dec72b1..e1d0218e9 100644 --- a/pkg/sentry/platform/kvm/kvm.go +++ b/pkg/sentry/platform/kvm/kvm.go @@ -69,6 +69,8 @@ type KVM struct { platform.DoesOwnPageTables + platform.HottestSyscallsNotSpecified + // machine is the backing VM. machine *machine } diff --git a/pkg/sentry/platform/platform.go b/pkg/sentry/platform/platform.go index 3e32d97de..fb11271b6 100644 --- a/pkg/sentry/platform/platform.go +++ b/pkg/sentry/platform/platform.go @@ -122,6 +122,16 @@ type Platform interface { // SyscallFilters returns syscalls made exclusively by this platform. SyscallFilters() seccomp.SyscallRules + + // HottestSyscalls returns the list of syscall numbers that this platform + // calls most often, most-frequently-called first. No more than a dozen + // syscalls. Returning an empty or a nil slice is OK. + // This is used to produce a more efficient seccomp-bpf program that can + // check for the most frequently called syscalls first. + // What matters here is only the frequency at which a syscall is called, + // not the total amount of CPU time that is used to process it in the host + // kernel. + HottestSyscalls() []uintptr } // NoCPUPreemptionDetection implements Platform.DetectsCPUPreemption and @@ -191,6 +201,15 @@ func (DoesNotOwnPageTables) OwnsPageTables() bool { return false } +// HottestSyscallsNotSpecified implements Platform.HottestSyscalls and does +// not return any syscall as being hot. +type HottestSyscallsNotSpecified struct{} + +// HottestSyscalls implements Platform.HottestSyscalls. +func (HottestSyscallsNotSpecified) HottestSyscalls() []uintptr { + return nil +} + // MemoryManager represents an abstraction above the platform address space // which manages memory mappings and their contents. type MemoryManager interface { diff --git a/pkg/sentry/platform/ptrace/ptrace.go b/pkg/sentry/platform/ptrace/ptrace.go index 985d480be..0dd10f8be 100644 --- a/pkg/sentry/platform/ptrace/ptrace.go +++ b/pkg/sentry/platform/ptrace/ptrace.go @@ -208,6 +208,7 @@ type PTrace struct { platform.NoCPUPreemptionDetection platform.UseHostGlobalMemoryBarrier platform.DoesNotOwnPageTables + platform.HottestSyscallsNotSpecified } // New returns a new ptrace-based implementation of the platform interface. diff --git a/pkg/sentry/platform/systrap/systrap.go b/pkg/sentry/platform/systrap/systrap.go index e5869845c..f1217602d 100644 --- a/pkg/sentry/platform/systrap/systrap.go +++ b/pkg/sentry/platform/systrap/systrap.go @@ -301,6 +301,7 @@ type Systrap struct { platform.NoCPUPreemptionDetection platform.UseHostGlobalMemoryBarrier platform.DoesNotOwnPageTables + platform.HottestSyscallsNotSpecified // memoryFile is used to create a stub sysmsg stack // which is shared with the Sentry. diff --git a/runsc/boot/filter/config_amd64.go b/runsc/boot/filter/config_amd64.go index a56abad9b..447542ec1 100644 --- a/runsc/boot/filter/config_amd64.go +++ b/runsc/boot/filter/config_amd64.go @@ -44,3 +44,7 @@ func init() { func archFstatAtSysNo() uintptr { return unix.SYS_NEWFSTATAT } + +func archSpecificHotSyscalls() []uintptr { + return nil // TODO(b/298726675): Populate. +} diff --git a/runsc/boot/filter/config_arm64.go b/runsc/boot/filter/config_arm64.go index 778cb5fd9..0f22944df 100644 --- a/runsc/boot/filter/config_arm64.go +++ b/runsc/boot/filter/config_arm64.go @@ -44,3 +44,7 @@ func init() { func archFstatAtSysNo() uintptr { return unix.SYS_FSTATAT } + +func archSpecificHotSyscalls() []uintptr { + return nil +} diff --git a/runsc/boot/filter/dumpfilter/dumpfilter.go b/runsc/boot/filter/dumpfilter/dumpfilter.go index 9328431ae..3c1459ee0 100644 --- a/runsc/boot/filter/dumpfilter/dumpfilter.go +++ b/runsc/boot/filter/dumpfilter/dumpfilter.go @@ -36,10 +36,11 @@ var ( func main() { flag.Parse() - rules, denyRules := filter.Rules(filter.Options{ + opt := filter.Options{ Platform: &systrap.Systrap{}, NVProxy: *nvproxy, - }) + } + rules, denyRules := filter.Rules(opt) insns, stats, err := seccomp.BuildProgram([]seccomp.RuleSet{ { Rules: denyRules, @@ -49,10 +50,7 @@ func main() { Rules: rules, Action: linux.SECCOMP_RET_ALLOW, }, - }, seccomp.ProgramOptions{ - DefaultAction: linux.SECCOMP_RET_ERRNO, - BadArchAction: linux.SECCOMP_RET_ERRNO, - }) + }, filter.SeccompOptions(opt)) if err != nil { log.Warningf("%v", err) os.Exit(1) diff --git a/runsc/boot/filter/filter.go b/runsc/boot/filter/filter.go index cc31f6a1a..762b8fd81 100644 --- a/runsc/boot/filter/filter.go +++ b/runsc/boot/filter/filter.go @@ -18,6 +18,7 @@ package filter import ( + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/seccomp" "gvisor.dev/gvisor/pkg/sentry/devices/accel" @@ -37,7 +38,7 @@ type Options struct { ControllerFD int } -// Rules returns the seccomp (rules, denyRules) to use for the Sentry. +// Rules returns the seccomp rules and denyRules to use for the Sentry. func Rules(opt Options) (seccomp.SyscallRules, seccomp.SyscallRules) { s := allowedSyscalls s.Merge(controlServerFilters(opt.ControllerFD)) @@ -72,14 +73,40 @@ func Rules(opt Options) (seccomp.SyscallRules, seccomp.SyscallRules) { } s.Merge(opt.Platform.SyscallFilters()) - return s, seccomp.DenyNewExecMappings } +// SeccompOptions returns the seccomp program options to use for the filter. +func SeccompOptions(opt Options) seccomp.ProgramOptions { + // futex(2) is unequivocally the most-frequently-used syscall by the + // Sentry across all platforms. + hotSyscalls := []uintptr{unix.SYS_FUTEX} + // ... Then comes the platform-specific hot syscalls which are typically + // part of the syscall interception hot path. + hotSyscalls = append(hotSyscalls, opt.Platform.HottestSyscalls()...) + // ... Then come a few syscalls that are frequent just from workloads in + // general. + hotSyscalls = append(hotSyscalls, archSpecificHotSyscalls()...) + + // Now deduplicate them. + sysnoMap := make(map[uintptr]struct{}, len(hotSyscalls)) + uniqueHotSyscalls := make([]uintptr, 0, len(hotSyscalls)) + for _, sysno := range hotSyscalls { + if _, alreadyAdded := sysnoMap[sysno]; !alreadyAdded { + sysnoMap[sysno] = struct{}{} + uniqueHotSyscalls = append(uniqueHotSyscalls, sysno) + } + } + + opts := seccomp.DefaultProgramOptions() + opts.HotSyscalls = uniqueHotSyscalls + return opts +} + // Install seccomp filters based on the given platform. func Install(opt Options) error { rules, denyRules := Rules(opt) - return seccomp.Install(rules, denyRules, seccomp.DefaultProgramOptions()) + return seccomp.Install(rules, denyRules, SeccompOptions(opt)) } // Report writes a warning message to the log. diff --git a/runsc/boot/filter/filter_bench_test.go b/runsc/boot/filter/filter_bench_test.go index 1c66d10ad..894c50ac4 100644 --- a/runsc/boot/filter/filter_bench_test.go +++ b/runsc/boot/filter/filter_bench_test.go @@ -37,9 +37,10 @@ type Options struct { // BenchmarkSentrySystrap benchmarks the seccomp filters used by the Sentry // using the Systrap platform. func BenchmarkSentrySystrap(b *testing.B) { - rules, denyRules := filter.Rules(filter.Options{ + opts := filter.Options{ Platform: &systrap.Systrap{}, - }) + } + rules, denyRules := filter.Rules(opts) secbench.Run(b, secbench.BenchFromSyscallRules( b, "Postgres", @@ -63,15 +64,17 @@ func BenchmarkSentrySystrap(b *testing.B) { }, rules, denyRules, + filter.SeccompOptions(opts), )) } // BenchmarkSentryKVM benchmarks the seccomp filters used by the Sentry // using the KVM platform. func BenchmarkSentryKVM(b *testing.B) { - rules, denyRules := filter.Rules(filter.Options{ + opts := filter.Options{ Platform: &kvm.KVM{}, - }) + } + rules, denyRules := filter.Rules(opts) secbench.Run(b, secbench.BenchFromSyscallRules( b, "Postgres", @@ -93,14 +96,16 @@ func BenchmarkSentryKVM(b *testing.B) { }, rules, denyRules, + filter.SeccompOptions(opts), )) } func BenchmarkNVProxyIoctl(b *testing.B) { - rules, denyRules := filter.Rules(filter.Options{ + opts := filter.Options{ Platform: &systrap.Systrap{}, NVProxy: true, - }) + } + rules, denyRules := filter.Rules(opts) var sequences []secbenchdef.Sequence if err := rules.ForSingleArgument(unix.SYS_IOCTL, 1, func(v seccomp.ValueMatcher) error { if arg1Equal, isArg1Equal := v.(seccomp.EqualTo); isArg1Equal { @@ -123,5 +128,6 @@ func BenchmarkNVProxyIoctl(b *testing.B) { }, rules, denyRules, + filter.SeccompOptions(opts), )) } diff --git a/test/secbench/secbench.go b/test/secbench/secbench.go index 7715b5349..72139d284 100644 --- a/test/secbench/secbench.go +++ b/test/secbench/secbench.go @@ -33,13 +33,22 @@ import ( ) // BenchFromSyscallRules returns a new Bench created from SyscallRules. -func BenchFromSyscallRules(b *testing.B, name string, profile secbenchdef.Profile, rules seccomp.SyscallRules, denyRules seccomp.SyscallRules) secbenchdef.Bench { +func BenchFromSyscallRules(b *testing.B, name string, profile secbenchdef.Profile, rules seccomp.SyscallRules, denyRules seccomp.SyscallRules, options seccomp.ProgramOptions) secbenchdef.Bench { // If there is a rule allowing rt_sigreturn to be called, // also add a rule for the stand-in syscall number instead. if rules.Has(unix.SYS_RT_SIGRETURN) { rules = rules.Copy() rules.Set(uintptr(secbenchdef.RTSigreturn.Data(profile.Arch).Nr), rules.Get(unix.SYS_RT_SIGRETURN)) } + // Also replace it in the list of hottest syscalls. + for i, sysno := range options.HotSyscalls { + if sysno == unix.SYS_RT_SIGRETURN { + options.HotSyscalls[i] = uintptr(secbenchdef.RTSigreturn.Data(profile.Arch).Nr) + } + } + + options.DefaultAction = linux.SECCOMP_RET_ERRNO + options.BadArchAction = linux.SECCOMP_RET_ERRNO insns, buildStats, err := seccomp.BuildProgram([]seccomp.RuleSet{ { Rules: denyRules, @@ -49,10 +58,7 @@ func BenchFromSyscallRules(b *testing.B, name string, profile secbenchdef.Profil Rules: rules, Action: linux.SECCOMP_RET_ALLOW, }, - }, seccomp.ProgramOptions{ - DefaultAction: linux.SECCOMP_RET_ERRNO, - BadArchAction: linux.SECCOMP_RET_ERRNO, - }) + }, options) if err != nil { b.Fatalf("BuildProgram() failed: %v", err) }