Skip to content

Commit 1961d9d

Browse files
mdlayhercixtor
authored andcommitted
bpf: add Go implementation of virtual machine
Fixes golang/go#16055. Change-Id: I80437e2895b0f2bf23e090dec29bd20c2900db9a Reviewed-on: https://go-review.googlesource.com/24136 Reviewed-by: David Anderson <danderson@google.com> Run-TryBot: Mikio Hara <mikioh.mikioh@gmail.com> Reviewed-by: Mikio Hara <mikioh.mikioh@gmail.com>
1 parent d7bf354 commit 1961d9d

10 files changed

+2137
-1
lines changed

bpf/doc.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
/*
66
77
Package bpf implements marshaling and unmarshaling of programs for the
8-
Berkeley Packet Filter virtual machine.
8+
Berkeley Packet Filter virtual machine, and provides a Go implementation
9+
of the virtual machine.
910
1011
BPF's main use is to specify a packet filter for network taps, so that
1112
the kernel doesn't have to expensively copy every packet it sees to

bpf/vm.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package bpf
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
)
11+
12+
// A VM is an emulated BPF virtual machine.
13+
type VM struct {
14+
filter []Instruction
15+
}
16+
17+
// NewVM returns a new VM using the input BPF program.
18+
func NewVM(filter []Instruction) (*VM, error) {
19+
if len(filter) == 0 {
20+
return nil, errors.New("one or more Instructions must be specified")
21+
}
22+
23+
for i, ins := range filter {
24+
check := len(filter) - (i + 1)
25+
switch ins := ins.(type) {
26+
// Check for out-of-bounds jumps in instructions
27+
case Jump:
28+
if check <= int(ins.Skip) {
29+
return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
30+
}
31+
case JumpIf:
32+
if check <= int(ins.SkipTrue) {
33+
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
34+
}
35+
if check <= int(ins.SkipFalse) {
36+
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
37+
}
38+
// Check for division or modulus by zero
39+
case ALUOpConstant:
40+
if ins.Val != 0 {
41+
break
42+
}
43+
44+
switch ins.Op {
45+
case ALUOpDiv, ALUOpMod:
46+
return nil, errors.New("cannot divide by zero using ALUOpConstant")
47+
}
48+
}
49+
}
50+
51+
// Make sure last instruction is a return instruction
52+
switch filter[len(filter)-1].(type) {
53+
case RetA, RetConstant:
54+
default:
55+
return nil, errors.New("BPF program must end with RetA or RetConstant")
56+
}
57+
58+
// Though our VM works using disassembled instructions, we
59+
// attempt to assemble the input filter anyway to ensure it is compatible
60+
// with an operating system VM.
61+
_, err := Assemble(filter)
62+
63+
return &VM{
64+
filter: filter,
65+
}, err
66+
}
67+
68+
// Run runs the VM's BPF program against the input bytes.
69+
// Run returns the number of bytes accepted by the BPF program, and any errors
70+
// which occurred while processing the program.
71+
func (v *VM) Run(in []byte) (int, error) {
72+
var (
73+
// Registers of the virtual machine
74+
regA uint32
75+
regX uint32
76+
regScratch [16]uint32
77+
78+
// OK is true if the program should continue processing the next
79+
// instruction, or false if not, causing the loop to break
80+
ok = true
81+
)
82+
83+
// TODO(mdlayher): implement:
84+
// - NegateA:
85+
// - would require a change from uint32 registers to int32
86+
// registers
87+
// - Extension:
88+
// - implement extensions that do not depend on kernel-specific
89+
// functionality, such as 'rand'
90+
91+
// TODO(mdlayher): add interop tests that check signedness of ALU
92+
// operations against kernel implementation, and make sure Go
93+
// implementation matches behavior
94+
95+
for i := 0; i < len(v.filter) && ok; i++ {
96+
ins := v.filter[i]
97+
98+
switch ins := ins.(type) {
99+
case ALUOpConstant:
100+
regA = aluOpConstant(ins, regA)
101+
case ALUOpX:
102+
regA, ok = aluOpX(ins, regA, regX)
103+
case Jump:
104+
i += int(ins.Skip)
105+
case JumpIf:
106+
jump := jumpIf(ins, regA)
107+
i += jump
108+
case LoadAbsolute:
109+
regA, ok = loadAbsolute(ins, in)
110+
case LoadConstant:
111+
regA, regX = loadConstant(ins, regA, regX)
112+
case LoadIndirect:
113+
regA, ok = loadIndirect(ins, in, regX)
114+
case LoadMemShift:
115+
regX, ok = loadMemShift(ins, in)
116+
case LoadScratch:
117+
regA, regX = loadScratch(ins, regScratch, regA, regX)
118+
case RetA:
119+
return int(regA), nil
120+
case RetConstant:
121+
return int(ins.Val), nil
122+
case StoreScratch:
123+
regScratch = storeScratch(ins, regScratch, regA, regX)
124+
case TAX:
125+
regX = regA
126+
case TXA:
127+
regA = regX
128+
default:
129+
return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
130+
}
131+
}
132+
133+
return 0, nil
134+
}

0 commit comments

Comments
 (0)