From a0799484c8759a585c5d8ab2665277c6a928d0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 28 Feb 2025 17:02:19 +0100 Subject: [PATCH 1/3] Always close the blob we are reading from a registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit == the HTTP response body. Signed-off-by: Miloslav Trmač --- cmd/skopeo/layers.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index 9bcf2f55..b93d4a83 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -151,10 +151,12 @@ func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) { }, opts.retryOpts); err != nil { return err } - if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil { - if closeErr := r.Close(); closeErr != nil { - return fmt.Errorf("%w (close error: %v)", err, closeErr) + defer func() { + if err := r.Close(); err != nil { + retErr = noteCloseFailure(retErr, fmt.Sprintf("closing blob %q", bd.digest.String()), err) } + }() + if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil { return err } } From 9fda7e7304fc69e33b6d701b30f0de8135292c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 28 Feb 2025 17:08:55 +0100 Subject: [PATCH 2/3] In (skopeo layers), validate the blob against the expected digest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- cmd/skopeo/layers.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index b93d4a83..f310a033 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -156,9 +156,17 @@ func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) { retErr = noteCloseFailure(retErr, fmt.Sprintf("closing blob %q", bd.digest.String()), err) } }() - if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil { + verifier := bd.digest.Verifier() + tr := io.TeeReader(r, verifier) + if _, err := dest.PutBlob(ctx, tr, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil { return err } + if _, err := io.Copy(io.Discard, tr); err != nil { // Ensure we process all of tr, so that we can validate the digest. + return err + } + if !verifier.Verified() { + return fmt.Errorf("corrupt blob %q", bd.digest.String()) + } } var manifest []byte From 9c0f31dccec4caa31474eed8c5fa2d688b06ae6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Trma=C4=8D?= Date: Fri, 28 Feb 2025 17:16:52 +0100 Subject: [PATCH 3/3] In (skopeo inspect), validate the manifest against a digest reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miloslav Trmač --- cmd/skopeo/inspect.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/skopeo/inspect.go b/cmd/skopeo/inspect.go index e969caa4..4ca03ac1 100644 --- a/cmd/skopeo/inspect.go +++ b/cmd/skopeo/inspect.go @@ -106,8 +106,9 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error) } }() + unparsedInstance := image.UnparsedInstance(src, nil) if err := retry.IfNecessary(ctx, func() error { - rawManifest, _, err = src.GetManifest(ctx, nil) + rawManifest, _, err = unparsedInstance.Manifest(ctx) return err }, opts.retryOpts); err != nil { return fmt.Errorf("Error retrieving manifest for image: %w", err) @@ -122,7 +123,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error) return nil } - img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, nil)) + img, err := image.FromUnparsedImage(ctx, sys, unparsedInstance) if err != nil { return fmt.Errorf("Error parsing manifest for image: %w", err) }