Skip to content

cpio: contain extraction within root using SecureJoin#3632

Open
alhudz wants to merge 1 commit into
u-root:mainfrom
alhudz:cpio-symlink-escape
Open

cpio: contain extraction within root using SecureJoin#3632
alhudz wants to merge 1 commit into
u-root:mainfrom
alhudz:cpio-symlink-escape

Conversation

@alhudz

@alhudz alhudz commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Repro: extract a cpio that stores a symlink record (x -> an absolute path, or ../.., outside the destination) followed by a regular-file record named x/foo; the file lands outside rootDir.
Cause: CreateFileInRoot only ran the record name through the lexical SafeFilepathJoin, so a write through a symlink unpacked from an earlier record still followed it out of rootDir.
Fix: resolve each record name with securejoin.SecureJoin (already vendored), which clamps .. and follows the symlinks already present under rootDir, so the resolved path stays contained. This drops the custom path-walking. Legitimate symlinks that stay within the root keep working.

TestCreateFileInRootSymlinkEscape fails before this change and passes after; TestCreateFileInRootSymlinkWithinRoot covers the in-root case.

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.55556% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.88%. Comparing base (81ca02e) to head (eaf8604).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3632      +/-   ##
==========================================
+ Coverage   60.86%   60.88%   +0.01%     
==========================================
  Files         659      659              
  Lines       46497    46533      +36     
==========================================
+ Hits        28301    28332      +31     
- Misses      18196    18201       +5     
Flag Coverage Δ
.-amd64 90.90% <ø> (ø)
cmds/...-amd64 52.43% <ø> (-0.02%) ⬇️
integration/generic-tests/...-amd64 30.23% <0.00%> (-0.08%) ⬇️
integration/generic-tests/...-arm 33.24% <0.00%> (-0.12%) ⬇️
integration/generic-tests/...-arm64 29.35% <0.00%> (-0.08%) ⬇️
integration/gotests/...-amd64 60.53% <80.55%> (+0.03%) ⬆️
integration/gotests/...-arm 61.24% <80.55%> (+0.12%) ⬆️
integration/gotests/...-arm64 61.28% <80.55%> (+0.13%) ⬆️
pkg/...-amd64 58.77% <80.55%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
everything 65.65% <80.55%> (+0.01%) ⬆️
cmds/exp 34.18% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rminnich rminnich left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a lot of complexity, and I would prefer that you first check the safepath (recently added) support in upstream packages.

Can you check to see if that could be used instead of this custom code?

Extraction rebases each record name into rootDir with a lexical join, but a
symlink record unpacked earlier can still redirect a later write outside the
root (e.g. record `x` -> `/etc`, then record `x/passwd` lands in `/etc`),
defeating the existing zipslip check.

Resolve each record name with `securejoin.SecureJoin`, which clamps `..` and
follows the symlinks already present under rootDir so the resolved path stays
contained. This drops the custom path-walking code in favour of the upstream
safepath helper.

Signed-off-by: alhudz <al.hudz.k@gmail.com>
@alhudz alhudz force-pushed the cpio-symlink-escape branch from eaf8604 to 42825e0 Compare June 17, 2026 07:38
@alhudz alhudz changed the title cpio: skip records that escape the extraction root via a symlink cpio: contain extraction within root using SecureJoin Jun 17, 2026
@alhudz

alhudz commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Good call. cyphar/filepath-securejoin (already vendored) does exactly this: SecureJoin(rootDir, f.Name) clamps .. and follows any symlinks already unpacked under the root, so the write can't escape. Swapped to it and dropped the custom path-walking, so CreateFileInRoot is back to a single join call. The two tests still pass.

Comment thread pkg/cpio/fs_unix.go

securejoin "github.com/cyphar/filepath-securejoin"
"github.com/u-root/u-root/pkg/ls"
"github.com/u-root/u-root/pkg/upath"

@binjip978 binjip978 Jun 19, 2026

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.

Our AI policy require human in the loop. The issue is here, but fix don't make a lot of sense. If the problem is in upath.SafeFilepathJoin what a point to replace once occurrence of it?

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.

3 participants