gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/bpf/interpreter_test.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 "reflect" 19 "slices" 20 "testing" 21 22 "gvisor.dev/gvisor/pkg/abi/linux" 23 "gvisor.dev/gvisor/pkg/hostarch" 24 "gvisor.dev/gvisor/pkg/marshal" 25 ) 26 27 func TestCompilationErrors(t *testing.T) { 28 for _, test := range []struct { 29 // desc is the test's description. 30 desc string 31 32 // insns is the BPF instructions to be compiled. 33 insns []Instruction 34 35 // expectedErr is the expected compilation error. 36 expectedErr error 37 }{ 38 { 39 desc: "Instructions must not be nil", 40 expectedErr: Error{InvalidInstructionCount, 0}, 41 }, 42 { 43 desc: "Instructions must not be empty", 44 insns: []Instruction{}, 45 expectedErr: Error{InvalidInstructionCount, 0}, 46 }, 47 { 48 desc: "A program must end with a return", 49 insns: make([]Instruction, MaxInstructions), 50 expectedErr: Error{InvalidEndOfProgram, MaxInstructions - 1}, 51 }, 52 { 53 desc: "A program must have MaxInstructions or fewer instructions", 54 insns: append(make([]Instruction, MaxInstructions), Stmt(Ret|K, 0)), 55 expectedErr: Error{InvalidInstructionCount, MaxInstructions + 1}, 56 }, 57 { 58 desc: "A load from an invalid M register is a compilation error", 59 insns: []Instruction{ 60 Stmt(Ld|Mem|W, ScratchMemRegisters), // A = M[16] 61 Stmt(Ret|K, 0), // return 0 62 }, 63 expectedErr: Error{InvalidRegister, 0}, 64 }, 65 { 66 desc: "A store to an invalid M register is a compilation error", 67 insns: []Instruction{ 68 Stmt(St, ScratchMemRegisters), // M[16] = A 69 Stmt(Ret|K, 0), // return 0 70 }, 71 expectedErr: Error{InvalidRegister, 0}, 72 }, 73 { 74 desc: "Division by literal zero is a compilation error", 75 insns: []Instruction{ 76 Stmt(Alu|Div|K, 0), // A /= 0 77 Stmt(Ret|K, 0), // return 0 78 }, 79 expectedErr: Error{DivisionByZero, 0}, 80 }, 81 { 82 desc: "An unconditional jump outside of the program is a compilation error", 83 insns: []Instruction{ 84 Jump(Jmp|Ja, 1, 0, 0), // jmp nextpc+1 85 Stmt(Ret|K, 0), // return 0 86 }, 87 expectedErr: Error{InvalidJumpTarget, 0}, 88 }, 89 { 90 desc: "A conditional jump outside of the program in the true case is a compilation error", 91 insns: []Instruction{ 92 Jump(Jmp|Jeq|K, 0, 1, 0), // if (A == K) jmp nextpc+1 93 Stmt(Ret|K, 0), // return 0 94 }, 95 expectedErr: Error{InvalidJumpTarget, 0}, 96 }, 97 { 98 desc: "A conditional jump outside of the program in the false case is a compilation error", 99 insns: []Instruction{ 100 Jump(Jmp|Jeq|K, 0, 0, 1), // if (A != K) jmp nextpc+1 101 Stmt(Ret|K, 0), // return 0 102 }, 103 expectedErr: Error{InvalidJumpTarget, 0}, 104 }, 105 } { 106 t.Run(test.desc, func(t *testing.T) { 107 _, err := Compile(test.insns, false) 108 if err != test.expectedErr { 109 t.Errorf("expected error %q, got error %q", test.expectedErr, err) 110 } 111 }) 112 } 113 } 114 115 func TestExecErrors(t *testing.T) { 116 for _, test := range []struct { 117 // desc is the test's description. 118 desc string 119 120 // insns is the BPF instructions to be executed. 121 insns []Instruction 122 123 // expectedErr is the expected execution error. 124 expectedErr error 125 }{ 126 { 127 desc: "An out-of-bounds load of input data is an execution error", 128 insns: []Instruction{ 129 Stmt(Ld|Abs|B, 0), // A = input[0] 130 Stmt(Ret|K, 0), // return 0 131 }, 132 expectedErr: Error{InvalidLoad, 0}, 133 }, 134 { 135 desc: "Division by zero at runtime is an execution error", 136 insns: []Instruction{ 137 Stmt(Alu|Div|X, 0), // A /= X 138 Stmt(Ret|K, 0), // return 0 139 }, 140 expectedErr: Error{DivisionByZero, 0}, 141 }, 142 { 143 desc: "Modulo zero at runtime is an execution error", 144 insns: []Instruction{ 145 Stmt(Alu|Mod|X, 0), // A %= X 146 Stmt(Ret|K, 0), // return 0 147 }, 148 expectedErr: Error{DivisionByZero, 0}, 149 }, 150 } { 151 t.Run(test.desc, func(t *testing.T) { 152 p, err := Compile(test.insns, false) 153 if err != nil { 154 t.Fatalf("unexpected compilation error: %v", err) 155 } 156 inp := Input{} 157 execution, err := InstrumentedExec[NativeEndian](p, inp) 158 if err != test.expectedErr { 159 t.Fatalf("expected execution error %q, got (%v, %v)", test.expectedErr, execution, err) 160 } 161 ret, err := Exec[NativeEndian](p, inp) 162 if err != test.expectedErr { 163 t.Fatalf("expected execution error %q, got (%d, %v)", test.expectedErr, ret, err) 164 } 165 optimizedProgram, err := Compile(test.insns, true) 166 if err != nil { 167 t.Fatalf("unexpected compilation error: %v", err) 168 } 169 if _, err := InstrumentedExec[NativeEndian](optimizedProgram, inp); err != test.expectedErr { 170 t.Fatalf("expected execution error from optimized program %q, got (%v, %v)", test.expectedErr, execution, err) 171 } 172 }) 173 } 174 } 175 176 func TestValidInstructions(t *testing.T) { 177 want := func(ex ExecutionMetrics) func(insns []Instruction, input []byte) ExecutionMetrics { 178 return func(insns []Instruction, input []byte) ExecutionMetrics { 179 return ex 180 } 181 } 182 allCoveredNoneReadAndReturns := func(ret uint32) func(insns []Instruction, input []byte) ExecutionMetrics { 183 return func(insns []Instruction, input []byte) ExecutionMetrics { 184 coverage := make([]bool, len(insns)) 185 for i := range insns { 186 coverage[i] = true 187 } 188 return ExecutionMetrics{ 189 Coverage: coverage, 190 InputAccessed: make([]bool, len(input)), 191 ReturnValue: ret, 192 } 193 } 194 } 195 for _, test := range []struct { 196 // desc is the test's description. 197 desc string 198 199 // insns is the BPF instructions to be compiled. 200 insns []Instruction 201 202 // input is the input data. Note that input will be read as big-endian. 203 input Input 204 205 // expected is the expected result of executing the BPF program. 206 // It takes in the instructions and input that the test will run. 207 expected func(insns []Instruction, input []byte) ExecutionMetrics 208 }{ 209 { 210 desc: "Return of immediate", 211 insns: []Instruction{ 212 Stmt(Ret|K, 42), // return 42 213 }, 214 expected: allCoveredNoneReadAndReturns(42), 215 }, 216 { 217 desc: "Load of immediate into A", 218 insns: []Instruction{ 219 Stmt(Ld|Imm|W, 42), // A = 42 220 Stmt(Ret|A, 0), // return A 221 }, 222 expected: allCoveredNoneReadAndReturns(42), 223 }, 224 { 225 desc: "Load of immediate into X and copying of X into A", 226 insns: []Instruction{ 227 Stmt(Ldx|Imm|W, 42), // X = 42 228 Stmt(Misc|Tax, 0), // A = X 229 Stmt(Ret|A, 0), // return A 230 }, 231 expected: allCoveredNoneReadAndReturns(42), 232 }, 233 { 234 desc: "Copying of A into X and back", 235 insns: []Instruction{ 236 Stmt(Ld|Imm|W, 42), // A = 42 237 Stmt(Misc|Txa, 0), // X = A 238 Stmt(Ld|Imm|W, 0), // A = 0 239 Stmt(Misc|Tax, 0), // A = X 240 Stmt(Ret|A, 0), // return A 241 }, 242 expected: allCoveredNoneReadAndReturns(42), 243 }, 244 { 245 desc: "Load of 32-bit input by absolute offset into A", 246 insns: []Instruction{ 247 Stmt(Ld|Abs|W, 1), // A = input[1..4] 248 Stmt(Ret|A, 0), // return A 249 }, 250 input: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, 251 expected: want(ExecutionMetrics{ 252 Coverage: []bool{true, true}, 253 InputAccessed: []bool{false, true, true, true, true, false}, 254 ReturnValue: hostarch.ByteOrder.Uint32([]byte{0x11, 0x22, 0x33, 0x44}), 255 }), 256 }, 257 { 258 desc: "Load of 16-bit input by absolute offset into A", 259 insns: []Instruction{ 260 Stmt(Ld|Abs|H, 1), // A = input[1..2] 261 Stmt(Ret|A, 0), // return A 262 }, 263 input: []byte{0x00, 0x11, 0x22, 0x33}, 264 expected: want(ExecutionMetrics{ 265 Coverage: []bool{true, true}, 266 InputAccessed: []bool{false, true, true, false}, 267 ReturnValue: uint32(hostarch.ByteOrder.Uint16([]byte{0x11, 0x22})), 268 }), 269 }, 270 { 271 desc: "Load of 8-bit input by absolute offset into A", 272 insns: []Instruction{ 273 Stmt(Ld|Abs|B, 1), // A = input[1] 274 Stmt(Ret|A, 0), // return A 275 }, 276 input: []byte{0x00, 0x11, 0x22}, 277 expected: want(ExecutionMetrics{ 278 Coverage: []bool{true, true}, 279 InputAccessed: []bool{false, true, false}, 280 ReturnValue: 0x11, 281 }), 282 }, 283 { 284 desc: "Load of 32-bit input by relative offset into A", 285 insns: []Instruction{ 286 Stmt(Ldx|Imm|W, 1), // X = 1 287 Stmt(Ld|Ind|W, 1), // A = input[X+1..X+4] 288 Stmt(Ret|A, 0), // return A 289 }, 290 input: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}, 291 expected: want(ExecutionMetrics{ 292 Coverage: []bool{true, true, true}, 293 InputAccessed: []bool{false, false, true, true, true, true, false}, 294 ReturnValue: hostarch.ByteOrder.Uint32([]byte{0x22, 0x33, 0x44, 0x55}), 295 }), 296 }, 297 { 298 desc: "Load of 16-bit input by relative offset into A", 299 insns: []Instruction{ 300 Stmt(Ldx|Imm|W, 1), // X = 1 301 Stmt(Ld|Ind|H, 1), // A = input[X+1..X+2] 302 Stmt(Ret|A, 0), // return A 303 }, 304 input: []byte{0x00, 0x11, 0x22, 0x33, 0x44}, 305 expected: want(ExecutionMetrics{ 306 Coverage: []bool{true, true, true}, 307 InputAccessed: []bool{false, false, true, true, false}, 308 ReturnValue: uint32(hostarch.ByteOrder.Uint16([]byte{0x22, 0x33})), 309 }), 310 }, 311 { 312 desc: "Load of 8-bit input by relative offset into A", 313 insns: []Instruction{ 314 Stmt(Ldx|Imm|W, 1), // X = 1 315 Stmt(Ld|Ind|B, 1), // A = input[X+1] 316 Stmt(Ret|A, 0), // return A 317 }, 318 input: []byte{0x00, 0x11, 0x22, 0x33}, 319 expected: want(ExecutionMetrics{ 320 Coverage: []bool{true, true, true}, 321 InputAccessed: []bool{false, false, true, false}, 322 ReturnValue: 0x22, 323 }), 324 }, 325 { 326 desc: "Load/store between A and scratch memory", 327 insns: []Instruction{ 328 Stmt(Ld|Imm|W, 42), // A = 42 329 Stmt(St, 2), // M[2] = A 330 Stmt(Ld|Imm|W, 0), // A = 0 331 Stmt(Ld|Mem|W, 2), // A = M[2] 332 Stmt(Ret|A, 0), // return A 333 }, 334 expected: allCoveredNoneReadAndReturns(42), 335 }, 336 { 337 desc: "Load/store between X and scratch memory", 338 insns: []Instruction{ 339 Stmt(Ldx|Imm|W, 42), // X = 42 340 Stmt(Stx, 3), // M[3] = X 341 Stmt(Ldx|Imm|W, 0), // X = 0 342 Stmt(Ldx|Mem|W, 3), // X = M[3] 343 Stmt(Misc|Tax, 0), // A = X 344 Stmt(Ret|A, 0), // return A 345 }, 346 expected: allCoveredNoneReadAndReturns(42), 347 }, 348 { 349 desc: "Load of input length into A", 350 insns: []Instruction{ 351 Stmt(Ld|Len|W, 0), // A = len(input) 352 Stmt(Ret|A, 0), // return A 353 }, 354 input: []byte{1, 2, 3}, 355 expected: allCoveredNoneReadAndReturns(3), 356 }, 357 { 358 desc: "Load of input length into X", 359 insns: []Instruction{ 360 Stmt(Ldx|Len|W, 0), // X = len(input) 361 Stmt(Misc|Tax, 0), // A = X 362 Stmt(Ret|A, 0), // return A 363 }, 364 input: []byte{1, 2, 3}, 365 expected: allCoveredNoneReadAndReturns(3), 366 }, 367 { 368 desc: "Load of MSH (?) into X", 369 insns: []Instruction{ 370 Stmt(Ldx|Msh|B, 0), // X = 4*(input[0]&0xf) 371 Stmt(Misc|Tax, 0), // A = X 372 Stmt(Ret|A, 0), // return A 373 }, 374 input: []byte{0xf1}, 375 expected: want(ExecutionMetrics{ 376 Coverage: []bool{true, true, true}, 377 InputAccessed: []bool{true}, 378 ReturnValue: 4, 379 }), 380 }, 381 { 382 desc: "Addition of immediate", 383 insns: []Instruction{ 384 Stmt(Ld|Imm|W, 10), // A = 10 385 Stmt(Alu|Add|K, 20), // A += 20 386 Stmt(Ret|A, 0), // return A 387 }, 388 expected: allCoveredNoneReadAndReturns(30), 389 }, 390 { 391 desc: "Addition of X", 392 insns: []Instruction{ 393 Stmt(Ld|Imm|W, 10), // A = 10 394 Stmt(Ldx|Imm|W, 20), // X = 20 395 Stmt(Alu|Add|X, 0), // A += X 396 Stmt(Ret|A, 0), // return A 397 }, 398 expected: allCoveredNoneReadAndReturns(30), 399 }, 400 { 401 desc: "Subtraction of immediate", 402 insns: []Instruction{ 403 Stmt(Ld|Imm|W, 30), // A = 30 404 Stmt(Alu|Sub|K, 20), // A -= 20 405 Stmt(Ret|A, 0), // return A 406 }, 407 expected: allCoveredNoneReadAndReturns(10), 408 }, 409 { 410 desc: "Subtraction of X", 411 insns: []Instruction{ 412 Stmt(Ld|Imm|W, 30), // A = 30 413 Stmt(Ldx|Imm|W, 20), // X = 20 414 Stmt(Alu|Sub|X, 0), // A -= X 415 Stmt(Ret|A, 0), // return A 416 }, 417 expected: allCoveredNoneReadAndReturns(10), 418 }, 419 { 420 desc: "Multiplication of immediate", 421 insns: []Instruction{ 422 Stmt(Ld|Imm|W, 2), // A = 2 423 Stmt(Alu|Mul|K, 3), // A *= 3 424 Stmt(Ret|A, 0), // return A 425 }, 426 expected: allCoveredNoneReadAndReturns(6), 427 }, 428 { 429 desc: "Multiplication of X", 430 insns: []Instruction{ 431 Stmt(Ld|Imm|W, 2), // A = 2 432 Stmt(Ldx|Imm|W, 3), // X = 3 433 Stmt(Alu|Mul|X, 0), // A *= X 434 Stmt(Ret|A, 0), // return A 435 }, 436 expected: allCoveredNoneReadAndReturns(6), 437 }, 438 { 439 desc: "Division by immediate", 440 insns: []Instruction{ 441 Stmt(Ld|Imm|W, 6), // A = 6 442 Stmt(Alu|Div|K, 3), // A /= 3 443 Stmt(Ret|A, 0), // return A 444 }, 445 expected: allCoveredNoneReadAndReturns(2), 446 }, 447 { 448 desc: "Division by X", 449 insns: []Instruction{ 450 Stmt(Ld|Imm|W, 6), // A = 6 451 Stmt(Ldx|Imm|W, 3), // X = 3 452 Stmt(Alu|Div|X, 0), // A /= X 453 Stmt(Ret|A, 0), // return A 454 }, 455 expected: allCoveredNoneReadAndReturns(2), 456 }, 457 { 458 desc: "Modulo immediate", 459 insns: []Instruction{ 460 Stmt(Ld|Imm|W, 17), // A = 17 461 Stmt(Alu|Mod|K, 7), // A %= 7 462 Stmt(Ret|A, 0), // return A 463 }, 464 expected: allCoveredNoneReadAndReturns(3), 465 }, 466 { 467 desc: "Modulo X", 468 insns: []Instruction{ 469 Stmt(Ld|Imm|W, 17), // A = 17 470 Stmt(Ldx|Imm|W, 7), // X = 7 471 Stmt(Alu|Mod|X, 0), // A %= X 472 Stmt(Ret|A, 0), // return A 473 }, 474 expected: allCoveredNoneReadAndReturns(3), 475 }, 476 { 477 desc: "Arithmetic negation", 478 insns: []Instruction{ 479 Stmt(Ld|Imm|W, 1), // A = 1 480 Stmt(Alu|Neg, 0), // A = -A 481 Stmt(Ret|A, 0), // return A 482 }, 483 expected: allCoveredNoneReadAndReturns(0xffffffff), 484 }, 485 { 486 desc: "Bitwise OR with immediate", 487 insns: []Instruction{ 488 Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55 489 Stmt(Alu|Or|K, 0xff0055aa), // A |= 0xff0055aa 490 Stmt(Ret|A, 0), // return A 491 }, 492 expected: allCoveredNoneReadAndReturns(0xff00ffff), 493 }, 494 { 495 desc: "Bitwise OR with X", 496 insns: []Instruction{ 497 Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55 498 Stmt(Ldx|Imm|W, 0xff0055aa), // X = 0xff0055aa 499 Stmt(Alu|Or|X, 0), // A |= X 500 Stmt(Ret|A, 0), // return A 501 }, 502 expected: allCoveredNoneReadAndReturns(0xff00ffff), 503 }, 504 { 505 desc: "Bitwise AND with immediate", 506 insns: []Instruction{ 507 Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55 508 Stmt(Alu|And|K, 0xff0055aa), // A &= 0xff0055aa 509 Stmt(Ret|A, 0), // return A 510 }, 511 expected: allCoveredNoneReadAndReturns(0xff000000), 512 }, 513 { 514 desc: "Bitwise AND with X", 515 insns: []Instruction{ 516 Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55 517 Stmt(Ldx|Imm|W, 0xff0055aa), // X = 0xff0055aa 518 Stmt(Alu|And|X, 0), // A &= X 519 Stmt(Ret|A, 0), // return A 520 }, 521 expected: allCoveredNoneReadAndReturns(0xff000000), 522 }, 523 { 524 desc: "Bitwise XOR with immediate", 525 insns: []Instruction{ 526 Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55 527 Stmt(Alu|Xor|K, 0xff0055aa), // A ^= 0xff0055aa 528 Stmt(Ret|A, 0), // return A 529 }, 530 expected: allCoveredNoneReadAndReturns(0x0000ffff), 531 }, 532 { 533 desc: "Bitwise XOR with X", 534 insns: []Instruction{ 535 Stmt(Ld|Imm|W, 0xff00aa55), // A = 0xff00aa55 536 Stmt(Ldx|Imm|W, 0xff0055aa), // X = 0xff0055aa 537 Stmt(Alu|Xor|X, 0), // A ^= X 538 Stmt(Ret|A, 0), // return A 539 }, 540 expected: allCoveredNoneReadAndReturns(0x0000ffff), 541 }, 542 { 543 desc: "Left shift by immediate", 544 insns: []Instruction{ 545 Stmt(Ld|Imm|W, 1), // A = 1 546 Stmt(Alu|Lsh|K, 5), // A <<= 5 547 Stmt(Ret|A, 0), // return A 548 }, 549 expected: allCoveredNoneReadAndReturns(32), 550 }, 551 { 552 desc: "Left shift by X", 553 insns: []Instruction{ 554 Stmt(Ld|Imm|W, 1), // A = 1 555 Stmt(Ldx|Imm|W, 5), // X = 5 556 Stmt(Alu|Lsh|X, 0), // A <<= X 557 Stmt(Ret|A, 0), // return A 558 }, 559 expected: allCoveredNoneReadAndReturns(32), 560 }, 561 { 562 desc: "Right shift by immediate", 563 insns: []Instruction{ 564 Stmt(Ld|Imm|W, 0xffffffff), // A = 0xffffffff 565 Stmt(Alu|Rsh|K, 31), // A >>= 31 566 Stmt(Ret|A, 0), // return A 567 }, 568 expected: allCoveredNoneReadAndReturns(1), 569 }, 570 { 571 desc: "Right shift by X", 572 insns: []Instruction{ 573 Stmt(Ld|Imm|W, 0xffffffff), // A = 0xffffffff 574 Stmt(Ldx|Imm|W, 31), // X = 31 575 Stmt(Alu|Rsh|X, 0), // A >>= X 576 Stmt(Ret|A, 0), // return A 577 }, 578 expected: allCoveredNoneReadAndReturns(1), 579 }, 580 { 581 desc: "Unconditional jump", 582 insns: []Instruction{ 583 Jump(Jmp|Ja, 1, 0, 0), // jmp nextpc+1 584 Stmt(Ret|K, 0), // return 0 585 Stmt(Ret|K, 1), // return 1 586 }, 587 expected: want(ExecutionMetrics{ 588 Coverage: []bool{true, false, true}, 589 InputAccessed: []bool{}, 590 ReturnValue: 1, 591 }), 592 }, 593 { 594 desc: "Jump when A == immediate", 595 insns: []Instruction{ 596 Stmt(Ld|Imm|W, 42), // A = 42 597 Jump(Jmp|Jeq|K, 42, 1, 2), // if (A == 42) jmp nextpc+1 else jmp nextpc+2 598 Stmt(Ret|K, 0), // return 0 599 Stmt(Ret|K, 1), // return 1 600 Stmt(Ret|K, 2), // return 2 601 }, 602 expected: want(ExecutionMetrics{ 603 Coverage: []bool{true, true, false, true, false}, 604 InputAccessed: []bool{}, 605 ReturnValue: 1, 606 }), 607 }, 608 { 609 desc: "Jump when A != immediate", 610 insns: []Instruction{ 611 Stmt(Ld|Imm|W, 41), // A = 41 612 Jump(Jmp|Jeq|K, 42, 1, 2), // if (A == 42) jmp nextpc+1 else jmp nextpc+2 613 Stmt(Ret|K, 0), // return 0 614 Stmt(Ret|K, 1), // return 1 615 Stmt(Ret|K, 2), // return 2 616 }, 617 expected: want(ExecutionMetrics{ 618 Coverage: []bool{true, true, false, false, true}, 619 InputAccessed: []bool{}, 620 ReturnValue: 2, 621 }), 622 }, 623 { 624 desc: "Jump when A == X", 625 insns: []Instruction{ 626 Stmt(Ld|Imm|W, 42), // A = 42 627 Stmt(Ldx|Imm|W, 42), // X = 42 628 Jump(Jmp|Jeq|X, 0, 1, 2), // if (A == X) jmp nextpc+1 else jmp nextpc+2 629 Stmt(Ret|K, 0), // return 0 630 Stmt(Ret|K, 1), // return 1 631 Stmt(Ret|K, 2), // return 2 632 }, 633 expected: want(ExecutionMetrics{ 634 Coverage: []bool{true, true, true, false, true, false}, 635 InputAccessed: []bool{}, 636 ReturnValue: 1, 637 }), 638 }, 639 { 640 desc: "Jump when A != X", 641 insns: []Instruction{ 642 Stmt(Ld|Imm|W, 42), // A = 42 643 Stmt(Ldx|Imm|W, 41), // X = 41 644 Jump(Jmp|Jeq|X, 0, 1, 2), // if (A == X) jmp nextpc+1 else jmp nextpc+2 645 Stmt(Ret|K, 0), // return 0 646 Stmt(Ret|K, 1), // return 1 647 Stmt(Ret|K, 2), // return 2 648 }, 649 expected: want(ExecutionMetrics{ 650 Coverage: []bool{true, true, true, false, false, true}, 651 InputAccessed: []bool{}, 652 ReturnValue: 2, 653 }), 654 }, 655 { 656 desc: "Jump when A > immediate", 657 insns: []Instruction{ 658 Stmt(Ld|Imm|W, 10), // A = 10 659 Jump(Jmp|Jgt|K, 9, 1, 2), // if (A > 9) jmp nextpc+1 else jmp nextpc+2 660 Stmt(Ret|K, 0), // return 0 661 Stmt(Ret|K, 1), // return 1 662 Stmt(Ret|K, 2), // return 2 663 }, 664 expected: want(ExecutionMetrics{ 665 Coverage: []bool{true, true, false, true, false}, 666 InputAccessed: []bool{}, 667 ReturnValue: 1, 668 }), 669 }, 670 { 671 desc: "Jump when A <= immediate", 672 insns: []Instruction{ 673 Stmt(Ld|Imm|W, 10), // A = 10 674 Jump(Jmp|Jgt|K, 10, 1, 2), // if (A > 10) jmp nextpc+1 else jmp nextpc+2 675 Stmt(Ret|K, 0), // return 0 676 Stmt(Ret|K, 1), // return 1 677 Stmt(Ret|K, 2), // return 2 678 }, 679 expected: want(ExecutionMetrics{ 680 Coverage: []bool{true, true, false, false, true}, 681 InputAccessed: []bool{}, 682 ReturnValue: 2, 683 }), 684 }, 685 { 686 desc: "Jump when A > X", 687 insns: []Instruction{ 688 Stmt(Ld|Imm|W, 10), // A = 10 689 Stmt(Ldx|Imm|W, 9), // X = 9 690 Jump(Jmp|Jgt|X, 0, 1, 2), // if (A > X) jmp nextpc+1 else jmp nextpc+2 691 Stmt(Ret|K, 0), // return 0 692 Stmt(Ret|K, 1), // return 1 693 Stmt(Ret|K, 2), // return 2 694 }, 695 expected: want(ExecutionMetrics{ 696 Coverage: []bool{true, true, true, false, true, false}, 697 InputAccessed: []bool{}, 698 ReturnValue: 1, 699 }), 700 }, 701 { 702 desc: "Jump when A <= X", 703 insns: []Instruction{ 704 Stmt(Ld|Imm|W, 10), // A = 10 705 Stmt(Ldx|Imm|W, 10), // X = 10 706 Jump(Jmp|Jgt|X, 0, 1, 2), // if (A > X) jmp nextpc+1 else jmp nextpc+2 707 Stmt(Ret|K, 0), // return 0 708 Stmt(Ret|K, 1), // return 1 709 Stmt(Ret|K, 2), // return 2 710 }, 711 expected: want(ExecutionMetrics{ 712 Coverage: []bool{true, true, true, false, false, true}, 713 InputAccessed: []bool{}, 714 ReturnValue: 2, 715 }), 716 }, 717 { 718 desc: "Jump when A >= immediate", 719 insns: []Instruction{ 720 Stmt(Ld|Imm|W, 10), // A = 10 721 Jump(Jmp|Jge|K, 10, 1, 2), // if (A >= 10) jmp nextpc+1 else jmp nextpc+2 722 Stmt(Ret|K, 0), // return 0 723 Stmt(Ret|K, 1), // return 1 724 Stmt(Ret|K, 2), // return 2 725 }, 726 expected: want(ExecutionMetrics{ 727 Coverage: []bool{true, true, false, true, false}, 728 InputAccessed: []bool{}, 729 ReturnValue: 1, 730 }), 731 }, 732 { 733 desc: "Jump when A < immediate", 734 insns: []Instruction{ 735 Stmt(Ld|Imm|W, 10), // A = 10 736 Jump(Jmp|Jge|K, 11, 1, 2), // if (A >= 11) jmp nextpc+1 else jmp nextpc+2 737 Stmt(Ret|K, 0), // return 0 738 Stmt(Ret|K, 1), // return 1 739 Stmt(Ret|K, 2), // return 2 740 }, 741 expected: want(ExecutionMetrics{ 742 Coverage: []bool{true, true, false, false, true}, 743 InputAccessed: []bool{}, 744 ReturnValue: 2, 745 }), 746 }, 747 { 748 desc: "Jump when A >= X", 749 insns: []Instruction{ 750 Stmt(Ld|Imm|W, 10), // A = 10 751 Stmt(Ldx|Imm|W, 10), // X = 10 752 Jump(Jmp|Jge|X, 0, 1, 2), // if (A >= X) jmp nextpc+1 else jmp nextpc+2 753 Stmt(Ret|K, 0), // return 0 754 Stmt(Ret|K, 1), // return 1 755 Stmt(Ret|K, 2), // return 2 756 }, 757 expected: want(ExecutionMetrics{ 758 Coverage: []bool{true, true, true, false, true, false}, 759 InputAccessed: []bool{}, 760 ReturnValue: 1, 761 }), 762 }, 763 { 764 desc: "Jump when A < X", 765 insns: []Instruction{ 766 Stmt(Ld|Imm|W, 10), // A = 10 767 Stmt(Ldx|Imm|W, 11), // X = 11 768 Jump(Jmp|Jge|X, 0, 1, 2), // if (A >= X) jmp nextpc+1 else jmp nextpc+2 769 Stmt(Ret|K, 0), // return 0 770 Stmt(Ret|K, 1), // return 1 771 Stmt(Ret|K, 2), // return 2 772 }, 773 expected: want(ExecutionMetrics{ 774 Coverage: []bool{true, true, true, false, false, true}, 775 InputAccessed: []bool{}, 776 ReturnValue: 2, 777 }), 778 }, 779 { 780 desc: "Jump when A & immediate != 0", 781 insns: []Instruction{ 782 Stmt(Ld|Imm|W, 0xff), // A = 0xff 783 Jump(Jmp|Jset|K, 0x101, 1, 2), // if (A & 0x101) jmp nextpc+1 else jmp nextpc+2 784 Stmt(Ret|K, 0), // return 0 785 Stmt(Ret|K, 1), // return 1 786 Stmt(Ret|K, 2), // return 2 787 }, 788 expected: want(ExecutionMetrics{ 789 Coverage: []bool{true, true, false, true, false}, 790 InputAccessed: []bool{}, 791 ReturnValue: 1, 792 }), 793 }, 794 { 795 desc: "Jump when A & immediate == 0", 796 insns: []Instruction{ 797 Stmt(Ld|Imm|W, 0xfe), // A = 0xfe 798 Jump(Jmp|Jset|K, 0x101, 1, 2), // if (A & 0x101) jmp nextpc+1 else jmp nextpc+2 799 Stmt(Ret|K, 0), // return 0 800 Stmt(Ret|K, 1), // return 1 801 Stmt(Ret|K, 2), // return 2 802 }, 803 expected: want(ExecutionMetrics{ 804 Coverage: []bool{true, true, false, false, true}, 805 InputAccessed: []bool{}, 806 ReturnValue: 2, 807 }), 808 }, 809 { 810 desc: "Jump when A & X != 0", 811 insns: []Instruction{ 812 Stmt(Ld|Imm|W, 0xff), // A = 0xff 813 Stmt(Ldx|Imm|W, 0x101), // X = 0x101 814 Jump(Jmp|Jset|X, 0, 1, 2), // if (A & X) jmp nextpc+1 else jmp nextpc+2 815 Stmt(Ret|K, 0), // return 0 816 Stmt(Ret|K, 1), // return 1 817 Stmt(Ret|K, 2), // return 2 818 }, 819 expected: want(ExecutionMetrics{ 820 Coverage: []bool{true, true, true, false, true, false}, 821 InputAccessed: []bool{}, 822 ReturnValue: 1, 823 }), 824 }, 825 { 826 desc: "Jump when A & X == 0", 827 insns: []Instruction{ 828 Stmt(Ld|Imm|W, 0xfe), // A = 0xfe 829 Stmt(Ldx|Imm|W, 0x101), // X = 0x101 830 Jump(Jmp|Jset|X, 0, 1, 2), // if (A & X) jmp nextpc+1 else jmp nextpc+2 831 Stmt(Ret|K, 0), // return 0 832 Stmt(Ret|K, 1), // return 1 833 Stmt(Ret|K, 2), // return 2 834 }, 835 expected: want(ExecutionMetrics{ 836 Coverage: []bool{true, true, true, false, false, true}, 837 InputAccessed: []bool{}, 838 ReturnValue: 2, 839 }), 840 }, 841 { 842 desc: "Optimizable program", 843 insns: []Instruction{ 844 Stmt(Ld|Imm|W, 42), // A = 42 845 Jump(Jmp|Jeq|K, 42, 0, 1), // if (A == 42) jmp 0 else 1 846 Jump(Jmp|Ja, 1, 0, 0), // jmp 1 847 Jump(Jmp|Ja, 2, 0, 0), // jmp 2 848 Stmt(Ld|Imm|W, 37), // A = 37 849 Stmt(Ret|K, 0), // return 0 850 Stmt(Ret|K, 1), // return 1 851 }, 852 expected: want(ExecutionMetrics{ 853 Coverage: []bool{true, true, true, false, true, true, false}, 854 InputAccessed: []bool{}, 855 ReturnValue: 0, 856 }), 857 }, 858 } { 859 t.Run(test.desc, func(t *testing.T) { 860 p, err := Compile(test.insns, false) 861 if err != nil { 862 t.Fatalf("unexpected compilation error: %v", err) 863 } 864 want := test.expected(test.insns, test.input) 865 execution, err := InstrumentedExec[NativeEndian](p, test.input) 866 if err != nil { 867 t.Fatalf("unexpected execution error: %v", err) 868 } 869 if !reflect.DeepEqual(execution, want) { 870 t.Fatalf("expected %s, got %s", want.String(), execution.String()) 871 } 872 retFast, err := Exec[NativeEndian](p, test.input) 873 if err != nil { 874 t.Fatalf("unexpected execution error during fast execution: %v", err) 875 } 876 if retFast != execution.ReturnValue { 877 t.Fatalf("instrumented execution returned %d, fast execution returned %d", execution.ReturnValue, retFast) 878 } 879 optimizedProgram, err := Compile(test.insns, true) 880 if err != nil { 881 t.Fatalf("unexpected compilation error: %v", err) 882 } 883 retOptimized, err := InstrumentedExec[NativeEndian](optimizedProgram, test.input) 884 if err != nil { 885 t.Fatalf("unexpected execution error: %v", err) 886 } 887 if retOptimized.ReturnValue != retFast { 888 t.Fatalf("expected return value from optimized version: got %d, non-optimized execution returned %d", retOptimized.ReturnValue, retFast) 889 } 890 if !slices.Equal(retOptimized.InputAccessed, execution.InputAccessed) { 891 t.Fatalf("expected input read coverage from optimized version: got %s, non-optimized execution was %s", retOptimized.String(), execution.String()) 892 } 893 }) 894 } 895 } 896 897 // Seccomp filter example given in Linux's 898 // Documentation/networking/filter.txt, translated to bytecode using the 899 // Linux kernel tree's tools/net/bpf_asm. 900 var sampleFilter = []Instruction{ 901 {0x20, 0, 0, 0x00000004}, // ld [4] /* offsetof(struct seccomp_data, arch) */ 902 {0x15, 0, 11, 0xc000003e}, // jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */ 903 {0x20, 0, 0, 0000000000}, // ld [0] /* offsetof(struct seccomp_data, nr) */ 904 {0x15, 10, 0, 0x0000000f}, // jeq #15, good /* __NR_rt_sigreturn */ 905 {0x15, 9, 0, 0x000000e7}, // jeq #231, good /* __NR_exit_group */ 906 {0x15, 8, 0, 0x0000003c}, // jeq #60, good /* __NR_exit */ 907 {0x15, 7, 0, 0000000000}, // jeq #0, good /* __NR_read */ 908 {0x15, 6, 0, 0x00000001}, // jeq #1, good /* __NR_write */ 909 {0x15, 5, 0, 0x00000005}, // jeq #5, good /* __NR_fstat */ 910 {0x15, 4, 0, 0x00000009}, // jeq #9, good /* __NR_mmap */ 911 {0x15, 3, 0, 0x0000000e}, // jeq #14, good /* __NR_rt_sigprocmask */ 912 {0x15, 2, 0, 0x0000000d}, // jeq #13, good /* __NR_rt_sigaction */ 913 {0x15, 1, 0, 0x00000023}, // jeq #35, good /* __NR_nanosleep */ 914 {0x06, 0, 0, 0000000000}, // bad: ret #0 /* SECCOMP_RET_KILL */ 915 {0x06, 0, 0, 0x7fff0000}, // good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */ 916 } 917 918 func TestSimpleFilter(t *testing.T) { 919 p, err := Compile(sampleFilter, false) 920 if err != nil { 921 t.Fatalf("Unexpected compilation error: %v", err) 922 } 923 924 // linux.SeccompData is 64 bytes long. 925 // The first 4 bytes is the syscall number. 926 // The next 4 bytes is the architecture. 927 // The last 56 bytes are the instruction pointer and the syscall arguments, 928 // which this sample program never accesses. 929 noRIPOrSyscallArgsAccess := make([]bool, 56) 930 931 for _, test := range []struct { 932 // desc is the test's description. 933 desc string 934 935 // SeccompData is the input data. 936 data linux.SeccompData 937 938 // expected is the expected execution result of the BPF program. 939 expected ExecutionMetrics 940 }{ 941 { 942 desc: "Invalid arch is rejected", 943 data: linux.SeccompData{Nr: 1 /* x86 exit */, Arch: 0x40000003 /* AUDIT_ARCH_I386 */}, 944 expected: ExecutionMetrics{ 945 ReturnValue: 0, 946 Coverage: []bool{ 947 true, // ld [4] /* offsetof(struct seccomp_data, arch) */ 948 true, // jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */ 949 false, // ld [0] /* offsetof(struct seccomp_data, nr) */ 950 false, // jeq #15, good /* __NR_rt_sigreturn */ 951 false, // jeq #231, good /* __NR_exit_group */ 952 false, // jeq #60, good /* __NR_exit */ 953 false, // jeq #0, good /* __NR_read */ 954 false, // jeq #1, good /* __NR_write */ 955 false, // jeq #5, good /* __NR_fstat */ 956 false, // jeq #9, good /* __NR_mmap */ 957 false, // jeq #14, good /* __NR_rt_sigprocmask */ 958 false, // jeq #13, good /* __NR_rt_sigaction */ 959 false, // jeq #35, good /* __NR_nanosleep */ 960 true, // bad: ret #0 /* SECCOMP_RET_KILL */ 961 false, // good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */ 962 }, 963 InputAccessed: append( 964 []bool{ 965 false, false, false, false, // Syscall number 966 true, true, true, true, // Architecture 967 }, 968 noRIPOrSyscallArgsAccess...), 969 }, 970 }, 971 { 972 desc: "Disallowed syscall is rejected", 973 data: linux.SeccompData{Nr: 105 /* __NR_setuid */, Arch: 0xc000003e}, 974 expected: ExecutionMetrics{ 975 ReturnValue: 0, 976 Coverage: []bool{ 977 true, // ld [4] /* offsetof(struct seccomp_data, arch) */ 978 true, // jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */ 979 true, // ld [0] /* offsetof(struct seccomp_data, nr) */ 980 true, // jeq #15, good /* __NR_rt_sigreturn */ 981 true, // jeq #231, good /* __NR_exit_group */ 982 true, // jeq #60, good /* __NR_exit */ 983 true, // jeq #0, good /* __NR_read */ 984 true, // jeq #1, good /* __NR_write */ 985 true, // jeq #5, good /* __NR_fstat */ 986 true, // jeq #9, good /* __NR_mmap */ 987 true, // jeq #14, good /* __NR_rt_sigprocmask */ 988 true, // jeq #13, good /* __NR_rt_sigaction */ 989 true, // jeq #35, good /* __NR_nanosleep */ 990 true, // bad: ret #0 /* SECCOMP_RET_KILL */ 991 false, // good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */ 992 }, 993 InputAccessed: append( 994 []bool{ 995 true, true, true, true, // Syscall number 996 true, true, true, true, // Architecture 997 }, 998 noRIPOrSyscallArgsAccess...), 999 }, 1000 }, 1001 { 1002 desc: "Allowed syscall is indeed allowed", 1003 data: linux.SeccompData{Nr: 231 /* __NR_exit_group */, Arch: 0xc000003e}, 1004 expected: ExecutionMetrics{ 1005 ReturnValue: 0x7fff0000, /* SECCOMP_RET_ALLOW */ 1006 Coverage: []bool{ 1007 true, // ld [4] /* offsetof(struct seccomp_data, arch) */ 1008 true, // jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */ 1009 true, // ld [0] /* offsetof(struct seccomp_data, nr) */ 1010 true, // jeq #15, good /* __NR_rt_sigreturn */ 1011 true, // jeq #231, good /* __NR_exit_group */ 1012 false, // jeq #60, good /* __NR_exit */ 1013 false, // jeq #0, good /* __NR_read */ 1014 false, // jeq #1, good /* __NR_write */ 1015 false, // jeq #5, good /* __NR_fstat */ 1016 false, // jeq #9, good /* __NR_mmap */ 1017 false, // jeq #14, good /* __NR_rt_sigprocmask */ 1018 false, // jeq #13, good /* __NR_rt_sigaction */ 1019 false, // jeq #35, good /* __NR_nanosleep */ 1020 false, // bad: ret #0 /* SECCOMP_RET_KILL */ 1021 true, // good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */ 1022 }, 1023 InputAccessed: append( 1024 []bool{ 1025 true, true, true, true, // Syscall number 1026 true, true, true, true, // Architecture 1027 }, 1028 noRIPOrSyscallArgsAccess...), 1029 }, 1030 }, 1031 } { 1032 t.Run(test.desc, func(t *testing.T) { 1033 execution, err := InstrumentedExec[NativeEndian](p, dataAsInput(&test.data)) 1034 if err != nil { 1035 t.Fatalf("expected return value of %d, got execution error: %v", test.expected.ReturnValue, err) 1036 } 1037 if !reflect.DeepEqual(execution, test.expected) { 1038 t.Errorf("expected %s, got %s", test.expected.String(), execution.String()) 1039 } 1040 }) 1041 } 1042 } 1043 1044 // asInput converts a seccompData to a bpf.Input. 1045 func dataAsInput(data *linux.SeccompData) Input { 1046 return marshal.Marshal(data) 1047 } 1048 1049 // BenchmarkInterpreter benchmarks the execution of the sample filter 1050 // for a sample syscall. 1051 func BenchmarkInterpreter(b *testing.B) { 1052 p, err := Compile(sampleFilter, true) 1053 if err != nil { 1054 b.Fatalf("Unexpected compilation error: %v", err) 1055 } 1056 data := dataAsInput(&linux.SeccompData{Nr: 231 /* __NR_exit_group */, Arch: 0xc000003e}) 1057 b.ResetTimer() 1058 for i := 0; i < b.N; i++ { 1059 if _, err := Exec[NativeEndian](p, data); err != nil { 1060 b.Fatalf("Unexpected execution error: %v", err) 1061 } 1062 } 1063 } 1064 1065 func BenchmarkInstrumentedInterpreter(b *testing.B) { 1066 p, err := Compile(sampleFilter, true) 1067 if err != nil { 1068 b.Fatalf("Unexpected compilation error: %v", err) 1069 } 1070 data := dataAsInput(&linux.SeccompData{Nr: 231 /* __NR_exit_group */, Arch: 0xc000003e}) 1071 b.ResetTimer() 1072 for i := 0; i < b.N; i++ { 1073 if _, err := InstrumentedExec[NativeEndian](p, data); err != nil { 1074 b.Fatalf("Unexpected execution error: %v", err) 1075 } 1076 } 1077 }