aboutsummaryrefslogtreecommitdiff
path: root/fuzz_test.go
blob: 866863dd8474037772560a416dab075bdd89a1e2 (plain) (blame)
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
package main

import (
	"path/filepath"
	"slices"
	"strings"
	"testing"
	"time"
)

// The parsers that are handed bytes from outside: an id token, a claim, a URL
// path, and a secret typed into a form. Each target states the property the
// caller relies on, so the seed corpus runs with the rest of the tests and a
// longer search is one flag away.

// splitJWT takes a token apart before anything is known about it, and
// verifySignature is then handed whatever came out.
func FuzzSplitJWT(f *testing.F) {
	f.Add("aGVhZGVy.cGF5bG9hZA.c2ln")
	f.Add("...")
	f.Add("")
	f.Fuzz(func(t *testing.T, token string) {
		signed, sig, kid, alg, _, err := splitJWT(token)
		if err != nil {
			return
		}
		if verifySignature(jwk{Kty: "RSA", N: kid, E: "AQAB"}, alg, signed, sig) == nil {
			t.Fatalf("a made up rsa key verified %q", token)
		}
		if verifySignature(jwk{Kty: "EC", Crv: "P-256", X: kid, Y: kid}, alg, signed, sig) == nil {
			t.Fatalf("a made up ec key verified %q", token)
		}
	})
}

// The name becomes a key in repos.json and a component of a URL, out of a
// claim from somewhere else.
func FuzzSanitizeName(f *testing.F) {
	f.Add("Carol")
	f.Add("../../config")
	f.Fuzz(func(t *testing.T, in string) {
		if out := sanitizeName(in); out != "" && !localNameRE.MatchString(out) {
			t.Fatalf("sanitizeName(%q) = %q, which is not a usable account name", in, out)
		}
	})
}

// Whatever this lets through is handed to both CGIs in an environment
// variable.
func FuzzServablePath(f *testing.F) {
	f.Add("/notes/log/")
	f.Add("/a/../b")
	f.Fuzz(func(t *testing.T, p string) {
		if !servablePath(p) {
			return
		}
		if hasControlByte(p) {
			t.Fatalf("servablePath(%q) accepted a control byte", p)
		}
		parts := strings.Split(strings.TrimPrefix(p, "/"), "/")
		for i, part := range parts {
			if part == "." || part == ".." || (part == "" && i < len(parts)-1) {
				t.Fatalf("servablePath(%q) accepted a %q component", p, part)
			}
		}
	})
}

// Whatever this lets through is joined onto the repos directory, for the
// repository and for both of its siblings.
func FuzzValidRepoPath(f *testing.F) {
	f.Add("infra/dns")
	f.Add("../etc")
	f.Fuzz(func(t *testing.T, p string) {
		if !validRepoPath(p) {
			return
		}
		for _, suffix := range []string{".git", ".issues.git", ".wiki.git"} {
			joined := filepath.Join("/repos", p+suffix)
			if joined != filepath.Clean(joined) || !strings.HasPrefix(joined, "/repos/") {
				t.Fatalf("validRepoPath(%q) accepted a path that lands on %q", p, joined)
			}
			if strings.Count(strings.TrimPrefix(joined, "/repos/"), "/") > 1 {
				t.Fatalf("validRepoPath(%q) accepted more than one section deep", p)
			}
		}
	})
}

// A page listed in the index has to open the file it was listed from. A push
// can put any name in the tree.
func FuzzWikiNameRoundTrip(f *testing.F) {
	f.Add("docs/index.md")
	f.Add("myindex.md")
	f.Add(".md")
	f.Fuzz(func(t *testing.T, file string) {
		page := wikiPage(file)
		files, ok := wikiFiles(page)
		if !ok || !slices.Contains(files, file) {
			return // not listed, so never linked to
		}
		if wikiPage(files[0]) != page {
			t.Fatalf("%q is listed as %q, which reads back as %q", file, page, wikiPage(files[0]))
		}
	})
}

// The secret being enrolled arrives in a form, so this decodes whatever was
// typed.
func FuzzTOTPSecret(f *testing.F) {
	f.Add("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ")
	f.Add("!!!")
	f.Fuzz(func(t *testing.T, secret string) {
		code := totpCode(secret, time.Unix(1234567890, 0))
		if code != "" && len(code) != totpDigits {
			t.Fatalf("totpCode(%q) = %q", secret, code)
		}
		if code == "" && totpStep(secret, "", time.Unix(1234567890, 0)) >= 0 {
			t.Fatalf("an unreadable secret accepted an empty code")
		}
	})
}