184184//! [`IndexPackage`]: index::IndexPackage
185185
186186use std:: collections:: HashSet ;
187+ use std:: fs;
187188use std:: fs:: { File , OpenOptions } ;
188189use std:: io;
189190use std:: io:: Read ;
@@ -196,6 +197,7 @@ use cargo_util::paths::{self, exclude_from_backups_and_indexing};
196197use flate2:: read:: GzDecoder ;
197198use log:: debug;
198199use serde:: Deserialize ;
200+ use serde:: Serialize ;
199201use tar:: Archive ;
200202
201203use crate :: core:: dependency:: Dependency ;
@@ -219,6 +221,14 @@ pub const CRATES_IO_HTTP_INDEX: &str = "sparse+https://index.crates.io/";
219221pub const CRATES_IO_REGISTRY : & str = "crates-io" ;
220222pub const CRATES_IO_DOMAIN : & str = "crates.io" ;
221223
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+
222232/// A [`Source`] implementation for a local or a remote registry.
223233///
224234/// This contains common functionality that is shared between each registry
@@ -546,6 +556,11 @@ impl<'cfg> RegistrySource<'cfg> {
546556 /// `.crate` files making `.cargo-ok` a symlink causing cargo to write "ok"
547557 /// to any arbitrary file on the filesystem it has permission to.
548558 ///
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+ ///
549564 /// This is all a long-winded way of explaining the circumstances that might
550565 /// cause a directory to contain a `.cargo-ok` file that is empty or
551566 /// otherwise corrupted. Either this was extracted by a version of Rust
@@ -567,15 +582,23 @@ impl<'cfg> RegistrySource<'cfg> {
567582 let path = dst. join ( PACKAGE_SOURCE_LOCK ) ;
568583 let path = self . config . assert_package_cache_locked ( & path) ;
569584 let unpack_dir = path. parent ( ) . unwrap ( ) ;
570- match path. metadata ( ) {
571- Ok ( meta) if meta. len ( ) > 0 => return Ok ( unpack_dir. to_path_buf ( ) ) ,
572- Ok ( _meta) => {
573- // See comment of `unpack_package` about why removing all stuff.
574- log:: warn!( "unexpected length of {path:?}, clearing cache" ) ;
575- paths:: remove_dir_all ( dst. as_path_unlocked ( ) ) ?;
576- }
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+ } ,
577600 Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => { }
578- 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}" ) ,
579602 }
580603 dst. create_dir ( ) ?;
581604 let mut tar = {
@@ -639,7 +662,9 @@ impl<'cfg> RegistrySource<'cfg> {
639662 . write ( true )
640663 . open ( & path)
641664 . with_context ( || format ! ( "failed to open `{}`" , path. display( ) ) ) ?;
642- write ! ( ok, "ok" ) ?;
665+
666+ let lock_meta = LockMetadata { v : 1 } ;
667+ write ! ( ok, "{}" , serde_json:: to_string( & lock_meta) . unwrap( ) ) ?;
643668
644669 Ok ( unpack_dir. to_path_buf ( ) )
645670 }
0 commit comments