summaryrefslogtreecommitdiffhomepage
path: root/pkg/bpf
diff options
context:
space:
mode:
authorgVisor bot <gvisor-bot@google.com>2019-06-02 06:44:55 +0000
committergVisor bot <gvisor-bot@google.com>2019-06-02 06:44:55 +0000
commitceb0d792f328d1fc0692197d8856a43c3936a571 (patch)
tree83155f302eff44a78bcc30a3a08f4efe59a79379 /pkg/bpf
parentdeb7ecf1e46862d54f4b102f2d163cfbcfc37f3b (diff)
parent216da0b733dbed9aad9b2ab92ac75bcb906fd7ee (diff)
Merge 216da0b7 (automated)
Diffstat (limited to 'pkg/bpf')
-rw-r--r--pkg/bpf/bpf.go129
-rwxr-xr-xpkg/bpf/bpf_state_autogen.go22
-rw-r--r--pkg/bpf/decoder.go245
-rw-r--r--pkg/bpf/input_bytes.go58
-rw-r--r--pkg/bpf/interpreter.go412
-rw-r--r--pkg/bpf/program_builder.go191
6 files changed, 1057 insertions, 0 deletions
diff --git a/pkg/bpf/bpf.go b/pkg/bpf/bpf.go
new file mode 100644
index 000000000..eb546f48f
--- /dev/null
+++ b/pkg/bpf/bpf.go
@@ -0,0 +1,129 @@
+// 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 bpf provides tools for working with Berkeley Packet Filter (BPF)
+// programs. More information on BPF can be found at
+// https://www.freebsd.org/cgi/man.cgi?bpf(4)
+package bpf
+
+import "gvisor.googlesource.com/gvisor/pkg/abi/linux"
+
+const (
+ // MaxInstructions is the maximum number of instructions in a BPF program,
+ // and is equal to Linux's BPF_MAXINSNS.
+ MaxInstructions = 4096
+
+ // ScratchMemRegisters is the number of M registers in a BPF virtual machine,
+ // and is equal to Linux's BPF_MEMWORDS.
+ ScratchMemRegisters = 16
+)
+
+// Parts of a linux.BPFInstruction.OpCode. Compare to the Linux kernel's
+// include/uapi/linux/filter.h.
+//
+// In the comments below:
+//
+// - A, X, and M[] are BPF virtual machine registers.
+//
+// - K refers to the instruction field linux.BPFInstruction.K.
+//
+// - Bits are counted from the LSB position.
+const (
+ // Instruction class, stored in bits 0-2.
+ Ld = 0x00 // load into A
+ Ldx = 0x01 // load into X
+ St = 0x02 // store from A
+ Stx = 0x03 // store from X
+ Alu = 0x04 // arithmetic
+ Jmp = 0x05 // jump
+ Ret = 0x06 // return
+ Misc = 0x07
+ instructionClassMask = 0x07
+
+ // Size of a load, stored in bits 3-4.
+ W = 0x00 // 32 bits
+ H = 0x08 // 16 bits
+ B = 0x10 // 8 bits
+ loadSizeMask = 0x18
+
+ // Source operand for a load, stored in bits 5-7.
+ // Address mode numbers in the comments come from Linux's
+ // Documentation/networking/filter.txt.
+ Imm = 0x00 // immediate value K (mode 4)
+ Abs = 0x20 // data in input at byte offset K (mode 1)
+ Ind = 0x40 // data in input at byte offset X+K (mode 2)
+ Mem = 0x60 // M[K] (mode 3)
+ Len = 0x80 // length of the input in bytes ("BPF extension len")
+ Msh = 0xa0 // 4 * lower nibble of input at byte offset K (mode 5)
+ loadModeMask = 0xe0
+
+ // Source operands for arithmetic, jump, and return instructions.
+ // Arithmetic and jump instructions can use K or X as source operands.
+ // Return instructions can use K or A as source operands.
+ K = 0x00 // still mode 4
+ X = 0x08 // mode 0
+ A = 0x10 // mode 9
+ srcAluJmpMask = 0x08
+ srcRetMask = 0x18
+
+ // Arithmetic instructions, stored in bits 4-7.
+ Add = 0x00
+ Sub = 0x10 // A - src
+ Mul = 0x20
+ Div = 0x30 // A / src
+ Or = 0x40
+ And = 0x50
+ Lsh = 0x60 // A << src
+ Rsh = 0x70 // A >> src
+ Neg = 0x80 // -A (src ignored)
+ Mod = 0x90 // A % src
+ Xor = 0xa0
+ aluMask = 0xf0
+
+ // Jump instructions, stored in bits 4-7.
+ Ja = 0x00 // unconditional (uses K for jump offset)
+ Jeq = 0x10 // if A == src
+ Jgt = 0x20 // if A > src
+ Jge = 0x30 // if A >= src
+ Jset = 0x40 // if (A & src) != 0
+ jmpMask = 0xf0
+
+ // Miscellaneous instructions, stored in bits 3-7.
+ Tax = 0x00 // A = X
+ Txa = 0x80 // X = A
+ miscMask = 0xf8
+
+ // Masks for bits that should be zero.
+ unusedBitsMask = 0xff00 // all valid instructions use only bits 0-7
+ storeUnusedBitsMask = 0xf8 // stores only use instruction class
+ 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{
+ 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{
+ OpCode: code,
+ JumpIfTrue: jt,
+ JumpIfFalse: jf,
+ K: k,
+ }
+}
diff --git a/pkg/bpf/bpf_state_autogen.go b/pkg/bpf/bpf_state_autogen.go
new file mode 100755
index 000000000..05effb7b6
--- /dev/null
+++ b/pkg/bpf/bpf_state_autogen.go
@@ -0,0 +1,22 @@
+// automatically generated by stateify.
+
+package bpf
+
+import (
+ "gvisor.googlesource.com/gvisor/pkg/state"
+)
+
+func (x *Program) beforeSave() {}
+func (x *Program) save(m state.Map) {
+ x.beforeSave()
+ m.Save("instructions", &x.instructions)
+}
+
+func (x *Program) afterLoad() {}
+func (x *Program) load(m state.Map) {
+ m.Load("instructions", &x.instructions)
+}
+
+func init() {
+ state.Register("bpf.Program", (*Program)(nil), state.Fns{Save: (*Program).save, Load: (*Program).load})
+}
diff --git a/pkg/bpf/decoder.go b/pkg/bpf/decoder.go
new file mode 100644
index 000000000..45c192215
--- /dev/null
+++ b/pkg/bpf/decoder.go
@@ -0,0 +1,245 @@
+// 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 bpf
+
+import (
+ "bytes"
+ "fmt"
+
+ "gvisor.googlesource.com/gvisor/pkg/abi/linux"
+)
+
+// DecodeProgram translates an array of BPF instructions into text format.
+func DecodeProgram(program []linux.BPFInstruction) (string, error) {
+ var ret bytes.Buffer
+ for line, s := range program {
+ ret.WriteString(fmt.Sprintf("%v: ", line))
+ if err := decode(s, line, &ret); err != nil {
+ return "", err
+ }
+ ret.WriteString("\n")
+ }
+ return ret.String(), nil
+}
+
+// Decode translates BPF instruction into text format.
+func Decode(inst linux.BPFInstruction) (string, error) {
+ var ret bytes.Buffer
+ err := decode(inst, -1, &ret)
+ return ret.String(), err
+}
+
+func decode(inst linux.BPFInstruction, line int, w *bytes.Buffer) error {
+ var err error
+ switch inst.OpCode & instructionClassMask {
+ case Ld:
+ err = decodeLd(inst, w)
+ case Ldx:
+ err = decodeLdx(inst, w)
+ case St:
+ w.WriteString(fmt.Sprintf("M[%v] <- A", inst.K))
+ case Stx:
+ w.WriteString(fmt.Sprintf("M[%v] <- X", inst.K))
+ case Alu:
+ err = decodeAlu(inst, w)
+ case Jmp:
+ err = decodeJmp(inst, line, w)
+ case Ret:
+ err = decodeRet(inst, w)
+ case Misc:
+ err = decodeMisc(inst, w)
+ default:
+ return fmt.Errorf("invalid BPF instruction: %v", inst)
+ }
+ return err
+}
+
+// A <- P[k:4]
+func decodeLd(inst linux.BPFInstruction, w *bytes.Buffer) error {
+ w.WriteString("A <- ")
+
+ switch inst.OpCode & loadModeMask {
+ case Imm:
+ w.WriteString(fmt.Sprintf("%v", inst.K))
+ case Abs:
+ w.WriteString(fmt.Sprintf("P[%v:", inst.K))
+ if err := decodeLdSize(inst, w); err != nil {
+ return err
+ }
+ w.WriteString("]")
+ case Ind:
+ w.WriteString(fmt.Sprintf("P[X+%v:", inst.K))
+ if err := decodeLdSize(inst, w); err != nil {
+ return err
+ }
+ w.WriteString("]")
+ case Mem:
+ w.WriteString(fmt.Sprintf("M[%v]", inst.K))
+ case Len:
+ w.WriteString("len")
+ default:
+ return fmt.Errorf("invalid BPF LD instruction: %v", inst)
+ }
+ return nil
+}
+
+func decodeLdSize(inst linux.BPFInstruction, w *bytes.Buffer) error {
+ switch inst.OpCode & loadSizeMask {
+ case W:
+ w.WriteString("4")
+ case H:
+ w.WriteString("2")
+ case B:
+ w.WriteString("1")
+ default:
+ return fmt.Errorf("Invalid BPF LD size: %v", inst)
+ }
+ return nil
+}
+
+// X <- P[k:4]
+func decodeLdx(inst linux.BPFInstruction, w *bytes.Buffer) error {
+ w.WriteString("X <- ")
+
+ switch inst.OpCode & loadModeMask {
+ case Imm:
+ w.WriteString(fmt.Sprintf("%v", inst.K))
+ case Mem:
+ w.WriteString(fmt.Sprintf("M[%v]", inst.K))
+ case Len:
+ w.WriteString("len")
+ case Msh:
+ w.WriteString(fmt.Sprintf("4*(P[%v:1]&0xf)", inst.K))
+ default:
+ return fmt.Errorf("invalid BPF LDX instruction: %v", inst)
+ }
+ return nil
+}
+
+// A <- A + k
+func decodeAlu(inst linux.BPFInstruction, w *bytes.Buffer) error {
+ code := inst.OpCode & aluMask
+ if code == Neg {
+ w.WriteString("A <- -A")
+ return nil
+ }
+
+ w.WriteString("A <- A ")
+ switch code {
+ case Add:
+ w.WriteString("+ ")
+ case Sub:
+ w.WriteString("- ")
+ case Mul:
+ w.WriteString("* ")
+ case Div:
+ w.WriteString("/ ")
+ case Or:
+ w.WriteString("| ")
+ case And:
+ w.WriteString("& ")
+ case Lsh:
+ w.WriteString("<< ")
+ case Rsh:
+ w.WriteString(">> ")
+ case Mod:
+ w.WriteString("% ")
+ case Xor:
+ w.WriteString("^ ")
+ default:
+ return fmt.Errorf("invalid BPF ALU instruction: %v", inst)
+ }
+ return decodeSource(inst, w)
+}
+
+func decodeSource(inst linux.BPFInstruction, 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 nil
+}
+
+// pc += (A > k) ? jt : jf
+func decodeJmp(inst linux.BPFInstruction, line int, w *bytes.Buffer) error {
+ code := inst.OpCode & jmpMask
+
+ w.WriteString("pc += ")
+ if code == Ja {
+ w.WriteString(printJmpTarget(inst.K, line))
+ } else {
+ w.WriteString("(A ")
+ switch code {
+ case Jeq:
+ w.WriteString("== ")
+ case Jgt:
+ w.WriteString("> ")
+ case Jge:
+ w.WriteString(">= ")
+ case Jset:
+ w.WriteString("& ")
+ default:
+ return fmt.Errorf("invalid BPF ALU instruction: %v", inst)
+ }
+ if err := decodeSource(inst, w); err != nil {
+ return err
+ }
+ w.WriteString(
+ fmt.Sprintf(") ? %s : %s",
+ printJmpTarget(uint32(inst.JumpIfTrue), line),
+ printJmpTarget(uint32(inst.JumpIfFalse), line)))
+ }
+ return nil
+}
+
+func printJmpTarget(target uint32, line int) string {
+ if line == -1 {
+ return fmt.Sprintf("%v", target)
+ }
+ return fmt.Sprintf("%v [%v]", target, int(target)+line+1)
+}
+
+// ret k
+func decodeRet(inst linux.BPFInstruction, w *bytes.Buffer) error {
+ w.WriteString("ret ")
+
+ code := inst.OpCode & srcRetMask
+ switch code {
+ case K:
+ w.WriteString(fmt.Sprintf("%v", inst.K))
+ case A:
+ w.WriteString("A")
+ default:
+ return fmt.Errorf("invalid BPF RET source instruction: %v", inst)
+ }
+ return nil
+}
+
+func decodeMisc(inst linux.BPFInstruction, w *bytes.Buffer) error {
+ code := inst.OpCode & miscMask
+ switch code {
+ case Tax:
+ w.WriteString("X <- A")
+ case Txa:
+ w.WriteString("A <- X")
+ default:
+ return fmt.Errorf("invalid BPF ALU/JMP source instruction: %v", inst)
+ }
+ return nil
+}
diff --git a/pkg/bpf/input_bytes.go b/pkg/bpf/input_bytes.go
new file mode 100644
index 000000000..86b216cfc
--- /dev/null
+++ b/pkg/bpf/input_bytes.go
@@ -0,0 +1,58 @@
+// 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 bpf
+
+import (
+ "encoding/binary"
+)
+
+// InputBytes implements the Input interface by providing access to a byte
+// slice. Unaligned loads are supported.
+type InputBytes struct {
+ // Data is the data accessed through the Input interface.
+ Data []byte
+
+ // Order is the byte order the data is accessed with.
+ Order binary.ByteOrder
+}
+
+// Load32 implements Input.Load32.
+func (i InputBytes) Load32(off uint32) (uint32, bool) {
+ if uint64(off)+4 > uint64(len(i.Data)) {
+ return 0, false
+ }
+ return i.Order.Uint32(i.Data[int(off):]), true
+}
+
+// Load16 implements Input.Load16.
+func (i InputBytes) Load16(off uint32) (uint16, bool) {
+ if uint64(off)+2 > uint64(len(i.Data)) {
+ return 0, false
+ }
+ return i.Order.Uint16(i.Data[int(off):]), true
+}
+
+// Load8 implements Input.Load8.
+func (i InputBytes) Load8(off uint32) (uint8, bool) {
+ if uint64(off)+1 > uint64(len(i.Data)) {
+ return 0, false
+ }
+ return i.Data[int(off)], true
+}
+
+// Length implements Input.Length.
+func (i InputBytes) Length() uint32 {
+ return uint32(len(i.Data))
+}
diff --git a/pkg/bpf/interpreter.go b/pkg/bpf/interpreter.go
new file mode 100644
index 000000000..86de523a2
--- /dev/null
+++ b/pkg/bpf/interpreter.go
@@ -0,0 +1,412 @@
+// 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 bpf
+
+import (
+ "fmt"
+
+ "gvisor.googlesource.com/gvisor/pkg/abi/linux"
+)
+
+// Possible values for ProgramError.Code.
+const (
+ // DivisionByZero indicates that a program contains, or executed, a
+ // division or modulo by zero.
+ DivisionByZero = iota
+
+ // InvalidEndOfProgram indicates that the last instruction of a program is
+ // not a return.
+ InvalidEndOfProgram
+
+ // InvalidInstructionCount indicates that a program has zero instructions
+ // or more than MaxInstructions instructions.
+ InvalidInstructionCount
+
+ // InvalidJumpTarget indicates that a program contains a jump whose target
+ // is outside of the program's bounds.
+ InvalidJumpTarget
+
+ // InvalidLoad indicates that a program executed an invalid load of input
+ // data.
+ InvalidLoad
+
+ // InvalidOpcode indicates that a program contains an instruction with an
+ // invalid opcode.
+ InvalidOpcode
+
+ // InvalidRegister indicates that a program contains a load from, or store
+ // to, a non-existent M register (index >= ScratchMemRegisters).
+ InvalidRegister
+)
+
+// Error is an error encountered while compiling or executing a BPF program.
+type Error struct {
+ // Code indicates the kind of error that occurred.
+ Code int
+
+ // PC is the program counter (index into the list of instructions) at which
+ // the error occurred.
+ PC int
+}
+
+func (e Error) codeString() string {
+ switch e.Code {
+ case DivisionByZero:
+ return "division by zero"
+ case InvalidEndOfProgram:
+ return "last instruction must be a return"
+ case InvalidInstructionCount:
+ return "invalid number of instructions"
+ case InvalidJumpTarget:
+ return "jump target out of bounds"
+ case InvalidLoad:
+ return "load out of bounds or violates input alignment requirements"
+ case InvalidOpcode:
+ return "invalid instruction opcode"
+ case InvalidRegister:
+ return "invalid M register"
+ default:
+ return "unknown error"
+ }
+}
+
+// Error implements error.Error.
+func (e Error) Error() string {
+ return fmt.Sprintf("at l%d: %s", e.PC, e.codeString())
+}
+
+// Program is a BPF program that has been validated for consistency.
+//
+// +stateify savable
+type Program struct {
+ instructions []linux.BPFInstruction
+}
+
+// Length returns the number of instructions in the program.
+func (p Program) Length() int {
+ return len(p.instructions)
+}
+
+// Compile performs validation on a sequence of BPF instructions before
+// wrapping them in a Program.
+func Compile(insns []linux.BPFInstruction) (Program, error) {
+ if len(insns) == 0 || len(insns) > MaxInstructions {
+ return Program{}, Error{InvalidInstructionCount, len(insns)}
+ }
+
+ // The last instruction must be a return.
+ if last := insns[len(insns)-1]; last.OpCode != (Ret|K) && last.OpCode != (Ret|A) {
+ return Program{}, Error{InvalidEndOfProgram, len(insns) - 1}
+ }
+
+ // Validate each instruction. Note that we skip a validation Linux does:
+ // Linux additionally verifies that every load from an M register is
+ // preceded, in every path, by a store to the same M register, in order to
+ // avoid having to clear M between programs
+ // (net/core/filter.c:check_load_and_stores). We always start with a zeroed
+ // M array.
+ for pc, i := range insns {
+ if i.OpCode&unusedBitsMask != 0 {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ switch i.OpCode & instructionClassMask {
+ case Ld:
+ mode := i.OpCode & loadModeMask
+ switch i.OpCode & loadSizeMask {
+ case W:
+ if mode != Imm && mode != Abs && mode != Ind && mode != Mem && mode != Len {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ if mode == Mem && i.K >= ScratchMemRegisters {
+ return Program{}, Error{InvalidRegister, pc}
+ }
+ case H, B:
+ if mode != Abs && mode != Ind {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ default:
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ case Ldx:
+ mode := i.OpCode & loadModeMask
+ switch i.OpCode & loadSizeMask {
+ case W:
+ if mode != Imm && mode != Mem && mode != Len {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ if mode == Mem && i.K >= ScratchMemRegisters {
+ return Program{}, Error{InvalidRegister, pc}
+ }
+ case B:
+ if mode != Msh {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ default:
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ case St, Stx:
+ if i.OpCode&storeUnusedBitsMask != 0 {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ if i.K >= ScratchMemRegisters {
+ return Program{}, Error{InvalidRegister, pc}
+ }
+ case Alu:
+ switch i.OpCode & aluMask {
+ case Add, Sub, Mul, Or, And, Lsh, Rsh, Xor:
+ break
+ case Div, Mod:
+ if src := i.OpCode & srcAluJmpMask; src == K && i.K == 0 {
+ return Program{}, Error{DivisionByZero, pc}
+ }
+ case Neg:
+ // Negation doesn't take a source operand.
+ if i.OpCode&srcAluJmpMask != 0 {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ default:
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ case Jmp:
+ switch i.OpCode & jmpMask {
+ case Ja:
+ // Unconditional jump doesn't take a source operand.
+ if i.OpCode&srcAluJmpMask != 0 {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ // Do the comparison in 64 bits to avoid the possibility of
+ // overflow from a very large i.K.
+ if uint64(pc)+uint64(i.K)+1 >= uint64(len(insns)) {
+ return Program{}, Error{InvalidJumpTarget, pc}
+ }
+ case Jeq, Jgt, Jge, Jset:
+ // jt and jf are uint16s, so there's no threat of overflow.
+ if pc+int(i.JumpIfTrue)+1 >= len(insns) {
+ return Program{}, Error{InvalidJumpTarget, pc}
+ }
+ if pc+int(i.JumpIfFalse)+1 >= len(insns) {
+ return Program{}, Error{InvalidJumpTarget, pc}
+ }
+ default:
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ case Ret:
+ if i.OpCode&retUnusedBitsMask != 0 {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ if src := i.OpCode & srcRetMask; src != K && src != A {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ case Misc:
+ if misc := i.OpCode & miscMask; misc != Tax && misc != Txa {
+ return Program{}, Error{InvalidOpcode, pc}
+ }
+ }
+ }
+
+ return Program{insns}, nil
+}
+
+// Input represents a source of input data for a BPF program. (BPF
+// documentation sometimes refers to the input data as the "packet" due to its
+// origins as a packet processing DSL.)
+//
+// For all of Input's Load methods:
+//
+// - The second (bool) return value is true if the load succeeded and false
+// otherwise.
+//
+// - Inputs should not assume that the loaded range falls within the input
+// data's length. Inputs should return false if the load falls outside of the
+// input data.
+//
+// - Inputs should not assume that the offset is correctly aligned. Inputs may
+// choose to service or reject loads to unaligned addresses.
+type Input interface {
+ // Load32 reads 32 bits from the input starting at the given byte offset.
+ Load32(off uint32) (uint32, bool)
+
+ // Load16 reads 16 bits from the input starting at the given byte offset.
+ Load16(off uint32) (uint16, bool)
+
+ // Load8 reads 8 bits from the input starting at the given byte offset.
+ Load8(off uint32) (uint8, bool)
+
+ // Length returns the length of the input in bytes.
+ Length() uint32
+}
+
+// machine represents the state of a BPF virtual machine.
+type machine struct {
+ A uint32
+ X uint32
+ M [ScratchMemRegisters]uint32
+}
+
+func conditionalJumpOffset(insn linux.BPFInstruction, cond bool) int {
+ if cond {
+ return int(insn.JumpIfTrue)
+ }
+ return int(insn.JumpIfFalse)
+}
+
+// Exec executes a BPF program over the given input and returns its return
+// value.
+func Exec(p Program, in Input) (uint32, error) {
+ var m machine
+ var pc int
+ for ; pc < len(p.instructions); pc++ {
+ i := p.instructions[pc]
+ switch i.OpCode {
+ case Ld | Imm | W:
+ m.A = i.K
+ case Ld | Abs | W:
+ val, ok := in.Load32(i.K)
+ if !ok {
+ return 0, Error{InvalidLoad, pc}
+ }
+ m.A = val
+ case Ld | Abs | H:
+ val, ok := in.Load16(i.K)
+ if !ok {
+ return 0, Error{InvalidLoad, pc}
+ }
+ m.A = uint32(val)
+ case Ld | Abs | B:
+ val, ok := in.Load8(i.K)
+ if !ok {
+ return 0, Error{InvalidLoad, pc}
+ }
+ m.A = uint32(val)
+ case Ld | Ind | W:
+ val, ok := in.Load32(m.X + i.K)
+ if !ok {
+ return 0, Error{InvalidLoad, pc}
+ }
+ m.A = val
+ case Ld | Ind | H:
+ val, ok := in.Load16(m.X + i.K)
+ if !ok {
+ return 0, Error{InvalidLoad, pc}
+ }
+ m.A = uint32(val)
+ case Ld | Ind | B:
+ val, ok := in.Load8(m.X + i.K)
+ if !ok {
+ return 0, Error{InvalidLoad, pc}
+ }
+ m.A = uint32(val)
+ case Ld | Mem | W:
+ m.A = m.M[int(i.K)]
+ case Ld | Len | W:
+ m.A = in.Length()
+ case Ldx | Imm | W:
+ m.X = i.K
+ case Ldx | Mem | W:
+ m.X = m.M[int(i.K)]
+ case Ldx | Len | W:
+ m.X = in.Length()
+ case Ldx | Msh | B:
+ val, ok := in.Load8(i.K)
+ if !ok {
+ return 0, Error{InvalidLoad, pc}
+ }
+ m.X = 4 * uint32(val&0xf)
+ case St:
+ m.M[int(i.K)] = m.A
+ case Stx:
+ m.M[int(i.K)] = m.X
+ case Alu | Add | K:
+ m.A += i.K
+ case Alu | Add | X:
+ m.A += m.X
+ case Alu | Sub | K:
+ m.A -= i.K
+ case Alu | Sub | X:
+ m.A -= m.X
+ case Alu | Mul | K:
+ m.A *= i.K
+ case Alu | Mul | X:
+ m.A *= m.X
+ case Alu | Div | K:
+ // K != 0 already checked by Compile.
+ m.A /= i.K
+ case Alu | Div | X:
+ if m.X == 0 {
+ return 0, Error{DivisionByZero, pc}
+ }
+ m.A /= m.X
+ case Alu | Or | K:
+ m.A |= i.K
+ case Alu | Or | X:
+ m.A |= m.X
+ case Alu | And | K:
+ m.A &= i.K
+ case Alu | And | X:
+ m.A &= m.X
+ case Alu | Lsh | K:
+ m.A <<= i.K
+ case Alu | Lsh | X:
+ m.A <<= m.X
+ case Alu | Rsh | K:
+ m.A >>= i.K
+ case Alu | Rsh | X:
+ m.A >>= m.X
+ case Alu | Neg:
+ m.A = uint32(-int32(m.A))
+ case Alu | Mod | K:
+ // K != 0 already checked by Compile.
+ m.A %= i.K
+ case Alu | Mod | X:
+ if m.X == 0 {
+ return 0, Error{DivisionByZero, pc}
+ }
+ m.A %= m.X
+ case Alu | Xor | K:
+ m.A ^= i.K
+ case Alu | Xor | X:
+ m.A ^= m.X
+ case Jmp | Ja:
+ pc += int(i.K)
+ case Jmp | Jeq | K:
+ pc += conditionalJumpOffset(i, m.A == i.K)
+ case Jmp | Jeq | X:
+ pc += conditionalJumpOffset(i, m.A == m.X)
+ case Jmp | Jgt | K:
+ pc += conditionalJumpOffset(i, m.A > i.K)
+ case Jmp | Jgt | X:
+ pc += conditionalJumpOffset(i, m.A > m.X)
+ case Jmp | Jge | K:
+ pc += conditionalJumpOffset(i, m.A >= i.K)
+ case Jmp | Jge | X:
+ pc += conditionalJumpOffset(i, m.A >= m.X)
+ case Jmp | Jset | K:
+ pc += conditionalJumpOffset(i, (m.A&i.K) != 0)
+ case Jmp | Jset | X:
+ pc += conditionalJumpOffset(i, (m.A&m.X) != 0)
+ case Ret | K:
+ return i.K, nil
+ case Ret | A:
+ return m.A, nil
+ case Misc | Tax:
+ m.A = m.X
+ case Misc | Txa:
+ m.X = m.A
+ default:
+ return 0, Error{InvalidOpcode, pc}
+ }
+ }
+ return 0, Error{InvalidEndOfProgram, pc}
+}
diff --git a/pkg/bpf/program_builder.go b/pkg/bpf/program_builder.go
new file mode 100644
index 000000000..fc9d27203
--- /dev/null
+++ b/pkg/bpf/program_builder.go
@@ -0,0 +1,191 @@
+// 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 bpf
+
+import (
+ "fmt"
+ "math"
+
+ "gvisor.googlesource.com/gvisor/pkg/abi/linux"
+)
+
+const (
+ labelTarget = math.MaxUint8
+ labelDirectTarget = math.MaxUint32
+)
+
+// ProgramBuilder assists with building a BPF program with jump
+// labels that are resolved to their proper offsets.
+type ProgramBuilder struct {
+ // Maps label names to label objects.
+ labels map[string]*label
+
+ // Array of BPF instructions that makes up the program.
+ instructions []linux.BPFInstruction
+}
+
+// NewProgramBuilder creates a new ProgramBuilder instance.
+func NewProgramBuilder() *ProgramBuilder {
+ return &ProgramBuilder{labels: map[string]*label{}}
+}
+
+// label contains information to resolve a label to an offset.
+type label struct {
+ // List of locations that reference the label in the program.
+ sources []source
+
+ // Program line when the label is located.
+ target int
+}
+
+type jmpType int
+
+const (
+ jDirect jmpType = iota
+ jTrue
+ jFalse
+)
+
+// source contains information about a single reference to a label.
+type source struct {
+ // Program line where the label reference is present.
+ line int
+
+ // True if label reference is in the 'jump if true' part of the jump.
+ // False if label reference is in the 'jump if false' part of the jump.
+ jt jmpType
+}
+
+// AddStmt adds a new statement to the program.
+func (b *ProgramBuilder) AddStmt(code uint16, k uint32) {
+ b.instructions = append(b.instructions, Stmt(code, k))
+}
+
+// AddJump adds a new jump to the program.
+func (b *ProgramBuilder) AddJump(code uint16, k uint32, jt, jf uint8) {
+ b.instructions = append(b.instructions, Jump(code, k, jt, jf))
+}
+
+// AddDirectJumpLabel adds a new jump to the program where is labelled.
+func (b *ProgramBuilder) AddDirectJumpLabel(labelName string) {
+ b.addLabelSource(labelName, jDirect)
+ b.AddJump(Jmp|Ja, labelDirectTarget, 0, 0)
+}
+
+// AddJumpTrueLabel adds a new jump to the program where 'jump if true' is a label.
+func (b *ProgramBuilder) AddJumpTrueLabel(code uint16, k uint32, jtLabel string, jf uint8) {
+ b.addLabelSource(jtLabel, jTrue)
+ b.AddJump(code, k, labelTarget, jf)
+}
+
+// AddJumpFalseLabel adds a new jump to the program where 'jump if false' is a label.
+func (b *ProgramBuilder) AddJumpFalseLabel(code uint16, k uint32, jt uint8, jfLabel string) {
+ b.addLabelSource(jfLabel, jFalse)
+ b.AddJump(code, k, jt, labelTarget)
+}
+
+// AddJumpLabels adds a new jump to the program where both jump targets are labels.
+func (b *ProgramBuilder) AddJumpLabels(code uint16, k uint32, jtLabel, jfLabel string) {
+ b.addLabelSource(jtLabel, jTrue)
+ b.addLabelSource(jfLabel, jFalse)
+ b.AddJump(code, k, labelTarget, labelTarget)
+}
+
+// AddLabel sets the given label name at the current location. The next instruction is executed
+// when the any code jumps to this label. More than one label can be added to the same location.
+func (b *ProgramBuilder) AddLabel(name string) error {
+ l, ok := b.labels[name]
+ if !ok {
+ // This is done to catch jump backwards cases, but it's not strictly wrong
+ // to have unused labels.
+ return fmt.Errorf("Adding a label that hasn't been used is not allowed: %v", name)
+ }
+ if l.target != -1 {
+ return fmt.Errorf("label %q target already set: %v", name, l.target)
+ }
+ l.target = len(b.instructions)
+ return nil
+}
+
+// Instructions returns an array of BPF instructions representing the program with all labels
+// 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) {
+ if err := b.resolveLabels(); err != nil {
+ return b.instructions, err
+ }
+ return b.instructions, nil
+}
+
+func (b *ProgramBuilder) addLabelSource(labelName string, t jmpType) {
+ l, ok := b.labels[labelName]
+ if !ok {
+ l = &label{sources: make([]source, 0), target: -1}
+ b.labels[labelName] = l
+ }
+ l.sources = append(l.sources, source{line: len(b.instructions), jt: t})
+}
+
+func (b *ProgramBuilder) resolveLabels() error {
+ for key, v := range b.labels {
+ if v.target == -1 {
+ return fmt.Errorf("label target not set: %v", key)
+ }
+ if v.target >= len(b.instructions) {
+ return fmt.Errorf("target is beyond end of ProgramBuilder")
+ }
+ for _, s := range v.sources {
+ // Finds jump instruction that references the label.
+ inst := b.instructions[s.line]
+ if s.line >= v.target {
+ return fmt.Errorf("cannot jump backwards")
+ }
+ // Calculates the jump offset from current line.
+ offset := v.target - s.line - 1
+ // Sets offset into jump instruction.
+ switch s.jt {
+ case jDirect:
+ if offset > labelDirectTarget {
+ return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line)
+ }
+ if inst.K != labelDirectTarget {
+ return fmt.Errorf("jump target is not a label")
+ }
+ inst.K = uint32(offset)
+ case jTrue:
+ if offset > labelTarget {
+ return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line)
+ }
+ if inst.JumpIfTrue != labelTarget {
+ return fmt.Errorf("jump target is not a label")
+ }
+ inst.JumpIfTrue = uint8(offset)
+ case jFalse:
+ if offset > labelTarget {
+ return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line)
+ }
+ if inst.JumpIfFalse != labelTarget {
+ return fmt.Errorf("jump target is not a label")
+ }
+ inst.JumpIfFalse = uint8(offset)
+ }
+
+ b.instructions[s.line] = inst
+ }
+ }
+ b.labels = map[string]*label{}
+ return nil
+}