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 }