From 115b2d95e56f01373ebe1c3f8f5a4d8c3c4d33e4 Mon Sep 17 00:00:00 2001 From: Taras Madan Date: Thu, 14 Oct 2021 14:47:55 +0000 Subject: syz-verifier: rename verifier.go and Result to better reflect internals --- syz-verifier/execresult.go | 139 ++++++++++++++++++++++++++++++++++++++++ syz-verifier/execresult_test.go | 96 +++++++++++++++++++++++++++ syz-verifier/main.go | 4 +- syz-verifier/main_test.go | 62 +++++++++--------- syz-verifier/rpcserver.go | 14 ++-- syz-verifier/test_utils.go | 8 +-- syz-verifier/verifier.go | 139 ---------------------------------------- syz-verifier/verifier_test.go | 96 --------------------------- 8 files changed, 278 insertions(+), 280 deletions(-) create mode 100644 syz-verifier/execresult.go create mode 100644 syz-verifier/execresult_test.go delete mode 100644 syz-verifier/verifier.go delete mode 100644 syz-verifier/verifier_test.go diff --git a/syz-verifier/execresult.go b/syz-verifier/execresult.go new file mode 100644 index 000000000..d3db18882 --- /dev/null +++ b/syz-verifier/execresult.go @@ -0,0 +1,139 @@ +// Copyright 2021 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "syscall" + + "github.com/google/syzkaller/pkg/ipc" + "github.com/google/syzkaller/prog" +) + +// ExecResult stores the results of executing a program. +type ExecResult struct { + // Pool is the index of the pool. + Pool int + // Hanged is set to true when a program was killed due to hanging. + Hanged bool + // Info contains information about the execution of each system call + // in the generated programs. + Info ipc.ProgInfo + // Crashed is set to true if a crash occurred while executing the program. + Crashed bool + + RunIdx int +} + +type ResultReport struct { + // Prog is the serialized program. + Prog string + // Reports contains information about each system call. + Reports []*CallReport +} + +type CallReport struct { + // Call is the name of the system call. + Call string + // States is a map between pools and their return state when executing the system call. + States map[int]ReturnState + // Mismatch is set to true if the returned error codes were not the same. + Mismatch bool +} + +// ReturnState stores the results of executing a system call. +type ReturnState struct { + // Errno is returned by executing the system call. + Errno int + // Flags stores the call flags (see pkg/ipc/ipc.go). + Flags ipc.CallFlags + // Crashed is set to true if the kernel crashed while executing the program + // that contains the system call. + Crashed bool +} + +func (s ReturnState) String() string { + state := "" + + if s.Crashed { + return "Crashed" + } + + state += fmt.Sprintf("Flags: %d, ", s.Flags) + errDesc := "success" + if s.Errno != 0 { + errDesc = syscall.Errno(s.Errno).Error() + } + state += fmt.Sprintf("Errno: %d (%s)", s.Errno, errDesc) + return state +} + +// VeifyRerun compares the results obtained from rerunning a program with what +// was reported in the initial result report. +func VerifyRerun(res []*ExecResult, rr *ResultReport) bool { + for idx, cr := range rr.Reports { + for _, r := range res { + var state ReturnState + if r.Crashed { + state = ReturnState{Crashed: true} + } else { + ci := r.Info.Calls[idx] + state = ReturnState{Errno: ci.Errno, Flags: ci.Flags} + } + if state != cr.States[r.Pool] { + return false + } + } + } + return true +} + +// Verify checks whether the Results of the same program, executed on different +// kernels, are the same. If that's not the case, it returns a ResultReport, +// highlighting the differences. +func Verify(res []*ExecResult, prog *prog.Prog, s *Stats) *ResultReport { + rr := &ResultReport{ + Prog: string(prog.Serialize()), + } + + // Build the CallReport for each system call in the program. + for idx, call := range prog.Calls { + cn := call.Meta.Name + s.Calls[cn].Occurrences++ + + cr := &CallReport{ + Call: cn, + States: map[int]ReturnState{}, + } + + for _, r := range res { + if r.Crashed { + cr.States[r.Pool] = ReturnState{Crashed: true} + continue + } + + ci := r.Info.Calls[idx] + cr.States[r.Pool] = ReturnState{Errno: ci.Errno, Flags: ci.Flags} + } + rr.Reports = append(rr.Reports, cr) + } + + var send bool + pool0 := res[0].Pool + for _, cr := range rr.Reports { + for _, state := range cr.States { + // For each CallReport, verify whether the ReturnStates from all + // the pools that executed the program are the same + if state0 := cr.States[pool0]; state0 != state { + cr.Mismatch = true + send = true + } + } + } + + if send { + return rr + } + return nil +} diff --git a/syz-verifier/execresult_test.go b/syz-verifier/execresult_test.go new file mode 100644 index 000000000..aae7bcdac --- /dev/null +++ b/syz-verifier/execresult_test.go @@ -0,0 +1,96 @@ +// Copyright 2021 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package main + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/syzkaller/prog" +) + +func TestVerify(t *testing.T) { + p := "breaks_returns()\n" + + "minimize$0(0x1, 0x1)\n" + + "test$res0()\n" + tests := []struct { + name string + res []*ExecResult + wantReport *ResultReport + wantStats []*CallStats + }{ + { + name: "only crashes", + res: []*ExecResult{ + makeExecResultCrashed(1), + makeExecResultCrashed(4), + }, + wantReport: nil, + }, + { + name: "mismatches because results and crashes", + res: []*ExecResult{ + makeExecResultCrashed(1), + makeExecResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), + makeExecResult(4, []int{11, 33, 22}, []int{1, 3, 3}...), + }, + wantReport: &ResultReport{ + Prog: p, + Reports: []*CallReport{ + {Call: "breaks_returns", States: map[int]ReturnState{ + 1: crashedReturnState(), + 2: returnState(11, 1), + 4: returnState(11, 1)}, + Mismatch: true}, + {Call: "minimize$0", States: map[int]ReturnState{ + 1: crashedReturnState(), + 2: returnState(33, 3), + 4: returnState(33, 3)}, + Mismatch: true}, + {Call: "test$res0", States: map[int]ReturnState{ + 1: crashedReturnState(), + 2: returnState(22, 3), + 4: returnState(22, 3)}, + Mismatch: true}, + }, + }, + }, + { + name: "mismatches not found in results", + res: []*ExecResult{ + makeExecResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), + makeExecResult(4, []int{11, 33, 22}, []int{1, 3, 3}...)}, + wantReport: nil, + }, + { + name: "mismatches found in results", + res: []*ExecResult{ + makeExecResult(1, []int{1, 3, 2}, []int{4, 7, 7}...), + makeExecResult(4, []int{1, 3, 5}, []int{4, 7, 3}...), + }, + wantReport: &ResultReport{ + Prog: p, + Reports: []*CallReport{ + {Call: "breaks_returns", States: map[int]ReturnState{1: {Errno: 1, Flags: 4}, 4: {Errno: 1, Flags: 4}}}, + {Call: "minimize$0", States: map[int]ReturnState{1: {Errno: 3, Flags: 7}, 4: {Errno: 3, Flags: 7}}}, + {Call: "test$res0", States: map[int]ReturnState{1: {Errno: 2, Flags: 7}, 4: {Errno: 5, Flags: 3}}, Mismatch: true}, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + target := prog.InitTargetTest(t, "test", "64") + prog, err := target.Deserialize([]byte(p), prog.Strict) + if err != nil { + t.Fatalf("failed to deserialise test program: %v", err) + } + stats := emptyTestStats() + got := Verify(test.res, prog, stats) + if diff := cmp.Diff(test.wantReport, got); diff != "" { + t.Errorf("Verify report mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/syz-verifier/main.go b/syz-verifier/main.go index 737d336b6..168e69d26 100755 --- a/syz-verifier/main.go +++ b/syz-verifier/main.go @@ -62,8 +62,6 @@ type Verifier struct { reruns int } - - // poolInfo contains kernel-specific information for spawning virtual machines // and reporting crashes. It also keeps track of the Runners executing on // spawned VMs, what programs have been sent to each Runner and what programs @@ -89,7 +87,7 @@ type progInfo struct { prog *prog.Prog idx int serialized []byte - res [][]*Result + res [][]*ExecResult // received stores the number of results received for this program. received int diff --git a/syz-verifier/main_test.go b/syz-verifier/main_test.go index 6bf7c3d7d..b083b1a9b 100644 --- a/syz-verifier/main_test.go +++ b/syz-verifier/main_test.go @@ -91,23 +91,23 @@ func TestNewResult(t *testing.T) { srv.pools = map[int]*poolInfo{0: {}, 1: {}} srv.progs = map[int]*progInfo{ 1: {idx: 1, - res: func() [][]*Result { - res := make([][]*Result, 1) - res[0] = make([]*Result, 2) + res: func() [][]*ExecResult { + res := make([][]*ExecResult, 1) + res[0] = make([]*ExecResult, 2) return res }(), }, 3: {idx: 3, - res: func() [][]*Result { - res := make([][]*Result, 1) - res[0] = make([]*Result, 2) - res[0][1] = &Result{Pool: 1} + res: func() [][]*ExecResult { + res := make([][]*ExecResult, 1) + res[0] = make([]*ExecResult, 2) + res[0][1] = &ExecResult{Pool: 1} return res }(), received: 1, }, } - gotReady := srv.newResult(&Result{Pool: 0}, srv.progs[test.idx]) + gotReady := srv.newResult(&ExecResult{Pool: 0}, srv.progs[test.idx]) if test.wantReady != gotReady { t.Errorf("srv.newResult: got %v want %v", gotReady, test.wantReady) } @@ -323,16 +323,16 @@ func TestUpdateUnsupportedNotCalledTwice(t *testing.T) { func TestProcessResults(t *testing.T) { tests := []struct { name string - res []*Result + res []*ExecResult prog string wantExist bool wantStats *Stats }{ { name: "report written", - res: []*Result{ - makeResult(0, []int{1, 3, 2}), - makeResult(1, []int{1, 3, 5}), + res: []*ExecResult{ + makeExecResult(0, []int{1, 3, 2}), + makeExecResult(1, []int{1, 3, 5}), }, wantExist: true, wantStats: &Stats{ @@ -347,9 +347,9 @@ func TestProcessResults(t *testing.T) { }, { name: "no report written", - res: []*Result{ - makeResult(0, []int{11, 33, 22}), - makeResult(1, []int{11, 33, 22}), + res: []*ExecResult{ + makeExecResult(0, []int{11, 33, 22}), + makeExecResult(1, []int{11, 33, 22}), }, wantStats: &Stats{ TotalProgs: 1, @@ -366,8 +366,8 @@ func TestProcessResults(t *testing.T) { prog := getTestProgram(t) pi := &progInfo{ prog: prog, - res: func() [][]*Result { - res := make([][]*Result, 1) + res: func() [][]*ExecResult { + res := make([][]*ExecResult, 1) res[0] = test.res return res }()} @@ -447,16 +447,16 @@ func TestCleanup(t *testing.T) { 4: { idx: 4, received: 0, - res: func() [][]*Result { - res := make([][]*Result, 1) - res[0] = make([]*Result, 3) + res: func() [][]*ExecResult { + res := make([][]*ExecResult, 1) + res[0] = make([]*ExecResult, 3) return res }(), }}, wantProg: &progInfo{ idx: 4, received: 1, - res: [][]*Result{{makeResultCrashed(0), nil, nil}}, + res: [][]*ExecResult{{makeExecResultCrashed(0), nil, nil}}, }, wantStats: emptyTestStats(), fileExists: false, @@ -468,11 +468,11 @@ func TestCleanup(t *testing.T) { idx: 4, prog: prog, received: 2, - res: func() [][]*Result { - res := make([][]*Result, 1) - res[0] = make([]*Result, 3) - res[0][1] = makeResultCrashed(1) - res[0][2] = makeResultCrashed(2) + res: func() [][]*ExecResult { + res := make([][]*ExecResult, 1) + res[0] = make([]*ExecResult, 3) + res[0][1] = makeExecResultCrashed(1) + res[0][2] = makeExecResultCrashed(2) return res }(), }}, @@ -493,11 +493,11 @@ func TestCleanup(t *testing.T) { idx: 4, prog: prog, received: 2, - res: func() [][]*Result { - res := make([][]*Result, 1) - res[0] = make([]*Result, 3) - res[0][1] = makeResult(1, []int{11, 33, 44}) - res[0][2] = makeResult(2, []int{11, 33, 22}) + res: func() [][]*ExecResult { + res := make([][]*ExecResult, 1) + res[0] = make([]*ExecResult, 3) + res[0][1] = makeExecResult(1, []int{11, 33, 44}) + res[0][2] = makeExecResult(2, []int{11, 33, 22}) return res }(), }}, diff --git a/syz-verifier/rpcserver.go b/syz-verifier/rpcserver.go index f4956da88..aef89a135 100644 --- a/syz-verifier/rpcserver.go +++ b/syz-verifier/rpcserver.go @@ -97,10 +97,10 @@ func (srv *RPCServer) NextExchange(a *rpctype.NextExchangeArgs, r *rpctype.NextE srv.mu.Lock() defer srv.mu.Unlock() - var res *Result + var res *ExecResult var prog *progInfo if a.Info.Calls != nil { - res = &Result{ + res = &ExecResult{ Pool: a.Pool, Hanged: a.Hanged, Info: a.Info, @@ -144,7 +144,7 @@ func (srv *RPCServer) NextExchange(a *rpctype.NextExchangeArgs, r *rpctype.NextE // newResult is called when a Runner sends a new Result. It returns true if all // Results from the corresponding programs have been received and they can be // sent for verification. Otherwise, it returns false. -func (srv *RPCServer) newResult(res *Result, prog *progInfo) bool { +func (srv *RPCServer) newResult(res *ExecResult, prog *progInfo) bool { ri := prog.runIdx if prog.res[ri][res.Pool] != nil { return false @@ -157,7 +157,7 @@ func (srv *RPCServer) newResult(res *Result, prog *progInfo) bool { func (srv *RPCServer) newRun(p *progInfo) { p.runIdx++ p.received = 0 - p.res[p.runIdx] = make([]*Result, len(srv.pools)) + p.res[p.runIdx] = make([]*ExecResult, len(srv.pools)) for _, pool := range srv.pools { pool.toRerun = append(pool.toRerun, p) } @@ -181,9 +181,9 @@ func (srv *RPCServer) newProgram(poolIdx, vmIdx int) ([]byte, int, int) { prog: prog, idx: progIdx, serialized: prog.Serialize(), - res: make([][]*Result, srv.vrf.reruns), + res: make([][]*ExecResult, srv.vrf.reruns), } - pi.res[0] = make([]*Result, len(srv.pools)) + pi.res[0] = make([]*ExecResult, len(srv.pools)) for _, pool := range srv.pools { pool.progs = append(pool.progs, pi) } @@ -202,7 +202,7 @@ func (srv *RPCServer) cleanup(poolIdx, vmIdx int) { progs := srv.pools[poolIdx].runners[vmIdx] for _, prog := range progs { - if srv.newResult(&Result{Pool: poolIdx, Crashed: true}, prog) { + if srv.newResult(&ExecResult{Pool: poolIdx, Crashed: true}, prog) { srv.vrf.processResults(prog) delete(srv.progs, prog.idx) delete(srv.pools[poolIdx].runners[vmIdx], prog.idx) diff --git a/syz-verifier/test_utils.go b/syz-verifier/test_utils.go index a32c0aa29..5dd41aee2 100644 --- a/syz-verifier/test_utils.go +++ b/syz-verifier/test_utils.go @@ -60,8 +60,8 @@ func makeTestResultDirectory(t *testing.T) string { return resultsdir } -func makeResult(pool int, errnos []int, flags ...int) *Result { - r := &Result{Pool: pool, Info: ipc.ProgInfo{Calls: []ipc.CallInfo{}}} +func makeExecResult(pool int, errnos []int, flags ...int) *ExecResult { + r := &ExecResult{Pool: pool, Info: ipc.ProgInfo{Calls: []ipc.CallInfo{}}} for _, e := range errnos { r.Info.Calls = append(r.Info.Calls, ipc.CallInfo{Errno: e}) } @@ -72,8 +72,8 @@ func makeResult(pool int, errnos []int, flags ...int) *Result { return r } -func makeResultCrashed(pool int) *Result { - return &Result{Pool: pool, Crashed: true} +func makeExecResultCrashed(pool int) *ExecResult { + return &ExecResult{Pool: pool, Crashed: true} } func emptyTestStats() *Stats { diff --git a/syz-verifier/verifier.go b/syz-verifier/verifier.go deleted file mode 100644 index 9e97046b9..000000000 --- a/syz-verifier/verifier.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2021 syzkaller project authors. All rights reserved. -// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - -package main - -import ( - "fmt" - "syscall" - - "github.com/google/syzkaller/pkg/ipc" - "github.com/google/syzkaller/prog" -) - -// Result stores the results of executing a program. -type Result struct { - // Pool is the index of the pool. - Pool int - // Hanged is set to true when a program was killed due to hanging. - Hanged bool - // Info contains information about the execution of each system call - // in the generated programs. - Info ipc.ProgInfo - // Crashed is set to true if a crash occurred while executing the program. - Crashed bool - - RunIdx int -} - -type ResultReport struct { - // Prog is the serialized program. - Prog string - // Reports contains information about each system call. - Reports []*CallReport -} - -type CallReport struct { - // Call is the name of the system call. - Call string - // States is a map between pools and their return state when executing the system call. - States map[int]ReturnState - // Mismatch is set to true if the returned error codes were not the same. - Mismatch bool -} - -// ReturnState stores the results of executing a system call. -type ReturnState struct { - // Errno is returned by executing the system call. - Errno int - // Flags stores the call flags (see pkg/ipc/ipc.go). - Flags ipc.CallFlags - // Crashed is set to true if the kernel crashed while executing the program - // that contains the system call. - Crashed bool -} - -func (s ReturnState) String() string { - state := "" - - if s.Crashed { - return "Crashed" - } - - state += fmt.Sprintf("Flags: %d, ", s.Flags) - errDesc := "success" - if s.Errno != 0 { - errDesc = syscall.Errno(s.Errno).Error() - } - state += fmt.Sprintf("Errno: %d (%s)", s.Errno, errDesc) - return state -} - -// VeifyRerun compares the results obtained from rerunning a program with what -// was reported in the initial result report. -func VerifyRerun(res []*Result, rr *ResultReport) bool { - for idx, cr := range rr.Reports { - for _, r := range res { - var state ReturnState - if r.Crashed { - state = ReturnState{Crashed: true} - } else { - ci := r.Info.Calls[idx] - state = ReturnState{Errno: ci.Errno, Flags: ci.Flags} - } - if state != cr.States[r.Pool] { - return false - } - } - } - return true -} - -// Verify checks whether the Results of the same program, executed on different -// kernels, are the same. If that's not the case, it returns a ResultReport, -// highlighting the differences. -func Verify(res []*Result, prog *prog.Prog, s *Stats) *ResultReport { - rr := &ResultReport{ - Prog: string(prog.Serialize()), - } - - // Build the CallReport for each system call in the program. - for idx, call := range prog.Calls { - cn := call.Meta.Name - s.Calls[cn].Occurrences++ - - cr := &CallReport{ - Call: cn, - States: map[int]ReturnState{}, - } - - for _, r := range res { - if r.Crashed { - cr.States[r.Pool] = ReturnState{Crashed: true} - continue - } - - ci := r.Info.Calls[idx] - cr.States[r.Pool] = ReturnState{Errno: ci.Errno, Flags: ci.Flags} - } - rr.Reports = append(rr.Reports, cr) - } - - var send bool - pool0 := res[0].Pool - for _, cr := range rr.Reports { - for _, state := range cr.States { - // For each CallReport, verify whether the ReturnStates from all - // the pools that executed the program are the same - if state0 := cr.States[pool0]; state0 != state { - cr.Mismatch = true - send = true - } - } - } - - if send { - return rr - } - return nil -} diff --git a/syz-verifier/verifier_test.go b/syz-verifier/verifier_test.go deleted file mode 100644 index 41344bafc..000000000 --- a/syz-verifier/verifier_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2021 syzkaller project authors. All rights reserved. -// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - -package main - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/syzkaller/prog" -) - -func TestVerify(t *testing.T) { - p := "breaks_returns()\n" + - "minimize$0(0x1, 0x1)\n" + - "test$res0()\n" - tests := []struct { - name string - res []*Result - wantReport *ResultReport - wantStats []*CallStats - }{ - { - name: "only crashes", - res: []*Result{ - makeResultCrashed(1), - makeResultCrashed(4), - }, - wantReport: nil, - }, - { - name: "mismatches because results and crashes", - res: []*Result{ - makeResultCrashed(1), - makeResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), - makeResult(4, []int{11, 33, 22}, []int{1, 3, 3}...), - }, - wantReport: &ResultReport{ - Prog: p, - Reports: []*CallReport{ - {Call: "breaks_returns", States: map[int]ReturnState{ - 1: crashedReturnState(), - 2: returnState(11, 1), - 4: returnState(11, 1)}, - Mismatch: true}, - {Call: "minimize$0", States: map[int]ReturnState{ - 1: crashedReturnState(), - 2: returnState(33, 3), - 4: returnState(33, 3)}, - Mismatch: true}, - {Call: "test$res0", States: map[int]ReturnState{ - 1: crashedReturnState(), - 2: returnState(22, 3), - 4: returnState(22, 3)}, - Mismatch: true}, - }, - }, - }, - { - name: "mismatches not found in results", - res: []*Result{ - makeResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), - makeResult(4, []int{11, 33, 22}, []int{1, 3, 3}...)}, - wantReport: nil, - }, - { - name: "mismatches found in results", - res: []*Result{ - makeResult(1, []int{1, 3, 2}, []int{4, 7, 7}...), - makeResult(4, []int{1, 3, 5}, []int{4, 7, 3}...), - }, - wantReport: &ResultReport{ - Prog: p, - Reports: []*CallReport{ - {Call: "breaks_returns", States: map[int]ReturnState{1: {Errno: 1, Flags: 4}, 4: {Errno: 1, Flags: 4}}}, - {Call: "minimize$0", States: map[int]ReturnState{1: {Errno: 3, Flags: 7}, 4: {Errno: 3, Flags: 7}}}, - {Call: "test$res0", States: map[int]ReturnState{1: {Errno: 2, Flags: 7}, 4: {Errno: 5, Flags: 3}}, Mismatch: true}, - }, - }, - }} - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - target := prog.InitTargetTest(t, "test", "64") - prog, err := target.Deserialize([]byte(p), prog.Strict) - if err != nil { - t.Fatalf("failed to deserialise test program: %v", err) - } - stats := emptyTestStats() - got := Verify(test.res, prog, stats) - if diff := cmp.Diff(test.wantReport, got); diff != "" { - t.Errorf("Verify report mismatch (-want +got):\n%s", diff) - } - }) - } -} -- cgit mrf-deployment