github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/prog/collide.go (about) 1 // Copyright 2021 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 // Contains prog transformations that intend to trigger more races. 5 6 package prog 7 8 import ( 9 "fmt" 10 "math/rand" 11 ) 12 13 // The executor has no more than 32 threads that are used both for async calls and for calls 14 // that timed out. If we just ignore that limit, we could end up generating programs that 15 // would force the executor to fail and thus stall the fuzzing process. 16 // As an educated guess, let's use no more than 24 async calls to let executor handle everything. 17 const maxAsyncPerProg = 24 18 19 // Ensures that if an async call produces a resource, then 20 // it is distanced from a call consuming the resource at least 21 // by one non-async call. 22 // This does not give 100% guarantee that the async call finishes 23 // by that time, but hopefully this is enough for most cases. 24 func AssignRandomAsync(origProg *Prog, rand *rand.Rand) *Prog { 25 var unassigned map[*ResultArg]bool 26 leftAsync := maxAsyncPerProg 27 prog := origProg.Clone() 28 for i := len(prog.Calls) - 1; i >= 0 && leftAsync > 0; i-- { 29 call := prog.Calls[i] 30 producesUnassigned := false 31 consumes := make(map[*ResultArg]bool) 32 ForeachArg(call, func(arg Arg, ctx *ArgCtx) { 33 res, ok := arg.(*ResultArg) 34 if !ok { 35 return 36 } 37 if res.Dir() != DirIn && unassigned[res] { 38 // If this call is made async, at least one of the resources 39 // will be empty when it's needed. 40 producesUnassigned = true 41 } 42 if res.Dir() != DirOut { 43 consumes[res.Res] = true 44 } 45 }) 46 // Make async with a 66% chance (but never the last call). 47 if !producesUnassigned && i+1 != len(prog.Calls) && rand.Intn(3) != 0 { 48 call.Props.Async = true 49 for res := range consumes { 50 unassigned[res] = true 51 } 52 leftAsync-- 53 } else { 54 call.Props.Async = false 55 unassigned = consumes 56 } 57 } 58 59 return prog 60 } 61 62 var rerunSteps = []int{32, 64} 63 64 func AssignRandomRerun(prog *Prog, rand *rand.Rand) { 65 for i := 0; i+1 < len(prog.Calls); i++ { 66 if !prog.Calls[i].Props.Async || rand.Intn(4) != 0 { 67 continue 68 } 69 // We assign rerun to consecutive pairs of calls, where the first call is async. 70 // TODO: consider assigning rerun also to non-collided progs. 71 rerun := rerunSteps[rand.Intn(len(rerunSteps))] 72 prog.Calls[i].Props.Rerun = rerun 73 prog.Calls[i+1].Props.Rerun = rerun 74 i++ 75 } 76 } 77 78 // We append prog to itself, but let the second part only reference resource from the first one. 79 // Then we execute all the duplicated calls simultaneously. 80 // This somehow resembles the way the previous collide mode was implemented - a program was executed 81 // normally and then one more time again, while keeping resource values from the first execution and 82 // not waiting until every other call finishes. 83 func DoubleExecCollide(origProg *Prog, rand *rand.Rand) (*Prog, error) { 84 if len(origProg.Calls)*2 > MaxCalls { 85 return nil, fmt.Errorf("the prog is too big for the DoubleExecCollide transformation") 86 } 87 prog := origProg.Clone() 88 dupCalls := cloneCalls(prog.Calls, nil) 89 leftAsync := maxAsyncPerProg 90 for _, c := range dupCalls { 91 if leftAsync == 0 { 92 break 93 } 94 c.Props.Async = true 95 leftAsync-- 96 } 97 prog.Calls = append(prog.Calls, dupCalls...) 98 return prog, nil 99 } 100 101 // DupCallCollide duplicates some of the calls in the program and marks them async. 102 // This should hopefully trigger races in a more granular way than DoubleExecCollide. 103 func DupCallCollide(origProg *Prog, rand *rand.Rand) (*Prog, error) { 104 if len(origProg.Calls) < 2 { 105 // For 1-call programs the behavior is similar to DoubleExecCollide. 106 return nil, fmt.Errorf("the prog is too small for the transformation") 107 } 108 // By default let's duplicate 1/3 calls in the original program. 109 insert := len(origProg.Calls) / 3 110 if insert == 0 { 111 // .. but always at least one. 112 insert = 1 113 } 114 if insert > maxAsyncPerProg { 115 insert = maxAsyncPerProg 116 } 117 if insert+len(origProg.Calls) > MaxCalls { 118 insert = MaxCalls - len(origProg.Calls) 119 } 120 if insert == 0 { 121 return nil, fmt.Errorf("no calls could be duplicated") 122 } 123 duplicate := map[int]bool{} 124 for _, pos := range rand.Perm(len(origProg.Calls))[:insert] { 125 duplicate[pos] = true 126 } 127 prog := origProg.Clone() 128 var retCalls []*Call 129 for i, c := range prog.Calls { 130 if duplicate[i] { 131 dupCall := cloneCall(c, nil) 132 dupCall.Props.Async = true 133 retCalls = append(retCalls, dupCall) 134 } 135 retCalls = append(retCalls, c) 136 } 137 prog.Calls = retCalls 138 return prog, nil 139 }