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