aboutsummaryrefslogtreecommitdiff
path: root/tools/syz-trace2syz/proggen/proggen.go
blob: 523327698c8686c0731bd064e2a1c961a0618a03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
// Copyright 2018 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 proggen

import (
	"encoding/binary"
	"fmt"
	"io/ioutil"
	"math/rand"

	"github.com/google/syzkaller/pkg/log"
	"github.com/google/syzkaller/prog"
	"github.com/google/syzkaller/tools/syz-trace2syz/parser"
)

func ParseFile(filename string, target *prog.Target) ([]*prog.Prog, error) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("error reading file: %v", err)
	}
	return ParseData(data, target)
}

func ParseData(data []byte, target *prog.Target) ([]*prog.Prog, error) {
	tree, err := parser.ParseData(data)
	if err != nil {
		return nil, err
	}
	if tree == nil {
		return nil, nil
	}
	var progs []*prog.Prog
	parseTree(tree, tree.RootPid, target, &progs)
	return progs, nil
}

// parseTree groups system calls in the trace by process id.
// The tree preserves process hierarchy i.e. parent->[]child
func parseTree(tree *parser.TraceTree, pid int64, target *prog.Target, progs *[]*prog.Prog) {
	log.Logf(2, "parsing trace pid %v", pid)
	if p := genProg(tree.TraceMap[pid], target); p != nil {
		*progs = append(*progs, p)
	}
	for _, childPid := range tree.Ptree[pid] {
		if tree.TraceMap[childPid] != nil {
			parseTree(tree, childPid, target, progs)
		}
	}
}

// Context stores metadata related to a syzkaller program
type context struct {
	builder           *prog.Builder
	target            *prog.Target
	selectors         []callSelector
	returnCache       returnCache
	currentStraceCall *parser.Syscall
	currentSyzCall    *prog.Call
}

// genProg converts a trace to one of our programs.
func genProg(trace *parser.Trace, target *prog.Target) *prog.Prog {
	retCache := newRCache()
	ctx := &context{
		builder:     prog.MakeProgGen(target),
		target:      target,
		selectors:   newSelectors(target, retCache),
		returnCache: retCache,
	}
	for _, sCall := range trace.Calls {
		if sCall.Paused {
			// Probably a case where the call was killed by a signal like the following
			// 2179  wait4(2180,  <unfinished ...>
			// 2179  <... wait4 resumed> 0x7fff28981bf8, 0, NULL) = ? ERESTARTSYS
			// 2179  --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=2180, si_uid=0} ---
			continue
		}
		if shouldSkip(sCall) {
			log.Logf(2, "skipping call: %s", sCall.CallName)
			continue
		}
		ctx.currentStraceCall = sCall
		call := ctx.genCall()
		if call == nil {
			continue
		}
		if err := ctx.builder.Append(call); err != nil {
			log.Fatalf("%v", err)
		}
	}
	p, err := ctx.builder.Finalize()
	if err != nil {
		log.Fatalf("error validating program: %v", err)
	}
	return p
}

func (ctx *context) genCall() *prog.Call {
	log.Logf(3, "parsing call: %s", ctx.currentStraceCall.CallName)
	straceCall := ctx.currentStraceCall
	ctx.currentSyzCall = new(prog.Call)
	ctx.currentSyzCall.Meta = ctx.Select(straceCall)
	syzCall := ctx.currentSyzCall
	if ctx.currentSyzCall.Meta == nil {
		log.Logf(2, "skipping call: %s which has no matching description", ctx.currentStraceCall.CallName)
		return nil
	}
	syzCall.Ret = prog.MakeReturnArg(syzCall.Meta.Ret)

	for i := range syzCall.Meta.Args {
		var strArg parser.IrType
		if i < len(straceCall.Args) {
			strArg = straceCall.Args[i]
		}
		res := ctx.genArgs(syzCall.Meta.Args[i], strArg)
		syzCall.Args = append(syzCall.Args, res)
	}
	ctx.genResult(syzCall.Meta.Ret, straceCall.Ret)
	return syzCall
}

func (ctx *context) Select(syscall *parser.Syscall) *prog.Syscall {
	for _, selector := range ctx.selectors {
		if variant := selector.Select(syscall); variant != nil {
			return variant
		}
	}
	return ctx.target.SyscallMap[syscall.CallName]
}

func (ctx *context) genResult(syzType prog.Type, straceRet int64) {
	if straceRet <= 0 {
		return
	}
	straceExpr := parser.Constant(uint64(straceRet))
	switch syzType.(type) {
	case *prog.ResourceType:
		log.Logf(2, "call: %s returned a resource type with val: %s",
			ctx.currentStraceCall.CallName, straceExpr.String())
		ctx.returnCache.cache(syzType, straceExpr, ctx.currentSyzCall.Ret)
	}
}

func (ctx *context) genArgs(syzType prog.Type, traceArg parser.IrType) prog.Arg {
	if traceArg == nil {
		log.Logf(3, "parsing syzType: %s, traceArg is nil. generating default arg...", syzType.Name())
		return syzType.DefaultArg()
	}
	log.Logf(3, "parsing arg of syz type: %s, ir type: %#v", syzType.Name(), traceArg)

	if syzType.Dir() == prog.DirOut {
		switch syzType.(type) {
		case *prog.PtrType, *prog.StructType, *prog.ResourceType, *prog.BufferType:
			// Resource Types need special care. Pointers, Structs can have resource fields e.g. pipe, socketpair
			// Buffer may need special care in out direction
		default:
			return syzType.DefaultArg()
		}
	}

	switch a := syzType.(type) {
	case *prog.IntType, *prog.ConstType, *prog.FlagsType, *prog.CsumType:
		return ctx.genConst(a, traceArg)
	case *prog.LenType:
		return syzType.DefaultArg()
	case *prog.ProcType:
		return ctx.parseProc(a, traceArg)
	case *prog.ResourceType:
		return ctx.genResource(a, traceArg)
	case *prog.PtrType:
		return ctx.genPtr(a, traceArg)
	case *prog.BufferType:
		return ctx.genBuffer(a, traceArg)
	case *prog.StructType:
		return ctx.genStruct(a, traceArg)
	case *prog.ArrayType:
		return ctx.genArray(a, traceArg)
	case *prog.UnionType:
		return ctx.genUnionArg(a, traceArg)
	case *prog.VmaType:
		return ctx.genVma(a, traceArg)
	default:
		log.Fatalf("unsupported type: %#v", syzType)
	}
	return nil
}

func (ctx *context) genVma(syzType *prog.VmaType, _ parser.IrType) prog.Arg {
	npages := uint64(1)
	if syzType.RangeBegin != 0 || syzType.RangeEnd != 0 {
		npages = syzType.RangeEnd
	}
	return prog.MakeVmaPointerArg(syzType, ctx.builder.AllocateVMA(npages), npages)
}

func (ctx *context) genArray(syzType *prog.ArrayType, traceType parser.IrType) prog.Arg {
	var args []prog.Arg
	switch a := traceType.(type) {
	case *parser.GroupType:
		for i := 0; i < len(a.Elems); i++ {
			args = append(args, ctx.genArgs(syzType.Type, a.Elems[i]))
		}
	default:
		log.Fatalf("unsupported type for array: %#v", traceType)
	}
	return prog.MakeGroupArg(syzType, args)
}

func (ctx *context) genStruct(syzType *prog.StructType, traceType parser.IrType) prog.Arg {
	var args []prog.Arg
	switch a := traceType.(type) {
	case *parser.GroupType:
		j := 0
		if ret, recursed := ctx.recurseStructs(syzType, a); recursed {
			return ret
		}
		for i := range syzType.Fields {
			if prog.IsPad(syzType.Fields[i]) {
				args = append(args, syzType.Fields[i].DefaultArg())
				continue
			}
			// If the last n fields of a struct are zero or NULL, strace will occasionally omit those values
			// this creates a mismatch in the number of elements in the ir type and in
			// our descriptions. We generate default values for omitted fields
			if j >= len(a.Elems) {
				args = append(args, syzType.Fields[i].DefaultArg())
			} else {
				args = append(args, ctx.genArgs(syzType.Fields[i], a.Elems[j]))
			}
			j++
		}
	case *parser.BufferType:
		// We could have a case like the following:
		// ioctl(3, 35111, {ifr_name="\x6c\x6f", ifr_hwaddr=00:00:00:00:00:00}) = 0
		// if_hwaddr gets parsed as a BufferType but our syscall descriptions have it as a struct type
		return syzType.DefaultArg()
	default:
		log.Fatalf("unsupported type for struct: %#v", a)
	}
	return prog.MakeGroupArg(syzType, args)
}

// recurseStructs handles cases where syzType corresponds to struct descriptions like
// sockaddr_storage_in6 {
//        addr    sockaddr_in6
// } [size[SOCKADDR_STORAGE_SIZE], align_ptr]
// which need to be recursively generated. It returns true if we needed to recurse
// along with the generated argument and false otherwise.
func (ctx *context) recurseStructs(syzType *prog.StructType, traceType *parser.GroupType) (prog.Arg, bool) {
	// only consider structs with one non-padded field
	numFields := 0
	for _, field := range syzType.Fields {
		if prog.IsPad(field) {
			continue
		}
		numFields++
	}
	if numFields != 1 {
		return nil, false
	}
	// the strace group type needs to have more one field (a mismatch)
	if len(traceType.Elems) == 1 {
		return nil, false
	}
	// first field needs to be a struct
	switch t := syzType.Fields[0].(type) {
	case *prog.StructType:
		var args []prog.Arg
		// first element and traceType should have the same number of elements
		if len(t.Fields) != len(traceType.Elems) {
			return nil, false
		}
		args = append(args, ctx.genStruct(t, traceType))
		for _, field := range syzType.Fields[1:] {
			args = append(args, field.DefaultArg())
		}
		return prog.MakeGroupArg(syzType, args), true
	}
	return nil, false
}

func (ctx *context) genUnionArg(syzType *prog.UnionType, straceType parser.IrType) prog.Arg {
	if straceType == nil {
		log.Logf(1, "generating union arg. straceType is nil")
		return syzType.DefaultArg()
	}
	log.Logf(4, "generating union arg: %s %#v", syzType.TypeName, straceType)

	// Unions are super annoying because they sometimes need to be handled case by case
	// We might need to lookinto a matching algorithm to identify the union type that most closely
	// matches our strace type.

	switch syzType.TypeName {
	case "sockaddr_storage":
		return ctx.genSockaddrStorage(syzType, straceType)
	case "sockaddr_nl":
		return ctx.genSockaddrNetlink(syzType, straceType)
	case "ifr_ifru":
		return ctx.genIfrIfru(syzType, straceType)
	}
	return prog.MakeUnionArg(syzType, ctx.genArgs(syzType.Fields[0], straceType))
}

func (ctx *context) genBuffer(syzType *prog.BufferType, traceType parser.IrType) prog.Arg {
	if syzType.Dir() == prog.DirOut {
		if !syzType.Varlen() {
			return prog.MakeOutDataArg(syzType, syzType.Size())
		}
		switch a := traceType.(type) {
		case *parser.BufferType:
			return prog.MakeOutDataArg(syzType, uint64(len(a.Val)))
		default:
			switch syzType.Kind {
			case prog.BufferBlobRand:
				size := rand.Intn(256)
				return prog.MakeOutDataArg(syzType, uint64(size))

			case prog.BufferBlobRange:
				max := rand.Intn(int(syzType.RangeEnd) - int(syzType.RangeBegin) + 1)
				size := max + int(syzType.RangeBegin)
				return prog.MakeOutDataArg(syzType, uint64(size))
			default:
				log.Fatalf("unexpected buffer type kind: %v. call %v arg %#v", syzType.Kind, ctx.currentSyzCall, traceType)
			}
		}
	}
	var bufVal []byte
	switch a := traceType.(type) {
	case *parser.BufferType:
		bufVal = []byte(a.Val)
	case parser.Constant:
		val := a.Val()
		bArr := make([]byte, 8)
		binary.LittleEndian.PutUint64(bArr, val)
		bufVal = bArr
	default:
		log.Fatalf("unsupported type for buffer: %#v", traceType)
	}
	// strace always drops the null byte for buffer types but we only need to add it back for filenames and strings
	switch syzType.Kind {
	case prog.BufferFilename, prog.BufferString:
		bufVal = append(bufVal, '\x00')
	}
	if !syzType.Varlen() {
		size := syzType.Size()
		for uint64(len(bufVal)) < size {
			bufVal = append(bufVal, 0)
		}
		bufVal = bufVal[:size]
	}
	return prog.MakeDataArg(syzType, bufVal)
}

func (ctx *context) genPtr(syzType *prog.PtrType, traceType parser.IrType) prog.Arg {
	switch a := traceType.(type) {
	case parser.Constant:
		if a.Val() == 0 {
			return prog.MakeSpecialPointerArg(syzType, 0)
		}
		// Likely have a type of the form bind(3, 0xfffffffff, [3]);
		res := syzType.Type.DefaultArg()
		return ctx.addr(syzType, res.Size(), res)
	default:
		res := ctx.genArgs(syzType.Type, a)
		return ctx.addr(syzType, res.Size(), res)
	}
}

func (ctx *context) genConst(syzType prog.Type, traceType parser.IrType) prog.Arg {
	switch a := traceType.(type) {
	case parser.Constant:
		return prog.MakeConstArg(syzType, a.Val())
	case *parser.GroupType:
		// Sometimes strace represents a pointer to int as [0] which gets parsed
		// as Array([0], len=1). A good example is ioctl(3, FIONBIO, [1]). We may also have an union int type that
		// is a represented as a struct in strace e.g.
		// sigev_value={sival_int=-2123636944, sival_ptr=0x7ffd816bdf30}
		// For now we choose the first option
		if len(a.Elems) == 0 {
			log.Logf(2, "parsing const type, got array type with len 0")
			return syzType.DefaultArg()
		}
		return ctx.genConst(syzType, a.Elems[0])
	case *parser.BufferType:
		// strace decodes some arguments as hex strings because those values are network ordered
		// e.g. sin_port or sin_addr fields of sockaddr_in.
		// network order is big endian byte order so if the len of byte array is 1, 2, 4, or 8 then
		// it is a good chance that we are decoding one of those fields. If it isn't, then most likely
		// we have an error i.e. a sockaddr_un struct passed to a connect call with an inet file descriptor
		var val uint64
		toUint64 := binary.LittleEndian.Uint64
		toUint32 := binary.LittleEndian.Uint32
		toUint16 := binary.LittleEndian.Uint16
		if syzType.Format() == prog.FormatBigEndian {
			toUint64 = binary.BigEndian.Uint64
			toUint32 = binary.BigEndian.Uint32
			toUint16 = binary.BigEndian.Uint16
		}
		switch len(a.Val) {
		case 8:
			val = toUint64([]byte(a.Val))
		case 4:
			val = uint64(toUint32([]byte(a.Val)))
		case 2:
			val = uint64(toUint16([]byte(a.Val)))
		case 1:
			val = uint64(a.Val[0])
		default:
			return syzType.DefaultArg()
		}
		return prog.MakeConstArg(syzType, val)
	default:
		log.Fatalf("unsupported type for const: %#v", traceType)
	}
	return nil
}

func (ctx *context) genResource(syzType *prog.ResourceType, traceType parser.IrType) prog.Arg {
	if syzType.Dir() == prog.DirOut {
		log.Logf(2, "resource returned by call argument: %s", traceType.String())
		res := prog.MakeResultArg(syzType, nil, syzType.Default())
		ctx.returnCache.cache(syzType, traceType, res)
		return res
	}
	switch a := traceType.(type) {
	case parser.Constant:
		val := a.Val()
		if arg := ctx.returnCache.get(syzType, traceType); arg != nil {
			res := prog.MakeResultArg(syzType, arg.(*prog.ResultArg), syzType.Default())
			return res
		}
		res := prog.MakeResultArg(syzType, nil, val)
		return res
	case *parser.GroupType:
		if len(a.Elems) == 1 {
			// For example: 5028  ioctl(3, SIOCSPGRP, [0])          = 0
			// last argument is a pointer to a resource. Strace will output a pointer to
			// a number x as [x].
			res := prog.MakeResultArg(syzType, nil, syzType.Default())
			ctx.returnCache.cache(syzType, a.Elems[0], res)
			return res
		}
		log.Fatalf("generating resource type from GroupType with %d elements", len(a.Elems))
	default:
		log.Fatalf("unsupported type for resource: %#v", traceType)
	}
	return nil
}

func (ctx *context) parseProc(syzType *prog.ProcType, traceType parser.IrType) prog.Arg {
	switch a := traceType.(type) {
	case parser.Constant:
		val := a.Val()
		if val >= syzType.ValuesPerProc {
			return prog.MakeConstArg(syzType, syzType.ValuesPerProc-1)
		}
		return prog.MakeConstArg(syzType, val)
	case *parser.BufferType:
		// Again probably an error case
		// Something like the following will trigger this
		// bind(3, {sa_family=AF_INET, sa_data="\xac"}, 3) = -1 EINVAL(Invalid argument)
		return syzType.DefaultArg()
	default:
		log.Fatalf("unsupported type for proc: %#v", traceType)
	}
	return nil
}

func (ctx *context) addr(syzType prog.Type, size uint64, data prog.Arg) prog.Arg {
	return prog.MakePointerArg(syzType, ctx.builder.Allocate(size), data)
}

func shouldSkip(c *parser.Syscall) bool {
	switch c.CallName {
	case "write":
		// We skip all writes to stdout and stderr because they can corrupt our crash summary.
		// Also there will be nothing on stdin, so any reads will hang.
		switch a := c.Args[0].(type) {
		case parser.Constant:
			if a.Val() <= 2 {
				return true
			}
		}
	}
	return unsupportedCalls[c.CallName]
}