aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xsyz-verifier/main.go41
-rw-r--r--syz-verifier/main_test.go101
-rw-r--r--syz-verifier/stats.go15
-rw-r--r--syz-verifier/stats_test.go27
-rw-r--r--syz-verifier/test_utils.go16
-rw-r--r--syz-verifier/verifier.go25
-rw-r--r--syz-verifier/verifier_test.go67
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{