| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package bpf |
| |
| import ( |
| "errors" |
| "fmt" |
| ) |
| |
| // A VM is an emulated BPF virtual machine. |
| type VM struct { |
| filter []Instruction |
| } |
| |
| // NewVM returns a new VM using the input BPF program. |
| func NewVM(filter []Instruction) (*VM, error) { |
| if len(filter) == 0 { |
| return nil, errors.New("one or more Instructions must be specified") |
| } |
| |
| for i, ins := range filter { |
| check := len(filter) - (i + 1) |
| switch ins := ins.(type) { |
| // Check for out-of-bounds jumps in instructions |
| case Jump: |
| if check <= int(ins.Skip) { |
| return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip) |
| } |
| case JumpIf: |
| if check <= int(ins.SkipTrue) { |
| return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue) |
| } |
| if check <= int(ins.SkipFalse) { |
| return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse) |
| } |
| // Check for division or modulus by zero |
| case ALUOpConstant: |
| if ins.Val != 0 { |
| break |
| } |
| |
| switch ins.Op { |
| case ALUOpDiv, ALUOpMod: |
| return nil, errors.New("cannot divide by zero using ALUOpConstant") |
| } |
| // Check for unknown extensions |
| case LoadExtension: |
| switch ins.Num { |
| case ExtLen: |
| default: |
| return nil, fmt.Errorf("extension %d not implemented", ins.Num) |
| } |
| } |
| } |
| |
| // Make sure last instruction is a return instruction |
| switch filter[len(filter)-1].(type) { |
| case RetA, RetConstant: |
| default: |
| return nil, errors.New("BPF program must end with RetA or RetConstant") |
| } |
| |
| // Though our VM works using disassembled instructions, we |
| // attempt to assemble the input filter anyway to ensure it is compatible |
| // with an operating system VM. |
| _, err := Assemble(filter) |
| |
| return &VM{ |
| filter: filter, |
| }, err |
| } |
| |
| // Run runs the VM's BPF program against the input bytes. |
| // Run returns the number of bytes accepted by the BPF program, and any errors |
| // which occurred while processing the program. |
| func (v *VM) Run(in []byte) (int, error) { |
| var ( |
| // Registers of the virtual machine |
| regA uint32 |
| regX uint32 |
| regScratch [16]uint32 |
| |
| // OK is true if the program should continue processing the next |
| // instruction, or false if not, causing the loop to break |
| ok = true |
| ) |
| |
| // TODO(mdlayher): implement: |
| // - NegateA: |
| // - would require a change from uint32 registers to int32 |
| // registers |
| |
| // TODO(mdlayher): add interop tests that check signedness of ALU |
| // operations against kernel implementation, and make sure Go |
| // implementation matches behavior |
| |
| for i := 0; i < len(v.filter) && ok; i++ { |
| ins := v.filter[i] |
| |
| switch ins := ins.(type) { |
| case ALUOpConstant: |
| regA = aluOpConstant(ins, regA) |
| case ALUOpX: |
| regA, ok = aluOpX(ins, regA, regX) |
| case Jump: |
| i += int(ins.Skip) |
| case JumpIf: |
| jump := jumpIf(ins, regA) |
| i += jump |
| case LoadAbsolute: |
| regA, ok = loadAbsolute(ins, in) |
| case LoadConstant: |
| regA, regX = loadConstant(ins, regA, regX) |
| case LoadExtension: |
| regA = loadExtension(ins, in) |
| case LoadIndirect: |
| regA, ok = loadIndirect(ins, in, regX) |
| case LoadMemShift: |
| regX, ok = loadMemShift(ins, in) |
| case LoadScratch: |
| regA, regX = loadScratch(ins, regScratch, regA, regX) |
| case RetA: |
| return int(regA), nil |
| case RetConstant: |
| return int(ins.Val), nil |
| case StoreScratch: |
| regScratch = storeScratch(ins, regScratch, regA, regX) |
| case TAX: |
| regX = regA |
| case TXA: |
| regA = regX |
| default: |
| return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins) |
| } |
| } |
| |
| return 0, nil |
| } |