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
} ;
@@ -186,6 +189,12 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
186
189
let ignored_invalid_badges = Badge :: update_crate ( & conn, & krate, new_crate. badges . as_ref ( ) ) ?;
187
190
let top_versions = krate. top_versions ( & conn) ?;
188
191
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
+
189
198
if let Some ( readme) = new_crate. readme {
190
199
render:: render_and_upload_readme (
191
200
version. id ,
@@ -198,12 +207,10 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
198
207
. enqueue ( & conn) ?;
199
208
}
200
209
201
- let cksum = app
202
- . config
210
+ // Upload crate tarball
211
+ app . config
203
212
. uploader ( )
204
- . upload_crate ( req, & krate, maximums, vers) ?;
205
-
206
- let hex_cksum = cksum. encode_hex :: < String > ( ) ;
213
+ . upload_crate ( & app, tarball, & krate, vers) ?;
207
214
208
215
// Register this crate in our local git repo.
209
216
let git_crate = git:: Crate {
@@ -356,6 +363,51 @@ pub fn add_dependencies(
356
363
Ok ( git_deps)
357
364
}
358
365
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
+
359
411
#[ cfg( test) ]
360
412
mod tests {
361
413
use super :: missing_metadata_error_message;
0 commit comments