// 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 (
"fmt"
)
type ExecProg struct {
Calls []ExecCall
Vars []uint64
}
type ExecCall struct {
Meta *Syscall
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
}
type ExecArgCsum struct {
Size uint64
Kind uint64
Chunks []ExecCsumChunk
}
type ExecCsumChunk struct {
Kind uint64
Value uint64
Size uint64
}
func (target *Target) DeserializeExec(exec []byte) (ExecProg, error) {
dec := &execDecoder{target: target, data: exec}
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
}
func (dec *execDecoder) parse() {
for dec.err == nil {
switch instr := dec.read(); instr {
case execInstrCopyin:
dec.commitCall()
dec.call.Copyin = append(dec.call.Copyin, ExecCopyin{
Addr: dec.read(),
Arg: dec.readArg(),
})
case execInstrCopyout:
dec.call.Copyout = append(dec.call.Copyout, ExecCopyout{
Index: dec.read(),
Addr: dec.read(),
Size: dec.read(),
})
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()
for i := dec.read(); 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
}
}
case execInstrEOF:
dec.commitCall()
return
}
}
}
func (dec *execDecoder) readArg() ExecArg {
switch typ := dec.read(); typ {
case execArgConst:
meta := dec.read()
return ExecArgConst{
Value: dec.read(),
Size: meta & 0xff,
Format: BinaryFormat((meta >> 8) & 0xff),
BitfieldOffset: (meta >> 16) & 0xff,
BitfieldLength: (meta >> 24) & 0xff,
PidStride: meta >> 32,
}
case execArgResult:
meta := dec.read()
arg := ExecArgResult{
Size: meta & 0xff,
Format: BinaryFormat((meta >> 8) & 0xff),
Index: dec.read(),
DivOp: dec.read(),
AddOp: dec.read(),
Default: dec.read(),
}
for uint64(len(dec.vars)) <= arg.Index {
dec.vars = append(dec.vars, 0)
}
dec.vars[arg.Index] = arg.Default
return arg
case execArgData:
return ExecArgData{
Data: dec.readBlob(dec.read()),
}
case execArgCsum:
size := dec.read()
switch kind := dec.read(); kind {
case ExecArgCsumInet:
chunks := make([]ExecCsumChunk, dec.read())
for i := range chunks {
chunks[i] = ExecCsumChunk{
Kind: dec.read(),
Value: dec.read(),
Size: dec.read(),
}
}
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() uint64 {
if len(dec.data) < 8 {
dec.setErr(fmt.Errorf("exec program overflow"))
}
if dec.err != nil {
return 0
}
var v uint64
for i := 0; i < 8; i++ {
v |= uint64(dec.data[i]) << uint(i*8)
}
dec.data = dec.data[8:]
return v
}
func (dec *execDecoder) readBlob(size uint64) []byte {
padded := (size + 7) / 8 * 8
if uint64(len(dec.data)) < padded {
dec.setErr(fmt.Errorf("exec program overflow"))
}
if dec.err != nil {
return nil
}
data := dec.data[:size]
dec.data = dec.data[padded:]
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{}
}