Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions pkg/build/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"debug/elf"
"encoding/hex"
"fmt"
"io"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -260,55 +259,80 @@ 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.
type SectionHashes struct {
Text map[string]string `json:"text"`
Data map[string]string `json:"data"` // Merged .data and .rodata.
}

// ElfSymbolHashes returns a map of sha256 hashes per section per 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) {
func ElfSymbolHashes(bin string) (SectionHashes, error) {
result := SectionHashes{
Text: make(map[string]string),
Data: make(map[string]string),
}

file, err := elf.Open(bin)
if err != nil {
return nil, err
return SectionHashes{}, err
}
defer file.Close()

symbols, err := file.Symbols()
if err != nil {
return nil, err
return SectionHashes{}, err
}

textSection := file.Section(".text")
if textSection == nil {
return nil, fmt.Errorf(".text section not found")
rawFile, err := os.Open(bin)
if err != nil {
return SectionHashes{}, err
}
defer rawFile.Close()

sectionReader, ok := textSection.Open().(io.ReaderAt)
if !ok {
return nil, fmt.Errorf(".text section reader does not support ReadAt")
sections := make(map[elf.SectionIndex]*elf.Section)
for i, s := range file.Sections {
sections[elf.SectionIndex(i)] = s
}

hashes := make(map[string]string)
for _, s := range symbols {
if elf.ST_TYPE(s.Info) != elf.STT_FUNC || s.Size == 0 {
if s.Name == "" || s.Size == 0 || s.Section >= elf.SHN_LORESERVE {
continue
}

if s.Section >= elf.SHN_LORESERVE || int(s.Section) >= len(file.Sections) ||
file.Sections[s.Section] != textSection {
symbolSection, ok := sections[s.Section]
if !ok || symbolSection.Type == elf.SHT_NOBITS {
continue
}

offset := s.Value - textSection.Addr
if offset+s.Size > textSection.Size {
var targetMap map[string]string

symbolType := elf.ST_TYPE(s.Info)
sectionFlags := symbolSection.Flags
switch {
case symbolType == elf.STT_FUNC && (sectionFlags&elf.SHF_EXECINSTR) != 0:
targetMap = result.Text
case symbolType == elf.STT_OBJECT && (sectionFlags&elf.SHF_ALLOC) != 0 &&
(sectionFlags&elf.SHF_EXECINSTR) == 0:
targetMap = result.Data
default:
continue
}

code := make([]byte, s.Size)
_, err := sectionReader.ReadAt(code, int64(offset))
offset := s.Value - symbolSection.Addr
if offset+s.Size > symbolSection.Size {
continue
}

data := make([]byte, s.Size)
_, err := rawFile.ReadAt(data, int64(symbolSection.Offset+offset))
if err != nil {
continue
}
hash := sha256.Sum256(code)
hashes[s.Name] = hex.EncodeToString(hash[:])

hash := sha256.Sum256(data)
targetMap[s.Name] = hex.EncodeToString(hash[:])
}
return hashes, nil
return result, nil
}

// elfBinarySignature calculates signature of an elf binary aiming at runtime behavior
Expand Down
3 changes: 2 additions & 1 deletion syz-cluster/workflow/build-step/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ func saveSymbolHashes(tracer debugtracer.DebugTracer) error {
if err != nil {
return fmt.Errorf("failed to query symbol hashes: %w", err)
}
tracer.Log("extracted hashes for %d symbols", len(hashes))
tracer.Log("extracted hashes for %d text symbols and %d data symbols",
len(hashes.Text), len(hashes.Data))
file, err := os.Create(filepath.Join(*flagOutput, "symbol_hashes.json"))
if err != nil {
return fmt.Errorf("failed to open symbol_hashes.json: %w", err)
Expand Down
50 changes: 34 additions & 16 deletions syz-cluster/workflow/fuzz-step/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"path/filepath"
"time"

"github.com/google/syzkaller/pkg/build"
"github.com/google/syzkaller/pkg/config"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/manager"
Expand Down Expand Up @@ -108,7 +109,7 @@ func run(baseCtx context.Context, client *api.Client, timeout time.Duration,
if shouldSkipFuzzing(baseSymbols, patchedSymbols) {
return errSkipFuzzing
}
manager.PatchFocusAreas(patched, series.PatchBodies(), baseSymbols, patchedSymbols)
manager.PatchFocusAreas(patched, series.PatchBodies(), baseSymbols.Text, patchedSymbols.Text)

if *flagCorpusURL != "" {
err := downloadCorpus(baseCtx, patched.Workdir, *flagCorpusURL)
Expand Down Expand Up @@ -311,15 +312,32 @@ func reportFinding(ctx context.Context, client *api.Client, bug *manager.UniqueB
return client.UploadFinding(ctx, finding)
}

func shouldSkipFuzzing(baseSymbols, patchedSymbols map[string]string) bool {
if len(baseSymbols) == 0 || len(patchedSymbols) == 0 {
var ignoreLinuxVariables = map[string]bool{
"raw_data": true, // from arch/x86/entry/vdso/vdso-image
// Build versions / timestamps.
"linux_banner": true,
"vermagic": true,
"init_uts_ns": true,
}

func shouldSkipFuzzing(base, patched build.SectionHashes) bool {
if len(base.Text) == 0 || len(patched.Text) == 0 {
// Likely, something went wrong during the kernel build step.
log.Logf(0, "skipped the binary equality check because some of them have 0 symbols")
return false
}
same := len(baseSymbols) == len(patchedSymbols)
for name, hash := range baseSymbols {
if patchedSymbols[name] != hash {
same := len(base.Text) == len(patched.Text) && len(base.Data) == len(patched.Data)
// For .text, demand all symbols to be equal.
for name, hash := range base.Text {
if patched.Text[name] != hash {
same = false
break
}
}
// For data sections ignore some of them.
for name, hash := range base.Data {
if !ignoreLinuxVariables[name] && patched.Data[name] != hash {
log.Logf(1, "symbol %q has different values in base vs patch", name)
same = false
break
}
Expand All @@ -332,31 +350,31 @@ func shouldSkipFuzzing(baseSymbols, patchedSymbols map[string]string) bool {
return false
}

func readSymbolHashes() (base, patched map[string]string, err error) {
func readSymbolHashes() (base, patched build.SectionHashes, err error) {
// These are saved by the build step.
base, err = readJSONMap("/base/symbol_hashes.json")
base, err = readSectionHashes("/base/symbol_hashes.json")
if err != nil {
return nil, nil, fmt.Errorf("failed to read base hashes: %w", err)
return build.SectionHashes{}, build.SectionHashes{}, fmt.Errorf("failed to read base hashes: %w", err)
}
patched, err = readJSONMap("/patched/symbol_hashes.json")
patched, err = readSectionHashes("/patched/symbol_hashes.json")
if err != nil {
return nil, nil, fmt.Errorf("failed to read patched hashes: %w", err)
return build.SectionHashes{}, build.SectionHashes{}, 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))
log.Logf(0, "extracted %d text symbol hashes for base and %d for patched", len(base.Text), len(patched.Text))
return
}

func readJSONMap(file string) (map[string]string, error) {
func readSectionHashes(file string) (build.SectionHashes, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
return build.SectionHashes{}, err
}
defer f.Close()

var data map[string]string
var data build.SectionHashes
err = json.NewDecoder(f).Decode(&data)
if err != nil {
return nil, err
return build.SectionHashes{}, err
}
return data, nil
}
Expand Down
81 changes: 73 additions & 8 deletions syz-cluster/workflow/fuzz-step/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
package main

import (
"encoding/json"
"io/fs"
"os"
"path/filepath"
"testing"

"github.com/google/syzkaller/pkg/build"
"github.com/google/syzkaller/pkg/osutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestConfigLoad(t *testing.T) {
Expand All @@ -29,26 +34,86 @@ func TestConfigLoad(t *testing.T) {
})
}

func TestReadSectionHashes(t *testing.T) {
hashes := build.SectionHashes{
Text: map[string]string{"A": "1"},
Data: map[string]string{"B": "2"},
}

jsonData, err := json.Marshal(hashes)
require.NoError(t, err)

file, err := osutil.WriteTempFile(jsonData)
require.NoError(t, err)
defer os.Remove(file)

fromFile, err := readSectionHashes(file)
require.NoError(t, err)
assert.Equal(t, hashes, fromFile)
}

// nolint: dupl
func TestShouldSkipFuzzing(t *testing.T) {
t.Run("one empty", func(t *testing.T) {
assert.False(t, shouldSkipFuzzing(nil, map[string]string{"A": "1"}))
assert.False(t, shouldSkipFuzzing(
build.SectionHashes{},
build.SectionHashes{
Text: map[string]string{"A": "1"},
},
))
})
t.Run("equal symbols", func(t *testing.T) {
assert.True(t, shouldSkipFuzzing(
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
Data: map[string]string{"C": "1", "D": "2"},
},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
Data: map[string]string{"C": "1", "D": "2"},
},
))
})
t.Run("equal", func(t *testing.T) {
t.Run("ignore known variables", func(t *testing.T) {
assert.True(t, shouldSkipFuzzing(
map[string]string{"A": "1", "B": "2"},
map[string]string{"A": "1", "B": "2"},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
Data: map[string]string{"C": "1", "raw_data": "A", "vermagic": "A"},
},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
Data: map[string]string{"C": "1", "raw_data": "B", "vermagic": "B"},
},
))
})
t.Run("same len, different hashes", func(t *testing.T) {
assert.False(t, shouldSkipFuzzing(
map[string]string{"A": "1", "B": "2"},
map[string]string{"A": "1", "B": "different"},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "different"},
},
))
assert.False(t, shouldSkipFuzzing(
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
Data: map[string]string{"C": "1", "D": "2"},
},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
Data: map[string]string{"C": "1", "D": "different"},
},
))
})
t.Run("different len, same hashes", func(t *testing.T) {
assert.False(t, shouldSkipFuzzing(
map[string]string{"A": "1", "B": "2", "C": "3"},
map[string]string{"A": "1", "B": "2"},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2"},
},
build.SectionHashes{
Text: map[string]string{"A": "1", "B": "2", "C": "new"},
},
))
})
}