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  }