github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/app_test.go (about)

     1  // Copyright 2017 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  	"errors"
     8  	"fmt"
     9  	"net/http"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  	"github.com/google/syzkaller/dashboard/dashapi"
    18  	"github.com/google/syzkaller/pkg/auth"
    19  	"github.com/google/syzkaller/pkg/subsystem"
    20  	_ "github.com/google/syzkaller/pkg/subsystem/lists"
    21  	"github.com/google/syzkaller/sys/targets"
    22  	"google.golang.org/appengine/v2/user"
    23  )
    24  
    25  func init() {
    26  	// This is ugly but without this go test hangs with:
    27  	// panic: Metadata fetch failed for 'instance/attributes/gae_backend_version':
    28  	//	Get http://metadata/computeMetadata/v1/instance/attributes/gae_backend_version:
    29  	//	dial tcp: lookup metadata on 127.0.0.1:53: no such host
    30  	// It's unclear what's the proper fix for this.
    31  	os.Setenv("GAE_MODULE_VERSION", "1")
    32  	os.Setenv("GAE_MINOR_VERSION", "1")
    33  
    34  	trustedAuthDomain = "" // Devappserver environment value is "", prod value is "gmail.com".
    35  	obsoleteWhatWontBeFixBisected = true
    36  	notifyAboutUnsuccessfulBisections = true
    37  	ensureConfigImmutability = true
    38  	initMocks()
    39  	installConfig(testConfig)
    40  }
    41  
    42  // Config used in tests.
    43  var testConfig = &GlobalConfig{
    44  	AccessLevel: AccessPublic,
    45  	ACL: []*ACLItem{
    46  		{
    47  			Domain: "syzkaller.com",
    48  			Access: AccessUser,
    49  		},
    50  		{
    51  			Email:  makeUser(AuthorizedAccessPublic).Email,
    52  			Access: AccessPublic,
    53  		},
    54  	},
    55  	Clients: map[string]string{
    56  		"reporting": "reportingkeyreportingkeyreportingkey",
    57  	},
    58  	EmailBlocklist: []string{
    59  		"\"Bar\" <Blocked@Domain.com>",
    60  	},
    61  	Obsoleting: ObsoletingConfig{
    62  		MinPeriod:         80 * 24 * time.Hour,
    63  		MaxPeriod:         100 * 24 * time.Hour,
    64  		NonFinalMinPeriod: 40 * 24 * time.Hour,
    65  		NonFinalMaxPeriod: 60 * 24 * time.Hour,
    66  		ReproRetestPeriod: 100 * 24 * time.Hour,
    67  	},
    68  	DiscussionEmails: []DiscussionEmailConfig{
    69  		{"lore@email.com", dashapi.DiscussionLore},
    70  	},
    71  	DefaultNamespace: "test1",
    72  	Namespaces: map[string]*Config{
    73  		"test1": {
    74  			AccessLevel:           AccessAdmin,
    75  			Key:                   "test1keytest1keytest1key",
    76  			FixBisectionAutoClose: true,
    77  			SimilarityDomain:      testDomain,
    78  			Clients: map[string]string{
    79  				client1: password1,
    80  				"oauth": auth.OauthMagic + "111111122222222",
    81  			},
    82  			Repos: []KernelRepo{
    83  				{
    84  					URL:    "git://syzkaller.org",
    85  					Branch: "branch10",
    86  					Alias:  "repo10alias",
    87  					CC: CCConfig{
    88  						Maintainers: []string{"maintainers@repo10.org", "bugs@repo10.org"},
    89  					},
    90  				},
    91  				{
    92  					URL:    "git://github.com/google/syzkaller",
    93  					Branch: "master",
    94  					Alias:  "repo10alias1",
    95  					CC: CCConfig{
    96  						Maintainers: []string{"maintainers@repo10.org", "bugs@repo10.org"},
    97  					},
    98  				},
    99  				{
   100  					URL:    "git://github.com/google/syzkaller",
   101  					Branch: "old_master",
   102  					Alias:  "repo10alias2",
   103  					NoPoll: true,
   104  				},
   105  			},
   106  			Managers: map[string]ConfigManager{
   107  				"special-obsoleting": {
   108  					ObsoletingMinPeriod: 10 * 24 * time.Hour,
   109  					ObsoletingMaxPeriod: 20 * 24 * time.Hour,
   110  				},
   111  			},
   112  			Reporting: []Reporting{
   113  				{
   114  					Name:       "reporting1",
   115  					DailyLimit: 5,
   116  					Embargo:    14 * 24 * time.Hour,
   117  					Filter:     skipWithRepro,
   118  					Config: &TestConfig{
   119  						Index: 1,
   120  					},
   121  				},
   122  				{
   123  					Name:       "reporting2",
   124  					DailyLimit: 5,
   125  					Config: &TestConfig{
   126  						Index: 2,
   127  					},
   128  				},
   129  			},
   130  			Subsystems: SubsystemsConfig{
   131  				Service: subsystem.MustMakeService(testSubsystems, 0),
   132  			},
   133  		},
   134  		"test2": {
   135  			AccessLevel:      AccessAdmin,
   136  			Key:              "test2keytest2keytest2key",
   137  			SimilarityDomain: testDomain,
   138  			Clients: map[string]string{
   139  				client2: password2,
   140  			},
   141  			Repos: []KernelRepo{
   142  				{
   143  					URL:    "git://syzkaller.org",
   144  					Branch: "branch10",
   145  					Alias:  "repo10alias",
   146  					CC: CCConfig{
   147  						Always:           []string{"always@cc.me"},
   148  						Maintainers:      []string{"maintainers@repo10.org", "bugs@repo10.org"},
   149  						BuildMaintainers: []string{"build-maintainers@repo10.org"},
   150  					},
   151  				},
   152  				{
   153  					URL:    "git://syzkaller.org",
   154  					Branch: "branch20",
   155  					Alias:  "repo20",
   156  					CC: CCConfig{
   157  						Maintainers: []string{"maintainers@repo20.org", "bugs@repo20.org"},
   158  					},
   159  				},
   160  			},
   161  			Managers: map[string]ConfigManager{
   162  				noFixBisectionManager: {
   163  					FixBisectionDisabled: true,
   164  				},
   165  				specialCCManager: {
   166  					CC: CCConfig{
   167  						Always:           []string{"always@manager.org"},
   168  						Maintainers:      []string{"maintainers@manager.org"},
   169  						BuildMaintainers: []string{"build-maintainers@manager.org"},
   170  					},
   171  				},
   172  			},
   173  			Reporting: []Reporting{
   174  				{
   175  					Name:       "reporting1",
   176  					DailyLimit: 5,
   177  					Embargo:    14 * 24 * time.Hour,
   178  					Filter:     skipWithRepro,
   179  					Config: &EmailConfig{
   180  						Email: "test@syzkaller.com",
   181  					},
   182  				},
   183  				{
   184  					Name:       "reporting2",
   185  					DailyLimit: 3,
   186  					Filter:     skipWithRepro2,
   187  					Config: &EmailConfig{
   188  						Email:              "bugs@syzkaller.com",
   189  						DefaultMaintainers: []string{"default@maintainers.com"},
   190  						SubjectPrefix:      "[syzbot]",
   191  						MailMaintainers:    true,
   192  					},
   193  				},
   194  				{
   195  					Name:       "reporting3",
   196  					DailyLimit: 3,
   197  					Config: &EmailConfig{
   198  						Email:              "bugs2@syzkaller.com",
   199  						DefaultMaintainers: []string{"default2@maintainers.com"},
   200  						MailMaintainers:    true,
   201  					},
   202  				},
   203  			},
   204  		},
   205  		// Namespaces for access level testing.
   206  		"access-admin": {
   207  			AccessLevel: AccessAdmin,
   208  			Key:         "adminkeyadminkeyadminkey",
   209  			Clients: map[string]string{
   210  				clientAdmin: keyAdmin,
   211  			},
   212  			Repos: []KernelRepo{
   213  				{
   214  					URL:    "git://syzkaller.org/access-admin.git",
   215  					Branch: "access-admin",
   216  					Alias:  "access-admin",
   217  				},
   218  			},
   219  			Reporting: []Reporting{
   220  				{
   221  					Name:       "access-admin-reporting1",
   222  					DailyLimit: 1000,
   223  					Config:     &TestConfig{Index: 1},
   224  				},
   225  				{
   226  					Name:       "access-admin-reporting2",
   227  					DailyLimit: 1000,
   228  					Config:     &TestConfig{Index: 2},
   229  				},
   230  			},
   231  		},
   232  		"access-user": {
   233  			AccessLevel: AccessUser,
   234  			Key:         "userkeyuserkeyuserkey",
   235  			Clients: map[string]string{
   236  				clientUser: keyUser,
   237  			},
   238  			Repos: []KernelRepo{
   239  				{
   240  					URL:    "git://syzkaller.org/access-user.git",
   241  					Branch: "access-user",
   242  					Alias:  "access-user",
   243  				},
   244  			},
   245  			Reporting: []Reporting{
   246  				{
   247  					AccessLevel: AccessAdmin,
   248  					Name:        "access-admin-reporting1",
   249  					DailyLimit:  1000,
   250  					Config:      &TestConfig{Index: 1},
   251  				},
   252  				{
   253  					Name:       "access-user-reporting2",
   254  					DailyLimit: 1000,
   255  					Config:     &TestConfig{Index: 2},
   256  				},
   257  			},
   258  		},
   259  		"access-public": {
   260  			AccessLevel: AccessPublic,
   261  			Key:         "publickeypublickeypublickey",
   262  			Clients: map[string]string{
   263  				clientPublic: keyPublic,
   264  			},
   265  			Repos: []KernelRepo{
   266  				{
   267  					URL:                    "git://syzkaller.org/access-public.git",
   268  					Branch:                 "access-public",
   269  					Alias:                  "access-public",
   270  					DetectMissingBackports: true,
   271  				},
   272  			},
   273  			Reporting: []Reporting{
   274  				{
   275  					AccessLevel: AccessUser,
   276  					Name:        "access-user-reporting1",
   277  					DailyLimit:  1000,
   278  					Config:      &TestConfig{Index: 1},
   279  				},
   280  				{
   281  					Name:       "access-public-reporting2",
   282  					DailyLimit: 1000,
   283  					Config:     &TestConfig{Index: 2},
   284  				},
   285  			},
   286  			FindBugOriginTrees: true,
   287  			CacheUIPages:       true,
   288  			RetestRepros:       true,
   289  		},
   290  		"access-public-email": {
   291  			AccessLevel: AccessPublic,
   292  			Key:         "publickeypublickeypublickey",
   293  			Clients: map[string]string{
   294  				clientPublicEmail: keyPublicEmail,
   295  			},
   296  			Managers: map[string]ConfigManager{
   297  				restrictedManager: {
   298  					RestrictedTestingRepo:   "git://restricted.git/restricted.git",
   299  					RestrictedTestingReason: "you should test only on restricted.git",
   300  				},
   301  			},
   302  			Repos: []KernelRepo{
   303  				{
   304  					URL:    "git://syzkaller.org/access-public-email.git",
   305  					Branch: "access-public-email",
   306  					Alias:  "access-public-email",
   307  				},
   308  				{
   309  					// Needed for TestTreeOriginLtsBisection().
   310  					URL:    "https://upstream.repo/repo",
   311  					Branch: "upstream-master",
   312  					Alias:  "upstream-master",
   313  				},
   314  			},
   315  			Reporting: []Reporting{
   316  				{
   317  					AccessLevel: AccessPublic,
   318  					Name:        "access-public-email-reporting1",
   319  					DailyLimit:  1000,
   320  					Config: &EmailConfig{
   321  						Email:            "test@syzkaller.com",
   322  						HandleListEmails: true,
   323  						SubjectPrefix:    "[syzbot]",
   324  					},
   325  				},
   326  			},
   327  			RetestRepros: true,
   328  			Subsystems: SubsystemsConfig{
   329  				Service: subsystem.MustMakeService(testSubsystems, 0),
   330  				Redirect: map[string]string{
   331  					"oldSubsystem": "subsystemA",
   332  				},
   333  			},
   334  		},
   335  		// The second namespace reporting to the same mailing list.
   336  		"access-public-email-2": {
   337  			AccessLevel: AccessPublic,
   338  			Key:         "publickeypublickeypublickey",
   339  			Clients: map[string]string{
   340  				clientPublicEmail2: keyPublicEmail2,
   341  			},
   342  			Repos: []KernelRepo{
   343  				{
   344  					URL:    "git://syzkaller.org/access-public-email2.git",
   345  					Branch: "access-public-email2",
   346  					Alias:  "access-public-email2",
   347  				},
   348  			},
   349  			Reporting: []Reporting{
   350  				{
   351  					AccessLevel: AccessPublic,
   352  					Name:        "access-public-email2-reporting1",
   353  					DailyLimit:  1000,
   354  					Config: &EmailConfig{
   355  						Email:            "test@syzkaller.com",
   356  						HandleListEmails: true,
   357  					},
   358  				},
   359  			},
   360  		},
   361  		"fs-bugs-reporting": {
   362  			AccessLevel: AccessPublic,
   363  			Key:         "fspublickeypublickeypublickey",
   364  			Clients: map[string]string{
   365  				clientPublicFs: keyPublicFs,
   366  			},
   367  			Repos: []KernelRepo{
   368  				{
   369  					URL:    "git://syzkaller.org/fs-bugs.git",
   370  					Branch: "fs-bugs",
   371  					Alias:  "fs-bugs",
   372  				},
   373  			},
   374  			Reporting: []Reporting{
   375  				{
   376  					Name:       "wait-repro",
   377  					DailyLimit: 1000,
   378  					Filter: func(bug *Bug) FilterResult {
   379  						if canBeVfsBug(bug) &&
   380  							bug.ReproLevel == dashapi.ReproLevelNone {
   381  							return FilterReport
   382  						}
   383  						return FilterSkip
   384  					},
   385  					Config: &TestConfig{Index: 1},
   386  				},
   387  				{
   388  					AccessLevel: AccessPublic,
   389  					Name:        "public",
   390  					DailyLimit:  1000,
   391  					Config: &EmailConfig{
   392  						Email:              "test@syzkaller.com",
   393  						HandleListEmails:   true,
   394  						DefaultMaintainers: []string{"linux-kernel@vger.kernel.org"},
   395  						MailMaintainers:    true,
   396  						SubjectPrefix:      "[syzbot]",
   397  					},
   398  				},
   399  			},
   400  			Subsystems: SubsystemsConfig{
   401  				Service: subsystem.ListService("linux"),
   402  			},
   403  		},
   404  		"test-decommission": {
   405  			AccessLevel:      AccessAdmin,
   406  			Key:              "testdecommissiontestdecommission",
   407  			SimilarityDomain: testDomain,
   408  			Clients: map[string]string{
   409  				clientTestDecomm: keyTestDecomm,
   410  			},
   411  			Repos: []KernelRepo{
   412  				{
   413  					URL:    "git://syzkaller.org",
   414  					Branch: "branch10",
   415  					Alias:  "repo10alias",
   416  				},
   417  			},
   418  			Reporting: []Reporting{
   419  				{
   420  					Name:       "reporting1",
   421  					DailyLimit: 3,
   422  					Embargo:    14 * 24 * time.Hour,
   423  					Filter:     skipWithRepro,
   424  					Config: &TestConfig{
   425  						Index: 1,
   426  					},
   427  				},
   428  				{
   429  					Name:       "reporting2",
   430  					DailyLimit: 3,
   431  					Config: &TestConfig{
   432  						Index: 2,
   433  					},
   434  				},
   435  			},
   436  		},
   437  		"test-mgr-decommission": {
   438  			AccessLevel:      AccessAdmin,
   439  			Key:              "testmgrdecommissiontestmgrdecommission",
   440  			SimilarityDomain: testDomain,
   441  			Clients: map[string]string{
   442  				clientMgrDecommission: keyMgrDecommission,
   443  			},
   444  			Managers: map[string]ConfigManager{
   445  				notYetDecommManger: {},
   446  				delegateToManager:  {},
   447  			},
   448  			Repos: []KernelRepo{
   449  				{
   450  					URL:    "git://syzkaller.org",
   451  					Branch: "branch10",
   452  					Alias:  "repo10alias",
   453  				},
   454  			},
   455  			Reporting: []Reporting{
   456  				{
   457  					Name:       "reporting1",
   458  					DailyLimit: 5,
   459  					Embargo:    14 * 24 * time.Hour,
   460  					Filter:     skipWithRepro,
   461  					Config: &EmailConfig{
   462  						Email: "test@syzkaller.com",
   463  					},
   464  				},
   465  				{
   466  					Name:       "reporting2",
   467  					DailyLimit: 3,
   468  					Filter:     skipWithRepro2,
   469  					Config: &EmailConfig{
   470  						Email:              "bugs@syzkaller.com",
   471  						DefaultMaintainers: []string{"default@maintainers.com"},
   472  						SubjectPrefix:      "[syzbot]",
   473  						MailMaintainers:    true,
   474  					},
   475  				},
   476  			},
   477  			RetestRepros: true,
   478  		},
   479  		"subsystem-reminders": {
   480  			AccessLevel: AccessPublic,
   481  			Key:         "subsystemreminderssubsystemreminders",
   482  			Clients: map[string]string{
   483  				clientSubsystemRemind: keySubsystemRemind,
   484  			},
   485  			Repos: []KernelRepo{
   486  				{
   487  					URL:    "git://syzkaller.org/reminders.git",
   488  					Branch: "main",
   489  					Alias:  "main",
   490  				},
   491  			},
   492  			Reporting: []Reporting{
   493  				{
   494  					// Let's emulate public moderation.
   495  					AccessLevel: AccessPublic,
   496  					Name:        "moderation",
   497  					DailyLimit:  1000,
   498  					Filter: func(bug *Bug) FilterResult {
   499  						if strings.Contains(bug.Title, "keep in moderation") {
   500  							return FilterReport
   501  						}
   502  						return FilterSkip
   503  					},
   504  					Config: &TestConfig{Index: 1},
   505  				},
   506  				{
   507  					AccessLevel: AccessPublic,
   508  					Name:        "public",
   509  					DailyLimit:  1000,
   510  					Config: &EmailConfig{
   511  						Email:              "bugs@syzkaller.com",
   512  						HandleListEmails:   true,
   513  						MailMaintainers:    true,
   514  						DefaultMaintainers: []string{"default@maintainers.com"},
   515  						SubjectPrefix:      "[syzbot]",
   516  					},
   517  				},
   518  			},
   519  			Subsystems: SubsystemsConfig{
   520  				Service: subsystem.MustMakeService(testSubsystems, 0),
   521  				Reminder: &BugListReportingConfig{
   522  					SourceReporting: "public",
   523  					BugsInReport:    6,
   524  					ModerationConfig: &EmailConfig{
   525  						Email:         "moderation@syzkaller.com",
   526  						SubjectPrefix: "[moderation]",
   527  					},
   528  					Config: &EmailConfig{
   529  						Email:           "bugs@syzkaller.com",
   530  						MailMaintainers: true,
   531  						SubjectPrefix:   "[syzbot]",
   532  					},
   533  				},
   534  			},
   535  		},
   536  		"tree-tests": {
   537  			AccessLevel:           AccessPublic,
   538  			FixBisectionAutoClose: true,
   539  			Key:                   "treeteststreeteststreeteststreeteststreeteststreetests",
   540  			Clients: map[string]string{
   541  				clientTreeTests: keyTreeTests,
   542  			},
   543  			Repos: []KernelRepo{
   544  				{
   545  					URL:                    "git://syzkaller.org/test.git",
   546  					Branch:                 "main",
   547  					Alias:                  "main",
   548  					DetectMissingBackports: true,
   549  				},
   550  			},
   551  			Managers: map[string]ConfigManager{
   552  				"better-manager": {
   553  					Priority: 1,
   554  				},
   555  			},
   556  			Reporting: []Reporting{
   557  				{
   558  					AccessLevel: AccessAdmin,
   559  					Name:        "non-public",
   560  					DailyLimit:  1000,
   561  					Filter: func(bug *Bug) FilterResult {
   562  						return FilterReport
   563  					},
   564  					Config: &TestConfig{Index: 1},
   565  				},
   566  				{
   567  					AccessLevel: AccessUser,
   568  					Name:        "user",
   569  					DailyLimit:  1000,
   570  					Config: &EmailConfig{
   571  						Email:         "bugs@syzkaller.com",
   572  						SubjectPrefix: "[syzbot]",
   573  					},
   574  					Labels: map[string]string{
   575  						"origin:downstream": "Bug presence analysis results: the bug reproduces only on the downstream tree.",
   576  					},
   577  				},
   578  			},
   579  			FindBugOriginTrees:     true,
   580  			RetestMissingBackports: true,
   581  		},
   582  		"coverage-tests": {
   583  			Coverage: &CoverageConfig{
   584  				EmailRegressionsTo:  "test@test.test",
   585  				RegressionThreshold: 1,
   586  			},
   587  			AccessLevel: AccessPublic,
   588  			Key:         "coveragetestskeycoveragetestskeycoveragetestskey",
   589  			Repos: []KernelRepo{
   590  				{
   591  					URL:    "git://syzkaller.org/test.git",
   592  					Branch: "main",
   593  					Alias:  "main",
   594  				},
   595  			},
   596  			Reporting: []Reporting{
   597  				{
   598  					Name:       "non-public",
   599  					DailyLimit: 1000,
   600  					Filter: func(bug *Bug) FilterResult {
   601  						return FilterReport
   602  					},
   603  					Config: &TestConfig{Index: 1},
   604  				},
   605  			},
   606  		},
   607  	},
   608  }
   609  
   610  var testSubsystems = []*subsystem.Subsystem{
   611  	{
   612  		Name:        "subsystemA",
   613  		PathRules:   []subsystem.PathRule{{IncludeRegexp: `a\.c`}},
   614  		Lists:       []string{"subsystemA@list.com"},
   615  		Maintainers: []string{"subsystemA@person.com"},
   616  	},
   617  	{
   618  		Name:        "subsystemB",
   619  		PathRules:   []subsystem.PathRule{{IncludeRegexp: `b\.c`}},
   620  		Lists:       []string{"subsystemB@list.com"},
   621  		Maintainers: []string{"subsystemB@person.com"},
   622  	},
   623  	{
   624  		Name:        "subsystemC",
   625  		PathRules:   []subsystem.PathRule{{IncludeRegexp: `c\.c`}},
   626  		Lists:       []string{"subsystemC@list.com"},
   627  		Maintainers: []string{"subsystemC@person.com"},
   628  		NoReminders: true,
   629  	},
   630  }
   631  
   632  const (
   633  	client1               = "client1"
   634  	client2               = "client2"
   635  	password1             = "client1keyclient1keyclient1key"
   636  	password2             = "client2keyclient2keyclient2key"
   637  	clientAdmin           = "client-admin"
   638  	keyAdmin              = "clientadminkeyclientadminkey"
   639  	clientUser            = "client-user"
   640  	keyUser               = "clientuserkeyclientuserkey"
   641  	clientPublic          = "client-public"
   642  	keyPublic             = "clientpublickeyclientpublickey"
   643  	clientPublicEmail     = "client-public-email"
   644  	keyPublicEmail        = "clientpublicemailkeyclientpublicemailkey"
   645  	clientPublicEmail2    = "client-public-email2"
   646  	keyPublicEmail2       = "clientpublicemailkeyclientpublicemailkey2"
   647  	clientPublicFs        = "client-public-fs"
   648  	keyPublicFs           = "keypublicfskeypublicfskeypublicfs"
   649  	clientTestDecomm      = "client-test-decomm"
   650  	keyTestDecomm         = "keyTestDecommkeyTestDecomm"
   651  	clientMgrDecommission = "client-mgr-decommission"
   652  	keyMgrDecommission    = "keyMgrDecommissionkeyMgrDecommission"
   653  	clientSubsystemRemind = "client-subystem-reminders"
   654  	keySubsystemRemind    = "keySubsystemRemindkeySubsystemRemind"
   655  	clientTreeTests       = "clientTreeTestsclientTreeTests"
   656  	keyTreeTests          = "keyTreeTestskeyTreeTestskeyTreeTests"
   657  
   658  	restrictedManager     = "restricted-manager"
   659  	noFixBisectionManager = "no-fix-bisection-manager"
   660  	specialCCManager      = "special-cc-manager"
   661  	notYetDecommManger    = "not-yet-decomm-manager"
   662  	delegateToManager     = "delegate-to-manager"
   663  
   664  	testDomain = "test"
   665  )
   666  
   667  func skipWithRepro(bug *Bug) FilterResult {
   668  	if strings.HasPrefix(bug.Title, "skip with repro") &&
   669  		bug.ReproLevel != dashapi.ReproLevelNone {
   670  		return FilterSkip
   671  	}
   672  	return FilterReport
   673  }
   674  
   675  func skipWithRepro2(bug *Bug) FilterResult {
   676  	if strings.HasPrefix(bug.Title, "skip reporting2 with repro") &&
   677  		bug.ReproLevel != dashapi.ReproLevelNone {
   678  		return FilterSkip
   679  	}
   680  	return FilterReport
   681  }
   682  
   683  type TestConfig struct {
   684  	Index int
   685  }
   686  
   687  func (cfg *TestConfig) Type() string {
   688  	return "test"
   689  }
   690  
   691  func (cfg *TestConfig) Validate() error {
   692  	return nil
   693  }
   694  
   695  func testBuild(id int) *dashapi.Build {
   696  	return &dashapi.Build{
   697  		Manager:           fmt.Sprintf("manager%v", id),
   698  		ID:                fmt.Sprintf("build%v", id),
   699  		OS:                targets.Linux,
   700  		Arch:              targets.AMD64,
   701  		VMArch:            targets.AMD64,
   702  		SyzkallerCommit:   fmt.Sprintf("syzkaller_commit%v", id),
   703  		CompilerID:        fmt.Sprintf("compiler%v", id),
   704  		KernelRepo:        fmt.Sprintf("repo%v", id),
   705  		KernelBranch:      fmt.Sprintf("branch%v", id),
   706  		KernelCommit:      strings.Repeat(fmt.Sprint(id), 40)[:40],
   707  		KernelCommitTitle: fmt.Sprintf("kernel_commit_title%v", id),
   708  		KernelCommitDate:  buildCommitDate,
   709  		KernelConfig:      []byte(fmt.Sprintf("config%v", id)),
   710  	}
   711  }
   712  
   713  var buildCommitDate = time.Date(1, 2, 3, 4, 5, 6, 0, time.UTC)
   714  
   715  func testCrash(build *dashapi.Build, id int) *dashapi.Crash {
   716  	return &dashapi.Crash{
   717  		BuildID:     build.ID,
   718  		Title:       fmt.Sprintf("title%v", id),
   719  		Log:         []byte(fmt.Sprintf("log%v", id)),
   720  		Report:      []byte(fmt.Sprintf("report%v", id)),
   721  		MachineInfo: []byte(fmt.Sprintf("machine info %v", id)),
   722  	}
   723  }
   724  
   725  func testCrashWithRepro(build *dashapi.Build, id int) *dashapi.Crash {
   726  	crash := testCrash(build, id)
   727  	crash.ReproOpts = []byte(fmt.Sprintf("repro opts %v", id))
   728  	crash.ReproSyz = []byte(fmt.Sprintf("syncfs(%v)", id))
   729  	crash.ReproC = []byte(fmt.Sprintf("int main() { return %v; }", id))
   730  	crash.ReproLog = []byte(fmt.Sprintf("repro log %d", id))
   731  	return crash
   732  }
   733  
   734  func testCrashID(crash *dashapi.Crash) *dashapi.CrashID {
   735  	return &dashapi.CrashID{
   736  		BuildID: crash.BuildID,
   737  		Title:   crash.Title,
   738  	}
   739  }
   740  
   741  func TestApp(t *testing.T) {
   742  	c := NewCtx(t)
   743  	defer c.Close()
   744  
   745  	_, err := c.GET("/test1")
   746  	c.expectOK(err)
   747  
   748  	apiClient1 := c.makeClient(client1, password1, false)
   749  	apiClient2 := c.makeClient(client2, password2, false)
   750  	c.expectFail("unknown api method", apiClient1.Query("unsupported_method", nil, nil))
   751  	c.client.LogError("name", "msg %s", "arg")
   752  
   753  	build := testBuild(1)
   754  	c.client.UploadBuild(build)
   755  	// Uploading the same build must be OK.
   756  	c.client.UploadBuild(build)
   757  
   758  	// Some bad combinations of client/key.
   759  	c.expectFail("unauthorized", c.makeClient(client1, "borked", false).Query("upload_build", build, nil))
   760  	c.expectFail("unauthorized", c.makeClient("unknown", password1, false).Query("upload_build", build, nil))
   761  	c.expectFail("unauthorized", c.makeClient(client1, password2, false).Query("upload_build", build, nil))
   762  
   763  	crash1 := testCrash(build, 1)
   764  	c.client.ReportCrash(crash1)
   765  	c.client.pollBug()
   766  
   767  	// Test that namespace isolation works.
   768  	c.expectFail("unknown build", apiClient2.Query("report_crash", crash1, nil))
   769  
   770  	crash2 := testCrashWithRepro(build, 2)
   771  	c.client.ReportCrash(crash2)
   772  	c.client.pollBug()
   773  
   774  	// Provoke purgeOldCrashes.
   775  	const purgeTestIters = 30
   776  	for i := 0; i < purgeTestIters; i++ {
   777  		// Also test how daily counts work.
   778  		if i == purgeTestIters/2 {
   779  			c.advanceTime(48 * time.Hour)
   780  		}
   781  		crash := testCrash(build, 3)
   782  		crash.Log = []byte(fmt.Sprintf("log%v", i))
   783  		crash.Report = []byte(fmt.Sprintf("report%v", i))
   784  		c.client.ReportCrash(crash)
   785  	}
   786  	rep := c.client.pollBug()
   787  	bug, _, _ := c.loadBug(rep.ID)
   788  	c.expectNE(bug, nil)
   789  	c.expectEQ(bug.DailyStats, []BugDailyStats{
   790  		{20000101, purgeTestIters / 2},
   791  		{20000103, purgeTestIters / 2},
   792  	})
   793  
   794  	cid := &dashapi.CrashID{
   795  		BuildID: "build1",
   796  		Title:   "title1",
   797  	}
   798  	c.client.ReportFailedRepro(cid)
   799  
   800  	c.client.ReportingPollBugs("test")
   801  
   802  	c.client.ReportingUpdate(&dashapi.BugUpdate{
   803  		ID:         "id",
   804  		Status:     dashapi.BugStatusOpen,
   805  		ReproLevel: dashapi.ReproLevelC,
   806  	})
   807  }
   808  
   809  func TestRedirects(t *testing.T) {
   810  	c := NewCtx(t)
   811  	defer c.Close()
   812  
   813  	checkRedirect(c, AccessUser, "/", "/test1", http.StatusFound) // redirect to default namespace
   814  	checkRedirect(c, AccessAdmin, "/", "/admin", http.StatusFound)
   815  	checkLoginRedirect(c, AccessPublic, "/access-user") // not accessible namespace
   816  
   817  	_, err := c.AuthGET(AccessUser, "/access-user")
   818  	c.expectOK(err)
   819  }
   820  
   821  func TestResponseStatusCode(t *testing.T) {
   822  	tests := []struct {
   823  		whatURL      string
   824  		wantRespCode int
   825  	}{
   826  		{
   827  			"/text?tag=CrashLog&x=13354bf5700000",
   828  			http.StatusNotFound,
   829  		},
   830  		{
   831  			"/text?tag=CrashReport&x=17a2bedcb00000",
   832  			http.StatusNotFound,
   833  		},
   834  		{
   835  			"/text?tag=ReproSyz&x=107e219b700000",
   836  			http.StatusNotFound,
   837  		},
   838  		{
   839  			"/text?tag=ReproC&x=1762ad64f00000",
   840  			http.StatusNotFound,
   841  		},
   842  		{
   843  			"/text?tag=CrashLog",
   844  			http.StatusBadRequest,
   845  		},
   846  		{
   847  			"/text?tag=CrashReport",
   848  			http.StatusBadRequest,
   849  		},
   850  		{
   851  			"/text?tag=ReproC",
   852  			http.StatusBadRequest,
   853  		},
   854  		{
   855  			"/text?tag=ReproSyz",
   856  			http.StatusBadRequest,
   857  		},
   858  	}
   859  
   860  	c := NewCtx(t)
   861  	defer c.Close()
   862  
   863  	for _, test := range tests {
   864  		checkResponseStatusCode(c, AccessUser, test.whatURL, test.wantRespCode)
   865  	}
   866  }
   867  
   868  func checkLoginRedirect(c *Ctx, accessLevel AccessLevel, url string) {
   869  	to, err := user.LoginURL(c.ctx, url)
   870  	if err != nil {
   871  		c.t.Fatal(err)
   872  	}
   873  	checkRedirect(c, accessLevel, url, to, http.StatusTemporaryRedirect)
   874  }
   875  
   876  func checkRedirect(c *Ctx, accessLevel AccessLevel, from, to string, status int) {
   877  	_, err := c.AuthGET(accessLevel, from)
   878  	c.expectNE(err, nil)
   879  	var httpErr *HTTPError
   880  	c.expectTrue(errors.As(err, &httpErr))
   881  	c.expectEQ(httpErr.Code, status)
   882  	c.expectEQ(httpErr.Headers["Location"], []string{to})
   883  }
   884  
   885  func checkResponseStatusCode(c *Ctx, accessLevel AccessLevel, url string, status int) {
   886  	_, err := c.AuthGET(accessLevel, url)
   887  	c.expectNE(err, nil)
   888  	var httpErr *HTTPError
   889  	c.expectTrue(errors.As(err, &httpErr))
   890  	c.expectEQ(httpErr.Code, status)
   891  }
   892  
   893  // Test purging of old crashes for bugs with lots of crashes.
   894  func TestPurgeOldCrashes(t *testing.T) {
   895  	if testing.Short() {
   896  		t.Skip()
   897  	}
   898  	c := NewCtx(t)
   899  	defer c.Close()
   900  
   901  	build := testBuild(1)
   902  	c.client.UploadBuild(build)
   903  
   904  	// First, send 3 crashes that are reported. These need to be preserved regardless.
   905  	crash := testCrash(build, 1)
   906  	crash.ReproOpts = []byte("no repro")
   907  	c.client.ReportCrash(crash)
   908  	rep := c.client.pollBug()
   909  
   910  	crash.ReproSyz = []byte("getpid()")
   911  	crash.ReproOpts = []byte("syz repro")
   912  	c.client.ReportCrash(crash)
   913  	c.client.pollBug()
   914  
   915  	crash.ReproC = []byte("int main() {}")
   916  	crash.ReproOpts = []byte("C repro")
   917  	c.client.ReportCrash(crash)
   918  	c.client.pollBug()
   919  
   920  	// Now report lots of bugs with/without repros. Some of the older ones should be purged.
   921  	var totalReported = 3 * maxCrashes()
   922  	for i := 0; i < totalReported; i++ {
   923  		c.advanceTime(2 * time.Hour) // This ensures that crashes are saved.
   924  		crash.ReproSyz = nil
   925  		crash.ReproC = nil
   926  		crash.ReproOpts = []byte(fmt.Sprintf("%v", i))
   927  		c.client.ReportCrash(crash)
   928  
   929  		crash.ReproSyz = []byte("syz repro")
   930  		crash.ReproC = []byte("C repro")
   931  		crash.ReproOpts = []byte(fmt.Sprintf("%v", i))
   932  		c.client.ReportCrash(crash)
   933  	}
   934  	bug, _, _ := c.loadBug(rep.ID)
   935  	crashes, _, err := queryCrashesForBug(c.ctx, bug.key(c.ctx), 10*totalReported)
   936  	c.expectOK(err)
   937  	// First, count how many crashes of different types we have.
   938  	// We should get all 3 reported crashes + some with repros and some without repros.
   939  	reported, norepro, repro := 0, 0, 0
   940  	for _, crash := range crashes {
   941  		if !crash.Reported.IsZero() {
   942  			reported++
   943  		} else if crash.ReproSyz == 0 {
   944  			norepro++
   945  		} else {
   946  			repro++
   947  		}
   948  	}
   949  	c.t.Logf("got reported=%v, norepro=%v, repro=%v, maxCrashes=%v",
   950  		reported, norepro, repro, maxCrashes())
   951  	if reported != 3 ||
   952  		norepro < maxCrashes() || norepro > maxCrashes()+10 ||
   953  		repro < maxCrashes() || repro > maxCrashes()+10 {
   954  		c.t.Fatalf("bad purged crashes")
   955  	}
   956  	// Then, check that latest crashes were preserved.
   957  	for _, crash := range crashes {
   958  		if !crash.Reported.IsZero() {
   959  			continue
   960  		}
   961  		idx, err := strconv.Atoi(string(crash.ReproOpts))
   962  		c.expectOK(err)
   963  		count := norepro
   964  		if crash.ReproSyz != 0 {
   965  			count = repro
   966  		}
   967  		if idx < totalReported-count {
   968  			c.t.Errorf("preserved bad crash repro=%v: %v", crash.ReproC != 0, idx)
   969  		}
   970  	}
   971  
   972  	firstCrashExists := func() bool {
   973  		_, crashKeys, err := queryCrashesForBug(c.ctx, bug.key(c.ctx), 10*totalReported)
   974  		c.expectOK(err)
   975  		for _, key := range crashKeys {
   976  			if key.IntID() == rep.CrashID {
   977  				return true
   978  			}
   979  		}
   980  		return false
   981  	}
   982  
   983  	// A sanity check for the test itself.
   984  	if !firstCrashExists() {
   985  		t.Fatalf("the first reported crash should be present")
   986  	}
   987  
   988  	// Unreport the first crash.
   989  	reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{
   990  		ID:               rep.ID,
   991  		Status:           dashapi.BugStatusUpdate,
   992  		ReproLevel:       dashapi.ReproLevelC,
   993  		UnreportCrashIDs: []int64{rep.CrashID},
   994  	})
   995  	c.expectEQ(reply.OK, true)
   996  
   997  	// Trigger more purge events.
   998  	var moreIterations = maxCrashes()
   999  	for i := 0; i < moreIterations; i++ {
  1000  		c.advanceTime(2 * time.Hour) // This ensures that crashes are saved.
  1001  		crash.ReproSyz = nil
  1002  		crash.ReproC = nil
  1003  		crash.ReproOpts = []byte(fmt.Sprintf("%v", i))
  1004  		c.client.ReportCrash(crash)
  1005  	}
  1006  	// Check that the unreported crash was purged.
  1007  	if firstCrashExists() {
  1008  		t.Fatalf("the unreported crash should have been purged")
  1009  	}
  1010  }
  1011  
  1012  func TestManagerFailedBuild(t *testing.T) {
  1013  	c := NewCtx(t)
  1014  	defer c.Close()
  1015  
  1016  	// Upload and check first build.
  1017  	build := testBuild(1)
  1018  	c.client.UploadBuild(build)
  1019  	checkManagerBuild(c, build, nil, nil)
  1020  
  1021  	// Upload and check second build.
  1022  	build.ID = "id1"
  1023  	build.KernelCommit = "kern1"
  1024  	build.SyzkallerCommit = "syz1"
  1025  	c.client.UploadBuild(build)
  1026  	checkManagerBuild(c, build, nil, nil)
  1027  
  1028  	// Upload failed kernel build.
  1029  	failedBuild := new(dashapi.Build)
  1030  	*failedBuild = *build
  1031  	failedBuild.ID = "id2"
  1032  	failedBuild.KernelCommit = "kern2"
  1033  	failedBuild.KernelCommitTitle = "failed build 1"
  1034  	failedBuild.SyzkallerCommit = "syz2"
  1035  	c.expectOK(c.client.ReportBuildError(&dashapi.BuildErrorReq{
  1036  		Build: *failedBuild,
  1037  		Crash: dashapi.Crash{
  1038  			Title: "failed build 1",
  1039  		},
  1040  	}))
  1041  	checkManagerBuild(c, build, failedBuild, nil)
  1042  
  1043  	// Now the old good build again, nothing should change.
  1044  	c.client.UploadBuild(build)
  1045  	checkManagerBuild(c, build, failedBuild, nil)
  1046  
  1047  	// New good kernel build, failed build must reset.
  1048  	build.ID = "id3"
  1049  	build.KernelCommit = "kern3"
  1050  	c.client.UploadBuild(build)
  1051  	checkManagerBuild(c, build, nil, nil)
  1052  
  1053  	// Now more complex scenario: OK -> failed kernel -> failed kernel+syzkaller -> failed syzkaller -> OK.
  1054  	failedBuild.ID = "id4"
  1055  	failedBuild.KernelCommit = "kern4"
  1056  	failedBuild.KernelCommitTitle = "failed build 4"
  1057  	failedBuild.SyzkallerCommit = "syz4"
  1058  	c.expectOK(c.client.ReportBuildError(&dashapi.BuildErrorReq{
  1059  		Build: *failedBuild,
  1060  		Crash: dashapi.Crash{
  1061  			Title: "failed build 4",
  1062  		},
  1063  	}))
  1064  	checkManagerBuild(c, build, failedBuild, nil)
  1065  
  1066  	failedBuild2 := new(dashapi.Build)
  1067  	*failedBuild2 = *failedBuild
  1068  	failedBuild2.ID = "id5"
  1069  	failedBuild2.KernelCommit = ""
  1070  	failedBuild2.KernelCommitTitle = "failed build 5"
  1071  	failedBuild2.SyzkallerCommit = "syz5"
  1072  	c.expectOK(c.client.ReportBuildError(&dashapi.BuildErrorReq{
  1073  		Build: *failedBuild2,
  1074  		Crash: dashapi.Crash{
  1075  			Title: "failed build 5",
  1076  		},
  1077  	}))
  1078  	checkManagerBuild(c, build, failedBuild, failedBuild2)
  1079  
  1080  	build.ID = "id6"
  1081  	build.KernelCommit = "kern6"
  1082  	c.client.UploadBuild(build)
  1083  	checkManagerBuild(c, build, nil, failedBuild2)
  1084  
  1085  	build.ID = "id7"
  1086  	build.KernelCommit = "kern6"
  1087  	build.SyzkallerCommit = "syz7"
  1088  	c.client.UploadBuild(build)
  1089  	checkManagerBuild(c, build, nil, nil)
  1090  }
  1091  
  1092  func checkManagerBuild(c *Ctx, build, failedKernelBuild, failedSyzBuild *dashapi.Build) {
  1093  	mgr, dbBuild := c.loadManager("test1", build.Manager)
  1094  	c.expectEQ(mgr.CurrentBuild, build.ID)
  1095  	compareBuilds(c, dbBuild, build)
  1096  	checkBuildBug(c, mgr.FailedBuildBug, failedKernelBuild)
  1097  	checkBuildBug(c, mgr.FailedSyzBuildBug, failedSyzBuild)
  1098  }
  1099  
  1100  func checkBuildBug(c *Ctx, hash string, build *dashapi.Build) {
  1101  	if build == nil {
  1102  		c.expectEQ(hash, "")
  1103  		return
  1104  	}
  1105  	c.expectNE(hash, "")
  1106  	bug, _, dbBuild := c.loadBugByHash(hash)
  1107  	c.expectEQ(bug.Title, build.KernelCommitTitle)
  1108  	compareBuilds(c, dbBuild, build)
  1109  }
  1110  
  1111  func compareBuilds(c *Ctx, dbBuild *Build, build *dashapi.Build) {
  1112  	c.expectEQ(dbBuild.ID, build.ID)
  1113  	c.expectEQ(dbBuild.KernelCommit, build.KernelCommit)
  1114  	c.expectEQ(dbBuild.SyzkallerCommit, build.SyzkallerCommit)
  1115  }
  1116  
  1117  func TestLinkifyReport(t *testing.T) {
  1118  	input := `
  1119   tipc_topsrv_stop net/tipc/topsrv.c:694 [inline]
  1120   tipc_topsrv_exit_net+0x149/0x340 net/tipc/topsrv.c:715
  1121  kernel BUG at fs/ext4/inode.c:2753!
  1122  pkg/sentry/fsimpl/fuse/fusefs.go:278 +0x384
  1123   kvm_vcpu_release+0x4d/0x70 arch/x86/kvm/../../../virt/kvm/kvm_main.c:3713
  1124  	arch/x86/entry/entry_64.S:298
  1125  [<81751700>] (show_stack) from [<8176d3e0>] (dump_stack_lvl+0x48/0x54 lib/dump_stack.c:106)
  1126  `
  1127  	// nolint: lll
  1128  	output := `
  1129   tipc_topsrv_stop <a href='https://github.com/google/syzkaller/blob/111222/net/tipc/topsrv.c#L694'>net/tipc/topsrv.c:694</a> [inline]
  1130   tipc_topsrv_exit_net+0x149/0x340 <a href='https://github.com/google/syzkaller/blob/111222/net/tipc/topsrv.c#L715'>net/tipc/topsrv.c:715</a>
  1131  kernel BUG at <a href='https://github.com/google/syzkaller/blob/111222/fs/ext4/inode.c#L2753'>fs/ext4/inode.c:2753</a>!
  1132  <a href='https://github.com/google/syzkaller/blob/111222/pkg/sentry/fsimpl/fuse/fusefs.go#L278'>pkg/sentry/fsimpl/fuse/fusefs.go:278</a> +0x384
  1133   kvm_vcpu_release+0x4d/0x70 <a href='https://github.com/google/syzkaller/blob/111222/arch/x86/kvm/../../../virt/kvm/kvm_main.c#L3713'>arch/x86/kvm/../../../virt/kvm/kvm_main.c:3713</a>
  1134  	<a href='https://github.com/google/syzkaller/blob/111222/arch/x86/entry/entry_64.S#L298'>arch/x86/entry/entry_64.S:298</a>
  1135  [&lt;81751700&gt;] (show_stack) from [&lt;8176d3e0&gt;] (dump_stack_lvl+0x48/0x54 <a href='https://github.com/google/syzkaller/blob/111222/lib/dump_stack.c#L106'>lib/dump_stack.c:106</a>)
  1136  `
  1137  	got := linkifyReport([]byte(input), "https://github.com/google/syzkaller", "111222")
  1138  	if diff := cmp.Diff(output, string(got)); diff != "" {
  1139  		t.Fatal(diff)
  1140  	}
  1141  }