@@ -3,10 +3,14 @@ package index
33import (
44 "bytes"
55 "crypto"
6- "github.com/go-git/go-git/v5/plumbing/hash"
7- "github.com/go-git/go-git/v5/utils/binary"
86 "io"
97 "testing"
8+ "time"
9+
10+ "github.com/go-git/go-git/v5/plumbing/hash"
11+ "github.com/go-git/go-git/v5/utils/binary"
12+ "github.com/stretchr/testify/assert"
13+ "github.com/stretchr/testify/require"
1014
1115 "github.com/go-git/go-git/v5/plumbing"
1216 "github.com/go-git/go-git/v5/plumbing/filemode"
@@ -87,7 +91,6 @@ func (s *IndexSuite) TestDecodeCacheTree(c *C) {
8791 c .Assert (idx .Cache .Entries [i ].Trees , Equals , expected .Trees )
8892 c .Assert (idx .Cache .Entries [i ].Hash .String (), Equals , expected .Hash .String ())
8993 }
90-
9194}
9295
9396var expectedEntries = []TreeEntry {
@@ -133,7 +136,6 @@ func (s *IndexSuite) TestDecodeMergeConflict(c *C) {
133136 c .Assert (e .Hash .String (), Equals , expected [i ].Hash )
134137 c .Assert (e .Name , Equals , "go/example.go" )
135138 }
136-
137139}
138140
139141func (s * IndexSuite ) TestDecodeExtendedV3 (c * C ) {
@@ -320,3 +322,110 @@ func (s *IndexSuite) TestDecodeInvalidHash(c *C) {
320322 err = d .Decode (idx )
321323 c .Assert (err , ErrorMatches , ErrInvalidChecksum .Error ())
322324}
325+
326+ func TestDecodeV4StripLength (t * testing.T ) {
327+ t .Parallel ()
328+
329+ tests := []struct {
330+ name string
331+ stripLen byte
332+ // firstEntry selects which entry's strip length to corrupt.
333+ // When true the first entry's varint is patched (lastEntry == nil path).
334+ // When false the second entry's varint is patched (lastEntry != nil path).
335+ firstEntry bool
336+ wantErr error
337+ }{
338+ {
339+ name : "SHA1: strip length equals name length" ,
340+ stripLen : 3 , // len("abc") — strips entire name, valid
341+ },
342+ {
343+ name : "SHA1: strip length exceeds name length by one" ,
344+ stripLen : 4 , // len("abc")+1 — one past the end
345+ wantErr : ErrMalformedIndexFile ,
346+ },
347+ {
348+ name : "SHA1: strip length far exceeds name length" ,
349+ stripLen : 100 ,
350+ wantErr : ErrMalformedIndexFile ,
351+ },
352+ {
353+ name : "SHA1: non-zero strip length on first entry" ,
354+ stripLen : 1 ,
355+ firstEntry : true ,
356+ wantErr : ErrMalformedIndexFile ,
357+ },
358+ }
359+
360+ for _ , tc := range tests {
361+ t .Run (tc .name , func (t * testing.T ) {
362+ t .Parallel ()
363+
364+ var h1 , h2 plumbing.Hash
365+ idx := & Index {
366+ Version : 4 ,
367+ Entries : []* Entry {
368+ {
369+ CreatedAt : time .Now (),
370+ ModifiedAt : time .Now (),
371+ Name : "abc" ,
372+ Hash : h1 ,
373+ Size : 1 ,
374+ },
375+ {
376+ CreatedAt : time .Now (),
377+ ModifiedAt : time .Now (),
378+ Name : "abd" ,
379+ Hash : h2 ,
380+ Size : 2 ,
381+ },
382+ },
383+ }
384+
385+ buf := bytes .NewBuffer (nil )
386+ enc := NewEncoder (buf )
387+ err := enc .Encode (idx )
388+ require .NoError (t , err )
389+
390+ raw := buf .Bytes ()
391+
392+ hashSize := crypto .SHA1 .Size ()
393+ h := crypto .SHA1 .New ()
394+
395+ // In a V4 index, entries are sorted and prefix-compressed. After the
396+ // 12-byte header (DIRC + version + count), entry 1 ("abc") is:
397+ // 40 (10×uint32) + hashSize (hash) + 2 (flags) + 1 (varint 0) + 4 ("abc\x00")
398+ // Entry 2 starts after entry 1. Its fixed header is 40 + hashSize + 2 bytes,
399+ // followed by the strip length varint.
400+ entryFixedLen := 40 + hashSize + 2
401+ entry1StripOffset := 12 + entryFixedLen
402+ entry1Len := entryFixedLen + 1 + 4 // + varint(0) + "abc\x00"
403+ entry2StripOffset := 12 + entry1Len + entryFixedLen
404+
405+ var stripLenOffset int
406+ if tc .firstEntry {
407+ stripLenOffset = entry1StripOffset
408+ } else {
409+ stripLenOffset = entry2StripOffset
410+ }
411+ require .Less (t , stripLenOffset , len (raw )- hashSize , "strip length offset must be within data" )
412+
413+ // Variable-width int encoding: a single byte with value < 128 encodes directly.
414+ raw [stripLenOffset ] = tc .stripLen
415+
416+ // Recompute the checksum over the modified content.
417+ h .Reset ()
418+ h .Write (raw [:len (raw )- hashSize ])
419+ copy (raw [len (raw )- hashSize :], h .Sum (nil ))
420+
421+ dec := NewDecoder (bytes .NewReader (raw ))
422+ out := & Index {}
423+ err = dec .Decode (out )
424+ if tc .wantErr != nil {
425+ assert .ErrorIs (t , err , tc .wantErr )
426+ } else {
427+ assert .NoError (t , err )
428+ }
429+ })
430+ }
431+ }
0 commit comments