syzkaller/csource/csource.go

366 lines
11 KiB
Go

// 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.
// I heard you like shell...
//go:generate bash -c "echo -e '// AUTOGENERATED FROM executor/common.h\npackage csource\nvar commonHeader = `' > common.go; cat ../executor/common.h | sed -e '/#include \"common_kvm_amd64.h\"/ {' -e 'r ../executor/common_kvm_amd64.h' -e 'd' -e '}' - | sed -e '/#include \"common_kvm_arm64.h\"/ {' -e 'r ../executor/common_kvm_arm64.h' -e 'd' -e '}' - | sed -e '/#include \"kvm.h\"/ {' -e 'r ../executor/kvm.h' -e 'd' -e '}' - | sed -e '/#include \"kvm.S.h\"/ {' -e 'r ../executor/kvm.S.h' -e 'd' -e '}' - | egrep -v '^[ ]*//' | sed '/^[ ]*\\/\\/.*/d' | sed 's#[ ]*//.*##g' >> common.go; echo '`' >> common.go"
package csource
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"unsafe"
"github.com/google/syzkaller/prog"
"github.com/google/syzkaller/sys"
)
type Options struct {
Threaded bool
Collide bool
Repeat bool
Procs int
Sandbox string
Repro bool // generate code for use with repro package
}
func Write(p *prog.Prog, opts Options) ([]byte, error) {
exec := make([]byte, prog.ExecBufferSize)
if err := p.SerializeForExec(exec, 0); err != nil {
return nil, fmt.Errorf("failed to serialize program: %v", err)
}
w := new(bytes.Buffer)
fmt.Fprint(w, "// autogenerated by syzkaller (http://github.com/google/syzkaller)\n\n")
handled := make(map[string]int)
for _, c := range p.Calls {
handled[c.Meta.CallName] = c.Meta.NR
}
for name, nr := range handled {
fmt.Fprintf(w, "#ifndef __NR_%v\n", name)
fmt.Fprintf(w, "#define __NR_%v %v\n", name, nr)
fmt.Fprintf(w, "#endif\n")
}
fmt.Fprintf(w, "\n")
enableTun := "false"
if _, ok := handled["syz_emit_ethernet"]; ok {
enableTun = "true"
}
hdr, err := preprocessCommonHeader(opts, handled)
if err != nil {
return nil, err
}
fmt.Fprint(w, hdr)
fmt.Fprint(w, "\n")
calls, nvar := generateCalls(exec)
fmt.Fprintf(w, "long r[%v];\n", nvar)
if !opts.Repeat {
generateTestFunc(w, opts, calls, "loop")
fmt.Fprint(w, "int main()\n{\n")
fmt.Fprintf(w, "\tsetup_main_process(0, %v);\n", enableTun)
fmt.Fprintf(w, "\tint pid = do_sandbox_%v();\n", opts.Sandbox)
fmt.Fprint(w, "\tint status = 0;\n")
fmt.Fprint(w, "\twhile (waitpid(pid, &status, __WALL) != pid) {}\n")
fmt.Fprint(w, "\treturn 0;\n}\n")
} else {
generateTestFunc(w, opts, calls, "test")
if opts.Procs <= 1 {
fmt.Fprint(w, "int main()\n{\n")
fmt.Fprintf(w, "\tsetup_main_process(0, %v);\n", enableTun)
fmt.Fprintf(w, "\tint pid = do_sandbox_%v();\n", opts.Sandbox)
fmt.Fprint(w, "\tint status = 0;\n")
fmt.Fprint(w, "\twhile (waitpid(pid, &status, __WALL) != pid) {}\n")
fmt.Fprint(w, "\treturn 0;\n}\n")
} else {
fmt.Fprint(w, "int main()\n{\n")
fmt.Fprint(w, "\tint i;")
fmt.Fprintf(w, "\tfor (i = 0; i < %v; i++) {\n", opts.Procs)
fmt.Fprint(w, "\t\tif (fork() == 0) {\n")
fmt.Fprintf(w, "\t\t\tsetup_main_process(i, %v);\n", enableTun)
fmt.Fprintf(w, "\t\t\tdo_sandbox_%v();\n", opts.Sandbox)
fmt.Fprint(w, "\t\t\treturn 0;\n")
fmt.Fprint(w, "\t\t}\n")
fmt.Fprint(w, "\t}\n")
fmt.Fprint(w, "\tsleep(1000000);\n")
fmt.Fprint(w, "\treturn 0;\n}\n")
}
}
// Remove duplicate new lines.
out := w.Bytes()
for {
out1 := bytes.Replace(out, []byte{'\n', '\n', '\n'}, []byte{'\n', '\n'}, -1)
if len(out) == len(out1) {
break
}
out = out1
}
return out, nil
}
func generateTestFunc(w io.Writer, opts Options, calls []string, name string) {
if !opts.Threaded && !opts.Collide {
fmt.Fprintf(w, "void %v()\n{\n", name)
if opts.Repro {
fmt.Fprintf(w, "\tsyscall(SYS_write, 1, \"executing program\\n\", strlen(\"executing program\\n\"));\n")
}
fmt.Fprintf(w, "\tmemset(r, -1, sizeof(r));\n")
for _, c := range calls {
fmt.Fprintf(w, "%s", c)
}
fmt.Fprintf(w, "}\n")
} else {
fmt.Fprintf(w, "void *thr(void *arg)\n{\n")
fmt.Fprintf(w, "\tswitch ((long)arg) {\n")
for i, c := range calls {
fmt.Fprintf(w, "\tcase %v:\n", i)
fmt.Fprintf(w, "%s", strings.Replace(c, "\t", "\t\t", -1))
fmt.Fprintf(w, "\t\tbreak;\n")
}
fmt.Fprintf(w, "\t}\n")
fmt.Fprintf(w, "\treturn 0;\n}\n\n")
fmt.Fprintf(w, "void %v()\n{\n", name)
fmt.Fprintf(w, "\tlong i;\n")
fmt.Fprintf(w, "\tpthread_t th[%v];\n", 2*len(calls))
fmt.Fprintf(w, "\n")
if opts.Repro {
fmt.Fprintf(w, "\tsyscall(SYS_write, 1, \"executing program\\n\", strlen(\"executing program\\n\"));\n")
}
fmt.Fprintf(w, "\tmemset(r, -1, sizeof(r));\n")
fmt.Fprintf(w, "\tsrand(getpid());\n")
fmt.Fprintf(w, "\tfor (i = 0; i < %v; i++) {\n", len(calls))
fmt.Fprintf(w, "\t\tpthread_create(&th[i], 0, thr, (void*)i);\n")
fmt.Fprintf(w, "\t\tusleep(10000);\n")
fmt.Fprintf(w, "\t}\n")
if opts.Collide {
fmt.Fprintf(w, "\tfor (i = 0; i < %v; i++) {\n", len(calls))
fmt.Fprintf(w, "\t\tpthread_create(&th[%v+i], 0, thr, (void*)i);\n", len(calls))
fmt.Fprintf(w, "\t\tif (rand()%%2)\n")
fmt.Fprintf(w, "\t\t\tusleep(rand()%%10000);\n")
fmt.Fprintf(w, "\t}\n")
}
fmt.Fprintf(w, "\tusleep(100000);\n")
fmt.Fprintf(w, "}\n\n")
}
}
func generateCalls(exec []byte) ([]string, int) {
read := func() uintptr {
if len(exec) < 8 {
panic("exec program overflow")
}
v := *(*uint64)(unsafe.Pointer(&exec[0]))
exec = exec[8:]
return uintptr(v)
}
resultRef := func() string {
arg := read()
res := fmt.Sprintf("r[%v]", arg)
if opDiv := read(); opDiv != 0 {
res = fmt.Sprintf("%v/%v", res, opDiv)
}
if opAdd := read(); opAdd != 0 {
res = fmt.Sprintf("%v+%v", res, opAdd)
}
return res
}
lastCall := 0
seenCall := false
var calls []string
w := new(bytes.Buffer)
newCall := func() {
if seenCall {
seenCall = false
calls = append(calls, w.String())
w = new(bytes.Buffer)
}
}
n := 0
loop:
for ; ; n++ {
switch instr := read(); instr {
case prog.ExecInstrEOF:
break loop
case prog.ExecInstrCopyin:
newCall()
addr := read()
typ := read()
size := read()
switch typ {
case prog.ExecArgConst:
arg := read()
bfOff := read()
bfLen := read()
if bfOff == 0 && bfLen == 0 {
fmt.Fprintf(w, "\tNONFAILING(*(uint%v_t*)0x%x = (uint%v_t)0x%x);\n", size*8, addr, size*8, arg)
} else {
fmt.Fprintf(w, "\tNONFAILING(STORE_BY_BITMASK(uint%v_t, 0x%x, 0x%x, %v, %v));\n", size*8, addr, arg, bfOff, bfLen)
}
case prog.ExecArgResult:
fmt.Fprintf(w, "\tNONFAILING(*(uint%v_t*)0x%x = %v);\n", size*8, addr, resultRef())
case prog.ExecArgData:
data := exec[:size]
exec = exec[(size+7)/8*8:]
var esc []byte
for _, v := range data {
hex := func(v byte) byte {
if v < 10 {
return '0' + v
}
return 'a' + v - 10
}
esc = append(esc, '\\', 'x', hex(v>>4), hex(v<<4>>4))
}
fmt.Fprintf(w, "\tNONFAILING(memcpy((void*)0x%x, \"%s\", %v));\n", addr, esc, size)
default:
panic("bad argument type")
}
case prog.ExecInstrCopyout:
addr := read()
size := read()
fmt.Fprintf(w, "\tif (r[%v] != -1)\n", lastCall)
fmt.Fprintf(w, "\t\tNONFAILING(r[%v] = *(uint%v_t*)0x%x);\n", n, size*8, addr)
default:
// Normal syscall.
newCall()
meta := sys.Calls[instr]
fmt.Fprintf(w, "\tr[%v] = execute_syscall(__NR_%v", n, meta.CallName)
nargs := read()
for i := uintptr(0); i < nargs; i++ {
typ := read()
size := read()
_ = size
switch typ {
case prog.ExecArgConst:
fmt.Fprintf(w, ", 0x%xul", read())
// Bitfields can't be args of a normal syscall, so just ignore them.
read() // bit field offset
read() // bit field length
case prog.ExecArgResult:
fmt.Fprintf(w, ", %v", resultRef())
default:
panic("unknown arg type")
}
}
for i := nargs; i < 9; i++ {
fmt.Fprintf(w, ", 0")
}
fmt.Fprintf(w, ");\n")
lastCall = n
seenCall = true
}
}
newCall()
return calls, n
}
func preprocessCommonHeader(opts Options, handled map[string]int) (string, error) {
var defines []string
switch opts.Sandbox {
case "none":
defines = append(defines, "SYZ_SANDBOX_NONE")
case "setuid":
defines = append(defines, "SYZ_SANDBOX_SETUID")
case "namespace":
defines = append(defines, "SYZ_SANDBOX_NAMESPACE")
default:
return "", fmt.Errorf("unknown sandbox mode: %v", opts.Sandbox)
}
if opts.Repeat {
defines = append(defines, "SYZ_REPEAT")
}
for name, _ := range handled {
defines = append(defines, "__NR_"+name)
}
// TODO: need to know target arch + do cross-compilation
defines = append(defines, "__x86_64__")
cmd := exec.Command("cpp", "-nostdinc", "-undef", "-fdirectives-only", "-dDI", "-E", "-P", "-")
for _, def := range defines {
cmd.Args = append(cmd.Args, "-D"+def)
}
cmd.Stdin = strings.NewReader(commonHeader)
stderr := new(bytes.Buffer)
stdout := new(bytes.Buffer)
cmd.Stderr = stderr
cmd.Stdout = stdout
if err := cmd.Run(); len(stdout.Bytes()) == 0 {
return "", fmt.Errorf("cpp failed: %v\n%v\n%v\n", err, stdout.String(), stderr.String())
}
remove := append(defines, []string{
"__STDC__",
"__STDC_VERSION__",
"__STDC_HOSTED__",
"__STDC_UTF_16__",
"__STDC_UTF_32__",
}...)
out := stdout.String()
for _, def := range remove {
out = strings.Replace(out, "#define "+def+" 1\n", "", -1)
}
return out, nil
}
// Build builds a C/C++ program from source src and returns name of the resulting binary.
// lang can be "c" or "c++".
func Build(lang, src string) (string, error) {
bin, err := ioutil.TempFile("", "syzkaller")
if err != nil {
return "", fmt.Errorf("failed to create temp file: %v", err)
}
bin.Close()
out, err := exec.Command("gcc", "-x", lang, "-Wall", "-Werror", src, "-o", bin.Name(), "-pthread", "-static", "-O1", "-g").CombinedOutput()
if err != nil {
// Some distributions don't have static libraries.
out, err = exec.Command("gcc", "-x", lang, "-Wall", "-Werror", src, "-o", bin.Name(), "-pthread", "-O1", "-g").CombinedOutput()
}
if err != nil {
os.Remove(bin.Name())
data, _ := ioutil.ReadFile(src)
return "", fmt.Errorf("failed to build program:\n%s\n%s", data, out)
}
return bin.Name(), nil
}
// Format reformats C source using clang-format.
func Format(src []byte) ([]byte, error) {
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
cmd := exec.Command("clang-format", "-assume-filename=/src.c", "-style", style)
cmd.Stdin = bytes.NewReader(src)
cmd.Stdout = stdout
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return src, fmt.Errorf("failed to format source: %v\n%v", err, stderr.String())
}
return stdout.Bytes(), nil
}
// Something acceptable for kernel developers and email-friendly.
var style = `{
BasedOnStyle: LLVM,
IndentWidth: 2,
UseTab: Never,
BreakBeforeBraces: Linux,
IndentCaseLabels: false,
DerivePointerAlignment: false,
PointerAlignment: Left,
AlignTrailingComments: true,
AllowShortBlocksOnASingleLine: false,
AllowShortCaseLabelsOnASingleLine: false,
AllowShortFunctionsOnASingleLine: false,
AllowShortIfStatementsOnASingleLine: false,
AllowShortLoopsOnASingleLine: false,
ColumnLimit: 72,
}`