runsc/filter: Refactor syscall filter config out to sub-package.

This separates the definition of which syscalls should be allowed/disallowed
from how that set of allowed syscalls is installed and managed.

In turn, this will enable the `runsc/filter` package to contain precompiled
programs of the seccomp filters, while the `runsc/filter/config` package is
focused only on defining what syscalls these programs will let through.

PiperOrigin-RevId: 583215418
This commit is contained in:
Etienne Perot
2023-11-16 17:35:24 -08:00
committed by gVisor bot
parent c96439ecd0
commit 7fd7e762f4
22 changed files with 229 additions and 165 deletions
+4 -34
View File
@@ -8,45 +8,14 @@ package(
go_library(
name = "filter",
srcs = [
"config.go",
"config_amd64.go",
"config_arm64.go",
"config_profile.go",
"extra_filters.go",
"extra_filters_asan.go",
"extra_filters_hostinet.go",
"extra_filters_msan.go",
"extra_filters_race.go",
"extra_filters_race_amd64.go",
"extra_filters_race_arm64.go",
"filter.go",
],
srcs = ["filter.go"],
visibility = [
"//runsc/boot:__subpackages__",
],
deps = [
"//pkg/abi/linux",
"//pkg/log",
"//pkg/seccomp",
"//pkg/sentry/devices/accel",
"//pkg/sentry/devices/nvproxy",
"//pkg/sentry/platform",
"//pkg/sentry/socket/hostinet",
"//pkg/tcpip/link/fdbased",
"@org_golang_x_sys//unix:go_default_library",
],
)
go_test(
name = "filter_test",
srcs = ["filter_test.go"],
library = ":filter",
deps = [
"//pkg/seccomp",
"//pkg/sentry/platform/kvm",
"//pkg/sentry/platform/systrap",
"@org_golang_x_sys//unix:go_default_library",
"//runsc/boot/filter/config",
],
)
@@ -59,6 +28,7 @@ secbench_test(
"//pkg/seccomp",
"//pkg/sentry/platform/kvm",
"//pkg/sentry/platform/systrap",
"//runsc/boot/filter/config",
"//test/secbench",
"//test/secbench/secbenchdef",
"@org_golang_x_sys//unix:go_default_library",
@@ -73,12 +43,12 @@ go_test(
],
data = ["filter_fuzz_golden.bpf"],
deps = [
":filter",
"//pkg/abi/linux",
"//pkg/bpf",
"//pkg/seccomp",
"//pkg/sentry/platform/systrap",
"//pkg/test/testutil",
"//runsc/boot/filter/config",
"//test/secfuzz",
"@org_golang_x_sys//unix:go_default_library",
],
+50
View File
@@ -0,0 +1,50 @@
load("//tools:defs.bzl", "go_library", "go_test")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
go_library(
name = "config",
srcs = [
"config.go",
"config_amd64.go",
"config_arm64.go",
"config_main.go",
"config_profile.go",
"extra_filters.go",
"extra_filters_asan.go",
"extra_filters_hostinet.go",
"extra_filters_msan.go",
"extra_filters_race.go",
"extra_filters_race_amd64.go",
"extra_filters_race_arm64.go",
],
visibility = [
"//runsc/boot/filter:__subpackages__",
],
deps = [
"//pkg/abi/linux",
"//pkg/log",
"//pkg/seccomp",
"//pkg/sentry/devices/accel",
"//pkg/sentry/devices/nvproxy",
"//pkg/sentry/platform",
"//pkg/sentry/socket/hostinet",
"//pkg/tcpip/link/fdbased",
"@org_golang_x_sys//unix:go_default_library",
],
)
go_test(
name = "config_test",
srcs = ["config_test.go"],
library = ":config",
deps = [
"//pkg/seccomp",
"//pkg/sentry/platform/kvm",
"//pkg/sentry/platform/systrap",
"@org_golang_x_sys//unix:go_default_library",
],
)
+119
View File
@@ -0,0 +1,119 @@
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package config defines all syscalls the sandbox is allowed to make
// to the host.
package config
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/devices/accel"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy"
"gvisor.dev/gvisor/pkg/sentry/platform"
)
// Options are seccomp filter related options.
type Options struct {
Platform platform.Platform
HostNetwork bool
HostNetworkRawSockets bool
HostFilesystem bool
ProfileEnable bool
NVProxy bool
TPUProxy bool
ControllerFD int
}
// 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 {
var warnings []string
if opt.HostNetwork {
if opt.HostNetworkRawSockets {
warnings = append(warnings, "host networking (with raw sockets) enabled: syscall filters less restrictive!")
} else {
warnings = append(warnings, "host networking enabled: syscall filters less restrictive!")
}
}
if opt.ProfileEnable {
warnings = append(warnings, "profile enabled: syscall filters less restrictive!")
}
if opt.HostFilesystem {
warnings = append(warnings, "host filesystem enabled: syscall filters less restrictive!")
}
if opt.NVProxy {
warnings = append(warnings, "Nvidia GPU driver proxy enabled: syscall filters less restrictive!")
}
if opt.TPUProxy {
warnings = append(warnings, "TPU device proxy enabled: syscall filters less restrictive!")
}
return warnings
}
// 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))
// Set of additional filters used by -race and -msan. Returns empty
// when not enabled.
s.Merge(instrumentationFilters())
if opt.HostNetwork {
s.Merge(hostInetFilters(opt.HostNetworkRawSockets))
}
if opt.ProfileEnable {
s.Merge(profileFilters())
}
if opt.HostFilesystem {
s.Merge(hostFilesystemFilters())
}
if opt.NVProxy {
s.Merge(nvproxy.Filters())
}
if opt.TPUProxy {
s.Merge(accel.Filters())
}
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
}
@@ -15,7 +15,7 @@
//go:build amd64
// +build amd64
package filter
package config
import (
"golang.org/x/sys/unix"
@@ -15,7 +15,7 @@
//go:build arm64
// +build arm64
package filter
package config
import (
"golang.org/x/sys/unix"
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package filter
package config
import (
"os"
@@ -15,7 +15,7 @@
//go:build !false
// +build !false
package filter
package config
import (
"golang.org/x/sys/unix"
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package filter
package config
import (
"fmt"
@@ -14,7 +14,7 @@
//go:build !asan && !msan && !race
package filter
package config
import (
"gvisor.dev/gvisor/pkg/seccomp"
@@ -15,16 +15,17 @@
//go:build asan
// +build asan
package filter
package config
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/seccomp"
)
// instrumentationFilters returns additional filters for syscalls used by ASAN.
func instrumentationFilters() seccomp.SyscallRules {
Report("ASAN is enabled: syscall filters less restrictive!")
log.Warningf("ASAN is enabled: syscall filters less restrictive!")
return seccomp.MakeSyscallRules(map[uintptr]seccomp.SyscallRule{
unix.SYS_CLONE: seccomp.MatchAll{},
unix.SYS_MMAP: seccomp.MatchAll{},
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package filter
package config
import (
"golang.org/x/sys/unix"
@@ -15,16 +15,17 @@
//go:build msan
// +build msan
package filter
package config
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/seccomp"
)
// instrumentationFilters returns additional filters for syscalls used by MSAN.
func instrumentationFilters() seccomp.SyscallRules {
Report("MSAN is enabled: syscall filters less restrictive!")
log.Warningf("MSAN is enabled: syscall filters less restrictive!")
return seccomp.MakeSyscallRules(map[uintptr]seccomp.SyscallRule{
unix.SYS_CLONE: seccomp.MatchAll{},
unix.SYS_MMAP: seccomp.MatchAll{},
@@ -15,16 +15,17 @@
//go:build race
// +build race
package filter
package config
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/seccomp"
)
// instrumentationFilters returns additional filters for syscalls used by TSAN.
func instrumentationFilters() seccomp.SyscallRules {
Report("TSAN is enabled: syscall filters less restrictive!")
log.Warningf("TSAN is enabled: syscall filters less restrictive!")
return archInstrumentationFilters(seccomp.MakeSyscallRules(map[uintptr]seccomp.SyscallRule{
unix.SYS_BRK: seccomp.MatchAll{},
unix.SYS_CLOCK_NANOSLEEP: seccomp.MatchAll{},
@@ -15,7 +15,7 @@
//go:build race
// +build race
package filter
package config
import (
"golang.org/x/sys/unix"
@@ -15,7 +15,7 @@
//go:build race
// +build race
package filter
package config
import (
"gvisor.dev/gvisor/pkg/seccomp"
+1 -1
View File
@@ -17,7 +17,7 @@ go_binary(
"//pkg/log",
"//pkg/seccomp",
"//pkg/sentry/platform/systrap",
"//runsc/boot/filter",
"//runsc/boot/filter/config",
"//runsc/flag",
],
)
+4 -4
View File
@@ -24,7 +24,7 @@ import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap"
"gvisor.dev/gvisor/runsc/boot/filter"
"gvisor.dev/gvisor/runsc/boot/filter/config"
"gvisor.dev/gvisor/runsc/flag"
)
@@ -62,13 +62,13 @@ func action(s string) linux.BPFAction {
func main() {
flag.Parse()
opt := filter.Options{
opt := config.Options{
Platform: &systrap.Systrap{},
NVProxy: *nvproxy,
}
rules, denyRules := filter.Rules(opt)
rules, denyRules := config.Rules(opt)
seccompOpts := filter.SeccompOptions(opt)
seccompOpts := config.SeccompOptions(opt)
seccompOpts.DefaultAction = action(*defaultAction)
seccompOpts.BadArchAction = action(*badArchAction)
insns, stats, err := seccomp.BuildProgram([]seccomp.RuleSet{
+12 -90
View File
@@ -12,104 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package filter defines all syscalls the sandbox is allowed to make
// to the host, and installs seccomp filters to prevent prohibited
// syscalls in case it's compromised.
// Package filter installs seccomp filters to prevent prohibited syscalls
// in case it's compromised.
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"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/runsc/boot/filter/config"
)
// Options are seccomp filter related options.
type Options struct {
Platform platform.Platform
HostNetwork bool
HostNetworkRawSockets bool
HostFilesystem bool
ProfileEnable bool
NVProxy bool
TPUProxy bool
ControllerFD int
}
// 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))
// Set of additional filters used by -race and -msan. Returns empty
// when not enabled.
s.Merge(instrumentationFilters())
if opt.HostNetwork {
if opt.HostNetworkRawSockets {
Report("host networking (with raw sockets) enabled: syscall filters less restrictive!")
} else {
Report("host networking enabled: syscall filters less restrictive!")
}
s.Merge(hostInetFilters(opt.HostNetworkRawSockets))
}
if opt.ProfileEnable {
Report("profile enabled: syscall filters less restrictive!")
s.Merge(profileFilters())
}
if opt.HostFilesystem {
Report("host filesystem enabled: syscall filters less restrictive!")
s.Merge(hostFilesystemFilters())
}
if opt.NVProxy {
Report("Nvidia GPU driver proxy enabled: syscall filters less restrictive!")
s.Merge(nvproxy.Filters())
}
if opt.TPUProxy {
Report("TPU device proxy enabled: syscall filters less restrictive!")
s.Merge(accel.Filters())
}
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
}
// Options is a re-export of the config Options type under this package.
type Options = config.Options
// Install seccomp filters based on the given platform.
func Install(opt Options) error {
rules, denyRules := Rules(opt)
return seccomp.Install(rules, denyRules, SeccompOptions(opt))
}
// Report writes a warning message to the log.
func Report(msg string) {
log.Warningf("*** SECCOMP WARNING: %s", msg)
// TODO(b/298726675): Look up precompiled rules and use them here if
// possible.
rules, denyRules := config.Rules(opt)
for _, warning := range config.Warnings(opt) {
log.Warningf("*** SECCOMP WARNING: %s", warning)
}
return seccomp.Install(rules, denyRules, config.SeccompOptions(opt))
}
+11 -11
View File
@@ -24,23 +24,23 @@ import (
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/platform/kvm"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap"
"gvisor.dev/gvisor/runsc/boot/filter"
"gvisor.dev/gvisor/runsc/boot/filter/config"
"gvisor.dev/gvisor/test/secbench"
"gvisor.dev/gvisor/test/secbench/secbenchdef"
)
type Options struct {
Name string
Options filter.Options
Options config.Options
}
// BenchmarkSentrySystrap benchmarks the seccomp filters used by the Sentry
// using the Systrap platform.
func BenchmarkSentrySystrap(b *testing.B) {
opts := filter.Options{
opts := config.Options{
Platform: &systrap.Systrap{},
}
rules, denyRules := filter.Rules(opts)
rules, denyRules := config.Rules(opts)
secbench.Run(b, secbench.BenchFromSyscallRules(
b,
"Postgres",
@@ -64,17 +64,17 @@ func BenchmarkSentrySystrap(b *testing.B) {
},
rules,
denyRules,
filter.SeccompOptions(opts),
config.SeccompOptions(opts),
))
}
// BenchmarkSentryKVM benchmarks the seccomp filters used by the Sentry
// using the KVM platform.
func BenchmarkSentryKVM(b *testing.B) {
opts := filter.Options{
opts := config.Options{
Platform: &kvm.KVM{},
}
rules, denyRules := filter.Rules(opts)
rules, denyRules := config.Rules(opts)
secbench.Run(b, secbench.BenchFromSyscallRules(
b,
"Postgres",
@@ -96,16 +96,16 @@ func BenchmarkSentryKVM(b *testing.B) {
},
rules,
denyRules,
filter.SeccompOptions(opts),
config.SeccompOptions(opts),
))
}
func BenchmarkNVProxyIoctl(b *testing.B) {
opts := filter.Options{
opts := config.Options{
Platform: &systrap.Systrap{},
NVProxy: true,
}
rules, denyRules := filter.Rules(opts)
rules, denyRules := config.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 {
@@ -128,6 +128,6 @@ func BenchmarkNVProxyIoctl(b *testing.B) {
},
rules,
denyRules,
filter.SeccompOptions(opts),
config.SeccompOptions(opts),
))
}
+4 -4
View File
@@ -26,7 +26,7 @@ import (
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/boot/filter"
"gvisor.dev/gvisor/runsc/boot/filter/config"
"gvisor.dev/gvisor/test/secfuzz"
)
@@ -62,10 +62,10 @@ func FuzzFilterAgainstGolden(f *testing.F) {
EnforceFullCoverage: false,
}
filterOpts := filter.Options{
filterOpts := config.Options{
Platform: &systrap.Systrap{},
}
rules, denyRules := filter.Rules(filterOpts)
rules, denyRules := config.Rules(filterOpts)
ruleSets := []seccomp.RuleSet{
{
Rules: denyRules,
@@ -76,7 +76,7 @@ func FuzzFilterAgainstGolden(f *testing.F) {
Action: linux.SECCOMP_RET_ALLOW,
},
}
opts := filter.SeccompOptions(filterOpts)
opts := config.SeccompOptions(filterOpts)
// We use unique actions here to be able to tell them apart.
opts.DefaultAction = linux.SECCOMP_RET_KILL_THREAD
opts.BadArchAction = linux.SECCOMP_RET_KILL_PROCESS

Some files were not shown because too many files have changed in this diff Show More