diff options
author | Dmitry Vyukov <dvyukov@google.com> | 2019-11-05 14:07:53 +0100 |
---|---|---|
committer | Dmitry Vyukov <dvyukov@google.com> | 2019-11-06 11:41:05 +0100 |
commit | 424cf6e8a12577719dc310bce2cce2a91723cf54 (patch) | |
tree | c7d350c5cdc1e116d4502ac434229b2ffd88f3aa | |
parent | c487cd4633a98235359d6084383d8c7ea49600bc (diff) | |
download | syzkaller-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.go | 29 | ||||
-rw-r--r-- | pkg/build/linux.go | 30 | ||||
-rw-r--r-- | pkg/build/linux_test.go | 82 | ||||
-rw-r--r-- | pkg/instance/instance.go | 2 | ||||
-rw-r--r-- | syz-ci/manager.go | 2 |
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), |