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 }