github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/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  		// TODO: if we propagate this error, something breaks and no coverage
    79  		// is displayed to the dashboard or the logs.
    80  		_ = w.serializeCall(c)
    81  	}
    82  	w.write(execInstrEOF)
    83  	if len(w.buf) > ExecBufferSize {
    84  		return nil, fmt.Errorf("encodingexec: too large program (%v/%v)", len(w.buf), ExecBufferSize)
    85  	}
    86  	if w.copyoutSeq > execMaxCommands {
    87  		return nil, fmt.Errorf("encodingexec: too many resources (%v/%v)", w.copyoutSeq, execMaxCommands)
    88  	}
    89  	return w.buf, nil
    90  }
    91  
    92  func (w *execContext) serializeCall(c *Call) error {
    93  	// We introduce special serialization logic for kfuzztest targets, which
    94  	// require special handling due to their use of relocation tables to copy
    95  	// entire blobs of data into the kenrel.
    96  	if c.Meta.Attrs.KFuzzTest {
    97  		return w.serializeKFuzzTestCall(c)
    98  	}
    99  
   100  	// Calculate arg offsets within structs.
   101  	// Generate copyin instructions that fill in data into pointer arguments.
   102  	w.writeCopyin(c)
   103  	// Generate checksum calculation instructions starting from the last one,
   104  	// since checksum values can depend on values of the latter ones
   105  	w.writeChecksums()
   106  	if !reflect.DeepEqual(c.Props, CallProps{}) {
   107  		// Push call properties.
   108  		w.writeCallProps(c.Props)
   109  	}
   110  	// Generate the call itself.
   111  	w.write(uint64(c.Meta.ID))
   112  	if c.Ret != nil && len(c.Ret.uses) != 0 {
   113  		if _, ok := w.args[c.Ret]; ok {
   114  			panic("argInfo is already created for return value")
   115  		}
   116  		w.args[c.Ret] = argInfo{Idx: w.copyoutSeq, Ret: true}
   117  		w.write(w.copyoutSeq)
   118  		w.copyoutSeq++
   119  	} else {
   120  		w.write(ExecNoCopyout)
   121  	}
   122  	w.write(uint64(len(c.Args)))
   123  	for _, arg := range c.Args {
   124  		w.writeArg(arg)
   125  	}
   126  
   127  	// Generate copyout instructions that persist interesting return values.
   128  	w.writeCopyout(c)
   129  	return nil
   130  }
   131  
   132  // KFuzzTest targets require special handling due to their use of relocation
   133  // tables for serializing all data (including pointed-to data) into a
   134  // continuous blob that can be passed into the kernel.
   135  func (w *execContext) serializeKFuzzTestCall(c *Call) error {
   136  	if !c.Meta.Attrs.KFuzzTest {
   137  		// This is a specialized function that shouldn't be called on anything
   138  		// other than an instance of a syz_kfuzztest_run$* syscall
   139  		panic("serializeKFuzzTestCall called on an invalid syscall")
   140  	}
   141  
   142  	// Generate the final syscall instruction with the update arguments.
   143  	kFuzzTestRunID, err := w.target.KFuzzTestRunID()
   144  	if err != nil {
   145  		panic(err)
   146  	}
   147  	// Ensures that we copy some arguments into the executor so that it doesn't
   148  	// receive an incomplete program on failure.
   149  	defer func() {
   150  		w.write(uint64(kFuzzTestRunID))
   151  		w.write(ExecNoCopyout)
   152  		w.write(uint64(len(c.Args)))
   153  		for _, arg := range c.Args {
   154  			w.writeArg(arg)
   155  		}
   156  	}()
   157  
   158  	// Write the initial string argument (test name) normally.
   159  	w.writeCopyin(&Call{Meta: c.Meta, Args: []Arg{c.Args[0]}})
   160  
   161  	// Args[1] is the second argument to syz_kfuzztest_run, which is a pointer
   162  	// to some struct input. This is the data that must be flattened and sent
   163  	// to the fuzzing driver with a relocation table.
   164  	dataArg := c.Args[1].(*PointerArg)
   165  	finalBlob := MarshallKFuzztestArg(dataArg.Res)
   166  	if len(finalBlob) > int(KFuzzTestMaxInputSize) {
   167  		return fmt.Errorf("encoded blob was too large")
   168  	}
   169  
   170  	// Use the buffer argument as data offset - this represents a buffer of
   171  	// size 64KiB - the maximum input size that the KFuzzTest module accepts.
   172  	bufferArg := c.Args[3].(*PointerArg)
   173  	if bufferArg.Res == nil {
   174  		return fmt.Errorf("buffer was nil")
   175  	}
   176  	blobAddress := w.target.PhysicalAddr(bufferArg) - w.target.DataOffset
   177  
   178  	// Write the entire marshalled blob as a raw byte array.
   179  	w.write(execInstrCopyin)
   180  	w.write(blobAddress)
   181  	w.write(execArgData)
   182  	w.write(uint64(len(finalBlob)))
   183  	w.buf = append(w.buf, finalBlob...)
   184  
   185  	// Update the value of the length arg which should now match the length of
   186  	// the byte array that we created. Previously, it contained the bytesize
   187  	// of the struct argument passed into the pseudo-syscall.
   188  	lenArg := c.Args[2].(*ConstArg)
   189  	lenArg.Val = uint64(len(finalBlob))
   190  	return nil
   191  }
   192  
   193  type execContext struct {
   194  	target     *Target
   195  	buf        []byte
   196  	args       map[Arg]argInfo
   197  	copyoutSeq uint64
   198  	// Per-call state cached here to not pass it through all functions.
   199  	csumMap  map[Arg]CsumInfo
   200  	csumUses map[Arg]struct{}
   201  }
   202  
   203  type argInfo struct {
   204  	Addr uint64 // physical addr
   205  	Idx  uint64 // copyout instruction index
   206  	Ret  bool
   207  }
   208  
   209  func (w *execContext) writeCallProps(props CallProps) {
   210  	w.write(execInstrSetProps)
   211  	props.ForeachProp(func(_, _ string, value reflect.Value) {
   212  		var uintVal uint64
   213  		switch kind := value.Kind(); kind {
   214  		case reflect.Int:
   215  			uintVal = uint64(value.Int())
   216  		case reflect.Bool:
   217  			if value.Bool() {
   218  				uintVal = 1
   219  			}
   220  		default:
   221  			panic("Unsupported (yet) kind: " + kind.String())
   222  		}
   223  		w.write(uintVal)
   224  	})
   225  }
   226  
   227  func (w *execContext) writeCopyin(c *Call) {
   228  	ForeachArg(c, func(arg Arg, ctx *ArgCtx) {
   229  		if ctx.Base == nil {
   230  			return
   231  		}
   232  		addr := w.target.PhysicalAddr(ctx.Base) - w.target.DataOffset + ctx.Offset
   233  		addr -= arg.Type().UnitOffset()
   234  		if w.willBeUsed(arg) {
   235  			w.args[arg] = argInfo{Addr: addr}
   236  		}
   237  		switch arg.(type) {
   238  		case *GroupArg, *UnionArg:
   239  			return
   240  		}
   241  		typ := arg.Type()
   242  		if arg.Dir() == DirOut || IsPad(typ) || (arg.Size() == 0 && !typ.IsBitfield()) {
   243  			return
   244  		}
   245  		w.write(execInstrCopyin)
   246  		w.write(addr)
   247  		w.writeArg(arg)
   248  	})
   249  }
   250  
   251  func (w *execContext) willBeUsed(arg Arg) bool {
   252  	if res, ok := arg.(*ResultArg); ok && len(res.uses) != 0 {
   253  		return true
   254  	}
   255  	_, ok1 := w.csumMap[arg]
   256  	_, ok2 := w.csumUses[arg]
   257  	return ok1 || ok2
   258  }
   259  
   260  func (w *execContext) writeChecksums() {
   261  	if len(w.csumMap) == 0 {
   262  		return
   263  	}
   264  	csumArgs := make([]Arg, 0, len(w.csumMap))
   265  	for arg := range w.csumMap {
   266  		csumArgs = append(csumArgs, arg)
   267  	}
   268  	sort.Slice(csumArgs, func(i, j int) bool {
   269  		return w.args[csumArgs[i]].Addr < w.args[csumArgs[j]].Addr
   270  	})
   271  	for i := len(csumArgs) - 1; i >= 0; i-- {
   272  		arg := csumArgs[i]
   273  		info := w.csumMap[arg]
   274  		if _, ok := arg.Type().(*CsumType); !ok {
   275  			panic("csum arg is not csum type")
   276  		}
   277  		w.write(execInstrCopyin)
   278  		w.write(w.args[arg].Addr)
   279  		w.write(execArgCsum)
   280  		w.write(arg.Size())
   281  		switch info.Kind {
   282  		case CsumInet:
   283  			w.write(ExecArgCsumInet)
   284  			w.write(uint64(len(info.Chunks)))
   285  			for _, chunk := range info.Chunks {
   286  				switch chunk.Kind {
   287  				case CsumChunkArg:
   288  					w.write(ExecArgCsumChunkData)
   289  					w.write(w.args[chunk.Arg].Addr)
   290  					w.write(chunk.Arg.Size())
   291  				case CsumChunkConst:
   292  					w.write(ExecArgCsumChunkConst)
   293  					w.write(chunk.Value)
   294  					w.write(chunk.Size)
   295  				default:
   296  					panic(fmt.Sprintf("csum chunk has unknown kind %v", chunk.Kind))
   297  				}
   298  			}
   299  		default:
   300  			panic(fmt.Sprintf("csum arg has unknown kind %v", info.Kind))
   301  		}
   302  	}
   303  }
   304  
   305  func (w *execContext) writeCopyout(c *Call) {
   306  	ForeachArg(c, func(arg Arg, _ *ArgCtx) {
   307  		if res, ok := arg.(*ResultArg); ok && len(res.uses) != 0 {
   308  			// Create a separate copyout instruction that has own Idx.
   309  			info := w.args[arg]
   310  			if info.Ret {
   311  				return // Idx is already assigned above.
   312  			}
   313  			info.Idx = w.copyoutSeq
   314  			w.copyoutSeq++
   315  			w.args[arg] = info
   316  			w.write(execInstrCopyout)
   317  			w.write(info.Idx)
   318  			w.write(info.Addr)
   319  			w.write(arg.Size())
   320  		}
   321  	})
   322  }
   323  
   324  func (w *execContext) write(v uint64) {
   325  	w.buf = binary.AppendVarint(w.buf, int64(v))
   326  }
   327  
   328  func (w *execContext) writeArg(arg Arg) {
   329  	switch a := arg.(type) {
   330  	case *ConstArg:
   331  		val, pidStride := a.Value()
   332  		typ := a.Type()
   333  		w.writeConstArg(typ.UnitSize(), val, typ.BitfieldOffset(), typ.BitfieldLength(), pidStride, typ.Format())
   334  	case *ResultArg:
   335  		if a.Res == nil {
   336  			w.writeConstArg(a.Size(), a.Val, 0, 0, 0, a.Type().Format())
   337  		} else {
   338  			info, ok := w.args[a.Res]
   339  			if !ok {
   340  				panic("no copyout index")
   341  			}
   342  			w.write(execArgResult)
   343  			meta := a.Size() | uint64(a.Type().Format())<<8
   344  			w.write(meta)
   345  			w.write(info.Idx)
   346  			w.write(a.OpDiv)
   347  			w.write(a.OpAdd)
   348  			w.write(a.Type().(*ResourceType).Default())
   349  		}
   350  	case *PointerArg:
   351  		switch a.Size() {
   352  		case 4:
   353  			w.write(execArgAddr32)
   354  		case 8:
   355  			w.write(execArgAddr64)
   356  		default:
   357  			panic(fmt.Sprintf("bad pointer address size %v", a.Size()))
   358  		}
   359  		w.write(w.target.PhysicalAddr(a) - w.target.DataOffset)
   360  	case *DataArg:
   361  		data := a.Data()
   362  		if len(data) == 0 {
   363  			panic("writing data arg with 0 size")
   364  		}
   365  		w.write(execArgData)
   366  		flags := uint64(len(data))
   367  		if isReadableDataType(a.Type().(*BufferType)) {
   368  			flags |= execArgDataReadable
   369  		}
   370  		w.write(flags)
   371  		w.buf = append(w.buf, data...)
   372  	case *UnionArg:
   373  		w.writeArg(a.Option)
   374  	default:
   375  		panic("unknown arg type")
   376  	}
   377  }
   378  
   379  func (w *execContext) writeConstArg(size, val, bfOffset, bfLength, pidStride uint64, bf BinaryFormat) {
   380  	w.write(execArgConst)
   381  	meta := size | uint64(bf)<<8 | bfOffset<<16 | bfLength<<24 | pidStride<<32
   382  	w.write(meta)
   383  	w.write(val)
   384  }