184
184
//! [`IndexPackage`]: index::IndexPackage
185
185
186
186
use std:: collections:: HashSet ;
187
+ use std:: fs;
187
188
use std:: fs:: { File , OpenOptions } ;
188
- use std:: io:: { self , Write } ;
189
+ use std:: io;
190
+ use std:: io:: Read ;
191
+ use std:: io:: Write ;
189
192
use std:: path:: { Path , PathBuf } ;
190
193
use std:: task:: { ready, Poll } ;
191
194
@@ -194,6 +197,7 @@ use cargo_util::paths::{self, exclude_from_backups_and_indexing};
194
197
use flate2:: read:: GzDecoder ;
195
198
use log:: debug;
196
199
use serde:: Deserialize ;
200
+ use serde:: Serialize ;
197
201
use tar:: Archive ;
198
202
199
203
use crate :: core:: dependency:: Dependency ;
@@ -217,6 +221,14 @@ pub const CRATES_IO_HTTP_INDEX: &str = "sparse+https://index.crates.io/";
217
221
pub const CRATES_IO_REGISTRY : & str = "crates-io" ;
218
222
pub const CRATES_IO_DOMAIN : & str = "crates.io" ;
219
223
224
+ /// The content inside `.cargo-ok`.
225
+ /// See [`RegistrySource::unpack_package`] for more.
226
+ #[ derive( Deserialize , Serialize ) ]
227
+ struct LockMetadata {
228
+ /// The version of `.cargo-ok` file
229
+ v : u32 ,
230
+ }
231
+
220
232
/// A [`Source`] implementation for a local or a remote registry.
221
233
///
222
234
/// This contains common functionality that is shared between each registry
@@ -544,6 +556,11 @@ impl<'cfg> RegistrySource<'cfg> {
544
556
/// `.crate` files making `.cargo-ok` a symlink causing cargo to write "ok"
545
557
/// to any arbitrary file on the filesystem it has permission to.
546
558
///
559
+ /// In 1.71, `.cargo-ok` changed to contain a JSON `{ v: 1 }` to indicate
560
+ /// the version of it. A failure of parsing will result in a heavy-hammer
561
+ /// approach that unpacks the `.crate` file again. This is in response to a
562
+ /// security issue that the unpacking didn't respect umask on Unix systems.
563
+ ///
547
564
/// This is all a long-winded way of explaining the circumstances that might
548
565
/// cause a directory to contain a `.cargo-ok` file that is empty or
549
566
/// otherwise corrupted. Either this was extracted by a version of Rust
@@ -565,22 +582,32 @@ impl<'cfg> RegistrySource<'cfg> {
565
582
let path = dst. join ( PACKAGE_SOURCE_LOCK ) ;
566
583
let path = self . config . assert_package_cache_locked ( & path) ;
567
584
let unpack_dir = path. parent ( ) . unwrap ( ) ;
568
- match path. metadata ( ) {
569
- Ok ( meta) if meta. len ( ) > 0 => return Ok ( unpack_dir. to_path_buf ( ) ) ,
570
- Ok ( _meta) => {
571
- // See comment of `unpack_package` about why removing all stuff.
572
- log:: warn!( "unexpected length of {path:?}, clearing cache" ) ;
573
- paths:: remove_dir_all ( dst. as_path_unlocked ( ) ) ?;
574
- }
585
+ match fs:: read_to_string ( path) {
586
+ Ok ( ok) => match serde_json:: from_str :: < LockMetadata > ( & ok) {
587
+ Ok ( lock_meta) if lock_meta. v == 1 => {
588
+ return Ok ( unpack_dir. to_path_buf ( ) ) ;
589
+ }
590
+ _ => {
591
+ if ok == "ok" {
592
+ log:: debug!( "old `ok` content found, clearing cache" ) ;
593
+ } else {
594
+ log:: warn!( "unrecognized .cargo-ok content, clearing cache: {ok}" ) ;
595
+ }
596
+ // See comment of `unpack_package` about why removing all stuff.
597
+ paths:: remove_dir_all ( dst. as_path_unlocked ( ) ) ?;
598
+ }
599
+ } ,
575
600
Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => { }
576
- Err ( e) => anyhow:: bail!( "failed to access package completion {path:?}: {e}" ) ,
601
+ Err ( e) => anyhow:: bail!( "unable to read .cargo-ok file at {path:?}: {e}" ) ,
577
602
}
578
603
dst. create_dir ( ) ?;
579
604
let mut tar = {
580
605
let size_limit = max_unpack_size ( self . config , tarball. metadata ( ) ?. len ( ) ) ;
581
606
let gz = GzDecoder :: new ( tarball) ;
582
607
let gz = LimitErrorReader :: new ( gz, size_limit) ;
583
- Archive :: new ( gz)
608
+ let mut tar = Archive :: new ( gz) ;
609
+ set_mask ( & mut tar) ;
610
+ tar
584
611
} ;
585
612
let prefix = unpack_dir. file_name ( ) . unwrap ( ) ;
586
613
let parent = unpack_dir. parent ( ) . unwrap ( ) ;
@@ -635,7 +662,9 @@ impl<'cfg> RegistrySource<'cfg> {
635
662
. write ( true )
636
663
. open ( & path)
637
664
. with_context ( || format ! ( "failed to open `{}`" , path. display( ) ) ) ?;
638
- write ! ( ok, "ok" ) ?;
665
+
666
+ let lock_meta = LockMetadata { v : 1 } ;
667
+ write ! ( ok, "{}" , serde_json:: to_string( & lock_meta) . unwrap( ) ) ?;
639
668
640
669
Ok ( unpack_dir. to_path_buf ( ) )
641
670
}
@@ -908,3 +937,16 @@ fn max_unpack_size(config: &Config, size: u64) -> u64 {
908
937
909
938
u64:: max ( max_unpack_size, size * max_compression_ratio as u64 )
910
939
}
940
+
941
+ /// Set the current [`umask`] value for the given tarball. No-op on non-Unix
942
+ /// platforms.
943
+ ///
944
+ /// On Windows, tar only looks at user permissions and tries to set the "read
945
+ /// only" attribute, so no-op as well.
946
+ ///
947
+ /// [`umask`]: https://man7.org/linux/man-pages/man2/umask.2.html
948
+ #[ allow( unused_variables) ]
949
+ fn set_mask < R : Read > ( tar : & mut Archive < R > ) {
950
+ #[ cfg( unix) ]
951
+ tar. set_mask ( crate :: util:: get_umask ( ) ) ;
952
+ }
0 commit comments