aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/build/linux.go52
-rw-r--r--pkg/manager/diff.go48
-rw-r--r--pkg/manager/diff_test.go47
-rw-r--r--syz-cluster/workflow/build-step/main.go31
-rw-r--r--syz-cluster/workflow/fuzz-step/main.go47
-rw-r--r--tools/syz-diff/diff.go2
6 files changed, 206 insertions, 21 deletions
diff --git a/pkg/build/linux.go b/pkg/build/linux.go
index bb5de4f28..e19eec60e 100644
--- a/pkg/build/linux.go
+++ b/pkg/build/linux.go
@@ -10,6 +10,7 @@ import (
"debug/elf"
"encoding/hex"
"fmt"
+ "io"
"os"
"path"
"path/filepath"
@@ -257,6 +258,57 @@ func queryLinuxCompiler(kernelDir string) (string, error) {
return string(result[1]), nil
}
+// ElfSymbolHashes returns a map of sha256 hashes per a symbol contained in the elf file.
+// It's best to call it on vmlinux.o since PCs in the binary code are not patched yet.
+func ElfSymbolHashes(bin string) (map[string]string, error) {
+ file, err := elf.Open(bin)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ symbols, err := file.Symbols()
+ if err != nil {
+ return nil, err
+ }
+
+ textSection := file.Section(".text")
+ if textSection == nil {
+ return nil, fmt.Errorf(".text section not found")
+ }
+
+ sectionReader, ok := textSection.Open().(io.ReaderAt)
+ if !ok {
+ return nil, fmt.Errorf(".text section reader does not support ReadAt")
+ }
+
+ hashes := make(map[string]string)
+ for _, s := range symbols {
+ if elf.ST_TYPE(s.Info) != elf.STT_FUNC || s.Size == 0 {
+ continue
+ }
+
+ if s.Section >= elf.SHN_LORESERVE || int(s.Section) >= len(file.Sections) ||
+ file.Sections[s.Section] != textSection {
+ continue
+ }
+
+ offset := s.Value - textSection.Addr
+ if offset+s.Size > textSection.Size {
+ continue
+ }
+
+ code := make([]byte, s.Size)
+ _, err := sectionReader.ReadAt(code, int64(offset))
+ if err != nil {
+ continue
+ }
+ hash := sha256.Sum256(code)
+ hashes[s.Name] = hex.EncodeToString(hash[:])
+ }
+ return hashes, nil
+}
+
// elfBinarySignature calculates signature of an elf binary aiming at runtime behavior
// (text/data, debug info is ignored).
func elfBinarySignature(bin string, tracer debugtracer.DebugTracer) (string, error) {
diff --git a/pkg/manager/diff.go b/pkg/manager/diff.go
index 64d0af444..aa640515d 100644
--- a/pkg/manager/diff.go
+++ b/pkg/manager/diff.go
@@ -297,9 +297,9 @@ func (dc *diffContext) monitorPatchedCoverage(ctx context.Context) error {
return nil
}
focusAreaStats := dc.new.progsPerArea()
- if focusAreaStats[modifiedArea]+focusAreaStats[includesArea] > 0 {
- log.Logf(0, "fuzzer has reached the modified code (%d + %d), continuing fuzzing",
- focusAreaStats[modifiedArea], focusAreaStats[includesArea])
+ if focusAreaStats[symbolsArea]+focusAreaStats[filesArea]+focusAreaStats[includesArea] > 0 {
+ log.Logf(0, "fuzzer has reached the modified code (%d + %d + %d), continuing fuzzing",
+ focusAreaStats[symbolsArea], focusAreaStats[filesArea], focusAreaStats[includesArea])
return nil
}
log.Logf(0, "fuzzer has not reached the modified code in %s, aborting",
@@ -721,18 +721,32 @@ func (rr *reproRunner) Run(ctx context.Context, r *repro.Result) {
}
const (
- modifiedArea = "modified"
+ symbolsArea = "symbols"
+ filesArea = "files"
includesArea = "included"
)
-func PatchFocusAreas(cfg *mgrconfig.Config, gitPatches [][]byte) {
+func PatchFocusAreas(cfg *mgrconfig.Config, gitPatches [][]byte, baseHashes, patchedHashes map[string]string) {
+ funcs := modifiedSymbols(baseHashes, patchedHashes)
+ if len(funcs) > 0 {
+ log.Logf(0, "adding modified_functions to focus areas: %q", funcs)
+ cfg.Experimental.FocusAreas = append(cfg.Experimental.FocusAreas,
+ mgrconfig.FocusArea{
+ Name: symbolsArea,
+ Filter: mgrconfig.CovFilterCfg{
+ Functions: funcs,
+ },
+ Weight: 6.0,
+ })
+ }
+
direct, transitive := affectedFiles(cfg, gitPatches)
if len(direct) > 0 {
sort.Strings(direct)
- log.Logf(0, "adding directly modified files to focus_order: %q", direct)
+ log.Logf(0, "adding directly modified files to focus areas: %q", direct)
cfg.Experimental.FocusAreas = append(cfg.Experimental.FocusAreas,
mgrconfig.FocusArea{
- Name: modifiedArea,
+ Name: filesArea,
Filter: mgrconfig.CovFilterCfg{
Files: direct,
},
@@ -742,7 +756,7 @@ func PatchFocusAreas(cfg *mgrconfig.Config, gitPatches [][]byte) {
if len(transitive) > 0 {
sort.Strings(transitive)
- log.Logf(0, "adding transitively affected to focus_order: %q", transitive)
+ log.Logf(0, "adding transitively affected to focus areas: %q", transitive)
cfg.Experimental.FocusAreas = append(cfg.Experimental.FocusAreas,
mgrconfig.FocusArea{
Name: includesArea,
@@ -808,3 +822,21 @@ func affectedFiles(cfg *mgrconfig.Config, gitPatches [][]byte) (direct, transiti
}
return
}
+
+// If there are too many different symbols, they are no longer specific enough.
+// Don't use them to focus the fuzzer.
+const modifiedSymbolThreshold = 0.05
+
+func modifiedSymbols(baseHashes, patchedHashes map[string]string) []string {
+ var ret []string
+ for name, hash := range patchedHashes {
+ if baseHash, ok := baseHashes[name]; !ok || baseHash != hash {
+ ret = append(ret, name)
+ if float64(len(ret)) > float64(len(patchedHashes))*modifiedSymbolThreshold {
+ return nil
+ }
+ }
+ }
+ sort.Strings(ret)
+ return ret
+}
diff --git a/pkg/manager/diff_test.go b/pkg/manager/diff_test.go
index f2408f13b..9e27dc288 100644
--- a/pkg/manager/diff_test.go
+++ b/pkg/manager/diff_test.go
@@ -4,6 +4,7 @@
package manager
import (
+ "fmt"
"testing"
"github.com/google/syzkaller/pkg/mgrconfig"
@@ -24,6 +25,10 @@ int main(void) { }`,
"c.c": `int main(void) { }`,
}))
+ baseHashes, patchedHashes := dummySymbolHashes(), dummySymbolHashes()
+ baseHashes["function"] = "hash1"
+ patchedHashes["function"] = "hash2"
+
PatchFocusAreas(cfg, [][]byte{
[]byte(`diff --git a/b.c b/b.c
index 103167d..fbf7a68 100644
@@ -34,7 +39,7 @@ index 103167d..fbf7a68 100644
\ No newline at end of file
+int main(void) { return 1; }
\ No newline at end of file`),
- // Also, emulate an update to te header.h.
+ // Also, emulate an update to header.h.
[]byte(`diff --git a/header.h b/header.h
index 103167d..fbf7a68 100644
--- a/header.h
@@ -44,11 +49,18 @@ index 103167d..fbf7a68 100644
\ No newline at end of file
+Test2
\ No newline at end of file`),
- })
+ }, baseHashes, patchedHashes)
assert.Equal(t, []mgrconfig.FocusArea{
{
- Name: modifiedArea,
+ Name: symbolsArea,
+ Filter: mgrconfig.CovFilterCfg{
+ Functions: []string{"function"},
+ },
+ Weight: 6.0,
+ },
+ {
+ Name: filesArea,
Filter: mgrconfig.CovFilterCfg{
Files: []string{"b.c", "header.h"},
},
@@ -66,3 +78,32 @@ index 103167d..fbf7a68 100644
},
}, cfg.Experimental.FocusAreas)
}
+
+func dummySymbolHashes() map[string]string {
+ ret := map[string]string{}
+ for i := 0; i < 100; i++ {
+ ret[fmt.Sprint(i)] = fmt.Sprint(i)
+ }
+ return ret
+}
+
+func TestModifiedSymbols(t *testing.T) {
+ t.Run("too many changed", func(t *testing.T) {
+ ret := modifiedSymbols(map[string]string{
+ "functionA": "hash1",
+ "functionB": "hash2",
+ }, map[string]string{
+ "functionA": "hash1",
+ "functionB": "hash is not hash2",
+ })
+ assert.Empty(t, ret)
+ })
+ t.Run("less than threshold", func(t *testing.T) {
+ base, patched := dummySymbolHashes(), dummySymbolHashes()
+ base["function"] = "hash1"
+ patched["function"] = "hash2"
+ base["function2"] = "hash1"
+ patched["function2"] = "hash2"
+ assert.Equal(t, []string{"function", "function2"}, modifiedSymbols(base, patched))
+ })
+}
diff --git a/syz-cluster/workflow/build-step/main.go b/syz-cluster/workflow/build-step/main.go
index 1abf5bc4a..3797681ea 100644
--- a/syz-cluster/workflow/build-step/main.go
+++ b/syz-cluster/workflow/build-step/main.go
@@ -10,6 +10,10 @@ import (
"errors"
"flag"
"fmt"
+ "log"
+ "os"
+ "path/filepath"
+
"github.com/google/syzkaller/pkg/build"
"github.com/google/syzkaller/pkg/debugtracer"
"github.com/google/syzkaller/pkg/osutil"
@@ -18,9 +22,6 @@ import (
"github.com/google/syzkaller/syz-cluster/pkg/api"
"github.com/google/syzkaller/syz-cluster/pkg/app"
"github.com/google/syzkaller/syz-cluster/pkg/triage"
- "log"
- "os"
- "path/filepath"
)
var (
@@ -261,8 +262,14 @@ func buildKernel(tracer debugtracer.DebugTracer, req *api.BuildRequest) (*BuildR
return nil, err
}
tracer.Log("build finished successfully")
+
+ err = saveSymbolHashes(tracer)
+ if err != nil {
+ tracer.Log("failed to save symbol hashes: %s", err)
+ }
// Note: Output directory has the following structure:
// |-- image
+ // |-- symbol_hashes.json
// |-- kernel
// |-- kernel.config
// `-- obj
@@ -270,6 +277,24 @@ func buildKernel(tracer debugtracer.DebugTracer, req *api.BuildRequest) (*BuildR
return ret, nil
}
+func saveSymbolHashes(tracer debugtracer.DebugTracer) error {
+ hashes, err := build.ElfSymbolHashes(filepath.Join(*flagRepository, "vmlinux.o"))
+ if err != nil {
+ return fmt.Errorf("failed to query symbol hashes: %w", err)
+ }
+ tracer.Log("extracted hashes for %d symbols", len(hashes))
+ file, err := os.Create(filepath.Join(*flagOutput, "symbol_hashes.json"))
+ if err != nil {
+ return fmt.Errorf("failed to open symbol_hashes.json: %w", err)
+ }
+ defer file.Close()
+ err = json.NewEncoder(file).Encode(hashes)
+ if err != nil {
+ return fmt.Errorf("failed to serialize: %w", err)
+ }
+ return nil
+}
+
func ensureFlags(args ...string) {
for i := 0; i+1 < len(args); i += 2 {
if args[i] == "" {
diff --git a/syz-cluster/workflow/fuzz-step/main.go b/syz-cluster/workflow/fuzz-step/main.go
index 6996ae8a0..f7ed5ef39 100644
--- a/syz-cluster/workflow/fuzz-step/main.go
+++ b/syz-cluster/workflow/fuzz-step/main.go
@@ -10,6 +10,12 @@ import (
"errors"
"flag"
"fmt"
+ "io"
+ "net/http"
+ "os"
+ "path/filepath"
+ "time"
+
"github.com/google/syzkaller/pkg/config"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/manager"
@@ -19,11 +25,6 @@ import (
"github.com/google/syzkaller/syz-cluster/pkg/api"
"github.com/google/syzkaller/syz-cluster/pkg/app"
"golang.org/x/sync/errgroup"
- "io"
- "net/http"
- "os"
- "path/filepath"
- "time"
)
var (
@@ -93,7 +94,12 @@ func run(baseCtx context.Context, client *api.Client, timeout time.Duration,
if err != nil {
return fmt.Errorf("failed to load configs: %w", err)
}
- manager.PatchFocusAreas(patched, series.PatchBodies())
+
+ baseSymbols, patchedSymbols, err := readSymbolHashes()
+ if err != nil {
+ app.Errorf("failed to read symbol hashes: %v", err)
+ }
+ manager.PatchFocusAreas(patched, series.PatchBodies(), baseSymbols, patchedSymbols)
if *flagCorpusURL != "" {
err := downloadCorpus(baseCtx, patched.Workdir, *flagCorpusURL)
@@ -282,6 +288,35 @@ func reportFinding(ctx context.Context, client *api.Client, bug *manager.UniqueB
return client.UploadFinding(ctx, finding)
}
+func readSymbolHashes() (base, patched map[string]string, err error) {
+ // These are saved by the build step.
+ base, err = readJSONMap("/base/symbol_hashes.json")
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to read base hashes: %w", err)
+ }
+ patched, err = readJSONMap("/patched/symbol_hashes.json")
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to read patched hashes: %w", err)
+ }
+ log.Logf(0, "extracted %d symbol hashes for base and %d for patched", len(base), len(patched))
+ return
+}
+
+func readJSONMap(file string) (map[string]string, error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ var data map[string]string
+ err = json.NewDecoder(f).Decode(&data)
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+}
+
func compressArtifacts(dir string) (io.Reader, error) {
var buf bytes.Buffer
lw := &LimitedWriter{
diff --git a/tools/syz-diff/diff.go b/tools/syz-diff/diff.go
index c77728d4a..3e7230fe0 100644
--- a/tools/syz-diff/diff.go
+++ b/tools/syz-diff/diff.go
@@ -43,7 +43,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
- manager.PatchFocusAreas(newCfg, [][]byte{data})
+ manager.PatchFocusAreas(newCfg, [][]byte{data}, nil, nil)
}
ctx := vm.ShutdownCtx()