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 }