184184//! [`IndexPackage`]: index::IndexPackage
185185
186186use std:: collections:: HashSet ;
187+ use std:: fs;
187188use std:: fs:: { File , OpenOptions } ;
188- use std:: io:: { self , Write } ;
189+ use std:: io;
190+ use std:: io:: Read ;
191+ use std:: io:: Write ;
189192use std:: path:: { Path , PathBuf } ;
190193use std:: task:: { ready, Poll } ;
191194
@@ -194,6 +197,7 @@ use cargo_util::paths::{self, exclude_from_backups_and_indexing};
194197use flate2:: read:: GzDecoder ;
195198use log:: debug;
196199use serde:: Deserialize ;
200+ use serde:: Serialize ;
197201use tar:: Archive ;
198202
199203use crate :: core:: dependency:: Dependency ;
@@ -217,6 +221,14 @@ pub const CRATES_IO_HTTP_INDEX: &str = "sparse+https://index.crates.io/";
217221pub const CRATES_IO_REGISTRY : & str = "crates-io" ;
218222pub const CRATES_IO_DOMAIN : & str = "crates.io" ;
219223
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+
220232/// A [`Source`] implementation for a local or a remote registry.
221233///
222234/// This contains common functionality that is shared between each registry
@@ -544,6 +556,11 @@ impl<'cfg> RegistrySource<'cfg> {
544556 /// `.crate` files making `.cargo-ok` a symlink causing cargo to write "ok"
545557 /// to any arbitrary file on the filesystem it has permission to.
546558 ///
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+ ///
547564 /// This is all a long-winded way of explaining the circumstances that might
548565 /// cause a directory to contain a `.cargo-ok` file that is empty or
549566 /// otherwise corrupted. Either this was extracted by a version of Rust
@@ -565,22 +582,32 @@ impl<'cfg> RegistrySource<'cfg> {
565582 let path = dst. join ( PACKAGE_SOURCE_LOCK ) ;
566583 let path = self . config . assert_package_cache_locked ( & path) ;
567584 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+ } ,
575600 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}" ) ,
577602 }
578603 dst. create_dir ( ) ?;
579604 let mut tar = {
580605 let size_limit = max_unpack_size ( self . config , tarball. metadata ( ) ?. len ( ) ) ;
581606 let gz = GzDecoder :: new ( tarball) ;
582607 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
584611 } ;
585612 let prefix = unpack_dir. file_name ( ) . unwrap ( ) ;
586613 let parent = unpack_dir. parent ( ) . unwrap ( ) ;
@@ -635,7 +662,9 @@ impl<'cfg> RegistrySource<'cfg> {
635662 . write ( true )
636663 . open ( & path)
637664 . 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( ) ) ?;
639668
640669 Ok ( unpack_dir. to_path_buf ( ) )
641670 }
@@ -908,3 +937,16 @@ fn max_unpack_size(config: &Config, size: u64) -> u64 {
908937
909938 u64:: max ( max_unpack_size, size * max_compression_ratio as u64 )
910939}
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