aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2019-11-05 14:07:53 +0100
committerDmitry Vyukov <dvyukov@google.com>2019-11-06 11:41:05 +0100
commit424cf6e8a12577719dc310bce2cce2a91723cf54 (patch)
treec7d350c5cdc1e116d4502ac434229b2ffd88f3aa
parentc487cd4633a98235359d6084383d8c7ea49600bc (diff)
downloadsyzkaller-424cf6e8a12577719dc310bce2cce2a91723cf54.tar.gz
pkg/build: add build signatures
Add optional build signature for images, currently only implemented for linux. This can be used in bisection process to detect changes that does not affect kernel. Update #1271
-rw-r--r--pkg/build/build.go29
-rw-r--r--pkg/build/linux.go30
-rw-r--r--pkg/build/linux_test.go82
-rw-r--r--pkg/instance/instance.go2
-rw-r--r--syz-ci/manager.go2
5 files changed, 138 insertions, 7 deletions
diff --git a/pkg/build/build.go b/pkg/build/build.go
index a34d1e006..6572efd4d 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -40,22 +40,37 @@ type Params struct {
// - kernel.config: actual kernel config used during build
// - obj/: directory with kernel object files (this should match KernelObject
// specified in sys/targets, e.g. vmlinux for linux)
-func Image(params *Params) error {
+// The returned string is a kernel ID that will be the same for kernels with the
+// same runtime behavior, and different for kernels with different runtime
+// behavior. Binary equal builds, or builds that differ only in e.g. debug info,
+// have the same ID. The ID may be empty if OS implementation does not have
+// a way to calculate such IDs.
+func Image(params *Params) (string, error) {
builder, err := getBuilder(params.TargetOS, params.TargetArch, params.VMType)
if err != nil {
- return err
+ return "", err
}
if err := osutil.MkdirAll(filepath.Join(params.OutputDir, "obj")); err != nil {
- return err
+ return "", err
}
if len(params.Config) != 0 {
// Write kernel config early, so that it's captured on build failures.
if err := osutil.WriteFile(filepath.Join(params.OutputDir, "kernel.config"), params.Config); err != nil {
- return fmt.Errorf("failed to write config file: %v", err)
+ return "", fmt.Errorf("failed to write config file: %v", err)
}
}
err = builder.build(params)
- return extractRootCause(err)
+ if err != nil {
+ return "", extractRootCause(err)
+ }
+ sign := ""
+ if signer, ok := builder.(signer); ok {
+ sign, err = signer.sign(params)
+ if err != nil {
+ return "", err
+ }
+ }
+ return sign, nil
}
func Clean(targetOS, targetArch, vmType, kernelDir string) error {
@@ -75,6 +90,10 @@ type builder interface {
clean(kernelDir, targetArch string) error
}
+type signer interface {
+ sign(params *Params) (string, error)
+}
+
func getBuilder(targetOS, targetArch, vmType string) (builder, error) {
var supported = []struct {
OS string
diff --git a/pkg/build/linux.go b/pkg/build/linux.go
index aafb33867..a74f056c0 100644
--- a/pkg/build/linux.go
+++ b/pkg/build/linux.go
@@ -10,7 +10,11 @@
package build
import (
+ "crypto/sha1"
+ "debug/elf"
+ "encoding/hex"
"fmt"
+ "io"
"io/ioutil"
"os"
"path/filepath"
@@ -139,3 +143,29 @@ func runMake(kernelDir string, args ...string) error {
_, err := osutil.Run(time.Hour, cmd)
return err
}
+
+// elfBinarySignature calculates signature of an elf binary aiming at runtime behavior
+// (text/data, debug info is ignored).
+func elfBinarySignature(bin string) (string, error) {
+ f, err := os.Open(bin)
+ if err != nil {
+ return "", fmt.Errorf("failed to open binary for signature: %v", err)
+ }
+ ef, err := elf.NewFile(f)
+ if err != nil {
+ return "", fmt.Errorf("failed to open elf binary: %v", err)
+ }
+ hasher := sha1.New()
+ for _, sec := range ef.Sections {
+ // Hash allocated sections (e.g. no debug info as it's not allocated)
+ // with file data (e.g. no bss). We also ignore .notes section as it
+ // contains some small changing binary blob that seems irrelevant.
+ // It's unclear if it's better to check NOTE type,
+ // or ".notes" name or !PROGBITS type.
+ if sec.Flags&elf.SHF_ALLOC == 0 || sec.Type == elf.SHT_NOBITS || sec.Type == elf.SHT_NOTE {
+ continue
+ }
+ io.Copy(hasher, sec.Open())
+ }
+ return hex.EncodeToString(hasher.Sum(nil)), nil
+}
diff --git a/pkg/build/linux_test.go b/pkg/build/linux_test.go
new file mode 100644
index 000000000..a3fae056a
--- /dev/null
+++ b/pkg/build/linux_test.go
@@ -0,0 +1,82 @@
+// Copyright 2019 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+// +build linux
+
+package build
+
+import (
+ "bytes"
+ "os"
+ "testing"
+ "text/template"
+
+ "github.com/google/syzkaller/pkg/osutil"
+)
+
+func TestElfBinarySignature(t *testing.T) {
+ enumerateFlags(t, nil, []string{"-g", "-O1", "-O2", "-no-pie", "-static"})
+}
+
+func enumerateFlags(t *testing.T, flags, allFlags []string) {
+ if len(allFlags) != 0 {
+ enumerateFlags(t, flags, allFlags[1:])
+ enumerateFlags(t, append(flags, allFlags[0]), allFlags[1:])
+ return
+ }
+ t.Logf("testing: %+v", flags)
+ sign1 := sign(t, flags, false, false)
+ sign2 := sign(t, flags, false, true)
+ sign3 := sign(t, flags, true, false)
+ if sign1 != sign2 {
+ t.Errorf("signature has changed after a comment-only change")
+ }
+ if sign1 == sign3 {
+ t.Errorf("signature has not changed after a change")
+ }
+
+ //func elfBinarySignature(bin string) (string, error) {
+}
+
+func sign(t *testing.T, flags []string, changed, comment bool) string {
+ buf := new(bytes.Buffer)
+ if err := srcTemplate.Execute(buf, SrcParams{Changed: changed, Comment: comment}); err != nil {
+ t.Fatal(err)
+ }
+ src := buf.Bytes()
+ bin, err := osutil.TempFile("syz-build-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(bin)
+ cmd := osutil.Command("gcc", append(flags, "-pthread", "-o", bin, "-x", "c", "-")...)
+ cmd.Stdin = buf
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("compiler failed: %v\n%s\n\n%s", err, src, out)
+ }
+ sign, err := elfBinarySignature(bin)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return sign
+}
+
+type SrcParams struct {
+ Changed bool
+ Comment bool
+}
+
+var srcTemplate = template.Must(template.New("").Parse(`
+#include <stdio.h>
+#include <pthread.h>
+
+int main() {
+ int x = {{if .Changed}}0{{else}}1{{end}};
+ {{if .Comment}}
+ // Some comment goes here.
+ // It affects line numbers in debug info.
+ {{end}}
+ printf("%d %p\n", x, pthread_create);
+}
+`))
diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go
index 163ef285a..e5370d7ee 100644
--- a/pkg/instance/instance.go
+++ b/pkg/instance/instance.go
@@ -103,7 +103,7 @@ func (env *env) BuildKernel(compilerBin, userspaceDir, cmdlineFile, sysctlFile s
SysctlFile: sysctlFile,
Config: kernelConfig,
}
- if err := build.Image(params); err != nil {
+ if _, err := build.Image(params); err != nil {
return "", err
}
if err := SetConfigImage(env.cfg, imageDir, true); err != nil {
diff --git a/syz-ci/manager.go b/syz-ci/manager.go
index 4dacc494e..1b9e6ce1e 100644
--- a/syz-ci/manager.go
+++ b/syz-ci/manager.go
@@ -302,7 +302,7 @@ func (mgr *Manager) build(kernelCommit *vcs.Commit) error {
SysctlFile: mgr.mgrcfg.KernelSysctl,
Config: mgr.configData,
}
- if err := build.Image(params); err != nil {
+ if _, err := build.Image(params); err != nil {
if buildErr, ok := err.(build.KernelBuildError); ok {
rep := &report.Report{
Title: fmt.Sprintf("%v build error", mgr.mgrcfg.RepoAlias),