diff options
| -rwxr-xr-x | syz-verifier/main.go | 41 | ||||
| -rw-r--r-- | syz-verifier/main_test.go | 101 | ||||
| -rw-r--r-- | syz-verifier/stats.go | 15 | ||||
| -rw-r--r-- | syz-verifier/stats_test.go | 27 | ||||
| -rw-r--r-- | syz-verifier/test_utils.go | 16 | ||||
| -rw-r--r-- | syz-verifier/verifier.go | 25 | ||||
| -rw-r--r-- | syz-verifier/verifier_test.go | 67 |
7 files changed, 180 insertions, 112 deletions
diff --git a/syz-verifier/main.go b/syz-verifier/main.go index 1a34c4716..b1eff4e78 100755 --- a/syz-verifier/main.go +++ b/syz-verifier/main.go @@ -95,9 +95,9 @@ type progInfo struct { idx int serialized []byte res []*Result - // left contains the indices of kernels that haven't sent results for this - // program yet. - left map[int]bool + + // done is set to true if the program was already verified. + done bool } func main() { @@ -375,15 +375,15 @@ func (srv *RPCServer) NextExchange(a *rpctype.NextExchangeArgs, r *rpctype.NextE // sent results for the program yet // 2.the cleanup call for the crash got the server's mutex before // the NextExchange call. - // As the pool was the only one still in prog.left, the cleanup - // call has already removed prog from srv.progs by the time the - // NextExchange call gets the server's mutex which is why the + // As the pool was the only one that hasn't sent the result, the + // cleanup call has already removed prog from srv.progs by the time + // the NextExchange call gets the server's mutex, which is why the // variable is nil. As the results for this program have already - // been sent for verification (if more than 2), we discard this one. + // been sent for verification, we discard this one. return nil } - if srv.newResult(res, prog) { + if prog.done = srv.newResult(res, prog); prog.done { srv.vrf.processResults(prog.res, prog.prog) delete(srv.progs, a.ProgIdx) } @@ -404,8 +404,7 @@ func (srv *RPCServer) NextExchange(a *rpctype.NextExchangeArgs, r *rpctype.NextE // sent for verification. Otherwise, it returns false. func (srv *RPCServer) newResult(res *Result, prog *progInfo) bool { prog.res = append(prog.res, res) - delete(prog.left, res.Pool) - return len(prog.left) == 0 + return len(prog.res) == len(srv.pools) } // processResults will send a set of complete results for verification and, in @@ -467,7 +466,7 @@ func createReport(rr *ResultReport, pools int) []byte { continue } - data += fmt.Sprintf("\t↳ Pool: %d, %s", i, state.String()) + data += fmt.Sprintf("\t↳ Pool: %d, %s\n", i, state) } data += "\n" @@ -486,12 +485,9 @@ func (srv *RPCServer) newProgram(poolIdx, vmIdx int) ([]byte, int) { prog: prog, idx: progIdx, serialized: prog.Serialize(), - res: make([]*Result, 0), - left: make(map[int]bool), } - for idx, pool := range srv.pools { + for _, pool := range srv.pools { pool.progs = append(pool.progs, pi) - pi.left[idx] = true } srv.progs[progIdx] = pi } @@ -514,15 +510,12 @@ func (srv *RPCServer) cleanup(poolIdx, vmIdx int) { progs := srv.pools[poolIdx].vmRunners[vmIdx] delete(srv.pools[poolIdx].vmRunners, vmIdx) for _, prog := range progs { - delete(prog.left, poolIdx) - if len(prog.left) == 0 { - if len(prog.res) > 1 { - // It might happen that all pools crashed (or all except one) - // while executing this program. We only send results for - // verification if at least two pools have returned results. - // TODO: consider logging this program or retrying execution - srv.vrf.processResults(prog.res, prog.prog) - } + if prog.done { + continue + } + + if prog.done = srv.newResult(&Result{Pool: poolIdx, Crashed: true}, prog); prog.done { + srv.vrf.processResults(prog.res, prog.prog) delete(srv.progs, prog.idx) continue } diff --git a/syz-verifier/main_test.go b/syz-verifier/main_test.go index 519aa9248..81042b6b3 100644 --- a/syz-verifier/main_test.go +++ b/syz-verifier/main_test.go @@ -41,22 +41,19 @@ func TestNewProgram(t *testing.T) { srv.pools = map[int]*poolInfo{ 1: { vmRunners: map[int][]*progInfo{ - 0: {{ - idx: 1, left: map[int]bool{1: true, 2: true}}}, + 0: {{idx: 1}}, }, - progs: []*progInfo{{ - idx: 3, left: map[int]bool{1: true}}}, + progs: []*progInfo{{idx: 3}}, }, 2: {vmRunners: map[int][]*progInfo{ - 2: {{ - idx: 1, left: map[int]bool{1: true, 2: true}}}, - }, + 2: {{idx: 1}}}, progs: []*progInfo{}, }, } + srv.progs = map[int]*progInfo{ - 1: {idx: 1, left: map[int]bool{1: true, 2: true}}, - 3: {idx: 3, left: map[int]bool{1: true}}, + 1: {idx: 1}, + 3: {idx: 3}, } _, gotProgIdx := srv.newProgram(test.pool, test.vm) @@ -76,7 +73,6 @@ func TestNewResult(t *testing.T) { name string idx int res Result - left map[int]bool wantReady bool }{ { @@ -84,34 +80,26 @@ func TestNewResult(t *testing.T) { idx: 3, res: Result{Pool: 1}, wantReady: true, - left: map[int]bool{}, }, { name: "No results ready for verification", idx: 1, res: Result{Pool: 1}, wantReady: false, - left: map[int]bool{ - 2: true, - }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { srv := createTestServer(t) srv.progs = map[int]*progInfo{ - 1: {idx: 1, - left: map[int]bool{1: true, 2: true}}, - 3: {idx: 3, - left: map[int]bool{1: true}}, + 1: {idx: 1}, + 3: {idx: 3, res: []*Result{{Pool: 2}}}, } + srv.pools = map[int]*poolInfo{1: {}, 2: {}} gotReady := srv.newResult(&test.res, srv.progs[test.idx]) if test.wantReady != gotReady { t.Errorf("srv.newResult: got %v want %v", gotReady, test.wantReady) } - if diff := cmp.Diff(test.left, srv.progs[test.idx].left); diff != "" { - t.Errorf("srv.left mismatch (-want +got):\n%s", diff) - } }) } } @@ -122,10 +110,10 @@ func TestConnect(t *testing.T) { 1: { vmRunners: map[int][]*progInfo{ 0: {{ - idx: 1, left: map[int]bool{1: true, 2: true}}}, + idx: 1}}, }, progs: []*progInfo{{ - idx: 3, left: map[int]bool{1: true}}}, + idx: 3}}, }} a := &rpctype.RunnerConnectArgs{ Pool: 1, @@ -139,7 +127,7 @@ func TestConnect(t *testing.T) { t.Errorf("Connect result mismatch (-want +got):\n%s", diff) } want, got := map[int][]*progInfo{ - 0: {{idx: 1, left: map[int]bool{1: true, 2: true}}}, + 0: {{idx: 1}}, 1: nil, }, srv.pools[a.Pool].vmRunners if diff := cmp.Diff(want, got, cmp.AllowUnexported(progInfo{})); diff != "" { @@ -393,17 +381,17 @@ func TestCreateReport(t *testing.T) { "test$res0()\n", Reports: []*CallReport{ {Call: "breaks_returns", States: map[int]ReturnState{ - 1: {Errno: 1, Flags: 1}, - 2: {Errno: 1, Flags: 1}, - 3: {Errno: 1, Flags: 1}}}, + 1: returnState(1, 1), + 2: returnState(1, 1), + 3: returnState(1, 1)}}, {Call: "minimize$0", States: map[int]ReturnState{ - 1: {Errno: 3, Flags: 3}, - 2: {Errno: 3, Flags: 3}, - 3: {Errno: 3, Flags: 3}}}, + 1: returnState(3, 3), + 2: returnState(3, 3), + 3: returnState(3, 3)}}, {Call: "test$res0", States: map[int]ReturnState{ - 1: {Errno: 2, Flags: 7}, - 2: {Errno: 5, Flags: 3}, - 3: {Errno: 22, Flags: 1}}, + 1: returnState(2, 7), + 2: returnState(5, 3), + 3: returnState(22, 1)}, Mismatch: true}, }, } @@ -437,12 +425,11 @@ func TestCleanup(t *testing.T) { name: "results not ready for verification", progs: map[int]*progInfo{ 4: { - idx: 4, - left: map[int]bool{0: true, 1: true, 2: true}, + idx: 4, }}, wantProg: &progInfo{ - idx: 4, - left: map[int]bool{1: true, 2: true}, + idx: 4, + res: []*Result{makeResultCrashed(0)}, }, wantStats: emptyTestStats(), fileExists: false, @@ -452,11 +439,10 @@ func TestCleanup(t *testing.T) { progs: map[int]*progInfo{ 4: { idx: 4, - left: map[int]bool{0: true}, prog: prog, res: []*Result{ - makeResult(1, []int{11, 33, 22}), - makeResult(2, []int{11, 33, 22}), + makeResultCrashed(1), + makeResultCrashed(2), }, }}, wantStats: &Stats{ @@ -474,7 +460,6 @@ func TestCleanup(t *testing.T) { progs: map[int]*progInfo{ 4: { idx: 4, - left: map[int]bool{0: true}, prog: prog, res: []*Result{ makeResult(1, []int{11, 33, 44}), @@ -482,30 +467,26 @@ func TestCleanup(t *testing.T) { }, }}, wantStats: &Stats{ - TotalMismatches: 1, + TotalMismatches: 3, Progs: 1, Calls: map[string]*CallStats{ - "breaks_returns": makeCallStats("breaks_returns", 1, 0, map[ReturnState]bool{}), - "minimize$0": makeCallStats("minimize$0", 1, 0, map[ReturnState]bool{}), - "test$res0": makeCallStats("test$res0", 1, 1, map[ReturnState]bool{{Errno: 22}: true, {Errno: 44}: true}), + "breaks_returns": makeCallStats("breaks_returns", 1, 1, + map[ReturnState]bool{ + crashedReturnState(): true, + returnState(11): true}), + "minimize$0": makeCallStats("minimize$0", 1, 1, + map[ReturnState]bool{ + crashedReturnState(): true, + returnState(33): true}), + "test$res0": makeCallStats("test$res0", 1, 1, + map[ReturnState]bool{ + crashedReturnState(): true, + returnState(22): true, + returnState(44): true}), }, }, fileExists: true, }, - { - name: "not enough results to send for verification", - progs: map[int]*progInfo{ - 4: { - idx: 4, - left: map[int]bool{0: true}, - res: []*Result{ - makeResult(2, []int{11, 33, 22}), - }, - }}, - wantStats: emptyTestStats(), - wantProg: nil, - fileExists: false, - }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -514,7 +495,7 @@ func TestCleanup(t *testing.T) { srv.pools = map[int]*poolInfo{ 0: {vmRunners: map[int][]*progInfo{ 0: {srv.progs[4]}}, - }} + }, 1: {}, 2: {}} resultFile := filepath.Join(srv.vrf.resultsdir, "result-0") srv.cleanup(0, 0) diff --git a/syz-verifier/stats.go b/syz-verifier/stats.go index 08bae2804..3321de478 100644 --- a/syz-verifier/stats.go +++ b/syz-verifier/stats.go @@ -128,17 +128,10 @@ func (s *Stats) getOrderedStats() []*CallStats { func (s *Stats) getOrderedStates(call string) []string { states := s.Calls[call].States - ss := make([]ReturnState, 0, len(states)) + ss := make([]string, 0, len(states)) for s := range states { - ss = append(ss, s) + ss = append(ss, fmt.Sprintf("%q", s)) } - sort.Slice(ss, func(i, j int) bool { - return ss[i].Errno < ss[j].Errno - }) - - descs := make([]string, 0, len(states)) - for _, s := range ss { - descs = append(descs, s.String()) - } - return descs + sort.Strings(ss) + return ss } diff --git a/syz-verifier/stats_test.go b/syz-verifier/stats_test.go index 6cbef81f6..81dc1ef2e 100644 --- a/syz-verifier/stats_test.go +++ b/syz-verifier/stats_test.go @@ -14,9 +14,17 @@ func dummyStats() *Stats { Progs: 24, TotalMismatches: 10, Calls: map[string]*CallStats{ - "foo": {"foo", 2, 8, map[ReturnState]bool{{Errno: 1}: true, {Errno: 3}: true}}, - "bar": {"bar", 5, 6, map[ReturnState]bool{{Errno: 10}: true, {Errno: 22}: true}}, - "tar": {"tar", 3, 4, map[ReturnState]bool{{Errno: 5}: true, {Errno: 17}: true, {Errno: 31}: true}}, + "foo": {"foo", 2, 8, map[ReturnState]bool{ + returnState(1, 7): true, + returnState(3, 7): true}}, + "bar": {"bar", 5, 6, map[ReturnState]bool{ + crashedReturnState(): true, + returnState(10, 7): true, + returnState(22, 7): true}}, + "tar": {"tar", 3, 4, map[ReturnState]bool{ + returnState(31, 7): true, + returnState(17, 7): true, + returnState(5, 7): true}}, "biz": {"biz", 0, 2, map[ReturnState]bool{}}, }, } @@ -38,7 +46,7 @@ func TestReportCallStats(t *testing.T) { "\t↳ mismatches of foo / occurrences of foo: 2 / 8 (25.00 %)\n" + "\t↳ mismatches of foo / total number of mismatches: 2 / 10 (20.00 %)\n" + "\t↳ 2 distinct states identified: " + - "[Errno: 1 (operation not permitted)\n Errno: 3 (no such process)\n]\n", + "[\"Flags: 7, Errno: 1 (operation not permitted)\" \"Flags: 7, Errno: 3 (no such process)\"]\n", }, } @@ -64,18 +72,21 @@ func TestReportGlobalStats(t *testing.T) { "statistics for bar:\n"+ "\t↳ mismatches of bar / occurrences of bar: 5 / 6 (83.33 %)\n"+ "\t↳ mismatches of bar / total number of mismatches: 5 / 10 (50.00 %)\n"+ - "\t↳ 2 distinct states identified: "+ - "[Errno: 10 (no child processes)\n Errno: 22 (invalid argument)\n]\n\n"+ + "\t↳ 3 distinct states identified: "+ + "[\"Crashed\" \"Flags: 7, Errno: 10 (no child processes)\" "+ + "\"Flags: 7, Errno: 22 (invalid argument)\"]\n\n"+ "statistics for tar:\n"+ "\t↳ mismatches of tar / occurrences of tar: 3 / 4 (75.00 %)\n"+ "\t↳ mismatches of tar / total number of mismatches: 3 / 10 (30.00 %)\n"+ "\t↳ 3 distinct states identified: "+ - "[Errno: 5 (input/output error)\n Errno: 17 (file exists)\n Errno: 31 (too many links)\n]\n\n"+ + "[\"Flags: 7, Errno: 17 (file exists)\" "+ + "\"Flags: 7, Errno: 31 (too many links)\" "+ + "\"Flags: 7, Errno: 5 (input/output error)\"]\n\n"+ "statistics for foo:\n"+ "\t↳ mismatches of foo / occurrences of foo: 2 / 8 (25.00 %)\n"+ "\t↳ mismatches of foo / total number of mismatches: 2 / 10 (20.00 %)\n"+ "\t↳ 2 distinct states identified: "+ - "[Errno: 1 (operation not permitted)\n Errno: 3 (no such process)\n]\n\n" + "[\"Flags: 7, Errno: 1 (operation not permitted)\" \"Flags: 7, Errno: 3 (no such process)\"]\n\n" if diff := cmp.Diff(want, got); diff != "" { t.Errorf("s.ReportGlobalStats mismatch (-want +got):\n%s", diff) diff --git a/syz-verifier/test_utils.go b/syz-verifier/test_utils.go index bc604e4ed..7c5df0cf5 100644 --- a/syz-verifier/test_utils.go +++ b/syz-verifier/test_utils.go @@ -71,6 +71,10 @@ func makeResult(pool int, errnos []int, flags ...int) *Result { return r } +func makeResultCrashed(pool int) *Result { + return &Result{Pool: pool, Crashed: true} +} + func emptyTestStats() *Stats { return &Stats{ Calls: map[string]*CallStats{ @@ -87,3 +91,15 @@ func makeCallStats(name string, occurrences, mismatches int, states map[ReturnSt Mismatches: mismatches, States: states} } + +func returnState(errno int, flags ...int) ReturnState { + rs := ReturnState{Errno: errno} + if flags != nil { + rs.Flags = ipc.CallFlags(flags[0]) + } + return rs +} + +func crashedReturnState() ReturnState { + return ReturnState{Crashed: true} +} diff --git a/syz-verifier/verifier.go b/syz-verifier/verifier.go index a9c584d46..c36d9236c 100644 --- a/syz-verifier/verifier.go +++ b/syz-verifier/verifier.go @@ -20,6 +20,8 @@ type Result struct { // 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 } type ResultReport struct { @@ -44,18 +46,24 @@ type ReturnState struct { 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.Flags != 0 { - state += fmt.Sprintf("Flags: %d, ", s.Flags) + + 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)\n", s.Errno, errDesc) + state += fmt.Sprintf("Errno: %d (%s)", s.Errno, errDesc) return state } @@ -78,8 +86,13 @@ func Verify(res []*Result, prog *prog.Prog, s *Stats) *ResultReport { } 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{ci.Errno, ci.Flags} + cr.States[r.Pool] = ReturnState{Errno: ci.Errno, Flags: ci.Flags} } rr.Reports = append(rr.Reports, cr) } @@ -91,8 +104,8 @@ func Verify(res []*Result, prog *prog.Prog, s *Stats) *ResultReport { mismatch := false for _, state := range cr.States { - // For each CallReport verify the ReturnStates from all the pools - // that executed the program are the same + // 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 diff --git a/syz-verifier/verifier_test.go b/syz-verifier/verifier_test.go index 254e28a25..babd87476 100644 --- a/syz-verifier/verifier_test.go +++ b/syz-verifier/verifier_test.go @@ -21,6 +21,67 @@ func TestVerify(t *testing.T) { wantStats *Stats }{ { + name: "only crashes", + res: []*Result{ + makeResultCrashed(1), + makeResultCrashed(4), + }, + wantReport: nil, + wantStats: &Stats{ + Calls: map[string]*CallStats{ + "breaks_returns": {Name: "breaks_returns", Occurrences: 1, States: map[ReturnState]bool{}}, + "minimize$0": {Name: "minimize$0", Occurrences: 1, States: map[ReturnState]bool{}}, + "test$res0": {Name: "test$res0", Occurrences: 1, States: map[ReturnState]bool{}}, + }, + }, + }, + { + 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}, + }, + }, + wantStats: &Stats{ + TotalMismatches: 6, + Calls: map[string]*CallStats{ + "breaks_returns": {Name: "breaks_returns", Occurrences: 1, Mismatches: 2, States: map[ReturnState]bool{ + crashedReturnState(): true, + returnState(11, 1): true, + }}, + "minimize$0": {Name: "minimize$0", Occurrences: 1, + Mismatches: 2, States: map[ReturnState]bool{ + crashedReturnState(): true, + returnState(33, 3): true, + }}, + "test$res0": {Name: "test$res0", Occurrences: 1, + Mismatches: 2, States: map[ReturnState]bool{ + crashedReturnState(): true, + returnState(22, 3): true}}, + }, + }, + }, + { name: "mismatches not found in results", res: []*Result{ makeResult(2, []int{11, 33, 22}, []int{1, 3, 3}...), @@ -43,9 +104,9 @@ func TestVerify(t *testing.T) { wantReport: &ResultReport{ Prog: p, Reports: []*CallReport{ - {Call: "breaks_returns", States: map[int]ReturnState{1: {1, 4}, 4: {1, 4}}}, - {Call: "minimize$0", States: map[int]ReturnState{1: {3, 7}, 4: {3, 7}}}, - {Call: "test$res0", States: map[int]ReturnState{1: {2, 7}, 4: {5, 3}}, Mismatch: true}, + {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}, }, }, wantStats: &Stats{ |
