github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/emulator/helper_functions.go (about)

     1  package emulator
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/dylandreimerink/gobpfld/bpfsys"
     8  	"github.com/dylandreimerink/gobpfld/bpftypes"
     9  	"github.com/dylandreimerink/gobpfld/ebpf"
    10  )
    11  
    12  // HelperFunc are functions in go space which can be called from the eBPF VM. They are used expand eBPF capabilities
    13  // without giving the VM direct access, much like a syscall in an OS context.
    14  // A helper function by convention will return a single value in R0, is passed R1-R5 as arguments and should never
    15  // touch R6-R9. A helper can gracefully return an error via R0, returning an error from the Go function means there
    16  // is no graceful way to handle the error and will cause the VM to abort execution.
    17  type HelperFunc func(vm *VM) error
    18  
    19  // LinuxHelperFunctions returns a helper function array of helper functions which are compatible with the linux
    20  // helper functions as defined in https://github.com/libbpf/libbpf/blob/master/src/bpf_helper_defs.h
    21  func LinuxHelperFunctions() []HelperFunc {
    22  	const maxLinuxHelperNum = 191
    23  	funcs := make([]HelperFunc, maxLinuxHelperNum+1)
    24  
    25  	// Helper func 0 doesn't exist
    26  	funcs[0] = nil
    27  	funcs[1] = MapLookupElement
    28  	funcs[2] = MapUpdateElement
    29  	funcs[3] = MapDeleteElement
    30  	// ...
    31  	funcs[12] = TailCall
    32  	// ...
    33  	funcs[14] = GetCurrentPidTgid
    34  	// ...
    35  	funcs[25] = PerfEventOutput
    36  	// ...
    37  	funcs[87] = MapPushElement
    38  	funcs[88] = MapPopElement
    39  	funcs[89] = MapPeekElement
    40  	// ...191
    41  
    42  	return funcs
    43  }
    44  
    45  // MapLookupElement implements the bpf_map_lookup_element helper
    46  func MapLookupElement(vm *VM) error {
    47  	// R1 = id/fd of the map, R2 = pointer to key value
    48  	m, err := regToMap(vm, vm.Registers.R1)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	if m == nil {
    53  		return nil
    54  	}
    55  
    56  	val, err := m.Lookup(vm.Registers.R2)
    57  	if err != nil {
    58  		switch err {
    59  		case errMapKeyNoPtr, errMapValNoPtr:
    60  			val = efault()
    61  		case errMapOutOfMemory:
    62  			val = e2big()
    63  		case errMapNotImplemented:
    64  			val = eperm()
    65  		default:
    66  			return fmt.Errorf("lookup: %w", err)
    67  		}
    68  	}
    69  
    70  	vm.Registers.R0 = val
    71  
    72  	return nil
    73  }
    74  
    75  // MapUpdateElement implements the bpf_map_update_element helper
    76  func MapUpdateElement(vm *VM) error {
    77  	m, err := regToMap(vm, vm.Registers.R1)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	if m == nil {
    82  		return nil
    83  	}
    84  
    85  	val, err := m.Update(vm.Registers.R2, vm.Registers.R3, bpfsys.BPFAttrMapElemFlags(vm.Registers.R4.Value()))
    86  	if err != nil {
    87  		switch err {
    88  		case errMapKeyNoPtr, errMapValNoPtr:
    89  			val = efault()
    90  		case errMapOutOfMemory:
    91  			val = e2big()
    92  		case errMapNotImplemented:
    93  			val = eperm()
    94  		default:
    95  			return fmt.Errorf("update: %w", err)
    96  		}
    97  	}
    98  
    99  	vm.Registers.R0 = val
   100  	return nil
   101  }
   102  
   103  // MapDeleteElement implements the bpf_map_delete_element helper
   104  func MapDeleteElement(vm *VM) error {
   105  	return errors.New("not yet implemented")
   106  }
   107  
   108  // Convert a register values passed into a helper to the actual map
   109  func regToMap(vm *VM, reg RegisterValue) (Map, error) {
   110  	mapIdx := reg.Value()
   111  
   112  	// If R1 is a pointer, not a value, we should dereference it.
   113  	// Pointers can still be valid, usually they are passed when the id/fd comes from a map-in-map type map.
   114  	if ptr, ok := reg.(*MemoryPtr); ok {
   115  		// Deref as 32 bit, which is always the size of an id/fd
   116  		mapIdxVal, err := ptr.Deref(0, ebpf.BPF_W)
   117  		if err != nil {
   118  			return nil, fmt.Errorf("deref ptr to map idx: %w", err)
   119  		}
   120  
   121  		mapIdx = mapIdxVal.Value()
   122  	}
   123  
   124  	if mapIdx < 1 || int(mapIdx) >= len(vm.Maps) {
   125  		vm.Registers.R0 = newIMM(0)
   126  		return nil, nil
   127  	}
   128  
   129  	return vm.Maps[mapIdx], nil
   130  }
   131  
   132  // TailCall implements the bpf_tail_call helper
   133  func TailCall(vm *VM) error {
   134  	// R1 = ctx, R2 = map fd(of prog_array), R3 = index in map (key for the program to execute)
   135  	mapIdx := vm.Registers.R2.Value()
   136  	if mapIdx < 1 || int(mapIdx) >= len(vm.Maps) {
   137  		vm.Registers.R0 = efault()
   138  		return nil
   139  	}
   140  
   141  	// This helper only works for PROG arrays
   142  	m := vm.Maps[mapIdx]
   143  	if m.GetDef().Type != bpftypes.BPF_MAP_TYPE_PROG_ARRAY {
   144  		vm.Registers.R0 = efault()
   145  		return nil
   146  	}
   147  
   148  	k := &MemoryPtr{
   149  		Memory: &ValueMemory{
   150  			Mapping: []RegisterValue{
   151  				vm.Registers.R3,
   152  				vm.Registers.R3,
   153  				vm.Registers.R3,
   154  				vm.Registers.R3,
   155  			},
   156  		},
   157  	}
   158  
   159  	// Lookup the value
   160  	val := newIMM(0)
   161  	valReg, err := m.Lookup(k)
   162  	if err != nil {
   163  		switch err {
   164  		case errMapKeyNoPtr, errMapValNoPtr:
   165  			val = efault()
   166  		case errMapOutOfMemory:
   167  			val = e2big()
   168  		case errMapNotImplemented:
   169  			val = eperm()
   170  		default:
   171  			return fmt.Errorf("lookup: %w", err)
   172  		}
   173  
   174  		vm.Registers.R0 = val
   175  		return nil
   176  	}
   177  
   178  	valPtr, ok := valReg.(PointerValue)
   179  	if !ok {
   180  		return fmt.Errorf("lookup didn't return a pointer")
   181  	}
   182  
   183  	valVar, err := valPtr.Deref(0, ebpf.BPF_W)
   184  	if err != nil {
   185  		return fmt.Errorf("deref value pointer: %w", err)
   186  	}
   187  
   188  	progIdx := valVar.Value()
   189  	if len(vm.Programs) < int(progIdx) {
   190  		vm.Registers.R0 = efault()
   191  		return nil
   192  	}
   193  
   194  	if progIdx == 0 {
   195  		return fmt.Errorf("no program loaded at index 0")
   196  	}
   197  
   198  	// On success, change the current program index
   199  	vm.Registers.PI = int(progIdx)
   200  	// Change the instruction pointer to -1, since after this helper call the PC will be incremented so we will end
   201  	// up at a PC of 0
   202  	vm.Registers.PC = -1
   203  	// Don't reset the VM since we want to preserve the memory and register state.
   204  	// Not that, because the ctx was passed as the first argument to this function it lives in the R1 register
   205  	// where the next program will expect it to be, this is free, no need to manually set the correct ctx.
   206  
   207  	// Set the return value to 0, for success
   208  	vm.Registers.R0 = val
   209  	return nil
   210  }
   211  
   212  // GetCurrentPidTgid implements the bpf_get_current_pid_tgid helper
   213  func GetCurrentPidTgid(vm *VM) error {
   214  	// TODO replace const value with value gotten from dynamic context of VM as soon as this feature is added.
   215  	return vm.Registers.Assign(ebpf.BPF_REG_0, newIMM(1234<<32+5678))
   216  }
   217  
   218  // PerfEventOutput implements the bpf_perf_event_output helper
   219  func PerfEventOutput(vm *VM) error {
   220  	// R1 = ctx, R2 = map index, R3 = flags, R4 = data, R5 = size
   221  	mapIdx := vm.Registers.R2.Value()
   222  	if mapIdx < 1 || int(mapIdx) >= len(vm.Maps) {
   223  		vm.Registers.R0 = efault()
   224  		return nil
   225  	}
   226  
   227  	m := vm.Maps[mapIdx]
   228  	pa, ok := m.(*PerfEventArray)
   229  	if !ok {
   230  		vm.Registers.R0 = efault()
   231  		return nil
   232  	}
   233  
   234  	val := newIMM(0)
   235  	err := pa.Push(vm.Registers.R4, vm.Registers.R5.Value())
   236  	if err != nil {
   237  		switch err {
   238  		case errMapKeyNoPtr, errMapValNoPtr:
   239  			val = efault()
   240  		case errMapOutOfMemory:
   241  			val = e2big()
   242  		case errMapNotImplemented:
   243  			val = eperm()
   244  		default:
   245  			return fmt.Errorf("push: %w", err)
   246  		}
   247  	}
   248  
   249  	vm.Registers.R0 = val
   250  
   251  	return nil
   252  }
   253  
   254  // MapPushElement implements the bpf_map_push_elem helper
   255  func MapPushElement(vm *VM) error {
   256  	m, err := regToMap(vm, vm.Registers.R1)
   257  	if err != nil {
   258  		return err
   259  	}
   260  	if m == nil {
   261  		return nil
   262  	}
   263  
   264  	val := newIMM(0)
   265  	err = m.Push(vm.Registers.R2, int64(m.GetDef().ValueSize))
   266  	if err != nil {
   267  		switch err {
   268  		case errMapKeyNoPtr, errMapValNoPtr:
   269  			val = efault()
   270  		case errMapOutOfMemory:
   271  			val = e2big()
   272  		case errMapNotImplemented:
   273  			val = eperm()
   274  		default:
   275  			return fmt.Errorf("push: %w", err)
   276  		}
   277  	}
   278  
   279  	vm.Registers.R0 = val
   280  	return nil
   281  }
   282  
   283  // MapPopElement implements the bpf_map_pop_element helper
   284  func MapPopElement(vm *VM) error {
   285  	// R1 = id/fd of the map, R2 = pointer to value
   286  	m, err := regToMap(vm, vm.Registers.R1)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	if m == nil {
   291  		return nil
   292  	}
   293  
   294  	vm.Registers.R0 = newIMM(0)
   295  	val, err := m.Pop()
   296  	if err != nil {
   297  		switch err {
   298  		case errMapKeyNoPtr, errMapValNoPtr:
   299  			vm.Registers.R0 = efault()
   300  		case errMapOutOfMemory:
   301  			vm.Registers.R0 = e2big()
   302  		case errMapNotImplemented:
   303  			vm.Registers.R0 = eperm()
   304  		default:
   305  			return fmt.Errorf("pop: %w", err)
   306  		}
   307  
   308  		return nil
   309  	}
   310  
   311  	switch valPtr := vm.Registers.R2.(type) {
   312  	case *MemoryPtr:
   313  		// Memory pointers point to the start of a memory block
   314  		err := valPtr.Memory.Write(int(valPtr.Offset), val, ebpf.BPF_DW)
   315  		if err != nil {
   316  			return fmt.Errorf("write memory: %w", err)
   317  		}
   318  
   319  	case *FramePointer:
   320  		// Frame pointers point to the end of a stack frame
   321  		err := valPtr.Memory.Write(valPtr.Memory.Size()+int(valPtr.Offset), val, ebpf.BPF_DW)
   322  		if err != nil {
   323  			return fmt.Errorf("write memory: %w", err)
   324  		}
   325  
   326  	default:
   327  		vm.Registers.R0 = efault()
   328  		return nil
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  // MapPeekElement implements the bpf_map_peek_element helper
   335  func MapPeekElement(vm *VM) error {
   336  	// R1 = id/fd of the map, R2 = pointer to value
   337  	m, err := regToMap(vm, vm.Registers.R1)
   338  	if err != nil {
   339  		return err
   340  	}
   341  	if m == nil {
   342  		return nil
   343  	}
   344  
   345  	key := newIMM(0)
   346  	val, err := m.Lookup(&MemoryPtr{
   347  		Memory: &ValueMemory{
   348  			Mapping: []RegisterValue{
   349  				key,
   350  				key,
   351  				key,
   352  				key,
   353  			},
   354  		},
   355  	})
   356  	ret := newIMM(0)
   357  	if err != nil {
   358  		switch err {
   359  		case errMapKeyNoPtr, errMapValNoPtr:
   360  			ret = efault()
   361  		case errMapOutOfMemory:
   362  			ret = e2big()
   363  		case errMapNotImplemented:
   364  			ret = eperm()
   365  		default:
   366  			return fmt.Errorf("peek: %w", err)
   367  		}
   368  	}
   369  
   370  	vm.Registers.R0 = ret
   371  	vm.Registers.R2 = val
   372  
   373  	return nil
   374  }