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 }