diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-09-20 21:18:36 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2017-09-20 21:19:29 +0200 |
| commit | 8cb7d3dcfcbe11a6d5682743816409d1c8e8f6a0 (patch) | |
| tree | 75e6dd62ae61ce7986cf4e0e04e9954586033635 | |
| parent | d606e60dfe3d50499812f7d740dae6e727fa9f76 (diff) | |
all: initial support for fuchsia
Nothing works, but builds.
Update #191
32 files changed, 1143 insertions, 923 deletions
diff --git a/executor/executor_fuchsia.cc b/executor/executor_fuchsia.cc index 6a312167b..3a214e4ac 100644 --- a/executor/executor_fuchsia.cc +++ b/executor/executor_fuchsia.cc @@ -3,7 +3,10 @@ // +build +#include "syscalls_fuchsia.h" + int main() { + (void)syscalls; return 0; } diff --git a/executor/executor_linux.cc b/executor/executor_linux.cc index cd0e406ff..d428fc1ea 100644 --- a/executor/executor_linux.cc +++ b/executor/executor_linux.cc @@ -26,7 +26,7 @@ #include <time.h> #include <unistd.h> -#include "syscalls.h" +#include "syscalls_linux.h" #define SYZ_EXECUTOR #include "common.h" diff --git a/executor/syscalls_fuchsia.h b/executor/syscalls_fuchsia.h new file mode 100644 index 000000000..6919aec79 --- /dev/null +++ b/executor/syscalls_fuchsia.h @@ -0,0 +1,26 @@ +// AUTOGENERATED FILE + +struct call_t { + const char* name; + int sys_nr; +}; + +#if defined(__x86_64__) || 0 +#define GOARCH "amd64" +#define SYZ_REVISION "d65b9adb4853817be6f471df44fc8347ebf6dfc6" + +static call_t syscalls[] = { + {"mx_time_get", 0}, + +}; +#endif + +#if defined(__aarch64__) || 0 +#define GOARCH "arm64" +#define SYZ_REVISION "bd2655e6d85f1fecceb1648649e1ad5adda81dc8" + +static call_t syscalls[] = { + {"mx_time_get", 0}, + +}; +#endif diff --git a/executor/syscalls.h b/executor/syscalls_linux.h index 7ee207b0f..48ea44e1c 100644 --- a/executor/syscalls.h +++ b/executor/syscalls_linux.h @@ -7,7 +7,7 @@ struct call_t { #if defined(__i386__) || 0 #define GOARCH "386" -#define SYZ_REVISION "86d577076d75af98e3c800a3c65bf8e4869e3ea4" +#define SYZ_REVISION "7b2f1949d48094cf3369932d0743db166049457b" #define __NR_syz_emit_ethernet 1000000 #define __NR_syz_extract_tcp_res 1000001 #define __NR_syz_fuse_mount 1000002 @@ -1505,7 +1505,7 @@ static call_t syscalls[] = { #if defined(__x86_64__) || 0 #define GOARCH "amd64" -#define SYZ_REVISION "e482e82186ee2d78bed701630c062b7c5d5b23c6" +#define SYZ_REVISION "8349df62e623f9c8d8bfaefcc8ba3febf463ce92" #define __NR_syz_emit_ethernet 1000000 #define __NR_syz_extract_tcp_res 1000001 #define __NR_syz_fuse_mount 1000002 @@ -3064,7 +3064,7 @@ static call_t syscalls[] = { #if defined(__arm__) || 0 #define GOARCH "arm" -#define SYZ_REVISION "41372f11d60aad90c991926434f714d736320cb1" +#define SYZ_REVISION "02567c0623e18214f0be8d059aad68c263064645" #define __NR_syz_emit_ethernet 1000000 #define __NR_syz_extract_tcp_res 1000001 #define __NR_syz_fuse_mount 1000002 @@ -4576,7 +4576,7 @@ static call_t syscalls[] = { #if defined(__aarch64__) || 0 #define GOARCH "arm64" -#define SYZ_REVISION "3424aa8d1e2be36f2a093d39f49a45b6904827e0" +#define SYZ_REVISION "0709fe60bdd20ea30d937d14615a40269fadb2b8" #define __NR_syz_emit_ethernet 1000000 #define __NR_syz_extract_tcp_res 1000001 #define __NR_syz_fuse_mount 1000002 @@ -6063,7 +6063,7 @@ static call_t syscalls[] = { #if defined(__ppc64__) || defined(__PPC64__) || defined(__powerpc64__) || 0 #define GOARCH "ppc64le" -#define SYZ_REVISION "0af6f5872777fdaf20c06a1f982df39659c46b6a" +#define SYZ_REVISION "8bfb8686625fa3398eb8d1a5d66834dec0d3fa06" #define __NR_syz_emit_ethernet 1000000 #define __NR_syz_extract_tcp_res 1000001 #define __NR_syz_fuse_mount 1000002 diff --git a/executor/test.go b/executor/test.go index 1f18dd3bf..7bab3933c 100644 --- a/executor/test.go +++ b/executor/test.go @@ -3,6 +3,8 @@ //go:generate bash -c "gcc kvm_gen.cc kvm.S -o kvm_gen && ./kvm_gen > kvm.S.h && rm ./kvm_gen" +// +build linux + package executor // int test_copyin(); diff --git a/executor/test_executor.cc b/executor/test_executor_linux.cc index 5b30286bf..8bbd56910 100644 --- a/executor/test_executor.cc +++ b/executor/test_executor_linux.cc @@ -1,7 +1,7 @@ // Copyright 2017 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. -#include "syscalls.h" +#include "syscalls_linux.h" #define SYZ_EXECUTOR #include "common.h" diff --git a/executor/test_test.go b/executor/test_test.go index 368454ac3..8048d50f0 100644 --- a/executor/test_test.go +++ b/executor/test_test.go @@ -1,6 +1,8 @@ // Copyright 2016 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 executor import "testing" diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index af0173c2e..a7e79b033 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -4,39 +4,13 @@ package ipc import ( - "bytes" "flag" "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - "sync/atomic" - "syscall" "time" - "unsafe" - "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" ) -type Env struct { - In []byte - Out []byte - - cmd *command - inFile *os.File - outFile *os.File - bin []string - pid int - config Config - - StatExecs uint64 - StatRestarts uint64 -} - // Configuration flags for Config.Flags. const ( FlagDebug = uint64(1) << iota // debug output from executor @@ -57,20 +31,6 @@ const ( FlagCollectComps // collect KCOV comparisons ) -const ( - outputSize = 16 << 20 - signalOffset = 15 << 20 - - statusFail = 67 - statusError = 68 - statusRetry = 69 - - // Comparison types masks taken from KCOV headers. - compSizeMask = 6 - compSize8 = 6 - compConstMask = 1 -) - var ( flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor") flagCollide = flag.Bool("collide", true, "collide syscalls to provoke data races") @@ -107,9 +67,8 @@ type Config struct { // Timeout is the execution timeout for a single program. Timeout time.Duration - // AbortSignal is the signal to send to the executor in error - // conditions. - AbortSignal syscall.Signal + // AbortSignal is the signal to send to the executor in error conditions. + AbortSignal int // BufferSize is the size of the internal buffer for executor output. BufferSize uint64 @@ -139,90 +98,11 @@ func DefaultConfig() (Config, error) { c.Flags |= FlagDebug } c.Timeout = *flagTimeout - c.AbortSignal = syscall.Signal(*flagAbortSignal) + c.AbortSignal = *flagAbortSignal c.BufferSize = *flagBufferSize return c, nil } -func MakeEnv(bin string, pid int, config Config) (*Env, error) { - // IPC timeout must be larger then executor timeout. - // Otherwise IPC will kill parent executor but leave child executor alive. - if config.Timeout < 7*time.Second { - config.Timeout = 7 * time.Second - } - inf, inmem, err := createMapping(prog.ExecBufferSize) - if err != nil { - return nil, err - } - defer func() { - if inf != nil { - closeMapping(inf, inmem) - } - }() - outf, outmem, err := createMapping(outputSize) - if err != nil { - return nil, err - } - defer func() { - if outf != nil { - closeMapping(outf, outmem) - } - }() - serializeUint64(inmem[0:], config.Flags) - serializeUint64(inmem[8:], uint64(pid)) - inmem = inmem[16:] - env := &Env{ - In: inmem, - Out: outmem, - inFile: inf, - outFile: outf, - bin: strings.Split(bin, " "), - pid: pid, - config: config, - } - if len(env.bin) == 0 { - return nil, fmt.Errorf("binary is empty string") - } - env.bin[0], err = filepath.Abs(env.bin[0]) // we are going to chdir - if err != nil { - return nil, fmt.Errorf("filepath.Abs failed: %v", err) - } - // Append pid to binary name. - // E.g. if binary is 'syz-executor' and pid=15, - // we create a link from 'syz-executor15' to 'syz-executor' and use 'syz-executor15' as binary. - // This allows to easily identify program that lead to a crash in the log. - // Log contains pid in "executing program 15" and crashes usually contain "Comm: syz-executor15". - base := filepath.Base(env.bin[0]) - pidStr := fmt.Sprint(pid) - if len(base)+len(pidStr) >= 16 { - // TASK_COMM_LEN is currently set to 16 - base = base[:15-len(pidStr)] - } - binCopy := filepath.Join(filepath.Dir(env.bin[0]), base+pidStr) - if err := os.Link(env.bin[0], binCopy); err == nil { - env.bin[0] = binCopy - } - inf = nil - outf = nil - return env, nil -} - -func (env *Env) Close() error { - if env.cmd != nil { - env.cmd.close() - } - err1 := closeMapping(env.inFile, env.In) - err2 := closeMapping(env.outFile, env.Out) - switch { - case err1 != nil: - return err1 - case err2 != nil: - return err2 - default: - return nil - } -} - type CallInfo struct { Signal []uint32 // feedback signal, filled if FlagSignal is set Cover []uint32 // per-call coverage, filled if FlagSignal is set and cover == true, @@ -239,524 +119,3 @@ func GetCompMaps(info []CallInfo) []prog.CompMap { } return compMaps } - -// Exec starts executor binary to execute program p and returns information about the execution: -// output: process output -// info: per-call info -// failed: true if executor has detected a kernel bug -// hanged: program hanged and was killed -// err0: failed to start process, or executor has detected a logical error -func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) { - if p != nil { - // Copy-in serialized program. - if err := p.SerializeForExec(env.In, env.pid); err != nil { - err0 = fmt.Errorf("executor %v: failed to serialize: %v", env.pid, err) - return - } - } - if env.config.Flags&FlagSignal != 0 { - // Zero out the first two words (ncmd and nsig), so that we don't have garbage there - // if executor crashes before writing non-garbage there. - for i := 0; i < 4; i++ { - env.Out[i] = 0 - } - } - - atomic.AddUint64(&env.StatExecs, 1) - if env.cmd == nil { - atomic.AddUint64(&env.StatRestarts, 1) - env.cmd, err0 = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile) - if err0 != nil { - return - } - } - var restart bool - output, failed, hanged, restart, err0 = env.cmd.exec(opts) - if err0 != nil || restart { - env.cmd.close() - env.cmd = nil - return - } - - if p == nil || env.config.Flags&FlagSignal == 0 && - env.config.Flags&FlagCollectComps == 0 { - return - } - info, err0 = env.readOutCoverage(p) - return -} - -func (env *Env) readOutCoverage(p *prog.Prog) (info []CallInfo, err0 error) { - out := ((*[1 << 28]uint32)(unsafe.Pointer(&env.Out[0])))[:len(env.Out)/int(unsafe.Sizeof(uint32(0)))] - readOut := func(v *uint32) bool { - if len(out) == 0 { - return false - } - *v = out[0] - out = out[1:] - return true - } - - readOutAndSetErr := func(v *uint32, msg string, args ...interface{}) bool { - if !readOut(v) { - err0 = fmt.Errorf(msg, args) - return false - } - return true - } - - // Reads out a 64 bits int in Little-endian as two blocks of 32 bits. - readOut64 := func(v *uint64, msg string, args ...interface{}) bool { - var a, b uint32 - if !(readOutAndSetErr(&a, msg, args) && readOutAndSetErr(&b, msg, args)) { - return false - } - *v = uint64(a) + uint64(b)<<32 - return true - } - - var ncmd uint32 - if !readOutAndSetErr(&ncmd, - "executor %v: failed to read output coverage", env.pid) { - return - } - info = make([]CallInfo, len(p.Calls)) - for i := range info { - info[i].Errno = -1 // not executed - } - dumpCov := func() string { - buf := new(bytes.Buffer) - for i, inf := range info { - str := "nil" - if inf.Signal != nil { - str = fmt.Sprint(len(inf.Signal)) - } - fmt.Fprintf(buf, "%v:%v|", i, str) - } - return buf.String() - } - for i := uint32(0); i < ncmd; i++ { - var callIndex, callNum, errno, faultInjected, signalSize, coverSize, compsSize uint32 - if !readOut(&callIndex) || !readOut(&callNum) || !readOut(&errno) || !readOut(&faultInjected) || !readOut(&signalSize) || !readOut(&coverSize) || !readOut(&compsSize) { - err0 = fmt.Errorf("executor %v: failed to read output coverage", env.pid) - return - } - if int(callIndex) >= len(info) { - err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v, call %v, total calls %v (cov: %v)", - env.pid, i, callIndex, len(info), dumpCov()) - return - } - c := p.Calls[callIndex] - if num := c.Meta.ID; uint32(num) != callNum { - err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v call %v: expect syscall %v, got %v, executed %v (cov: %v)", - env.pid, i, callIndex, num, callNum, ncmd, dumpCov()) - return - } - if info[callIndex].Signal != nil { - err0 = fmt.Errorf("executor %v: failed to read output coverage: double coverage for call %v (cov: %v)", - env.pid, callIndex, dumpCov()) - return - } - info[callIndex].Errno = int(errno) - info[callIndex].FaultInjected = faultInjected != 0 - if signalSize > uint32(len(out)) { - err0 = fmt.Errorf("executor %v: failed to read output signal: record %v, call %v, signalsize=%v coversize=%v", - env.pid, i, callIndex, signalSize, coverSize) - return - } - // Read out signals. - info[callIndex].Signal = out[:signalSize:signalSize] - out = out[signalSize:] - // Read out coverage. - if coverSize > uint32(len(out)) { - err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v, call %v, signalsize=%v coversize=%v", - env.pid, i, callIndex, signalSize, coverSize) - return - } - info[callIndex].Cover = out[:coverSize:coverSize] - out = out[coverSize:] - // Read out comparisons. - compMap := make(prog.CompMap) - for j := uint32(0); j < compsSize; j++ { - var typ uint32 - var op1, op2 uint64 - if !readOutAndSetErr(&typ, - "executor %v: failed while reading type of comparison %v", env.pid, j) { - return - } - if typ > compConstMask|compSizeMask { - err0 = fmt.Errorf("executor %v: got wrong value (%v) while reading type of comparison %v", - env.pid, typ, j) - return - } - - isSize8 := (typ & compSizeMask) == compSize8 - isConst := (typ & compConstMask) != 0 - arg1ErrString := "executor %v: failed while reading op1 of comparison %v" - arg2ErrString := "executor %v: failed while reading op2 of comparison %v" - if isSize8 { - var tmp1, tmp2 uint32 - if !readOutAndSetErr(&tmp1, arg1ErrString, env.pid, j) || - !readOutAndSetErr(&tmp2, arg2ErrString, env.pid, j) { - return - } - op1 = uint64(tmp1) - op2 = uint64(tmp2) - } else { - if !readOut64(&op1, arg1ErrString, env.pid, j) || - !readOut64(&op2, arg2ErrString, env.pid, j) { - return - } - } - if op1 == op2 { - // It's useless to store such comparisons. - continue - } - compMap.AddComp(op2, op1) - if isConst { - // If one of the operands was const, then this operand is always - // placed first in the instrumented callbacks. Such an operand - // could not be an argument of our syscalls (because otherwise - // it wouldn't be const), thus we simply ignore it. - continue - } - compMap.AddComp(op1, op2) - } - info[callIndex].Comps = compMap - } - return -} - -func createMapping(size int) (f *os.File, mem []byte, err error) { - f, err = ioutil.TempFile("./", "syzkaller-shm") - if err != nil { - err = fmt.Errorf("failed to create temp file: %v", err) - return - } - if err = f.Truncate(int64(size)); err != nil { - err = fmt.Errorf("failed to truncate shm file: %v", err) - f.Close() - os.Remove(f.Name()) - return - } - f.Close() - fname := f.Name() - f, err = os.OpenFile(f.Name(), os.O_RDWR, osutil.DefaultFilePerm) - if err != nil { - err = fmt.Errorf("failed to open shm file: %v", err) - os.Remove(fname) - return - } - mem, err = syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - err = fmt.Errorf("failed to mmap shm file: %v", err) - f.Close() - os.Remove(f.Name()) - return - } - return -} - -func closeMapping(f *os.File, mem []byte) error { - err1 := syscall.Munmap(mem) - err2 := f.Close() - err3 := os.Remove(f.Name()) - switch { - case err1 != nil: - return err1 - case err2 != nil: - return err2 - case err3 != nil: - return err3 - default: - return nil - } -} - -type command struct { - pid int - config Config - cmd *exec.Cmd - dir string - readDone chan []byte - exited chan struct{} - inrp *os.File - outwp *os.File -} - -func makeCommand(pid int, bin []string, config Config, inFile *os.File, outFile *os.File) (*command, error) { - dir, err := ioutil.TempDir("./", "syzkaller-testdir") - if err != nil { - return nil, fmt.Errorf("failed to create temp dir: %v", err) - } - - c := &command{ - pid: pid, - config: config, - dir: dir, - } - defer func() { - if c != nil { - c.close() - } - }() - - if config.Flags&(FlagSandboxSetuid|FlagSandboxNamespace) != 0 { - if err := os.Chmod(dir, 0777); err != nil { - return nil, fmt.Errorf("failed to chmod temp dir: %v", err) - } - } - - // Output capture pipe. - rp, wp, err := os.Pipe() - if err != nil { - return nil, fmt.Errorf("failed to create pipe: %v", err) - } - defer wp.Close() - - // Input command pipe. - inrp, inwp, err := os.Pipe() - if err != nil { - return nil, fmt.Errorf("failed to create pipe: %v", err) - } - defer inwp.Close() - c.inrp = inrp - - // Output command pipe. - outrp, outwp, err := os.Pipe() - if err != nil { - return nil, fmt.Errorf("failed to create pipe: %v", err) - } - defer outrp.Close() - c.outwp = outwp - - c.readDone = make(chan []byte, 1) - c.exited = make(chan struct{}) - - cmd := exec.Command(bin[0], bin[1:]...) - cmd.ExtraFiles = []*os.File{inFile, outFile, outrp, inwp} - cmd.Env = []string{} - cmd.Dir = dir - if config.Flags&FlagDebug == 0 { - cmd.Stdout = wp - cmd.Stderr = wp - go func(c *command) { - // Read out output in case executor constantly prints something. - bufSize := c.config.BufferSize - if bufSize == 0 { - bufSize = 128 << 10 - } - output := make([]byte, bufSize) - var size uint64 - for { - n, err := rp.Read(output[size:]) - if n > 0 { - size += uint64(n) - if size >= bufSize*3/4 { - copy(output, output[size-bufSize/2:size]) - size = bufSize / 2 - } - } - if err != nil { - rp.Close() - c.readDone <- output[:size] - close(c.readDone) - return - } - } - }(c) - } else { - close(c.readDone) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - } - if err := cmd.Start(); err != nil { - return nil, fmt.Errorf("failed to start executor binary: %v", err) - } - c.cmd = cmd - wp.Close() - inwp.Close() - if err := c.waitServing(); err != nil { - return nil, err - } - - tmp := c - c = nil // disable defer above - return tmp, nil -} - -func (c *command) close() { - if c.cmd != nil { - c.abort() - c.wait() - } - osutil.UmountAll(c.dir) - os.RemoveAll(c.dir) - if c.inrp != nil { - c.inrp.Close() - } - if c.outwp != nil { - c.outwp.Close() - } -} - -// Wait for executor to start serving (sandbox setup can take significant time). -func (c *command) waitServing() error { - read := make(chan error, 1) - go func() { - var buf [1]byte - _, err := c.inrp.Read(buf[:]) - read <- err - }() - timeout := time.NewTimer(time.Minute) - select { - case err := <-read: - timeout.Stop() - if err != nil { - c.abort() - output := <-c.readDone - err = fmt.Errorf("executor is not serving: %v\n%s", err, output) - c.wait() - if c.cmd.ProcessState != nil { - sys := c.cmd.ProcessState.Sys() - if ws, ok := sys.(syscall.WaitStatus); ok { - // Magic values returned by executor. - if ws.ExitStatus() == statusFail { - err = ExecutorFailure(fmt.Sprintf("executor is not serving:\n%s", output)) - } - } - } - } - return err - case <-timeout.C: - return fmt.Errorf("executor is not serving") - } -} - -// abort sends the abort signal to the command and then SIGKILL if wait doesn't -// return within 5s. -func (c *command) abort() { - sig := c.config.AbortSignal - if sig <= 0 || sig >= 32 { - sig = syscall.SIGKILL - } - syscall.Kill(c.cmd.Process.Pid, sig) - if sig != syscall.SIGKILL { - go func() { - t := time.NewTimer(5 * time.Second) - select { - case <-t.C: - syscall.Kill(c.cmd.Process.Pid, syscall.SIGKILL) - case <-c.exited: - t.Stop() - } - }() - } -} - -func (c *command) wait() error { - err := c.cmd.Wait() - select { - case <-c.exited: - // c.exited closed by an earlier call to wait. - default: - close(c.exited) - } - return err -} - -func (c *command) exec(opts *ExecOpts) (output []byte, failed, hanged, restart bool, err0 error) { - if opts.Flags&FlagInjectFault != 0 { - enableFaultOnce.Do(enableFaultInjection) - } - var inCmd [24]byte - serializeUint64(inCmd[0:], opts.Flags) - serializeUint64(inCmd[8:], uint64(opts.FaultCall)) - serializeUint64(inCmd[16:], uint64(opts.FaultNth)) - if _, err := c.outwp.Write(inCmd[:]); err != nil { - output = <-c.readDone - err0 = fmt.Errorf("failed to write control pipe: %v", err) - return - } - done := make(chan bool) - hang := make(chan bool) - go func() { - t := time.NewTimer(c.config.Timeout) - select { - case <-t.C: - c.abort() - hang <- true - case <-done: - t.Stop() - hang <- false - } - }() - var reply [1]byte - readN, readErr := c.inrp.Read(reply[:]) - close(done) - status := 0 - if readErr == nil { - if readN != len(reply) { - panic(fmt.Sprintf("executor %v: read only %v bytes", c.pid, readN)) - } - status = int(reply[0]) - if status == 0 { - // Program was OK. - <-hang - return - } - // Executor writes magic values into the pipe before exiting, - // so proceed with killing and joining it. - } - c.abort() - output = <-c.readDone - if err := c.wait(); <-hang { - hanged = true - // In all likelihood, this will be duplicated by the default - // case below, but that's fine. - output = append(output, []byte(err.Error())...) - output = append(output, '\n') - } - // Handle magic values returned by executor. - switch status { - case statusFail: - err0 = ExecutorFailure(fmt.Sprintf("executor failed: %s", output)) - case statusError: - err0 = fmt.Errorf("executor detected kernel bug") - failed = true - case statusRetry: - // This is a temporal error (ENOMEM) or an unfortunate - // program that messes with testing setup (e.g. kills executor - // loop process). Pretend that nothing happened. - // It's better than a false crash report. - err0 = nil - hanged = false - restart = true - default: - // Failed to get a valid (or perhaps any) status from the - // executor. - // - // Once the executor is serving the status is always written to - // the pipe, so we don't bother to check the specific exit - // codes from wait. - err0 = fmt.Errorf("invalid (or no) executor status received: %d, executor exit: %s", status, c.cmd.ProcessState) - } - return -} - -func serializeUint64(buf []byte, v uint64) { - for i := 0; i < 8; i++ { - buf[i] = byte(v >> (8 * uint(i))) - } -} - -var enableFaultOnce sync.Once - -func enableFaultInjection() { - if err := osutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N")); err != nil { - panic(fmt.Sprintf("failed to write /sys/kernel/debug/failslab/ignore-gfp-wait: %v", err)) - } - if err := osutil.WriteFile("/sys/kernel/debug/fail_futex/ignore-private", []byte("N")); err != nil { - panic(fmt.Sprintf("failed to write /sys/kernel/debug/fail_futex/ignore-private: %v", err)) - } -} diff --git a/pkg/ipc/ipc_fuchsia.go b/pkg/ipc/ipc_fuchsia.go new file mode 100644 index 000000000..a95a47f85 --- /dev/null +++ b/pkg/ipc/ipc_fuchsia.go @@ -0,0 +1,30 @@ +// Copyright 2017 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 fuchsia + +package ipc + +import ( + "github.com/google/syzkaller/prog" +) + +type Env struct { + In []byte + + StatExecs uint64 + StatRestarts uint64 +} + +func MakeEnv(bin string, pid int, config Config) (*Env, error) { + env := &Env{} + return env, nil +} + +func (env *Env) Close() error { + return nil +} + +func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) { + return +} diff --git a/pkg/ipc/ipc_linux.go b/pkg/ipc/ipc_linux.go new file mode 100644 index 000000000..3023ac0a6 --- /dev/null +++ b/pkg/ipc/ipc_linux.go @@ -0,0 +1,650 @@ +// Copyright 2015 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. + +package ipc + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/prog" +) + +type Env struct { + In []byte + out []byte + + cmd *command + inFile *os.File + outFile *os.File + bin []string + pid int + config Config + + StatExecs uint64 + StatRestarts uint64 +} + +const ( + outputSize = 16 << 20 + + statusFail = 67 + statusError = 68 + statusRetry = 69 + + // Comparison types masks taken from KCOV headers. + compSizeMask = 6 + compSize8 = 6 + compConstMask = 1 +) + +func MakeEnv(bin string, pid int, config Config) (*Env, error) { + // IPC timeout must be larger then executor timeout. + // Otherwise IPC will kill parent executor but leave child executor alive. + if config.Timeout < 7*time.Second { + config.Timeout = 7 * time.Second + } + inf, inmem, err := createMapping(prog.ExecBufferSize) + if err != nil { + return nil, err + } + defer func() { + if inf != nil { + closeMapping(inf, inmem) + } + }() + outf, outmem, err := createMapping(outputSize) + if err != nil { + return nil, err + } + defer func() { + if outf != nil { + closeMapping(outf, outmem) + } + }() + serializeUint64(inmem[0:], config.Flags) + serializeUint64(inmem[8:], uint64(pid)) + inmem = inmem[16:] + env := &Env{ + In: inmem, + out: outmem, + inFile: inf, + outFile: outf, + bin: strings.Split(bin, " "), + pid: pid, + config: config, + } + if len(env.bin) == 0 { + return nil, fmt.Errorf("binary is empty string") + } + env.bin[0], err = filepath.Abs(env.bin[0]) // we are going to chdir + if err != nil { + return nil, fmt.Errorf("filepath.Abs failed: %v", err) + } + // Append pid to binary name. + // E.g. if binary is 'syz-executor' and pid=15, + // we create a link from 'syz-executor15' to 'syz-executor' and use 'syz-executor15' as binary. + // This allows to easily identify program that lead to a crash in the log. + // Log contains pid in "executing program 15" and crashes usually contain "Comm: syz-executor15". + base := filepath.Base(env.bin[0]) + pidStr := fmt.Sprint(pid) + if len(base)+len(pidStr) >= 16 { + // TASK_COMM_LEN is currently set to 16 + base = base[:15-len(pidStr)] + } + binCopy := filepath.Join(filepath.Dir(env.bin[0]), base+pidStr) + if err := os.Link(env.bin[0], binCopy); err == nil { + env.bin[0] = binCopy + } + inf = nil + outf = nil + return env, nil +} + +func (env *Env) Close() error { + if env.cmd != nil { + env.cmd.close() + } + err1 := closeMapping(env.inFile, env.In) + err2 := closeMapping(env.outFile, env.out) + switch { + case err1 != nil: + return err1 + case err2 != nil: + return err2 + default: + return nil + } +} + +// Exec starts executor binary to execute program p and returns information about the execution: +// output: process output +// info: per-call info +// failed: true if executor has detected a kernel bug +// hanged: program hanged and was killed +// err0: failed to start process, or executor has detected a logical error +func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) { + if p != nil { + // Copy-in serialized program. + if err := p.SerializeForExec(env.In, env.pid); err != nil { + err0 = fmt.Errorf("executor %v: failed to serialize: %v", env.pid, err) + return + } + } + if env.config.Flags&FlagSignal != 0 { + // Zero out the first two words (ncmd and nsig), so that we don't have garbage there + // if executor crashes before writing non-garbage there. + for i := 0; i < 4; i++ { + env.out[i] = 0 + } + } + + atomic.AddUint64(&env.StatExecs, 1) + if env.cmd == nil { + atomic.AddUint64(&env.StatRestarts, 1) + env.cmd, err0 = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile) + if err0 != nil { + return + } + } + var restart bool + output, failed, hanged, restart, err0 = env.cmd.exec(opts) + if err0 != nil || restart { + env.cmd.close() + env.cmd = nil + return + } + + if p == nil || env.config.Flags&FlagSignal == 0 && + env.config.Flags&FlagCollectComps == 0 { + return + } + info, err0 = env.readOutCoverage(p) + return +} + +func (env *Env) readOutCoverage(p *prog.Prog) (info []CallInfo, err0 error) { + out := ((*[1 << 28]uint32)(unsafe.Pointer(&env.out[0])))[:len(env.out)/int(unsafe.Sizeof(uint32(0)))] + readOut := func(v *uint32) bool { + if len(out) == 0 { + return false + } + *v = out[0] + out = out[1:] + return true + } + + readOutAndSetErr := func(v *uint32, msg string, args ...interface{}) bool { + if !readOut(v) { + err0 = fmt.Errorf(msg, args) + return false + } + return true + } + + // Reads out a 64 bits int in Little-endian as two blocks of 32 bits. + readOut64 := func(v *uint64, msg string, args ...interface{}) bool { + var a, b uint32 + if !(readOutAndSetErr(&a, msg, args) && readOutAndSetErr(&b, msg, args)) { + return false + } + *v = uint64(a) + uint64(b)<<32 + return true + } + + var ncmd uint32 + if !readOutAndSetErr(&ncmd, + "executor %v: failed to read output coverage", env.pid) { + return + } + info = make([]CallInfo, len(p.Calls)) + for i := range info { + info[i].Errno = -1 // not executed + } + dumpCov := func() string { + buf := new(bytes.Buffer) + for i, inf := range info { + str := "nil" + if inf.Signal != nil { + str = fmt.Sprint(len(inf.Signal)) + } + fmt.Fprintf(buf, "%v:%v|", i, str) + } + return buf.String() + } + for i := uint32(0); i < ncmd; i++ { + var callIndex, callNum, errno, faultInjected, signalSize, coverSize, compsSize uint32 + if !readOut(&callIndex) || !readOut(&callNum) || !readOut(&errno) || !readOut(&faultInjected) || !readOut(&signalSize) || !readOut(&coverSize) || !readOut(&compsSize) { + err0 = fmt.Errorf("executor %v: failed to read output coverage", env.pid) + return + } + if int(callIndex) >= len(info) { + err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v, call %v, total calls %v (cov: %v)", + env.pid, i, callIndex, len(info), dumpCov()) + return + } + c := p.Calls[callIndex] + if num := c.Meta.ID; uint32(num) != callNum { + err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v call %v: expect syscall %v, got %v, executed %v (cov: %v)", + env.pid, i, callIndex, num, callNum, ncmd, dumpCov()) + return + } + if info[callIndex].Signal != nil { + err0 = fmt.Errorf("executor %v: failed to read output coverage: double coverage for call %v (cov: %v)", + env.pid, callIndex, dumpCov()) + return + } + info[callIndex].Errno = int(errno) + info[callIndex].FaultInjected = faultInjected != 0 + if signalSize > uint32(len(out)) { + err0 = fmt.Errorf("executor %v: failed to read output signal: record %v, call %v, signalsize=%v coversize=%v", + env.pid, i, callIndex, signalSize, coverSize) + return + } + // Read out signals. + info[callIndex].Signal = out[:signalSize:signalSize] + out = out[signalSize:] + // Read out coverage. + if coverSize > uint32(len(out)) { + err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v, call %v, signalsize=%v coversize=%v", + env.pid, i, callIndex, signalSize, coverSize) + return + } + info[callIndex].Cover = out[:coverSize:coverSize] + out = out[coverSize:] + // Read out comparisons. + compMap := make(prog.CompMap) + for j := uint32(0); j < compsSize; j++ { + var typ uint32 + var op1, op2 uint64 + if !readOutAndSetErr(&typ, + "executor %v: failed while reading type of comparison %v", env.pid, j) { + return + } + if typ > compConstMask|compSizeMask { + err0 = fmt.Errorf("executor %v: got wrong value (%v) while reading type of comparison %v", + env.pid, typ, j) + return + } + + isSize8 := (typ & compSizeMask) == compSize8 + isConst := (typ & compConstMask) != 0 + arg1ErrString := "executor %v: failed while reading op1 of comparison %v" + arg2ErrString := "executor %v: failed while reading op2 of comparison %v" + if isSize8 { + var tmp1, tmp2 uint32 + if !readOutAndSetErr(&tmp1, arg1ErrString, env.pid, j) || + !readOutAndSetErr(&tmp2, arg2ErrString, env.pid, j) { + return + } + op1 = uint64(tmp1) + op2 = uint64(tmp2) + } else { + if !readOut64(&op1, arg1ErrString, env.pid, j) || + !readOut64(&op2, arg2ErrString, env.pid, j) { + return + } + } + if op1 == op2 { + // It's useless to store such comparisons. + continue + } + compMap.AddComp(op2, op1) + if isConst { + // If one of the operands was const, then this operand is always + // placed first in the instrumented callbacks. Such an operand + // could not be an argument of our syscalls (because otherwise + // it wouldn't be const), thus we simply ignore it. + continue + } + compMap.AddComp(op1, op2) + } + info[callIndex].Comps = compMap + } + return +} + +func createMapping(size int) (f *os.File, mem []byte, err error) { + f, err = ioutil.TempFile("./", "syzkaller-shm") + if err != nil { + err = fmt.Errorf("failed to create temp file: %v", err) + return + } + if err = f.Truncate(int64(size)); err != nil { + err = fmt.Errorf("failed to truncate shm file: %v", err) + f.Close() + os.Remove(f.Name()) + return + } + f.Close() + fname := f.Name() + f, err = os.OpenFile(f.Name(), os.O_RDWR, osutil.DefaultFilePerm) + if err != nil { + err = fmt.Errorf("failed to open shm file: %v", err) + os.Remove(fname) + return + } + mem, err = syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + err = fmt.Errorf("failed to mmap shm file: %v", err) + f.Close() + os.Remove(f.Name()) + return + } + return +} + +func closeMapping(f *os.File, mem []byte) error { + err1 := syscall.Munmap(mem) + err2 := f.Close() + err3 := os.Remove(f.Name()) + switch { + case err1 != nil: + return err1 + case err2 != nil: + return err2 + case err3 != nil: + return err3 + default: + return nil + } +} + +type command struct { + pid int + config Config + cmd *exec.Cmd + dir string + readDone chan []byte + exited chan struct{} + inrp *os.File + outwp *os.File +} + +func makeCommand(pid int, bin []string, config Config, inFile *os.File, outFile *os.File) (*command, error) { + dir, err := ioutil.TempDir("./", "syzkaller-testdir") + if err != nil { + return nil, fmt.Errorf("failed to create temp dir: %v", err) + } + + c := &command{ + pid: pid, + config: config, + dir: dir, + } + defer func() { + if c != nil { + c.close() + } + }() + + if config.Flags&(FlagSandboxSetuid|FlagSandboxNamespace) != 0 { + if err := os.Chmod(dir, 0777); err != nil { + return nil, fmt.Errorf("failed to chmod temp dir: %v", err) + } + } + + // Output capture pipe. + rp, wp, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("failed to create pipe: %v", err) + } + defer wp.Close() + + // Input command pipe. + inrp, inwp, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("failed to create pipe: %v", err) + } + defer inwp.Close() + c.inrp = inrp + + // Output command pipe. + outrp, outwp, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("failed to create pipe: %v", err) + } + defer outrp.Close() + c.outwp = outwp + + c.readDone = make(chan []byte, 1) + c.exited = make(chan struct{}) + + cmd := exec.Command(bin[0], bin[1:]...) + cmd.ExtraFiles = []*os.File{inFile, outFile, outrp, inwp} + cmd.Env = []string{} + cmd.Dir = dir + if config.Flags&FlagDebug == 0 { + cmd.Stdout = wp + cmd.Stderr = wp + go func(c *command) { + // Read out output in case executor constantly prints something. + bufSize := c.config.BufferSize + if bufSize == 0 { + bufSize = 128 << 10 + } + output := make([]byte, bufSize) + var size uint64 + for { + n, err := rp.Read(output[size:]) + if n > 0 { + size += uint64(n) + if size >= bufSize*3/4 { + copy(output, output[size-bufSize/2:size]) + size = bufSize / 2 + } + } + if err != nil { + rp.Close() + c.readDone <- output[:size] + close(c.readDone) + return + } + } + }(c) + } else { + close(c.readDone) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + } + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("failed to start executor binary: %v", err) + } + c.cmd = cmd + wp.Close() + inwp.Close() + if err := c.waitServing(); err != nil { + return nil, err + } + + tmp := c + c = nil // disable defer above + return tmp, nil +} + +func (c *command) close() { + if c.cmd != nil { + c.abort() + c.wait() + } + osutil.UmountAll(c.dir) + os.RemoveAll(c.dir) + if c.inrp != nil { + c.inrp.Close() + } + if c.outwp != nil { + c.outwp.Close() + } +} + +// Wait for executor to start serving (sandbox setup can take significant time). +func (c *command) waitServing() error { + read := make(chan error, 1) + go func() { + var buf [1]byte + _, err := c.inrp.Read(buf[:]) + read <- err + }() + timeout := time.NewTimer(time.Minute) + select { + case err := <-read: + timeout.Stop() + if err != nil { + c.abort() + output := <-c.readDone + err = fmt.Errorf("executor is not serving: %v\n%s", err, output) + c.wait() + if c.cmd.ProcessState != nil { + sys := c.cmd.ProcessState.Sys() + if ws, ok := sys.(syscall.WaitStatus); ok { + // Magic values returned by executor. + if ws.ExitStatus() == statusFail { + err = ExecutorFailure(fmt.Sprintf("executor is not serving:\n%s", output)) + } + } + } + } + return err + case <-timeout.C: + return fmt.Errorf("executor is not serving") + } +} + +// abort sends the abort signal to the command and then SIGKILL if wait doesn't +// return within 5s. +func (c *command) abort() { + sig := syscall.Signal(c.config.AbortSignal) + if sig <= 0 || sig >= 32 { + sig = syscall.SIGKILL + } + syscall.Kill(c.cmd.Process.Pid, sig) + if sig != syscall.SIGKILL { + go func() { + t := time.NewTimer(5 * time.Second) + select { + case <-t.C: + syscall.Kill(c.cmd.Process.Pid, syscall.SIGKILL) + case <-c.exited: + t.Stop() + } + }() + } +} + +func (c *command) wait() error { + err := c.cmd.Wait() + select { + case <-c.exited: + // c.exited closed by an earlier call to wait. + default: + close(c.exited) + } + return err +} + +func (c *command) exec(opts *ExecOpts) (output []byte, failed, hanged, restart bool, err0 error) { + if opts.Flags&FlagInjectFault != 0 { + enableFaultOnce.Do(enableFaultInjection) + } + var inCmd [24]byte + serializeUint64(inCmd[0:], opts.Flags) + serializeUint64(inCmd[8:], uint64(opts.FaultCall)) + serializeUint64(inCmd[16:], uint64(opts.FaultNth)) + if _, err := c.outwp.Write(inCmd[:]); err != nil { + output = <-c.readDone + err0 = fmt.Errorf("failed to write control pipe: %v", err) + return + } + done := make(chan bool) + hang := make(chan bool) + go func() { + t := time.NewTimer(c.config.Timeout) + select { + case <-t.C: + c.abort() + hang <- true + case <-done: + t.Stop() + hang <- false + } + }() + var reply [1]byte + readN, readErr := c.inrp.Read(reply[:]) + close(done) + status := 0 + if readErr == nil { + if readN != len(reply) { + panic(fmt.Sprintf("executor %v: read only %v bytes", c.pid, readN)) + } + status = int(reply[0]) + if status == 0 { + // Program was OK. + <-hang + return + } + // Executor writes magic values into the pipe before exiting, + // so proceed with killing and joining it. + } + c.abort() + output = <-c.readDone + if err := c.wait(); <-hang { + hanged = true + // In all likelihood, this will be duplicated by the default + // case below, but that's fine. + output = append(output, []byte(err.Error())...) + output = append(output, '\n') + } + // Handle magic values returned by executor. + switch status { + case statusFail: + err0 = ExecutorFailure(fmt.Sprintf("executor failed: %s", output)) + case statusError: + err0 = fmt.Errorf("executor detected kernel bug") + failed = true + case statusRetry: + // This is a temporal error (ENOMEM) or an unfortunate + // program that messes with testing setup (e.g. kills executor + // loop process). Pretend that nothing happened. + // It's better than a false crash report. + err0 = nil + hanged = false + restart = true + default: + // Failed to get a valid (or perhaps any) status from the + // executor. + // + // Once the executor is serving the status is always written to + // the pipe, so we don't bother to check the specific exit + // codes from wait. + err0 = fmt.Errorf("invalid (or no) executor status received: %d, executor exit: %s", status, c.cmd.ProcessState) + } + return +} + +func serializeUint64(buf []byte, v uint64) { + for i := 0; i < 8; i++ { + buf[i] = byte(v >> (8 * uint(i))) + } +} + +var enableFaultOnce sync.Once + +func enableFaultInjection() { + if err := osutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N")); err != nil { + panic(fmt.Sprintf("failed to write /sys/kernel/debug/failslab/ignore-gfp-wait: %v", err)) + } + if err := osutil.WriteFile("/sys/kernel/debug/fail_futex/ignore-private", []byte("N")); err != nil { + panic(fmt.Sprintf("failed to write /sys/kernel/debug/fail_futex/ignore-private: %v", err)) + } +} diff --git a/pkg/serializer/serializer.go b/pkg/serializer/serializer.go index bbe13fc17..af2708fb3 100644 --- a/pkg/serializer/serializer.go +++ b/pkg/serializer/serializer.go @@ -15,9 +15,15 @@ import ( // does not write package names before types, omits struct fields with default values, // omits type names where possible, etc. On the other hand, it currently does not // support all types (e.g. channels and maps). -func Write(w io.Writer, v interface{}) { - ww := writer{w} - ww.do(reflect.ValueOf(v), false) +func Write(ww io.Writer, i interface{}) { + w := writer{ww} + v := reflect.ValueOf(i) + if v.Kind() == reflect.Slice && (v.IsNil() || v.Len() == 0) { + w.typ(v.Type()) + w.string("(nil)") + return + } + w.do(v, false) } type writer struct { diff --git a/prog/target.go b/prog/target.go index 1a099bf8e..45a3e9c3d 100644 --- a/prog/target.go +++ b/prog/target.go @@ -19,6 +19,8 @@ type Target struct { Syscalls []*Syscall Resources []*ResourceDesc + Structs []*KeyedStruct + Consts []ConstValue // Syscall used by MakeMmap. // It has some special meaning because there are usually too many of them. @@ -50,6 +52,7 @@ type Target struct { // Filled by prog package: SyscallMap map[string]*Syscall + ConstMap map[string]uint64 resourceMap map[string]*ResourceDesc // Maps resource name to a list of calls that can create the resource. resourceCtors map[string][]*Syscall @@ -57,12 +60,15 @@ type Target struct { var targets = make(map[string]*Target) -func RegisterTarget(target *Target) { +func RegisterTarget(target *Target, initArch func(target *Target)) { key := target.OS + "/" + target.Arch if targets[key] != nil { panic(fmt.Sprintf("duplicate target %v", key)) } + target.SanitizeCall = func(c *Call) {} initTarget(target) + initArch(target) + target.ConstMap = nil // currently used only by initArch targets[key] = target } @@ -95,15 +101,49 @@ func AllTargets() []*Target { } func initTarget(target *Target) { + target.ConstMap = make(map[string]uint64) + for _, c := range target.Consts { + target.ConstMap[c.Name] = c.Value + } + + target.resourceMap = make(map[string]*ResourceDesc) + for _, res := range target.Resources { + target.resourceMap[res.Name] = res + } + + keyedStructs := make(map[StructKey]*StructDesc) + for _, desc := range target.Structs { + keyedStructs[desc.Key] = desc.Desc + } + target.Structs = nil + target.SyscallMap = make(map[string]*Syscall) for _, c := range target.Syscalls { target.SyscallMap[c.Name] = c + ForeachType(c, func(t0 Type) { + switch t := t0.(type) { + case *ResourceType: + t.Desc = target.resourceMap[t.TypeName] + if t.Desc == nil { + panic("no resource desc") + } + case *StructType: + t.StructDesc = keyedStructs[t.Key] + if t.StructDesc == nil { + panic("no struct desc") + } + case *UnionType: + t.StructDesc = keyedStructs[t.Key] + if t.StructDesc == nil { + panic("no union desc") + } + } + }) } - target.resourceMap = make(map[string]*ResourceDesc) + target.resourceCtors = make(map[string][]*Syscall) - for _, r := range target.Resources { - target.resourceMap[r.Name] = r - target.resourceCtors[r.Name] = target.calcResourceCtors(r.Kind, false) + for _, res := range target.Resources { + target.resourceCtors[res.Name] = target.calcResourceCtors(res.Kind, false) } } diff --git a/sys/fuchsia/amd64.go b/sys/fuchsia/amd64.go new file mode 100644 index 000000000..eb51d797d --- /dev/null +++ b/sys/fuchsia/amd64.go @@ -0,0 +1,27 @@ +// AUTOGENERATED FILE +package fuchsia + +import . "github.com/google/syzkaller/prog" + +func init() { + RegisterTarget(&Target{OS: "fuchsia", Arch: "amd64", Revision: revision_amd64, PtrSize: 8, Syscalls: syscalls_amd64, Resources: resources_amd64, Structs: structDescs_amd64, Consts: consts_amd64}, initTarget) +} + +var resources_amd64 = []*ResourceDesc(nil) + +var structDescs_amd64 = []*KeyedStruct(nil) + +var syscalls_amd64 = []*Syscall{ + {Name: "mx_time_get", CallName: "mx_time_get", Args: []Type{ + &FlagsType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "clock_id", FldName: "clock_id", TypeSize: 8}}, Vals: []uint64{0, 1, 2}}, + }}, +} + +var consts_amd64 = []ConstValue{ + {Name: "MX_CLOCK_MONOTONIC"}, + {Name: "MX_CLOCK_THREAD", Value: 2}, + {Name: "MX_CLOCK_UTC", Value: 1}, + {Name: "__NR_mx_time_get"}, +} + +const revision_amd64 = "d65b9adb4853817be6f471df44fc8347ebf6dfc6" diff --git a/sys/fuchsia/arm64.go b/sys/fuchsia/arm64.go new file mode 100644 index 000000000..b39870ffa --- /dev/null +++ b/sys/fuchsia/arm64.go @@ -0,0 +1,27 @@ +// AUTOGENERATED FILE +package fuchsia + +import . "github.com/google/syzkaller/prog" + +func init() { + RegisterTarget(&Target{OS: "fuchsia", Arch: "arm64", Revision: revision_arm64, PtrSize: 8, Syscalls: syscalls_arm64, Resources: resources_arm64, Structs: structDescs_arm64, Consts: consts_arm64}, initTarget) +} + +var resources_arm64 = []*ResourceDesc(nil) + +var structDescs_arm64 = []*KeyedStruct(nil) + +var syscalls_arm64 = []*Syscall{ + {Name: "mx_time_get", CallName: "mx_time_get", Args: []Type{ + &FlagsType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "clock_id", FldName: "clock_id", TypeSize: 8}}, Vals: []uint64{0, 1, 2}}, + }}, +} + +var consts_arm64 = []ConstValue{ + {Name: "MX_CLOCK_MONOTONIC"}, + {Name: "MX_CLOCK_THREAD", Value: 2}, + {Name: "MX_CLOCK_UTC", Value: 1}, + {Name: "__NR_mx_time_get"}, +} + +const revision_arm64 = "bd2655e6d85f1fecceb1648649e1ad5adda81dc8" diff --git a/sys/fuchsia/init.go b/sys/fuchsia/init.go new file mode 100644 index 000000000..0dfe874cc --- /dev/null +++ b/sys/fuchsia/init.go @@ -0,0 +1,35 @@ +// Copyright 2017 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. + +package fuchsia + +import ( + "github.com/google/syzkaller/prog" +) + +func initTarget(target *prog.Target) { + arch := &arch{} + + target.PageSize = pageSize + target.DataOffset = dataOffset + target.MmapSyscall = arch.mmapSyscall + target.MakeMmap = arch.makeMmap + target.AnalyzeMmap = arch.analyzeMmap +} + +const ( + pageSize = 4 << 10 + dataOffset = 512 << 20 +) + +type arch struct { +} + +// createMmapCall creates a "normal" mmap call that maps [start, start+npages) page range. +func (arch *arch) makeMmap(start, npages uint64) *prog.Call { + return nil +} + +func (arch *arch) analyzeMmap(c *prog.Call) (start, npages uint64, mapped bool) { + return +} diff --git a/sys/fuchsia/sys.txt b/sys/fuchsia/sys.txt new file mode 100644 index 000000000..543d5da01 --- /dev/null +++ b/sys/fuchsia/sys.txt @@ -0,0 +1,6 @@ +# Copyright 2017 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. + +mx_time_get(clock_id flags[clock_id]) + +clock_id = MX_CLOCK_MONOTONIC, MX_CLOCK_UTC, MX_CLOCK_THREAD diff --git a/sys/fuchsia/sys_amd64.const b/sys/fuchsia/sys_amd64.const new file mode 100644 index 000000000..c6aa53ea0 --- /dev/null +++ b/sys/fuchsia/sys_amd64.const @@ -0,0 +1,5 @@ +__NR_mx_time_get = 0 + +MX_CLOCK_MONOTONIC = 0 +MX_CLOCK_UTC = 1 +MX_CLOCK_THREAD = 2 diff --git a/sys/fuchsia/sys_arm64.const b/sys/fuchsia/sys_arm64.const new file mode 100644 index 000000000..c6aa53ea0 --- /dev/null +++ b/sys/fuchsia/sys_arm64.const @@ -0,0 +1,5 @@ +__NR_mx_time_get = 0 + +MX_CLOCK_MONOTONIC = 0 +MX_CLOCK_UTC = 1 +MX_CLOCK_THREAD = 2 diff --git a/sys/linux/386.go b/sys/linux/386.go index bb166ec6e..431f21d24 100644 --- a/sys/linux/386.go +++ b/sys/linux/386.go @@ -4,7 +4,7 @@ package linux import . "github.com/google/syzkaller/prog" func init() { - initArch(revision_386, syscalls_386, resources_386, structDescs_386, consts_386, "386", 4) + RegisterTarget(&Target{OS: "linux", Arch: "386", Revision: revision_386, PtrSize: 4, Syscalls: syscalls_386, Resources: resources_386, Structs: structDescs_386, Consts: consts_386}, initTarget) } var resources_386 = []*ResourceDesc{ @@ -16734,4 +16734,4 @@ var consts_386 = []ConstValue{ {Name: "__WNOTHREAD", Value: 536870912}, } -const revision_386 = "86d577076d75af98e3c800a3c65bf8e4869e3ea4" +const revision_386 = "7b2f1949d48094cf3369932d0743db166049457b" diff --git a/sys/linux/amd64.go b/sys/linux/amd64.go index f3f058d81..1a43b6760 100644 --- a/sys/linux/amd64.go +++ b/sys/linux/amd64.go @@ -4,7 +4,7 @@ package linux import . "github.com/google/syzkaller/prog" func init() { - initArch(revision_amd64, syscalls_amd64, resources_amd64, structDescs_amd64, consts_amd64, "amd64", 8) + RegisterTarget(&Target{OS: "linux", Arch: "amd64", Revision: revision_amd64, PtrSize: 8, Syscalls: syscalls_amd64, Resources: resources_amd64, Structs: structDescs_amd64, Consts: consts_amd64}, initTarget) } var resources_amd64 = []*ResourceDesc{ @@ -17264,4 +17264,4 @@ var consts_amd64 = []ConstValue{ {Name: "__WNOTHREAD", Value: 536870912}, } -const revision_amd64 = "e482e82186ee2d78bed701630c062b7c5d5b23c6" +const revision_amd64 = "8349df62e623f9c8d8bfaefcc8ba3febf463ce92" diff --git a/sys/linux/arm.go b/sys/linux/arm.go index 0a1509600..5c4fddab6 100644 --- a/sys/linux/arm.go +++ b/sys/linux/arm.go @@ -4,7 +4,7 @@ package linux import . "github.com/google/syzkaller/prog" func init() { - initArch(revision_arm, syscalls_arm, resources_arm, structDescs_arm, consts_arm, "arm", 4) + RegisterTarget(&Target{OS: "linux", Arch: "arm", Revision: revision_arm, PtrSize: 4, Syscalls: syscalls_arm, Resources: resources_arm, Structs: structDescs_arm, Consts: consts_arm}, initTarget) } var resources_arm = []*ResourceDesc{ @@ -16631,4 +16631,4 @@ var consts_arm = []ConstValue{ {Name: "__WNOTHREAD", Value: 536870912}, } -const revision_arm = "41372f11d60aad90c991926434f714d736320cb1" +const revision_arm = "02567c0623e18214f0be8d059aad68c263064645" diff --git a/sys/linux/arm64.go b/sys/linux/arm64.go index 77d20bd0c..39061d67a 100644 --- a/sys/linux/arm64.go +++ b/sys/linux/arm64.go @@ -4,7 +4,7 @@ package linux import . "github.com/google/syzkaller/prog" func init() { - initArch(revision_arm64, syscalls_arm64, resources_arm64, structDescs_arm64, consts_arm64, "arm64", 8) + RegisterTarget(&Target{OS: "linux", Arch: "arm64", Revision: revision_arm64, PtrSize: 8, Syscalls: syscalls_arm64, Resources: resources_arm64, Structs: structDescs_arm64, Consts: consts_arm64}, initTarget) } var resources_arm64 = []*ResourceDesc{ @@ -16641,4 +16641,4 @@ var consts_arm64 = []ConstValue{ {Name: "__WNOTHREAD", Value: 536870912}, } -const revision_arm64 = "3424aa8d1e2be36f2a093d39f49a45b6904827e0" +const revision_arm64 = "0709fe60bdd20ea30d937d14615a40269fadb2b8" diff --git a/sys/linux/init.go b/sys/linux/init.go index 509e2ab04..c727cb5b2 100644 --- a/sys/linux/init.go +++ b/sys/linux/init.go @@ -9,29 +9,48 @@ import ( "github.com/google/syzkaller/prog" ) -func initArch(rev string, syscalls []*prog.Syscall, resources []*prog.ResourceDesc, - structDescs []*prog.KeyedStruct, consts []prog.ConstValue, archName string, ptrSize uint64) { - arch := makeArch(syscalls, resources, structDescs, consts, archName) - target := &prog.Target{ - OS: "linux", - Arch: archName, - Revision: rev, - PtrSize: ptrSize, - PageSize: pageSize, - DataOffset: dataOffset, - Syscalls: syscalls, - Resources: resources, - MmapSyscall: arch.mmapSyscall, - MakeMmap: arch.makeMmap, - AnalyzeMmap: arch.analyzeMmap, - SanitizeCall: arch.sanitizeCall, - SpecialStructs: map[string]func(g *prog.Gen, typ *prog.StructType, old *prog.GroupArg) (prog.Arg, []*prog.Call){ - "timespec": arch.generateTimespec, - "timeval": arch.generateTimespec, - }, - StringDictionary: stringDictionary, +func initTarget(target *prog.Target) { + arch := &arch{ + mmapSyscall: target.SyscallMap["mmap"], + clockGettimeSyscall: target.SyscallMap["clock_gettime"], + PROT_READ: target.ConstMap["PROT_READ"], + PROT_WRITE: target.ConstMap["PROT_WRITE"], + MAP_ANONYMOUS: target.ConstMap["MAP_ANONYMOUS"], + MAP_PRIVATE: target.ConstMap["MAP_PRIVATE"], + MAP_FIXED: target.ConstMap["MAP_FIXED"], + MREMAP_MAYMOVE: target.ConstMap["MREMAP_MAYMOVE"], + MREMAP_FIXED: target.ConstMap["MREMAP_FIXED"], + S_IFREG: target.ConstMap["S_IFREG"], + S_IFCHR: target.ConstMap["S_IFCHR"], + S_IFBLK: target.ConstMap["S_IFBLK"], + S_IFIFO: target.ConstMap["S_IFIFO"], + S_IFSOCK: target.ConstMap["S_IFSOCK"], + SYSLOG_ACTION_CONSOLE_OFF: target.ConstMap["SYSLOG_ACTION_CONSOLE_OFF"], + SYSLOG_ACTION_CONSOLE_ON: target.ConstMap["SYSLOG_ACTION_CONSOLE_ON"], + SYSLOG_ACTION_SIZE_UNREAD: target.ConstMap["SYSLOG_ACTION_SIZE_UNREAD"], + FIFREEZE: target.ConstMap["FIFREEZE"], + FITHAW: target.ConstMap["FITHAW"], + PTRACE_TRACEME: target.ConstMap["PTRACE_TRACEME"], + CLOCK_REALTIME: target.ConstMap["CLOCK_REALTIME"], + } + + target.PageSize = pageSize + target.DataOffset = dataOffset + target.MmapSyscall = arch.mmapSyscall + target.MakeMmap = arch.makeMmap + target.AnalyzeMmap = arch.analyzeMmap + target.SanitizeCall = arch.sanitizeCall + target.SpecialStructs = map[string]func(g *prog.Gen, typ *prog.StructType, old *prog.GroupArg) (prog.Arg, []*prog.Call){ + "timespec": arch.generateTimespec, + "timeval": arch.generateTimespec, + } + target.StringDictionary = stringDictionary + + if target.Arch == runtime.GOARCH { + KCOV_INIT_TRACE = uintptr(target.ConstMap["KCOV_INIT_TRACE"]) + KCOV_ENABLE = uintptr(target.ConstMap["KCOV_ENABLE"]) + KCOV_TRACE_CMP = uintptr(target.ConstMap["KCOV_TRACE_CMP"]) } - prog.RegisterTarget(target) } const ( @@ -263,101 +282,3 @@ func (arch *arch) generateTimespec(g *prog.Gen, typ *prog.StructType, old *prog. } return } - -func makeArch(syscalls []*prog.Syscall, resources []*prog.ResourceDesc, - structDescs []*prog.KeyedStruct, consts []prog.ConstValue, archName string) *arch { - resourceMap := make(map[string]*prog.ResourceDesc) - for _, res := range resources { - resourceMap[res.Name] = res - } - - keyedStructs := make(map[prog.StructKey]*prog.StructDesc) - for _, desc := range structDescs { - keyedStructs[desc.Key] = desc.Desc - } - - arch := &arch{} - for _, c := range syscalls { - prog.ForeachType(c, func(t0 prog.Type) { - switch t := t0.(type) { - case *prog.ResourceType: - t.Desc = resourceMap[t.TypeName] - if t.Desc == nil { - panic("no resource desc") - } - case *prog.StructType: - t.StructDesc = keyedStructs[t.Key] - if t.StructDesc == nil { - panic("no struct desc") - } - case *prog.UnionType: - t.StructDesc = keyedStructs[t.Key] - if t.StructDesc == nil { - panic("no union desc") - } - } - }) - switch c.Name { - case "mmap": - arch.mmapSyscall = c - case "clock_gettime": - arch.clockGettimeSyscall = c - } - } - - for _, c := range consts { - switch c.Name { - case "KCOV_INIT_TRACE": - if archName == runtime.GOARCH { - KCOV_INIT_TRACE = uintptr(c.Value) - } - case "KCOV_ENABLE": - if archName == runtime.GOARCH { - KCOV_ENABLE = uintptr(c.Value) - } - case "KCOV_TRACE_CMP": - if archName == runtime.GOARCH { - KCOV_TRACE_CMP = uintptr(c.Value) - } - case "PROT_READ": - arch.PROT_READ = c.Value - case "PROT_WRITE": - arch.PROT_WRITE = c.Value - case "MAP_ANONYMOUS": - arch.MAP_ANONYMOUS = c.Value - case "MAP_PRIVATE": - arch.MAP_PRIVATE = c.Value - case "MAP_FIXED": - arch.MAP_FIXED = c.Value - case "MREMAP_MAYMOVE": - arch.MREMAP_MAYMOVE = c.Value - case "MREMAP_FIXED": - arch.MREMAP_FIXED = c.Value - case "S_IFREG": - arch.S_IFREG = c.Value - case "S_IFCHR": - arch.S_IFCHR = c.Value - case "S_IFBLK": - arch.S_IFBLK = c.Value - case "S_IFIFO": - arch.S_IFIFO = c.Value - case "S_IFSOCK": - arch.S_IFSOCK = c.Value - case "SYSLOG_ACTION_CONSOLE_OFF": - arch.SYSLOG_ACTION_CONSOLE_OFF = c.Value - case "SYSLOG_ACTION_CONSOLE_ON": - arch.SYSLOG_ACTION_CONSOLE_ON = c.Value - case "SYSLOG_ACTION_SIZE_UNREAD": - arch.SYSLOG_ACTION_SIZE_UNREAD = c.Value - case "FIFREEZE": - arch.FIFREEZE = c.Value - case "FITHAW": - arch.FITHAW = c.Value - case "PTRACE_TRACEME": - arch.PTRACE_TRACEME = c.Value - case "CLOCK_REALTIME": - arch.CLOCK_REALTIME = c.Value - } - } - return arch -} diff --git a/sys/linux/ppc64le.go b/sys/linux/ppc64le.go index 6686885b5..1c02c5a08 100644 --- a/sys/linux/ppc64le.go +++ b/sys/linux/ppc64le.go @@ -4,7 +4,7 @@ package linux import . "github.com/google/syzkaller/prog" func init() { - initArch(revision_ppc64le, syscalls_ppc64le, resources_ppc64le, structDescs_ppc64le, consts_ppc64le, "ppc64le", 8) + RegisterTarget(&Target{OS: "linux", Arch: "ppc64le", Revision: revision_ppc64le, PtrSize: 8, Syscalls: syscalls_ppc64le, Resources: resources_ppc64le, Structs: structDescs_ppc64le, Consts: consts_ppc64le}, initTarget) } var resources_ppc64le = []*ResourceDesc{ @@ -16330,4 +16330,4 @@ var consts_ppc64le = []ConstValue{ {Name: "__WNOTHREAD", Value: 536870912}, } -const revision_ppc64le = "0af6f5872777fdaf20c06a1f982df39659c46b6a" +const revision_ppc64le = "8bfb8686625fa3398eb8d1a5d66834dec0d3fa06" diff --git a/sys/syz-sysgen/sysgen.go b/sys/syz-sysgen/sysgen.go index 56ad15f46..3aa98a16a 100644 --- a/sys/syz-sysgen/sysgen.go +++ b/sys/syz-sysgen/sysgen.go @@ -113,7 +113,7 @@ func main() { } } - writeExecutorSyscalls(syscallArchs) + writeExecutorSyscalls(OS, syscallArchs) } if *flagMemProfile != "" { @@ -135,8 +135,11 @@ func generate(target *targets.Target, prg *compiler.Prog, consts map[string]uint fmt.Fprintf(out, "import . \"github.com/google/syzkaller/prog\"\n\n") fmt.Fprintf(out, "func init() {\n") - fmt.Fprintf(out, "\tinitArch(revision_%v, syscalls_%v, resources_%v, structDescs_%v, consts_%v, %q, %v)\n", - target.Arch, target.Arch, target.Arch, target.Arch, target.Arch, target.Arch, target.PtrSize) + fmt.Fprintf(out, "\tRegisterTarget(&Target{OS: %q, Arch: %q, Revision: revision_%v, PtrSize: %v,"+ + "Syscalls: syscalls_%v, Resources: resources_%v, Structs: structDescs_%v, Consts: consts_%v}, "+ + "initTarget)\n", + target.OS, target.Arch, target.Arch, target.PtrSize, + target.Arch, target.Arch, target.Arch, target.Arch) fmt.Fprintf(out, "}\n\n") fmt.Fprintf(out, "var resources_%v = ", target.Arch) @@ -203,13 +206,13 @@ func generateExecutorSyscalls(target *targets.Target, syscalls []*prog.Syscall, return buf.Bytes() } -func writeExecutorSyscalls(archs [][]byte) { +func writeExecutorSyscalls(OS string, archs [][]byte) { buf := new(bytes.Buffer) buf.WriteString(syscallsTempl) for _, arch := range archs { buf.Write(arch) } - writeFile("executor/syscalls.h", buf.Bytes()) + writeFile(filepath.Join("executor", fmt.Sprintf("syscalls_%v.h", OS)), buf.Bytes()) } func writeSource(file string, data []byte) { diff --git a/sys/targets/targets.go b/sys/targets/targets.go index 07f3ee8a7..6c5028c1d 100644 --- a/sys/targets/targets.go +++ b/sys/targets/targets.go @@ -62,6 +62,16 @@ var List = map[string]map[string]*Target{ KernelHeaderArch: "powerpc", }, }, + "fuchsia": map[string]*Target{ + "amd64": { + PtrSize: 8, + CArch: []string{"__x86_64__"}, + }, + "arm64": { + PtrSize: 8, + CArch: []string{"__aarch64__"}, + }, + }, } func init() { diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 8c94c7d49..01003a665 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -11,7 +11,6 @@ import ( "net/http" _ "net/http/pprof" "os" - "os/signal" "runtime" "runtime/debug" "strconv" @@ -30,7 +29,6 @@ import ( . "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys" - "github.com/google/syzkaller/sys/linux" ) var ( @@ -114,14 +112,7 @@ func main() { Fatalf("%v", err) } - go func() { - // Handles graceful preemption on GCE. - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) - <-c - Logf(0, "SYZ-FUZZER: PREEMPTED") - os.Exit(1) - }() + osInit() if *flagPprof != "" { go func() { @@ -774,110 +765,3 @@ retry: Logf(2, "result failed=%v hanged=%v: %v\n", failed, hanged, string(output)) return info } - -func kmemleakInit() { - fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) - if err != nil { - if *flagLeak { - Fatalf("BUG: /sys/kernel/debug/kmemleak is missing (%v). Enable CONFIG_KMEMLEAK and mount debugfs.", err) - } else { - return - } - } - defer syscall.Close(fd) - what := "scan=off" - if !*flagLeak { - what = "off" - } - if _, err := syscall.Write(fd, []byte(what)); err != nil { - // kmemleak returns EBUSY when kmemleak is already turned off. - if err != syscall.EBUSY { - panic(err) - } - } -} - -var kmemleakBuf []byte - -func kmemleakScan(report bool) { - fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) - if err != nil { - panic(err) - } - defer syscall.Close(fd) - // Kmemleak has false positives. To mitigate most of them, it checksums - // potentially leaked objects, and reports them only on the next scan - // iff the checksum does not change. Because of that we do the following - // intricate dance: - // Scan, sleep, scan again. At this point we can get some leaks. - // If there are leaks, we sleep and scan again, this can remove - // false leaks. Then, read kmemleak again. If we get leaks now, then - // hopefully these are true positives during the previous testing cycle. - if _, err := syscall.Write(fd, []byte("scan")); err != nil { - panic(err) - } - time.Sleep(time.Second) - if _, err := syscall.Write(fd, []byte("scan")); err != nil { - panic(err) - } - if report { - if kmemleakBuf == nil { - kmemleakBuf = make([]byte, 128<<10) - } - n, err := syscall.Read(fd, kmemleakBuf) - if err != nil { - panic(err) - } - if n != 0 { - time.Sleep(time.Second) - if _, err := syscall.Write(fd, []byte("scan")); err != nil { - panic(err) - } - n, err := syscall.Read(fd, kmemleakBuf) - if err != nil { - panic(err) - } - if n != 0 { - // BUG in output should be recognized by manager. - Logf(0, "BUG: memory leak:\n%s\n", kmemleakBuf[:n]) - } - } - } - if _, err := syscall.Write(fd, []byte("clear")); err != nil { - panic(err) - } -} - -// Checks if the KCOV device supports comparisons. -// Returns a pair of bools: -// First - is the kcov device present in the system. -// Second - is the kcov device supporting comparisons. -func checkCompsSupported() (kcov, comps bool) { - // TODO(dvyukov): this should run under target arch. - // E.g. KCOV ioctls were initially not supported on 386 (missing compat_ioctl), - // and a 386 executor won't be able to use them, but an amd64 fuzzer will be. - fd, err := syscall.Open("/sys/kernel/debug/kcov", syscall.O_RDWR, 0) - if err != nil { - return - } - defer syscall.Close(fd) - kcov = true - coverSize := uintptr(64 << 10) - _, _, errno := syscall.Syscall( - syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_INIT_TRACE, coverSize) - if errno != 0 { - Logf(1, "KCOV_CHECK: KCOV_INIT_TRACE = %v", errno) - return - } - _, err = syscall.Mmap(fd, 0, int(coverSize*8), - syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - Logf(1, "KCOV_CHECK: mmap = %v", err) - return - } - _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, - uintptr(fd), linux.KCOV_ENABLE, linux.KCOV_TRACE_CMP) - Logf(1, "KCOV_CHECK: KCOV_ENABLE = %v", errno) - comps = errno == 0 - return -} diff --git a/syz-fuzzer/fuzzer_fuchsia.go b/syz-fuzzer/fuzzer_fuchsia.go new file mode 100644 index 000000000..9a5f0defe --- /dev/null +++ b/syz-fuzzer/fuzzer_fuchsia.go @@ -0,0 +1,26 @@ +// Copyright 2017 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 fuchsia + +package main + +import ( + "github.com/google/syzkaller/pkg/log" +) + +func osInit() { +} + +func kmemleakInit() { + if *flagLeak { + log.Fatalf("leak checking is not supported on fuchsia") + } +} + +func kmemleakScan(report bool) { +} + +func checkCompsSupported() (kcov, comps bool) { + return false, false +} diff --git a/syz-fuzzer/fuzzer_linux.go b/syz-fuzzer/fuzzer_linux.go new file mode 100644 index 000000000..6443d3d32 --- /dev/null +++ b/syz-fuzzer/fuzzer_linux.go @@ -0,0 +1,132 @@ +// Copyright 2017 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. + +package main + +import ( + "os" + "os/signal" + "syscall" + "time" + + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/sys/linux" +) + +func osInit() { + go func() { + // Handles graceful preemption on GCE. + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + <-c + log.Logf(0, "SYZ-FUZZER: PREEMPTED") + os.Exit(1) + }() +} + +func kmemleakInit() { + fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) + if err != nil { + if *flagLeak { + log.Fatalf("BUG: /sys/kernel/debug/kmemleak is missing (%v). Enable CONFIG_KMEMLEAK and mount debugfs.", err) + } else { + return + } + } + defer syscall.Close(fd) + what := "scan=off" + if !*flagLeak { + what = "off" + } + if _, err := syscall.Write(fd, []byte(what)); err != nil { + // kmemleak returns EBUSY when kmemleak is already turned off. + if err != syscall.EBUSY { + panic(err) + } + } +} + +var kmemleakBuf []byte + +func kmemleakScan(report bool) { + fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) + if err != nil { + panic(err) + } + defer syscall.Close(fd) + // Kmemleak has false positives. To mitigate most of them, it checksums + // potentially leaked objects, and reports them only on the next scan + // iff the checksum does not change. Because of that we do the following + // intricate dance: + // Scan, sleep, scan again. At this point we can get some leaks. + // If there are leaks, we sleep and scan again, this can remove + // false leaks. Then, read kmemleak again. If we get leaks now, then + // hopefully these are true positives during the previous testing cycle. + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + panic(err) + } + time.Sleep(time.Second) + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + panic(err) + } + if report { + if kmemleakBuf == nil { + kmemleakBuf = make([]byte, 128<<10) + } + n, err := syscall.Read(fd, kmemleakBuf) + if err != nil { + panic(err) + } + if n != 0 { + time.Sleep(time.Second) + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + panic(err) + } + n, err := syscall.Read(fd, kmemleakBuf) + if err != nil { + panic(err) + } + if n != 0 { + // BUG in output should be recognized by manager. + log.Logf(0, "BUG: memory leak:\n%s\n", kmemleakBuf[:n]) + } + } + } + if _, err := syscall.Write(fd, []byte("clear")); err != nil { + panic(err) + } +} + +// Checks if the KCOV device supports comparisons. +// Returns a pair of bools: +// First - is the kcov device present in the system. +// Second - is the kcov device supporting comparisons. +func checkCompsSupported() (kcov, comps bool) { + // TODO(dvyukov): this should run under target arch. + // E.g. KCOV ioctls were initially not supported on 386 (missing compat_ioctl), + // and a 386 executor won't be able to use them, but an amd64 fuzzer will be. + fd, err := syscall.Open("/sys/kernel/debug/kcov", syscall.O_RDWR, 0) + if err != nil { + return + } + defer syscall.Close(fd) + kcov = true + coverSize := uintptr(64 << 10) + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_INIT_TRACE, coverSize) + if errno != 0 { + log.Logf(1, "KCOV_CHECK: KCOV_INIT_TRACE = %v", errno) + return + } + _, err = syscall.Mmap(fd, 0, int(coverSize*8), + syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + log.Logf(1, "KCOV_CHECK: mmap = %v", err) + return + } + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, + uintptr(fd), linux.KCOV_ENABLE, linux.KCOV_TRACE_CMP) + log.Logf(1, "KCOV_CHECK: KCOV_ENABLE = %v", errno) + comps = errno == 0 + return +} diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index 282e416e3..9f2c45ba6 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -12,11 +12,9 @@ import ( "fmt" "io/ioutil" "os" - "os/signal" "runtime" "sync" "sync/atomic" - "syscall" "time" "github.com/google/syzkaller/pkg/cover" @@ -192,15 +190,6 @@ func main() { }() } - go func() { - c := make(chan os.Signal, 2) - signal.Notify(c, syscall.SIGINT) - <-c - Logf(0, "shutting down...") - atomic.StoreUint32(&shutdown, 1) - <-c - Fatalf("terminating") - }() - + go handleInterrupt(&shutdown) wg.Wait() } diff --git a/tools/syz-execprog/execprog_fuchsia.go b/tools/syz-execprog/execprog_fuchsia.go new file mode 100644 index 000000000..14eac9306 --- /dev/null +++ b/tools/syz-execprog/execprog_fuchsia.go @@ -0,0 +1,9 @@ +// Copyright 2017 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 fuchsia + +package main + +func handleInterrupt(shutdown *uint32) { +} diff --git a/tools/syz-execprog/execprog_linux.go b/tools/syz-execprog/execprog_linux.go new file mode 100644 index 000000000..01e74cec5 --- /dev/null +++ b/tools/syz-execprog/execprog_linux.go @@ -0,0 +1,23 @@ +// Copyright 2017 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. + +package main + +import ( + "os" + "os/signal" + "sync/atomic" + "syscall" + + "github.com/google/syzkaller/pkg/log" +) + +func handleInterrupt(shutdown *uint32) { + c := make(chan os.Signal, 2) + signal.Notify(c, syscall.SIGINT) + <-c + log.Logf(0, "shutting down...") + atomic.StoreUint32(shutdown, 1) + <-c + log.Fatalf("terminating") +} |
