| // 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 + "/" |
| } |
| } |