Skip to content

Commit cb6d75b

Browse files
authored
fix: check the Docker-Content-Digest header in Repository.Blobs().Fetch() (#897)
1. Verify the `Docker-Content-Digest header` against the descriptor digest in `Repository.Blobs().Fetch()` 2. Add corresponding unit tests Fixes: #895 Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
1 parent 8d44f23 commit cb6d75b

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

registry/remote/repository.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,10 +744,13 @@ func (s *blobStore) Fetch(ctx context.Context, target ocispec.Descriptor) (rc io
744744
}()
745745

746746
switch resp.StatusCode {
747-
case http.StatusOK: // server does not support seek as `Range` was ignored.
747+
case http.StatusOK:
748748
if size := resp.ContentLength; size != -1 && size != target.Size {
749749
return nil, fmt.Errorf("%s %q: mismatch Content-Length", resp.Request.Method, resp.Request.URL)
750750
}
751+
if err := verifyContentDigest(resp, target.Digest); err != nil {
752+
return nil, err
753+
}
751754

752755
// check server range request capability.
753756
// Docker spec allows range header form of "Range: bytes=<start>-<end>".

registry/remote/repository_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2747,6 +2747,92 @@ func Test_BlobStore_Fetch_ZeroSizedBlob(t *testing.T) {
27472747
}
27482748
}
27492749

2750+
func Test_BlobStore_Fetch_BadResponse(t *testing.T) {
2751+
blob := []byte("hello world")
2752+
blobDesc := ocispec.Descriptor{
2753+
MediaType: "test",
2754+
Digest: digest.FromBytes(blob),
2755+
Size: int64(len(blob)),
2756+
}
2757+
2758+
t.Run("Docker-Content-Digest header mismatches", func(t *testing.T) {
2759+
randomDgst := digest.FromBytes([]byte("random"))
2760+
2761+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2762+
if r.Method != http.MethodGet {
2763+
t.Errorf("unexpected access: %s %s", r.Method, r.URL)
2764+
w.WriteHeader(http.StatusMethodNotAllowed)
2765+
return
2766+
}
2767+
switch r.URL.Path {
2768+
case "/v2/test/blobs/" + blobDesc.Digest.String():
2769+
w.Header().Set("Content-Type", "application/octet-stream")
2770+
w.Header().Set("Docker-Content-Digest", randomDgst.String())
2771+
if _, err := w.Write(blob); err != nil {
2772+
t.Errorf("failed to write %q: %v", r.URL, err)
2773+
}
2774+
default:
2775+
w.WriteHeader(http.StatusNotFound)
2776+
}
2777+
}))
2778+
defer ts.Close()
2779+
uri, err := url.Parse(ts.URL)
2780+
if err != nil {
2781+
t.Fatalf("invalid test http server: %v", err)
2782+
}
2783+
2784+
repo, err := NewRepository(uri.Host + "/test")
2785+
if err != nil {
2786+
t.Fatalf("NewRepository() error = %v", err)
2787+
}
2788+
repo.PlainHTTP = true
2789+
store := repo.Blobs()
2790+
ctx := context.Background()
2791+
2792+
if _, err = store.Fetch(ctx, blobDesc); err == nil {
2793+
t.Error("Blobs.Fetch() error = nil, want error")
2794+
}
2795+
})
2796+
2797+
t.Run("Content-Length header mismatches", func(t *testing.T) {
2798+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2799+
if r.Method != http.MethodGet {
2800+
t.Errorf("unexpected access: %s %s", r.Method, r.URL)
2801+
w.WriteHeader(http.StatusMethodNotAllowed)
2802+
return
2803+
}
2804+
switch r.URL.Path {
2805+
case "/v2/test/blobs/" + blobDesc.Digest.String():
2806+
w.Header().Set("Content-Type", "application/octet-stream")
2807+
w.Header().Set("Docker-Content-Digest", blobDesc.Digest.String())
2808+
w.Header().Set("Content-Length", strconv.FormatInt(blobDesc.Size+1, 10))
2809+
if _, err := w.Write(blob); err != nil {
2810+
t.Errorf("failed to write %q: %v", r.URL, err)
2811+
}
2812+
default:
2813+
w.WriteHeader(http.StatusNotFound)
2814+
}
2815+
}))
2816+
defer ts.Close()
2817+
uri, err := url.Parse(ts.URL)
2818+
if err != nil {
2819+
t.Fatalf("invalid test http server: %v", err)
2820+
}
2821+
2822+
repo, err := NewRepository(uri.Host + "/test")
2823+
if err != nil {
2824+
t.Fatalf("NewRepository() error = %v", err)
2825+
}
2826+
repo.PlainHTTP = true
2827+
store := repo.Blobs()
2828+
ctx := context.Background()
2829+
2830+
if _, err = store.Fetch(ctx, blobDesc); err == nil {
2831+
t.Error("Blobs.Fetch() error = nil, want error")
2832+
}
2833+
})
2834+
}
2835+
27502836
func Test_BlobStore_Push(t *testing.T) {
27512837
blob := []byte("hello world")
27522838
blobDesc := ocispec.Descriptor{

0 commit comments

Comments
 (0)