// 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 }