github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/subsystem/linux/parents.go (about)

     1  // Copyright 2023 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  package linux
     5  
     6  import "github.com/google/syzkaller/pkg/subsystem"
     7  
     8  // parentTransformations applies all subsystem list transformations that have been implemented.
     9  func parentTransformations(matrix *CoincidenceMatrix,
    10  	list []*subsystem.Subsystem) ([]*subsystem.Subsystem, error) {
    11  	list = dropSmallSubsystems(matrix, list)
    12  	list = dropDuplicateSubsystems(matrix, list)
    13  	err := setParents(matrix, list)
    14  	if err != nil {
    15  		return nil, err
    16  	}
    17  	return list, nil
    18  }
    19  
    20  // setParents attempts to determine the parent-child relations among the extracted subsystems.
    21  // We assume A is a child of B if:
    22  // 1) B covers more paths than A.
    23  // 2) Most of the paths that relate to A also relate to B.
    24  func setParents(matrix *CoincidenceMatrix, list []*subsystem.Subsystem) error {
    25  	// Some subsystems might have already been dropeed.
    26  	inInput := map[*subsystem.Subsystem]bool{}
    27  	for _, item := range list {
    28  		inInput[item] = true
    29  	}
    30  	matrix.NonEmptyPairs(func(a, b *subsystem.Subsystem, count int) {
    31  		if !inInput[a] || !inInput[b] {
    32  			return
    33  		}
    34  		// Demand that >= 50% paths are related.
    35  		if 2*count/matrix.Count(a) >= 1 && matrix.Count(a) < matrix.Count(b) {
    36  			a.Parents = append(a.Parents, b)
    37  			a.ReachableParents() // make sure we haven't created a loop
    38  		}
    39  	})
    40  	transitiveReduction(list)
    41  	return nil
    42  }
    43  
    44  // dropSmallSubsystems removes subsystems for which we have found only a few matches in the filesystem tree.
    45  func dropSmallSubsystems(matrix *CoincidenceMatrix, list []*subsystem.Subsystem) []*subsystem.Subsystem {
    46  	const cutOffCount = 2
    47  
    48  	newList := []*subsystem.Subsystem{}
    49  	for _, item := range list {
    50  		if matrix.Count(item) > cutOffCount || len(item.Syscalls) > 0 {
    51  			newList = append(newList, item)
    52  		}
    53  	}
    54  	return newList
    55  }
    56  
    57  // dropDuplicateSubsystems makes sure there are no duplicate subsystems.
    58  // First, if subsystems A and B 100% overlap, we prefer the one that's alphabetically first.
    59  // Second, if subsystem A is fully enclosed in subsystem B and constitutes more than 75% of B,
    60  // we drop A, since it brings little value.
    61  func dropDuplicateSubsystems(matrix *CoincidenceMatrix, list []*subsystem.Subsystem) []*subsystem.Subsystem {
    62  	drop := map[*subsystem.Subsystem]struct{}{}
    63  	firstIsBetter := func(first, second *subsystem.Subsystem) bool {
    64  		firstEmail, secondEmail := "", ""
    65  		if len(first.Lists) > 0 {
    66  			firstEmail = first.Lists[0]
    67  		}
    68  		if len(second.Lists) > 0 {
    69  			secondEmail = second.Lists[0]
    70  		}
    71  		return firstEmail < secondEmail
    72  	}
    73  	matrix.NonEmptyPairs(func(a, b *subsystem.Subsystem, count int) {
    74  		// Only consider cases when A is fully enclosed in B, i.e. M[A][B] == M[A][A].
    75  		if count != matrix.Count(a) {
    76  			return
    77  		}
    78  		// If A and B 100% coincide, eliminate A and keep B if A > B.
    79  		if count == matrix.Count(b) {
    80  			if firstIsBetter(a, b) {
    81  				return
    82  			}
    83  			drop[a] = struct{}{}
    84  			return
    85  		}
    86  		// If A constitutes > 75% of B, drop A.
    87  		if 4*matrix.Count(a)/matrix.Count(b) >= 3 {
    88  			drop[a] = struct{}{}
    89  		}
    90  	})
    91  	newList := []*subsystem.Subsystem{}
    92  	for _, item := range list {
    93  		if _, exists := drop[item]; !exists {
    94  			newList = append(newList, item)
    95  		}
    96  	}
    97  	return newList
    98  }
    99  
   100  // The algorithm runs in O(E * (E + V)).
   101  // We expect that E is quite low here, so it should be fine.
   102  func transitiveReduction(list []*subsystem.Subsystem) {
   103  	for _, s := range list {
   104  		removeParents := map[*subsystem.Subsystem]bool{}
   105  		for _, p := range s.Parents {
   106  			for otherP := range p.ReachableParents() {
   107  				removeParents[otherP] = true
   108  			}
   109  		}
   110  		newParents := []*subsystem.Subsystem{}
   111  		for _, p := range s.Parents {
   112  			if !removeParents[p] {
   113  				newParents = append(newParents, p)
   114  			}
   115  		}
   116  		s.Parents = newParents
   117  	}
   118  }