Skip to content

pkg/v1/mutate: preserve relative symlinks that stay within rootfs in Extract#2279

Merged
Subserial merged 7 commits into
google:mainfrom
proudhare:fix/ph-issue-2268
May 13, 2026
Merged

pkg/v1/mutate: preserve relative symlinks that stay within rootfs in Extract#2279
Subserial merged 7 commits into
google:mainfrom
proudhare:fix/ph-issue-2268

Conversation

@anishesg

Copy link
Copy Markdown
Contributor

The extract() function in pkg/v1/mutate/mutate.go had all symlink filtering removed in the #2250 revert. Without any filtering, images can contain symlinks with absolute targets (e.g. /etc/passwd) or relative targets that escape the rootfs (e.g. ../../../tmp/evil), both of which are unsafe when layers are extracted to disk.

At the same time, #2268 reports that a prior version of the filtering was over-aggressive: it dropped all relative symlinks that contained .. anywhere in their target, even safe ones like ../lib/ld-linux-x86-64.so.2 from usr/local/bin/ — a pattern common in glibc and C toolchains.

This change adds targeted filtering back in extract(): absolute Linkname values are always dropped, and relative ones are resolved against the symlink's own directory using filepath.Join+filepath.Clean; only those whose resolved path starts with .. (i.e. would leave the rootfs) are dropped. Safe relative symlinks that resolve inside the rootfs are preserved unchanged.

A new table-driven test TestExtractSymlinkFiltering covers the four cases: safe relative symlink that stays inside rootfs (preserved), another common glibc pattern (preserved), absolute target (dropped), and an escaping relative target (dropped).

Fixes #2268

…Extract

The `extract()` function in `pkg/v1/mutate/mutate.go` had all symlink filtering removed in the google#2250 revert. Without any filtering, images can contain symlinks with absolute targets (e.g. `/etc/passwd`) or relative targets that escape the rootfs (e.g. `../../../tmp/evil`), both of which are unsafe when layers are extracted to disk.

Signed-off-by: anish k <ak8686@princeton.edu>
@anishesg anishesg marked this pull request as ready for review April 25, 2026 07:56
@codecov-commenter

codecov-commenter commented Apr 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.76%. Comparing base (a70d75a) to head (771d7b8).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2279      +/-   ##
==========================================
+ Coverage   56.74%   56.76%   +0.01%     
==========================================
  Files         165      165              
  Lines       11248    11253       +5     
==========================================
+ Hits         6383     6388       +5     
  Misses       4106     4106              
  Partials      759      759              

☔ 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.

…false positive

The filepath.Join at this line is only used to compute a resolved path for
validation purposes; no file I/O is performed with it. Gosec G305 fires
because the path is constructed from user-supplied tar header fields, but
the guard logic immediately after is exactly what prevents any traversal.

Signed-off-by: anish k <ak8686@princeton.edu>
Comment thread pkg/v1/mutate/mutate.go Outdated
Comment on lines +307 to +309
if filepath.IsAbs(header.Linkname) {
continue
}

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.

This code is called for both crane export and crane flatten. #2238 shows us a use-case for when absolute links should be preserved. So, there is still open discussion on whether we should prune absolute links at all. I suggest for now skipping this check on absolute paths,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, removed the absolute-path check. Now only relative targets that resolve outside the rootfs are dropped; absolute targets pass through unchanged. Updated the test and comment to match, referencing #2238.

…#2238 discussion

Signed-off-by: anish k <ak8686@princeton.edu>
@Subserial

Subserial commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Whoops, I tried manually merging on the UI and messed up the formatting. Could you re-merge for submit? Sorry about that.

@Subserial Subserial merged commit c29d91c into google:main May 13, 2026
18 of 19 checks passed
Subserial pushed a commit to Subserial/go-containerregistry that referenced this pull request May 15, 2026
…Extract (google#2279)

* pkg/v1/mutate: preserve relative symlinks that stay within rootfs in Extract

The `extract()` function in `pkg/v1/mutate/mutate.go` had all symlink filtering removed in the google#2250 revert. Without any filtering, images can contain symlinks with absolute targets (e.g. `/etc/passwd`) or relative targets that escape the rootfs (e.g. `../../../tmp/evil`), both of which are unsafe when layers are extracted to disk.

Signed-off-by: anish k <ak8686@princeton.edu>
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.

crane: 2238 started dropping symlinks, but it seems agressive for relative ones too

3 participants