blob: 81c70cb2bb1c69a6a45ba811f3c2b615540e4692 [file] [log] [blame] [edit]
// 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 prog
import (
"encoding/binary"
"fmt"
"reflect"
"strings"
)
type ExecProg struct {
Calls []ExecCall
Vars []uint64
}
type ExecCall struct {
Meta *Syscall
Props CallProps
Index uint64
Args []ExecArg
Copyin []ExecCopyin
Copyout []ExecCopyout
}
type ExecCopyin struct {
Addr uint64
Arg ExecArg
}
type ExecCopyout struct {
Index uint64
Addr uint64
Size uint64
}
type ExecArg interface{} // one of ExecArg*
type ExecArgConst struct {
Size uint64
Format BinaryFormat
Value uint64
BitfieldOffset uint64
BitfieldLength uint64
PidStride uint64
}
type ExecArgResult struct {
Size uint64
Format BinaryFormat
Index uint64
DivOp uint64
AddOp uint64
Default uint64
}
type ExecArgData struct {
Data []byte
Readable bool
}
type ExecArgCsum struct {
Size uint64
Kind uint64
Chunks []ExecCsumChunk
}
type ExecCsumChunk struct {
Kind uint64
Value uint64
Size uint64
}
func ExecCallCount(exec []byte) (int, error) {
v, n := binary.Varint(exec)
if n <= 0 {
return 0, fmt.Errorf("not enough data in the buffer")
}
if v > MaxCalls {
return 0, fmt.Errorf("too many calls (%v)", v)
}
return int(v), nil
}
func (target *Target) DeserializeExec(exec []byte, stats map[string]int) (ExecProg, error) {
dec := &execDecoder{target: target, data: exec, stats: stats}
dec.parse()
if dec.err != nil {
return ExecProg{}, dec.err
}
if uint64(len(dec.vars)) != dec.numVars {
return ExecProg{}, fmt.Errorf("mismatching number of vars: %v/%v",
len(dec.vars), dec.numVars)
}
p := ExecProg{
Calls: dec.calls,
Vars: dec.vars,
}
return p, nil
}
type execDecoder struct {
target *Target
data []byte
err error
numVars uint64
vars []uint64
call ExecCall
calls []ExecCall
stats map[string]int
}
func (dec *execDecoder) parse() {
ncalls := dec.read("header")
for dec.err == nil {
switch instr := dec.read("instr/opcode"); instr {
case execInstrCopyin:
dec.commitCall()
dec.call.Copyin = append(dec.call.Copyin, ExecCopyin{
Addr: dec.read("instr/copyin") + dec.target.DataOffset,
Arg: dec.readArg(),
})
case execInstrCopyout:
dec.call.Copyout = append(dec.call.Copyout, ExecCopyout{
Index: dec.read("instr/copyout/index"),
Addr: dec.read("instr/copyout/addr") + dec.target.DataOffset,
Size: dec.read("instr/copyout/size"),
})
case execInstrEOF:
dec.commitCall()
if ncalls != uint64(len(dec.calls)) {
dec.err = fmt.Errorf("bad number of calls: %v/%v", ncalls, len(dec.calls))
}
return
case execInstrSetProps:
dec.commitCall()
dec.readCallProps(&dec.call.Props)
default:
dec.commitCall()
if instr >= uint64(len(dec.target.Syscalls)) {
dec.setErr(fmt.Errorf("bad syscall %v", instr))
return
}
dec.call.Meta = dec.target.Syscalls[instr]
dec.call.Index = dec.read("instr/index")
for i := dec.read("instr/nargs"); i > 0; i-- {
switch arg := dec.readArg(); arg.(type) {
case ExecArgConst, ExecArgResult:
dec.call.Args = append(dec.call.Args, arg)
default:
dec.setErr(fmt.Errorf("bad call arg %+v", arg))
return
}
}
}
}
}
func (dec *execDecoder) readCallProps(props *CallProps) {
props.ForeachProp(func(_, _ string, value reflect.Value) {
arg := dec.read("call prop")
switch kind := value.Kind(); kind {
case reflect.Int:
value.SetInt(int64(arg))
case reflect.Bool:
if arg == 1 {
value.SetBool(true)
}
default:
panic("Unsupported (yet) kind: " + kind.String())
}
})
}
func (dec *execDecoder) readArg() ExecArg {
switch typ := dec.read("arg/type"); typ {
case execArgConst:
meta := dec.read("arg/const/meta")
return ExecArgConst{
Value: dec.read("arg/const/value"),
Size: meta & 0xff,
Format: BinaryFormat((meta >> 8) & 0xff),
BitfieldOffset: (meta >> 16) & 0xff,
BitfieldLength: (meta >> 24) & 0xff,
PidStride: meta >> 32,
}
case execArgAddr32:
fallthrough
case execArgAddr64:
size := 4
if typ == execArgAddr64 {
size = 8
}
return ExecArgConst{
Value: dec.read("arg/addr") + dec.target.DataOffset,
Size: uint64(size),
}
case execArgResult:
meta := dec.read("arg/result/meta")
arg := ExecArgResult{
Size: meta & 0xff,
Format: BinaryFormat((meta >> 8) & 0xff),
Index: dec.read("arg/result/index"),
DivOp: dec.read("arg/result/divop"),
AddOp: dec.read("arg/result/addop"),
Default: dec.read("arg/result/default"),
}
for uint64(len(dec.vars)) <= arg.Index {
dec.vars = append(dec.vars, 0)
}
dec.vars[arg.Index] = arg.Default
return arg
case execArgData:
flags := dec.read("arg/data/size")
size := flags & ^execArgDataReadable
dec.addStat("arg/data/blob", int(size))
readable := flags&execArgDataReadable != 0
return ExecArgData{
Data: dec.readBlob(size),
Readable: readable,
}
case execArgCsum:
size := dec.read("arg/csum/size")
switch kind := dec.read("arg/csum/kind"); kind {
case ExecArgCsumInet:
chunks := make([]ExecCsumChunk, dec.read("arg/csum/chunks"))
for i := range chunks {
kind := dec.read("arg/csum/chunk/kind")
addr := dec.read("arg/csum/chunk/addr")
size := dec.read("arg/csum/chunk/size")
if kind == ExecArgCsumChunkData {
addr += dec.target.DataOffset
}
chunks[i] = ExecCsumChunk{
Kind: kind,
Value: addr,
Size: size,
}
}
return ExecArgCsum{
Size: size,
Kind: kind,
Chunks: chunks,
}
default:
dec.setErr(fmt.Errorf("unknown csum kind %v", kind))
return nil
}
default:
dec.setErr(fmt.Errorf("bad argument type %v", typ))
return nil
}
}
func (dec *execDecoder) read(stat string) uint64 {
if dec.err != nil {
return 0
}
v, n := binary.Varint(dec.data)
if n <= 0 {
dec.setErr(fmt.Errorf("exec program overflow"))
return 0
}
dec.addStat(stat, n)
dec.data = dec.data[n:]
return uint64(v)
}
func (dec *execDecoder) readBlob(size uint64) []byte {
if uint64(len(dec.data)) < size {
dec.setErr(fmt.Errorf("exec program overflow"))
}
if dec.err != nil {
return nil
}
data := dec.data[:size]
dec.data = dec.data[size:]
return data
}
func (dec *execDecoder) setErr(err error) {
if dec.err == nil {
dec.err = err
}
}
func (dec *execDecoder) commitCall() {
if dec.call.Meta == nil {
return
}
if dec.call.Index != ExecNoCopyout && dec.numVars < dec.call.Index+1 {
dec.numVars = dec.call.Index + 1
}
for _, copyout := range dec.call.Copyout {
if dec.numVars < copyout.Index+1 {
dec.numVars = copyout.Index + 1
}
}
dec.calls = append(dec.calls, dec.call)
dec.call = ExecCall{}
}
func (dec *execDecoder) addStat(stat string, n int) {
if dec.stats == nil {
return
}
prefix := ""
for _, part := range strings.Split(stat, "/") {
dec.stats[prefix+part] += n
prefix += part + "/"
}
}