github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/bpf/interpreter.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bpf
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    21  )
    22  
    23  // Possible values for ProgramError.Code.
    24  const (
    25  	// DivisionByZero indicates that a program contains, or executed, a
    26  	// division or modulo by zero.
    27  	DivisionByZero = iota
    28  
    29  	// InvalidEndOfProgram indicates that the last instruction of a program is
    30  	// not a return.
    31  	InvalidEndOfProgram
    32  
    33  	// InvalidInstructionCount indicates that a program has zero instructions
    34  	// or more than MaxInstructions instructions.
    35  	InvalidInstructionCount
    36  
    37  	// InvalidJumpTarget indicates that a program contains a jump whose target
    38  	// is outside of the program's bounds.
    39  	InvalidJumpTarget
    40  
    41  	// InvalidLoad indicates that a program executed an invalid load of input
    42  	// data.
    43  	InvalidLoad
    44  
    45  	// InvalidOpcode indicates that a program contains an instruction with an
    46  	// invalid opcode.
    47  	InvalidOpcode
    48  
    49  	// InvalidRegister indicates that a program contains a load from, or store
    50  	// to, a non-existent M register (index >= ScratchMemRegisters).
    51  	InvalidRegister
    52  )
    53  
    54  // Error is an error encountered while compiling or executing a BPF program.
    55  type Error struct {
    56  	// Code indicates the kind of error that occurred.
    57  	Code int
    58  
    59  	// PC is the program counter (index into the list of instructions) at which
    60  	// the error occurred.
    61  	PC int
    62  }
    63  
    64  func (e Error) codeString() string {
    65  	switch e.Code {
    66  	case DivisionByZero:
    67  		return "division by zero"
    68  	case InvalidEndOfProgram:
    69  		return "last instruction must be a return"
    70  	case InvalidInstructionCount:
    71  		return "invalid number of instructions"
    72  	case InvalidJumpTarget:
    73  		return "jump target out of bounds"
    74  	case InvalidLoad:
    75  		return "load out of bounds or violates input alignment requirements"
    76  	case InvalidOpcode:
    77  		return "invalid instruction opcode"
    78  	case InvalidRegister:
    79  		return "invalid M register"
    80  	default:
    81  		return "unknown error"
    82  	}
    83  }
    84  
    85  // Error implements error.Error.
    86  func (e Error) Error() string {
    87  	return fmt.Sprintf("at l%d: %s", e.PC, e.codeString())
    88  }
    89  
    90  // Program is a BPF program that has been validated for consistency.
    91  //
    92  // +stateify savable
    93  type Program struct {
    94  	instructions []linux.BPFInstruction
    95  }
    96  
    97  // Length returns the number of instructions in the program.
    98  func (p Program) Length() int {
    99  	return len(p.instructions)
   100  }
   101  
   102  // Compile performs validation on a sequence of BPF instructions before
   103  // wrapping them in a Program.
   104  func Compile(insns []linux.BPFInstruction) (Program, error) {
   105  	if len(insns) == 0 || len(insns) > MaxInstructions {
   106  		return Program{}, Error{InvalidInstructionCount, len(insns)}
   107  	}
   108  
   109  	// The last instruction must be a return.
   110  	if last := insns[len(insns)-1]; last.OpCode != (Ret|K) && last.OpCode != (Ret|A) {
   111  		return Program{}, Error{InvalidEndOfProgram, len(insns) - 1}
   112  	}
   113  
   114  	// Validate each instruction. Note that we skip a validation Linux does:
   115  	// Linux additionally verifies that every load from an M register is
   116  	// preceded, in every path, by a store to the same M register, in order to
   117  	// avoid having to clear M between programs
   118  	// (net/core/filter.c:check_load_and_stores). We always start with a zeroed
   119  	// M array.
   120  	for pc, i := range insns {
   121  		if i.OpCode&unusedBitsMask != 0 {
   122  			return Program{}, Error{InvalidOpcode, pc}
   123  		}
   124  		switch i.OpCode & instructionClassMask {
   125  		case Ld:
   126  			mode := i.OpCode & loadModeMask
   127  			switch i.OpCode & loadSizeMask {
   128  			case W:
   129  				if mode != Imm && mode != Abs && mode != Ind && mode != Mem && mode != Len {
   130  					return Program{}, Error{InvalidOpcode, pc}
   131  				}
   132  				if mode == Mem && i.K >= ScratchMemRegisters {
   133  					return Program{}, Error{InvalidRegister, pc}
   134  				}
   135  			case H, B:
   136  				if mode != Abs && mode != Ind {
   137  					return Program{}, Error{InvalidOpcode, pc}
   138  				}
   139  			default:
   140  				return Program{}, Error{InvalidOpcode, pc}
   141  			}
   142  		case Ldx:
   143  			mode := i.OpCode & loadModeMask
   144  			switch i.OpCode & loadSizeMask {
   145  			case W:
   146  				if mode != Imm && mode != Mem && mode != Len {
   147  					return Program{}, Error{InvalidOpcode, pc}
   148  				}
   149  				if mode == Mem && i.K >= ScratchMemRegisters {
   150  					return Program{}, Error{InvalidRegister, pc}
   151  				}
   152  			case B:
   153  				if mode != Msh {
   154  					return Program{}, Error{InvalidOpcode, pc}
   155  				}
   156  			default:
   157  				return Program{}, Error{InvalidOpcode, pc}
   158  			}
   159  		case St, Stx:
   160  			if i.OpCode&storeUnusedBitsMask != 0 {
   161  				return Program{}, Error{InvalidOpcode, pc}
   162  			}
   163  			if i.K >= ScratchMemRegisters {
   164  				return Program{}, Error{InvalidRegister, pc}
   165  			}
   166  		case Alu:
   167  			switch i.OpCode & aluMask {
   168  			case Add, Sub, Mul, Or, And, Lsh, Rsh, Xor:
   169  				break
   170  			case Div, Mod:
   171  				if src := i.OpCode & srcAluJmpMask; src == K && i.K == 0 {
   172  					return Program{}, Error{DivisionByZero, pc}
   173  				}
   174  			case Neg:
   175  				// Negation doesn't take a source operand.
   176  				if i.OpCode&srcAluJmpMask != 0 {
   177  					return Program{}, Error{InvalidOpcode, pc}
   178  				}
   179  			default:
   180  				return Program{}, Error{InvalidOpcode, pc}
   181  			}
   182  		case Jmp:
   183  			switch i.OpCode & jmpMask {
   184  			case Ja:
   185  				// Unconditional jump doesn't take a source operand.
   186  				if i.OpCode&srcAluJmpMask != 0 {
   187  					return Program{}, Error{InvalidOpcode, pc}
   188  				}
   189  				// Do the comparison in 64 bits to avoid the possibility of
   190  				// overflow from a very large i.K.
   191  				if uint64(pc)+uint64(i.K)+1 >= uint64(len(insns)) {
   192  					return Program{}, Error{InvalidJumpTarget, pc}
   193  				}
   194  			case Jeq, Jgt, Jge, Jset:
   195  				// jt and jf are uint16s, so there's no threat of overflow.
   196  				if pc+int(i.JumpIfTrue)+1 >= len(insns) {
   197  					return Program{}, Error{InvalidJumpTarget, pc}
   198  				}
   199  				if pc+int(i.JumpIfFalse)+1 >= len(insns) {
   200  					return Program{}, Error{InvalidJumpTarget, pc}
   201  				}
   202  			default:
   203  				return Program{}, Error{InvalidOpcode, pc}
   204  			}
   205  		case Ret:
   206  			if i.OpCode&retUnusedBitsMask != 0 {
   207  				return Program{}, Error{InvalidOpcode, pc}
   208  			}
   209  			if src := i.OpCode & srcRetMask; src != K && src != A {
   210  				return Program{}, Error{InvalidOpcode, pc}
   211  			}
   212  		case Misc:
   213  			if misc := i.OpCode & miscMask; misc != Tax && misc != Txa {
   214  				return Program{}, Error{InvalidOpcode, pc}
   215  			}
   216  		}
   217  	}
   218  
   219  	return Program{insns}, nil
   220  }
   221  
   222  // Input represents a source of input data for a BPF program. (BPF
   223  // documentation sometimes refers to the input data as the "packet" due to its
   224  // origins as a packet processing DSL.)
   225  //
   226  // For all of Input's Load methods:
   227  //
   228  //   - The second (bool) return value is true if the load succeeded and false
   229  //     otherwise.
   230  //
   231  //   - Inputs should not assume that the loaded range falls within the input
   232  //     data's length. Inputs should return false if the load falls outside of the
   233  //     input data.
   234  //
   235  //   - Inputs should not assume that the offset is correctly aligned. Inputs may
   236  //     choose to service or reject loads to unaligned addresses.
   237  type Input interface {
   238  	// Load32 reads 32 bits from the input starting at the given byte offset.
   239  	Load32(off uint32) (uint32, bool)
   240  
   241  	// Load16 reads 16 bits from the input starting at the given byte offset.
   242  	Load16(off uint32) (uint16, bool)
   243  
   244  	// Load8 reads 8 bits from the input starting at the given byte offset.
   245  	Load8(off uint32) (uint8, bool)
   246  
   247  	// Length returns the length of the input in bytes.
   248  	Length() uint32
   249  }
   250  
   251  // machine represents the state of a BPF virtual machine.
   252  type machine struct {
   253  	A uint32
   254  	X uint32
   255  	M [ScratchMemRegisters]uint32
   256  }
   257  
   258  func conditionalJumpOffset(insn linux.BPFInstruction, cond bool) int {
   259  	if cond {
   260  		return int(insn.JumpIfTrue)
   261  	}
   262  	return int(insn.JumpIfFalse)
   263  }
   264  
   265  // Exec executes a BPF program over the given input and returns its return
   266  // value.
   267  func Exec(p Program, in Input) (uint32, error) {
   268  	var m machine
   269  	var pc int
   270  	for ; pc < len(p.instructions); pc++ {
   271  		i := p.instructions[pc]
   272  		switch i.OpCode {
   273  		case Ld | Imm | W:
   274  			m.A = i.K
   275  		case Ld | Abs | W:
   276  			val, ok := in.Load32(i.K)
   277  			if !ok {
   278  				return 0, Error{InvalidLoad, pc}
   279  			}
   280  			m.A = val
   281  		case Ld | Abs | H:
   282  			val, ok := in.Load16(i.K)
   283  			if !ok {
   284  				return 0, Error{InvalidLoad, pc}
   285  			}
   286  			m.A = uint32(val)
   287  		case Ld | Abs | B:
   288  			val, ok := in.Load8(i.K)
   289  			if !ok {
   290  				return 0, Error{InvalidLoad, pc}
   291  			}
   292  			m.A = uint32(val)
   293  		case Ld | Ind | W:
   294  			val, ok := in.Load32(m.X + i.K)
   295  			if !ok {
   296  				return 0, Error{InvalidLoad, pc}
   297  			}
   298  			m.A = val
   299  		case Ld | Ind | H:
   300  			val, ok := in.Load16(m.X + i.K)
   301  			if !ok {
   302  				return 0, Error{InvalidLoad, pc}
   303  			}
   304  			m.A = uint32(val)
   305  		case Ld | Ind | B:
   306  			val, ok := in.Load8(m.X + i.K)
   307  			if !ok {
   308  				return 0, Error{InvalidLoad, pc}
   309  			}
   310  			m.A = uint32(val)
   311  		case Ld | Mem | W:
   312  			m.A = m.M[int(i.K)]
   313  		case Ld | Len | W:
   314  			m.A = in.Length()
   315  		case Ldx | Imm | W:
   316  			m.X = i.K
   317  		case Ldx | Mem | W:
   318  			m.X = m.M[int(i.K)]
   319  		case Ldx | Len | W:
   320  			m.X = in.Length()
   321  		case Ldx | Msh | B:
   322  			val, ok := in.Load8(i.K)
   323  			if !ok {
   324  				return 0, Error{InvalidLoad, pc}
   325  			}
   326  			m.X = 4 * uint32(val&0xf)
   327  		case St:
   328  			m.M[int(i.K)] = m.A
   329  		case Stx:
   330  			m.M[int(i.K)] = m.X
   331  		case Alu | Add | K:
   332  			m.A += i.K
   333  		case Alu | Add | X:
   334  			m.A += m.X
   335  		case Alu | Sub | K:
   336  			m.A -= i.K
   337  		case Alu | Sub | X:
   338  			m.A -= m.X
   339  		case Alu | Mul | K:
   340  			m.A *= i.K
   341  		case Alu | Mul | X:
   342  			m.A *= m.X
   343  		case Alu | Div | K:
   344  			// K != 0 already checked by Compile.
   345  			m.A /= i.K
   346  		case Alu | Div | X:
   347  			if m.X == 0 {
   348  				return 0, Error{DivisionByZero, pc}
   349  			}
   350  			m.A /= m.X
   351  		case Alu | Or | K:
   352  			m.A |= i.K
   353  		case Alu | Or | X:
   354  			m.A |= m.X
   355  		case Alu | And | K:
   356  			m.A &= i.K
   357  		case Alu | And | X:
   358  			m.A &= m.X
   359  		case Alu | Lsh | K:
   360  			m.A <<= i.K
   361  		case Alu | Lsh | X:
   362  			m.A <<= m.X
   363  		case Alu | Rsh | K:
   364  			m.A >>= i.K
   365  		case Alu | Rsh | X:
   366  			m.A >>= m.X
   367  		case Alu | Neg:
   368  			m.A = uint32(-int32(m.A))
   369  		case Alu | Mod | K:
   370  			// K != 0 already checked by Compile.
   371  			m.A %= i.K
   372  		case Alu | Mod | X:
   373  			if m.X == 0 {
   374  				return 0, Error{DivisionByZero, pc}
   375  			}
   376  			m.A %= m.X
   377  		case Alu | Xor | K:
   378  			m.A ^= i.K
   379  		case Alu | Xor | X:
   380  			m.A ^= m.X
   381  		case Jmp | Ja:
   382  			pc += int(i.K)
   383  		case Jmp | Jeq | K:
   384  			pc += conditionalJumpOffset(i, m.A == i.K)
   385  		case Jmp | Jeq | X:
   386  			pc += conditionalJumpOffset(i, m.A == m.X)
   387  		case Jmp | Jgt | K:
   388  			pc += conditionalJumpOffset(i, m.A > i.K)
   389  		case Jmp | Jgt | X:
   390  			pc += conditionalJumpOffset(i, m.A > m.X)
   391  		case Jmp | Jge | K:
   392  			pc += conditionalJumpOffset(i, m.A >= i.K)
   393  		case Jmp | Jge | X:
   394  			pc += conditionalJumpOffset(i, m.A >= m.X)
   395  		case Jmp | Jset | K:
   396  			pc += conditionalJumpOffset(i, (m.A&i.K) != 0)
   397  		case Jmp | Jset | X:
   398  			pc += conditionalJumpOffset(i, (m.A&m.X) != 0)
   399  		case Ret | K:
   400  			return i.K, nil
   401  		case Ret | A:
   402  			return m.A, nil
   403  		case Misc | Tax:
   404  			m.A = m.X
   405  		case Misc | Txa:
   406  			m.X = m.A
   407  		default:
   408  			return 0, Error{InvalidOpcode, pc}
   409  		}
   410  	}
   411  	return 0, Error{InvalidEndOfProgram, pc}
   412  }