Skip to content

Commit 696413c

Browse files
committed
Auto merge of #4493 - alexcrichton:verify-tarballs, r=matklad
Verify tarballs don't extract into other directories Continuation of rust-lang/crates.io#1054 except support on the Cargo side of things
2 parents 8118b02 + 0b9e382 commit 696413c

File tree

3 files changed

+80
-4
lines changed

3 files changed

+80
-4
lines changed

src/cargo/sources/registry/mod.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,33 @@ impl<'cfg> RegistrySource<'cfg> {
313313

314314
let gz = GzDecoder::new(tarball.file())?;
315315
let mut tar = Archive::new(gz);
316-
tar.unpack(dst.parent().unwrap())?;
316+
let prefix = dst.file_name().unwrap();
317+
let parent = dst.parent().unwrap();
318+
for entry in tar.entries()? {
319+
let mut entry = entry.chain_err(|| "failed to iterate over archive")?;
320+
let entry_path = entry.path()
321+
.chain_err(|| "failed to read entry path")?
322+
.into_owned();
323+
324+
// We're going to unpack this tarball into the global source
325+
// directory, but we want to make sure that it doesn't accidentally
326+
// (or maliciously) overwrite source code from other crates. Cargo
327+
// itself should never generate a tarball that hits this error, and
328+
// crates.io should also block uploads with these sorts of tarballs,
329+
// but be extra sure by adding a check here as well.
330+
if !entry_path.starts_with(prefix) {
331+
return Err(format!("invalid tarball downloaded, contains \
332+
a file at {:?} which isn't under {:?}",
333+
entry_path, prefix).into())
334+
}
335+
336+
// Once that's verified, unpack the entry as usual.
337+
entry.unpack_in(parent).chain_err(|| {
338+
format!("failed to unpack entry at `{}`", entry_path.display())
339+
})?;
340+
}
317341
File::create(&ok)?;
318-
Ok(dst)
342+
Ok(dst.clone())
319343
}
320344

321345
fn do_update(&mut self) -> CargoResult<()> {

tests/cargotest/support/registry.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct Package {
2424
vers: String,
2525
deps: Vec<Dependency>,
2626
files: Vec<(String, String)>,
27+
extra_files: Vec<(String, String)>,
2728
yanked: bool,
2829
features: HashMap<String, Vec<String>>,
2930
local: bool,
@@ -72,6 +73,7 @@ impl Package {
7273
vers: vers.to_string(),
7374
deps: Vec::new(),
7475
files: Vec::new(),
76+
extra_files: Vec::new(),
7577
yanked: false,
7678
features: HashMap::new(),
7779
local: false,
@@ -88,6 +90,11 @@ impl Package {
8890
self
8991
}
9092

93+
pub fn extra_file(&mut self, name: &str, contents: &str) -> &mut Package {
94+
self.extra_files.push((name.to_string(), contents.to_string()));
95+
self
96+
}
97+
9198
pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
9299
self.full_dep(name, vers, None, "normal", &[])
93100
}
@@ -235,14 +242,22 @@ impl Package {
235242
self.append(&mut a, name, contents);
236243
}
237244
}
245+
for &(ref name, ref contents) in self.extra_files.iter() {
246+
self.append_extra(&mut a, name, contents);
247+
}
238248
}
239249

240250
fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, contents: &str) {
251+
self.append_extra(ar,
252+
&format!("{}-{}/{}", self.name, self.vers, file),
253+
contents);
254+
}
255+
256+
fn append_extra<W: Write>(&self, ar: &mut Builder<W>, path: &str, contents: &str) {
241257
let mut header = Header::new_ustar();
242258
header.set_size(contents.len() as u64);
243-
t!(header.set_path(format!("{}-{}/{}", self.name, self.vers, file)));
259+
t!(header.set_path(path));
244260
header.set_cksum();
245-
246261
t!(ar.append(&header, contents.as_bytes()));
247262
}
248263

tests/registry.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,3 +1408,40 @@ fn vv_prints_warnings() {
14081408
assert_that(p.cargo("build").arg("-vv"),
14091409
execs().with_status(0));
14101410
}
1411+
1412+
#[test]
1413+
fn bad_and_or_malicious_packages_rejected() {
1414+
Package::new("foo", "0.2.0")
1415+
.extra_file("foo-0.1.0/src/lib.rs", "")
1416+
.publish();
1417+
1418+
let p = project("foo")
1419+
.file("Cargo.toml", r#"
1420+
[project]
1421+
name = "fo"
1422+
version = "0.5.0"
1423+
authors = []
1424+
1425+
[dependencies]
1426+
foo = "0.2"
1427+
"#)
1428+
.file("src/main.rs", "fn main() {}");
1429+
p.build();
1430+
1431+
assert_that(p.cargo("build").arg("-vv"),
1432+
execs().with_status(101)
1433+
.with_stderr("\
1434+
[UPDATING] [..]
1435+
[DOWNLOADING] [..]
1436+
error: unable to get packages from source
1437+
1438+
Caused by:
1439+
failed to download [..]
1440+
1441+
Caused by:
1442+
failed to unpack [..]
1443+
1444+
Caused by:
1445+
[..] contains a file at \"foo-0.1.0/src/lib.rs\" which isn't under \"foo-0.2.0\"
1446+
"));
1447+
}

0 commit comments

Comments
 (0)