Skip to content

Commit fd1ab6a

Browse files
committed
Auto merge of #4096 - nipunn1313:tarball_up, r=Turbo87
controllers::krate::publish: Move reading of tarball up from uploader to publish Pull upward hex_cksum and verify_tarball to publish to the point before readme rendering - so that readme can leverage information from tarball. Additionally, it will avoid rendering readme for failure cases where the tarball doesn't verify. Will be useful to access cargo_vcs_info earlier on (for #3484)
2 parents 872ccec + 5da5717 commit fd1ab6a

File tree

2 files changed

+65
-67
lines changed

2 files changed

+65
-67
lines changed

src/controllers/krate/publish.rs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! Functionality related to publishing a new crate or version of a crate.
22
3+
use flate2::read::GzDecoder;
34
use hex::ToHex;
5+
use sha2::{Digest, Sha256};
6+
use std::io::Read;
47
use std::sync::Arc;
58
use swirl::Job;
69

@@ -14,7 +17,7 @@ use crate::models::{
1417
use crate::render;
1518
use crate::schema::*;
1619
use crate::util::errors::{cargo_err, AppResult};
17-
use crate::util::{read_fill, read_le_u32, Maximums};
20+
use crate::util::{read_fill, read_le_u32, LimitErrorReader, Maximums};
1821
use crate::views::{
1922
EncodableCrate, EncodableCrateDependency, EncodableCrateUpload, GoodCrate, PublishWarnings,
2023
};
@@ -186,6 +189,12 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
186189
let ignored_invalid_badges = Badge::update_crate(&conn, &krate, new_crate.badges.as_ref())?;
187190
let top_versions = krate.top_versions(&conn)?;
188191

192+
// Read tarball from request
193+
let mut tarball = Vec::new();
194+
LimitErrorReader::new(req.body(), maximums.max_upload_size).read_to_end(&mut tarball)?;
195+
let hex_cksum: String = Sha256::digest(&tarball).encode_hex();
196+
verify_tarball(&krate, vers, &tarball, maximums.max_unpack_size)?;
197+
189198
if let Some(readme) = new_crate.readme {
190199
render::render_and_upload_readme(
191200
version.id,
@@ -198,12 +207,10 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
198207
.enqueue(&conn)?;
199208
}
200209

201-
let cksum = app
202-
.config
210+
// Upload crate tarball
211+
app.config
203212
.uploader()
204-
.upload_crate(req, &krate, maximums, vers)?;
205-
206-
let hex_cksum = cksum.encode_hex::<String>();
213+
.upload_crate(&app, tarball, &krate, vers)?;
207214

208215
// Register this crate in our local git repo.
209216
let git_crate = git::Crate {
@@ -356,6 +363,51 @@ pub fn add_dependencies(
356363
Ok(git_deps)
357364
}
358365

366+
fn verify_tarball(
367+
krate: &Crate,
368+
vers: &semver::Version,
369+
tarball: &[u8],
370+
max_unpack: u64,
371+
) -> AppResult<()> {
372+
// All our data is currently encoded with gzip
373+
let decoder = GzDecoder::new(tarball);
374+
375+
// Don't let gzip decompression go into the weeeds, apply a fixed cap after
376+
// which point we say the decompressed source is "too large".
377+
let decoder = LimitErrorReader::new(decoder, max_unpack);
378+
379+
// Use this I/O object now to take a peek inside
380+
let mut archive = tar::Archive::new(decoder);
381+
let prefix = format!("{}-{}", krate.name, vers);
382+
for entry in archive.entries()? {
383+
let entry = entry.map_err(|err| {
384+
err.chain(cargo_err(
385+
"uploaded tarball is malformed or too large when decompressed",
386+
))
387+
})?;
388+
389+
// Verify that all entries actually start with `$name-$vers/`.
390+
// Historically Cargo didn't verify this on extraction so you could
391+
// upload a tarball that contains both `foo-0.1.0/` source code as well
392+
// as `bar-0.1.0/` source code, and this could overwrite other crates in
393+
// the registry!
394+
if !entry.path()?.starts_with(&prefix) {
395+
return Err(cargo_err("invalid tarball uploaded"));
396+
}
397+
398+
// Historical versions of the `tar` crate which Cargo uses internally
399+
// don't properly prevent hard links and symlinks from overwriting
400+
// arbitrary files on the filesystem. As a bit of a hammer we reject any
401+
// tarball with these sorts of links. Cargo doesn't currently ever
402+
// generate a tarball with these file types so this should work for now.
403+
let entry_type = entry.header().entry_type();
404+
if entry_type.is_hard_link() || entry_type.is_symlink() {
405+
return Err(cargo_err("invalid tarball uploaded"));
406+
}
407+
}
408+
Ok(())
409+
}
410+
359411
#[cfg(test)]
360412
mod tests {
361413
use super::missing_metadata_error_message;

src/uploaders.rs

Lines changed: 7 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
use anyhow::Result;
2-
use conduit::RequestExt;
3-
use flate2::read::GzDecoder;
42
use reqwest::{blocking::Client, header};
5-
use sha2::{Digest, Sha256};
63

7-
use crate::util::errors::{cargo_err, internal, AppError, AppResult};
8-
use crate::util::{LimitErrorReader, Maximums};
4+
use crate::app::App;
5+
use crate::util::errors::{internal, AppResult};
96

107
use std::env;
118
use std::fs::{self, File};
12-
use std::io::{Cursor, Read};
9+
use std::io::Cursor;
1310
use std::sync::Arc;
1411

15-
use crate::middleware::app::RequestApp;
1612
use crate::models::Crate;
1713

1814
const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable";
@@ -129,17 +125,12 @@ impl Uploader {
129125
/// Uploads a crate and returns the checksum of the uploaded crate file.
130126
pub fn upload_crate(
131127
&self,
132-
req: &mut dyn RequestExt,
128+
app: &Arc<App>,
129+
body: Vec<u8>,
133130
krate: &Crate,
134-
maximums: Maximums,
135131
vers: &semver::Version,
136-
) -> AppResult<[u8; 32]> {
137-
let app = Arc::clone(req.app());
132+
) -> AppResult<()> {
138133
let path = Uploader::crate_path(&krate.name, &vers.to_string());
139-
let mut body = Vec::new();
140-
LimitErrorReader::new(req.body(), maximums.max_upload_size).read_to_end(&mut body)?;
141-
verify_tarball(krate, vers, &body, maximums.max_unpack_size)?;
142-
let checksum = Sha256::digest(&body);
143134
let content_length = body.len() as u64;
144135
let content = Cursor::new(body);
145136
let mut extra_headers = header::HeaderMap::new();
@@ -156,7 +147,7 @@ impl Uploader {
156147
extra_headers,
157148
)
158149
.map_err(|e| internal(&format_args!("failed to upload crate: {}", e)))?;
159-
Ok(checksum.into())
150+
Ok(())
160151
}
161152

162153
pub(crate) fn upload_readme(
@@ -185,48 +176,3 @@ impl Uploader {
185176
Ok(())
186177
}
187178
}
188-
189-
fn verify_tarball(
190-
krate: &Crate,
191-
vers: &semver::Version,
192-
tarball: &[u8],
193-
max_unpack: u64,
194-
) -> AppResult<()> {
195-
// All our data is currently encoded with gzip
196-
let decoder = GzDecoder::new(tarball);
197-
198-
// Don't let gzip decompression go into the weeeds, apply a fixed cap after
199-
// which point we say the decompressed source is "too large".
200-
let decoder = LimitErrorReader::new(decoder, max_unpack);
201-
202-
// Use this I/O object now to take a peek inside
203-
let mut archive = tar::Archive::new(decoder);
204-
let prefix = format!("{}-{}", krate.name, vers);
205-
for entry in archive.entries()? {
206-
let entry = entry.map_err(|err| {
207-
err.chain(cargo_err(
208-
"uploaded tarball is malformed or too large when decompressed",
209-
))
210-
})?;
211-
212-
// Verify that all entries actually start with `$name-$vers/`.
213-
// Historically Cargo didn't verify this on extraction so you could
214-
// upload a tarball that contains both `foo-0.1.0/` source code as well
215-
// as `bar-0.1.0/` source code, and this could overwrite other crates in
216-
// the registry!
217-
if !entry.path()?.starts_with(&prefix) {
218-
return Err(cargo_err("invalid tarball uploaded"));
219-
}
220-
221-
// Historical versions of the `tar` crate which Cargo uses internally
222-
// don't properly prevent hard links and symlinks from overwriting
223-
// arbitrary files on the filesystem. As a bit of a hammer we reject any
224-
// tarball with these sorts of links. Cargo doesn't currently ever
225-
// generate a tarball with these file types so this should work for now.
226-
let entry_type = entry.header().entry_type();
227-
if entry_type.is_hard_link() || entry_type.is_symlink() {
228-
return Err(cargo_err("invalid tarball uploaded"));
229-
}
230-
}
231-
Ok(())
232-
}

0 commit comments

Comments
 (0)