github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/syz-verifier/verifier_test.go (about)

     1  // Copyright 2021 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  
    14  	"github.com/google/go-cmp/cmp"
    15  	"github.com/google/syzkaller/pkg/osutil"
    16  	"github.com/google/syzkaller/pkg/rpctype"
    17  	"github.com/google/syzkaller/prog"
    18  )
    19  
    20  func TestFinalizeCallSet(t *testing.T) {
    21  	target, err := prog.GetTarget("test", "64")
    22  	if err != nil {
    23  		t.Fatalf("failed to initialise test target: %v", err)
    24  	}
    25  
    26  	vrf := Verifier{
    27  		target: target,
    28  		reasons: map[*prog.Syscall]string{
    29  			target.SyscallMap["test$res0"]:  "foo",
    30  			target.SyscallMap["minimize$0"]: "bar",
    31  		},
    32  		calls: map[*prog.Syscall]bool{
    33  			target.SyscallMap["minimize$0"]: true,
    34  			target.SyscallMap["test$res0"]:  true,
    35  			target.SyscallMap["disabled1"]:  true,
    36  		},
    37  		reportReasons: true,
    38  	}
    39  
    40  	out := bytes.Buffer{}
    41  	vrf.finalizeCallSet(&out)
    42  	wantLines := []string{
    43  		"The following calls have been disabled:\n",
    44  		"\ttest$res0: foo\n",
    45  		"\tminimize$0: bar\n",
    46  	}
    47  	output := out.String()
    48  	for _, line := range wantLines {
    49  		if !strings.Contains(output, line) {
    50  			t.Errorf("finalizeCallSet: %q missing in reported output", line)
    51  		}
    52  	}
    53  
    54  	wantCalls, gotCalls := map[*prog.Syscall]bool{
    55  		target.SyscallMap["disabled1"]: true,
    56  	}, vrf.calls
    57  	if diff := cmp.Diff(wantCalls, gotCalls); diff != "" {
    58  		t.Errorf("srv.calls mismatch (-want +got):\n%s", diff)
    59  	}
    60  }
    61  
    62  func TestUpdateUnsupported(t *testing.T) {
    63  	target, err := prog.GetTarget("test", "64")
    64  	if err != nil {
    65  		t.Fatalf("failed to initialise test target: %v", err)
    66  	}
    67  
    68  	tests := []struct {
    69  		name           string
    70  		vrfPools       map[int]*poolInfo
    71  		wantPools      map[int]*poolInfo
    72  		wantCalls      map[*prog.Syscall]bool
    73  		wantNotChecked int
    74  		nilCT          bool
    75  	}{
    76  		{
    77  			name:           "choice table not generated",
    78  			vrfPools:       map[int]*poolInfo{0: {}, 1: {}},
    79  			wantPools:      map[int]*poolInfo{0: {checked: true}, 1: {}},
    80  			wantNotChecked: 1,
    81  			wantCalls: map[*prog.Syscall]bool{
    82  				target.SyscallMap["minimize$0"]:     true,
    83  				target.SyscallMap["breaks_returns"]: true,
    84  				target.SyscallMap["test$res0"]:      true,
    85  				target.SyscallMap["test$union0"]:    true,
    86  			},
    87  			nilCT: true,
    88  		},
    89  		{
    90  			name:           "choice table generated",
    91  			vrfPools:       map[int]*poolInfo{0: {}},
    92  			wantPools:      map[int]*poolInfo{0: {checked: true}},
    93  			wantNotChecked: 0,
    94  			wantCalls: map[*prog.Syscall]bool{
    95  				target.SyscallMap["minimize$0"]:     true,
    96  				target.SyscallMap["breaks_returns"]: true,
    97  			},
    98  			nilCT: false,
    99  		},
   100  	}
   101  
   102  	for _, test := range tests {
   103  		t.Run(test.name, func(t *testing.T) {
   104  			vrf := Verifier{
   105  				target:        target,
   106  				pools:         test.vrfPools,
   107  				reasons:       make(map[*prog.Syscall]string),
   108  				reportReasons: true,
   109  				calls: map[*prog.Syscall]bool{
   110  					target.SyscallMap["minimize$0"]:     true,
   111  					target.SyscallMap["breaks_returns"]: true,
   112  					target.SyscallMap["test$res0"]:      true,
   113  					target.SyscallMap["test$union0"]:    true,
   114  				},
   115  				stats: MakeStats(),
   116  			}
   117  			vrf.Init()
   118  
   119  			a := &rpctype.UpdateUnsupportedArgs{
   120  				Pool: 0,
   121  				UnsupportedCalls: []rpctype.SyscallReason{
   122  					{ID: target.SyscallMap["test$res0"].ID, Reason: "foo"},
   123  					{ID: 2, Reason: "bar"},
   124  					{ID: target.SyscallMap["test$union0"].ID, Reason: "tar"},
   125  				}}
   126  			if err := vrf.srv.UpdateUnsupported(a, nil); err != nil {
   127  				t.Fatalf("srv.UpdateUnsupported failed: %v", err)
   128  			}
   129  
   130  			if diff := cmp.Diff(test.wantPools, vrf.pools, cmp.AllowUnexported(poolInfo{})); diff != "" {
   131  				t.Errorf("srv.pools mismatch (-want +got):\n%s", diff)
   132  			}
   133  
   134  			wantReasons := map[*prog.Syscall]string{
   135  				target.SyscallMap["test$res0"]:   "foo",
   136  				target.SyscallMap["test$union0"]: "tar",
   137  			}
   138  			if diff := cmp.Diff(wantReasons, vrf.reasons); diff != "" {
   139  				t.Errorf("srv.reasons mismatch (-want +got):\n%s", diff)
   140  			}
   141  
   142  			if diff := cmp.Diff(test.wantCalls, vrf.calls); diff != "" {
   143  				t.Errorf("srv.calls mismatch (-want +got):\n%s", diff)
   144  			}
   145  
   146  			if want, got := test.wantNotChecked, vrf.srv.notChecked; want != got {
   147  				t.Errorf("srv.notChecked: got %d want %d", got, want)
   148  			}
   149  
   150  			if want, got := test.nilCT, vrf.choiceTable == nil; want != got {
   151  				t.Errorf("vrf.choiceTable == nil: want nil, got: %v", vrf.choiceTable)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestUpdateUnsupportedNotCalledTwice(t *testing.T) {
   158  	vrf := Verifier{
   159  		pools: map[int]*poolInfo{
   160  			0: {checked: false},
   161  			1: {checked: false},
   162  		},
   163  	}
   164  	srv, err := startRPCServer(&vrf)
   165  	if err != nil {
   166  		t.Fatalf("failed to initialise RPC server: %v", err)
   167  	}
   168  	a := &rpctype.UpdateUnsupportedArgs{Pool: 0}
   169  
   170  	if err := srv.UpdateUnsupported(a, nil); err != nil {
   171  		t.Fatalf("srv.UpdateUnsupported failed: %v", err)
   172  	}
   173  	if want, got := 1, srv.notChecked; want != got {
   174  		t.Errorf("srv.notChecked: got %d want %d", got, want)
   175  	}
   176  
   177  	if err := srv.UpdateUnsupported(a, nil); err != nil {
   178  		t.Fatalf("srv.UpdateUnsupported failed: %v", err)
   179  	}
   180  	if want, got := 1, srv.notChecked; want != got {
   181  		t.Fatalf("srv.UpdateUnsupported called twice")
   182  	}
   183  
   184  	wantPools := map[int]*poolInfo{
   185  		0: {checked: true},
   186  		1: {checked: false},
   187  	}
   188  	if diff := cmp.Diff(wantPools, vrf.pools, cmp.AllowUnexported(poolInfo{})); diff != "" {
   189  		t.Errorf("srv.pools mismatch (-want +got):\n%s", diff)
   190  	}
   191  }
   192  
   193  func TestSaveDiffResults(t *testing.T) {
   194  	tests := []struct {
   195  		name      string
   196  		res       []*ExecResult
   197  		prog      string
   198  		wantExist bool
   199  		wantStats *Stats
   200  	}{
   201  		{
   202  			name: "report written",
   203  			res: []*ExecResult{
   204  				makeExecResult(0, []int{1, 3, 2}),
   205  				makeExecResult(1, []int{1, 3, 5}),
   206  			},
   207  			wantExist: true,
   208  			wantStats: (&Stats{
   209  				TotalCallMismatches: StatUint64{1, nil},
   210  				Calls: StatMapStringToCallStats{
   211  					mapStringToCallStats: mapStringToCallStats{
   212  						"breaks_returns": makeCallStats("breaks_returns", 1, 0, map[ReturnState]bool{}),
   213  						"test$res0":      makeCallStats("test$res0", 1, 1, map[ReturnState]bool{{Errno: 2}: true, {Errno: 5}: true}),
   214  						"minimize$0":     makeCallStats("minimize$0", 1, 0, map[ReturnState]bool{}),
   215  					},
   216  				},
   217  			}).Init(),
   218  		},
   219  	}
   220  	for _, test := range tests {
   221  		t.Run(test.name, func(t *testing.T) {
   222  			prog := getTestProgram(t)
   223  			vrf := Verifier{
   224  				resultsdir: makeTestResultDirectory(t),
   225  				stats:      emptyTestStats(),
   226  			}
   227  			resultFile := filepath.Join(vrf.resultsdir, "result-0")
   228  
   229  			vrf.AddCallsExecutionStat(test.res, prog)
   230  			vrf.SaveDiffResults(test.res, prog)
   231  
   232  			if diff := cmp.Diff(test.wantStats,
   233  				vrf.stats,
   234  				cmp.AllowUnexported(
   235  					Stats{},
   236  					StatUint64{},
   237  					StatTime{},
   238  					sync.Mutex{},
   239  					StatMapStringToCallStats{},
   240  				)); diff != "" {
   241  				t.Errorf("vrf.stats mismatch (-want +got):\n%s", diff)
   242  			}
   243  
   244  			if got, want := osutil.IsExist(resultFile), test.wantExist; got != want {
   245  				t.Errorf("osutil.IsExist report file: got %v want %v", got, want)
   246  			}
   247  			os.Remove(filepath.Join(vrf.resultsdir, "result-0"))
   248  		})
   249  	}
   250  }
   251  
   252  func TestCreateReport(t *testing.T) {
   253  	rr := ResultReport{
   254  		Prog: "breaks_returns()\n" +
   255  			"minimize$0(0x1, 0x1)\n" +
   256  			"test$res0()\n",
   257  		Reports: []*CallReport{
   258  			{Call: "breaks_returns", States: map[int]ReturnState{
   259  				0: returnState(1, 1),
   260  				1: returnState(1, 1),
   261  				2: returnState(1, 1)}},
   262  			{Call: "minimize$0", States: map[int]ReturnState{
   263  				0: returnState(3, 3),
   264  				1: returnState(3, 3),
   265  				2: returnState(3, 3)}},
   266  			{Call: "test$res0", States: map[int]ReturnState{
   267  				0: returnState(2, 7),
   268  				1: returnState(5, 3),
   269  				2: returnState(22, 1)},
   270  				Mismatch: true},
   271  		},
   272  	}
   273  	got := string(createReport(&rr, 3))
   274  	want := "ERRNO mismatches found for program:\n\n" +
   275  		"[=] breaks_returns()\n" +
   276  		"\t↳ Pool: 0, Flags: 1, Errno: 1 (operation not permitted)\n" +
   277  		"\t↳ Pool: 1, Flags: 1, Errno: 1 (operation not permitted)\n" +
   278  		"\t↳ Pool: 2, Flags: 1, Errno: 1 (operation not permitted)\n\n" +
   279  		"[=] minimize$0(0x1, 0x1)\n" +
   280  		"\t↳ Pool: 0, Flags: 3, Errno: 3 (no such process)\n" +
   281  		"\t↳ Pool: 1, Flags: 3, Errno: 3 (no such process)\n" +
   282  		"\t↳ Pool: 2, Flags: 3, Errno: 3 (no such process)\n\n" +
   283  		"[!] test$res0()\n" +
   284  		"\t↳ Pool: 0, Flags: 7, Errno: 2 (no such file or directory)\n" +
   285  		"\t↳ Pool: 1, Flags: 3, Errno: 5 (input/output error)\n" +
   286  		"\t↳ Pool: 2, Flags: 1, Errno: 22 (invalid argument)\n\n"
   287  	if diff := cmp.Diff(got, want); diff != "" {
   288  		t.Errorf("createReport: (-want +got):\n%s", diff)
   289  	}
   290  }