1// Copyright 2018 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 4package dash 5 6import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "strings" 11 12 "golang.org/x/net/context" 13 "google.golang.org/appengine" 14 "google.golang.org/appengine/datastore" 15 "google.golang.org/appengine/log" 16 "google.golang.org/appengine/user" 17) 18 19type AccessLevel int 20 21const ( 22 AccessPublic AccessLevel = iota + 1 23 AccessUser 24 AccessAdmin 25) 26 27func verifyAccessLevel(access AccessLevel) { 28 switch access { 29 case AccessPublic, AccessUser, AccessAdmin: 30 return 31 default: 32 panic(fmt.Sprintf("bad access level %v", access)) 33 } 34} 35 36var ErrAccess = errors.New("unauthorized") 37 38func checkAccessLevel(c context.Context, r *http.Request, level AccessLevel) error { 39 if accessLevel(c, r) >= level { 40 return nil 41 } 42 if u := user.Current(c); u != nil { 43 // Log only if user is signed in. Not-signed-in users are redirected to login page. 44 log.Errorf(c, "unauthorized access: %q [%q] access level %v", u.Email, u.AuthDomain, level) 45 } 46 return ErrAccess 47} 48 49func accessLevel(c context.Context, r *http.Request) AccessLevel { 50 if user.IsAdmin(c) { 51 switch r.FormValue("access") { 52 case "public": 53 return AccessPublic 54 case "user": 55 return AccessUser 56 } 57 return AccessAdmin 58 } 59 u := user.Current(c) 60 if u == nil || 61 // devappserver is broken 62 u.AuthDomain != "gmail.com" && !appengine.IsDevAppServer() || 63 !strings.HasSuffix(u.Email, config.AuthDomain) { 64 return AccessPublic 65 } 66 return AccessUser 67} 68 69func checkTextAccess(c context.Context, r *http.Request, tag string, id int64) (*Crash, error) { 70 switch tag { 71 default: 72 return nil, checkAccessLevel(c, r, AccessAdmin) 73 case textPatch: 74 return nil, checkJobTextAccess(c, r, "Patch", id) 75 case textError: 76 return nil, checkJobTextAccess(c, r, "Error", id) 77 case textKernelConfig: 78 // This is checked based on text namespace. 79 return nil, nil 80 case textCrashLog: 81 // Log and Report can be attached to a Crash or Job. 82 crash, err := checkCrashTextAccess(c, r, "Log", id) 83 if err == nil || err == ErrAccess { 84 return crash, err 85 } 86 return nil, checkJobTextAccess(c, r, "CrashLog", id) 87 case textCrashReport: 88 crash, err := checkCrashTextAccess(c, r, "Report", id) 89 if err == nil || err == ErrAccess { 90 return crash, err 91 } 92 return nil, checkJobTextAccess(c, r, "CrashReport", id) 93 case textReproSyz: 94 return checkCrashTextAccess(c, r, "ReproSyz", id) 95 case textReproC: 96 return checkCrashTextAccess(c, r, "ReproC", id) 97 } 98} 99 100func checkCrashTextAccess(c context.Context, r *http.Request, field string, id int64) (*Crash, error) { 101 var crashes []*Crash 102 keys, err := datastore.NewQuery("Crash"). 103 Filter(field+"=", id). 104 GetAll(c, &crashes) 105 if err != nil { 106 return nil, fmt.Errorf("failed to query crashes: %v", err) 107 } 108 if len(crashes) != 1 { 109 return nil, fmt.Errorf("checkCrashTextAccess: found %v crashes for %v=%v", 110 len(crashes), field, id) 111 } 112 crash := crashes[0] 113 bug := new(Bug) 114 if err := datastore.Get(c, keys[0].Parent(), bug); err != nil { 115 return nil, fmt.Errorf("failed to get bug: %v", err) 116 } 117 bugLevel := bug.sanitizeAccess(accessLevel(c, r)) 118 return crash, checkAccessLevel(c, r, bugLevel) 119} 120 121func checkJobTextAccess(c context.Context, r *http.Request, field string, id int64) error { 122 keys, err := datastore.NewQuery("Job"). 123 Filter(field+"=", id). 124 KeysOnly(). 125 GetAll(c, nil) 126 if err != nil { 127 return fmt.Errorf("failed to query jobs: %v", err) 128 } 129 if len(keys) != 1 { 130 return fmt.Errorf("checkJobTextAccess: found %v jobs for %v=%v", 131 len(keys), field, id) 132 } 133 bug := new(Bug) 134 if err := datastore.Get(c, keys[0].Parent(), bug); err != nil { 135 return fmt.Errorf("failed to get bug: %v", err) 136 } 137 bugLevel := bug.sanitizeAccess(accessLevel(c, r)) 138 return checkAccessLevel(c, r, bugLevel) 139} 140 141func (bug *Bug) sanitizeAccess(currentLevel AccessLevel) AccessLevel { 142 for ri := len(bug.Reporting) - 1; ri >= 0; ri-- { 143 bugReporting := &bug.Reporting[ri] 144 if ri == 0 || !bugReporting.Reported.IsZero() { 145 ns := config.Namespaces[bug.Namespace] 146 bugLevel := ns.ReportingByName(bugReporting.Name).AccessLevel 147 if currentLevel < bugLevel { 148 if bug.Status == BugStatusFixed || len(bug.Commits) != 0 { 149 // Fixed bugs are visible in all reportings, 150 // however, without previous reporting private information. 151 lastLevel := ns.Reporting[len(ns.Reporting)-1].AccessLevel 152 if currentLevel >= lastLevel { 153 bugLevel = lastLevel 154 sanitizeReporting(bug) 155 } 156 } 157 } 158 return bugLevel 159 } 160 } 161 panic("unreachable") 162} 163 164func sanitizeReporting(bug *Bug) { 165 bug.DupOf = "" 166 for ri := range bug.Reporting { 167 bugReporting := &bug.Reporting[ri] 168 bugReporting.ID = "" 169 bugReporting.ExtID = "" 170 bugReporting.Link = "" 171 } 172} 173