diff options
author | gVisor bot <gvisor-bot@google.com> | 2019-06-02 06:44:55 +0000 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2019-06-02 06:44:55 +0000 |
commit | ceb0d792f328d1fc0692197d8856a43c3936a571 (patch) | |
tree | 83155f302eff44a78bcc30a3a08f4efe59a79379 /pkg/bpf | |
parent | deb7ecf1e46862d54f4b102f2d163cfbcfc37f3b (diff) | |
parent | 216da0b733dbed9aad9b2ab92ac75bcb906fd7ee (diff) |
Merge 216da0b7 (automated)
Diffstat (limited to 'pkg/bpf')
-rw-r--r-- | pkg/bpf/bpf.go | 129 | ||||
-rwxr-xr-x | pkg/bpf/bpf_state_autogen.go | 22 | ||||
-rw-r--r-- | pkg/bpf/decoder.go | 245 | ||||
-rw-r--r-- | pkg/bpf/input_bytes.go | 58 | ||||
-rw-r--r-- | pkg/bpf/interpreter.go | 412 | ||||
-rw-r--r-- | pkg/bpf/program_builder.go | 191 |
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 +} |