Browse Source

Added Program.Verify method

master
maze 11 months ago
parent
commit
4cc007b945
4 changed files with 96 additions and 63 deletions
  1. +0
    -7
      assembler.go
  2. +9
    -0
      cmd/bpfasm/main.go
  3. +76
    -0
      program.go
  4. +11
    -56
      vm.go

+ 0
- 7
assembler.go View File

@@ -16,13 +16,6 @@ import (
"maze.io/x/bpf/internal/parser"
)

// Program is an assembled program.
type Program []bpf.Instruction

func (p Program) Assemble() ([]bpf.RawInstruction, error) {
return bpf.Assemble(p)
}

// Assemble BPF instructions from source.
func Assemble(source string) (Program, error) {
var (

+ 9
- 0
cmd/bpfasm/main.go View File

@@ -22,6 +22,12 @@ The C output format pretty prints a C-like construct.
Disassembler

Disassembles a Berkeley Packet Filter binary to assembler or C format.


A note on the use of labels

The Berkeley Packet Filter only allows for positive offsets in jumps, so
jumping to a previously defined label will result in an error.
*/
package main

@@ -186,6 +192,9 @@ func assemble(ctx *cli.Context) error {
if err != nil {
fmt.Fprintf(os.Stderr, "error assembling %s: %v\n", sourcePath, err)
os.Exit(1)
} else if err = program.Verify(); err != nil {
fmt.Fprintf(os.Stderr, "error verifying program: %v\n", err)
os.Exit(1)
}

var output io.WriteCloser

+ 76
- 0
program.go View File

@@ -0,0 +1,76 @@
package bpf

import (
"errors"
"fmt"

"golang.org/x/net/bpf"
)

// Program is an assembled program.
type Program []bpf.Instruction

func (p Program) Assemble() ([]bpf.RawInstruction, error) {
return bpf.Assemble(p)
}

// Verify the program doing bounary checks and detecting division by zero
// errors.
func (p Program) Verify() error {
l := len(p)
if l == 0 {
return errors.New("bpf: no instructions in program")
}

for i, ins := range p {
check := l - (i + 1)
switch ins := ins.(type) {
// Check for out-of-bounds jumps in instructions
case bpf.Jump:
if check <= int(ins.Skip) {
return fmt.Errorf("bpf: cannot jump %d instructions; jumping past program bounds", ins.Skip)
}

case bpf.JumpIf:
if check <= int(ins.SkipTrue) {
return fmt.Errorf("bpf: cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
}
if check <= int(ins.SkipFalse) {
return fmt.Errorf("bpf: cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
}

case bpf.JumpIfX:
if check <= int(ins.SkipTrue) {
return fmt.Errorf("bpf: cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
}
if check <= int(ins.SkipFalse) {
return fmt.Errorf("bpf: cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
}

// Check for division or modulus by zero
case bpf.ALUOpConstant:
if ins.Val != 0 {
break
}

switch ins.Op {
case bpf.ALUOpDiv, bpf.ALUOpMod:
return errors.New("cannot divide by zero using ALUOpConstant")
}
}
}

// Make sure last instruction is a return instruction
switch p[l-1].(type) {
case bpf.RetA, bpf.RetConstant:
default:
return 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 := bpf.Assemble(p)

return err
}

+ 11
- 56
vm.go View File

@@ -6,7 +6,6 @@
package bpf

import (
"errors"
"fmt"

"golang.org/x/net/bpf"
@@ -29,70 +28,26 @@ func NewVM(program Program) *VM {

// Verify runs sanity checks on the loaded program.
func (vm *VM) Verify() error {
if len(vm.program) == 0 {
return errors.New("bpf: no instructions in program")
if err := vm.program.Verify(); err != nil {
return err
}

for i, ins := range vm.program {
check := len(vm.program) - (i + 1)
switch ins := ins.(type) {
// Check for out-of-bounds jumps in instructions
case bpf.Jump:
if check <= int(ins.Skip) {
return fmt.Errorf("bpf: cannot jump %d instructions; jumping past program bounds", ins.Skip)
}
case bpf.JumpIf:
if check <= int(ins.SkipTrue) {
return fmt.Errorf("bpf: cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
}
if check <= int(ins.SkipFalse) {
return fmt.Errorf("bpf: cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
}
case bpf.JumpIfX:
if check <= int(ins.SkipTrue) {
return fmt.Errorf("bpf: cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
}
if check <= int(ins.SkipFalse) {
return fmt.Errorf("bpf: cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
}
// Check for division or modulus by zero
case bpf.ALUOpConstant:
if ins.Val != 0 {
break
}

switch ins.Op {
case bpf.ALUOpDiv, bpf.ALUOpMod:
return errors.New("cannot divide by zero using ALUOpConstant")
}
// Check for unknown extensions
case bpf.LoadExtension:
switch ins.Num {
// Verify extensions.
for _, ins := range vm.program {
if ext, ok := ins.(bpf.LoadExtension); ok {
switch ext.Num {
case bpf.ExtLen:
continue

default:
if vm.Extensions != nil {
if _, ok := vm.Extensions[ins.Num]; ok {
continue
}
if _, ok = vm.Extensions[ext.Num]; !ok {
return fmt.Errorf("extension %d is not available", ext.Num)
}
return fmt.Errorf("extension %d not implemented", ins.Num)
}
}
}

// Make sure last instruction is a return instruction
switch vm.program[len(vm.program)-1].(type) {
case bpf.RetA, bpf.RetConstant:
default:
return 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 := bpf.Assemble(vm.program)

return err
return nil
}

func (vm *VM) Run(in []byte) (verdict uint32, err error) {

Loading…
Cancel
Save