bpf: Replace most uses of linux.BPFInstruction with bpf.Instruction.

`bpf.Instruction` is the same type as `linux.BPFInstruction`, except that it
uses the BPF instruction-to-string decoder to give a nice human-readable
stringification.

PiperOrigin-RevId: 570499020
This commit is contained in:
Etienne Perot
2023-10-03 14:34:53 -07:00
committed by gVisor bot
parent 9d5198a863
commit 5f5692dd20
20 changed files with 157 additions and 132 deletions
+1
View File
@@ -14,6 +14,7 @@ go_library(
"interpreter.go",
"program_builder.go",
],
imports = ["gvisor.dev/gvisor/pkg/abi/linux"],
visibility = ["//visibility:public"],
deps = ["//pkg/abi/linux"],
)
+28 -7
View File
@@ -17,7 +17,11 @@
// https://www.freebsd.org/cgi/man.cgi?bpf(4)
package bpf
import "gvisor.dev/gvisor/pkg/abi/linux"
import (
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
)
const (
// MaxInstructions is the maximum number of instructions in a BPF program,
@@ -110,17 +114,34 @@ const (
retUnusedBitsMask = 0xe0 // returns only use instruction class and source operand
)
// Stmt returns a linux.BPFInstruction representing a BPF non-jump instruction.
func Stmt(code uint16, k uint32) linux.BPFInstruction {
return linux.BPFInstruction{
// Instruction is a type alias for linux.BPFInstruction.
// It adds a human-readable stringification function.
//
// +marshal slice:InstructionSlice
// +stateify savable
// +stateify identtype
type Instruction linux.BPFInstruction
// String returns a human-readable version of the instruction.
func (ins *Instruction) String() string {
s, err := Decode(*ins)
if err != nil {
return fmt.Sprintf("[invalid %v: %v]", (*linux.BPFInstruction)(ins), err)
}
return s
}
// Stmt returns an Instruction representing a BPF non-jump instruction.
func Stmt(code uint16, k uint32) Instruction {
return Instruction{
OpCode: code,
K: k,
}
}
// Jump returns a linux.BPFInstruction representing a BPF jump instruction.
func Jump(code uint16, k uint32, jt, jf uint8) linux.BPFInstruction {
return linux.BPFInstruction{
// Jump returns an Instruction representing a BPF jump instruction.
func Jump(code uint16, k uint32, jt, jf uint8) Instruction {
return Instruction{
OpCode: code,
JumpIfTrue: jt,
JumpIfFalse: jf,
+21 -21
View File
@@ -27,7 +27,7 @@ func DecodeProgram(p Program) (string, error) {
}
// DecodeInstructions translates an array of BPF instructions into text format.
func DecodeInstructions(instns []linux.BPFInstruction) (string, error) {
func DecodeInstructions(instns []Instruction) (string, error) {
var ret bytes.Buffer
for line, s := range instns {
ret.WriteString(fmt.Sprintf("%v: ", line))
@@ -40,13 +40,13 @@ func DecodeInstructions(instns []linux.BPFInstruction) (string, error) {
}
// Decode translates a single BPF instruction into text format.
func Decode(inst linux.BPFInstruction) (string, error) {
func Decode(ins Instruction) (string, error) {
var ret bytes.Buffer
err := decode(inst, -1, &ret)
err := decode(ins, -1, &ret)
return ret.String(), err
}
func decode(inst linux.BPFInstruction, line int, w *bytes.Buffer) error {
func decode(inst Instruction, line int, w *bytes.Buffer) error {
var err error
switch inst.OpCode & instructionClassMask {
case Ld:
@@ -66,13 +66,13 @@ func decode(inst linux.BPFInstruction, line int, w *bytes.Buffer) error {
case Misc:
err = decodeMisc(inst, w)
default:
return fmt.Errorf("invalid BPF instruction: %v", inst)
return fmt.Errorf("invalid BPF instruction: %v", linux.BPFInstruction(inst))
}
return err
}
// A <- P[k:4]
func decodeLd(inst linux.BPFInstruction, w *bytes.Buffer) error {
func decodeLd(inst Instruction, w *bytes.Buffer) error {
w.WriteString("A <- ")
switch inst.OpCode & loadModeMask {
@@ -95,12 +95,12 @@ func decodeLd(inst linux.BPFInstruction, w *bytes.Buffer) error {
case Len:
w.WriteString("len")
default:
return fmt.Errorf("invalid BPF LD instruction: %v", inst)
return fmt.Errorf("invalid BPF LD instruction: %v", linux.BPFInstruction(inst))
}
return nil
}
func decodeLdSize(inst linux.BPFInstruction, w *bytes.Buffer) error {
func decodeLdSize(inst Instruction, w *bytes.Buffer) error {
switch inst.OpCode & loadSizeMask {
case W:
w.WriteString("4")
@@ -109,13 +109,13 @@ func decodeLdSize(inst linux.BPFInstruction, w *bytes.Buffer) error {
case B:
w.WriteString("1")
default:
return fmt.Errorf("invalid BPF LD size: %v", inst)
return fmt.Errorf("invalid BPF LD size: %v", linux.BPFInstruction(inst))
}
return nil
}
// X <- P[k:4]
func decodeLdx(inst linux.BPFInstruction, w *bytes.Buffer) error {
func decodeLdx(inst Instruction, w *bytes.Buffer) error {
w.WriteString("X <- ")
switch inst.OpCode & loadModeMask {
@@ -128,13 +128,13 @@ func decodeLdx(inst linux.BPFInstruction, w *bytes.Buffer) error {
case Msh:
w.WriteString(fmt.Sprintf("4*(P[%v:1]&0xf)", inst.K))
default:
return fmt.Errorf("invalid BPF LDX instruction: %v", inst)
return fmt.Errorf("invalid BPF LDX instruction: %v", linux.BPFInstruction(inst))
}
return nil
}
// A <- A + k
func decodeAlu(inst linux.BPFInstruction, w *bytes.Buffer) error {
func decodeAlu(inst Instruction, w *bytes.Buffer) error {
code := inst.OpCode & aluMask
if code == Neg {
w.WriteString("A <- -A")
@@ -164,25 +164,25 @@ func decodeAlu(inst linux.BPFInstruction, w *bytes.Buffer) error {
case Xor:
w.WriteString("^ ")
default:
return fmt.Errorf("invalid BPF ALU instruction: %v", inst)
return fmt.Errorf("invalid BPF ALU instruction: %v", linux.BPFInstruction(inst))
}
return decodeSource(inst, w)
}
func decodeSource(inst linux.BPFInstruction, w *bytes.Buffer) error {
func decodeSource(inst Instruction, w *bytes.Buffer) error {
switch inst.OpCode & srcAluJmpMask {
case K:
w.WriteString(fmt.Sprintf("%v", inst.K))
case X:
w.WriteString("X")
default:
return fmt.Errorf("invalid BPF ALU/JMP source instruction: %v", inst)
return fmt.Errorf("invalid BPF ALU/JMP source instruction: %v", linux.BPFInstruction(inst))
}
return nil
}
// pc += (A > k) ? jt : jf
func decodeJmp(inst linux.BPFInstruction, line int, w *bytes.Buffer) error {
func decodeJmp(inst Instruction, line int, w *bytes.Buffer) error {
code := inst.OpCode & jmpMask
w.WriteString("pc += ")
@@ -200,7 +200,7 @@ func decodeJmp(inst linux.BPFInstruction, line int, w *bytes.Buffer) error {
case Jset:
w.WriteString("& ")
default:
return fmt.Errorf("invalid BPF ALU instruction: %v", inst)
return fmt.Errorf("invalid BPF ALU instruction: %v", linux.BPFInstruction(inst))
}
if err := decodeSource(inst, w); err != nil {
return err
@@ -221,7 +221,7 @@ func printJmpTarget(target uint32, line int) string {
}
// ret k
func decodeRet(inst linux.BPFInstruction, w *bytes.Buffer) error {
func decodeRet(inst Instruction, w *bytes.Buffer) error {
w.WriteString("ret ")
code := inst.OpCode & srcRetMask
@@ -231,12 +231,12 @@ func decodeRet(inst linux.BPFInstruction, w *bytes.Buffer) error {
case A:
w.WriteString("A")
default:
return fmt.Errorf("invalid BPF RET source instruction: %v", inst)
return fmt.Errorf("invalid BPF RET source instruction: %v", linux.BPFInstruction(inst))
}
return nil
}
func decodeMisc(inst linux.BPFInstruction, w *bytes.Buffer) error {
func decodeMisc(inst Instruction, w *bytes.Buffer) error {
code := inst.OpCode & miscMask
switch code {
case Tax:
@@ -244,7 +244,7 @@ func decodeMisc(inst linux.BPFInstruction, w *bytes.Buffer) error {
case Txa:
w.WriteString("A <- X")
default:
return fmt.Errorf("invalid BPF ALU/JMP source instruction: %v", inst)
return fmt.Errorf("invalid BPF ALU/JMP source instruction: %v", linux.BPFInstruction(inst))
}
return nil
}
+4 -6
View File
@@ -16,13 +16,11 @@ package bpf
import (
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
)
func TestDecode(t *testing.T) {
for _, test := range []struct {
filter linux.BPFInstruction
filter Instruction
expected string
fail bool
}{
@@ -96,12 +94,12 @@ func TestDecode(t *testing.T) {
func TestDecodeInstructions(t *testing.T) {
for _, test := range []struct {
name string
program []linux.BPFInstruction
program []Instruction
expected string
fail bool
}{
{name: "basic with jump indexes",
program: []linux.BPFInstruction{
program: []Instruction{
Stmt(Ld+Abs+W, 10),
Stmt(Ldx+Mem, 10),
Stmt(St, 10),
@@ -123,7 +121,7 @@ func TestDecodeInstructions(t *testing.T) {
"8: X <- A\n",
},
{name: "invalid instruction",
program: []linux.BPFInstruction{Stmt(Ld+Abs+W, 10), Stmt(Ld+Len+Mem, 0)},
program: []Instruction{Stmt(Ld+Abs+W, 10), Stmt(Ld+Len+Mem, 0)},
fail: true},
} {
got, err := DecodeInstructions(test.program)
+3 -5
View File
@@ -16,8 +16,6 @@ package bpf
import (
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
)
// Possible values for ProgramError.Code.
@@ -91,7 +89,7 @@ func (e Error) Error() string {
//
// +stateify savable
type Program struct {
instructions []linux.BPFInstruction
instructions []Instruction
}
// Length returns the number of instructions in the program.
@@ -101,7 +99,7 @@ func (p Program) Length() int {
// Compile performs validation on a sequence of BPF instructions before
// wrapping them in a Program.
func Compile(insns []linux.BPFInstruction) (Program, error) {
func Compile(insns []Instruction) (Program, error) {
if len(insns) == 0 || len(insns) > MaxInstructions {
return Program{}, Error{InvalidInstructionCount, len(insns)}
}
@@ -255,7 +253,7 @@ type machine struct {
M [ScratchMemRegisters]uint32
}
func conditionalJumpOffset(insn linux.BPFInstruction, cond bool) int {
func conditionalJumpOffset(insn Instruction, cond bool) int {
if cond {
return int(insn.JumpIfTrue)
}
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -17,8 +17,6 @@ package bpf
import (
"fmt"
"math"
"gvisor.dev/gvisor/pkg/abi/linux"
)
const (
@@ -38,7 +36,7 @@ type ProgramBuilder struct {
unusableLabels map[string]bool
// Array of BPF instructions that makes up the program.
instructions []linux.BPFInstruction
instructions []Instruction
}
// NewProgramBuilder creates a new ProgramBuilder instance.
@@ -134,7 +132,7 @@ func (b *ProgramBuilder) AddLabel(name string) error {
// resolved. Return error in case label resolution failed due to an invalid program.
//
// N.B. Partial results will be returned in the error case, which is useful for debugging.
func (b *ProgramBuilder) Instructions() ([]linux.BPFInstruction, error) {
func (b *ProgramBuilder) Instructions() ([]Instruction, error) {
if err := b.resolveLabels(); err != nil {
return b.instructions, err
}
+4 -6
View File
@@ -17,11 +17,9 @@ package bpf
import (
"fmt"
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
)
func validate(p *ProgramBuilder, expected []linux.BPFInstruction) error {
func validate(p *ProgramBuilder, expected []Instruction) error {
instructions, err := p.Instructions()
if err != nil {
return fmt.Errorf("Instructions() failed: %v", err)
@@ -45,7 +43,7 @@ func TestProgramBuilderSimple(t *testing.T) {
p.AddStmt(Ld+Abs+W, 10)
p.AddJump(Jmp+Ja, 10, 0, 0)
expected := []linux.BPFInstruction{
expected := []Instruction{
Stmt(Ld+Abs+W, 10),
Jump(Jmp+Ja, 10, 0, 0),
}
@@ -84,7 +82,7 @@ func TestProgramBuilderLabels(t *testing.T) {
}
p.AddStmt(Ld+Abs+W, 5)
expected := []linux.BPFInstruction{
expected := []Instruction{
Jump(Jmp+Jeq+K, 11, 2, 0),
Jump(Jmp+Jeq+K, 12, 0, 3),
Jump(Jmp+Jeq+K, 13, 1, 3),
@@ -131,7 +129,7 @@ func TestProgramBuilderUnusedLabel(t *testing.T) {
p.AddStmt(Ld+Abs+W, 10)
p.AddJump(Jmp+Ja, 10, 0, 0)
expected := []linux.BPFInstruction{
expected := []Instruction{
Stmt(Ld+Abs+W, 10),
Jump(Jmp+Ja, 10, 0, 0),
}
+1 -1
View File
@@ -130,7 +130,7 @@ var SyscallName = func(sysno uintptr) string {
// 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) ([]linux.BPFInstruction, error) {
func BuildProgram(rules []RuleSet, defaultAction, badArchAction linux.BPFAction) ([]bpf.Instruction, error) {
program := bpf.NewProgramBuilder()
// Be paranoid and check that syscall is done in the expected architecture.
+3 -2
View File
@@ -21,10 +21,11 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
)
// SetFilter installs the given BPF program.
func SetFilter(instrs []linux.BPFInstruction) error {
func SetFilter(instrs []bpf.Instruction) error {
// PR_SET_NO_NEW_PRIVS is required in order to enable seccomp. See
// seccomp(2) for details.
//
@@ -73,7 +74,7 @@ func SetFilter(instrs []linux.BPFInstruction) error {
//
//go:norace
//go:nosplit
func SetFilterInChild(instrs []linux.BPFInstruction) unix.Errno {
func SetFilterInChild(instrs []bpf.Instruction) unix.Errno {
if _, _, errno := unix.RawSyscall6(unix.SYS_PRCTL, linux.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0, 0); errno != 0 {
return errno
}
+1
View File
@@ -27,6 +27,7 @@ go_library(
visibility = ["//:sandbox"],
deps = [
"//pkg/abi/linux",
"//pkg/bpf",
"//pkg/context",
"//pkg/cpuid",
"//pkg/hostarch",
@@ -22,6 +22,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
"gvisor.dev/gvisor/pkg/hosttid"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/seccomp"
@@ -130,7 +131,7 @@ func attachedThread(flags uintptr, defaultAction linux.BPFAction) (*thread, erro
// not race instrument it.
//
//go:norace
func forkStub(flags uintptr, instrs []linux.BPFInstruction) (*thread, error) {
func forkStub(flags uintptr, instrs []bpf.Instruction) (*thread, error) {
// Declare all variables up front in order to ensure that there's no
// need for allocations between beforeFork & afterFork.
var (
+1
View File
@@ -74,6 +74,7 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/atomicbitops",
"//pkg/bpf",
"//pkg/context",
"//pkg/cpuid",
"//pkg/hostarch",
+3 -3
View File
@@ -21,6 +21,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/safecopy"
@@ -55,16 +56,15 @@ func unsafeSlice(addr uintptr, length int) (slice []byte) {
//
//go:nosplit
func prepareSeccompRules(stubSysmsgStart, stubSysmsgRules, stubSysmsgRulesLen uintptr) {
instrs := sysmsgThreadRules(stubSysmsgStart)
progLen := len(instrs) * int(unsafe.Sizeof(linux.BPFInstruction{}))
progLen := len(instrs) * int(unsafe.Sizeof(bpf.Instruction{}))
progPtr := stubSysmsgRules + unsafe.Sizeof(linux.SockFprog{})
if progLen+int(unsafe.Sizeof(linux.SockFprog{})) > int(stubSysmsgRulesLen) {
panic("not enough space for sysmsg seccomp rules")
}
var targetSlice []linux.BPFInstruction
var targetSlice []bpf.Instruction
sh := (*reflect.SliceHeader)(unsafe.Pointer(&targetSlice))
sh.Data = progPtr
sh.Cap = len(instrs)
@@ -22,6 +22,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/arch"
)
@@ -139,7 +140,7 @@ func attachedThread(flags uintptr, defaultAction linux.BPFAction) (*thread, erro
// not race instrument it.
//
//go:norace
func forkStub(flags uintptr, instrs []linux.BPFInstruction) (*thread, error) {
func forkStub(flags uintptr, instrs []bpf.Instruction) (*thread, error) {
// Declare all variables up front in order to ensure that there's no
// need for allocations between beforeFork & afterFork.
var (
+2 -1
View File
@@ -19,6 +19,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/arch"
@@ -96,7 +97,7 @@ func (p *sysmsgThread) Debugf(format string, v ...any) {
p.thread.Debugf(format+postfix, v...)
}
func sysmsgThreadRules(stubStart uintptr) []linux.BPFInstruction {
func sysmsgThreadRules(stubStart uintptr) []bpf.Instruction {
rules := []seccomp.RuleSet{}
rules = appendSysThreadArchSeccompRules(rules)
rules = append(rules, []seccomp.RuleSet{
+5 -1
View File
@@ -63,7 +63,11 @@ func seccomp(t *kernel.Task, mode, flags uint64, addr hostarch.Addr) error {
if _, err := linux.CopyBPFInstructionSliceIn(t, hostarch.Addr(fprog.Filter), filter); err != nil {
return err
}
compiledFilter, err := bpf.Compile(filter)
bpfFilter := make([]bpf.Instruction, len(filter))
for i, ins := range filter {
bpfFilter[i] = bpf.Instruction(ins)
}
compiledFilter, err := bpf.Compile(bpfFilter)
if err != nil {
t.Debugf("Invalid seccomp-bpf filter: %v", err)
return linuxerr.EINVAL
+2 -2
View File
@@ -34,11 +34,11 @@ import (
)
// install installs the given program on the runner.
func install(program []linux.BPFInstruction) error {
func install(program []bpf.Instruction) error {
// Rewrite the program so that all return actions are either ALLOW or
// RET_ERRNO. This allows us to benchmark the program without worrying
// that we'll crash if we call a bad system call.
rewritten := make([]linux.BPFInstruction, len(program))
rewritten := make([]bpf.Instruction, len(program))
copy(rewritten, program)
for pc, ins := range rewritten {
switch ins.OpCode {
+1
View File
@@ -16,6 +16,7 @@ go_library(
],
deps = [
"//pkg/abi/linux",
"//pkg/bpf",
"@org_golang_x_sys//unix:go_default_library",
],
)
+2 -2
View File
@@ -20,7 +20,7 @@ import (
"fmt"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
)
// Bench represents a benchmark to run.
@@ -30,7 +30,7 @@ type Bench struct {
// Profile represents the syscall pattern profile being benchmarked.
Profile Profile `json:"profile"`
// Program is the seccomp-bpf program to run the benchmark with.
Program []linux.BPFInstruction `json:"program"`
Program []bpf.Instruction `json:"program"`
// 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