github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/subsystem.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 main
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"reflect"
    10  	"sort"
    11  	"time"
    12  
    13  	"github.com/google/syzkaller/pkg/debugtracer"
    14  	"github.com/google/syzkaller/pkg/subsystem"
    15  	db "google.golang.org/appengine/v2/datastore"
    16  	"google.golang.org/appengine/v2/log"
    17  )
    18  
    19  // reassignBugSubsystems is expected to be periodically called to refresh old automatic
    20  // subsystem assignments.
    21  func reassignBugSubsystems(c context.Context, ns string, count int) error {
    22  	service := getNsConfig(c, ns).Subsystems.Service
    23  	if service == nil {
    24  		return nil
    25  	}
    26  	bugs, keys, err := bugsToUpdateSubsystems(c, ns, count)
    27  	if err != nil {
    28  		return err
    29  	}
    30  	log.Infof(c, "updating subsystems for %d bugs in %#v", len(keys), ns)
    31  	rev := getNsConfig(c, ns).Subsystems.Revision
    32  	for i, bugKey := range keys {
    33  		if bugs[i].hasUserSubsystems() {
    34  			// It might be that the user-set subsystem no longer exists.
    35  			// For now let's just log an error in this case.
    36  			checkOutdatedSubsystems(c, service, bugs[i])
    37  			// If we don't set the latst revision, we'll have to update this
    38  			// bug over and over again.
    39  			err = updateBugSubsystems(c, bugKey, nil, updateRevision(rev))
    40  		} else {
    41  			var list []*subsystem.Subsystem
    42  			list, err = inferSubsystems(c, bugs[i], bugKey, &debugtracer.NullTracer{})
    43  			if err != nil {
    44  				return fmt.Errorf("failed to infer subsystems: %w", err)
    45  			}
    46  			err = updateBugSubsystems(c, bugKey, list, autoInference(rev))
    47  		}
    48  		if err != nil {
    49  			return fmt.Errorf("failed to save subsystems: %w", err)
    50  		}
    51  	}
    52  	return nil
    53  }
    54  
    55  func bugsToUpdateSubsystems(c context.Context, ns string, count int) ([]*Bug, []*db.Key, error) {
    56  	now := timeNow(c)
    57  	rev := getSubsystemRevision(c, ns)
    58  	queries := []*db.Query{
    59  		// If revision has been updated, first update open bugs.
    60  		db.NewQuery("Bug").
    61  			Filter("Namespace=", ns).
    62  			Filter("Status=", BugStatusOpen).
    63  			Filter("SubsystemsRev<", rev),
    64  		// The next priority is the regular update of open bugs.
    65  		db.NewQuery("Bug").
    66  			Filter("Namespace=", ns).
    67  			Filter("Status=", BugStatusOpen).
    68  			Filter("SubsystemsTime<", now.Add(-openBugsUpdateTime)),
    69  		// Then let's consider the update of closed bugs.
    70  		db.NewQuery("Bug").
    71  			Filter("Namespace=", ns).
    72  			Filter("Status=", BugStatusFixed).
    73  			Filter("SubsystemsRev<", rev),
    74  		// And, at the end, everything else.
    75  		db.NewQuery("Bug").
    76  			Filter("Namespace=", ns).
    77  			Filter("SubsystemsRev<", rev),
    78  	}
    79  	var bugs []*Bug
    80  	var keys []*db.Key
    81  	for i, query := range queries {
    82  		if count <= 0 {
    83  			break
    84  		}
    85  		var tmpBugs []*Bug
    86  		tmpKeys, err := query.Limit(count).GetAll(c, &tmpBugs)
    87  		if err != nil {
    88  			return nil, nil, fmt.Errorf("query %d failed: %w", i, err)
    89  		}
    90  		bugs = append(bugs, tmpBugs...)
    91  		keys = append(keys, tmpKeys...)
    92  		count -= len(tmpKeys)
    93  	}
    94  	return bugs, keys, nil
    95  }
    96  
    97  func checkOutdatedSubsystems(c context.Context, service *subsystem.Service, bug *Bug) {
    98  	for _, item := range bug.LabelValues(SubsystemLabel) {
    99  		if service.ByName(item.Value) == nil {
   100  			log.Errorf(c, "ns=%s bug=%s subsystem %s no longer exists", bug.Namespace, bug.Title, item.Value)
   101  		}
   102  	}
   103  }
   104  
   105  type (
   106  	autoInference  int
   107  	updateRevision int
   108  )
   109  
   110  func updateBugSubsystems(c context.Context, bugKey *db.Key,
   111  	list []*subsystem.Subsystem, info interface{}) error {
   112  	now := timeNow(c)
   113  	return updateSingleBug(c, bugKey, func(bug *Bug) error {
   114  		switch v := info.(type) {
   115  		case autoInference:
   116  			logSubsystemChange(c, bug, list)
   117  			bug.SetAutoSubsystems(c, list, now, int(v))
   118  		case updateRevision:
   119  			bug.SubsystemsRev = int(v)
   120  			bug.SubsystemsTime = now
   121  		}
   122  		return nil
   123  	})
   124  }
   125  
   126  func logSubsystemChange(c context.Context, bug *Bug, new []*subsystem.Subsystem) {
   127  	var oldNames, newNames []string
   128  	for _, item := range bug.LabelValues(SubsystemLabel) {
   129  		oldNames = append(oldNames, item.Value)
   130  	}
   131  	for _, item := range new {
   132  		newNames = append(newNames, item.Name)
   133  	}
   134  	sort.Strings(oldNames)
   135  	sort.Strings(newNames)
   136  	if !reflect.DeepEqual(oldNames, newNames) {
   137  		log.Infof(c, "bug %s: subsystems set from %v to %v",
   138  			bug.keyHash(c), oldNames, newNames)
   139  	}
   140  }
   141  
   142  const (
   143  	// We load the top crashesForInference crashes to determine the bug subsystem(s).
   144  	crashesForInference = 7
   145  	// How often we update open bugs.
   146  	openBugsUpdateTime = time.Hour * 24 * 30
   147  )
   148  
   149  // inferSubsystems determines the best yet possible estimate of the bug's subsystems.
   150  func inferSubsystems(c context.Context, bug *Bug, bugKey *db.Key,
   151  	tracer debugtracer.DebugTracer) ([]*subsystem.Subsystem, error) {
   152  	service := getSubsystemService(c, bug.Namespace)
   153  	if service == nil {
   154  		// There's nothing we can do.
   155  		return nil, nil
   156  	}
   157  	dbCrashes, dbCrashKeys, err := queryCrashesForBug(c, bugKey, crashesForInference)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	crashes := []*subsystem.Crash{}
   162  	for i, dbCrash := range dbCrashes {
   163  		crash := &subsystem.Crash{}
   164  		if len(dbCrash.ReportElements.GuiltyFiles) > 0 {
   165  			// For now we anyway only store one.
   166  			crash.GuiltyPath = dbCrash.ReportElements.GuiltyFiles[0]
   167  		}
   168  		if dbCrash.ReproSyz != 0 {
   169  			crash.SyzRepro, _, err = getText(c, textReproSyz, dbCrash.ReproSyz)
   170  			if err != nil {
   171  				return nil, fmt.Errorf("failed to load syz repro for %s: %w",
   172  					dbCrashKeys[i], err)
   173  			}
   174  		}
   175  		crashes = append(crashes, crash)
   176  	}
   177  	return service.TracedExtract(crashes, tracer), nil
   178  }
   179  
   180  // subsystemMaintainers queries the list of emails to send the bug to.
   181  func subsystemMaintainers(c context.Context, ns, subsystemName string) []string {
   182  	service := getNsConfig(c, ns).Subsystems.Service
   183  	if service == nil {
   184  		return nil
   185  	}
   186  	item := service.ByName(subsystemName)
   187  	if item == nil {
   188  		return nil
   189  	}
   190  	return item.Emails()
   191  }
   192  
   193  func getSubsystemService(c context.Context, ns string) *subsystem.Service {
   194  	return getNsConfig(c, ns).Subsystems.Service
   195  }
   196  
   197  func getSubsystemRevision(c context.Context, ns string) int {
   198  	return getNsConfig(c, ns).Subsystems.Revision
   199  }
   200  
   201  func subsystemListURL(c context.Context, ns string) string {
   202  	if getNsConfig(c, ns).Subsystems.Service == nil {
   203  		return ""
   204  	}
   205  	return fmt.Sprintf("%v/%v/subsystems?all=true", appURL(c), ns)
   206  }