You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
3.6 KiB
140 lines
3.6 KiB
// 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 |
|
}
|
|
|