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.
|
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 | } |