github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/prog/encodingexec.go (about)

     1  // Copyright 2015 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  // This file does serialization of programs for executor binary.
     5  // The format aims at simple parsing: binary and irreversible.
     6  
     7  // Exec format is an sequence of uint64's which encodes a sequence of calls.
     8  // The sequence is terminated by a speciall call execInstrEOF.
     9  // Each call is (call ID, copyout index, number of arguments, arguments...).
    10  // Each argument is (type, size, value).
    11  // There are the following types of arguments:
    12  //  - execArgConst: value is const value
    13  //  - execArgAddr32/64: constant address
    14  //  - execArgResult: value is copyout index we want to reference
    15  //  - execArgData: value is a binary blob (represented as ]size/8[ uint64's)
    16  //  - execArgCsum: runtime checksum calculation
    17  // There are the following special calls:
    18  //  - execInstrCopyin: copies its second argument into address specified by first argument
    19  //  - execInstrCopyout: reads value at address specified by first argument (result can be referenced by execArgResult)
    20  //  - execInstrSetProps: sets special properties for the previous call
    21  
    22  package prog
    23  
    24  import (
    25  	"encoding/binary"
    26  	"fmt"
    27  	"reflect"
    28  	"sort"
    29  )
    30  
    31  const (
    32  	execInstrEOF = ^uint64(iota)
    33  	execInstrCopyin
    34  	execInstrCopyout
    35  	execInstrSetProps
    36  )
    37  
    38  const (
    39  	execArgConst = uint64(iota)
    40  	execArgAddr32
    41  	execArgAddr64
    42  	execArgResult
    43  	execArgData
    44  	execArgCsum
    45  
    46  	execArgDataReadable = uint64(1 << 63)
    47  )
    48  
    49  const (
    50  	ExecArgCsumInet = uint64(iota)
    51  )
    52  
    53  const (
    54  	ExecArgCsumChunkData = uint64(iota)
    55  	ExecArgCsumChunkConst
    56  )
    57  
    58  const (
    59  	ExecBufferSize = 4 << 20 // keep in sync with kMaxInput in executor.cc
    60  	ExecNoCopyout  = ^uint64(0)
    61  
    62  	execMaxCommands = 1000 // executor knows about this constant (kMaxCommands)
    63  )
    64  
    65  // SerializeForExec serializes program p for execution by process pid into the provided buffer.
    66  // Returns number of bytes written to the buffer.
    67  // If the provided buffer is too small for the program an error is returned.
    68  func (p *Prog) SerializeForExec() ([]byte, error) {
    69  	p.debugValidate()
    70  	w := &execContext{
    71  		target: p.Target,
    72  		buf:    make([]byte, 0, 4<<10),
    73  		args:   make(map[Arg]argInfo),
    74  	}
    75  	w.write(uint64(len(p.Calls)))
    76  	for _, c := range p.Calls {
    77  		w.csumMap, w.csumUses = calcChecksumsCall(c)
    78  		w.serializeCall(c)
    79  	}
    80  	w.write(execInstrEOF)
    81  	if len(w.buf) > ExecBufferSize {
    82  		return nil, fmt.Errorf("encodingexec: too large program (%v/%v)", len(w.buf), ExecBufferSize)
    83  	}
    84  	if w.copyoutSeq > execMaxCommands {
    85  		return nil, fmt.Errorf("encodingexec: too many resources (%v/%v)", w.copyoutSeq, execMaxCommands)
    86  	}
    87  	return w.buf, nil
    88  }
    89  
    90  func (w *execContext) serializeCall(c *Call) {
    91  	// Calculate arg offsets within structs.
    92  	// Generate copyin instructions that fill in data into pointer arguments.
    93  	w.writeCopyin(c)
    94  	// Generate checksum calculation instructions starting from the last one,
    95  	// since checksum values can depend on values of the latter ones
    96  	w.writeChecksums()
    97  	if !reflect.DeepEqual(c.Props, CallProps{}) {
    98  		// Push call properties.
    99  		w.writeCallProps(c.Props)
   100  	}
   101  	// Generate the call itself.
   102  	w.write(uint64(c.Meta.ID))
   103  	if c.Ret != nil && len(c.Ret.uses) != 0 {
   104  		if _, ok := w.args[c.Ret]; ok {
   105  			panic("argInfo is already created for return value")
   106  		}
   107  		w.args[c.Ret] = argInfo{Idx: w.copyoutSeq, Ret: true}
   108  		w.write(w.copyoutSeq)
   109  		w.copyoutSeq++
   110  	} else {
   111  		w.write(ExecNoCopyout)
   112  	}
   113  	w.write(uint64(len(c.Args)))
   114  	for _, arg := range c.Args {
   115  		w.writeArg(arg)
   116  	}
   117  
   118  	// Generate copyout instructions that persist interesting return values.
   119  	w.writeCopyout(c)
   120  }
   121  
   122  type execContext struct {
   123  	target     *Target
   124  	buf        []byte
   125  	args       map[Arg]argInfo
   126  	copyoutSeq uint64
   127  	// Per-call state cached here to not pass it through all functions.
   128  	csumMap  map[Arg]CsumInfo
   129  	csumUses map[Arg]struct{}
   130  }
   131  
   132  type argInfo struct {
   133  	Addr uint64 // physical addr
   134  	Idx  uint64 // copyout instruction index
   135  	Ret  bool
   136  }
   137  
   138  func (w *execContext) writeCallProps(props CallProps) {
   139  	w.write(execInstrSetProps)
   140  	props.ForeachProp(func(_, _ string, value reflect.Value) {
   141  		var uintVal uint64
   142  		switch kind := value.Kind(); kind {
   143  		case reflect.Int:
   144  			uintVal = uint64(value.Int())
   145  		case reflect.Bool:
   146  			if value.Bool() {
   147  				uintVal = 1
   148  			}
   149  		default:
   150  			panic("Unsupported (yet) kind: " + kind.String())
   151  		}
   152  		w.write(uintVal)
   153  	})
   154  }
   155  
   156  func (w *execContext) writeCopyin(c *Call) {
   157  	ForeachArg(c, func(arg Arg, ctx *ArgCtx) {
   158  		if ctx.Base == nil {
   159  			return
   160  		}
   161  		addr := w.target.PhysicalAddr(ctx.Base) - w.target.DataOffset + ctx.Offset
   162  		addr -= arg.Type().UnitOffset()
   163  		if w.willBeUsed(arg) {
   164  			w.args[arg] = argInfo{Addr: addr}
   165  		}
   166  		switch arg.(type) {
   167  		case *GroupArg, *UnionArg:
   168  			return
   169  		}
   170  		typ := arg.Type()
   171  		if arg.Dir() == DirOut || IsPad(typ) || (arg.Size() == 0 && !typ.IsBitfield()) {
   172  			return
   173  		}
   174  		w.write(execInstrCopyin)
   175  		w.write(addr)
   176  		w.writeArg(arg)
   177  	})
   178  }
   179  
   180  func (w *execContext) willBeUsed(arg Arg) bool {
   181  	if res, ok := arg.(*ResultArg); ok && len(res.uses) != 0 {
   182  		return true
   183  	}
   184  	_, ok1 := w.csumMap[arg]
   185  	_, ok2 := w.csumUses[arg]
   186  	return ok1 || ok2
   187  }
   188  
   189  func (w *execContext) writeChecksums() {
   190  	if len(w.csumMap) == 0 {
   191  		return
   192  	}
   193  	csumArgs := make([]Arg, 0, len(w.csumMap))
   194  	for arg := range w.csumMap {
   195  		csumArgs = append(csumArgs, arg)
   196  	}
   197  	sort.Slice(csumArgs, func(i, j int) bool {
   198  		return w.args[csumArgs[i]].Addr < w.args[csumArgs[j]].Addr
   199  	})
   200  	for i := len(csumArgs) - 1; i >= 0; i-- {
   201  		arg := csumArgs[i]
   202  		info := w.csumMap[arg]
   203  		if _, ok := arg.Type().(*CsumType); !ok {
   204  			panic("csum arg is not csum type")
   205  		}
   206  		w.write(execInstrCopyin)
   207  		w.write(w.args[arg].Addr)
   208  		w.write(execArgCsum)
   209  		w.write(arg.Size())
   210  		switch info.Kind {
   211  		case CsumInet:
   212  			w.write(ExecArgCsumInet)
   213  			w.write(uint64(len(info.Chunks)))
   214  			for _, chunk := range info.Chunks {
   215  				switch chunk.Kind {
   216  				case CsumChunkArg:
   217  					w.write(ExecArgCsumChunkData)
   218  					w.write(w.args[chunk.Arg].Addr)
   219  					w.write(chunk.Arg.Size())
   220  				case CsumChunkConst:
   221  					w.write(ExecArgCsumChunkConst)
   222  					w.write(chunk.Value)
   223  					w.write(chunk.Size)
   224  				default:
   225  					panic(fmt.Sprintf("csum chunk has unknown kind %v", chunk.Kind))
   226  				}
   227  			}
   228  		default:
   229  			panic(fmt.Sprintf("csum arg has unknown kind %v", info.Kind))
   230  		}
   231  	}
   232  }
   233  
   234  func (w *execContext) writeCopyout(c *Call) {
   235  	ForeachArg(c, func(arg Arg, _ *ArgCtx) {
   236  		if res, ok := arg.(*ResultArg); ok && len(res.uses) != 0 {
   237  			// Create a separate copyout instruction that has own Idx.
   238  			info := w.args[arg]
   239  			if info.Ret {
   240  				return // Idx is already assigned above.
   241  			}
   242  			info.Idx = w.copyoutSeq
   243  			w.copyoutSeq++
   244  			w.args[arg] = info
   245  			w.write(execInstrCopyout)
   246  			w.write(info.Idx)
   247  			w.write(info.Addr)
   248  			w.write(arg.Size())
   249  		}
   250  	})
   251  }
   252  
   253  func (w *execContext) write(v uint64) {
   254  	w.buf = binary.AppendVarint(w.buf, int64(v))
   255  }
   256  
   257  func (w *execContext) writeArg(arg Arg) {
   258  	switch a := arg.(type) {
   259  	case *ConstArg:
   260  		val, pidStride := a.Value()
   261  		typ := a.Type()
   262  		w.writeConstArg(typ.UnitSize(), val, typ.BitfieldOffset(), typ.BitfieldLength(), pidStride, typ.Format())
   263  	case *ResultArg:
   264  		if a.Res == nil {
   265  			w.writeConstArg(a.Size(), a.Val, 0, 0, 0, a.Type().Format())
   266  		} else {
   267  			info, ok := w.args[a.Res]
   268  			if !ok {
   269  				panic("no copyout index")
   270  			}
   271  			w.write(execArgResult)
   272  			meta := a.Size() | uint64(a.Type().Format())<<8
   273  			w.write(meta)
   274  			w.write(info.Idx)
   275  			w.write(a.OpDiv)
   276  			w.write(a.OpAdd)
   277  			w.write(a.Type().(*ResourceType).Default())
   278  		}
   279  	case *PointerArg:
   280  		switch a.Size() {
   281  		case 4:
   282  			w.write(execArgAddr32)
   283  		case 8:
   284  			w.write(execArgAddr64)
   285  		default:
   286  			panic(fmt.Sprintf("bad pointer address size %v", a.Size()))
   287  		}
   288  		w.write(w.target.PhysicalAddr(a) - w.target.DataOffset)
   289  	case *DataArg:
   290  		data := a.Data()
   291  		if len(data) == 0 {
   292  			panic("writing data arg with 0 size")
   293  		}
   294  		w.write(execArgData)
   295  		flags := uint64(len(data))
   296  		if isReadableDataType(a.Type().(*BufferType)) {
   297  			flags |= execArgDataReadable
   298  		}
   299  		w.write(flags)
   300  		w.buf = append(w.buf, data...)
   301  	case *UnionArg:
   302  		w.writeArg(a.Option)
   303  	default:
   304  		panic("unknown arg type")
   305  	}
   306  }
   307  
   308  func (w *execContext) writeConstArg(size, val, bfOffset, bfLength, pidStride uint64, bf BinaryFormat) {
   309  	w.write(execArgConst)
   310  	meta := size | uint64(bf)<<8 | bfOffset<<16 | bfLength<<24 | pidStride<<32
   311  	w.write(meta)
   312  	w.write(val)
   313  }