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 }