secbench: Benchmark optimization duration and compression ratio.

Current values for the Sentry filters:

```
              │   current   │
              │  build-sec  │
SentrySystrap   13.73m ± 0%
SentryKVM       16.36m ± 0%

              │      current      │
              │ compression-ratio │
SentrySystrap          2.165 ± 0%
SentryKVM              2.132 ± 0%

              │   current   │
              │  gen-instr  │
SentrySystrap   1.288k ± 0%
SentryKVM       1.373k ± 0%

              │  current   │
              │ opt-instr  │
SentrySystrap   595.0 ± 0%
SentryKVM       644.0 ± 0%

              │   current   │
              │   opt-sec   │
SentrySystrap   819.0µ ± 2%
SentryKVM       897.0µ ± 1%
```

PiperOrigin-RevId: 572089103
This commit is contained in:
Etienne Perot
2023-10-09 17:53:22 -07:00
committed by gVisor bot
parent 1a680b825b
commit 71dc79e653
11 changed files with 56 additions and 22 deletions
+30 -6
View File
@@ -19,6 +19,7 @@ package seccomp
import (
"fmt"
"sort"
"time"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
@@ -67,7 +68,7 @@ func Install(rules SyscallRules, denyRules SyscallRules) error {
log.Infof("Installing seccomp filters for %d syscalls (action=%v)", len(rules), defaultAction)
instrs, err := BuildProgram([]RuleSet{
instrs, _, err := BuildProgram([]RuleSet{
{
Rules: denyRules,
Action: defaultAction,
@@ -269,9 +270,25 @@ func (l *labelSet) Push(labelSuffix string, newRuleMatch, newRuleMismatch label)
}
}
// BuildStats contains information about seccomp program generation.
type BuildStats struct {
// SizeBeforeOptimizations and SizeAfterOptimizations correspond to the
// number of instructions in the program before vs after optimization.
SizeBeforeOptimizations, SizeAfterOptimizations int
// BuildDuration is the amount of time it took to build the program (before
// BPF bytecode optimizations).
BuildDuration time.Duration
// OptimizeDuration is the amount of time it took to run BPF bytecode
// optimizations.
OptimizeDuration time.Duration
}
// BuildProgram builds a BPF program from the given map of actions to matching
// SyscallRules. The single generated program covers all provided RuleSets.
func BuildProgram(rules []RuleSet, defaultAction, badArchAction linux.BPFAction) ([]bpf.Instruction, error) {
func BuildProgram(rules []RuleSet, defaultAction, badArchAction linux.BPFAction) ([]bpf.Instruction, BuildStats, error) {
start := time.Now()
program := &syscallProgram{
program: bpf.NewProgramBuilder(),
}
@@ -284,7 +301,7 @@ func BuildProgram(rules []RuleSet, defaultAction, badArchAction linux.BPFAction)
program.Stmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetArch)
program.IfNot(bpf.Jmp|bpf.Jeq|bpf.K, LINUX_AUDIT_ARCH, badArchLabel)
if err := buildIndex(rules, program); err != nil {
return nil, err
return nil, BuildStats{}, err
}
// Default label if none of the rules matched:
@@ -297,13 +314,20 @@ func BuildProgram(rules []RuleSet, defaultAction, badArchAction linux.BPFAction)
insns, err := program.program.Instructions()
if err != nil {
return insns, err
return nil, BuildStats{}, err
}
beforeOpt := len(insns)
buildDuration := time.Since(start)
insns = bpf.Optimize(insns)
optimizeDuration := time.Since(start) - buildDuration
afterOpt := len(insns)
log.Debugf("Seccomp program optimized from %d to %d instructions", beforeOpt, afterOpt)
return insns, nil
log.Debugf("Seccomp program optimized from %d to %d instructions; took %v to build and %v to optimize", beforeOpt, afterOpt, buildDuration, optimizeDuration)
return insns, BuildStats{
SizeBeforeOptimizations: beforeOpt,
SizeAfterOptimizations: afterOpt,
BuildDuration: buildDuration,
OptimizeDuration: optimizeDuration,
}, nil
}
// buildIndex builds a BST to quickly search through all syscalls.
+2 -2
View File
@@ -877,7 +877,7 @@ func TestBasic(t *testing.T) {
},
} {
t.Run(test.name, func(t *testing.T) {
instrs, err := BuildProgram(test.ruleSets, test.defaultAction, test.badArchAction)
instrs, _, err := BuildProgram(test.ruleSets, test.defaultAction, test.badArchAction)
if err != nil {
t.Fatalf("BuildProgram() got error: %v", err)
}
@@ -913,7 +913,7 @@ func TestRandom(t *testing.T) {
}
t.Logf("Testing filters: %v", syscallRules)
instrs, err := BuildProgram([]RuleSet{
instrs, _, err := BuildProgram([]RuleSet{
{
Rules: syscallRules,
Action: linux.SECCOMP_RET_ALLOW,
+1 -1
View File
@@ -791,7 +791,7 @@ func seccompMmapRules(m *machine) {
Action: linux.SECCOMP_RET_TRAP,
},
}
instrs, err := seccomp.BuildProgram(rules, linux.SECCOMP_RET_ALLOW, linux.SECCOMP_RET_ALLOW)
instrs, _, err := seccomp.BuildProgram(rules, linux.SECCOMP_RET_ALLOW, linux.SECCOMP_RET_ALLOW)
if err != nil {
panic(fmt.Sprintf("failed to build rules: %v", err))
}
@@ -114,7 +114,7 @@ func attachedThread(flags uintptr, defaultAction linux.BPFAction) (*thread, erro
})
}
rules = appendArchSeccompRules(rules, defaultAction)
instrs, err := seccomp.BuildProgram(rules, defaultAction, defaultAction)
instrs, _, err := seccomp.BuildProgram(rules, defaultAction, defaultAction)
if err != nil {
return nil, err
}
@@ -130,7 +130,7 @@ func attachedThread(flags uintptr, defaultAction linux.BPFAction) (*thread, erro
rules = append(rules, ruleSet)
rules = appendArchSeccompRules(rules)
}
instrs, err := seccomp.BuildProgram(rules, defaultAction, defaultAction)
instrs, _, err := seccomp.BuildProgram(rules, defaultAction, defaultAction)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -146,7 +146,7 @@ func sysmsgThreadRules(stubStart uintptr) []bpf.Instruction {
Action: linux.SECCOMP_RET_ALLOW,
},
}...)
instrs, err := seccomp.BuildProgram(rules, linux.SECCOMP_RET_TRAP, linux.SECCOMP_RET_TRAP)
instrs, _, err := seccomp.BuildProgram(rules, linux.SECCOMP_RET_TRAP, linux.SECCOMP_RET_TRAP)
if err != nil {
panic(fmt.Sprintf("failed to build rules for sysmsg threads: %v", err))
}
+1 -1
View File
@@ -51,7 +51,7 @@ func BuildProgram(s *specs.LinuxSeccomp) (bpf.Program, error) {
return bpf.Program{}, fmt.Errorf("invalid seccomp rules: %w", err)
}
instrs, err := seccomp.BuildProgram(ruleset, defaultAction, killThreadAction)
instrs, _, err := seccomp.BuildProgram(ruleset, defaultAction, killThreadAction)
if err != nil {
return bpf.Program{}, fmt.Errorf("building seccomp program: %w", err)
}
+1 -1
View File
@@ -77,7 +77,7 @@ func run(req secbenchdef.BenchRunRequest) (secbenchdef.BenchRunResponse, error)
// We're ready. Install the BPF program.
if req.InstallFilter {
if err := install(bn.Program); err != nil {
if err := install(bn.Instructions); err != nil {
panic(fmt.Sprintf("cannot install BPF program: %v", err))
}
}
+12 -6
View File
@@ -35,14 +35,14 @@ import (
"gvisor.dev/gvisor/test/secbench/secbenchdef"
)
// BenchFromSyscallRules returns a new Bench creates from SyscallRules.
// 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 {
// If there is a rule allowing rt_sigreturn to be called,
// also add a rule for the stand-in syscall number instead.
if sigreturnRule, found := rules[unix.SYS_RT_SIGRETURN]; found {
rules[uintptr(secbenchdef.RTSigreturn.Data(profile.Arch).Nr)] = sigreturnRule
}
instrs, err := seccomp.BuildProgram([]seccomp.RuleSet{
insns, buildStats, err := seccomp.BuildProgram([]seccomp.RuleSet{
{
Rules: denyRules,
Action: linux.SECCOMP_RET_ERRNO,
@@ -56,9 +56,10 @@ func BenchFromSyscallRules(b *testing.B, name string, profile secbenchdef.Profil
b.Fatalf("BuildProgram() failed: %v", err)
}
return secbenchdef.Bench{
Name: name,
Profile: secbenchdef.Profile(profile),
Program: instrs,
Name: name,
Profile: secbenchdef.Profile(profile),
Instructions: insns,
BuildStats: buildStats,
}
}
@@ -153,10 +154,15 @@ func RunBench(b *testing.B, bn secbenchdef.Bench) {
// two runs.
// If there are no syscall sequences that will be approved, then we can
// skip running the runner the second time altogether.
program, err := bpf.Compile(bn.Program)
program, err := bpf.Compile(bn.Instructions)
if err != nil {
b.Fatalf("program does not compile: %v", err)
}
b.ReportMetric(float64(bn.BuildStats.BuildDuration.Nanoseconds()), "build-ns")
b.ReportMetric(float64(bn.BuildStats.OptimizeDuration.Nanoseconds()), "opt-ns")
b.ReportMetric(float64(bn.BuildStats.SizeBeforeOptimizations), "gen-instr")
b.ReportMetric(float64(bn.BuildStats.SizeAfterOptimizations), "opt-instr")
b.ReportMetric(float64(bn.BuildStats.SizeBeforeOptimizations)/float64(bn.BuildStats.SizeAfterOptimizations), "compression-ratio")
activeSequences := make([]bool, len(bn.Profile.Sequences))
positiveSequenceIndexes := make(map[int]struct{}, len(bn.Profile.Sequences))
for i, seq := range bn.Profile.Sequences {
+1
View File
@@ -17,6 +17,7 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/bpf",
"//pkg/seccomp",
"@org_golang_x_sys//unix:go_default_library",
],
)
+5 -2
View File
@@ -21,6 +21,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/bpf"
"gvisor.dev/gvisor/pkg/seccomp"
)
// Bench represents a benchmark to run.
@@ -29,8 +30,10 @@ type Bench struct {
Name string `json:"name"`
// Profile represents the syscall pattern profile being benchmarked.
Profile Profile `json:"profile"`
// Program is the seccomp-bpf program to run the benchmark with.
Program []bpf.Instruction `json:"program"`
// Instructions is the seccomp-bpf program to run the benchmark with.
Instructions []bpf.Instruction `json:"instructions"`
// BuildStats contains information on timing and size of the program.
BuildStats seccomp.BuildStats `json:"buildStats"`
// AllowRejected can be set to true if some sequences in the application
// profile are expected to not be allowed.
// If this is the case, the program's overall performance will not be