From c46ffacf2f118fb707a20e053ff027bb725bc06c Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Sun, 12 Nov 2023 00:44:32 -0800 Subject: [PATCH] Separate out rule optimizers from main syscall rendering. This does an optimization pass over `RuleSet`s prior to rendering anything, rather than optimizing at the last minute for each syscall. PiperOrigin-RevId: 581671748 --- pkg/seccomp/seccomp.go | 34 +++++++++++++++++----- runsc/boot/filter/dumpfilter/dumpfilter.go | 5 ++-- test/secbench/secbench.go | 4 ++- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/pkg/seccomp/seccomp.go b/pkg/seccomp/seccomp.go index 30392bc7c..81ddd1203 100644 --- a/pkg/seccomp/seccomp.go +++ b/pkg/seccomp/seccomp.go @@ -330,15 +330,32 @@ type BuildStats struct { // BPF bytecode optimizations). BuildDuration time.Duration - // OptimizeDuration is the amount of time it took to run BPF bytecode + // RuleOptimizeDuration is the amount of time it took to run SyscallRule // optimizations. - OptimizeDuration time.Duration + RuleOptimizeDuration time.Duration + + // BPFOptimizeDuration is the amount of time it took to run BPF bytecode + // optimizations. + BPFOptimizeDuration 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, options ProgramOptions) ([]bpf.Instruction, BuildStats, error) { start := time.Now() + + // Make a copy of the syscall rules and optimize them. + rulesCopy := make([]RuleSet, len(rules)) + for i, rs := range rules { + rulesMap := MakeSyscallRules(make(map[uintptr]SyscallRule, len(rs.Rules.rules))) + for sysno, rule := range rs.Rules.rules { + rulesMap.rules[sysno] = optimizeSyscallRule(rule) + } + rs.Rules = rulesMap + rulesCopy[i] = rs + } + ruleOptimizeDuration := time.Since(start) + program := &syscallProgram{ program: bpf.NewProgramBuilder(), } @@ -350,7 +367,7 @@ func BuildProgram(rules []RuleSet, options ProgramOptions) ([]bpf.Instruction, B badArchLabel := label("badarch") 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 { + if err := buildIndex(rulesCopy, program); err != nil { return nil, BuildStats{}, err } @@ -367,16 +384,17 @@ func BuildProgram(rules []RuleSet, options ProgramOptions) ([]bpf.Instruction, B return nil, BuildStats{}, err } beforeOpt := len(insns) - buildDuration := time.Since(start) + buildDuration := time.Since(start) - ruleOptimizeDuration insns = bpf.Optimize(insns) - optimizeDuration := time.Since(start) - buildDuration + bpfOptimizeDuration := time.Since(start) - ruleOptimizeDuration - buildDuration afterOpt := len(insns) - log.Debugf("Seccomp program optimized from %d to %d instructions; took %v to build and %v to optimize", beforeOpt, afterOpt, buildDuration, optimizeDuration) + log.Debugf("Seccomp program optimized from %d to %d instructions; took %v to build and %v to optimize", beforeOpt, afterOpt, buildDuration, bpfOptimizeDuration) return insns, BuildStats{ SizeBeforeOptimizations: beforeOpt, SizeAfterOptimizations: afterOpt, BuildDuration: buildDuration, - OptimizeDuration: optimizeDuration, + RuleOptimizeDuration: ruleOptimizeDuration, + BPFOptimizeDuration: bpfOptimizeDuration, }, nil } @@ -532,7 +550,7 @@ func buildBSTProgram(n *node, rng knownRange, rules []RuleSet, program *syscallP // check the next rule set. We need to ensure // that at the very end, we insert a direct // jump label for the unmatched case. - optimizeSyscallRule(rule).Render(program, ruleSetLabelSet) + rule.Render(program, ruleSetLabelSet) ruleSetFrag.MustHaveJumpedTo(ruleSetLabelSet.Matched(), ruleSetLabelSet.Mismatched()) program.Label(ruleSetLabelSet.Matched()) program.Ret(rs.Action) diff --git a/runsc/boot/filter/dumpfilter/dumpfilter.go b/runsc/boot/filter/dumpfilter/dumpfilter.go index 5a707aeaf..9328431ae 100644 --- a/runsc/boot/filter/dumpfilter/dumpfilter.go +++ b/runsc/boot/filter/dumpfilter/dumpfilter.go @@ -60,8 +60,9 @@ func main() { log.Infof("Size before optimizations: %d", stats.SizeBeforeOptimizations) log.Infof("Size after optimizations: %d", stats.SizeAfterOptimizations) log.Infof("Build duration: %v", stats.BuildDuration) - log.Infof("Optimization passes duration: %v", stats.OptimizeDuration) - log.Infof("Total duration: %v", stats.BuildDuration+stats.OptimizeDuration) + log.Infof("Rule optimization passes duration: %v", stats.RuleOptimizeDuration) + log.Infof("BPF optimization passes duration: %v", stats.BPFOptimizeDuration) + log.Infof("Total duration: %v", stats.BuildDuration+stats.RuleOptimizeDuration+stats.BPFOptimizeDuration) switch *output { case "fancy": dump, err := bpf.DecodeInstructions(insns) diff --git a/test/secbench/secbench.go b/test/secbench/secbench.go index 766d3009e..7715b5349 100644 --- a/test/secbench/secbench.go +++ b/test/secbench/secbench.go @@ -149,7 +149,9 @@ func RunBench(b *testing.B, bn secbenchdef.Bench) { 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.RuleOptimizeDuration.Nanoseconds()), "ruleopt-ns") + b.ReportMetric(float64(bn.BuildStats.BPFOptimizeDuration.Nanoseconds()), "bpfopt-ns") + b.ReportMetric(float64((bn.BuildStats.RuleOptimizeDuration + bn.BuildStats.BPFOptimizeDuration).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")