Skip to content

Commit d202835

Browse files
authored
Merge pull request #119 from sueun-dev/fix-digestset-ambiguous-crossalg
digestset: detect ambiguous short codes across algorithms
2 parents e5208fd + 0f61dbb commit d202835

2 files changed

Lines changed: 50 additions & 8 deletions

File tree

digestset/set.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,30 @@ func (dst *Set) Lookup(d string) (digest.Digest, error) {
109109
}
110110
}
111111
idx := sort.Search(len(dst.entries), searchFunc)
112-
if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
113-
return "", ErrDigestNotFound
114-
}
115-
if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
116-
return dst.entries[idx].digest, nil
112+
113+
// Entries whose value has hex as a prefix form a contiguous run starting
114+
// at idx. Digests of a different algorithm may sort within that run, so a
115+
// second matching entry is not necessarily adjacent to the first; scan the
116+
// whole run instead of only inspecting idx and idx+1.
117+
var match *digestEntry
118+
for i := idx; i < len(dst.entries) && strings.HasPrefix(dst.entries[i].val, hex); i++ {
119+
if !checkShortMatch(dst.entries[i].alg, dst.entries[i].val, string(alg), hex) {
120+
continue
121+
}
122+
if dst.entries[i].alg == alg && dst.entries[i].val == hex {
123+
// An exact whole-value match is unambiguous.
124+
return dst.entries[i].digest, nil
125+
}
126+
if match != nil {
127+
return "", ErrDigestAmbiguous
128+
}
129+
match = dst.entries[i]
117130
}
118-
if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
119-
return "", ErrDigestAmbiguous
131+
if match == nil {
132+
return "", ErrDigestNotFound
120133
}
121134

122-
return dst.entries[idx].digest, nil
135+
return match.digest, nil
123136
}
124137

125138
// Add adds the given digest to the set. An error will be returned

digestset/set_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"crypto/sha256"
2121
"encoding/binary"
2222
"math/rand"
23+
"strings"
2324
"testing"
2425

2526
"github.com/opencontainers/go-digest"
@@ -391,3 +392,31 @@ func BenchmarkShortCode100(b *testing.B) {
391392
func BenchmarkShortCode1000(b *testing.B) {
392393
benchShortCodeNTable(b, 1000, 12)
393394
}
395+
396+
// TestLookupAmbiguousAcrossAlgorithms verifies that an algorithm-scoped short
397+
// code is reported as ambiguous even when a digest of a different algorithm
398+
// sorts between the two matching entries. The two sha256 digests below share
399+
// the prefix "1234", so "sha256:1234" is ambiguous (see TestLookup). The sha512
400+
// digest's value also starts with "1234" and sorts lexically between them.
401+
func TestLookupAmbiguousAcrossAlgorithms(t *testing.T) {
402+
sha256A := digest.Digest("sha256:1234" + strings.Repeat("0", 60))
403+
sha256C := digest.Digest("sha256:1234" + strings.Repeat("1", 60))
404+
sha512B := digest.Digest("sha512:12340" + strings.Repeat("5", 123))
405+
406+
dset := NewSet()
407+
for _, d := range []digest.Digest{sha256A, sha512B, sha256C} {
408+
if err := dset.Add(d); err != nil {
409+
t.Fatal(err)
410+
}
411+
}
412+
413+
if _, err := dset.Lookup("sha256:1234"); err != ErrDigestAmbiguous {
414+
t.Fatalf("Lookup(%q) = %v; want ErrDigestAmbiguous", "sha256:1234", err)
415+
}
416+
417+
// A different-algorithm entry that sorts first in the prefix run must not
418+
// hide the matching entries either.
419+
if _, err := dset.Lookup("sha512:12340"); err != nil {
420+
t.Fatalf("Lookup(%q) = %v; want the sha512 digest", "sha512:12340", err)
421+
}
422+
}

0 commit comments

Comments
 (0)