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