gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/bpf/program_builder_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 "fmt" 19 "reflect" 20 "testing" 21 22 "gvisor.dev/gvisor/pkg/abi/linux" 23 ) 24 25 func validate(p *ProgramBuilder, expected []Instruction) error { 26 instructions, err := p.Instructions() 27 if err != nil { 28 return fmt.Errorf("Instructions() failed: %v", err) 29 } 30 got, err := DecodeInstructions(instructions) 31 if err != nil { 32 return fmt.Errorf("DecodeInstructions('instructions') failed: %v", err) 33 } 34 expectedDecoded, err := DecodeInstructions(expected) 35 if err != nil { 36 return fmt.Errorf("DecodeInstructions('expected') failed: %v", err) 37 } 38 if got != expectedDecoded { 39 return fmt.Errorf("DecodeInstructions() failed, expected: %q, got: %q", expectedDecoded, got) 40 } 41 return nil 42 } 43 44 func TestProgramBuilderSimple(t *testing.T) { 45 p := NewProgramBuilder() 46 p.AddStmt(Ld+Abs+W, 10) 47 p.AddJump(Jmp+Ja, 10, 0, 0) 48 49 expected := []Instruction{ 50 Stmt(Ld+Abs+W, 10), 51 Jump(Jmp+Ja, 10, 0, 0), 52 } 53 54 if err := validate(p, expected); err != nil { 55 t.Errorf("Validate() failed: %v", err) 56 } 57 } 58 59 func TestProgramBuilderLabels(t *testing.T) { 60 p := NewProgramBuilder() 61 p.AddJumpTrueLabel(Jmp+Jeq+K, 11, "label_1", 0) 62 p.AddJumpFalseLabel(Jmp+Jeq+K, 12, 0, "label_2") 63 p.AddJumpLabels(Jmp+Jeq+K, 13, "label_3", "label_4") 64 if err := p.AddLabel("label_1"); err != nil { 65 t.Errorf("AddLabel(label_1) failed: %v", err) 66 } 67 p.AddStmt(Ld+Abs+W, 1) 68 if err := p.AddLabel("label_3"); err != nil { 69 t.Errorf("AddLabel(label_3) failed: %v", err) 70 } 71 p.AddJumpLabels(Jmp+Jeq+K, 14, "label_4", "label_5") 72 if err := p.AddLabel("label_2"); err != nil { 73 t.Errorf("AddLabel(label_2) failed: %v", err) 74 } 75 p.AddJumpLabels(Jmp+Jeq+K, 15, "label_4", "label_6") 76 if err := p.AddLabel("label_4"); err != nil { 77 t.Errorf("AddLabel(label_4) failed: %v", err) 78 } 79 p.AddStmt(Ld+Abs+W, 4) 80 if err := p.AddLabel("label_5"); err != nil { 81 t.Errorf("AddLabel(label_5) failed: %v", err) 82 } 83 if err := p.AddLabel("label_6"); err != nil { 84 t.Errorf("AddLabel(label_6) failed: %v", err) 85 } 86 p.AddStmt(Ld+Abs+W, 5) 87 88 expected := []Instruction{ 89 Jump(Jmp+Jeq+K, 11, 2, 0), 90 Jump(Jmp+Jeq+K, 12, 0, 3), 91 Jump(Jmp+Jeq+K, 13, 1, 3), 92 Stmt(Ld+Abs+W, 1), 93 Jump(Jmp+Jeq+K, 14, 1, 2), 94 Jump(Jmp+Jeq+K, 15, 0, 1), 95 Stmt(Ld+Abs+W, 4), 96 Stmt(Ld+Abs+W, 5), 97 } 98 if err := validate(p, expected); err != nil { 99 t.Errorf("Validate() failed: %v", err) 100 } 101 // Calling validate()=>p.Instructions() again to make sure 102 // Instructions can be called multiple times without ruining 103 // the program. 104 if err := validate(p, expected); err != nil { 105 t.Errorf("Validate() failed: %v", err) 106 } 107 } 108 109 func TestProgramBuilderMissingErrorTarget(t *testing.T) { 110 p := NewProgramBuilder() 111 p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0) 112 if _, err := p.Instructions(); err == nil { 113 t.Errorf("Instructions() should have failed") 114 } 115 } 116 117 func TestProgramBuilderLabelWithNoInstruction(t *testing.T) { 118 p := NewProgramBuilder() 119 p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0) 120 if err := p.AddLabel("label_1"); err != nil { 121 t.Errorf("AddLabel(label_1) failed: %v", err) 122 } 123 if _, err := p.Instructions(); err == nil { 124 t.Errorf("Instructions() should have failed") 125 } 126 } 127 128 // TestProgramBuilderUnusedLabel tests that adding an unused label doesn't 129 // cause program generation to fail. 130 func TestProgramBuilderUnusedLabel(t *testing.T) { 131 p := NewProgramBuilder() 132 p.AddStmt(Ld+Abs+W, 10) 133 p.AddJump(Jmp+Ja, 10, 0, 0) 134 135 expected := []Instruction{ 136 Stmt(Ld+Abs+W, 10), 137 Jump(Jmp+Ja, 10, 0, 0), 138 } 139 140 if err := p.AddLabel("unused"); err != nil { 141 t.Errorf("AddLabel(unused) should have succeeded") 142 } 143 144 if err := validate(p, expected); err != nil { 145 t.Errorf("Validate() failed: %v", err) 146 } 147 } 148 149 // TestProgramBuilderBackwardsReference tests that including a backwards 150 // reference to a label in a program causes a failure. 151 func TestProgramBuilderBackwardsReference(t *testing.T) { 152 p := NewProgramBuilder() 153 if err := p.AddLabel("bw_label"); err != nil { 154 t.Errorf("failed to add label") 155 } 156 p.AddStmt(Ld+Abs+W, 10) 157 p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "bw_label", 0) 158 if _, err := p.Instructions(); err == nil { 159 t.Errorf("Instructions() should have failed") 160 } 161 } 162 163 func TestProgramBuilderLabelAddedTwice(t *testing.T) { 164 p := NewProgramBuilder() 165 p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0) 166 if err := p.AddLabel("label_1"); err != nil { 167 t.Errorf("AddLabel(label_1) failed: %v", err) 168 } 169 p.AddStmt(Ld+Abs+W, 0) 170 if err := p.AddLabel("label_1"); err == nil { 171 t.Errorf("AddLabel(label_1) failed: %v", err) 172 } 173 } 174 175 func TestProgramBuilderJumpBackwards(t *testing.T) { 176 p := NewProgramBuilder() 177 p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0) 178 if err := p.AddLabel("label_1"); err != nil { 179 t.Errorf("AddLabel(label_1) failed: %v", err) 180 } 181 p.AddStmt(Ld+Abs+W, 0) 182 p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "label_1", 0) 183 if _, err := p.Instructions(); err == nil { 184 t.Errorf("Instructions() should have failed") 185 } 186 } 187 188 func TestProgramBuilderOutcomes(t *testing.T) { 189 p := NewProgramBuilder() 190 getOverallFragment := p.Record() 191 fixup := func(f FragmentOutcomes) FragmentOutcomes { 192 if f.MayJumpToUnresolvedLabels == nil { 193 f.MayJumpToUnresolvedLabels = map[string]struct{}{} 194 } 195 if f.MayReturnImmediate == nil { 196 f.MayReturnImmediate = map[linux.BPFAction]struct{}{} 197 } 198 return f 199 } 200 for _, test := range []struct { 201 // Name of the sub-test. 202 name string 203 204 // Function that adds statements to `p`. 205 build func() 206 207 // Expected outcomes from recording the instructions added 208 // by `build` alone. 209 wantLocal FragmentOutcomes 210 211 // Expected value of calling `MayReturn` on the local fragment. 212 wantLocalMayReturn bool 213 214 // Expected outcomes from recording the instructions added 215 // to the program since the test began. 216 wantOverall FragmentOutcomes 217 }{ 218 { 219 name: "empty program", 220 build: func() {}, 221 wantLocal: FragmentOutcomes{MayFallThrough: true}, 222 wantOverall: FragmentOutcomes{MayFallThrough: true}, 223 }, 224 { 225 name: "simple instruction", 226 build: func() { 227 p.AddStmt(Ld|Abs|W, 10) 228 }, 229 wantLocal: FragmentOutcomes{MayFallThrough: true}, 230 wantOverall: FragmentOutcomes{MayFallThrough: true}, 231 }, 232 { 233 name: "jump to unresolved label", 234 build: func() { 235 p.AddDirectJumpLabel("label1") 236 }, 237 wantLocal: FragmentOutcomes{ 238 MayJumpToUnresolvedLabels: map[string]struct{}{ 239 "label1": struct{}{}, 240 }, 241 }, 242 wantOverall: FragmentOutcomes{ 243 MayJumpToUnresolvedLabels: map[string]struct{}{ 244 "label1": struct{}{}, 245 }, 246 }, 247 }, 248 { 249 name: "another simple load so may fall through again", 250 build: func() { 251 p.AddStmt(Ld|Abs|W, 10) 252 }, 253 wantLocal: FragmentOutcomes{ 254 MayFallThrough: true, 255 }, 256 wantOverall: FragmentOutcomes{ 257 MayJumpToUnresolvedLabels: map[string]struct{}{ 258 "label1": struct{}{}, 259 }, 260 MayFallThrough: true, 261 }, 262 }, 263 { 264 name: "resolve label1", 265 build: func() { 266 p.AddLabel("label1") 267 }, 268 wantLocal: FragmentOutcomes{ 269 MayFallThrough: true, 270 }, 271 wantOverall: FragmentOutcomes{ 272 MayFallThrough: true, 273 }, 274 }, 275 { 276 name: "populate instruction at label1", 277 build: func() { 278 p.AddStmt(Ld|Abs|W, 10) 279 }, 280 wantLocal: FragmentOutcomes{ 281 MayFallThrough: true, 282 }, 283 wantOverall: FragmentOutcomes{ 284 MayFallThrough: true, 285 }, 286 }, 287 { 288 name: "conditional jump to two unresolved labels", 289 build: func() { 290 p.AddJumpLabels(Jmp|Jeq|K, 1337, "truelabel", "falselabel") 291 }, 292 wantLocal: FragmentOutcomes{ 293 MayJumpToUnresolvedLabels: map[string]struct{}{ 294 "truelabel": struct{}{}, 295 "falselabel": struct{}{}, 296 }, 297 }, 298 wantOverall: FragmentOutcomes{ 299 MayJumpToUnresolvedLabels: map[string]struct{}{ 300 "truelabel": struct{}{}, 301 "falselabel": struct{}{}, 302 }, 303 }, 304 }, 305 { 306 name: "resolve truelabel only", 307 build: func() { 308 p.AddLabel("truelabel") 309 }, 310 wantLocal: FragmentOutcomes{ 311 MayFallThrough: true, 312 }, 313 wantOverall: FragmentOutcomes{ 314 MayJumpToUnresolvedLabels: map[string]struct{}{ 315 "falselabel": struct{}{}, 316 }, 317 MayFallThrough: true, 318 }, 319 }, 320 { 321 name: "jump one beyond end of program", 322 build: func() { 323 p.AddJump(Jmp|Ja, 1, 0, 0) 324 }, 325 wantLocal: FragmentOutcomes{ 326 MayJumpToKnownOffsetBeyondFragment: true, 327 }, 328 wantOverall: FragmentOutcomes{ 329 MayJumpToUnresolvedLabels: map[string]struct{}{ 330 "falselabel": struct{}{}, 331 }, 332 MayJumpToKnownOffsetBeyondFragment: true, 333 }, 334 }, 335 { 336 name: "add immediate return", 337 build: func() { 338 p.AddStmt(Ret|K, 1337) 339 }, 340 wantLocal: FragmentOutcomes{ 341 MayReturnImmediate: map[linux.BPFAction]struct{}{ 342 1337: struct{}{}, 343 }, 344 }, 345 wantLocalMayReturn: true, 346 wantOverall: FragmentOutcomes{ 347 MayJumpToUnresolvedLabels: map[string]struct{}{ 348 "falselabel": struct{}{}, 349 }, 350 MayFallThrough: true, // From jump in previous test. 351 MayReturnImmediate: map[linux.BPFAction]struct{}{ 352 1337: struct{}{}, 353 }, 354 }, 355 }, 356 { 357 name: "add register A return", 358 build: func() { 359 p.AddStmt(Ret|A, 0) 360 }, 361 wantLocal: FragmentOutcomes{ 362 MayReturnRegisterA: true, 363 }, 364 wantLocalMayReturn: true, 365 wantOverall: FragmentOutcomes{ 366 MayJumpToUnresolvedLabels: map[string]struct{}{ 367 "falselabel": struct{}{}, 368 }, 369 MayFallThrough: false, // Jump no longer pointing at end of fragment. 370 MayReturnImmediate: map[linux.BPFAction]struct{}{ 371 1337: struct{}{}, 372 }, 373 MayReturnRegisterA: true, 374 }, 375 }, 376 { 377 name: "add another instruction after return", 378 build: func() { 379 p.AddStmt(Ld|Abs|W, 10) 380 }, 381 wantLocal: FragmentOutcomes{ 382 MayFallThrough: true, 383 }, 384 wantOverall: FragmentOutcomes{ 385 MayJumpToUnresolvedLabels: map[string]struct{}{ 386 "falselabel": struct{}{}, 387 }, 388 MayReturnImmediate: map[linux.BPFAction]struct{}{ 389 1337: struct{}{}, 390 }, 391 MayReturnRegisterA: true, 392 MayFallThrough: true, 393 }, 394 }, 395 { 396 name: "zero-instruction jump counts as fallthrough", 397 build: func() { 398 p.AddJump(Jmp|Ja, 0, 0, 0) 399 }, 400 wantLocal: FragmentOutcomes{ 401 MayFallThrough: true, 402 }, 403 wantOverall: FragmentOutcomes{ 404 MayJumpToUnresolvedLabels: map[string]struct{}{ 405 "falselabel": struct{}{}, 406 }, 407 MayReturnImmediate: map[linux.BPFAction]struct{}{ 408 1337: struct{}{}, 409 }, 410 MayReturnRegisterA: true, 411 MayFallThrough: true, 412 }, 413 }, 414 { 415 name: "non-zero-instruction jumps that points to end of fragment also counts as fallthrough", 416 build: func() { 417 p.AddJump(Jmp|Jeq|K, 42, 3, 1) 418 p.AddJump(Jmp|Ja, 2, 0, 0) 419 p.AddStmt(Ld|Abs|W, 11) 420 p.AddStmt(Ld|Abs|W, 12) 421 }, 422 wantLocal: FragmentOutcomes{ 423 MayFallThrough: true, 424 }, 425 wantOverall: FragmentOutcomes{ 426 MayJumpToUnresolvedLabels: map[string]struct{}{ 427 "falselabel": struct{}{}, 428 }, 429 MayReturnImmediate: map[linux.BPFAction]struct{}{ 430 1337: struct{}{}, 431 }, 432 MayReturnRegisterA: true, 433 MayFallThrough: true, 434 }, 435 }, 436 { 437 name: "jump forward beyond fragment", 438 build: func() { 439 p.AddJumpFalseLabel(Jmp|Jeq|K, 1337, 123, "falselabel") 440 }, 441 wantLocal: FragmentOutcomes{ 442 MayJumpToUnresolvedLabels: map[string]struct{}{ 443 "falselabel": struct{}{}, 444 }, 445 MayJumpToKnownOffsetBeyondFragment: true, 446 }, 447 wantOverall: FragmentOutcomes{ 448 MayJumpToKnownOffsetBeyondFragment: true, 449 MayJumpToUnresolvedLabels: map[string]struct{}{ 450 "falselabel": struct{}{}, 451 }, 452 MayReturnImmediate: map[linux.BPFAction]struct{}{ 453 1337: struct{}{}, 454 }, 455 MayReturnRegisterA: true, 456 }, 457 }, 458 { 459 name: "resolve falselabel", 460 build: func() { 461 p.AddLabel("falselabel") 462 }, 463 wantLocal: FragmentOutcomes{ 464 MayFallThrough: true, 465 }, 466 wantOverall: FragmentOutcomes{ 467 MayJumpToKnownOffsetBeyondFragment: true, 468 MayReturnImmediate: map[linux.BPFAction]struct{}{ 469 1337: struct{}{}, 470 }, 471 MayReturnRegisterA: true, 472 MayFallThrough: true, 473 }, 474 }, 475 } { 476 t.Run(test.name, func(t *testing.T) { 477 getLocalFragment := p.Record() 478 test.build() 479 localFragment := getLocalFragment() 480 localOutcomes := localFragment.Outcomes() 481 if !reflect.DeepEqual(fixup(localOutcomes), fixup(test.wantLocal)) { 482 t.Errorf("local fragment %v: got outcomes %v want %v", localFragment, localOutcomes, test.wantLocal) 483 } 484 if gotMayReturn := localOutcomes.MayReturn(); gotMayReturn != test.wantLocalMayReturn { 485 t.Errorf("local fragment MayReturn(): got %v want %v", gotMayReturn, test.wantLocalMayReturn) 486 } 487 overallFragment := getOverallFragment() 488 if overallOutcomes := overallFragment.Outcomes(); !reflect.DeepEqual(fixup(overallOutcomes), fixup(test.wantOverall)) { 489 t.Errorf("overall fragment %v: got outcomes %v want %v", overallFragment, overallOutcomes, test.wantOverall) 490 } 491 }) 492 } 493 } 494 495 func TestProgramBuilderMayModifyRegisterA(t *testing.T) { 496 t.Run("empty program", func(t *testing.T) { 497 if got := NewProgramBuilder().Record()().MayModifyRegisterA(); got != false { 498 t.Errorf("MayModifyRegisterA: got %v want %v", got, false) 499 } 500 }) 501 t.Run("does not modify register A", func(t *testing.T) { 502 b := NewProgramBuilder() 503 stop := b.Record() 504 b.AddJump(Jmp|Ja, 0, 0, 0) 505 b.AddJump(Jmp|Jeq|K, 0, 0, 0) 506 b.AddStmt(Misc|Txa, 0) 507 b.AddStmt(Ret|K, 1337) 508 if got := stop().MayModifyRegisterA(); got != false { 509 t.Errorf("MayModifyRegisterA: got %v want %v", got, false) 510 } 511 }) 512 for _, ins := range []Instruction{ 513 Stmt(Ld|Abs|W, 0), 514 Stmt(Alu|Neg, 0), 515 Stmt(Misc|Tax, 0), 516 } { 517 t.Run(fmt.Sprintf("modifies register A via %v", ins), func(t *testing.T) { 518 b := NewProgramBuilder() 519 stop := b.Record() 520 b.AddJump(Jmp|Ja, 0, 0, 0) 521 b.AddJump(Jmp|Jeq|K, 0, 0, 0) 522 b.AddStmt(ins.OpCode, ins.K) 523 b.AddStmt(Ret|K, 1337) 524 if got := stop().MayModifyRegisterA(); got != true { 525 t.Errorf("MayModifyRegisterA: got %v want %v", got, true) 526 } 527 }) 528 } 529 }