mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-27 05:10:43 +00:00
2b2857bd21
Checks for too long functions (based on lines and statements).
351 lines
10 KiB
Go
351 lines
10 KiB
Go
// Copyright 2018 syzkaller project authors. All rights reserved.
|
|
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/google/syzkaller/dashboard/dashapi"
|
|
"google.golang.org/appengine/user"
|
|
)
|
|
|
|
// TestAccessConfig checks that access level were properly assigned throughout the config.
|
|
func TestAccessConfig(t *testing.T) {
|
|
tests := []struct {
|
|
what string
|
|
want AccessLevel
|
|
level AccessLevel
|
|
}{
|
|
{"admin", AccessAdmin, config.Namespaces["access-admin"].AccessLevel},
|
|
{"admin/0", AccessAdmin, config.Namespaces["access-admin"].Reporting[0].AccessLevel},
|
|
{"admin/1", AccessAdmin, config.Namespaces["access-admin"].Reporting[1].AccessLevel},
|
|
{"user", AccessUser, config.Namespaces["access-user"].AccessLevel},
|
|
{"user/0", AccessAdmin, config.Namespaces["access-user"].Reporting[0].AccessLevel},
|
|
{"user/1", AccessUser, config.Namespaces["access-user"].Reporting[1].AccessLevel},
|
|
{"public", AccessPublic, config.Namespaces["access-public"].AccessLevel},
|
|
{"public/0", AccessUser, config.Namespaces["access-public"].Reporting[0].AccessLevel},
|
|
{"public/1", AccessPublic, config.Namespaces["access-public"].Reporting[1].AccessLevel},
|
|
}
|
|
for _, test := range tests {
|
|
if test.level != test.want {
|
|
t.Errorf("%v level %v, want %v", test.what, test.level, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAccess checks that all UIs respect access levels.
|
|
// nolint: funlen
|
|
func TestAccess(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip()
|
|
}
|
|
c := NewCtx(t)
|
|
defer c.Close()
|
|
|
|
// entity describes pages/bugs/texts/etc.
|
|
type entity struct {
|
|
level AccessLevel // level on which this entity must be visible.
|
|
ref string // a unique entity reference id.
|
|
url string // url at which this entity can be requested.
|
|
}
|
|
entities := []entity{
|
|
// Main pages.
|
|
{
|
|
level: AccessAdmin,
|
|
url: "/admin",
|
|
},
|
|
{
|
|
level: AccessPublic,
|
|
url: "/access-public",
|
|
},
|
|
{
|
|
level: AccessPublic,
|
|
url: "/access-public/fixed",
|
|
},
|
|
{
|
|
level: AccessPublic,
|
|
url: "/access-public/invalid",
|
|
},
|
|
{
|
|
level: AccessUser,
|
|
url: "/access-user",
|
|
},
|
|
{
|
|
level: AccessUser,
|
|
url: "/access-user/fixed",
|
|
},
|
|
{
|
|
level: AccessUser,
|
|
url: "/access-user/invalid",
|
|
},
|
|
{
|
|
level: AccessAdmin,
|
|
url: "/access-admin",
|
|
},
|
|
{
|
|
level: AccessAdmin,
|
|
url: "/access-admin/fixed",
|
|
},
|
|
{
|
|
level: AccessAdmin,
|
|
url: "/access-admin/invalid",
|
|
},
|
|
{
|
|
// Any references to namespace, reporting, links, etc.
|
|
level: AccessUser,
|
|
ref: "access-user",
|
|
},
|
|
{
|
|
// Any references to namespace, reporting, links, etc.
|
|
level: AccessAdmin,
|
|
ref: "access-admin",
|
|
},
|
|
}
|
|
|
|
// noteBugAccessLevel collects all entities associated with the extID bug.
|
|
noteBugAccessLevel := func(extID string, level AccessLevel) {
|
|
bug, _, err := findBugByReportingID(c.ctx, extID)
|
|
c.expectOK(err)
|
|
crash, _, err := findCrashForBug(c.ctx, bug)
|
|
c.expectOK(err)
|
|
bugID := bug.keyHash()
|
|
entities = append(entities, []entity{
|
|
{
|
|
level: level,
|
|
ref: bugID,
|
|
url: fmt.Sprintf("/bug?id=%v", bugID),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: bug.Reporting[0].ID,
|
|
url: fmt.Sprintf("/bug?extid=%v", bug.Reporting[0].ID),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: bug.Reporting[1].ID,
|
|
url: fmt.Sprintf("/bug?extid=%v", bug.Reporting[1].ID),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.Log),
|
|
url: fmt.Sprintf("/text?tag=CrashLog&id=%v", crash.Log),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.Log),
|
|
url: fmt.Sprintf("/text?tag=CrashLog&x=%v",
|
|
strconv.FormatUint(uint64(crash.Log), 16)),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.Report),
|
|
url: fmt.Sprintf("/text?tag=CrashReport&id=%v", crash.Report),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.Report),
|
|
url: fmt.Sprintf("/text?tag=CrashReport&x=%v",
|
|
strconv.FormatUint(uint64(crash.Report), 16)),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.ReproC),
|
|
url: fmt.Sprintf("/text?tag=ReproC&id=%v", crash.ReproC),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.ReproC),
|
|
url: fmt.Sprintf("/text?tag=ReproC&x=%v",
|
|
strconv.FormatUint(uint64(crash.ReproC), 16)),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.ReproSyz),
|
|
url: fmt.Sprintf("/text?tag=ReproSyz&id=%v", crash.ReproSyz),
|
|
},
|
|
{
|
|
level: level,
|
|
ref: fmt.Sprint(crash.ReproSyz),
|
|
url: fmt.Sprintf("/text?tag=ReproSyz&x=%v",
|
|
strconv.FormatUint(uint64(crash.ReproSyz), 16)),
|
|
},
|
|
}...)
|
|
}
|
|
|
|
// noteBuildccessLevel collects all entities associated with the kernel build buildID.
|
|
noteBuildccessLevel := func(ns, buildID string) {
|
|
build, err := loadBuild(c.ctx, ns, buildID)
|
|
c.expectOK(err)
|
|
entities = append(entities, entity{
|
|
level: config.Namespaces[ns].AccessLevel,
|
|
ref: build.ID,
|
|
url: fmt.Sprintf("/text?tag=KernelConfig&id=%v", build.KernelConfig),
|
|
})
|
|
}
|
|
|
|
// These strings are put into crash log/report, kernel config, etc.
|
|
// If a request at level UserPublic sees a page containing "access-user",
|
|
// that will be flagged as error.
|
|
accessLevelPrefix := func(level AccessLevel) string {
|
|
switch level {
|
|
case AccessPublic:
|
|
return "access-public-"
|
|
case AccessUser:
|
|
return "access-user-"
|
|
default:
|
|
return "access-admin-"
|
|
}
|
|
}
|
|
|
|
// For each namespace we create 8 bugs:
|
|
// invalid, dup, fixed and open for both reportings.
|
|
// Bugs are setup in such a way that there are lots of
|
|
// duplicate/similar cross-references.
|
|
for _, ns := range []string{"access-admin", "access-user", "access-public"} {
|
|
clientName, clientKey := "", ""
|
|
for k, v := range config.Namespaces[ns].Clients {
|
|
clientName, clientKey = k, v
|
|
}
|
|
namespaceAccessPrefix := accessLevelPrefix(config.Namespaces[ns].AccessLevel)
|
|
client := c.makeClient(clientName, clientKey, true)
|
|
build := testBuild(1)
|
|
build.KernelConfig = []byte(namespaceAccessPrefix + "build")
|
|
client.UploadBuild(build)
|
|
noteBuildccessLevel(ns, build.ID)
|
|
|
|
for reportingIdx := 0; reportingIdx < 2; reportingIdx++ {
|
|
accessLevel := config.Namespaces[ns].Reporting[reportingIdx].AccessLevel
|
|
accessPrefix := accessLevelPrefix(accessLevel)
|
|
|
|
crashInvalid := testCrashWithRepro(build, reportingIdx*10+0)
|
|
client.ReportCrash(crashInvalid)
|
|
repInvalid := client.pollBug()
|
|
if reportingIdx != 0 {
|
|
client.updateBug(repInvalid.ID, dashapi.BugStatusUpstream, "")
|
|
repInvalid = client.pollBug()
|
|
}
|
|
client.updateBug(repInvalid.ID, dashapi.BugStatusInvalid, "")
|
|
// Invalid bugs become visible up to the last reporting.
|
|
finalLevel := config.Namespaces[ns].
|
|
Reporting[len(config.Namespaces[ns].Reporting)-1].AccessLevel
|
|
noteBugAccessLevel(repInvalid.ID, finalLevel)
|
|
|
|
crashFixed := testCrashWithRepro(build, reportingIdx*10+0)
|
|
client.ReportCrash(crashFixed)
|
|
repFixed := client.pollBug()
|
|
if reportingIdx != 0 {
|
|
client.updateBug(repFixed.ID, dashapi.BugStatusUpstream, "")
|
|
repFixed = client.pollBug()
|
|
}
|
|
reply, _ := client.ReportingUpdate(&dashapi.BugUpdate{
|
|
ID: repFixed.ID,
|
|
Status: dashapi.BugStatusOpen,
|
|
FixCommits: []string{ns + "-patch0"},
|
|
ExtID: accessPrefix + "reporting-ext-id",
|
|
Link: accessPrefix + "reporting-link",
|
|
})
|
|
c.expectEQ(reply.OK, true)
|
|
buildFixing := testBuild(reportingIdx*10 + 2)
|
|
buildFixing.Manager = build.Manager
|
|
buildFixing.Commits = []string{ns + "-patch0"}
|
|
client.UploadBuild(buildFixing)
|
|
noteBuildccessLevel(ns, buildFixing.ID)
|
|
// Fixed bugs are also visible up to the last reporting.
|
|
noteBugAccessLevel(repFixed.ID, finalLevel)
|
|
|
|
crashOpen := testCrashWithRepro(build, reportingIdx*10+0)
|
|
crashOpen.Log = []byte(accessPrefix + "log")
|
|
crashOpen.Report = []byte(accessPrefix + "report")
|
|
crashOpen.ReproC = []byte(accessPrefix + "repro c")
|
|
crashOpen.ReproSyz = []byte(accessPrefix + "repro syz")
|
|
client.ReportCrash(crashOpen)
|
|
repOpen := client.pollBug()
|
|
if reportingIdx != 0 {
|
|
client.updateBug(repOpen.ID, dashapi.BugStatusUpstream, "")
|
|
repOpen = client.pollBug()
|
|
}
|
|
noteBugAccessLevel(repOpen.ID, accessLevel)
|
|
|
|
crashPatched := testCrashWithRepro(build, reportingIdx*10+1)
|
|
client.ReportCrash(crashPatched)
|
|
repPatched := client.pollBug()
|
|
if reportingIdx != 0 {
|
|
client.updateBug(repPatched.ID, dashapi.BugStatusUpstream, "")
|
|
repPatched = client.pollBug()
|
|
}
|
|
reply, _ = client.ReportingUpdate(&dashapi.BugUpdate{
|
|
ID: repPatched.ID,
|
|
Status: dashapi.BugStatusOpen,
|
|
FixCommits: []string{ns + "-patch0"},
|
|
ExtID: accessPrefix + "reporting-ext-id",
|
|
Link: accessPrefix + "reporting-link",
|
|
})
|
|
c.expectEQ(reply.OK, true)
|
|
// Patched bugs are also visible up to the last reporting.
|
|
noteBugAccessLevel(repPatched.ID, finalLevel)
|
|
|
|
crashDup := testCrashWithRepro(build, reportingIdx*10+2)
|
|
client.ReportCrash(crashDup)
|
|
repDup := client.pollBug()
|
|
if reportingIdx != 0 {
|
|
client.updateBug(repDup.ID, dashapi.BugStatusUpstream, "")
|
|
repDup = client.pollBug()
|
|
}
|
|
client.updateBug(repDup.ID, dashapi.BugStatusDup, repOpen.ID)
|
|
noteBugAccessLevel(repDup.ID, accessLevel)
|
|
}
|
|
}
|
|
|
|
// checkReferences checks that page contents do not contain
|
|
// references to entities that must not be visible.
|
|
checkReferences := func(url string, requestLevel AccessLevel, reply []byte) {
|
|
for _, ent := range entities {
|
|
if requestLevel >= ent.level || ent.ref == "" {
|
|
continue
|
|
}
|
|
if bytes.Contains(reply, []byte(ent.ref)) {
|
|
t.Errorf("request %v at level %v contains ref %v at level %v:\n%s\n\n",
|
|
url, requestLevel, ent.ref, ent.level, reply)
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkPage checks that the page at url is accessible/not accessible as required.
|
|
checkPage := func(requestLevel, pageLevel AccessLevel, url string) []byte {
|
|
reply, err := c.AuthGET(requestLevel, url)
|
|
if requestLevel >= pageLevel {
|
|
c.expectOK(err)
|
|
} else if requestLevel == AccessPublic {
|
|
loginURL, err1 := user.LoginURL(c.ctx, url)
|
|
if err1 != nil {
|
|
t.Fatal(err1)
|
|
}
|
|
c.expectNE(err, nil)
|
|
httpErr, ok := err.(HTTPError)
|
|
c.expectTrue(ok)
|
|
c.expectEQ(httpErr.Code, http.StatusTemporaryRedirect)
|
|
c.expectEQ(httpErr.Headers["Location"], []string{loginURL})
|
|
} else {
|
|
c.expectForbidden(err)
|
|
}
|
|
return reply
|
|
}
|
|
|
|
// Finally, request all entities at all access levels and
|
|
// check that we see only what we need to see.
|
|
for requestLevel := AccessPublic; requestLevel < AccessAdmin; requestLevel++ {
|
|
for _, ent := range entities {
|
|
if ent.url == "" {
|
|
continue
|
|
}
|
|
reply := checkPage(requestLevel, ent.level, ent.url)
|
|
checkReferences(ent.url, requestLevel, reply)
|
|
}
|
|
}
|
|
}
|