Files
Etienne Perot 0cd10e4a36 runsc boot: Add startup timer instrumentation.
Example output:

```
I0807 10:27:23.158030       1 timing.go:866] ---- runsc boot timing information ----
[10:27:22.991938]                               ╭─ runsc boot
[10:27:22.995285]        +3 346µs ▋             ├─ Go code running: 3 346µs
[10:27:22.996191]          +906µs ▏             ├─ boot subcommand dispatched: 906µs
[10:27:22.996643]          +451µs               ├─ CPUID initialized: 451µs
[10:27:22.996645]            +2µs               ├─ ring0 initialized: 2µs
[10:27:22.997012]          +367µs               ├─ host info read: 367µs
[10:27:22.998024]        +1 011µs ▏             ├─ spec read: 1 011µs
[10:27:22.998555]          +530µs               ├─ chroot base mounted: 530µs
[10:27:22.998822]          +267µs               ├─ chroot proc mounted: 267µs
[10:27:23.000271]        +1 449µs ▎             ├─ chroot procfs set up: 1 449µs
[10:27:23.000369]           +97µs               ├─ chroot sealed read-only: 97µs
[10:27:23.002635]        +2 266µs ▍             ├─ chroot set up: 2 266µs
[10:27:23.003523]          +887µs ▏             ├─ sandbox capabilities computed: 887µs
[10:27:23.003693]          +170µs               ├─ current capabilities read: 170µs
[10:27:23.012905]        +9 211µs ▉▊            ├─ bounding set trimmed on all threads: 9 211µs
[10:27:23.013139]          +233µs               ├─ capset applied on all threads: 233µs
[10:27:23.013449]          +310µs               ├─ capabilities applied: 310µs
[10:27:23.024366]       +10 916µs ▉▉            ├─ resolved mounts read: 10 916µs
[10:27:23.029416]        +5 049µs ▉             ├─ seccheck initialized: 5 049µs
[10:27:23.029419]            +3µs               ├─ RNG initialized: 3µs
[10:27:23.029571]          +152µs               ├─ memory usage initialized: 152µs
[10:27:23.029619]           +47µs               ├─ GOMAXPROCS set: 47µs
[10:27:23.057560]       +27 941µs ▉▉▉▉▉▍        ├─ stdio FDs remapped: 27 941µs
[10:27:23.057563]            +2µs               ├─ donated FDs ingested: 2µs
[10:27:23.057889]          +326µs               ├─ systrap memory file created: 326µs
[10:27:23.058714]          +825µs ▏             ├─ systrap stub initialized: 825µs
[10:27:23.063232]        +4 517µs █             ├─ systrap source process created: 4 517µs
[10:27:23.063244]           +12µs               ├─ waiting for membarrier: 12µs
[10:27:23.063246]            +1µs               ├─ host membarrier probed: 1µs
[10:27:23.063251]            +4µs               ├─ platform created: 4µs
[10:27:23.063390]          +138µs               ├─ memory file created: 138µs
[10:27:23.063438]           +48µs               ├─ VDSO prepared: 48µs
[10:27:23.063473]           +34µs               ├─ timekeeper configured: 34µs
[10:27:23.063675]          +201µs               ├─ network stack created: 201µs
[10:27:23.063854]          +179µs               ├─ kernel initialized: 179µs
[10:27:23.063859]            +4µs               ├─ filesystems registered: 4µs
[10:27:23.063943]           +84µs               ├─ process args created: 84µs
[10:27:23.063947]            +3µs               ├─ compat logs initialized: 3µs
[10:27:23.063948]            +0µs               ├─ mount hints parsed: 0µs
[10:27:23.063949]            +1µs               ├─ host FS mount created: 1µs
[10:27:23.064142]          +192µs               ├─ control server started: 192µs
[10:27:23.064142]            +0µs               ├─ loader created: 0µs
[10:27:23.064152]            +9µs               ├─ metrics initialized: 9µs
[10:27:23.064152]            +0µs               ├─ notifying parent: 0µs
[10:27:23.126745]       +62 592µs ▉▉▉▉▉▉▉▉▉▉▉▉  ├─ network args received: 62 592µs
[10:27:23.126972]          +227µs               ├─ start RPC received: 227µs
[10:27:23.126982]           +10µs               ├─ start signal received: 10µs
[10:27:23.127852]          +869µs ▏             ├─ network configured: 869µs
[10:27:23.128646]          +794µs ▏             ├─ open FDs validated: 794µs
[10:27:23.131274]        +2 628µs ▌             ├─ pre-seccomp callback done: 2 628µs
[10:27:23.132513]        +1 238µs ▎             ├─ seccomp rules computed: 1 238µs
[10:27:23.146898]       +14 384µs ▉▉▊           ├─ seccomp program compiled: 14 384µs
[10:27:23.148596]        +1 698µs ▎             ├─ seccomp program installed: 1 698µs
[10:27:23.148655]           +58µs               ├─ seccomp filters installed: 58µs
[10:27:23.148655]            +0µs               ├─ forked: 0µs                          ╭─ root container
[10:27:23.148876]          +220µs               │ ... 220µs ...                         ├─ FD table created: 220µs
[10:27:23.149125]          +249µs               │ ... 469µs ...                         ├─ cgroups mounted: 249µs
[10:27:23.149798]          +673µs ▏             │ ... 1 143µs ...                       ├─ rootfs mounted: 673µs
[10:27:23.149804]            +5µs               │ ... 1 148µs ...                       ├─ submounts prepared: 5µs
[10:27:23.150009]          +205µs               │ ... 1 353µs ...                       ├─ mounted "/dev": 205µs
[10:27:23.150362]          +352µs               │ ... 1 706µs ...                       ├─ mounted "/sys": 352µs
[10:27:23.150822]          +459µs               │ ... 2 166µs ...                       ├─ mounted "/proc": 459µs
[10:27:23.150886]           +63µs               │ ... 2 230µs ...                       ├─ mounted "/dev/pts": 63µs
[10:27:23.150915]           +29µs               │ ... 2 259µs ...                       ├─ mounted "/dev/shm": 29µs
[10:27:23.151200]          +285µs               │ ... 2 544µs ...                       ├─ mounted "/etc/hosts": 285µs
[10:27:23.151212]           +12µs               │ ... 2 557µs ...                       ├─ mounted "/dev/mqueue": 12µs
[10:27:23.151539]          +326µs               │ ... 2 883µs ...                       ├─ mounted "/etc/hostname": 326µs
[10:27:23.151660]          +121µs               │ ... 3 004µs ...                       ├─ mounted "/sys/fs/cgroup": 121µs
[10:27:23.151846]          +186µs               │ ... 3 190µs ...                       ├─ mounted "/etc/resolv.conf": 186µs
[10:27:23.151974]          +128µs               │ ... 3 319µs ...                       ├─ submounts mounted: 128µs
[10:27:23.151981]            +6µs               │ ... 3 325µs ...                       ├─ device files created: 6µs
[10:27:23.151981]            +0µs               │ ... 3 326µs ...                       ├─ executable path resolved: 0µs
[10:27:23.152057]           +75µs               │ ... 3 401µs ...                       ├─ exec user home resolved: 75µs
[10:27:23.152085]           +28µs               │ ... 3 429µs ...                       ├─ FS context created: 28µs
[10:27:23.153742]        +1 656µs ▎             │ ... 5 086µs ...                       ├─ memory manager created: 1 656µs
[10:27:23.156409]        +2 667µs ▌             │ ... 7 753µs ...                       ├─ executable loaded: 2 667µs
[10:27:23.156419]           +10µs               │ ... 7 763µs ...                       ├─ VDSO mapped: 10µs
[10:27:23.156425]            +5µs               │ ... 7 769µs ...                       ├─ stack allocated: 5µs
[10:27:23.156491]           +65µs               │ ... 7 835µs ...                       ├─ stack contents loaded: 65µs
[10:27:23.156497]            +5µs               │ ... 7 841µs ...                       ├─ task image loaded: 5µs
[10:27:23.156543]           +46µs               │ ... 7 887µs ...                       ├─ init task created: 46µs
[10:27:23.156599]           +56µs               ├─ root container created: 7 943µs      │ ... 56µs ...
[10:27:23.157146]          +547µs               ├─ signal forwarding started: 547µs     │ ... 603µs ...
[10:27:23.157575]          +428µs               ├─ kernel started: 428µs                │ ... 1 031µs ...
[10:27:23.157575]            +0µs               │ ... 0µs ...                           ╰─ END 1 032µs, total 8 919µs
[10:27:23.157576]            +1µs               ╰─ END 1µs, total 165 638µs
---- End of runsc boot timing information ----
```
2026-08-07 10:26:34 -07:00

1181 lines
39 KiB
Go

// 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 seccomp provides generation of basic seccomp filters. Currently,
// only little endian systems are supported.
package seccomp
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/timing"
)
const (
// skipOneInst is the offset to take for skipping one instruction.
skipOneInst = 1
// defaultLabel is the label for the default action.
defaultLabel = label("default_action")
// vsyscallPageIPMask is the bit we expect to see in the instruction
// pointer of a vsyscall call.
vsyscallPageIPMask = 1 << 31
)
// Install generates BPF code based on the set of syscalls provided. It only
// allows syscalls that conform to the specification. Syscalls that violate the
// specification will trigger RET_KILL_PROCESS. If RET_KILL_PROCESS is not
// supported, violations will trigger RET_TRAP instead. RET_KILL_THREAD is not
// used because it only kills the offending thread and often keeps the sentry
// hanging.
//
// denyRules describes forbidden syscalls. rules describes allowed syscalls.
// denyRules is executed before rules.
//
// Be aware that RET_TRAP sends SIGSYS to the process and it may be ignored,
// making it possible for the process to continue running after a violation.
// However, it will leave a SECCOMP audit event trail behind. In any case, the
// syscall is still blocked from executing.
func (p *Program) Install(timer *timing.Timer) error {
// *** DEBUG TIP ***
// If you suspect the Sentry is getting killed due to a seccomp violation,
// look for the `debugFilter` boolean in `//runsc/boot/filter/filter.go`.
totalSize := 0
for _, ruleSet := range p.RuleSets {
totalSize += ruleSet.Rules.Size()
}
log.Infof("Installing seccomp filters for %d syscalls (action=%v)", totalSize, p.Options.DefaultAction)
instrs, _, err := p.Build()
if log.IsLogging(log.Debug) {
programStr, errDecode := bpf.DecodeInstructions(instrs)
if errDecode != nil {
programStr = fmt.Sprintf("Error: %v\n%s", errDecode, programStr)
}
log.Debugf("Seccomp program dump:\n%s", programStr)
}
if err != nil {
return err
}
timer.Reached("seccomp program compiled")
// Perform the actual installation.
if err := SetFilter(instrs); err != nil {
return fmt.Errorf("failed to set filter: %v", err)
}
timer.Reached("seccomp program installed")
log.Infof("Seccomp filters installed.")
return nil
}
// Action is the action to take when a seccomp rule is matched.
// Unlike the ABI variant, it supports the notion of a "default",
// which is convenient to express in RuleSets.
type Action string
// Values for SeccompAction.
const (
// Default is the default action; see saneDefaultAction().
Default Action = ""
// Allow the syscall.
Allow Action = "allow"
// Trap the syscall.
Trap Action = "trap"
// KillThread kills the thread.
KillThread Action = "kill_thread"
// KillProcess kills the process.
KillProcess Action = "kill_process"
// UserNotify notifies userspace.
UserNotify Action = "user_notify"
// ReturnError returns the given error code to the application.
ReturnError Action = "return_error"
// Trace returns the given trace to the application.
Trace Action = "trace"
)
// Code returns an Action that holds the given code.
// Behavior is action-dependent.
func (a Action) Code(code uint16) Action {
if colonIndex := strings.Index(string(a), ":"); colonIndex != -1 {
return Action(fmt.Sprintf("%s:%04x", a[:colonIndex], code))
}
return Action(fmt.Sprintf("%s:%04x", a, code))
}
// String returns the string representation of the seccomp action.
func (a Action) String() string {
if a == Default {
return "[default]"
}
return string(a)
}
// bpf returns the corresponding BPF action for the given seccomp action.
// If the action is `Default`, `defaultAction` is called.
func (a Action) bpf(defaultAction func() (linux.BPFAction, error)) (linux.BPFAction, error) {
switch a {
case Default:
da, err := defaultAction()
if err != nil {
return 0, fmt.Errorf("failed to determine default seccomp action: %w", err)
}
return da, nil
case Allow:
return linux.SECCOMP_RET_ALLOW, nil
case UserNotify:
return linux.SECCOMP_RET_USER_NOTIF, nil
case Trap:
return linux.SECCOMP_RET_TRAP, nil
case Trace:
return linux.SECCOMP_RET_TRACE, nil
case ReturnError:
return linux.SECCOMP_RET_ERRNO, nil
case KillThread:
return linux.SECCOMP_RET_KILL_THREAD, nil
case KillProcess:
return linux.SECCOMP_RET_KILL_PROCESS, nil
default:
colonIndex := strings.Index(string(a), ":")
if colonIndex == -1 {
panic(fmt.Sprintf("unknown seccomp action: %v", a))
}
ins := Action(a[:colonIndex])
code, err := strconv.ParseUint(string(a[colonIndex+1:]), 16, 16)
if err != nil {
panic(fmt.Sprintf("failed to parse seccomp action: %v", err))
}
act, err := ins.bpf(defaultAction)
if err != nil {
return 0, err
}
return act.WithReturnCode(uint16(code)), nil
}
}
var (
// killProcessAvailableOnce is used to ensure that isKillProcessAvailable is
// only called once.
killProcessAvailableOnce sync.Once
// killProcessAvailable is true if SECCOMP_RET_KILL_PROCESS is available.
killProcessAvailable bool
// killProcessAvailableErr is the error obtained when trying to determine if
// SECCOMP_RET_KILL_PROCESS is available.
killProcessAvailableErr error
)
// saneDefaultAction returns a sane default for a failure to match
// a seccomp-bpf filter. Either kill the process, or trap.
func saneDefaultAction() (linux.BPFAction, error) {
killProcessAvailableOnce.Do(func() {
killProcessAvailable, killProcessAvailableErr = isKillProcessAvailable()
})
if killProcessAvailableErr != nil {
return 0, killProcessAvailableErr
}
if killProcessAvailable {
return linux.SECCOMP_RET_KILL_PROCESS, nil
}
return linux.SECCOMP_RET_TRAP, nil
}
// RuleSet is a set of rules and associated action.
type RuleSet struct {
Rules SyscallRules
Action Action
// Vsyscall indicates that a check is made for a function being called
// from kernel mappings. This is where the vsyscall page is located
// (and typically) emulated, so this RuleSet will not match any
// functions not dispatched from the vsyscall page.
Vsyscall bool
}
// SyscallName gives names to system calls. It is used purely for debugging purposes.
//
// An alternate namer can be provided to the package at initialization time.
var SyscallName = func(sysno uintptr) string {
return fmt.Sprintf("syscall_%d", sysno)
}
// syscallProgram builds a BPF program for applying syscall rules.
// It is a stateful struct that is updated as the program is built.
type syscallProgram struct {
// program is the underlying BPF program being built.
program *bpf.ProgramBuilder
}
// Stmt adds a statement to the program.
func (s *syscallProgram) Stmt(code uint16, k uint32) {
s.program.AddStmt(code, k)
}
// label is a custom label type which is returned by `labelSet`.
type label string
// JumpTo adds a jump instruction to the program, jumping to the given label.
func (s *syscallProgram) JumpTo(label label) {
s.program.AddDirectJumpLabel(string(label))
}
// If checks a condition and jumps to a label if the condition is true.
// If the condition is false, the program continues executing (no jumping).
func (s *syscallProgram) If(code uint16, k uint32, jt label) {
s.program.AddJump(code, k, 0, skipOneInst)
s.JumpTo(jt)
}
// IfNot checks a condition and jumps to a label if the condition is false.
// If the condition is true, the program continues executing (no jumping).
func (s *syscallProgram) IfNot(code uint16, k uint32, jf label) {
s.program.AddJump(code, k, skipOneInst, 0)
s.JumpTo(jf)
}
// Ret adds a return instruction to the program.
func (s *syscallProgram) Ret(action linux.BPFAction) {
s.Stmt(bpf.Ret|bpf.K, uint32(action))
}
// Label adds a label to the program.
// It panics if this label has already been added to the program.
func (s *syscallProgram) Label(label label) {
if err := s.program.AddLabel(string(label)); err != nil {
panic(fmt.Sprintf("cannot add label %q to program: %v", label, err))
}
}
// Record starts recording the instructions added to the program from now on.
// It returns a syscallFragment which can be used to perform assertions on the
// possible set of outcomes of the set of instruction that has been added
// since `Record` was called.
func (s *syscallProgram) Record() syscallProgramFragment {
return syscallProgramFragment{s.program.Record()}
}
// syscallProgramFragment represents a fragment of the syscall program.
type syscallProgramFragment struct {
getFragment func() bpf.ProgramFragment
}
// MustHaveJumpedTo asserts that the fragment must jump to one of the
// given labels.
// The fragment may not jump to any other label, nor return, nor fall through.
func (f syscallProgramFragment) MustHaveJumpedTo(labels ...label) {
f.MustHaveJumpedToOrReturned(labels, nil)
}
// MustHaveJumpedToOrReturned asserts that the fragment must jump to one of
// the given labels, or have returned one of the given return values.
// The fragment may not jump to any other label, nor fall through,
// nor return a non-deterministic value.
func (f syscallProgramFragment) MustHaveJumpedToOrReturned(possibleLabels []label, possibleReturnValues map[linux.BPFAction]struct{}) {
fragment := f.getFragment()
outcomes := fragment.Outcomes()
if outcomes.MayFallThrough {
panic(fmt.Sprintf("fragment %v may fall through", fragment))
}
if len(possibleReturnValues) == 0 && outcomes.MayReturn() {
panic(fmt.Sprintf("fragment %v may return", fragment))
}
if outcomes.MayReturnRegisterA {
panic(fmt.Sprintf("fragment %v may return register A", fragment))
}
if outcomes.MayJumpToKnownOffsetBeyondFragment {
panic(fmt.Sprintf("fragment %v may jump to an offset beyond the fragment", fragment))
}
for jumpLabel := range outcomes.MayJumpToUnresolvedLabels {
found := false
for _, wantLabel := range possibleLabels {
if jumpLabel == string(wantLabel) {
found = true
break
}
}
if !found {
panic(fmt.Sprintf("fragment %v may jump to a label %q which is not one of %v", fragment, jumpLabel, possibleLabels))
}
}
for returnValue := range outcomes.MayReturnImmediate {
if _, found := possibleReturnValues[returnValue]; !found {
panic(fmt.Sprintf("fragment %v may return a value %q which is not one of %v", fragment, returnValue, possibleReturnValues))
}
}
}
// labelSet keeps track of labels that individual rules may jump to if they
// either match or mismatch.
// It can generate unique label names, and can be used recursively within
// rules.
type labelSet struct {
// prefix is a label prefix used when generating label names.
prefix string
// labelCounter is used to generate unique label names.
labelCounter int
// ruleMatched is the label that a rule should jump to if it matches.
ruleMatched label
// ruleMismatched is the label that a rule should jump to if it doesn't
// match.
ruleMismatched label
}
// NewLabel returns a new unique label.
func (l *labelSet) NewLabel() label {
newLabel := label(fmt.Sprintf("%s#%d", l.prefix, l.labelCounter))
l.labelCounter++
return newLabel
}
// Matched returns the label to jump to if the rule matches.
func (l *labelSet) Matched() label {
return l.ruleMatched
}
// Mismatched returns the label to jump to if the rule does not match.
func (l *labelSet) Mismatched() label {
return l.ruleMismatched
}
// Push creates a new labelSet meant to be used in a recursive context of the
// rule currently being rendered.
// Labels generated by this new labelSet will have `labelSuffix` appended to
// this labelSet's current prefix, and will have its matched/mismatched labels
// point to the given labels.
func (l *labelSet) Push(labelSuffix string, newRuleMatch, newRuleMismatch label) *labelSet {
newPrefix := labelSuffix
if l.prefix != "" {
newPrefix = fmt.Sprintf("%s_%s", l.prefix, labelSuffix)
}
return &labelSet{
prefix: newPrefix,
ruleMatched: newRuleMatch,
ruleMismatched: newRuleMismatch,
}
}
// matchedValue keeps track of BPF instructions needed to load a 64-bit value
// being matched against. Since BPF can only do operations on 32-bit
// instructions, value-matching code needs to selectively load one or the
// other half of the 64-bit value.
type matchedValue struct {
program *syscallProgram
dataOffsetHigh uint32
dataOffsetLow uint32
}
// LoadHigh32Bits loads the high 32-bit of the 64-bit value into register A.
func (m matchedValue) LoadHigh32Bits() {
m.program.Stmt(bpf.Ld|bpf.Abs|bpf.W, m.dataOffsetHigh)
}
// LoadLow32Bits loads the low 32-bit of the 64-bit value into register A.
func (m matchedValue) LoadLow32Bits() {
m.program.Stmt(bpf.Ld|bpf.Abs|bpf.W, m.dataOffsetLow)
}
// Program represents a complete seccomp program.
type Program struct {
// RuleSets is the set of rules to be used for the seccomp program.
// The first matching RuleSet is used.
RuleSets []RuleSet
// Options is the set of options to be used for the seccomp program.
Options ProgramOptions
}
// ProgramOptions configure a seccomp program.
type ProgramOptions struct {
// DefaultAction is the action returned when none of the rules match.
DefaultAction Action
// BadArchAction is the action returned when the architecture of the
// syscall structure input doesn't match the one the program expects.
BadArchAction Action
// SkipOptimizations disables optimizations for the seccomp program
// and its BPF bytecode.
SkipOptimizations bool
// HotSyscalls is the set of syscall numbers that are the hottest,
// where "hotness" refers to frequency (regardless of the amount of
// computation that the kernel will do handling them, and regardless of
// the complexity of the syscall rule for this).
// It should only contain very hot syscalls (i.e. any syscall that is
// called >10% of the time out of all syscalls made).
// It is ordered from most frequent to least frequent.
HotSyscalls []uintptr
}
// 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
// RuleOptimizeDuration is the amount of time it took to run SyscallRule
// optimizations.
RuleOptimizeDuration time.Duration
// BPFOptimizeDuration is the amount of time it took to run BPF bytecode
// optimizations.
BPFOptimizeDuration time.Duration
}
// Build builds a BPF program from the given map of actions to matching
// SyscallRules. The single generated program covers all provided RuleSets.
func (p *Program) Build() ([]bpf.Instruction, BuildStats, error) {
start := time.Now()
// Make a copy of the syscall rules and maybe optimize them.
ors, ruleOptimizeDuration, err := orderRuleSets(p.RuleSets, p.Options)
if err != nil {
return nil, BuildStats{}, err
}
defaultActionFn := func() (linux.BPFAction, error) {
return p.Options.DefaultAction.bpf(saneDefaultAction)
}
defaultAction, err := defaultActionFn()
if err != nil {
return nil, BuildStats{}, err
}
possibleActions := make(map[linux.BPFAction]struct{})
for _, ruleSet := range p.RuleSets {
action, err := ruleSet.Action.bpf(defaultActionFn)
if err != nil {
return nil, BuildStats{}, err
}
possibleActions[action] = struct{}{}
}
program := &syscallProgram{
program: bpf.NewProgramBuilder(),
}
// Be paranoid and check that syscall is done in the expected architecture.
//
// A = seccomp_data.arch
// if (A != AUDIT_ARCH) goto badArchLabel.
badArchLabel := label("badarch")
program.Stmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetArch)
program.IfNot(bpf.Jmp|bpf.Jeq|bpf.K, LINUX_AUDIT_ARCH, badArchLabel)
orsFrag := program.Record()
if err := ors.render(program); err != nil {
return nil, BuildStats{}, err
}
orsFrag.MustHaveJumpedToOrReturned([]label{defaultLabel}, possibleActions)
// Default label if none of the rules matched:
program.Label(defaultLabel)
program.Ret(defaultAction)
// Label if the architecture didn't match:
program.Label(badArchLabel)
badArchAction, err := p.Options.BadArchAction.bpf(defaultActionFn)
if err != nil {
return nil, BuildStats{}, err
}
program.Ret(badArchAction)
insns, err := program.program.Instructions()
if err != nil {
return nil, BuildStats{}, err
}
beforeOpt := len(insns)
buildDuration := time.Since(start) - ruleOptimizeDuration
var bpfOptimizeDuration time.Duration
afterOpt := beforeOpt
if !p.Options.SkipOptimizations {
insns = bpf.Optimize(insns)
bpfOptimizeDuration = time.Since(start) - buildDuration - ruleOptimizeDuration
afterOpt = len(insns)
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,
RuleOptimizeDuration: ruleOptimizeDuration,
BPFOptimizeDuration: bpfOptimizeDuration,
}, nil
}
// singleSyscallRuleSet represents what to do for a single syscall.
// It is used inside `orderedRules`.
type singleSyscallRuleSet struct {
sysno uintptr
rules []syscallRuleAction
vsyscall bool
}
// Render renders the ruleset for this syscall.
func (ssrs singleSyscallRuleSet) Render(program *syscallProgram, ls *labelSet, noMatch label) {
frag := program.Record()
if ssrs.vsyscall {
// Emit a vsyscall check.
// This rule ensures that the top bit is set in the
// instruction pointer, which is where the vsyscall page
// will be mapped.
program.Stmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetIPHigh)
program.IfNot(bpf.Jmp|bpf.Jset|bpf.K, vsyscallPageIPMask, noMatch)
}
var nextRule label
actions := make(map[linux.BPFAction]struct{})
for i, ra := range ssrs.rules {
actions[ra.action] = struct{}{}
// Render the rule.
nextRule = ls.NewLabel()
ruleLabels := ls.Push(fmt.Sprintf("sysno%d_rule%d", ssrs.sysno, i), ls.NewLabel(), nextRule)
ruleFrag := program.Record()
ra.rule.Render(program, ruleLabels)
program.Label(ruleLabels.Matched())
program.Ret(ra.action)
ruleFrag.MustHaveJumpedToOrReturned(
[]label{nextRule},
map[linux.BPFAction]struct{}{
ra.action: {},
})
program.Label(nextRule)
}
program.JumpTo(noMatch)
frag.MustHaveJumpedToOrReturned([]label{noMatch}, actions)
}
// String returns a human-friendly representation of the
// `singleSyscallRuleSet`.
func (ssrs singleSyscallRuleSet) String() string {
var sb strings.Builder
if ssrs.vsyscall {
sb.WriteString("Vsyscall ")
} else {
sb.WriteString("Syscall ")
}
fmt.Fprintf(&sb, "%3d: ", ssrs.sysno)
switch len(ssrs.rules) {
case 0:
sb.WriteString("(no rules)")
case 1:
sb.WriteString(ssrs.rules[0].String())
default:
sb.WriteRune('{')
for i, r := range ssrs.rules {
if i != 0 {
sb.WriteString("; ")
}
sb.WriteString(r.String())
}
sb.WriteRune('}')
}
return sb.String()
}
// syscallRuleAction groups a `SyscallRule` and an action that should be
// returned if the rule matches.
type syscallRuleAction struct {
rule SyscallRule
action linux.BPFAction
}
// String returns a human-friendly representation of the `syscallRuleAction`.
func (sra syscallRuleAction) String() string {
if _, isMatchAll := sra.rule.(MatchAll); isMatchAll {
return sra.action.String()
}
return fmt.Sprintf("(%v) => %v", sra.rule.String(), sra.action)
}
// orderedRules contains an ordering of syscall rules used to render a
// program. It is derived from a list of `RuleSet`s and `ProgramOptions`.
// Its fields represent the order in which rulesets are rendered.
// There are three categorization criteria:
// - "Hot" vs "cold": hot syscalls go first and are checked linearly, cold
// syscalls go later.
// - "Trivial" vs "non-trivial": A "trivial" syscall rule means one that
// does not require checking any argument or RIP data. This basically
// means a syscall mapped to `MatchAll{}`.
// If a syscall shows up in multiple RuleSets where any of them is
// non-trivial, the whole syscall is considered non-trivial.
// - "vsyscall" vs "non-vsyscall": A syscall that needs vsyscall checking
// checks that the function is dispatched from the vsyscall page by
// checking RIP. This inherently makes it non-trivial. All trivial
// rules are non-vsyscall, but not all non-vsyscall rules are trivial.
type orderedRuleSets struct {
// hotNonTrivial is the set of hot syscalls that are non-trivial
// and may or may not require vsyscall checking.
// They come first and are checked linearly using `hotNonTrivialOrder`.
hotNonTrivial map[uintptr]singleSyscallRuleSet
// hotNonTrivial is the set of hot syscalls that are non-trivial
// and may or may not require vsyscall checking.
// They come first and are checked linearly using `hotNonTrivialOrder`.
hotNonTrivialOrder []uintptr
// coldNonTrivial is the set of non-hot syscalls that are non-trivial.
// They may or may not require vsyscall checking.
// They come second.
coldNonTrivial map[uintptr]singleSyscallRuleSet
// trivial is the set of syscalls that are trivial. They may or may not be
// hot, but they may not require vsyscall checking (otherwise they would
// be non-trivial).
// They come last. This is because the host kernel will cache the results
// of these system calls, and will never execute them on the hot path.
trivial map[uintptr]singleSyscallRuleSet
}
// orderRuleSets converts a set of `RuleSet`s into an `orderedRuleSets`.
// It orders the rulesets, along with the time to optimize the
// rules (if any).
func orderRuleSets(rules []RuleSet, options ProgramOptions) (orderedRuleSets, time.Duration, error) {
// Do a pass to determine if vsyscall is consistent across syscall numbers.
vsyscallBySysno := make(map[uintptr]bool)
for _, rs := range rules {
for sysno := range rs.Rules.rules {
if prevVsyscall, ok := vsyscallBySysno[sysno]; ok {
if prevVsyscall != rs.Vsyscall {
return orderedRuleSets{}, 0, fmt.Errorf("syscall %d has conflicting vsyscall checking rules", sysno)
}
} else {
vsyscallBySysno[sysno] = rs.Vsyscall
}
}
}
// Build a single map of per-syscall syscallRuleActions.
// We will split this map up later.
allSyscallRuleActions := make(map[uintptr][]syscallRuleAction)
defaultActionFn := func() (linux.BPFAction, error) {
return options.DefaultAction.bpf(saneDefaultAction)
}
for _, rs := range rules {
action, err := rs.Action.bpf(defaultActionFn)
if err != nil {
return orderedRuleSets{}, 0, err
}
for sysno, rule := range rs.Rules.rules {
existing, found := allSyscallRuleActions[sysno]
if !found {
allSyscallRuleActions[sysno] = []syscallRuleAction{{
rule: rule,
action: action,
}}
continue
}
if existing[len(existing)-1].action == action {
// If the last action for this syscall is the same, union the rules.
existing[len(existing)-1].rule = Or{existing[len(existing)-1].rule, rule}
} else {
// Otherwise, add it as a new ruleset.
existing = append(existing, syscallRuleAction{
rule: rule,
action: action,
})
}
allSyscallRuleActions[sysno] = existing
}
}
// Optimize all rules.
var optimizeDuration time.Duration
if !options.SkipOptimizations {
optimizeStart := time.Now()
for _, ruleActions := range allSyscallRuleActions {
for i, ra := range ruleActions {
ra.rule = optimizeSyscallRule(ra.rule)
ruleActions[i] = ra
}
}
optimizeDuration = time.Since(optimizeStart)
}
// Do a pass that checks which syscall numbers are trivial.
isTrivial := make(map[uintptr]bool)
for sysno, ruleActions := range allSyscallRuleActions {
for _, ra := range ruleActions {
_, isMatchAll := ra.rule.(MatchAll)
isVsyscall := vsyscallBySysno[sysno]
trivial := isMatchAll && !isVsyscall
if prevTrivial, ok := isTrivial[sysno]; ok {
isTrivial[sysno] = prevTrivial && trivial
} else {
isTrivial[sysno] = trivial
}
}
}
// Compute the set of non-trivial hot syscalls and their order.
hotNonTrivialSyscallsIndex := make(map[uintptr]int, len(options.HotSyscalls))
for i, sysno := range options.HotSyscalls {
if _, hasRule := allSyscallRuleActions[sysno]; !hasRule {
continue
}
if isTrivial[sysno] {
continue
}
if _, ok := hotNonTrivialSyscallsIndex[sysno]; ok {
continue
}
hotNonTrivialSyscallsIndex[sysno] = i
}
hotNonTrivialOrder := make([]uintptr, 0, len(hotNonTrivialSyscallsIndex))
for sysno := range hotNonTrivialSyscallsIndex {
hotNonTrivialOrder = append(hotNonTrivialOrder, sysno)
}
sort.Slice(hotNonTrivialOrder, func(i, j int) bool {
return hotNonTrivialSyscallsIndex[hotNonTrivialOrder[i]] < hotNonTrivialSyscallsIndex[hotNonTrivialOrder[j]]
})
// Now split up the map and build the `orderedRuleSets`.
ors := orderedRuleSets{
hotNonTrivial: make(map[uintptr]singleSyscallRuleSet),
hotNonTrivialOrder: hotNonTrivialOrder,
coldNonTrivial: make(map[uintptr]singleSyscallRuleSet),
trivial: make(map[uintptr]singleSyscallRuleSet),
}
for sysno, ruleActions := range allSyscallRuleActions {
_, hot := hotNonTrivialSyscallsIndex[sysno]
trivial := isTrivial[sysno]
var subMap map[uintptr]singleSyscallRuleSet
switch {
case trivial:
subMap = ors.trivial
case hot:
subMap = ors.hotNonTrivial
default:
subMap = ors.coldNonTrivial
}
subMap[sysno] = singleSyscallRuleSet{
sysno: sysno,
vsyscall: vsyscallBySysno[sysno],
rules: ruleActions,
}
}
// Log our findings.
if log.IsLogging(log.Debug) {
ors.log(log.Debugf)
}
return ors, optimizeDuration, nil
}
// log logs the set of seccomp rules to the given logger.
func (ors orderedRuleSets) log(logFn func(string, ...any)) {
logFn("Ordered seccomp rules:")
for _, sm := range []struct {
name string
m map[uintptr]singleSyscallRuleSet
}{
{"Hot non-trivial", ors.hotNonTrivial},
{"Cold non-trivial", ors.coldNonTrivial},
{"Trivial", ors.trivial},
} {
if len(sm.m) == 0 {
logFn(" %s syscalls: None.", sm.name)
continue
}
logFn(" %s syscalls:", sm.name)
orderedSysnos := make([]int, 0, len(sm.m))
for sysno := range sm.m {
orderedSysnos = append(orderedSysnos, int(sysno))
}
sort.Ints(orderedSysnos)
for _, sysno := range orderedSysnos {
logFn(" - %s", sm.m[uintptr(sysno)].String())
}
}
logFn("End of ordered seccomp rules.")
}
// render renders all rulesets in the given program.
func (ors orderedRuleSets) render(program *syscallProgram) error {
ls := &labelSet{prefix: string("ors")}
// totalFrag wraps the entire output of the `render` function.
totalFrag := program.Record()
// Load syscall number into register A.
program.Stmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetNR)
// Keep track of which syscalls we've already looked for.
sysnosChecked := make(map[uintptr]struct{})
// First render hot syscalls linearly.
if len(ors.hotNonTrivialOrder) > 0 {
notHotLabel := ls.NewLabel()
// hotFrag wraps the "hot syscalls" part of the program.
// It must either return one of `hotActions`, or jump to `defaultLabel` if
// the syscall number matched but the vsyscall match failed, or
// `notHotLabel` if none of the hot syscall numbers matched.
hotFrag := program.Record()
possibleActions := ors.renderLinear(program, ls, sysnosChecked, ors.hotNonTrivial, ors.hotNonTrivialOrder, notHotLabel)
hotFrag.MustHaveJumpedToOrReturned([]label{notHotLabel, defaultLabel}, possibleActions)
program.Label(notHotLabel)
}
// Now render the cold non-trivial rules as a binary search tree:
if len(ors.coldNonTrivial) > 0 {
frag := program.Record()
noSycallNumberMatch := ls.NewLabel()
possibleActions, err := ors.renderBST(program, ls, sysnosChecked, ors.coldNonTrivial, noSycallNumberMatch)
if err != nil {
return err
}
frag.MustHaveJumpedToOrReturned([]label{noSycallNumberMatch, defaultLabel}, possibleActions)
program.Label(noSycallNumberMatch)
}
// Finally render the trivial rules as a binary search tree:
if len(ors.trivial) > 0 {
frag := program.Record()
noSycallNumberMatch := ls.NewLabel()
possibleActions, err := ors.renderBST(program, ls, sysnosChecked, ors.trivial, noSycallNumberMatch)
if err != nil {
return err
}
frag.MustHaveJumpedToOrReturned([]label{noSycallNumberMatch, defaultLabel}, possibleActions)
program.Label(noSycallNumberMatch)
}
program.JumpTo(defaultLabel)
// Reached the end of the program.
// Independently verify the set of all possible actions.
allPossibleActions := make(map[linux.BPFAction]struct{})
for _, mapping := range []map[uintptr]singleSyscallRuleSet{
ors.hotNonTrivial,
ors.coldNonTrivial,
ors.trivial,
} {
for _, ssrs := range mapping {
for _, ra := range ssrs.rules {
allPossibleActions[ra.action] = struct{}{}
}
}
}
totalFrag.MustHaveJumpedToOrReturned([]label{defaultLabel}, allPossibleActions)
return nil
}
// renderLinear renders linear search code that searches for syscall matches
// in the given order. It assumes the syscall number is loaded into register
// A. Rulesets for all syscall numbers in `order` must exist in `syscallMap`.
// It returns the list of possible actions the generated code may return.
// `alreadyChecked` will be updated with the syscalls that have been checked.
func (ors orderedRuleSets) renderLinear(program *syscallProgram, ls *labelSet, alreadyChecked map[uintptr]struct{}, syscallMap map[uintptr]singleSyscallRuleSet, order []uintptr, noSycallNumberMatch label) map[linux.BPFAction]struct{} {
allActions := make(map[linux.BPFAction]struct{})
for _, sysno := range order {
ssrs, found := syscallMap[sysno]
if !found {
panic(fmt.Sprintf("syscall %d found in linear order but not map", sysno))
}
nextSyscall := ls.NewLabel()
// sysnoFrag wraps the "statements about this syscall number" part of
// the program. It must either return one of the actions specified in
// that syscall number's rules (`sysnoActions`), or jump to
// `nextSyscall`.
sysnoFrag := program.Record()
sysnoActions := make(map[linux.BPFAction]struct{})
for _, ra := range ssrs.rules {
sysnoActions[ra.action] = struct{}{}
allActions[ra.action] = struct{}{}
}
program.IfNot(bpf.Jmp|bpf.Jeq|bpf.K, uint32(ssrs.sysno), nextSyscall)
ssrs.Render(program, ls, defaultLabel)
sysnoFrag.MustHaveJumpedToOrReturned([]label{nextSyscall, defaultLabel}, sysnoActions)
program.Label(nextSyscall)
}
program.JumpTo(noSycallNumberMatch)
for _, sysno := range order {
alreadyChecked[sysno] = struct{}{}
}
return allActions
}
// renderBST renders a binary search tree that searches the given map of
// syscalls. It assumes the syscall number is loaded into register A.
// It returns the list of possible actions the generated code may return.
// `alreadyChecked` will be updated with the syscalls that the BST has
// searched.
func (ors orderedRuleSets) renderBST(program *syscallProgram, ls *labelSet, alreadyChecked map[uintptr]struct{}, syscallMap map[uintptr]singleSyscallRuleSet, noSycallNumberMatch label) (map[linux.BPFAction]struct{}, error) {
possibleActions := make(map[linux.BPFAction]struct{})
orderedSysnos := make([]uintptr, 0, len(syscallMap))
for sysno, ruleActions := range syscallMap {
orderedSysnos = append(orderedSysnos, sysno)
for _, ra := range ruleActions.rules {
possibleActions[ra.action] = struct{}{}
}
}
sort.Slice(orderedSysnos, func(i, j int) bool {
return orderedSysnos[i] < orderedSysnos[j]
})
frag := program.Record()
root := createBST(orderedSysnos)
root.root = true
knownRng := knownRange{
lowerBoundExclusive: -1,
// sysno fits in 32 bits, so this is definitely out of bounds:
upperBoundExclusive: 1 << 32,
previouslyChecked: alreadyChecked,
}
if err := root.traverse(renderBSTTraversal, knownRng, syscallMap, program, noSycallNumberMatch); err != nil {
return nil, err
}
if err := root.traverse(renderBSTRules, knownRng, syscallMap, program, noSycallNumberMatch); err != nil {
return nil, err
}
frag.MustHaveJumpedToOrReturned([]label{noSycallNumberMatch, defaultLabel}, possibleActions)
for sysno := range syscallMap {
alreadyChecked[sysno] = struct{}{}
}
return possibleActions, nil
}
// createBST converts sorted syscall slice into a balanced BST.
// Panics if syscalls is empty.
func createBST(syscalls []uintptr) *node {
i := len(syscalls) / 2
parent := node{value: syscalls[i]}
if i > 0 {
parent.left = createBST(syscalls[:i])
}
if i+1 < len(syscalls) {
parent.right = createBST(syscalls[i+1:])
}
return &parent
}
// renderBSTTraversal renders the traversal bytecode for a binary search tree.
// The outline of the code is as follows, given a BST with:
//
// 22
// / \
// 9 24
// / / \
// 8 23 50
//
// index_22: // SYS_PIPE(22), root
// (A < 22) ? goto index_9 : continue
// (A > 22) ? goto index_24 : continue
// goto checkArgs_22
//
// index_9: // SYS_MMAP(9), single child
// (A < 9) ? goto index_8 : continue
// (A == 9) ? continue : goto defaultLabel
// goto checkArgs_9
//
// index_8: // SYS_LSEEK(8), leaf
// (A == 8) ? continue : goto defaultLabel
// goto checkArgs_8
//
// index_24: // SYS_SCHED_YIELD(24)
// (A < 24) ? goto index_23 : continue
// (A > 22) ? goto index_50 : continue
// goto checkArgs_24
//
// index_23: // SYS_SELECT(23), leaf with parent nodes adjacent in value
// # Notice that we do not check for equality at all here, since we've
// # already established that the syscall number is 23 from the
// # two parent nodes that we've already traversed.
// # This is tracked in the `rng knownRange` argument during traversal.
// goto rules_23
//
// index_50: // SYS_LISTEN(50), leaf
// (A == 50) ? continue : goto defaultLabel
// goto checkArgs_50
//
// All of the "checkArgs_XYZ" labels are not defined in this function; they
// are created using the `renderBSTRules` function, which is expected to be
// called after this one on the entire BST.
func renderBSTTraversal(n *node, rng knownRange, syscallMap map[uintptr]singleSyscallRuleSet, program *syscallProgram, searchFailed label) error {
// Root node is never referenced by label, skip it.
if !n.root {
program.Label(n.label())
}
sysno := n.value
nodeFrag := program.Record()
checkArgsLabel := label(fmt.Sprintf("checkArgs_%d", sysno))
if n.left != nil {
program.IfNot(bpf.Jmp|bpf.Jge|bpf.K, uint32(sysno), n.left.label())
rng.lowerBoundExclusive = int(sysno - 1)
}
if n.right != nil {
program.If(bpf.Jmp|bpf.Jgt|bpf.K, uint32(sysno), n.right.label())
rng.upperBoundExclusive = int(sysno + 1)
}
if rng.lowerBoundExclusive != int(sysno-1) || rng.upperBoundExclusive != int(sysno+1) {
// If the previous BST nodes we traversed haven't fully established
// that the current node's syscall value is exactly `sysno`, we still
// need to verify it.
program.IfNot(bpf.Jmp|bpf.Jeq|bpf.K, uint32(sysno), searchFailed)
}
program.JumpTo(checkArgsLabel)
nodeFrag.MustHaveJumpedTo(n.left.label(), n.right.label(), checkArgsLabel, searchFailed)
return nil
}
// renderBSTRules renders the `checkArgs_XYZ` labels that `renderBSTTraversal`
// jumps to as part of the BST traversal code. It contains all the
// argument-specific syscall rules for each syscall number.
func renderBSTRules(n *node, rng knownRange, syscallMap map[uintptr]singleSyscallRuleSet, program *syscallProgram, searchFailed label) error {
sysno := n.value
checkArgsLabel := label(fmt.Sprintf("checkArgs_%d", sysno))
program.Label(checkArgsLabel)
ruleSetsFrag := program.Record()
possibleActions := make(map[linux.BPFAction]struct{})
for _, ra := range syscallMap[sysno].rules {
possibleActions[ra.action] = struct{}{}
}
nodeLabelSet := &labelSet{prefix: string(n.label())}
syscallMap[sysno].Render(program, nodeLabelSet, defaultLabel)
ruleSetsFrag.MustHaveJumpedToOrReturned(
[]label{
defaultLabel, // Either we jumped to the default label (if the rules didn't match)...
},
possibleActions, // ... or we returned one of the actions of the rulesets.
)
return nil
}
// node represents a tree node.
type node struct {
value uintptr
left *node
right *node
root bool
}
// label returns the label corresponding to this node.
//
// If n is nil, then the defaultLabel is returned.
func (n *node) label() label {
if n == nil {
return defaultLabel
}
return label(fmt.Sprintf("node_%d", n.value))
}
// knownRange represents the known set of node numbers that we've
// already checked. This is used as part of BST traversal.
type knownRange struct {
lowerBoundExclusive int
upperBoundExclusive int
// alreadyChecked is a set of node values that were already checked
// earlier in the program (prior to the BST being built).
// It is *not* updated during BST traversal.
previouslyChecked map[uintptr]struct{}
}
// withLowerBoundExclusive returns an updated `knownRange` with the given
// new exclusive lower bound. The actual exclusive lower bound of the
// returned `knownRange` may be higher, in case `previouslyChecked` covers
// more numbers.
func (kr knownRange) withLowerBoundExclusive(newLowerBoundExclusive int) knownRange {
nkr := knownRange{
lowerBoundExclusive: newLowerBoundExclusive,
upperBoundExclusive: kr.upperBoundExclusive,
previouslyChecked: kr.previouslyChecked,
}
for ; nkr.lowerBoundExclusive < nkr.upperBoundExclusive; nkr.lowerBoundExclusive++ {
if _, ok := nkr.previouslyChecked[uintptr(nkr.lowerBoundExclusive+1)]; !ok {
break
}
}
return nkr
}
// withUpperBoundExclusive returns an updated `knownRange` with the given
// new exclusive upper bound. The actual exclusive upper bound of the
// returned `knownRange` may be lower, in case `previouslyChecked` covers
// more numbers.
func (kr knownRange) withUpperBoundExclusive(newUpperBoundExclusive int) knownRange {
nkr := knownRange{
lowerBoundExclusive: kr.lowerBoundExclusive,
upperBoundExclusive: newUpperBoundExclusive,
previouslyChecked: kr.previouslyChecked,
}
for ; nkr.lowerBoundExclusive < nkr.upperBoundExclusive; nkr.upperBoundExclusive-- {
if _, ok := nkr.previouslyChecked[uintptr(nkr.upperBoundExclusive-1)]; !ok {
break
}
}
return nkr
}
// traverseFunc is called as the BST is traversed.
type traverseFunc func(*node, knownRange, map[uintptr]singleSyscallRuleSet, *syscallProgram, label) error
func (n *node) traverse(fn traverseFunc, kr knownRange, syscallMap map[uintptr]singleSyscallRuleSet, program *syscallProgram, searchFailed label) error {
if n == nil {
return nil
}
if err := fn(n, kr, syscallMap, program, searchFailed); err != nil {
return err
}
if err := n.left.traverse(
fn,
kr.withUpperBoundExclusive(int(n.value)),
syscallMap,
program,
searchFailed,
); err != nil {
return err
}
return n.right.traverse(
fn,
kr.withLowerBoundExclusive(int(n.value)),
syscallMap,
program,
searchFailed,
)
}
// DataAsBPFInput converts a linux.SeccompData to a bpf.Input.
// It uses `buf` as scratch buffer; this buffer must be wide enough
// to accommodate a mashaled version of `d`.
func DataAsBPFInput(d *linux.SeccompData, buf []byte) bpf.Input {
if len(buf) < d.SizeBytes() {
panic(fmt.Sprintf("buffer must be at least %d bytes long", d.SizeBytes()))
}
d.MarshalUnsafe(buf)
return buf[:d.SizeBytes()]
}