[tools][fuzz] Use tmpfs for faster mutable data storage
Particularly when writing large corpora consisting of many small files,
minfs was acting as a performance bottleneck. This changes undercoat to
transparently map data/ paths to tmp/ paths behind the scenes.
With this change, the e2e test shows a 10x speedup when transferring a
2k element corpus.
Bug: 92637
Change-Id: Ief50ea60be24f5b9024b84e5354f44a0c87830ba
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/642353
Reviewed-by: Aaron Green <aarongreen@google.com>
Commit-Queue: Cameron Finucane <eep@google.com>
diff --git a/tools/fuzz/command_mock.go b/tools/fuzz/command_mock.go
index 0550541..37cea6a 100644
--- a/tools/fuzz/command_mock.go
+++ b/tools/fuzz/command_mock.go
@@ -35,12 +35,21 @@
}
fuzzerName := fmt.Sprintf("%s/%s", m[1], m[2])
- // Look up the artifact prefix to use
+ // Look up arguments that we want to test
var artifactPrefix string
+ var mergeFile string
+ var corpusPath string
for _, arg := range c.args[1:] {
+ // Save first non-option arg
+ if corpusPath == "" && !strings.HasPrefix(arg, "-") {
+ corpusPath = arg
+ continue
+ }
+
if parts := strings.Split(arg, "="); parts[0] == "-artifact_prefix" {
artifactPrefix = parts[1]
- break
+ } else if parts[0] == "-merge_control_file" {
+ mergeFile = parts[1]
}
}
if artifactPrefix == "" {
@@ -49,6 +58,11 @@
artifactLine := fmt.Sprintf("artifact_prefix='%s'; "+
"Test unit written to %scrash-1312", artifactPrefix, artifactPrefix)
+ if corpusPath == "" {
+ return nil, fmt.Errorf("run command missing output corpus dir: %q", c.args)
+ }
+ corpusLine := fmt.Sprintf("INFO: 4 files found in %s", corpusPath)
+
var output []string
switch fuzzerName {
case "foo/bar":
@@ -60,11 +74,20 @@
}
output = append(filler,
fmt.Sprintf("running %v", c.args),
+ corpusLine,
"==123==", // pid
"MS: ", // mut
"Deadly signal",
artifactLine,
)
+
+ // This wouldn't actually be emitted during a non-merge run, but we
+ // want to exercise an option with a path
+ if mergeFile != "" {
+ output = append(output,
+ fmt.Sprintf("MERGE-INNER: using the control file '%s'", mergeFile),
+ )
+ }
case "fail/nopid":
// No PID
output = []string{
diff --git a/tools/fuzz/fuzzer.go b/tools/fuzz/fuzzer.go
index 1fb9bde..1f5c55c 100644
--- a/tools/fuzz/fuzzer.go
+++ b/tools/fuzz/fuzzer.go
@@ -52,16 +52,35 @@
}
}
+// Map paths as referenced by ClusterFuzz to internally-used paths as seen by
+// libFuzzer, SFTP, etc.
+func translatePath(relpath string) string {
+ // Note: we can't use path.Join or other path functions that normalize the path
+ // because it will drop trailing slashes, which is important to preserve in
+ // places like artifact_prefix.
+
+ // Rewrite all references to data/ to tmp/ for better performance
+ if strings.HasPrefix(relpath, "data/") {
+ relpath = "tmp/" + strings.TrimPrefix(relpath, "data/")
+ }
+
+ return relpath
+}
+
// AbsPath returns the absolute target path for a given relative path in a
// fuzzer package. The path may differ depending on whether it is identified as
// a resource, data, or neither.
func (f *Fuzzer) AbsPath(relpath string) string {
+ relpath = translatePath(relpath)
+
if strings.HasPrefix(relpath, "/") {
return relpath
} else if strings.HasPrefix(relpath, "pkg/") {
return fmt.Sprintf("/pkgfs/packages/%s/0/%s", f.pkg, relpath[4:])
} else if strings.HasPrefix(relpath, "data/") {
return fmt.Sprintf("/data/r/sys/fuchsia.com:%s:0#meta:%s/%s", f.pkg, f.cmx, relpath[5:])
+ } else if strings.HasPrefix(relpath, "tmp/") {
+ return fmt.Sprintf("/tmp/r/sys/fuchsia.com:%s:0#meta:%s/%s", f.pkg, f.cmx, relpath[4:])
} else {
return fmt.Sprintf("/%s", relpath)
}
@@ -75,9 +94,9 @@
for _, arg := range args {
submatch := re.FindStringSubmatch(arg)
if submatch == nil {
- f.args = append(f.args, arg)
+ f.args = append(f.args, translatePath(arg))
} else {
- f.options[submatch[1]] = submatch[2]
+ f.options[submatch[1]] = translatePath(submatch[2])
}
}
}
@@ -230,7 +249,7 @@
}
// Clear any persistent data in the fuzzer's namespace, resetting its state
- dataPath := f.AbsPath("data/*")
+ dataPath := f.AbsPath("tmp/*")
if err := conn.Command("rm", "-rf", dataPath).Run(); err != nil {
return fmt.Errorf("error clear fuzzer data namespace %q: %s", dataPath, err)
}
@@ -250,11 +269,11 @@
// Ensure artifact_prefix will be writable, and fall back to default if not
// specified
if artPrefix, ok := f.options["artifact_prefix"]; ok {
- if !strings.HasPrefix(strings.TrimLeft(artPrefix, "/"), "data/") {
- return nil, fmt.Errorf("artifact_prefix not in data/ namespace: %q", artPrefix)
+ if !strings.HasPrefix(artPrefix, "tmp/") {
+ return nil, fmt.Errorf("artifact_prefix not in mutable namespace: %q", artPrefix)
}
} else {
- f.options["artifact_prefix"] = "data/"
+ f.options["artifact_prefix"] = "tmp/"
}
// The overall flow of fuzzer output data is as follows:
diff --git a/tools/fuzz/fuzzer_test.go b/tools/fuzz/fuzzer_test.go
index f38956b..856261d 100644
--- a/tools/fuzz/fuzzer_test.go
+++ b/tools/fuzz/fuzzer_test.go
@@ -21,7 +21,7 @@
absPaths := map[string]string{
"pkg/data/relpath": "/pkgfs/packages/foo/0/data/relpath",
"/pkg/data/relpath": "/pkg/data/relpath",
- "data/relpath": "/data/r/sys/fuchsia.com:foo:0#meta:bar.cmx/relpath",
+ "data/relpath": "/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/relpath",
"/data/relpath": "/data/relpath",
"relpath": "/relpath",
"/relpath": "/relpath",
@@ -94,7 +94,7 @@
t.Fatalf("failed to load fuzzer: %s", err)
}
- f.Parse(args)
+ f.Parse(append(args, "data/corpus"))
var outBuf bytes.Buffer
artifacts, err := f.Run(conn, &outBuf, "/some/artifactDir")
@@ -103,7 +103,8 @@
}
func TestRun(t *testing.T) {
- out, artifacts, err := runFuzzer(t, "foo/bar", nil, FuzzerNormal)
+ out, artifacts, err := runFuzzer(t, "foo/bar",
+ []string{"-merge_control_file=data/.mergefile"}, FuzzerNormal)
if err != nil {
t.Fatalf("failed to run fuzzer: %s", err)
}
@@ -119,7 +120,7 @@
}
// Check for artifact detection
- artifactAbsPath := "/data/r/sys/fuchsia.com:foo:0#meta:bar.cmx/crash-1312"
+ artifactAbsPath := "/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/crash-1312"
if !reflect.DeepEqual(artifacts, []string{artifactAbsPath}) {
t.Fatalf("unexpected artifact list: %s", artifacts)
}
@@ -128,12 +129,21 @@
if !strings.Contains(out, "/some/artifactDir/crash-1312") {
t.Fatalf("artifact prefix not rewritten: %q", out)
}
+
+ // Check that paths in libFuzzer options/args are translated
+ if !strings.Contains(out, "tmp/.mergefile") {
+ t.Fatalf("mergefile prefix not rewritten: %q", out)
+ }
+
+ if !strings.Contains(out, "tmp/corpus") {
+ t.Fatalf("corpus prefix not rewritten: %q", out)
+ }
}
func TestRunWithInvalidArtifactPrefix(t *testing.T) {
args := []string{"-artifact_prefix=foo/bar"}
_, _, err := runFuzzer(t, "foo/bar", args, FuzzerNormal)
- if err == nil || !strings.Contains(err.Error(), "artifact_prefix not in data/") {
+ if err == nil || !strings.Contains(err.Error(), "artifact_prefix not in mutable") {
t.Fatalf("expected failure to run but got: %s", err)
}
}
diff --git a/tools/fuzz/instance_test.go b/tools/fuzz/instance_test.go
index 38622f3..bcd1962 100644
--- a/tools/fuzz/instance_test.go
+++ b/tools/fuzz/instance_test.go
@@ -200,7 +200,7 @@
t.Fatalf("Error getting from instance: %s", err)
}
- expected := []string{"/data/r/sys/fuchsia.com:foo:0#meta:bar.cmx/path/to/remoteFile"}
+ expected := []string{"/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/path/to/remoteFile"}
got := i.Connector.(*mockConnector).PathsGot
if !reflect.DeepEqual(got, expected) {
t.Fatalf("incorrect file get list: %v", got)
@@ -211,7 +211,7 @@
t.Fatalf("Error putting to instance: %s", err)
}
- expected = []string{"/data/r/sys/fuchsia.com:foo:0#meta:bar.cmx/path/to/otherFile"}
+ expected = []string{"/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/path/to/otherFile"}
got = i.Connector.(*mockConnector).PathsPut
if !reflect.DeepEqual(got, expected) {
t.Fatalf("incorrect file put list: %v", got)
@@ -246,7 +246,8 @@
hostArtifactDir := "/art/dir"
var outBuf bytes.Buffer
- if err := i.RunFuzzer(&outBuf, "foo/bar", hostArtifactDir, "-artifact_prefix=data/wow/x"); err != nil {
+ args := []string{"-artifact_prefix=data/wow/x", "data/corpus"}
+ if err := i.RunFuzzer(&outBuf, "foo/bar", hostArtifactDir, args...); err != nil {
t.Fatalf("Error running fuzzer: %s", err)
}
@@ -255,7 +256,7 @@
t.Fatalf("fuzzer output missing host artifact path: %q", out)
}
- expected := []string{"/data/r/sys/fuchsia.com:foo:0#meta:bar.cmx/wow/xcrash-1312"}
+ expected := []string{"/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/wow/xcrash-1312"}
got := i.Connector.(*mockConnector).PathsGot
if !reflect.DeepEqual(got, expected) {
t.Fatalf("incorrect file get list: %v", got)