Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Blame · core/lfs/fuzz_test.go

Line attribution on main · 136 line(s)

16bf0ebf
Keenan Nemetz
10d ago
1 package lfs
2
3 import (
4 "bytes"
5 "fmt"
6 "strings"
7 "testing"
8 )
9
10 // frame writes one text packet the way a client does, so the seeds below are the
11 // shapes a real pusher sends rather than plausible-looking bytes.
12 func frame(payload string) string {
13 return fmt.Sprintf("%04x%s", len(payload)+4, payload)
14 }
15
16 // FuzzPktReader drives the reader that turns a stream from a pusher into packet
17 // lengths and payloads. A length that is believed when it should not be is how a
18 // stream parser is turned into an allocation or a desynchronised connection.
19 //
20 // Properties:
21 //
22 // - It cannot return more bytes than it was given, so no packet invents data.
23 // - A control packet carries no payload.
24 // - What it reads survives being written again. The two sides of this protocol
25 // are this same file, and a round trip that disagrees means a client and the
26 // server would disagree about where a packet ends.
27 func FuzzPktReader(f *testing.F) {
28 for _, seed := range []string{
29 frame("version https://git-lfs/1.0\n") + "0000",
30 frame("batch\n") + "0001" + frame("upload\n") + "0000",
31 frame("put\n") + "0001" + frame("123\n") + "0000",
32 frame("a") + frame("b") + "0000",
33 "",
34 "0000",
35 "0001",
36 "0002",
37 "0003",
38 "0004",
39 "0005a",
40 "zzzz",
41 "FFFF",
42 "ffff",
43 "ffffshort",
44 "0005a0000",
45 frame("x\n") + "0001" + "0000",
46 } {
47 f.Add(seed)
48 }
49 f.Fuzz(func(t *testing.T, stream string) {
50 pr := newPktReader(strings.NewReader(stream))
51 var lines []string
52 read := 0
53 for {
54 data, kind, err := pr.next()
55 if err != nil {
56 // Either the input ended between packets or it was refused.
57 break
58 }
59 if kind != -1 {
60 if data != nil {
61 t.Fatalf("control packet %d carried %d bytes", kind, len(data))
62 }
63 continue
64 }
65 read += len(data)
66 lines = append(lines, strings.TrimRight(string(data), "\n"))
67 }
68 if read > len(stream) {
69 t.Fatalf("read %d bytes of payload from a %d byte stream", read, len(stream))
70 }
71 if len(lines) == 0 {
72 return
73 }
74 // Write the lines back out and read them again. pkt appends the newline the
75 // framing counts, which is why the comparison is on the trimmed line.
76 var buf bytes.Buffer
77 pw := newPktWriter(&buf)
78 for _, line := range lines {
79 if err := pw.pkt(line); err != nil {
80 // An oversized line cannot be written back; the reader refuses it too.
81 return
82 }
83 }
84 if err := pw.flush(); err != nil {
85 t.Fatalf("flush: %v", err)
86 }
87 again := newPktReader(bytes.NewReader(buf.Bytes()))
88 for i, want := range lines {
89 data, kind, err := again.next()
90 if err != nil || kind != -1 {
91 t.Fatalf("line %d (%q) did not survive re-encoding: kind %d, err %v", i, want, kind, err)
92 }
93 if got := strings.TrimRight(string(data), "\n"); got != want {
94 t.Fatalf("line %d re-encoded as %q, want %q", i, got, want)
95 }
96 }
97 })
98 }
99
100 // FuzzReadBatch drives the two loops above the packet reader: the arguments up to
101 // a delimiter, then the object lines up to a flush. These are the loops that a
102 // stream can hold open, so the property is that each one terminates on the input
103 // it was given and returns no more than it contains.
104 func FuzzReadBatch(f *testing.F) {
105 for _, seed := range []string{
106 frame("batch\n") + "0001" + frame("upload\n") + frame("oid size\n") + "0000",
107 frame("batch\n") + "0000",
108 "0001" + "0000",
109 "0001",
110 frame("a\n") + "0001" + frame("b\n") + frame("c\n") + "0000",
111 "",
112 "0000",
113 "zzzz",
114 "0005a" + "0000",
115 } {
116 f.Add(seed)
117 }
118 f.Fuzz(func(t *testing.T, stream string) {
119 args, lines, err := newPktReader(strings.NewReader(stream)).readBatch()
120 if err != nil {
121 return
122 }
123 returned := 0
124 for _, list := range [][]string{args, lines} {
125 for _, line := range list {
126 returned += len(line)
127 if strings.HasSuffix(line, "\n") {
128 t.Fatalf("readBatch returned %q with its newline still attached", line)
129 }
130 }
131 }
132 if returned > len(stream) {
133 t.Fatalf("readBatch returned %d bytes from a %d byte stream", returned, len(stream))
134 }
135 })
136 }