aboutsummaryrefslogtreecommitdiffstats
path: root/prog/collide.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2021-09-23 16:15:41 +0000
committerAleksandr Nogikh <wp32pw@gmail.com>2021-12-10 12:30:07 +0100
commitfd8caa5462e64f37cb9eebd75ffca1737dde447d (patch)
treebfa900ebf41099b21476e72acdf063ee630178c9 /prog/collide.go
parent4d4ce9bc2a12073dcc8b917f9fc2a4ecba26c4c5 (diff)
all: replace collide mode by `async` call property
Replace the currently existing straightforward approach to race triggering (that was almost entirely implemented inside syz-executor) with a more flexible one. The `async` call property instructs syz-executor not to block until the call has completed execution and proceed immediately to the next call. The decision on what calls to mark with `async` is made by syz-fuzzer. Ultimately this should let us implement more intelligent race provoking strategies as well as make more fine-grained reproducers.
Diffstat (limited to 'prog/collide.go')
-rw-r--r--prog/collide.go57
1 files changed, 57 insertions, 0 deletions
diff --git a/prog/collide.go b/prog/collide.go
new file mode 100644
index 000000000..cd059c60f
--- /dev/null
+++ b/prog/collide.go
@@ -0,0 +1,57 @@
+// Copyright 2021 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.
+
+// Contains prog transformations that intend to trigger more races.
+
+package prog
+
+import "math/rand"
+
+// The executor has no more than 32 threads that are used both for async calls and for calls
+// that timed out. If we just ignore that limit, we could end up generating programs that
+// would force the executor to fail and thus stall the fuzzing process.
+// As an educated guess, let's use no more than 24 async calls to let executor handle everything.
+const maxAsyncPerProg = 24
+
+// Ensures that if an async call produces a resource, then
+// it is distanced from a call consuming the resource at least
+// by one non-async call.
+// This does not give 100% guarantee that the async call finishes
+// by that time, but hopefully this is enough for most cases.
+func AssignRandomAsync(origProg *Prog, rand *rand.Rand) *Prog {
+ var unassigned map[*ResultArg]bool
+ leftAsync := maxAsyncPerProg
+ prog := origProg.Clone()
+ for i := len(prog.Calls) - 1; i >= 0 && leftAsync > 0; i-- {
+ call := prog.Calls[i]
+ producesUnassigned := false
+ consumes := make(map[*ResultArg]bool)
+ ForeachArg(call, func(arg Arg, ctx *ArgCtx) {
+ res, ok := arg.(*ResultArg)
+ if !ok {
+ return
+ }
+ if res.Dir() != DirIn && unassigned[res] {
+ // If this call is made async, at least one of the resources
+ // will be empty when it's needed.
+ producesUnassigned = true
+ }
+ if res.Dir() != DirOut {
+ consumes[res.Res] = true
+ }
+ })
+ // Make async with a 66% chance (but never the last call).
+ if !producesUnassigned && i+1 != len(prog.Calls) && rand.Intn(3) != 0 {
+ call.Props.Async = true
+ for res := range consumes {
+ unassigned[res] = true
+ }
+ leftAsync--
+ } else {
+ call.Props.Async = false
+ unassigned = consumes
+ }
+ }
+
+ return prog
+}