Plumb seccomp program options through the Sentry filter and Platform.

This adds a `HottestSyscalls` function to the `Platform` interface, as the
platform determines which syscalls are most often called by the Sentry.

This is used by the Sentry seccomp filter code to build the final list of
hot syscalls.

PiperOrigin-RevId: 582833363
This commit is contained in:
Etienne Perot
2023-11-15 15:57:53 -08:00
committed by gVisor bot
parent 8d87a534c0
commit 9fd832da02
10 changed files with 88 additions and 20 deletions
+2
View File
@@ -69,6 +69,8 @@ type KVM struct {
platform.DoesOwnPageTables
platform.HottestSyscallsNotSpecified
// machine is the backing VM.
machine *machine
}
+19
View File
@@ -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 {
+1
View File
@@ -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.
+1
View File
@@ -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.
+4
View File
@@ -44,3 +44,7 @@ func init() {
func archFstatAtSysNo() uintptr {
return unix.SYS_NEWFSTATAT
}
func archSpecificHotSyscalls() []uintptr {
return nil // TODO(b/298726675): Populate.
}
+4
View File
@@ -44,3 +44,7 @@ func init() {
func archFstatAtSysNo() uintptr {
return unix.SYS_FSTATAT
}
func archSpecificHotSyscalls() []uintptr {
return nil
}
+4 -6
View File
@@ -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)
+30 -3
View File
@@ -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.
+12 -6
View File
@@ -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),
))
}
+11 -5
View File
@@ -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)
}