Skip to content

Commit a506ceb

Browse files
committed
refactor(registry): extract the general unpack logic
1 parent 47c911e commit a506ceb

File tree

1 file changed

+61
-54
lines changed
  • src/cargo/sources/registry

1 file changed

+61
-54
lines changed

src/cargo/sources/registry/mod.rs

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -630,60 +630,8 @@ impl<'gctx> RegistrySource<'gctx> {
630630
Err(e) => anyhow::bail!("unable to read .cargo-ok file at {path:?}: {e}"),
631631
}
632632
dst.create_dir()?;
633-
let mut tar = {
634-
let size_limit = max_unpack_size(self.gctx, tarball.metadata()?.len());
635-
let gz = GzDecoder::new(tarball);
636-
let gz = LimitErrorReader::new(gz, size_limit);
637-
let mut tar = Archive::new(gz);
638-
set_mask(&mut tar);
639-
tar
640-
};
641-
let mut bytes_written = 0;
642-
let prefix = unpack_dir.file_name().unwrap();
643-
let parent = unpack_dir.parent().unwrap();
644-
for entry in tar.entries()? {
645-
let mut entry = entry.context("failed to iterate over archive")?;
646-
let entry_path = entry
647-
.path()
648-
.context("failed to read entry path")?
649-
.into_owned();
650-
651-
// We're going to unpack this tarball into the global source
652-
// directory, but we want to make sure that it doesn't accidentally
653-
// (or maliciously) overwrite source code from other crates. Cargo
654-
// itself should never generate a tarball that hits this error, and
655-
// crates.io should also block uploads with these sorts of tarballs,
656-
// but be extra sure by adding a check here as well.
657-
if !entry_path.starts_with(prefix) {
658-
anyhow::bail!(
659-
"invalid tarball downloaded, contains \
660-
a file at {:?} which isn't under {:?}",
661-
entry_path,
662-
prefix
663-
)
664-
}
665-
// Prevent unpacking the lockfile from the crate itself.
666-
if entry_path
667-
.file_name()
668-
.map_or(false, |p| p == PACKAGE_SOURCE_LOCK)
669-
{
670-
continue;
671-
}
672-
// Unpacking failed
673-
bytes_written += entry.size();
674-
let mut result = entry.unpack_in(parent).map_err(anyhow::Error::from);
675-
if cfg!(windows) && restricted_names::is_windows_reserved_path(&entry_path) {
676-
result = result.with_context(|| {
677-
format!(
678-
"`{}` appears to contain a reserved Windows path, \
679-
it cannot be extracted on Windows",
680-
entry_path.display()
681-
)
682-
});
683-
}
684-
result
685-
.with_context(|| format!("failed to unpack entry at `{}`", entry_path.display()))?;
686-
}
633+
634+
let bytes_written = unpack(self.gctx, tarball, unpack_dir)?;
687635

688636
// Now that we've finished unpacking, create and write to the lock file to indicate that
689637
// unpacking was successful.
@@ -1046,3 +994,62 @@ fn set_mask<R: Read>(tar: &mut Archive<R>) {
1046994
#[cfg(unix)]
1047995
tar.set_mask(crate::util::get_umask());
1048996
}
997+
998+
/// Unpack a tarball with zip bomb and overwrite protections.
999+
fn unpack(gctx: &GlobalContext, tarball: &File, unpack_dir: &Path) -> CargoResult<u64> {
1000+
let mut tar = {
1001+
let size_limit = max_unpack_size(gctx, tarball.metadata()?.len());
1002+
let gz = GzDecoder::new(tarball);
1003+
let gz = LimitErrorReader::new(gz, size_limit);
1004+
let mut tar = Archive::new(gz);
1005+
set_mask(&mut tar);
1006+
tar
1007+
};
1008+
let mut bytes_written = 0;
1009+
let prefix = unpack_dir.file_name().unwrap();
1010+
let parent = unpack_dir.parent().unwrap();
1011+
for entry in tar.entries()? {
1012+
let mut entry = entry.context("failed to iterate over archive")?;
1013+
let entry_path = entry
1014+
.path()
1015+
.context("failed to read entry path")?
1016+
.into_owned();
1017+
1018+
// We're going to unpack this tarball into the global source
1019+
// directory, but we want to make sure that it doesn't accidentally
1020+
// (or maliciously) overwrite source code from other crates. Cargo
1021+
// itself should never generate a tarball that hits this error, and
1022+
// crates.io should also block uploads with these sorts of tarballs,
1023+
// but be extra sure by adding a check here as well.
1024+
if !entry_path.starts_with(prefix) {
1025+
anyhow::bail!(
1026+
"invalid tarball downloaded, contains \
1027+
a file at {:?} which isn't under {:?}",
1028+
entry_path,
1029+
prefix
1030+
)
1031+
}
1032+
// Prevent unpacking the lockfile from the crate itself.
1033+
if entry_path
1034+
.file_name()
1035+
.map_or(false, |p| p == PACKAGE_SOURCE_LOCK)
1036+
{
1037+
continue;
1038+
}
1039+
// Unpacking failed
1040+
bytes_written += entry.size();
1041+
let mut result = entry.unpack_in(parent).map_err(anyhow::Error::from);
1042+
if cfg!(windows) && restricted_names::is_windows_reserved_path(&entry_path) {
1043+
result = result.with_context(|| {
1044+
format!(
1045+
"`{}` appears to contain a reserved Windows path, \
1046+
it cannot be extracted on Windows",
1047+
entry_path.display()
1048+
)
1049+
});
1050+
}
1051+
result.with_context(|| format!("failed to unpack entry at `{}`", entry_path.display()))?;
1052+
}
1053+
1054+
Ok(bytes_written)
1055+
}

0 commit comments

Comments
 (0)