Skip to content

fix: preserve per-occurrence layer identity in mutate.Image.Layers()#2299

Merged
Subserial merged 2 commits into
google:mainfrom
iahsanGill:fix/mutate-append-duplicate-diff-id
May 15, 2026
Merged

fix: preserve per-occurrence layer identity in mutate.Image.Layers()#2299
Subserial merged 2 commits into
google:mainfrom
iahsanGill:fix/mutate-append-duplicate-diff-id

Conversation

@iahsanGill

Copy link
Copy Markdown
Contributor

Fixes #2034.

When a base image and an appended layer share a diff ID but have different blob digests — same uncompressed content, different compression — mutate.Image.Layers() was returning the appended layer twice instead of [base, appended]. The manifest itself still listed both digests correctly, but downstream code that walks Layers() to upload blobs would push the appended layer twice and never upload the base's blob, so the final manifest PUT failed with MANIFEST_BLOB_UNKNOWN. The reporter hit this through kaniko; another commenter ran into the same thing via crane append with RPM-extracted tarballs.

The root cause is in pkg/v1/mutate/image.go. Layers() walked the rootfs diff IDs and resolved each one via LayerByDiffID, which has no way to disambiguate two layers that share a diff ID — the addendum overwrites the base entry in diffIDMap during compute(), so both diff-ID lookups return the same (appended) layer.

The manifest layer descriptors are already unambiguous: one entry per occurrence, each with its own digest. Walking those and resolving via LayerByDigest keeps the same length and order but preserves per-occurrence identity. LayerByDigest naturally handles the case because digestMap is keyed on (unique) digests, and base-image digests fall through to i.base.LayerByDigest.

Added TestAppendLayers_DuplicateDiffID covering it — uses a thin wrapper that reports the inner layer's DiffID with its own Digest/Size/Compressed, which is how the bug shows up in practice. Confirmed the test fails on main (returns the wrong layer at index 0) and passes with this change. Full pkg/v1/mutate suite passes with -race.

@google-cla

google-cla Bot commented May 15, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@codecov-commenter

codecov-commenter commented May 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.89%. Comparing base (35b354b) to head (2b93119).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2299   +/-   ##
=======================================
  Coverage   56.89%   56.89%           
=======================================
  Files         165      165           
  Lines       11334    11331    -3     
=======================================
- Hits         6448     6447    -1     
+ Misses       4121     4120    -1     
+ Partials      765      764    -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Subserial Subserial left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I can merge this once the language checks pass.

When a base image and an appended layer share a diff ID but have
different blob digests (e.g. same uncompressed tar, different
compression), mutate.Image.Layers() previously returned the appended
layer twice instead of [base, appended]. The manifest still listed
both digests correctly, but downstream code that walks Layers() to
upload blobs would upload the appended layer twice and never push
the base's blob, so the PUT manifest would fail with
MANIFEST_BLOB_UNKNOWN.

Root cause: Layers() walked the rootfs diff IDs and resolved each
via LayerByDiffID, which can't disambiguate two layers with the same
diff ID — the addendum overwrites the base entry in diffIDMap.

The manifest layer descriptors are already unambiguous (one entry per
occurrence, each with its own digest), so walk those instead and
resolve via LayerByDigest. Same length and order, but per-occurrence
identity is preserved.

Fixes google#2034
@iahsanGill iahsanGill force-pushed the fix/mutate-append-duplicate-diff-id branch from 23fe3de to 88af782 Compare May 15, 2026 22:06
@Subserial Subserial merged commit 68a569e into google:main May 15, 2026
17 checks passed
Subserial pushed a commit to Subserial/go-containerregistry that referenced this pull request May 15, 2026
When a base image and an appended layer share a diff ID but have
different blob digests (e.g. same uncompressed tar, different
compression), mutate.Image.Layers() previously returned the appended
layer twice instead of [base, appended]. The manifest still listed
both digests correctly, but downstream code that walks Layers() to
upload blobs would upload the appended layer twice and never push
the base's blob, so the PUT manifest would fail with
MANIFEST_BLOB_UNKNOWN.

Root cause: Layers() walked the rootfs diff IDs and resolved each
via LayerByDiffID, which can't disambiguate two layers with the same
diff ID — the addendum overwrites the base entry in diffIDMap.

The manifest layer descriptors are already unambiguous (one entry per
occurrence, each with its own digest), so walk those instead and
resolve via LayerByDigest. Same length and order, but per-occurrence
identity is preserved.

Fixes google#2034
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ggcr: mutate.Append creates invalid images when appending duplicate layers (same diff ID) but different blob

3 participants