Skip to content

sanitize volume and atom names in webc_to_package_dir - #6711

Open
netliomax25-code wants to merge 2 commits into
wasmerio:mainfrom
netliomax25-code:webc-unpack-path-traversal
Open

sanitize volume and atom names in webc_to_package_dir#6711
netliomax25-code wants to merge 2 commits into
wasmerio:mainfrom
netliomax25-code:webc-unpack-path-traversal

Conversation

@netliomax25-code

@netliomax25-code netliomax25-code commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Description

  1. webc_to_package_dir builds the on-disk destination for each volume as target_dir.join(mapping.volume_name.trim_start_matches('/')), and for each atom as module_dir.join(atom_name). Both names come straight from the (untrusted) webc, and trim_start_matches('/') only removes a leading slash, not ...
  2. A package whose filesystem annotation names a volume ../escaped then makes volume.unpack write under the parent of target_dir, so wasmer package unpack of a hostile package can drop files outside the chosen output directory.
  3. Names are written verbatim (so unpack stays consistent with the generated wasmer.toml and remains round-trippable), but before joining we now check whether a volume or atom name would resolve outside the output directory. If it would and the new --allow-escape flag is not set, the conversion errors out with a hint to use the flag; with the flag set the original behavior is preserved.

Added a regression test that unpacks a webc whose volume is named ../escaped and checks that the default rejects it (writing nothing to a sibling directory) while allow_escape lets it through.

@theduke theduke left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This isn't explicitly documented , but we would like unpack to be "idempotent", meaning you can run unpack and build and get the same package back.

This is a valid concern though.

We could add a --allow-escape flag or similar.
If not provided , escaping the main package directory would lead to an error, with a hint to use the flag.

Copilot AI 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.

Pull request overview

This PR hardens webc_to_package_dir against path traversal by sanitizing container-controlled names before constructing on-disk extraction paths, preventing a hostile WebC from unpacking files outside the requested output directory.

Changes:

  • Sanitize filesystem volume names before joining them onto target_dir during volume unpack.
  • Sanitize atom names before joining them onto module_dir when writing atom files.
  • Add a regression test covering a volume named ../escaped.

Comment on lines 147 to 151
// Atom names also come from the container, so sanitize before
// joining to keep the write inside module_dir.
let atom_subpath = webc::sanitize_path(&atom_name);
let atom_path = module_dir.join(atom_subpath.trim_start_matches('/'));

Comment on lines 93 to 99
// The volume name is taken verbatim from the (untrusted) webc and
// is used to build an on-disk path. Without normalization a name
// such as "../x" would unpack outside target_dir, so route it
// through the same sanitizer used elsewhere in the crate.
let volume_subpath = webc::sanitize_path(&mapping.volume_name);
let volume_path = target_dir.join(volume_subpath.trim_start_matches('/'));

Comment on lines +296 to +305
webc_to_package_dir(&container, &target_dir).unwrap();

// The volume contents must stay under target_dir, never in a sibling.
let escaped = root.join("escaped").join("secret");
assert!(
!escaped.exists(),
"volume escaped the output directory to {}",
escaped.display()
);
assert!(target_dir.join("escaped").join("secret").exists());
@netliomax25-code

Copy link
Copy Markdown
Contributor Author

Good point on idempotency. Reworked it along the lines you suggested:

  1. Issue: the silent re-rooting I had broke round-tripping, since the on-disk path no longer matched the name kept in wasmer.toml.
  2. Change: names are now written verbatim again, but before joining I check whether a volume or atom name would resolve outside the output directory (a leading .. etc.). If it would and the new --allow-escape flag isn't set, the conversion errors out with a hint to pass the flag; with the flag the original escaping behavior is preserved.
  3. Validation: updated the regression test to assert both paths (default rejects and writes nothing to a sibling dir; allow_escape lets it through). webc_to_package_dir now takes an allow_escape: bool and wasmer package unpack exposes --allow-escape. Existing roundtrip test still passes.

This way nothing is rewritten, so unpack/build stays consistent.

@marxin
marxin requested a review from theduke June 18, 2026 07:37
@netliomax25-code

Copy link
Copy Markdown
Contributor Author

any update?

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