1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6 "bytes"
7 "errors"
8 "strings"
9 "testing"
10)
11
12func okOrCorrupt(t *testing.T, err error) {
13 t.Helper()
14 if err != nil && !errors.Is(err, ErrCorrupt) && !errors.Is(err, ErrTooLarge) && !errors.Is(err, ErrUnsupported) {
15 t.Fatalf("error is not a typed one: %v", err)
16 }
17}
18
19func FuzzPackEntryHeader(f *testing.F) {
20 pack := func(body ...byte) []byte {
21 d := append([]byte("PACK\x00\x00\x00\x02\x00\x00\x00\x01"), body...)
22 return append(d, make([]byte, 20)...)
23 }
24 f.Add(pack(0x35, 0x78, 0x9c, 0x01), uint64(12))
25 f.Add(pack(0xb4, 0x23, 0x78), uint64(12))
26 f.Add(pack(0x65, 0x05, 0x78), uint64(12))
27 f.Add(pack(0x65, 0xff, 0xff, 0xff, 0x7f, 0x78), uint64(12))
28 f.Add(append(pack(0x75), bytes.Repeat([]byte{7}, 25)...), uint64(12))
29 f.Add(pack(bytes.Repeat([]byte{0xff}, 30)...), uint64(12))
30 f.Add([]byte{}, uint64(0))
31 f.Fuzz(func(t *testing.T, data []byte, off uint64) {
32 h, err := parseEntryHeader(data, off)
33 okOrCorrupt(t, err)
34 if err == nil {
35 if h.data > uint64(len(data)) || (h.typ == objOfsDelta && h.baseOff >= off) {
36 t.Fatalf("inconsistent header %+v for %d bytes at %d", h, len(data), off)
37 }
38 }
39 })
40}
41
42func FuzzDeltaApply(f *testing.F) {
43 base := []byte("hello, delta world")
44 good := append(varint(uint64(len(base))), varint(12)...)
45 good = append(good, copyOp(0, 5)...)
46 good = append(good, 3, ' ', 'G', 'o')
47 good = append(good, copyOp(11, 4)...)
48 f.Add(base, good)
49 f.Add(base, []byte{})
50 f.Add(base, []byte{0x12, 0x05, 0x80})
51 f.Add([]byte{}, []byte{0x00, 0x00})
52 f.Add(base, append(varint(uint64(len(base))), 0xff, 0xff, 0xff, 0xff, 0x0f, 0x90, 0x01))
53 f.Add(base, append(append(varint(uint64(len(base))), varint(1<<20)...), 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff))
54 f.Fuzz(func(t *testing.T, base, delta []byte) {
55 out, err := patch(base, delta, 1<<20)
56 okOrCorrupt(t, err)
57 if err == nil {
58 _, rs, _, herr := deltaHeader(delta)
59 if herr != nil || uint64(len(out)) != rs {
60 t.Fatalf("result of %d bytes, header says %d (%v)", len(out), rs, herr)
61 }
62 }
63 })
64}
65
66func FuzzParseTree(f *testing.F) {
67 f.Add(append([]byte("100644 a\x00"), bytes.Repeat([]byte{1}, 20)...))
68 f.Add(append(append([]byte("40000 dir\x00"), bytes.Repeat([]byte{2}, 20)...), append([]byte("160000 sub\x00"), bytes.Repeat([]byte{3}, 20)...)...))
69 f.Add([]byte("100644 \x00"))
70 f.Add([]byte("100644 name"))
71 f.Add([]byte{})
72 f.Add(bytes.Repeat([]byte("1 \x00"), 30))
73 f.Fuzz(func(t *testing.T, data []byte) {
74 n := 0
75 err := ParseTree(data, func(e Entry) bool {
76 n++
77 if len(e.Name) == 0 {
78 t.Fatal("empty name accepted")
79 }
80 return true
81 })
82 okOrCorrupt(t, err)
83 if err == nil && n == 0 && len(data) != 0 {
84 t.Fatal("non-empty tree without entries")
85 }
86 })
87}
88
89func FuzzParseCommit(f *testing.F) {
90 tree, par := strings.Repeat("a", 40), strings.Repeat("b", 40)
91 f.Add([]byte("tree " + tree + "\nparent " + par + "\nauthor A <a@x> 1700000000 +0100\ncommitter B <b@x> 1700000001 -0830\n\nmessage\n"))
92 f.Add([]byte("tree " + tree + "\ngpgsig -----BEGIN-----\n \n abc\n -----END-----\nencoding latin1\n\n\xe9\xff"))
93 f.Add([]byte("tree " + tree))
94 f.Add([]byte("tree " + tree + "\nauthor <> 99999999999999999999999 +9999\n\n"))
95 f.Add([]byte("tree\n\n"))
96 f.Add([]byte("tree " + tree + "\ntree " + par + "\nparent " + par + "\n\n"))
97 f.Add([]byte("tree " + tree + "\nparent " + par + "\nauthor A <a@x> 1 +0000\nparent " + tree + "\n\nm"))
98 f.Add([]byte("parent " + par + "\ntree " + tree + "\n\n"))
99 f.Add([]byte{})
100 f.Add([]byte("\n\n\n"))
101 var c Commit
102 f.Fuzz(func(t *testing.T, data []byte) {
103 err := ParseCommit(data, &c)
104 okOrCorrupt(t, err)
105 if err == nil && len(c.Message) > len(data) {
106 t.Fatal("message longer than input")
107 }
108 if err == nil && !bytes.HasPrefix(data, []byte("tree ")) {
109 t.Fatal("accepted a commit whose first header is not the tree")
110 }
111 if err == nil && len(c.Parents) > 0 {
112 // The parents are the lines that follow the tree directly.
113 rest := data[bytes.IndexByte(data, '\n')+1:]
114 for _, p := range c.Parents {
115 want := append([]byte("parent "), []byte(p.String())...)
116 if len(rest) < len(want) || !bytes.EqualFold(rest[:len(want)], want) {
117 t.Fatalf("parent %s is not on the line after the tree", p)
118 }
119 rest = rest[bytes.IndexByte(rest, '\n')+1:]
120 }
121 }
122 var tg Tag
123 okOrCorrupt(t, ParseTag(data, &tg))
124 _ = parseSignature(data)
125 })
126}
127
128func FuzzIdxLookup(f *testing.F) {
129 var offs []uint64
130 b1, b2 := []byte("one"), []byte("two")
131 _, idx := buildPack([][]byte{entryBytes(3, 3, nil, b1), entryBytes(3, 3, nil, b2)},
132 []ObjectID{objectIDOf("blob", b1), objectIDOf("blob", b2)}, &offs)
133 id := objectIDOf("blob", b1)
134 f.Add(idx, id[:])
135 f.Add(idx[:len(idx)-1], id[:])
136 f.Add(idx[:1100], id[:])
137 f.Add([]byte{}, id[:])
138 big := bytes.Clone(idx)
139 big[len(big)-41] |= 0x80
140 f.Add(big, id[:])
141 f.Fuzz(func(t *testing.T, data, key []byte) {
142 x, err := parseIdx(data)
143 if err != nil {
144 okOrCorrupt(t, err)
145 return
146 }
147 var want ObjectID
148 copy(want[:], key)
149 if pos, ok := x.find(&want); ok {
150 if pos >= x.n || !bytes.Equal(x.id(pos), want[:]) {
151 t.Fatalf("find returned position %d for %s", pos, want)
152 }
153 _, err := x.offset(pos)
154 okOrCorrupt(t, err)
155 }
156 if len(key) > 0 {
157 nib := min(len(key)*2, 40)
158 x.prefixMatches(key, nib, func(id ObjectID) bool {
159 if !hasNibblePrefix(id[:], key, nib) {
160 t.Fatalf("prefixMatches yielded %s for %x/%d", id, key, nib)
161 }
162 return true
163 })
164 }
165 for i := uint32(0); i < min(x.n, 8); i++ {
166 _, err := x.offset(i)
167 okOrCorrupt(t, err)
168 }
169 })
170}
171
172func FuzzPackedRefsAndConfig(f *testing.F) {
173 a := strings.Repeat("a", 40)
174 f.Add("# pack-refs with: peeled fully-peeled sorted \n" + a + " refs/heads/main\n" + a + " refs/tags/v1\n^" + a + "\n")
175 f.Add("^" + a)
176 f.Add("[core]\n\trepositoryformatversion = 1\n[extensions]\n\tobjectformat = sha1\n")
177 f.Add("[")
178 f.Add("ref: refs/heads/main\n")
179 f.Fuzz(func(t *testing.T, s string) {
180 _, err := parsePackedRefs(s)
181 okOrCorrupt(t, err)
182 okOrCorrupt(t, checkConfigData([]byte(s)))
183 _, err = parseRefContent([]byte(s))
184 okOrCorrupt(t, err)
185 _ = validRefName(s)
186 })
187}