1
1
//! Functionality related to publishing a new crate or version of a crate.
2
2
3
+ use flate2:: read:: GzDecoder ;
3
4
use hex:: ToHex ;
5
+ use sha2:: { Digest , Sha256 } ;
6
+ use std:: io:: Read ;
4
7
use std:: sync:: Arc ;
5
8
use swirl:: Job ;
6
9
@@ -14,7 +17,7 @@ use crate::models::{
14
17
use crate :: render;
15
18
use crate :: schema:: * ;
16
19
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 } ;
18
21
use crate :: views:: {
19
22
EncodableCrate , EncodableCrateDependency , EncodableCrateUpload , GoodCrate , PublishWarnings ,
20
23
} ;
@@ -185,6 +188,12 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
185
188
let ignored_invalid_badges = Badge :: update_crate ( & conn, & krate, new_crate. badges . as_ref ( ) ) ?;
186
189
let top_versions = krate. top_versions ( & conn) ?;
187
190
191
+ // Read tarball from request
192
+ let mut tarball = Vec :: new ( ) ;
193
+ LimitErrorReader :: new ( req. body ( ) , maximums. max_upload_size ) . read_to_end ( & mut tarball) ?;
194
+ let hex_cksum: String = Sha256 :: digest ( & tarball) . encode_hex ( ) ;
195
+ verify_tarball ( & krate, vers, & tarball, maximums. max_unpack_size ) ?;
196
+
188
197
if let Some ( readme) = new_crate. readme {
189
198
render:: render_and_upload_readme (
190
199
version. id ,
@@ -197,12 +206,10 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
197
206
. enqueue ( & conn) ?;
198
207
}
199
208
200
- let cksum = app
201
- . config
209
+ // Upload crate tarball
210
+ app . config
202
211
. uploader ( )
203
- . upload_crate ( req, & krate, maximums, vers) ?;
204
-
205
- let hex_cksum = cksum. encode_hex :: < String > ( ) ;
212
+ . upload_crate ( & app, tarball, & krate, vers) ?;
206
213
207
214
// Register this crate in our local git repo.
208
215
let git_crate = git:: Crate {
@@ -355,6 +362,51 @@ pub fn add_dependencies(
355
362
Ok ( git_deps)
356
363
}
357
364
365
+ fn verify_tarball (
366
+ krate : & Crate ,
367
+ vers : & semver:: Version ,
368
+ tarball : & [ u8 ] ,
369
+ max_unpack : u64 ,
370
+ ) -> AppResult < ( ) > {
371
+ // All our data is currently encoded with gzip
372
+ let decoder = GzDecoder :: new ( tarball) ;
373
+
374
+ // Don't let gzip decompression go into the weeeds, apply a fixed cap after
375
+ // which point we say the decompressed source is "too large".
376
+ let decoder = LimitErrorReader :: new ( decoder, max_unpack) ;
377
+
378
+ // Use this I/O object now to take a peek inside
379
+ let mut archive = tar:: Archive :: new ( decoder) ;
380
+ let prefix = format ! ( "{}-{}" , krate. name, vers) ;
381
+ for entry in archive. entries ( ) ? {
382
+ let entry = entry. map_err ( |err| {
383
+ err. chain ( cargo_err (
384
+ "uploaded tarball is malformed or too large when decompressed" ,
385
+ ) )
386
+ } ) ?;
387
+
388
+ // Verify that all entries actually start with `$name-$vers/`.
389
+ // Historically Cargo didn't verify this on extraction so you could
390
+ // upload a tarball that contains both `foo-0.1.0/` source code as well
391
+ // as `bar-0.1.0/` source code, and this could overwrite other crates in
392
+ // the registry!
393
+ if !entry. path ( ) ?. starts_with ( & prefix) {
394
+ return Err ( cargo_err ( "invalid tarball uploaded" ) ) ;
395
+ }
396
+
397
+ // Historical versions of the `tar` crate which Cargo uses internally
398
+ // don't properly prevent hard links and symlinks from overwriting
399
+ // arbitrary files on the filesystem. As a bit of a hammer we reject any
400
+ // tarball with these sorts of links. Cargo doesn't currently ever
401
+ // generate a tarball with these file types so this should work for now.
402
+ let entry_type = entry. header ( ) . entry_type ( ) ;
403
+ if entry_type. is_hard_link ( ) || entry_type. is_symlink ( ) {
404
+ return Err ( cargo_err ( "invalid tarball uploaded" ) ) ;
405
+ }
406
+ }
407
+ Ok ( ( ) )
408
+ }
409
+
358
410
#[ cfg( test) ]
359
411
mod tests {
360
412
use super :: missing_metadata_error_message;
0 commit comments