• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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