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  }