diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2020-12-02 17:24:59 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2020-12-03 10:01:52 +0100 |
| commit | 15ab7140c0419b3ca8c88e153d9876b3c8aae0eb (patch) | |
| tree | 6011ed8e432d53f2c9cb06368f57391254702b58 /syz-hub/state/state_test.go | |
| parent | 8c9190ef9ef69993519136740a4e67c74f45fdb3 (diff) | |
syz-hub: support input domains
Hub input domain identifier (optional).
The domain is used to avoid duplicate work (input minimization, smashing)
across multiple managers testing similar kernels and connected to the same hub.
If two managers are in the same domain, they will not do input minimization after each other.
If additionally they are in the same smashing sub-domain, they will also not do smashing
after each other.
By default (empty domain) all managers testing the same OS are placed into the same domain,
this is a reasonable setting if managers test roughly the same kernel. In this case they
will not do minimization nor smashing after each other.
The setting can be either a single identifier (e.g. "foo") which will affect both minimization
and smashing; or two identifiers separated with '/' (e.g. "foo/bar"), in this case the first
identifier affects minimization and both affect smashing.
For example, if managers test different Linux kernel versions with different tools,
a reasonable use of domains on these managers can be:
- "upstream/kasan"
- "upstream/kmsan"
- "upstream/kcsan"
- "5.4/kasan"
- "5.4/kcsan"
- "4.19/kasan"
Fixes #2095
Diffstat (limited to 'syz-hub/state/state_test.go')
| -rw-r--r-- | syz-hub/state/state_test.go | 237 |
1 files changed, 162 insertions, 75 deletions
diff --git a/syz-hub/state/state_test.go b/syz-hub/state/state_test.go index 0972ad6bc..8db7a30f6 100644 --- a/syz-hub/state/state_test.go +++ b/syz-hub/state/state_test.go @@ -4,111 +4,198 @@ package state import ( - "fmt" "io/ioutil" "os" - "path/filepath" - "runtime" + "sort" "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/syzkaller/pkg/rpctype" ) -func TestState(t *testing.T) { +type TestState struct { + t *testing.T + dir string + state *State +} + +func MakeTestState(t *testing.T) *TestState { + t.Parallel() dir, err := ioutil.TempDir("", "syz-hub-state-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } - defer os.RemoveAll(dir) - - st, err := Make(dir) + state, err := Make(dir) if err != nil { + os.RemoveAll(dir) t.Fatalf("failed to make state: %v", err) } - _, _, err = st.Sync("foo", nil, nil) - if err == nil { - t.Fatalf("synced with unconnected manager") - } - calls := []string{"read", "write"} - if err := st.Connect("foo", false, calls, nil); err != nil { - t.Fatalf("Connect failed: %v", err) - } - _, _, err = st.Sync("foo", nil, nil) + return &TestState{t, dir, state} +} + +func (ts *TestState) Close() { + os.RemoveAll(ts.dir) +} + +func (ts *TestState) Reload() { + ts.state.Flush() + state, err := Make(ts.dir) if err != nil { - t.Fatalf("Sync failed: %v", err) + ts.t.Fatalf("failed to make state: %v", err) } + ts.state = state } -func TestRepro(t *testing.T) { - dir, err := ioutil.TempDir("", "syz-hub-state-test") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) +func (ts *TestState) Connect(name, domain string, fresh bool, calls []string, corpus [][]byte) { + ts.t.Helper() + if err := ts.state.Connect(name, domain, fresh, calls, corpus); err != nil { + ts.t.Fatalf("Connect failed: %v", err) } - defer os.RemoveAll(dir) +} - st, err := Make(dir) +func (ts *TestState) Sync(name string, add [][]byte, del []string) (string, []rpctype.HubInput, int) { + ts.t.Helper() + domain, inputs, pending, err := ts.state.Sync(name, add, del) if err != nil { - t.Fatalf("failed to make state: %v", err) - } + ts.t.Fatalf("Sync failed: %v", err) + } + sort.Slice(inputs, func(i, j int) bool { + if inputs[i].Domain != inputs[j].Domain { + return inputs[i].Domain < inputs[j].Domain + } + return string(inputs[i].Prog) < string(inputs[j].Prog) + }) + return domain, inputs, pending +} - if err := st.Connect("foo", false, []string{"open", "read", "write"}, nil); err != nil { - t.Fatalf("Connect failed: %v", err) +func (ts *TestState) AddRepro(name string, repro []byte) { + ts.t.Helper() + if err := ts.state.AddRepro(name, repro); err != nil { + ts.t.Fatalf("AddRepro failed: %v", err) } - if err := st.Connect("bar", false, []string{"open", "read", "close"}, nil); err != nil { - t.Fatalf("Connect failed: %v", err) +} + +func (ts *TestState) PendingRepro(name string) []byte { + ts.t.Helper() + repro, err := ts.state.PendingRepro(name) + if err != nil { + ts.t.Fatalf("PendingRepro failed: %v", err) } - checkPendingRepro(t, st, "foo", "") - checkPendingRepro(t, st, "bar", "") + return repro +} + +func TestBasic(t *testing.T) { + st := MakeTestState(t) + defer st.Close() - if err := st.AddRepro("foo", []byte("open()")); err != nil { - t.Fatalf("AddRepro failed: %v", err) + if _, _, _, err := st.state.Sync("foo", nil, nil); err == nil { + t.Fatalf("synced with unconnected manager") } - checkPendingRepro(t, st, "foo", "") - checkPendingRepro(t, st, "bar", "open()") - checkPendingRepro(t, st, "bar", "") + calls := []string{"read", "write"} + st.Connect("foo", "", false, calls, nil) + st.Sync("foo", nil, nil) +} + +func TestRepro(t *testing.T) { + st := MakeTestState(t) + defer st.Close() + + st.Connect("foo", "", false, []string{"open", "read", "write"}, nil) + st.Connect("bar", "", false, []string{"open", "read", "close"}, nil) + + expectPendingRepro := func(name, result string) { + t.Helper() + repro := st.PendingRepro(name) + if string(repro) != result { + t.Fatalf("PendingRepro returned %q, want %q", string(repro), result) + } + } + expectPendingRepro("foo", "") + expectPendingRepro("bar", "") + st.AddRepro("foo", []byte("open()")) + expectPendingRepro("foo", "") + expectPendingRepro("bar", "open()") + expectPendingRepro("bar", "") // This repro is already present. - if err := st.AddRepro("bar", []byte("open()")); err != nil { - t.Fatalf("AddRepro failed: %v", err) - } - if err := st.AddRepro("bar", []byte("read()")); err != nil { - t.Fatalf("AddRepro failed: %v", err) - } - if err := st.AddRepro("bar", []byte("open()\nread()")); err != nil { - t.Fatalf("AddRepro failed: %v", err) - } + st.AddRepro("bar", []byte("open()")) + st.AddRepro("bar", []byte("read()")) + st.AddRepro("bar", []byte("open()\nread()")) // This does not satisfy foo's call set. - if err := st.AddRepro("bar", []byte("close()")); err != nil { - t.Fatalf("AddRepro failed: %v", err) - } - checkPendingRepro(t, st, "bar", "") + st.AddRepro("bar", []byte("close()")) + expectPendingRepro("bar", "") // Check how persistence works. - st, err = Make(dir) - if err != nil { - t.Fatalf("failed to make state: %v", err) - } - if err := st.Connect("foo", false, []string{"open", "read", "write"}, nil); err != nil { - t.Fatalf("Connect failed: %v", err) - } - if err := st.Connect("bar", false, []string{"open", "read", "close"}, nil); err != nil { - t.Fatalf("Connect failed: %v", err) - } - checkPendingRepro(t, st, "bar", "") - checkPendingRepro(t, st, "foo", "read()") - checkPendingRepro(t, st, "foo", "open()\nread()") - checkPendingRepro(t, st, "foo", "") + st.Reload() + st.Connect("foo", "", false, []string{"open", "read", "write"}, nil) + st.Connect("bar", "", false, []string{"open", "read", "close"}, nil) + expectPendingRepro("bar", "") + expectPendingRepro("foo", "read()") + expectPendingRepro("foo", "open()\nread()") + expectPendingRepro("foo", "") } -func checkPendingRepro(t *testing.T, st *State, name, result string) { - repro, err := st.PendingRepro(name) - if err != nil { - t.Fatalf("\n%v: PendingRepro failed: %v", caller(1), err) - } - if string(repro) != result { - t.Fatalf("\n%v: PendingRepro returned %q, want %q", caller(1), string(repro), result) - } -} +func TestDomain(t *testing.T) { + st := MakeTestState(t) + defer st.Close() -func caller(skip int) string { - _, file, line, _ := runtime.Caller(skip + 1) - return fmt.Sprintf("%v:%v", filepath.Base(file), line) + st.Connect("client0", "", false, []string{"open"}, nil) + st.Connect("client1", "domain1", false, []string{"open"}, nil) + st.Connect("client2", "domain2", false, []string{"open"}, nil) + st.Connect("client3", "domain3", false, []string{"open"}, nil) + { + domain, inputs, pending := st.Sync("client0", [][]byte{[]byte("open(0x0)")}, nil) + if domain != "" || len(inputs) != 0 || pending != 0 { + t.Fatalf("bad sync result: %v, %v, %v", domain, inputs, pending) + } + } + { + domain, inputs, pending := st.Sync("client0", [][]byte{[]byte("open(0x1)")}, nil) + if domain != "" || len(inputs) != 0 || pending != 0 { + t.Fatalf("bad sync result: %v, %v, %v", domain, inputs, pending) + } + } + { + domain, inputs, pending := st.Sync("client1", [][]byte{[]byte("open(0x2)"), []byte("open(0x1)")}, nil) + if domain != "domain1" || pending != 0 { + t.Fatalf("bad sync result: %v, %v, %v", domain, inputs, pending) + } + if diff := cmp.Diff(inputs, []rpctype.HubInput{ + {Domain: "", Prog: []byte("open(0x0)")}, + }); diff != "" { + t.Fatal(diff) + } + } + { + _, inputs, _ := st.Sync("client2", [][]byte{[]byte("open(0x3)")}, nil) + if diff := cmp.Diff(inputs, []rpctype.HubInput{ + {Domain: "", Prog: []byte("open(0x0)")}, + {Domain: "domain1", Prog: []byte("open(0x1)")}, + {Domain: "domain1", Prog: []byte("open(0x2)")}, + }); diff != "" { + t.Fatal(diff) + } + } + { + _, inputs, _ := st.Sync("client0", nil, nil) + if diff := cmp.Diff(inputs, []rpctype.HubInput{ + {Domain: "domain1", Prog: []byte("open(0x2)")}, + {Domain: "domain2", Prog: []byte("open(0x3)")}, + }); diff != "" { + t.Fatal(diff) + } + } + st.Reload() + st.Connect("client3", "domain3", false, []string{"open"}, nil) + { + _, inputs, _ := st.Sync("client3", nil, nil) + if diff := cmp.Diff(inputs, []rpctype.HubInput{ + {Domain: "", Prog: []byte("open(0x0)")}, + {Domain: "domain1", Prog: []byte("open(0x1)")}, + {Domain: "domain1", Prog: []byte("open(0x2)")}, + {Domain: "domain2", Prog: []byte("open(0x3)")}, + }); diff != "" { + t.Fatal(diff) + } + } } |
