diff --git a/SPECS/skopeo/CVE-2024-3727.patch b/SPECS/skopeo/CVE-2024-3727.patch new file mode 100644 index 0000000000..026304b431 --- /dev/null +++ b/SPECS/skopeo/CVE-2024-3727.patch @@ -0,0 +1,851 @@ +From 28694484fdc289dd6dcf3135fdeebf849cf2acf1 Mon Sep 17 00:00:00 2001 +From: Rohit Rawat +Date: Thu, 20 Jun 2024 10:02:30 +0000 +Subject: [PATCH] Fix CVE-2024-3727 in github.com/containers/image + +--- + .../containers/image/v5/copy/progress_bars.go | 7 +++- + .../containers/image/v5/copy/single.go | 39 ++++++++++++++----- + .../image/v5/directory/directory_dest.go | 22 +++++++++-- + .../image/v5/directory/directory_src.go | 17 ++++++-- + .../image/v5/directory/directory_transport.go | 25 ++++++++---- + .../image/v5/docker/docker_client.go | 20 ++++++++-- + .../image/v5/docker/docker_image.go | 7 +++- + .../image/v5/docker/docker_image_dest.go | 22 +++++++++-- + .../image/v5/docker/docker_image_src.go | 18 ++++++++- + .../image/v5/docker/internal/tarfile/dest.go | 12 +++++- + .../v5/docker/internal/tarfile/writer.go | 34 ++++++++++++---- + .../image/v5/docker/registries_d.go | 7 +++- + .../image/v5/openshift/openshift_src.go | 3 ++ + .../containers/image/v5/ostree/ostree_dest.go | 10 +++++ + .../containers/image/v5/ostree/ostree_src.go | 4 +- + .../image/v5/storage/storage_dest.go | 32 ++++++++++----- + .../image/v5/storage/storage_image.go | 14 +++++-- + .../image/v5/storage/storage_reference.go | 10 ++++- + .../image/v5/storage/storage_src.go | 19 +++++++-- + 19 files changed, 254 insertions(+), 68 deletions(-) + +diff --git a/vendor/github.com/containers/image/v5/copy/progress_bars.go b/vendor/github.com/containers/image/v5/copy/progress_bars.go +index ce07823..ba6a273 100644 +--- a/vendor/github.com/containers/image/v5/copy/progress_bars.go ++++ b/vendor/github.com/containers/image/v5/copy/progress_bars.go +@@ -48,10 +48,13 @@ type progressBar struct { + // As a convention, most users of progress bars should call mark100PercentComplete on full success; + // by convention, we don't leave progress bars in partial state when fully done + // (even if we copied much less data than anticipated). +-func (c *copier) createProgressBar(pool *mpb.Progress, partial bool, info types.BlobInfo, kind string, onComplete string) *progressBar { ++func (c *copier) createProgressBar(pool *mpb.Progress, partial bool, info types.BlobInfo, kind string, onComplete string) (*progressBar, error) { + // shortDigestLen is the length of the digest used for blobs. + const shortDigestLen = 12 + ++ if err := info.Digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly. ++ return nil, err ++ } + prefix := fmt.Sprintf("Copying %s %s", kind, info.Digest.Encoded()) + // Truncate the prefix (chopping of some part of the digest) to make all progress bars aligned in a column. + maxPrefixLen := len("Copying blob ") + shortDigestLen +@@ -104,7 +107,7 @@ func (c *copier) createProgressBar(pool *mpb.Progress, partial bool, info types. + return &progressBar{ + Bar: bar, + originalSize: info.Size, +- } ++ }, nil + } + + // printCopyInfo prints a "Copying ..." message on the copier if the output is +diff --git a/vendor/github.com/containers/image/v5/copy/single.go b/vendor/github.com/containers/image/v5/copy/single.go +index 67ca43f..d36b854 100644 +--- a/vendor/github.com/containers/image/v5/copy/single.go ++++ b/vendor/github.com/containers/image/v5/copy/single.go +@@ -599,7 +599,10 @@ func (ic *imageCopier) copyConfig(ctx context.Context, src types.Image) error { + destInfo, err := func() (types.BlobInfo, error) { // A scope for defer + progressPool := ic.c.newProgressPool() + defer progressPool.Wait() +- bar := ic.c.createProgressBar(progressPool, false, srcInfo, "config", "done") ++ bar, err := ic.c.createProgressBar(progressPool, false, srcInfo, "config", "done") ++ if err != nil { ++ return types.BlobInfo{}, err ++ } + defer bar.Abort(false) + ic.c.printCopyInfo("config", srcInfo) + +@@ -707,11 +710,17 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to + } + if reused { + logrus.Debugf("Skipping blob %s (already present):", srcInfo.Digest) +- func() { // A scope for defer +- bar := ic.c.createProgressBar(pool, false, types.BlobInfo{Digest: reusedBlob.Digest, Size: 0}, "blob", "skipped: already exists") ++ if err := func() error { // A scope for defer ++ bar, err := ic.c.createProgressBar(pool, false, types.BlobInfo{Digest: reusedBlob.Digest, Size: 0}, "blob", "skipped: already exists") ++ if err != nil { ++ return err ++ } + defer bar.Abort(false) + bar.mark100PercentComplete() +- }() ++ return nil ++ }(); err != nil { ++ return types.BlobInfo{}, "", err ++ } + + // Throw an event that the layer has been skipped + if ic.c.options.Progress != nil && ic.c.options.ProgressInterval > 0 { +@@ -730,8 +739,11 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to + // Attempt a partial only when the source allows to retrieve a blob partially and + // the destination has support for it. + if canAvoidProcessingCompleteLayer && ic.c.rawSource.SupportsGetBlobAt() && ic.c.dest.SupportsPutBlobPartial() { +- if reused, blobInfo := func() (bool, types.BlobInfo) { // A scope for defer +- bar := ic.c.createProgressBar(pool, true, srcInfo, "blob", "done") ++ reused, blobInfo, err := func() (bool, types.BlobInfo, error) { // A scope for defer ++ bar, err := ic.c.createProgressBar(pool, true, srcInfo, "blob", "done") ++ if err != nil { ++ return false, types.BlobInfo{}, err ++ } + hideProgressBar := true + defer func() { // Note that this is not the same as defer bar.Abort(hideProgressBar); we need hideProgressBar to be evaluated lazily. + bar.Abort(hideProgressBar) +@@ -751,18 +763,25 @@ func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, to + bar.mark100PercentComplete() + hideProgressBar = false + logrus.Debugf("Retrieved partial blob %v", srcInfo.Digest) +- return true, updatedBlobInfoFromUpload(srcInfo, uploadedBlob) ++ return true, updatedBlobInfoFromUpload(srcInfo, uploadedBlob), nil + } + logrus.Debugf("Failed to retrieve partial blob: %v", err) +- return false, types.BlobInfo{} +- }(); reused { ++ return false, types.BlobInfo{}, nil ++ }() ++ if err != nil { ++ return types.BlobInfo{}, "", err ++ } ++ if reused { + return blobInfo, cachedDiffID, nil + } + } + + // Fallback: copy the layer, computing the diffID if we need to do so + return func() (types.BlobInfo, digest.Digest, error) { // A scope for defer +- bar := ic.c.createProgressBar(pool, false, srcInfo, "blob", "done") ++ bar, err := ic.c.createProgressBar(pool, false, srcInfo, "blob", "done") ++ if err != nil { ++ return types.BlobInfo{}, "", err ++ } + defer bar.Abort(false) + + srcStream, srcBlobSize, err := ic.c.rawSource.GetBlob(ctx, srcInfo, ic.c.blobInfoCache) +diff --git a/vendor/github.com/containers/image/v5/directory/directory_dest.go b/vendor/github.com/containers/image/v5/directory/directory_dest.go +index 222723a..d32877e 100644 +--- a/vendor/github.com/containers/image/v5/directory/directory_dest.go ++++ b/vendor/github.com/containers/image/v5/directory/directory_dest.go +@@ -173,7 +173,10 @@ func (d *dirImageDestination) PutBlobWithOptions(ctx context.Context, stream io. + } + } + +- blobPath := d.ref.layerPath(blobDigest) ++ blobPath, err := d.ref.layerPath(blobDigest) ++ if err != nil { ++ return private.UploadedBlob{}, err ++ } + // need to explicitly close the file, since a rename won't otherwise not work on Windows + blobFile.Close() + explicitClosed = true +@@ -196,7 +199,10 @@ func (d *dirImageDestination) TryReusingBlobWithOptions(ctx context.Context, inf + if info.Digest == "" { + return false, private.ReusedBlob{}, fmt.Errorf("Can not check for a blob with unknown digest") + } +- blobPath := d.ref.layerPath(info.Digest) ++ blobPath, err := d.ref.layerPath(info.Digest) ++ if err != nil { ++ return false, private.ReusedBlob{}, err ++ } + finfo, err := os.Stat(blobPath) + if err != nil && os.IsNotExist(err) { + return false, private.ReusedBlob{}, nil +@@ -216,7 +222,11 @@ func (d *dirImageDestination) TryReusingBlobWithOptions(ctx context.Context, inf + // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), + // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. + func (d *dirImageDestination) PutManifest(ctx context.Context, manifest []byte, instanceDigest *digest.Digest) error { +- return os.WriteFile(d.ref.manifestPath(instanceDigest), manifest, 0644) ++ path, err := d.ref.manifestPath(instanceDigest) ++ if err != nil { ++ return err ++ } ++ return os.WriteFile(path, manifest, 0644) + } + + // PutSignaturesWithFormat writes a set of signatures to the destination. +@@ -229,7 +239,11 @@ func (d *dirImageDestination) PutSignaturesWithFormat(ctx context.Context, signa + if err != nil { + return err + } +- if err := os.WriteFile(d.ref.signaturePath(i, instanceDigest), blob, 0644); err != nil { ++ path, err := d.ref.signaturePath(i, instanceDigest) ++ if err != nil { ++ return err ++ } ++ if err := os.WriteFile(path, blob, 0644); err != nil { + return err + } + } +diff --git a/vendor/github.com/containers/image/v5/directory/directory_src.go b/vendor/github.com/containers/image/v5/directory/directory_src.go +index 5fc83bb..6d725bc 100644 +--- a/vendor/github.com/containers/image/v5/directory/directory_src.go ++++ b/vendor/github.com/containers/image/v5/directory/directory_src.go +@@ -55,7 +55,11 @@ func (s *dirImageSource) Close() error { + // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list); + // this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists). + func (s *dirImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { +- m, err := os.ReadFile(s.ref.manifestPath(instanceDigest)) ++ path, err := s.ref.manifestPath(instanceDigest) ++ if err != nil { ++ return nil, "", err ++ } ++ m, err := os.ReadFile(path) + if err != nil { + return nil, "", err + } +@@ -66,7 +70,11 @@ func (s *dirImageSource) GetManifest(ctx context.Context, instanceDigest *digest + // The Digest field in BlobInfo is guaranteed to be provided, Size may be -1 and MediaType may be optionally provided. + // May update BlobInfoCache, preferably after it knows for certain that a blob truly exists at a specific location. + func (s *dirImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) { +- r, err := os.Open(s.ref.layerPath(info.Digest)) ++ path, err := s.ref.layerPath(info.Digest) ++ if err != nil { ++ return nil, -1, err ++ } ++ r, err := os.Open(path) + if err != nil { + return nil, -1, err + } +@@ -84,7 +92,10 @@ func (s *dirImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache + func (s *dirImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) { + signatures := []signature.Signature{} + for i := 0; ; i++ { +- path := s.ref.signaturePath(i, instanceDigest) ++ path, err := s.ref.signaturePath(i, instanceDigest) ++ if err != nil { ++ return nil, err ++ } + sigBlob, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { +diff --git a/vendor/github.com/containers/image/v5/directory/directory_transport.go b/vendor/github.com/containers/image/v5/directory/directory_transport.go +index 7e30686..4f7d596 100644 +--- a/vendor/github.com/containers/image/v5/directory/directory_transport.go ++++ b/vendor/github.com/containers/image/v5/directory/directory_transport.go +@@ -161,25 +161,34 @@ func (ref dirReference) DeleteImage(ctx context.Context, sys *types.SystemContex + } + + // manifestPath returns a path for the manifest within a directory using our conventions. +-func (ref dirReference) manifestPath(instanceDigest *digest.Digest) string { ++func (ref dirReference) manifestPath(instanceDigest *digest.Digest) (string, error) { + if instanceDigest != nil { +- return filepath.Join(ref.path, instanceDigest.Encoded()+".manifest.json") ++ if err := instanceDigest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, and could possibly result in a path with ../, so validate explicitly. ++ return "", err ++ } ++ return filepath.Join(ref.path, instanceDigest.Encoded()+".manifest.json"), nil + } +- return filepath.Join(ref.path, "manifest.json") ++ return filepath.Join(ref.path, "manifest.json"), nil + } + + // layerPath returns a path for a layer tarball within a directory using our conventions. +-func (ref dirReference) layerPath(digest digest.Digest) string { ++func (ref dirReference) layerPath(digest digest.Digest) (string, error) { ++ if err := digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, and could possibly result in a path with ../, so validate explicitly. ++ return "", err ++ } + // FIXME: Should we keep the digest identification? +- return filepath.Join(ref.path, digest.Encoded()) ++ return filepath.Join(ref.path, digest.Encoded()), nil + } + + // signaturePath returns a path for a signature within a directory using our conventions. +-func (ref dirReference) signaturePath(index int, instanceDigest *digest.Digest) string { ++func (ref dirReference) signaturePath(index int, instanceDigest *digest.Digest) (string, error) { + if instanceDigest != nil { +- return filepath.Join(ref.path, fmt.Sprintf(instanceDigest.Encoded()+".signature-%d", index+1)) ++ if err := instanceDigest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, and could possibly result in a path with ../, so validate explicitly. ++ return "", err ++ } ++ return filepath.Join(ref.path, fmt.Sprintf(instanceDigest.Encoded()+".signature-%d", index+1)), nil + } +- return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1)) ++ return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1)), nil + } + + // versionPath returns a path for the version file within a directory using our conventions. +diff --git a/vendor/github.com/containers/image/v5/docker/docker_client.go b/vendor/github.com/containers/image/v5/docker/docker_client.go +index 6ce8f70..d03f87a 100644 +--- a/vendor/github.com/containers/image/v5/docker/docker_client.go ++++ b/vendor/github.com/containers/image/v5/docker/docker_client.go +@@ -952,6 +952,8 @@ func (c *dockerClient) detectProperties(ctx context.Context) error { + return c.detectPropertiesError + } + ++// fetchManifest fetches a manifest for (the repo of ref) + tagOrDigest. ++// The caller is responsible for ensuring tagOrDigest uses the expected format. + func (c *dockerClient) fetchManifest(ctx context.Context, ref dockerReference, tagOrDigest string) ([]byte, string, error) { + path := fmt.Sprintf(manifestPath, reference.Path(ref.ref), tagOrDigest) + headers := map[string][]string{ +@@ -1034,6 +1036,9 @@ func (c *dockerClient) getBlob(ctx context.Context, ref dockerReference, info ty + } + } + ++ if err := info.Digest.Validate(); err != nil { // Make sure info.Digest.String() does not contain any unexpected characters ++ return nil, 0, err ++ } + path := fmt.Sprintf(blobsPath, reference.Path(ref.ref), info.Digest.String()) + logrus.Debugf("Downloading %s", path) + res, err := c.makeRequest(ctx, http.MethodGet, path, nil, nil, v2Auth, nil) +@@ -1097,7 +1102,10 @@ func isManifestUnknownError(err error) bool { + // digest in ref. + // It returns (nil, nil) if the manifest does not exist. + func (c *dockerClient) getSigstoreAttachmentManifest(ctx context.Context, ref dockerReference, digest digest.Digest) (*manifest.OCI1, error) { +- tag := sigstoreAttachmentTag(digest) ++ tag, err := sigstoreAttachmentTag(digest) ++ if err != nil { ++ return nil, err ++ } + sigstoreRef, err := reference.WithTag(reference.TrimNamed(ref.ref), tag) + if err != nil { + return nil, err +@@ -1130,6 +1138,9 @@ func (c *dockerClient) getSigstoreAttachmentManifest(ctx context.Context, ref do + // getExtensionsSignatures returns signatures from the X-Registry-Supports-Signatures API extension, + // using the original data structures. + func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) { ++ if err := manifestDigest.Validate(); err != nil { // Make sure manifestDigest.String() does not contain any unexpected characters ++ return nil, err ++ } + path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest) + res, err := c.makeRequest(ctx, http.MethodGet, path, nil, nil, v2Auth, nil) + if err != nil { +@@ -1153,8 +1164,11 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe + } + + // sigstoreAttachmentTag returns a sigstore attachment tag for the specified digest. +-func sigstoreAttachmentTag(d digest.Digest) string { +- return strings.Replace(d.String(), ":", "-", 1) + ".sig" ++func sigstoreAttachmentTag(d digest.Digest) (string, error) { ++ if err := d.Validate(); err != nil { // Make sure d.String() doesn’t contain any unexpected characters ++ return "", err ++ } ++ return strings.Replace(d.String(), ":", "-", 1) + ".sig", nil + } + + // Close removes resources associated with an initialized dockerClient, if any. +diff --git a/vendor/github.com/containers/image/v5/docker/docker_image.go b/vendor/github.com/containers/image/v5/docker/docker_image.go +index 9316048..4c80bb2 100644 +--- a/vendor/github.com/containers/image/v5/docker/docker_image.go ++++ b/vendor/github.com/containers/image/v5/docker/docker_image.go +@@ -88,7 +88,12 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types. + if err = json.NewDecoder(res.Body).Decode(&tagsHolder); err != nil { + return nil, err + } +- tags = append(tags, tagsHolder.Tags...) ++ for _, tag := range tagsHolder.Tags { ++ if _, err := reference.WithTag(dr.ref, tag); err != nil { // Ensure the tag does not contain unexpected values ++ return nil, fmt.Errorf("registry returned invalid tag %q: %w", tag, err) ++ } ++ tags = append(tags, tag) ++ } + + link := res.Header.Get("Link") + if link == "" { +diff --git a/vendor/github.com/containers/image/v5/docker/docker_image_dest.go b/vendor/github.com/containers/image/v5/docker/docker_image_dest.go +index a9a36f0..0c0505a 100644 +--- a/vendor/github.com/containers/image/v5/docker/docker_image_dest.go ++++ b/vendor/github.com/containers/image/v5/docker/docker_image_dest.go +@@ -229,6 +229,9 @@ func (d *dockerImageDestination) PutBlobWithOptions(ctx context.Context, stream + // If the destination does not contain the blob, or it is unknown, blobExists ordinarily returns (false, -1, nil); + // it returns a non-nil error only on an unexpected failure. + func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.Named, digest digest.Digest, extraScope *authScope) (bool, int64, error) { ++ if err := digest.Validate(); err != nil { // Make sure digest.String() does not contain any unexpected characters ++ return false, -1, err ++ } + checkPath := fmt.Sprintf(blobsPath, reference.Path(repo), digest.String()) + logrus.Debugf("Checking %s", checkPath) + res, err := d.c.makeRequest(ctx, http.MethodHead, checkPath, nil, nil, v2Auth, extraScope) +@@ -466,6 +469,7 @@ func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte, inst + // particular instance. + refTail = instanceDigest.String() + // Double-check that the manifest we've been given matches the digest we've been given. ++ // This also validates the format of instanceDigest. + matches, err := manifest.MatchesDigest(m, *instanceDigest) + if err != nil { + return fmt.Errorf("digesting manifest in PutManifest: %w", err) +@@ -632,11 +636,13 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures []signature + + // NOTE: Keep this in sync with docs/signature-protocols.md! + for i, signature := range signatures { +- sigURL := lookasideStorageURL(d.c.signatureBase, manifestDigest, i) +- err := d.putOneSignature(sigURL, signature) ++ sigURL, err := lookasideStorageURL(d.c.signatureBase, manifestDigest, i) + if err != nil { + return err + } ++ if err := d.putOneSignature(sigURL, signature); err != nil { ++ return err ++ } + } + // Remove any other signatures, if present. + // We stop at the first missing signature; if a previous deleting loop aborted +@@ -644,7 +650,10 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures []signature + // is enough for dockerImageSource to stop looking for other signatures, so that + // is sufficient. + for i := len(signatures); ; i++ { +- sigURL := lookasideStorageURL(d.c.signatureBase, manifestDigest, i) ++ sigURL, err := lookasideStorageURL(d.c.signatureBase, manifestDigest, i) ++ if err != nil { ++ return err ++ } + missing, err := d.c.deleteOneSignature(sigURL) + if err != nil { + return err +@@ -775,8 +784,12 @@ func (d *dockerImageDestination) putSignaturesToSigstoreAttachments(ctx context. + if err != nil { + return err + } ++ attachmentTag, err := sigstoreAttachmentTag(manifestDigest) ++ if err != nil { ++ return err ++ } + logrus.Debugf("Uploading sigstore attachment manifest") +- return d.uploadManifest(ctx, manifestBlob, sigstoreAttachmentTag(manifestDigest)) ++ return d.uploadManifest(ctx, manifestBlob, attachmentTag) + } + + func layerMatchesSigstoreSignature(layer imgspecv1.Descriptor, mimeType string, +@@ -892,6 +905,7 @@ func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context + return err + } + ++ // manifestDigest is known to be valid because it was not rejected by getExtensionsSignatures above. + path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), manifestDigest.String()) + res, err := d.c.makeRequest(ctx, http.MethodPut, path, nil, bytes.NewReader(body), v2Auth, nil) + if err != nil { +diff --git a/vendor/github.com/containers/image/v5/docker/docker_image_src.go b/vendor/github.com/containers/image/v5/docker/docker_image_src.go +index f9d4d60..274cd6d 100644 +--- a/vendor/github.com/containers/image/v5/docker/docker_image_src.go ++++ b/vendor/github.com/containers/image/v5/docker/docker_image_src.go +@@ -194,6 +194,9 @@ func simplifyContentType(contentType string) string { + // this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists). + func (s *dockerImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { + if instanceDigest != nil { ++ if err := instanceDigest.Validate(); err != nil { // Make sure instanceDigest.String() does not contain any unexpected characters ++ return nil, "", err ++ } + return s.fetchManifest(ctx, instanceDigest.String()) + } + err := s.ensureManifestIsLoaded(ctx) +@@ -203,6 +206,8 @@ func (s *dockerImageSource) GetManifest(ctx context.Context, instanceDigest *dig + return s.cachedManifest, s.cachedManifestMIMEType, nil + } + ++// fetchManifest fetches a manifest for tagOrDigest. ++// The caller is responsible for ensuring tagOrDigest uses the expected format. + func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest string) ([]byte, string, error) { + return s.c.fetchManifest(ctx, s.physicalRef, tagOrDigest) + } +@@ -352,6 +357,9 @@ func (s *dockerImageSource) GetBlobAt(ctx context.Context, info types.BlobInfo, + return nil, nil, fmt.Errorf("external URLs not supported with GetBlobAt") + } + ++ if err := info.Digest.Validate(); err != nil { // Make sure info.Digest.String() does not contain any unexpected characters ++ return nil, nil, err ++ } + path := fmt.Sprintf(blobsPath, reference.Path(s.physicalRef.ref), info.Digest.String()) + logrus.Debugf("Downloading %s", path) + res, err := s.c.makeRequest(ctx, http.MethodGet, path, headers, nil, v2Auth, nil) +@@ -462,7 +470,10 @@ func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, inst + return nil, fmt.Errorf("server provided %d signatures, assuming that's unreasonable and a server error", maxLookasideSignatures) + } + +- sigURL := lookasideStorageURL(s.c.signatureBase, manifestDigest, i) ++ sigURL, err := lookasideStorageURL(s.c.signatureBase, manifestDigest, i) ++ if err != nil { ++ return nil, err ++ } + signature, missing, err := s.getOneSignature(ctx, sigURL) + if err != nil { + return nil, err +@@ -660,7 +671,10 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere + } + + for i := 0; ; i++ { +- sigURL := lookasideStorageURL(c.signatureBase, manifestDigest, i) ++ sigURL, err := lookasideStorageURL(c.signatureBase, manifestDigest, i) ++ if err != nil { ++ return err ++ } + missing, err := c.deleteOneSignature(sigURL) + if err != nil { + return err +diff --git a/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go b/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go +index 7507d85..106490c 100644 +--- a/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go ++++ b/vendor/github.com/containers/image/v5/docker/internal/tarfile/dest.go +@@ -111,11 +111,19 @@ func (d *Destination) PutBlobWithOptions(ctx context.Context, stream io.Reader, + return private.UploadedBlob{}, fmt.Errorf("reading Config file stream: %w", err) + } + d.config = buf +- if err := d.archive.sendFileLocked(d.archive.configPath(inputInfo.Digest), inputInfo.Size, bytes.NewReader(buf)); err != nil { ++ configPath, err := d.archive.configPath(inputInfo.Digest) ++ if err != nil { ++ return private.UploadedBlob{}, err ++ } ++ if err := d.archive.sendFileLocked(configPath, inputInfo.Size, bytes.NewReader(buf)); err != nil { + return private.UploadedBlob{}, fmt.Errorf("writing Config file: %w", err) + } + } else { +- if err := d.archive.sendFileLocked(d.archive.physicalLayerPath(inputInfo.Digest), inputInfo.Size, stream); err != nil { ++ layerPath, err := d.archive.physicalLayerPath(inputInfo.Digest) ++ if err != nil { ++ return private.UploadedBlob{}, err ++ } ++ if err := d.archive.sendFileLocked(layerPath, inputInfo.Size, stream); err != nil { + return private.UploadedBlob{}, err + } + } +diff --git a/vendor/github.com/containers/image/v5/docker/internal/tarfile/writer.go b/vendor/github.com/containers/image/v5/docker/internal/tarfile/writer.go +index df7b2c0..7f6bd0e 100644 +--- a/vendor/github.com/containers/image/v5/docker/internal/tarfile/writer.go ++++ b/vendor/github.com/containers/image/v5/docker/internal/tarfile/writer.go +@@ -95,7 +95,10 @@ func (w *Writer) ensureSingleLegacyLayerLocked(layerID string, layerDigest diges + if !w.legacyLayers.Contains(layerID) { + // Create a symlink for the legacy format, where there is one subdirectory per layer ("image"). + // See also the comment in physicalLayerPath. +- physicalLayerPath := w.physicalLayerPath(layerDigest) ++ physicalLayerPath, err := w.physicalLayerPath(layerDigest) ++ if err != nil { ++ return err ++ } + if err := w.sendSymlinkLocked(filepath.Join(layerID, legacyLayerFileName), filepath.Join("..", physicalLayerPath)); err != nil { + return fmt.Errorf("creating layer symbolic link: %w", err) + } +@@ -139,6 +142,9 @@ func (w *Writer) writeLegacyMetadataLocked(layerDescriptors []manifest.Schema2De + } + + // This chainID value matches the computation in docker/docker/layer.CreateChainID … ++ if err := l.Digest.Validate(); err != nil { // This should never fail on this code path, still: make sure the chainID computation is unambiguous. ++ return err ++ } + if chainID == "" { + chainID = l.Digest + } else { +@@ -204,12 +210,20 @@ func checkManifestItemsMatch(a, b *ManifestItem) error { + func (w *Writer) ensureManifestItemLocked(layerDescriptors []manifest.Schema2Descriptor, configDigest digest.Digest, repoTags []reference.NamedTagged) error { + layerPaths := []string{} + for _, l := range layerDescriptors { +- layerPaths = append(layerPaths, w.physicalLayerPath(l.Digest)) ++ p, err := w.physicalLayerPath(l.Digest) ++ if err != nil { ++ return err ++ } ++ layerPaths = append(layerPaths, p) + } + + var item *ManifestItem ++ configPath, err := w.configPath(configDigest) ++ if err != nil { ++ return err ++ } + newItem := ManifestItem{ +- Config: w.configPath(configDigest), ++ Config: configPath, + RepoTags: []string{}, + Layers: layerPaths, + Parent: "", // We don’t have this information +@@ -294,21 +308,27 @@ func (w *Writer) Close() error { + // configPath returns a path we choose for storing a config with the specified digest. + // NOTE: This is an internal implementation detail, not a format property, and can change + // any time. +-func (w *Writer) configPath(configDigest digest.Digest) string { +- return configDigest.Hex() + ".json" ++func (w *Writer) configPath(configDigest digest.Digest) (string, error) { ++ if err := configDigest.Validate(); err != nil { // digest.Digest.Hex() panics on failure, and could possibly result in unexpected paths, so validate explicitly. ++ return "", err ++ } ++ return configDigest.Hex() + ".json", nil + } + + // physicalLayerPath returns a path we choose for storing a layer with the specified digest + // (the actual path, i.e. a regular file, not a symlink that may be used in the legacy format). + // NOTE: This is an internal implementation detail, not a format property, and can change + // any time. +-func (w *Writer) physicalLayerPath(layerDigest digest.Digest) string { ++func (w *Writer) physicalLayerPath(layerDigest digest.Digest) (string, error) { ++ if err := layerDigest.Validate(); err != nil { // digest.Digest.Hex() panics on failure, and could possibly result in unexpected paths, so validate explicitly. ++ return "", err ++ } + // Note that this can't be e.g. filepath.Join(l.Digest.Hex(), legacyLayerFileName); due to the way + // writeLegacyMetadata constructs layer IDs differently from inputinfo.Digest values (as described + // inside it), most of the layers would end up in subdirectories alone without any metadata; (docker load) + // tries to load every subdirectory as an image and fails if the config is missing. So, keep the layers + // in the root of the tarball. +- return layerDigest.Hex() + ".tar" ++ return layerDigest.Hex() + ".tar", nil + } + + type tarFI struct { +diff --git a/vendor/github.com/containers/image/v5/docker/registries_d.go b/vendor/github.com/containers/image/v5/docker/registries_d.go +index c7b884a..9d651d9 100644 +--- a/vendor/github.com/containers/image/v5/docker/registries_d.go ++++ b/vendor/github.com/containers/image/v5/docker/registries_d.go +@@ -286,8 +286,11 @@ func (ns registryNamespace) signatureTopLevel(write bool) string { + // lookasideStorageURL returns an URL usable for accessing signature index in base with known manifestDigest. + // base is not nil from the caller + // NOTE: Keep this in sync with docs/signature-protocols.md! +-func lookasideStorageURL(base lookasideStorageBase, manifestDigest digest.Digest, index int) *url.URL { ++func lookasideStorageURL(base lookasideStorageBase, manifestDigest digest.Digest, index int) (*url.URL, error) { ++ if err := manifestDigest.Validate(); err != nil { // digest.Digest.Hex() panics on failure, and could possibly result in a path with ../, so validate explicitly. ++ return nil, err ++ } + sigURL := *base + sigURL.Path = fmt.Sprintf("%s@%s=%s/signature-%d", sigURL.Path, manifestDigest.Algorithm(), manifestDigest.Hex(), index+1) +- return &sigURL ++ return &sigURL, nil + } +diff --git a/vendor/github.com/containers/image/v5/openshift/openshift_src.go b/vendor/github.com/containers/image/v5/openshift/openshift_src.go +index 0ac0127..62774af 100644 +--- a/vendor/github.com/containers/image/v5/openshift/openshift_src.go ++++ b/vendor/github.com/containers/image/v5/openshift/openshift_src.go +@@ -109,6 +109,9 @@ func (s *openshiftImageSource) GetSignaturesWithFormat(ctx context.Context, inst + } + imageStreamImageName = s.imageStreamImageName + } else { ++ if err := instanceDigest.Validate(); err != nil { // Make sure instanceDigest.String() does not contain any unexpected characters ++ return nil, err ++ } + imageStreamImageName = instanceDigest.String() + } + image, err := s.client.getImage(ctx, imageStreamImageName) +diff --git a/vendor/github.com/containers/image/v5/ostree/ostree_dest.go b/vendor/github.com/containers/image/v5/ostree/ostree_dest.go +index d00a0cd..29177f1 100644 +--- a/vendor/github.com/containers/image/v5/ostree/ostree_dest.go ++++ b/vendor/github.com/containers/image/v5/ostree/ostree_dest.go +@@ -345,6 +345,10 @@ func (d *ostreeImageDestination) TryReusingBlobWithOptions(ctx context.Context, + } + d.repo = repo + } ++ ++ if err := info.Digest.Validate(); err != nil { // digest.Digest.Hex() panics on failure, so validate explicitly. ++ return false, private.ReusedBlob{}, err ++ } + branch := fmt.Sprintf("ociimage/%s", info.Digest.Hex()) + + found, data, err := readMetadata(d.repo, branch, "docker.uncompressed_digest") +@@ -470,12 +474,18 @@ func (d *ostreeImageDestination) Commit(context.Context, types.UnparsedImage) er + return nil + } + for _, layer := range d.schema.LayersDescriptors { ++ if err := layer.Digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly. ++ return err ++ } + hash := layer.Digest.Hex() + if err = checkLayer(hash); err != nil { + return err + } + } + for _, layer := range d.schema.FSLayers { ++ if err := layer.BlobSum.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly. ++ return err ++ } + hash := layer.BlobSum.Hex() + if err = checkLayer(hash); err != nil { + return err +diff --git a/vendor/github.com/containers/image/v5/ostree/ostree_src.go b/vendor/github.com/containers/image/v5/ostree/ostree_src.go +index 9983acc..a9568c2 100644 +--- a/vendor/github.com/containers/image/v5/ostree/ostree_src.go ++++ b/vendor/github.com/containers/image/v5/ostree/ostree_src.go +@@ -286,7 +286,9 @@ func (s *ostreeImageSource) readSingleFile(commit, path string) (io.ReadCloser, + // The Digest field in BlobInfo is guaranteed to be provided, Size may be -1 and MediaType may be optionally provided. + // May update BlobInfoCache, preferably after it knows for certain that a blob truly exists at a specific location. + func (s *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) { +- ++ if err := info.Digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly. ++ return nil, -1, err ++ } + blob := info.Digest.Hex() + + // Ensure s.compressed is initialized. It is build by LayerInfosForCopy. +diff --git a/vendor/github.com/containers/image/v5/storage/storage_dest.go b/vendor/github.com/containers/image/v5/storage/storage_dest.go +index 07e1d5e..6b59be1 100644 +--- a/vendor/github.com/containers/image/v5/storage/storage_dest.go ++++ b/vendor/github.com/containers/image/v5/storage/storage_dest.go +@@ -324,6 +324,13 @@ func (s *storageImageDestination) TryReusingBlobWithOptions(ctx context.Context, + // tryReusingBlobAsPending implements TryReusingBlobWithOptions for (digest, size or -1), filling s.blobDiffIDs and other metadata. + // The caller must arrange the blob to be eventually committed using s.commitLayer(). + func (s *storageImageDestination) tryReusingBlobAsPending(digest digest.Digest, size int64, options *private.TryReusingBlobOptions) (bool, private.ReusedBlob, error) { ++ if digest == "" { ++ return false, private.ReusedBlob{}, errors.New(`Can not check for a blob with unknown digest`) ++ } ++ if err := digest.Validate(); err != nil { ++ return false, private.ReusedBlob{}, fmt.Errorf("Can not check for a blob with invalid digest: %w", err) ++ } ++ + // lock the entire method as it executes fairly quickly + s.lock.Lock() + defer s.lock.Unlock() +@@ -344,13 +351,6 @@ func (s *storageImageDestination) tryReusingBlobAsPending(digest digest.Digest, + } + } + +- if digest == "" { +- return false, private.ReusedBlob{}, errors.New(`Can not check for a blob with unknown digest`) +- } +- if err := digest.Validate(); err != nil { +- return false, private.ReusedBlob{}, fmt.Errorf("Can not check for a blob with invalid digest: %w", err) +- } +- + // Check if we've already cached it in a file. + if size, ok := s.fileSizes[digest]; ok { + return true, private.ReusedBlob{ +@@ -803,8 +803,12 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t + if err != nil { + return fmt.Errorf("digesting top-level manifest: %w", err) + } ++ key, err := manifestBigDataKey(manifestDigest) ++ if err != nil { ++ return err ++ } + options.BigData = append(options.BigData, storage.ImageBigDataOption{ +- Key: manifestBigDataKey(manifestDigest), ++ Key: key, + Data: toplevelManifest, + Digest: manifestDigest, + }) +@@ -812,8 +816,12 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t + // Set up to save the image's manifest. Allow looking it up by digest by using the key convention defined by the Store. + // Record the manifest twice: using a digest-specific key to allow references to that specific digest instance, + // and using storage.ImageDigestBigDataKey for future users that don’t specify any digest and for compatibility with older readers. ++ key, err := manifestBigDataKey(s.manifestDigest) ++ if err != nil { ++ return err ++ } + options.BigData = append(options.BigData, storage.ImageBigDataOption{ +- Key: manifestBigDataKey(s.manifestDigest), ++ Key: key, + Data: s.manifest, + Digest: s.manifestDigest, + }) +@@ -831,8 +839,12 @@ func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel t + }) + } + for instanceDigest, signatures := range s.signatureses { ++ key, err := signatureBigDataKey(instanceDigest) ++ if err != nil { ++ return err ++ } + options.BigData = append(options.BigData, storage.ImageBigDataOption{ +- Key: signatureBigDataKey(instanceDigest), ++ Key: key, + Data: signatures, + Digest: digest.Canonical.FromBytes(signatures), + }) +diff --git a/vendor/github.com/containers/image/v5/storage/storage_image.go b/vendor/github.com/containers/image/v5/storage/storage_image.go +index ac09f3d..ba25a0c 100644 +--- a/vendor/github.com/containers/image/v5/storage/storage_image.go ++++ b/vendor/github.com/containers/image/v5/storage/storage_image.go +@@ -26,14 +26,20 @@ type storageImageCloser struct { + // manifestBigDataKey returns a key suitable for recording a manifest with the specified digest using storage.Store.ImageBigData and related functions. + // If a specific manifest digest is explicitly requested by the user, the key returned by this function should be used preferably; + // for compatibility, if a manifest is not available under this key, check also storage.ImageDigestBigDataKey +-func manifestBigDataKey(digest digest.Digest) string { +- return storage.ImageDigestManifestBigDataNamePrefix + "-" + digest.String() ++func manifestBigDataKey(digest digest.Digest) (string, error) { ++ if err := digest.Validate(); err != nil { // Make sure info.Digest.String() uses the expected format and does not collide with other BigData keys. ++ return "", err ++ } ++ return storage.ImageDigestManifestBigDataNamePrefix + "-" + digest.String(), nil + } + + // signatureBigDataKey returns a key suitable for recording the signatures associated with the manifest with the specified digest using storage.Store.ImageBigData and related functions. + // If a specific manifest digest is explicitly requested by the user, the key returned by this function should be used preferably; +-func signatureBigDataKey(digest digest.Digest) string { +- return "signature-" + digest.Encoded() ++func signatureBigDataKey(digest digest.Digest) (string, error) { ++ if err := digest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly. ++ return "", err ++ } ++ return "signature-" + digest.Encoded(), nil + } + + // Size() returns the previously-computed size of the image, with no error. +diff --git a/vendor/github.com/containers/image/v5/storage/storage_reference.go b/vendor/github.com/containers/image/v5/storage/storage_reference.go +index a55e340..6b7565f 100644 +--- a/vendor/github.com/containers/image/v5/storage/storage_reference.go ++++ b/vendor/github.com/containers/image/v5/storage/storage_reference.go +@@ -73,7 +73,10 @@ func multiArchImageMatchesSystemContext(store storage.Store, img *storage.Image, + // We don't need to care about storage.ImageDigestBigDataKey because + // manifests lists are only stored into storage by c/image versions + // that know about manifestBigDataKey, and only using that key. +- key := manifestBigDataKey(manifestDigest) ++ key, err := manifestBigDataKey(manifestDigest) ++ if err != nil { ++ return false // This should never happen, manifestDigest comes from a reference.Digested, and that validates the format. ++ } + manifestBytes, err := store.ImageBigData(img.ID, key) + if err != nil { + return false +@@ -95,7 +98,10 @@ func multiArchImageMatchesSystemContext(store storage.Store, img *storage.Image, + if err != nil { + return false + } +- key = manifestBigDataKey(chosenInstance) ++ key, err = manifestBigDataKey(chosenInstance) ++ if err != nil { ++ return false ++ } + _, err = store.ImageBigData(img.ID, key) + return err == nil // true if img.ID is based on chosenInstance. + } +diff --git a/vendor/github.com/containers/image/v5/storage/storage_src.go b/vendor/github.com/containers/image/v5/storage/storage_src.go +index f1ce086..7e4b69f 100644 +--- a/vendor/github.com/containers/image/v5/storage/storage_src.go ++++ b/vendor/github.com/containers/image/v5/storage/storage_src.go +@@ -202,7 +202,10 @@ func (s *storageImageSource) getBlobAndLayerID(digest digest.Digest, layers []st + // GetManifest() reads the image's manifest. + func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) (manifestBlob []byte, mimeType string, err error) { + if instanceDigest != nil { +- key := manifestBigDataKey(*instanceDigest) ++ key, err := manifestBigDataKey(*instanceDigest) ++ if err != nil { ++ return nil, "", err ++ } + blob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key) + if err != nil { + return nil, "", fmt.Errorf("reading manifest for image instance %q: %w", *instanceDigest, err) +@@ -214,7 +217,10 @@ func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *di + // Prefer the manifest corresponding to the user-specified digest, if available. + if s.imageRef.named != nil { + if digested, ok := s.imageRef.named.(reference.Digested); ok { +- key := manifestBigDataKey(digested.Digest()) ++ key, err := manifestBigDataKey(digested.Digest()) ++ if err != nil { ++ return nil, "", err ++ } + blob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key) + if err != nil && !os.IsNotExist(err) { // os.IsNotExist is true if the image exists but there is no data corresponding to key + return nil, "", err +@@ -329,7 +335,14 @@ func (s *storageImageSource) GetSignaturesWithFormat(ctx context.Context, instan + instance := "default instance" + if instanceDigest != nil { + signatureSizes = s.SignaturesSizes[*instanceDigest] +- key = signatureBigDataKey(*instanceDigest) ++ k, err := signatureBigDataKey(*instanceDigest) ++ if err != nil { ++ return nil, err ++ } ++ key = k ++ if err := instanceDigest.Validate(); err != nil { // digest.Digest.Encoded() panics on failure, so validate explicitly. ++ return nil, err ++ } + instance = instanceDigest.Encoded() + } + if len(signatureSizes) > 0 { +-- +2.33.8 + diff --git a/SPECS/skopeo/skopeo.spec b/SPECS/skopeo/skopeo.spec index 3e5f864a35..53b158aa14 100644 --- a/SPECS/skopeo/skopeo.spec +++ b/SPECS/skopeo/skopeo.spec @@ -1,7 +1,7 @@ Summary: Inspect container images and repositories on registries Name: skopeo Version: 1.14.2 -Release: 4%{?dist} +Release: 5%{?dist} License: Apache-2.0 Vendor: Microsoft Corporation Distribution: Mariner @@ -9,6 +9,7 @@ Group: Applications/Tools URL: https://github.com/containers/skopeo Source0: https://github.com/containers/skopeo/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz Patch0: CVE-2023-45288.patch +Patch1: CVE-2024-3727.patch %global debug_package %{nil} %define our_gopath %{_topdir}/.gopath BuildRequires: btrfs-progs-devel @@ -46,9 +47,12 @@ make test-unit-local %{_mandir}/man1/%%{name}* %changelog -* Thu Jun 06 2024 CBL-Mariner Servicing Account - 1.14.2-4 +* Wed Jun 26 2024 CBL-Mariner Servicing Account - 1.14.2-5 - Bump release to rebuild with go 1.21.11 +* Thu Jun 20 2024 Rohit Rawat - 1.14.2-4 +- Fix CVE-2024-3727 in github.com/containers/image + * Thu Apr 18 2024 Chris Gunn - 1.14.2-3 - Fix for CVE-2023-45288