Browse Source

Ported the bpf vm with pluggable extensions

maze 5 months ago
parent
commit
9d17131fc0
11 changed files with 2210 additions and 14 deletions
  1. 0
    0
      Makefile
  2. 10
    14
      assembler.go
  3. 145
    0
      vm.go
  4. 495
    0
      vm_alu_test.go
  5. 14
    0
      vm_debug.go
  6. 709
    0
      vm_jump_test.go
  7. 235
    0
      vm_load_test.go
  8. 188
    0
      vm_registers.go
  9. 111
    0
      vm_ret_test.go
  10. 245
    0
      vm_scratch_test.go
  11. 58
    0
      vm_test.go

+ 0
- 0
Makefile View File


+ 10
- 14
assembler.go View File

@@ -5,7 +5,6 @@ package bpf
5 5
 import (
6 6
 	"errors"
7 7
 	"fmt"
8
-	"log"
9 8
 
10 9
 	"github.com/antlr/antlr4/runtime/Go/antlr"
11 10
 	"golang.org/x/net/bpf"
@@ -13,8 +12,15 @@ import (
13 12
 	"maze.io/x/bpf/internal/parser"
14 13
 )
15 14
 
15
+// Program is an assembled program.
16
+type Program []bpf.Instruction
17
+
18
+func (p Program) Assemble() ([]bpf.RawInstruction, error) {
19
+	return bpf.Assemble(p)
20
+}
21
+
16 22
 // Assemble BPF instructions from source.
17
-func Assemble(source string) ([]bpf.RawInstruction, error) {
23
+func Assemble(source string) (Program, error) {
18 24
 	var (
19 25
 		input  = antlr.NewInputStream(source)
20 26
 		lexer  = parser.NewBPFLexer(input)
@@ -26,17 +32,8 @@ func Assemble(source string) ([]bpf.RawInstruction, error) {
26 32
 	asm := new(assembler)
27 33
 	result := asm.Visit(parse.Program()).([]interface{})
28 34
 
29
-	/*
30
-		tokens := stream.GetAllTokens()
31
-		log.Printf("%d tokens", len(tokens))
32
-		for _, token := range tokens {
33
-			log.Printf("token: %s", token)
34
-		}
35
-	*/
36
-	log.Printf("result: %+v", result)
37
-
38 35
 	if result[1] == nil {
39
-		return result[0].([]bpf.RawInstruction), nil
36
+		return result[0].([]bpf.Instruction), nil
40 37
 	}
41 38
 	return nil, result[1].(error)
42 39
 }
@@ -166,8 +163,7 @@ func (asm *assembler) VisitProgram(ctx *parser.ProgramContext) interface{} {
166 163
 		}
167 164
 	}
168 165
 
169
-	raw, err := bpf.Assemble(instructions)
170
-	return []interface{}{raw, err}
166
+	return []interface{}{instructions, nil}
171 167
 }
172 168
 
173 169
 func (asm *assembler) VisitLabelDefinition(ctx *parser.LabelDefinitionContext) interface{} {

+ 145
- 0
vm.go View File

@@ -0,0 +1,145 @@
1
+package bpf
2
+
3
+import (
4
+	"errors"
5
+	"fmt"
6
+
7
+	"golang.org/x/net/bpf"
8
+)
9
+
10
+// A VM is an emulated BPF virtual machine.
11
+type VM struct {
12
+	// Extensions are optional callback to load extensions. Note that the
13
+	// "len" extension is always provided by the VM.
14
+	Extensions map[bpf.Extension]func(*Registers) uint32
15
+
16
+	program Program
17
+}
18
+
19
+func NewVM(program Program) *VM {
20
+	return &VM{
21
+		program: program,
22
+	}
23
+}
24
+
25
+// Verify runs sanity checks on the loaded program.
26
+func (vm *VM) Verify() error {
27
+	if len(vm.program) == 0 {
28
+		return errors.New("bpf: no instructions in program")
29
+	}
30
+
31
+	for i, ins := range vm.program {
32
+		check := len(vm.program) - (i + 1)
33
+		switch ins := ins.(type) {
34
+		// Check for out-of-bounds jumps in instructions
35
+		case bpf.Jump:
36
+			if check <= int(ins.Skip) {
37
+				return fmt.Errorf("bpf: cannot jump %d instructions; jumping past program bounds", ins.Skip)
38
+			}
39
+		case bpf.JumpIf:
40
+			if check <= int(ins.SkipTrue) {
41
+				return fmt.Errorf("bpf: cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
42
+			}
43
+			if check <= int(ins.SkipFalse) {
44
+				return fmt.Errorf("bpf: cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
45
+			}
46
+		case bpf.JumpIfX:
47
+			if check <= int(ins.SkipTrue) {
48
+				return fmt.Errorf("bpf: cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
49
+			}
50
+			if check <= int(ins.SkipFalse) {
51
+				return fmt.Errorf("bpf: cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
52
+			}
53
+		// Check for division or modulus by zero
54
+		case bpf.ALUOpConstant:
55
+			if ins.Val != 0 {
56
+				break
57
+			}
58
+
59
+			switch ins.Op {
60
+			case bpf.ALUOpDiv, bpf.ALUOpMod:
61
+				return errors.New("cannot divide by zero using ALUOpConstant")
62
+			}
63
+		// Check for unknown extensions
64
+		case bpf.LoadExtension:
65
+			switch ins.Num {
66
+			case bpf.ExtLen:
67
+			default:
68
+				if vm.Extensions != nil {
69
+					if _, ok := vm.Extensions[ins.Num]; ok {
70
+						continue
71
+					}
72
+				}
73
+				return fmt.Errorf("extension %d not implemented", ins.Num)
74
+			}
75
+		}
76
+	}
77
+
78
+	// Make sure last instruction is a return instruction
79
+	switch vm.program[len(vm.program)-1].(type) {
80
+	case bpf.RetA, bpf.RetConstant:
81
+	default:
82
+		return errors.New("bpf: program must end with RetA or RetConstant")
83
+	}
84
+
85
+	// Though our VM works using disassembled instructions, we
86
+	// attempt to assemble the input filter anyway to ensure it is compatible
87
+	// with an operating system VM.
88
+	_, err := bpf.Assemble(vm.program)
89
+
90
+	return err
91
+}
92
+
93
+func (vm *VM) Run(in []byte) (verdict uint32, err error) {
94
+	var (
95
+		reg = new(Registers)
96
+		end = uint32(len(vm.program))
97
+		ok  = true
98
+	)
99
+	for ok && reg.PC < end {
100
+		debugf("ins=%s (%T) reg=%+v -> ", vm.program[reg.PC], vm.program[reg.PC], reg)
101
+		switch ins := vm.program[reg.PC].(type) {
102
+		case bpf.ALUOpConstant:
103
+			reg.aluOpConstant(ins)
104
+		case bpf.ALUOpX:
105
+			ok = reg.aluOpX(ins)
106
+		case bpf.Jump:
107
+			reg.PC += uint32(ins.Skip)
108
+		case bpf.JumpIf:
109
+			reg.jumpIf(ins)
110
+		case bpf.JumpIfX:
111
+			reg.jumpIfX(ins)
112
+		case bpf.LoadAbsolute:
113
+			ok = reg.loadAbsolute(ins, in)
114
+		case bpf.LoadConstant:
115
+			reg.loadConstant(ins)
116
+		case bpf.LoadExtension:
117
+			reg.loadExtension(ins, in, vm.Extensions)
118
+		case bpf.LoadIndirect:
119
+			ok = reg.loadIndirect(ins, in)
120
+		case bpf.LoadMemShift:
121
+			ok = reg.loadMemShift(ins, in)
122
+		case bpf.LoadScratch:
123
+			reg.loadScratch(ins)
124
+		case bpf.RetA:
125
+			return reg.A, nil
126
+		case bpf.RetConstant:
127
+			return ins.Val, nil
128
+		case bpf.StoreScratch:
129
+			reg.storeScratch(ins)
130
+		case bpf.TAX:
131
+			reg.X = reg.A
132
+		case bpf.TXA:
133
+			reg.A = reg.X
134
+		case bpf.NegateA:
135
+			reg.A = uint32(-int32(reg.A))
136
+		default:
137
+			return 0, fmt.Errorf("bpf: unknown instruction at pc=%d: %T", reg.PC-1, ins)
138
+		}
139
+
140
+		debugf("reg=%+v\n", reg)
141
+		reg.PC++
142
+	}
143
+
144
+	return
145
+}

+ 495
- 0
vm_alu_test.go View File

@@ -0,0 +1,495 @@
1
+package bpf_test
2
+
3
+import (
4
+	"testing"
5
+
6
+	"golang.org/x/net/bpf"
7
+)
8
+
9
+func TestVMALUOpAdd(t *testing.T) {
10
+	vm, err := testVM(t, []bpf.Instruction{
11
+		bpf.LoadAbsolute{
12
+			Off:  8,
13
+			Size: 1,
14
+		},
15
+		bpf.ALUOpConstant{
16
+			Op:  bpf.ALUOpAdd,
17
+			Val: 3,
18
+		},
19
+		bpf.RetA{},
20
+	})
21
+	if err != nil {
22
+		t.Fatalf("failed to load BPF program: %v", err)
23
+	}
24
+
25
+	out, err := vm.Run([]byte{
26
+		0xff, 0xff, 0xff, 0xff,
27
+		0xff, 0xff, 0xff, 0xff,
28
+		8, 2, 3,
29
+	})
30
+	if err != nil {
31
+		t.Fatalf("unexpected error while running program: %v", err)
32
+	}
33
+	if want, got := uint32(3), out; want != got {
34
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
35
+			want, got)
36
+	}
37
+}
38
+
39
+func TestVMALUOpSub(t *testing.T) {
40
+	vm, err := testVM(t, []bpf.Instruction{
41
+		bpf.LoadAbsolute{
42
+			Off:  8,
43
+			Size: 1,
44
+		},
45
+		bpf.TAX{},
46
+		bpf.ALUOpX{
47
+			Op: bpf.ALUOpSub,
48
+		},
49
+		bpf.RetA{},
50
+	})
51
+	if err != nil {
52
+		t.Fatalf("failed to load BPF program: %v", err)
53
+	}
54
+
55
+	out, err := vm.Run([]byte{
56
+		0xff, 0xff, 0xff, 0xff,
57
+		0xff, 0xff, 0xff, 0xff,
58
+		1, 2, 3,
59
+	})
60
+	if err != nil {
61
+		t.Fatalf("unexpected error while running program: %v", err)
62
+	}
63
+	if want, got := uint32(0), out; want != got {
64
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
65
+			want, got)
66
+	}
67
+}
68
+
69
+func TestVMALUOpMul(t *testing.T) {
70
+	vm, err := testVM(t, []bpf.Instruction{
71
+		bpf.LoadAbsolute{
72
+			Off:  8,
73
+			Size: 1,
74
+		},
75
+		bpf.ALUOpConstant{
76
+			Op:  bpf.ALUOpMul,
77
+			Val: 2,
78
+		},
79
+		bpf.RetA{},
80
+	})
81
+	if err != nil {
82
+		t.Fatalf("failed to load BPF program: %v", err)
83
+	}
84
+
85
+	out, err := vm.Run([]byte{
86
+		0xff, 0xff, 0xff, 0xff,
87
+		0xff, 0xff, 0xff, 0xff,
88
+		6, 2, 3, 4,
89
+	})
90
+	if err != nil {
91
+		t.Fatalf("unexpected error while running program: %v", err)
92
+	}
93
+	if want, got := uint32(4), out; want != got {
94
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
95
+			want, got)
96
+	}
97
+}
98
+
99
+func TestVMALUOpDiv(t *testing.T) {
100
+	vm, err := testVM(t, []bpf.Instruction{
101
+		bpf.LoadAbsolute{
102
+			Off:  8,
103
+			Size: 1,
104
+		},
105
+		bpf.ALUOpConstant{
106
+			Op:  bpf.ALUOpDiv,
107
+			Val: 2,
108
+		},
109
+		bpf.RetA{},
110
+	})
111
+	if err != nil {
112
+		t.Fatalf("failed to load BPF program: %v", err)
113
+	}
114
+
115
+	out, err := vm.Run([]byte{
116
+		0xff, 0xff, 0xff, 0xff,
117
+		0xff, 0xff, 0xff, 0xff,
118
+		20, 2, 3, 4,
119
+	})
120
+	if err != nil {
121
+		t.Fatalf("unexpected error while running program: %v", err)
122
+	}
123
+	if want, got := uint32(2), out; want != got {
124
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
125
+			want, got)
126
+	}
127
+}
128
+
129
+func TestVMALUOpDivByZeroALUOpConstant(t *testing.T) {
130
+	_, err := testVM(t, []bpf.Instruction{
131
+		bpf.ALUOpConstant{
132
+			Op:  bpf.ALUOpDiv,
133
+			Val: 0,
134
+		},
135
+		bpf.RetA{},
136
+	})
137
+	if errStr(err) != "cannot divide by zero using ALUOpConstant" {
138
+		t.Fatalf("failed to load BPF program: %v", err)
139
+	}
140
+}
141
+
142
+func TestVMALUOpDivByZeroALUOpX(t *testing.T) {
143
+	vm, err := testVM(t, []bpf.Instruction{
144
+		// Load byte 0 into X
145
+		bpf.LoadAbsolute{
146
+			Off:  8,
147
+			Size: 1,
148
+		},
149
+		bpf.TAX{},
150
+		// Load byte 1 into A
151
+		bpf.LoadAbsolute{
152
+			Off:  9,
153
+			Size: 1,
154
+		},
155
+		// Attempt to perform 1/0
156
+		bpf.ALUOpX{
157
+			Op: bpf.ALUOpDiv,
158
+		},
159
+		// Return 4 bytes if program does not terminate
160
+		bpf.LoadConstant{
161
+			Val: 12,
162
+		},
163
+		bpf.RetA{},
164
+	})
165
+	if err != nil {
166
+		t.Fatalf("failed to load BPF program: %v", err)
167
+	}
168
+
169
+	out, err := vm.Run([]byte{
170
+		0xff, 0xff, 0xff, 0xff,
171
+		0xff, 0xff, 0xff, 0xff,
172
+		0, 1, 3, 4,
173
+	})
174
+	if err != nil {
175
+		t.Fatalf("unexpected error while running program: %v", err)
176
+	}
177
+	if want, got := uint32(0), out; want != got {
178
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
179
+			want, got)
180
+	}
181
+}
182
+
183
+func TestVMALUOpOr(t *testing.T) {
184
+	vm, err := testVM(t, []bpf.Instruction{
185
+		bpf.LoadAbsolute{
186
+			Off:  8,
187
+			Size: 2,
188
+		},
189
+		bpf.ALUOpConstant{
190
+			Op:  bpf.ALUOpOr,
191
+			Val: 0x01,
192
+		},
193
+		bpf.RetA{},
194
+	})
195
+	if err != nil {
196
+		t.Fatalf("failed to load BPF program: %v", err)
197
+	}
198
+
199
+	out, err := vm.Run([]byte{
200
+		0xff, 0xff, 0xff, 0xff,
201
+		0xff, 0xff, 0xff, 0xff,
202
+		0x00, 0x10, 0x03, 0x04,
203
+		0x05, 0x06, 0x07, 0x08,
204
+		0x09, 0xff,
205
+	})
206
+	if err != nil {
207
+		t.Fatalf("unexpected error while running program: %v", err)
208
+	}
209
+	if want, got := uint32(9), out; want != got {
210
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
211
+			want, got)
212
+	}
213
+}
214
+
215
+func TestVMALUOpAnd(t *testing.T) {
216
+	vm, err := testVM(t, []bpf.Instruction{
217
+		bpf.LoadAbsolute{
218
+			Off:  8,
219
+			Size: 2,
220
+		},
221
+		bpf.ALUOpConstant{
222
+			Op:  bpf.ALUOpAnd,
223
+			Val: 0x0019,
224
+		},
225
+		bpf.RetA{},
226
+	})
227
+	if err != nil {
228
+		t.Fatalf("failed to load BPF program: %v", err)
229
+	}
230
+
231
+	out, err := vm.Run([]byte{
232
+		0xff, 0xff, 0xff, 0xff,
233
+		0xff, 0xff, 0xff, 0xff,
234
+		0xaa, 0x09,
235
+	})
236
+	if err != nil {
237
+		t.Fatalf("unexpected error while running program: %v", err)
238
+	}
239
+	if want, got := uint32(1), out; want != got {
240
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
241
+			want, got)
242
+	}
243
+}
244
+
245
+func TestVMALUOpShiftLeft(t *testing.T) {
246
+	vm, err := testVM(t, []bpf.Instruction{
247
+		bpf.LoadAbsolute{
248
+			Off:  8,
249
+			Size: 1,
250
+		},
251
+		bpf.ALUOpConstant{
252
+			Op:  bpf.ALUOpShiftLeft,
253
+			Val: 0x01,
254
+		},
255
+		bpf.JumpIf{
256
+			Cond:     bpf.JumpEqual,
257
+			Val:      0x02,
258
+			SkipTrue: 1,
259
+		},
260
+		bpf.RetConstant{
261
+			Val: 0,
262
+		},
263
+		bpf.RetConstant{
264
+			Val: 9,
265
+		},
266
+	})
267
+	if err != nil {
268
+		t.Fatalf("failed to load BPF program: %v", err)
269
+	}
270
+
271
+	out, err := vm.Run([]byte{
272
+		0xff, 0xff, 0xff, 0xff,
273
+		0xff, 0xff, 0xff, 0xff,
274
+		0x01, 0xaa,
275
+	})
276
+	if err != nil {
277
+		t.Fatalf("unexpected error while running program: %v", err)
278
+	}
279
+	if want, got := uint32(1), out; want != got {
280
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
281
+			want, got)
282
+	}
283
+}
284
+
285
+func TestVMALUOpShiftRight(t *testing.T) {
286
+	vm, err := testVM(t, []bpf.Instruction{
287
+		bpf.LoadAbsolute{
288
+			Off:  8,
289
+			Size: 1,
290
+		},
291
+		bpf.ALUOpConstant{
292
+			Op:  bpf.ALUOpShiftRight,
293
+			Val: 0x01,
294
+		},
295
+		bpf.JumpIf{
296
+			Cond:     bpf.JumpEqual,
297
+			Val:      0x04,
298
+			SkipTrue: 1,
299
+		},
300
+		bpf.RetConstant{
301
+			Val: 0,
302
+		},
303
+		bpf.RetConstant{
304
+			Val: 9,
305
+		},
306
+	})
307
+	if err != nil {
308
+		t.Fatalf("failed to load BPF program: %v", err)
309
+	}
310
+
311
+	out, err := vm.Run([]byte{
312
+		0xff, 0xff, 0xff, 0xff,
313
+		0xff, 0xff, 0xff, 0xff,
314
+		0x08, 0xff, 0xff,
315
+	})
316
+	if err != nil {
317
+		t.Fatalf("unexpected error while running program: %v", err)
318
+	}
319
+	if want, got := uint32(1), out; want != got {
320
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
321
+			want, got)
322
+	}
323
+}
324
+
325
+func TestVMALUOpMod(t *testing.T) {
326
+	vm, err := testVM(t, []bpf.Instruction{
327
+		bpf.LoadAbsolute{
328
+			Off:  8,
329
+			Size: 1,
330
+		},
331
+		bpf.ALUOpConstant{
332
+			Op:  bpf.ALUOpMod,
333
+			Val: 20,
334
+		},
335
+		bpf.RetA{},
336
+	})
337
+	if err != nil {
338
+		t.Fatalf("failed to load BPF program: %v", err)
339
+	}
340
+
341
+	out, err := vm.Run([]byte{
342
+		0xff, 0xff, 0xff, 0xff,
343
+		0xff, 0xff, 0xff, 0xff,
344
+		30, 0, 0,
345
+	})
346
+	if err != nil {
347
+		t.Fatalf("unexpected error while running program: %v", err)
348
+	}
349
+	if want, got := uint32(2), out; want != got {
350
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
351
+			want, got)
352
+	}
353
+}
354
+
355
+func TestVMALUOpModByZeroALUOpConstant(t *testing.T) {
356
+	_, err := testVM(t, []bpf.Instruction{
357
+		bpf.LoadAbsolute{
358
+			Off:  8,
359
+			Size: 1,
360
+		},
361
+		bpf.ALUOpConstant{
362
+			Op:  bpf.ALUOpMod,
363
+			Val: 0,
364
+		},
365
+		bpf.RetA{},
366
+	})
367
+	if errStr(err) != "cannot divide by zero using ALUOpConstant" {
368
+		t.Fatalf("unexpected error: %v", err)
369
+	}
370
+}
371
+
372
+func TestVMALUOpModByZeroALUOpX(t *testing.T) {
373
+	vm, err := testVM(t, []bpf.Instruction{
374
+		// Load byte 0 into X
375
+		bpf.LoadAbsolute{
376
+			Off:  8,
377
+			Size: 1,
378
+		},
379
+		bpf.TAX{},
380
+		// Load byte 1 into A
381
+		bpf.LoadAbsolute{
382
+			Off:  9,
383
+			Size: 1,
384
+		},
385
+		// Attempt to perform 1%0
386
+		bpf.ALUOpX{
387
+			Op: bpf.ALUOpMod,
388
+		},
389
+		// Return 4 bytes if program does not terminate
390
+		bpf.LoadConstant{
391
+			Val: 12,
392
+		},
393
+		bpf.RetA{},
394
+	})
395
+	if err != nil {
396
+		t.Fatalf("failed to load BPF program: %v", err)
397
+	}
398
+
399
+	out, err := vm.Run([]byte{
400
+		0xff, 0xff, 0xff, 0xff,
401
+		0xff, 0xff, 0xff, 0xff,
402
+		0, 1, 3, 4,
403
+	})
404
+	if err != nil {
405
+		t.Fatalf("unexpected error while running program: %v", err)
406
+	}
407
+	if want, got := uint32(0), out; want != got {
408
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
409
+			want, got)
410
+	}
411
+}
412
+
413
+func TestVMALUOpXor(t *testing.T) {
414
+	vm, err := testVM(t, []bpf.Instruction{
415
+		bpf.LoadAbsolute{
416
+			Off:  8,
417
+			Size: 1,
418
+		},
419
+		bpf.ALUOpConstant{
420
+			Op:  bpf.ALUOpXor,
421
+			Val: 0x0a,
422
+		},
423
+		bpf.JumpIf{
424
+			Cond:     bpf.JumpEqual,
425
+			Val:      0x01,
426
+			SkipTrue: 1,
427
+		},
428
+		bpf.RetConstant{
429
+			Val: 0,
430
+		},
431
+		bpf.RetConstant{
432
+			Val: 9,
433
+		},
434
+	})
435
+	if err != nil {
436
+		t.Fatalf("failed to load BPF program: %v", err)
437
+	}
438
+
439
+	out, err := vm.Run([]byte{
440
+		0xff, 0xff, 0xff, 0xff,
441
+		0xff, 0xff, 0xff, 0xff,
442
+		0x0b, 0x00, 0x00, 0x00,
443
+	})
444
+	if err != nil {
445
+		t.Fatalf("unexpected error while running program: %v", err)
446
+	}
447
+	if want, got := uint32(1), out; want != got {
448
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
449
+			want, got)
450
+	}
451
+}
452
+
453
+func TestVMALUOpUnknown(t *testing.T) {
454
+	vm, err := testVM(t, []bpf.Instruction{
455
+		bpf.LoadAbsolute{
456
+			Off:  8,
457
+			Size: 1,
458
+		},
459
+		bpf.ALUOpConstant{
460
+			Op:  bpf.ALUOpAdd,
461
+			Val: 1,
462
+		},
463
+		// Verify that an unknown operation is a no-op
464
+		bpf.ALUOpConstant{
465
+			Op: 100,
466
+		},
467
+		bpf.JumpIf{
468
+			Cond:     bpf.JumpEqual,
469
+			Val:      0x02,
470
+			SkipTrue: 1,
471
+		},
472
+		bpf.RetConstant{
473
+			Val: 0,
474
+		},
475
+		bpf.RetConstant{
476
+			Val: 9,
477
+		},
478
+	})
479
+	if err != nil {
480
+		t.Fatalf("failed to load BPF program: %v", err)
481
+	}
482
+
483
+	out, err := vm.Run([]byte{
484
+		0xff, 0xff, 0xff, 0xff,
485
+		0xff, 0xff, 0xff, 0xff,
486
+		1,
487
+	})
488
+	if err != nil {
489
+		t.Fatalf("unexpected error while running program: %v", err)
490
+	}
491
+	if want, got := uint32(1), out; want != got {
492
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
493
+			want, got)
494
+	}
495
+}

+ 14
- 0
vm_debug.go View File

@@ -0,0 +1,14 @@
1
+package bpf
2
+
3
+import (
4
+	"fmt"
5
+	"os"
6
+)
7
+
8
+var debug = os.Getenv("BPF_VM_DEBUG") != ""
9
+
10
+func debugf(format string, v ...interface{}) {
11
+	if debug {
12
+		fmt.Fprintf(os.Stderr, format, v...)
13
+	}
14
+}

+ 709
- 0
vm_jump_test.go View File

@@ -0,0 +1,709 @@
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_test
6
+
7
+import (
8
+	"testing"
9
+
10
+	"golang.org/x/net/bpf"
11
+)
12
+
13
+func TestVMJumpOne(t *testing.T) {
14
+	vm, err := testVM(t, []bpf.Instruction{
15
+		bpf.LoadAbsolute{
16
+			Off:  8,
17
+			Size: 1,
18
+		},
19
+		bpf.Jump{
20
+			Skip: 1,
21
+		},
22
+		bpf.RetConstant{
23
+			Val: 0,
24
+		},
25
+		bpf.RetConstant{
26
+			Val: 9,
27
+		},
28
+	})
29
+	if err != nil {
30
+		t.Fatalf("failed to load BPF program: %v", err)
31
+	}
32
+
33
+	out, err := vm.Run([]byte{
34
+		0xff, 0xff, 0xff, 0xff,
35
+		0xff, 0xff, 0xff, 0xff,
36
+		1,
37
+	})
38
+	if err != nil {
39
+		t.Fatalf("unexpected error while running program: %v", err)
40
+	}
41
+	if want, got := uint32(1), out; want != got {
42
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
43
+			want, got)
44
+	}
45
+}
46
+
47
+func TestVMJumpOutOfProgram(t *testing.T) {
48
+	_, err := testVM(t, []bpf.Instruction{
49
+		bpf.Jump{
50
+			Skip: 1,
51
+		},
52
+		bpf.RetA{},
53
+	})
54
+	if errStr(err) != "bpf: cannot jump 1 instructions; jumping past program bounds" {
55
+		t.Fatalf("unexpected error: %v", err)
56
+	}
57
+}
58
+
59
+func TestVMJumpIfTrueOutOfProgram(t *testing.T) {
60
+	_, err := testVM(t, []bpf.Instruction{
61
+		bpf.JumpIf{
62
+			Cond:     bpf.JumpEqual,
63
+			SkipTrue: 2,
64
+		},
65
+		bpf.RetA{},
66
+	})
67
+	if errStr(err) != "bpf: cannot jump 2 instructions in true case; jumping past program bounds" {
68
+		t.Fatalf("unexpected error: %v", err)
69
+	}
70
+}
71
+
72
+func TestVMJumpIfFalseOutOfProgram(t *testing.T) {
73
+	_, err := testVM(t, []bpf.Instruction{
74
+		bpf.JumpIf{
75
+			Cond:      bpf.JumpEqual,
76
+			SkipFalse: 3,
77
+		},
78
+		bpf.RetA{},
79
+	})
80
+	if errStr(err) != "bpf: cannot jump 3 instructions in false case; jumping past program bounds" {
81
+		t.Fatalf("unexpected error: %v", err)
82
+	}
83
+}
84
+
85
+func TestVMJumpIfXTrueOutOfProgram(t *testing.T) {
86
+	_, err := testVM(t, []bpf.Instruction{
87
+		bpf.JumpIfX{
88
+			Cond:     bpf.JumpEqual,
89
+			SkipTrue: 2,
90
+		},
91
+		bpf.RetA{},
92
+	})
93
+	if errStr(err) != "bpf: cannot jump 2 instructions in true case; jumping past program bounds" {
94
+		t.Fatalf("unexpected error: %v", err)
95
+	}
96
+}
97
+
98
+func TestVMJumpIfXFalseOutOfProgram(t *testing.T) {
99
+	_, err := testVM(t, []bpf.Instruction{
100
+		bpf.JumpIfX{
101
+			Cond:      bpf.JumpEqual,
102
+			SkipFalse: 3,
103
+		},
104
+		bpf.RetA{},
105
+	})
106
+	if errStr(err) != "bpf: cannot jump 3 instructions in false case; jumping past program bounds" {
107
+		t.Fatalf("unexpected error: %v", err)
108
+	}
109
+}
110
+
111
+func TestVMJumpIfEqual(t *testing.T) {
112
+	vm, err := testVM(t, []bpf.Instruction{
113
+		bpf.LoadAbsolute{
114
+			Off:  8,
115
+			Size: 1,
116
+		},
117
+		bpf.JumpIf{
118
+			Cond:     bpf.JumpEqual,
119
+			Val:      1,
120
+			SkipTrue: 1,
121
+		},
122
+		bpf.RetConstant{
123
+			Val: 0,
124
+		},
125
+		bpf.RetConstant{
126
+			Val: 9,
127
+		},
128
+	})
129
+	if err != nil {
130
+		t.Fatalf("failed to load BPF program: %v", err)
131
+	}
132
+
133
+	out, err := vm.Run([]byte{
134
+		0xff, 0xff, 0xff, 0xff,
135
+		0xff, 0xff, 0xff, 0xff,
136
+		1,
137
+	})
138
+	if err != nil {
139
+		t.Fatalf("unexpected error while running program: %v", err)
140
+	}
141
+	if want, got := uint32(1), out; want != got {
142
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
143
+			want, got)
144
+	}
145
+}
146
+
147
+func TestVMJumpIfNotEqual(t *testing.T) {
148
+	vm, err := testVM(t, []bpf.Instruction{
149
+		bpf.LoadAbsolute{
150
+			Off:  8,
151
+			Size: 1,
152
+		},
153
+		bpf.JumpIf{
154
+			Cond:      bpf.JumpNotEqual,
155
+			Val:       1,
156
+			SkipFalse: 1,
157
+		},
158
+		bpf.RetConstant{
159
+			Val: 0,
160
+		},
161
+		bpf.RetConstant{
162
+			Val: 9,
163
+		},
164
+	})
165
+	if err != nil {
166
+		t.Fatalf("failed to load BPF program: %v", err)
167
+	}
168
+
169
+	out, err := vm.Run([]byte{
170
+		0xff, 0xff, 0xff, 0xff,
171
+		0xff, 0xff, 0xff, 0xff,
172
+		1,
173
+	})
174
+	if err != nil {
175
+		t.Fatalf("unexpected error while running program: %v", err)
176
+	}
177
+	if want, got := uint32(1), out; want != got {
178
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
179
+			want, got)
180
+	}
181
+}
182
+
183
+func TestVMJumpIfGreaterThan(t *testing.T) {
184
+	vm, err := testVM(t, []bpf.Instruction{
185
+		bpf.LoadAbsolute{
186
+			Off:  8,
187
+			Size: 4,
188
+		},
189
+		bpf.JumpIf{
190
+			Cond:     bpf.JumpGreaterThan,
191
+			Val:      0x00010202,
192
+			SkipTrue: 1,
193
+		},
194
+		bpf.RetConstant{
195
+			Val: 0,
196
+		},
197
+		bpf.RetConstant{
198
+			Val: 12,
199
+		},
200
+	})
201
+	if err != nil {
202
+		t.Fatalf("failed to load BPF program: %v", err)
203
+	}
204
+
205
+	out, err := vm.Run([]byte{
206
+		0xff, 0xff, 0xff, 0xff,
207
+		0xff, 0xff, 0xff, 0xff,
208
+		0, 1, 2, 3,
209
+	})
210
+	if err != nil {
211
+		t.Fatalf("unexpected error while running program: %v", err)
212
+	}
213
+	if want, got := uint32(4), out; want != got {
214
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
215
+			want, got)
216
+	}
217
+}
218
+
219
+func TestVMJumpIfLessThan(t *testing.T) {
220
+	vm, err := testVM(t, []bpf.Instruction{
221
+		bpf.LoadAbsolute{
222
+			Off:  8,
223
+			Size: 4,
224
+		},
225
+		bpf.JumpIf{
226
+			Cond:     bpf.JumpLessThan,
227
+			Val:      0xff010203,
228
+			SkipTrue: 1,
229
+		},
230
+		bpf.RetConstant{
231
+			Val: 0,
232
+		},
233
+		bpf.RetConstant{
234
+			Val: 12,
235
+		},
236
+	})
237
+	if err != nil {
238
+		t.Fatalf("failed to load BPF program: %v", err)
239
+	}
240
+
241
+	out, err := vm.Run([]byte{
242
+		0xff, 0xff, 0xff, 0xff,
243
+		0xff, 0xff, 0xff, 0xff,
244
+		0, 1, 2, 3,
245
+	})
246
+	if err != nil {
247
+		t.Fatalf("unexpected error while running program: %v", err)
248
+	}
249
+	if want, got := uint32(4), out; want != got {
250
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
251
+			want, got)
252
+	}
253
+}
254
+
255
+func TestVMJumpIfGreaterOrEqual(t *testing.T) {
256
+	vm, err := testVM(t, []bpf.Instruction{
257
+		bpf.LoadAbsolute{
258
+			Off:  8,
259
+			Size: 4,
260
+		},
261
+		bpf.JumpIf{
262
+			Cond:     bpf.JumpGreaterOrEqual,
263
+			Val:      0x00010203,
264
+			SkipTrue: 1,
265
+		},
266
+		bpf.RetConstant{
267
+			Val: 0,
268
+		},
269
+		bpf.RetConstant{
270
+			Val: 12,
271
+		},
272
+	})
273
+	if err != nil {
274
+		t.Fatalf("failed to load BPF program: %v", err)
275
+	}
276
+
277
+	out, err := vm.Run([]byte{
278
+		0xff, 0xff, 0xff, 0xff,
279
+		0xff, 0xff, 0xff, 0xff,
280
+		0, 1, 2, 3,
281
+	})
282
+	if err != nil {
283
+		t.Fatalf("unexpected error while running program: %v", err)
284
+	}
285
+	if want, got := uint32(4), out; want != got {
286
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
287
+			want, got)
288
+	}
289
+}
290
+
291
+func TestVMJumpIfLessOrEqual(t *testing.T) {
292
+	vm, err := testVM(t, []bpf.Instruction{
293
+		bpf.LoadAbsolute{
294
+			Off:  8,
295
+			Size: 4,
296
+		},
297
+		bpf.JumpIf{
298
+			Cond:     bpf.JumpLessOrEqual,
299
+			Val:      0xff010203,
300
+			SkipTrue: 1,
301
+		},
302
+		bpf.RetConstant{
303
+			Val: 0,
304
+		},
305
+		bpf.RetConstant{
306
+			Val: 12,
307
+		},
308
+	})
309
+	if err != nil {
310
+		t.Fatalf("failed to load BPF program: %v", err)
311
+	}
312
+
313
+	out, err := vm.Run([]byte{
314
+		0xff, 0xff, 0xff, 0xff,
315
+		0xff, 0xff, 0xff, 0xff,
316
+		0, 1, 2, 3,
317
+	})
318
+	if err != nil {
319
+		t.Fatalf("unexpected error while running program: %v", err)
320
+	}
321
+	if want, got := uint32(4), out; want != got {
322
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
323
+			want, got)
324
+	}
325
+}
326
+
327
+func TestVMJumpIfBitsSet(t *testing.T) {
328
+	vm, err := testVM(t, []bpf.Instruction{
329
+		bpf.LoadAbsolute{
330
+			Off:  8,
331
+			Size: 2,
332
+		},
333
+		bpf.JumpIf{
334
+			Cond:     bpf.JumpBitsSet,
335
+			Val:      0x1122,
336
+			SkipTrue: 1,
337
+		},
338
+		bpf.RetConstant{
339
+			Val: 0,
340
+		},
341
+		bpf.RetConstant{
342
+			Val: 10,
343
+		},
344
+	})
345
+	if err != nil {
346
+		t.Fatalf("failed to load BPF program: %v", err)
347
+	}
348
+
349
+	out, err := vm.Run([]byte{
350
+		0xff, 0xff, 0xff, 0xff,
351
+		0xff, 0xff, 0xff, 0xff,
352
+		0x01, 0x02,
353
+	})
354
+	if err != nil {
355
+		t.Fatalf("unexpected error while running program: %v", err)
356
+	}
357
+	if want, got := uint32(2), out; want != got {
358
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
359
+			want, got)
360
+	}
361
+}
362
+
363
+func TestVMJumpIfBitsNotSet(t *testing.T) {
364
+	vm, err := testVM(t, []bpf.Instruction{
365
+		bpf.LoadAbsolute{
366
+			Off:  8,
367
+			Size: 2,
368
+		},
369
+		bpf.JumpIf{
370
+			Cond:     bpf.JumpBitsNotSet,
371
+			Val:      0x1221,
372
+			SkipTrue: 1,
373
+		},
374
+		bpf.RetConstant{
375
+			Val: 0,
376
+		},
377
+		bpf.RetConstant{
378
+			Val: 10,
379
+		},
380
+	})
381
+	if err != nil {
382
+		t.Fatalf("failed to load BPF program: %v", err)
383
+	}
384
+
385
+	out, err := vm.Run([]byte{
386
+		0xff, 0xff, 0xff, 0xff,
387
+		0xff, 0xff, 0xff, 0xff,
388
+		0x01, 0x02,
389
+	})
390
+	if err != nil {
391
+		t.Fatalf("unexpected error while running program: %v", err)
392
+	}
393
+	if want, got := uint32(2), out; want != got {
394
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
395
+			want, got)
396
+	}
397
+}
398
+
399
+func TestVMJumpIfXEqual(t *testing.T) {
400
+	vm, err := testVM(t, []bpf.Instruction{
401
+		bpf.LoadAbsolute{
402
+			Off:  8,
403
+			Size: 1,
404
+		},
405
+		bpf.LoadConstant{
406
+			Dst: bpf.RegX,
407
+			Val: 1,
408
+		},
409
+		bpf.JumpIfX{
410
+			Cond:     bpf.JumpEqual,
411
+			SkipTrue: 1,
412
+		},
413
+		bpf.RetConstant{
414
+			Val: 0,
415
+		},
416
+		bpf.RetConstant{
417
+			Val: 9,
418
+		},
419
+	})
420
+	if err != nil {
421
+		t.Fatalf("failed to load BPF program: %v", err)
422
+	}
423
+
424
+	out, err := vm.Run([]byte{
425
+		0xff, 0xff, 0xff, 0xff,
426
+		0xff, 0xff, 0xff, 0xff,
427
+		1,
428
+	})
429
+	if err != nil {
430
+		t.Fatalf("unexpected error while running program: %v", err)
431
+	}
432
+	if want, got := uint32(1), out; want != got {
433
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
434
+			want, got)
435
+	}
436
+}
437
+
438
+func TestVMJumpIfXNotEqual(t *testing.T) {
439
+	vm, err := testVM(t, []bpf.Instruction{
440
+		bpf.LoadAbsolute{
441
+			Off:  8,
442
+			Size: 1,
443
+		},
444
+		bpf.LoadConstant{
445
+			Dst: bpf.RegX,
446
+			Val: 1,
447
+		},
448
+		bpf.JumpIfX{
449
+			Cond:      bpf.JumpNotEqual,
450
+			SkipFalse: 1,
451
+		},
452
+		bpf.RetConstant{
453
+			Val: 0,
454
+		},
455
+		bpf.RetConstant{
456
+			Val: 9,
457
+		},
458
+	})
459
+	if err != nil {
460
+		t.Fatalf("failed to load BPF program: %v", err)
461
+	}
462
+
463
+	out, err := vm.Run([]byte{
464
+		0xff, 0xff, 0xff, 0xff,
465
+		0xff, 0xff, 0xff, 0xff,
466
+		1,
467
+	})
468
+	if err != nil {
469
+		t.Fatalf("unexpected error while running program: %v", err)
470
+	}
471
+	if want, got := uint32(1), out; want != got {
472
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
473
+			want, got)
474
+	}
475
+}
476
+
477
+func TestVMJumpIfXGreaterThan(t *testing.T) {
478
+	vm, err := testVM(t, []bpf.Instruction{
479
+		bpf.LoadAbsolute{
480
+			Off:  8,
481
+			Size: 4,
482
+		},
483
+		bpf.LoadConstant{
484
+			Dst: bpf.RegX,
485
+			Val: 0x00010202,
486
+		},
487
+		bpf.JumpIfX{
488
+			Cond:     bpf.JumpGreaterThan,
489
+			SkipTrue: 1,
490
+		},
491
+		bpf.RetConstant{
492
+			Val: 0,
493
+		},
494
+		bpf.RetConstant{
495
+			Val: 12,
496
+		},
497
+	})
498
+	if err != nil {
499
+		t.Fatalf("failed to load BPF program: %v", err)
500
+	}
501
+
502
+	out, err := vm.Run([]byte{
503
+		0xff, 0xff, 0xff, 0xff,
504
+		0xff, 0xff, 0xff, 0xff,
505
+		0, 1, 2, 3,
506
+	})
507
+	if err != nil {
508
+		t.Fatalf("unexpected error while running program: %v", err)
509
+	}
510
+	if want, got := uint32(4), out; want != got {
511
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
512
+			want, got)
513
+	}
514
+}
515
+
516
+func TestVMJumpIfXLessThan(t *testing.T) {
517
+	vm, err := testVM(t, []bpf.Instruction{
518
+		bpf.LoadAbsolute{
519
+			Off:  8,
520
+			Size: 4,
521
+		},
522
+		bpf.LoadConstant{
523
+			Dst: bpf.RegX,
524
+			Val: 0xff010203,
525
+		},
526
+		bpf.JumpIfX{
527
+			Cond:     bpf.JumpLessThan,
528
+			SkipTrue: 1,
529
+		},
530
+		bpf.RetConstant{
531
+			Val: 0,
532
+		},
533
+		bpf.RetConstant{
534
+			Val: 12,
535
+		},
536
+	})
537
+	if err != nil {
538
+		t.Fatalf("failed to load BPF program: %v", err)
539
+	}
540
+
541
+	out, err := vm.Run([]byte{
542
+		0xff, 0xff, 0xff, 0xff,
543
+		0xff, 0xff, 0xff, 0xff,
544
+		0, 1, 2, 3,
545
+	})
546
+	if err != nil {
547
+		t.Fatalf("unexpected error while running program: %v", err)
548
+	}
549
+	if want, got := uint32(4), out; want != got {
550
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
551
+			want, got)
552
+	}
553
+}
554
+
555
+func TestVMJumpIfXGreaterOrEqual(t *testing.T) {
556
+	vm, err := testVM(t, []bpf.Instruction{
557
+		bpf.LoadAbsolute{
558
+			Off:  8,
559
+			Size: 4,
560
+		},
561
+		bpf.LoadConstant{
562
+			Dst: bpf.RegX,
563
+			Val: 0x00010203,
564
+		},
565
+		bpf.JumpIfX{
566
+			Cond:     bpf.JumpGreaterOrEqual,
567
+			SkipTrue: 1,
568
+		},
569
+		bpf.RetConstant{
570
+			Val: 0,
571
+		},
572
+		bpf.RetConstant{
573
+			Val: 12,
574
+		},
575
+	})
576
+	if err != nil {
577
+		t.Fatalf("failed to load BPF program: %v", err)
578
+	}
579
+
580
+	out, err := vm.Run([]byte{
581
+		0xff, 0xff, 0xff, 0xff,
582
+		0xff, 0xff, 0xff, 0xff,
583
+		0, 1, 2, 3,
584
+	})
585
+	if err != nil {
586
+		t.Fatalf("unexpected error while running program: %v", err)
587
+	}
588
+	if want, got := uint32(4), out; want != got {
589
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
590
+			want, got)
591
+	}
592
+}
593
+
594
+func TestVMJumpIfXLessOrEqual(t *testing.T) {
595
+	vm, err := testVM(t, []bpf.Instruction{
596
+		bpf.LoadAbsolute{
597
+			Off:  8,
598
+			Size: 4,
599
+		},
600
+		bpf.LoadConstant{
601
+			Dst: bpf.RegX,
602
+			Val: 0xff010203,
603
+		},
604
+		bpf.JumpIfX{
605
+			Cond:     bpf.JumpLessOrEqual,
606
+			SkipTrue: 1,
607
+		},
608
+		bpf.RetConstant{
609
+			Val: 0,
610
+		},
611
+		bpf.RetConstant{
612
+			Val: 12,
613
+		},
614
+	})
615
+	if err != nil {
616
+		t.Fatalf("failed to load BPF program: %v", err)
617
+	}
618
+
619
+	out, err := vm.Run([]byte{
620
+		0xff, 0xff, 0xff, 0xff,
621
+		0xff, 0xff, 0xff, 0xff,
622
+		0, 1, 2, 3,
623
+	})
624
+	if err != nil {
625
+		t.Fatalf("unexpected error while running program: %v", err)
626
+	}
627
+	if want, got := uint32(4), out; want != got {
628
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
629
+			want, got)
630
+	}
631
+}
632
+
633
+func TestVMJumpIfXBitsSet(t *testing.T) {
634
+	vm, err := testVM(t, []bpf.Instruction{
635
+		bpf.LoadAbsolute{
636
+			Off:  8,
637
+			Size: 2,
638
+		},
639
+		bpf.LoadConstant{
640
+			Dst: bpf.RegX,
641
+			Val: 0x1122,
642
+		},
643
+		bpf.JumpIfX{
644
+			Cond:     bpf.JumpBitsSet,
645
+			SkipTrue: 1,
646
+		},
647
+		bpf.RetConstant{
648
+			Val: 0,
649
+		},
650
+		bpf.RetConstant{
651
+			Val: 10,
652
+		},
653
+	})
654
+	if err != nil {
655
+		t.Fatalf("failed to load BPF program: %v", err)
656
+	}
657
+
658
+	out, err := vm.Run([]byte{
659
+		0xff, 0xff, 0xff, 0xff,
660
+		0xff, 0xff, 0xff, 0xff,
661
+		0x01, 0x02,
662
+	})
663
+	if err != nil {
664
+		t.Fatalf("unexpected error while running program: %v", err)
665
+	}
666
+	if want, got := uint32(2), out; want != got {
667
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
668
+			want, got)
669
+	}
670
+}
671
+
672
+func TestVMJumpIfXBitsNotSet(t *testing.T) {
673
+	vm, err := testVM(t, []bpf.Instruction{
674
+		bpf.LoadAbsolute{
675
+			Off:  8,
676
+			Size: 2,
677
+		},
678
+		bpf.LoadConstant{
679
+			Dst: bpf.RegX,
680
+			Val: 0x1221,
681
+		},
682
+		bpf.JumpIfX{
683
+			Cond:     bpf.JumpBitsNotSet,
684
+			SkipTrue: 1,
685
+		},
686
+		bpf.RetConstant{
687
+			Val: 0,
688
+		},
689
+		bpf.RetConstant{
690
+			Val: 10,
691
+		},
692
+	})
693
+	if err != nil {
694
+		t.Fatalf("failed to load BPF program: %v", err)
695
+	}
696
+
697
+	out, err := vm.Run([]byte{
698
+		0xff, 0xff, 0xff, 0xff,
699
+		0xff, 0xff, 0xff, 0xff,
700
+		0x01, 0x02,
701
+	})
702
+	if err != nil {
703
+		t.Fatalf("unexpected error while running program: %v", err)
704
+	}
705
+	if want, got := uint32(2), out; want != got {
706
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
707
+			want, got)
708
+	}
709
+}

+ 235
- 0
vm_load_test.go View File

@@ -0,0 +1,235 @@
1
+package bpf_test
2
+
3
+import (
4
+	"net"
5
+	"testing"
6
+
7
+	"golang.org/x/net/bpf"
8
+	"golang.org/x/net/ipv4"
9
+)
10
+
11
+func TestVMLoadAbsoluteOffsetOutOfBounds(t *testing.T) {
12
+	vm, err := testVM(t, []bpf.Instruction{
13
+		bpf.LoadAbsolute{
14
+			Off:  100,
15
+			Size: 2,
16
+		},
17
+		bpf.RetA{},
18
+	})
19
+	if err != nil {
20
+		t.Fatalf("failed to load BPF program: %v", err)
21
+	}
22
+
23
+	out, err := vm.Run([]byte{
24
+		0xff, 0xff, 0xff, 0xff,
25
+		0xff, 0xff, 0xff, 0xff,
26
+		0, 1, 2, 3,
27
+	})
28
+	if err != nil {
29
+		t.Fatalf("unexpected error while running program: %v", err)
30
+	}
31
+	if want, got := uint32(0), out; want != got {
32
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
33
+			want, got)
34
+	}
35
+}
36
+
37
+func TestVMLoadAbsoluteOffsetPlusSizeOutOfBounds(t *testing.T) {
38
+	vm, err := testVM(t, []bpf.Instruction{
39
+		bpf.LoadAbsolute{
40
+			Off:  8,
41
+			Size: 2,
42
+		},
43
+		bpf.RetA{},
44
+	})
45
+	if err != nil {
46
+		t.Fatalf("failed to load BPF program: %v", err)
47
+	}
48
+
49
+	out, err := vm.Run([]byte{
50
+		0xff, 0xff, 0xff, 0xff,
51
+		0xff, 0xff, 0xff, 0xff,
52
+		0,
53
+	})
54
+	if err != nil {
55
+		t.Fatalf("unexpected error while running program: %v", err)
56
+	}
57
+	if want, got := uint32(0), out; want != got {
58
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
59
+			want, got)
60
+	}
61
+}
62
+
63
+func TestVMLoadAbsoluteBadInstructionSize(t *testing.T) {
64
+	_, err := testVM(t, []bpf.Instruction{
65
+		bpf.LoadAbsolute{
66
+			Size: 5,
67
+		},
68
+		bpf.RetA{},
69
+	})
70
+	if errStr(err) != "assembling instruction 1: invalid load byte length 0" {
71
+		t.Fatalf("unexpected error: %v", err)
72
+	}
73
+}
74
+
75
+func TestVMLoadConstantOK(t *testing.T) {
76
+	vm, err := testVM(t, []bpf.Instruction{
77
+		bpf.LoadConstant{
78
+			Dst: bpf.RegX,
79
+			Val: 9,
80
+		},
81
+		bpf.TXA{},
82
+		bpf.RetA{},
83
+	})
84
+	if err != nil {
85
+		t.Fatalf("failed to load BPF program: %v", err)
86
+	}
87
+
88
+	out, err := vm.Run([]byte{
89
+		0xff, 0xff, 0xff, 0xff,
90
+		0xff, 0xff, 0xff, 0xff,
91
+		0,
92
+	})
93
+	if err != nil {
94
+		t.Fatalf("unexpected error while running program: %v", err)
95
+	}
96
+	if want, got := uint32(1), out; want != got {
97
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
98
+			want, got)
99
+	}
100
+}
101
+
102
+func TestVMLoadIndirectOutOfBounds(t *testing.T) {
103
+	vm, err := testVM(t, []bpf.Instruction{
104
+		bpf.LoadIndirect{
105
+			Off:  100,
106
+			Size: 1,
107
+		},
108
+		bpf.RetA{},
109
+	})
110
+	if err != nil {
111
+		t.Fatalf("failed to load BPF program: %v", err)
112
+	}
113
+
114
+	out, err := vm.Run([]byte{
115
+		0xff, 0xff, 0xff, 0xff,
116
+		0xff, 0xff, 0xff, 0xff,
117
+		0,
118
+	})
119
+	if err != nil {
120
+		t.Fatalf("unexpected error while running program: %v", err)
121
+	}
122
+	if want, got := uint32(0), out; want != got {
123
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
124
+			want, got)
125
+	}
126
+}
127
+
128
+func TestVMLoadMemShiftOutOfBounds(t *testing.T) {
129
+	vm, err := testVM(t, []bpf.Instruction{
130
+		bpf.LoadMemShift{
131
+			Off: 100,
132
+		},
133
+		bpf.RetA{},
134
+	})
135
+	if err != nil {
136
+		t.Fatalf("failed to load BPF program: %v", err)
137
+	}
138
+
139
+	out, err := vm.Run([]byte{
140
+		0xff, 0xff, 0xff, 0xff,
141
+		0xff, 0xff, 0xff, 0xff,
142
+		0,
143
+	})
144
+	if err != nil {
145
+		t.Fatalf("unexpected error while running program: %v", err)
146
+	}
147
+	if want, got := uint32(0), out; want != got {
148
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
149
+			want, got)
150
+	}
151
+}
152
+
153
+const (
154
+	dhcp4Port = 53
155
+)
156
+
157
+func TestVMLoadMemShiftLoadIndirectNoResult(t *testing.T) {
158
+	vm, in := testDHCPv4(t)
159
+
160
+	// Append mostly empty UDP header with incorrect DHCPv4 port
161
+	in = append(in, []byte{
162
+		0, 0,
163
+		0, dhcp4Port + 1,
164
+		0, 0,
165
+		0, 0,
166
+	}...)
167
+
168
+	out, err := vm.Run(in)
169
+	if err != nil {
170
+		t.Fatalf("unexpected error while running program: %v", err)
171
+	}
172
+	if want, got := uint32(0), out; want != got {
173
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
174
+			want, got)
175
+	}
176
+}
177
+
178
+func TestVMLoadMemShiftLoadIndirectOK(t *testing.T) {
179
+	vm, in := testDHCPv4(t)
180
+
181
+	// Append mostly empty UDP header with correct DHCPv4 port
182
+	in = append(in, []byte{
183
+		0, 0,
184
+		0, dhcp4Port,
185
+		0, 0,
186
+		0, 0,
187
+	}...)
188
+
189
+	out, err := vm.Run(in)
190
+	if err != nil {
191
+		t.Fatalf("unexpected error while running program: %v", err)
192
+	}
193
+	if want, got := uint32(len(in)-8), out; want != got {
194
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
195
+			want, got)
196
+	}
197
+}
198
+
199
+func testDHCPv4(t *testing.T) (*testVirtualMachine, []byte) {
200
+	// DHCPv4 test data courtesy of David Anderson:
201
+	// https://github.com/google/netboot/blob/master/dhcp4/conn_linux.go#L59-L70
202
+	vm, err := testVM(t, []bpf.Instruction{
203
+		// Load IPv4 packet length
204
+		bpf.LoadMemShift{Off: 8},
205
+		// Get UDP dport
206
+		bpf.LoadIndirect{Off: 8 + 2, Size: 2},
207
+		// Correct dport?
208
+		bpf.JumpIf{Cond: bpf.JumpEqual, Val: dhcp4Port, SkipFalse: 1},
209
+		// Accept
210
+		bpf.RetConstant{Val: 1500},
211
+		// Ignore
212
+		bpf.RetConstant{Val: 0},
213
+	})
214
+	if err != nil {
215
+		t.Fatalf("failed to load BPF program: %v", err)
216
+	}
217
+
218
+	// Minimal requirements to make a valid IPv4 header
219
+	h := &ipv4.Header{
220
+		Len: ipv4.HeaderLen,
221
+		Src: net.IPv4(192, 168, 1, 1),
222
+		Dst: net.IPv4(192, 168, 1, 2),
223
+	}
224
+	hb, err := h.Marshal()
225
+	if err != nil {
226
+		t.Fatalf("failed to marshal IPv4 header: %v", err)
227
+	}
228
+
229
+	hb = append([]byte{
230
+		0xff, 0xff, 0xff, 0xff,
231
+		0xff, 0xff, 0xff, 0xff,
232
+	}, hb...)
233
+
234
+	return vm, hb
235
+}

+ 188
- 0
vm_registers.go View File

@@ -0,0 +1,188 @@
1
+package bpf
2
+
3
+import (
4
+	"encoding/binary"
5
+	"fmt"
6
+
7
+	"golang.org/x/net/bpf"
8
+)
9
+
10
+// Registers for a BPF virtual machine.
11
+type Registers struct {
12
+	PC uint32     // Program Counter
13
+	A  uint32     // Accumulator
14
+	X  uint32     //
15
+	R  [16]uint32 // Scratch
16
+}
17
+
18
+func (reg *Registers) aluOpConstant(ins bpf.ALUOpConstant) {
19
+	reg.A = reg.aluOpCommon(ins.Op, ins.Val)
20
+}
21
+
22
+func (reg *Registers) aluOpX(ins bpf.ALUOpX) bool {
23
+	// Guard against division or modulus by zero by terminating
24
+	// the program, as the OS BPF VM does
25
+	if reg.X == 0 {
26
+		switch ins.Op {
27
+		case bpf.ALUOpDiv, bpf.ALUOpMod:
28
+			reg.A = 0
29
+			return false
30
+		}
31
+	}
32
+	reg.A = reg.aluOpCommon(ins.Op, reg.X)
33
+	return true
34
+}
35
+
36
+func (reg *Registers) aluOpCommon(op bpf.ALUOp, value uint32) uint32 {
37
+	switch op {
38
+	case bpf.ALUOpAdd:
39
+		return reg.A + value
40
+	case bpf.ALUOpSub:
41
+		return reg.A - value
42
+	case bpf.ALUOpMul:
43
+		return reg.A * value
44
+	case bpf.ALUOpDiv:
45
+		return reg.A / value
46
+	case bpf.ALUOpMod:
47
+		return reg.A % value
48
+	case bpf.ALUOpAnd:
49
+		return reg.A & value
50
+	case bpf.ALUOpOr:
51
+		return reg.A | value
52
+	case bpf.ALUOpXor:
53
+		return reg.A ^ value
54
+	case bpf.ALUOpShiftLeft:
55
+		return reg.A << value
56
+	case bpf.ALUOpShiftRight:
57
+		return reg.A >> value
58
+	default:
59
+		return reg.A
60
+	}
61
+}
62
+
63
+func (reg *Registers) jumpIf(ins bpf.JumpIf) {
64
+	reg.jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, ins.Val)
65
+}
66
+
67
+func (reg *Registers) jumpIfX(ins bpf.JumpIfX) {
68
+	reg.jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, reg.X)
69
+}
70
+
71
+func (reg *Registers) jumpIfCommon(cond bpf.JumpTest, skipTrue, skipFalse uint8, value uint32) {
72
+	var ok bool
73
+
74
+	switch cond {
75
+	case bpf.JumpEqual:
76
+		ok = reg.A == value
77
+	case bpf.JumpNotEqual:
78
+		ok = reg.A != value
79
+	case bpf.JumpGreaterThan:
80
+		ok = reg.A > value
81
+	case bpf.JumpLessThan:
82
+		ok = reg.A < value
83
+	case bpf.JumpGreaterOrEqual:
84
+		ok = reg.A >= value
85
+	case bpf.JumpLessOrEqual:
86
+		ok = reg.A <= value
87
+	case bpf.JumpBitsSet:
88
+		ok = (reg.A & value) != 0
89
+	case bpf.JumpBitsNotSet:
90
+		ok = (reg.A & value) == 0
91
+	}
92
+
93
+	if ok {
94
+		reg.PC += uint32(skipTrue)
95
+	} else {
96
+		reg.PC += uint32(skipFalse)
97
+	}
98
+}
99
+
100
+func (reg *Registers) loadAbsolute(ins bpf.LoadAbsolute, in []byte) (ok bool) {
101
+	offset := int(ins.Off)
102
+	size := int(ins.Size)
103
+
104
+	reg.A, ok = loadCommon(in, offset, size)
105
+	return
106
+}
107
+
108
+func (reg *Registers) loadConstant(ins bpf.LoadConstant) {
109
+	switch ins.Dst {
110
+	case bpf.RegA:
111
+		reg.A = ins.Val
112
+	case bpf.RegX:
113
+		reg.X = ins.Val
114
+	}
115
+}
116
+
117
+func (reg *Registers) loadExtension(ins bpf.LoadExtension, in []byte, ext map[bpf.Extension]func(*Registers) uint32) uint32 {
118
+	switch ins.Num {
119
+	case bpf.ExtLen:
120
+		// Builtin
121
+		return uint32(len(in))
122
+
123
+	default:
124
+		if ext != nil {
125
+			if fn, ok := ext[ins.Num]; ok {
126
+				return fn(reg)
127
+			}
128
+		}
129
+		panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
130
+	}
131
+}
132
+
133
+func (reg *Registers) loadIndirect(ins bpf.LoadIndirect, in []byte) (ok bool) {
134
+	offset := int(ins.Off) + int(reg.X)
135
+	size := int(ins.Size)
136
+	reg.A, ok = loadCommon(in, offset, size)
137
+	return
138
+}
139
+
140
+func (reg *Registers) loadMemShift(ins bpf.LoadMemShift, in []byte) (ok bool) {
141
+	offset := int(ins.Off)
142
+	if !inBounds(len(in), offset, 0) {
143
+		return false
144
+	}
145
+
146
+	// Mask off high 4 bits and multiply low 4 bits by 4
147
+	reg.X, ok = uint32(in[offset]&0x0f)*4, true
148
+	return
149
+}
150
+
151
+func inBounds(inLen int, offset int, size int) bool {
152
+	return offset+size <= inLen
153
+}
154
+
155
+func loadCommon(in []byte, offset int, size int) (uint32, bool) {
156
+	if !inBounds(len(in), offset, size) {
157
+		return 0, false
158
+	}
159
+
160
+	switch size {
161
+	case 1:
162
+		return uint32(in[offset]), true
163
+	case 2:
164
+		return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
165
+	case 4:
166
+		return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
167
+	default:
168
+		panic(fmt.Sprintf("invalid load size: %d", size))
169
+	}
170
+}
171
+
172
+func (reg *Registers) loadScratch(ins bpf.LoadScratch) {
173
+	switch ins.Dst {
174
+	case bpf.RegA:
175
+		reg.A = reg.R[ins.N]
176
+	case bpf.RegX:
177
+		reg.X = reg.R[ins.N]
178
+	}
179
+}
180
+
181
+func (reg *Registers) storeScratch(ins bpf.StoreScratch) {
182
+	switch ins.Src {
183
+	case bpf.RegA:
184
+		reg.R[ins.N] = reg.A
185
+	case bpf.RegX:
186
+		reg.R[ins.N] = reg.X
187
+	}
188
+}

+ 111
- 0
vm_ret_test.go View File

@@ -0,0 +1,111 @@
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_test
6
+
7
+import (
8
+	"testing"
9
+
10
+	"golang.org/x/net/bpf"
11
+)
12
+
13
+func TestVMRetA(t *testing.T) {
14
+	vm, err := testVM(t, []bpf.Instruction{
15
+		bpf.LoadAbsolute{
16
+			Off:  8,
17
+			Size: 1,
18
+		},
19
+		bpf.RetA{},
20
+	})
21
+	if err != nil {
22
+		t.Fatalf("failed to load BPF program: %v", err)
23
+	}
24
+
25
+	out, err := vm.Run([]byte{
26
+		0xff, 0xff, 0xff, 0xff,
27
+		0xff, 0xff, 0xff, 0xff,
28
+		9,
29
+	})
30
+	if err != nil {
31
+		t.Fatalf("unexpected error while running program: %v", err)
32
+	}
33
+	if want, got := uint32(1), out; want != got {
34
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
35
+			want, got)
36
+	}
37
+}
38
+
39
+func TestVMRetALargerThanInput(t *testing.T) {
40
+	vm, err := testVM(t, []bpf.Instruction{
41
+		bpf.LoadAbsolute{
42
+			Off:  8,
43
+			Size: 2,
44
+		},
45
+		bpf.RetA{},
46
+	})
47
+	if err != nil {
48
+		t.Fatalf("failed to load BPF program: %v", err)
49
+	}
50
+
51
+	out, err := vm.Run([]byte{
52
+		0xff, 0xff, 0xff, 0xff,
53
+		0xff, 0xff, 0xff, 0xff,
54
+		0, 255,
55
+	})
56
+	if err != nil {
57
+		t.Fatalf("unexpected error while running program: %v", err)
58
+	}
59
+	if want, got := uint32(2), out; want != got {
60
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
61
+			want, got)
62
+	}
63
+}
64
+
65
+func TestVMRetConstant(t *testing.T) {
66
+	vm, err := testVM(t, []bpf.Instruction{
67
+		bpf.RetConstant{
68
+			Val: 9,
69
+		},
70
+	})
71
+	if err != nil {
72
+		t.Fatalf("failed to load BPF program: %v", err)
73
+	}
74
+
75
+	out, err := vm.Run([]byte{
76
+		0xff, 0xff, 0xff, 0xff,
77
+		0xff, 0xff, 0xff, 0xff,
78
+		0, 1,
79
+	})
80
+	if err != nil {
81
+		t.Fatalf("unexpected error while running program: %v", err)
82
+	}
83
+	if want, got := uint32(1), out; want != got {
84
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
85
+			want, got)
86
+	}
87
+}
88
+
89
+func TestVMRetConstantLargerThanInput(t *testing.T) {
90
+	vm, err := testVM(t, []bpf.Instruction{
91
+		bpf.RetConstant{
92
+			Val: 16,
93
+		},
94
+	})
95
+	if err != nil {
96
+		t.Fatalf("failed to load BPF program: %v", err)
97
+	}
98
+
99
+	out, err := vm.Run([]byte{
100
+		0xff, 0xff, 0xff, 0xff,
101
+		0xff, 0xff, 0xff, 0xff,
102
+		0, 1,
103
+	})
104
+	if err != nil {
105
+		t.Fatalf("unexpected error while running program: %v", err)
106
+	}
107
+	if want, got := uint32(2), out; want != got {
108
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
109
+			want, got)
110
+	}
111
+}

+ 245
- 0
vm_scratch_test.go View File

@@ -0,0 +1,245 @@
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_test
6
+
7
+import (
8
+	"testing"
9
+
10
+	"golang.org/x/net/bpf"
11
+)
12
+
13
+func TestVMStoreScratchInvalidScratchRegisterTooSmall(t *testing.T) {
14
+	_, err := testVM(t, []bpf.Instruction{
15
+		bpf.StoreScratch{
16
+			Src: bpf.RegA,
17
+			N:   -1,
18
+		},
19
+		bpf.RetA{},
20
+	})
21
+	if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
22
+		t.Fatalf("unexpected error: %v", err)
23
+	}
24
+}
25
+
26
+func TestVMStoreScratchInvalidScratchRegisterTooLarge(t *testing.T) {
27
+	_, err := testVM(t, []bpf.Instruction{
28
+		bpf.StoreScratch{
29
+			Src: bpf.RegA,
30
+			N:   16,
31
+		},
32
+		bpf.RetA{},
33
+	})
34
+	if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
35
+		t.Fatalf("unexpected error: %v", err)
36
+	}
37
+}
38
+
39
+func TestVMStoreScratchUnknownSourceRegister(t *testing.T) {
40
+	_, err := testVM(t, []bpf.Instruction{
41
+		bpf.StoreScratch{
42
+			Src: 100,
43
+			N:   0,
44
+		},
45
+		bpf.RetA{},
46
+	})
47
+	if errStr(err) != "assembling instruction 1: invalid source register 100" {
48
+		t.Fatalf("unexpected error: %v", err)
49
+	}
50
+}
51
+
52
+func TestVMLoadScratchInvalidScratchRegisterTooSmall(t *testing.T) {
53
+	_, err := testVM(t, []bpf.Instruction{
54
+		bpf.LoadScratch{
55
+			Dst: bpf.RegX,
56
+			N:   -1,
57
+		},
58
+		bpf.RetA{},
59
+	})
60
+	if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
61
+		t.Fatalf("unexpected error: %v", err)
62
+	}
63
+}
64
+
65
+func TestVMLoadScratchInvalidScratchRegisterTooLarge(t *testing.T) {
66
+	_, err := testVM(t, []bpf.Instruction{
67
+		bpf.LoadScratch{
68
+			Dst: bpf.RegX,
69
+			N:   16,
70
+		},
71
+		bpf.RetA{},
72
+	})
73
+	if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
74
+		t.Fatalf("unexpected error: %v", err)
75
+	}
76
+}
77
+
78
+func TestVMLoadScratchUnknownDestinationRegister(t *testing.T) {
79
+	_, err := testVM(t, []bpf.Instruction{
80
+		bpf.LoadScratch{
81
+			Dst: 100,
82
+			N:   0,
83
+		},
84
+		bpf.RetA{},
85
+	})
86
+	if errStr(err) != "assembling instruction 1: invalid target register 100" {
87
+		t.Fatalf("unexpected error: %v", err)
88
+	}
89
+}
90
+
91
+func TestVMStoreScratchLoadScratchOneValue(t *testing.T) {
92
+	vm, err := testVM(t, []bpf.Instruction{
93
+		// Load byte 255
94
+		bpf.LoadAbsolute{
95
+			Off:  8,
96
+			Size: 1,
97
+		},
98
+		// Copy to X and store in scratch[0]
99
+		bpf.TAX{},
100
+		bpf.StoreScratch{
101
+			Src: bpf.RegX,
102
+			N:   0,
103
+		},
104
+		// Load byte 1
105
+		bpf.LoadAbsolute{
106
+			Off:  9,
107
+			Size: 1,
108
+		},
109
+		// Overwrite 1 with 255 from scratch[0]
110
+		bpf.LoadScratch{
111
+			Dst: bpf.RegA,
112
+			N:   0,
113
+		},
114
+		// Return 255
115
+		bpf.RetA{},
116
+	})
117
+	if err != nil {
118
+		t.Fatalf("failed to load BPF program: %v", err)
119
+	}
120
+
121
+	out, err := vm.Run([]byte{
122
+		0xff, 0xff, 0xff, 0xff,
123
+		0xff, 0xff, 0xff, 0xff,
124
+		255, 1, 2,
125
+	})
126
+	if err != nil {
127
+		t.Fatalf("unexpected error while running program: %v", err)
128
+	}
129
+	if want, got := uint32(3), out; want != got {
130
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
131
+			want, got)
132
+	}
133
+}
134
+
135
+func TestVMStoreScratchLoadScratchMultipleValues(t *testing.T) {
136
+	vm, err := testVM(t, []bpf.Instruction{
137
+		// Load byte 10
138
+		bpf.LoadAbsolute{
139
+			Off:  8,
140
+			Size: 1,
141
+		},
142
+		// Store in scratch[0]
143
+		bpf.StoreScratch{
144
+			Src: bpf.RegA,
145
+			N:   0,
146
+		},
147
+		// Load byte 20
148
+		bpf.LoadAbsolute{
149
+			Off:  9,
150
+			Size: 1,
151
+		},
152
+		// Store in scratch[1]
153
+		bpf.StoreScratch{
154
+			Src: bpf.RegA,
155
+			N:   1,
156
+		},
157
+		// Load byte 30
158
+		bpf.LoadAbsolute{
159
+			Off:  10,
160
+			Size: 1,
161
+		},
162
+		// Store in scratch[2]
163
+		bpf.StoreScratch{
164
+			Src: bpf.RegA,
165
+			N:   2,
166
+		},
167
+		// Load byte 1
168
+		bpf.LoadAbsolute{
169
+			Off:  11,
170
+			Size: 1,
171
+		},
172
+		// Store in scratch[3]
173
+		bpf.StoreScratch{
174
+			Src: bpf.RegA,
175
+			N:   3,
176
+		},
177
+		// Load in byte 10 to X
178
+		bpf.LoadScratch{
179
+			Dst: bpf.RegX,
180
+			N:   0,
181
+		},
182
+		// Copy X -> A
183
+		bpf.TXA{},
184
+		// Verify value is 10
185
+		bpf.JumpIf{
186
+			Cond:     bpf.JumpEqual,
187
+			Val:      10,
188
+			SkipTrue: 1,
189
+		},
190
+		// Fail test if incorrect
191
+		bpf.RetConstant{
192
+			Val: 0,
193
+		},
194
+		// Load in byte 20 to A
195
+		bpf.LoadScratch{
196
+			Dst: bpf.RegA,
197
+			N:   1,
198
+		},
199
+		// Verify value is 20
200
+		bpf.JumpIf{
201
+			Cond:     bpf.JumpEqual,
202
+			Val:      20,
203
+			SkipTrue: 1,
204
+		},
205
+		// Fail test if incorrect
206
+		bpf.RetConstant{
207
+			Val: 0,
208
+		},
209
+		// Load in byte 30 to A
210
+		bpf.LoadScratch{
211
+			Dst: bpf.RegA,
212
+			N:   2,
213
+		},
214
+		// Verify value is 30
215
+		bpf.JumpIf{
216
+			Cond:     bpf.JumpEqual,
217
+			Val:      30,
218
+			SkipTrue: 1,
219
+		},
220
+		// Fail test if incorrect
221
+		bpf.RetConstant{
222
+			Val: 0,
223
+		},
224
+		// Return first two bytes on success
225
+		bpf.RetConstant{
226
+			Val: 10,
227
+		},
228
+	})
229
+	if err != nil {
230
+		t.Fatalf("failed to load BPF program: %v", err)
231
+	}
232
+
233
+	out, err := vm.Run([]byte{
234
+		0xff, 0xff, 0xff, 0xff,
235
+		0xff, 0xff, 0xff, 0xff,
236
+		10, 20, 30, 1,
237
+	})
238
+	if err != nil {
239
+		t.Fatalf("unexpected error while running program: %v", err)
240
+	}
241
+	if want, got := uint32(2), out; want != got {
242
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
243
+			want, got)
244
+	}
245
+}

+ 58
- 0
vm_test.go View File

@@ -0,0 +1,58 @@
1
+package bpf_test
2
+
3
+import (
4
+	"testing"
5
+
6
+	gobpf "golang.org/x/net/bpf"
7
+	"maze.io/x/bpf"
8
+)
9
+
10
+// udpHeaderLen is the length of a UDP header.
11
+const udpHeaderLen = 8
12
+
13
+type testVirtualMachine struct {
14
+	*bpf.VM
15
+}
16
+
17
+func (vm *testVirtualMachine) Run(in []byte) (uint32, error) {
18
+	out, err := vm.VM.Run(in)
19
+
20
+	// All tests have a UDP header as part of input, because the OS VM
21
+	// packets always will. For the Go VM, this output is trimmed before
22
+	// being sent back to tests.
23
+	if out >= udpHeaderLen {
24
+		out -= udpHeaderLen
25
+	}
26
+
27
+	// If Go output is larger than the size of the packet, packet filtering
28
+	// interop tests must trim the output bytes to the length of the packet.
29
+	// The BPF VM should not do this on its own, as other uses of it do
30
+	// not trim the output byte count.
31
+	trim := uint32(len(in) - udpHeaderLen)
32
+	if out > trim {
33
+		out = trim
34
+	}
35
+
36
+	return out, err
37
+}
38
+
39
+func testVM(t *testing.T, instructions []gobpf.Instruction) (*testVirtualMachine, error) {
40
+	t.Helper()
41
+
42
+	v := bpf.NewVM(instructions)
43
+	if err := v.Verify(); err != nil {
44
+		return nil, err
45
+	}
46
+
47
+	return &testVirtualMachine{v}, nil
48
+}
49
+
50
+// errStr returns the string representation of an error, or
51
+// "<nil>" if it is nil.
52
+func errStr(err error) string {
53
+	if err == nil {
54
+		return "<nil>"
55
+	}
56
+
57
+	return err.Error()
58
+}

Loading…
Cancel
Save