mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 23:52:26 -05:00 
			
		
		
		
	
		
			
	
	
		
			151 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			151 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | // 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) | ||
|  | 			} | ||
|  | 		case JumpIfX: | ||
|  | 			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 JumpIfX: | ||
|  | 			jump := jumpIfX(ins, regA, regX) | ||
|  | 			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 | ||
|  | } |