@@ -30,7 +30,7 @@ mod blocking_io {
3030 #[ test]
3131 fn fetch_shallow_no_checkout_then_unshallow ( ) -> crate :: Result {
3232 let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
33- let called_configure_remote = std:: sync:: Arc :: new ( std :: sync :: atomic :: AtomicBool :: default ( ) ) ;
33+ let called_configure_remote = std:: sync:: Arc :: new ( AtomicBool :: default ( ) ) ;
3434 let remote_name = "special" ;
3535 let desired_fetch_tags = gix:: remote:: fetch:: Tags :: Included ;
3636 let mut prepare = gix:: prepare_clone_bare ( remote:: repo ( "base" ) . path ( ) , tmp. path ( ) ) ?
@@ -50,7 +50,7 @@ mod blocking_io {
5050 }
5151 } )
5252 . with_shallow ( Shallow :: DepthAtRemote ( 2 . try_into ( ) . expect ( "non-zero" ) ) ) ;
53- let ( repo, _out) = prepare. fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
53+ let ( repo, _out) = prepare. fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
5454 drop ( prepare) ;
5555
5656 assert_eq ! (
@@ -95,7 +95,7 @@ mod blocking_io {
9595 let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
9696 let ( repo, _out) = gix:: prepare_clone_bare ( remote:: repo ( "base" ) . path ( ) , tmp. path ( ) ) ?
9797 . with_shallow ( Shallow :: DepthAtRemote ( 1 . try_into ( ) ?) )
98- . fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
98+ . fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
9999
100100 assert ! ( repo. is_shallow( ) , "repository should be shallow" ) ;
101101
@@ -129,7 +129,7 @@ mod blocking_io {
129129 Default :: default ( ) ,
130130 gix:: open:: Options :: isolated ( ) . config_overrides ( [ Clone :: REJECT_SHALLOW . validated_assignment_fmt ( & true ) ?] ) ,
131131 ) ?
132- . fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) )
132+ . fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) )
133133 . unwrap_err ( ) ;
134134 assert ! (
135135 matches!(
@@ -148,7 +148,7 @@ mod blocking_io {
148148 let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
149149 let ( repo, _change) = gix:: prepare_clone_bare ( remote:: repo ( "base.shallow" ) . path ( ) , tmp. path ( ) ) ?
150150 . with_in_memory_config_overrides ( Some ( "my.marker=1" ) )
151- . fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
151+ . fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
152152 assert_eq ! (
153153 shallow_ids( & repo, "present" ) ?,
154154 vec![
@@ -181,7 +181,7 @@ mod blocking_io {
181181 r. replace_refspecs ( Some ( "refs/heads/main:refs/remotes/origin/main" ) , Direction :: Fetch ) ?;
182182 Ok ( r)
183183 } )
184- . fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
184+ . fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
185185
186186 assert ! ( repo. is_shallow( ) ) ;
187187 assert_eq ! (
@@ -256,7 +256,7 @@ mod blocking_io {
256256 . collect ( ) ,
257257 since_cutoff : None ,
258258 } )
259- . fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
259+ . fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
260260
261261 assert ! ( repo. is_shallow( ) ) ;
262262 assert_eq ! (
@@ -281,7 +281,7 @@ mod blocking_io {
281281 #[ test]
282282 fn fetch_only_with_configuration ( ) -> crate :: Result {
283283 let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
284- let called_configure_remote = std:: sync:: Arc :: new ( std :: sync :: atomic :: AtomicBool :: default ( ) ) ;
284+ let called_configure_remote = std:: sync:: Arc :: new ( AtomicBool :: default ( ) ) ;
285285 let remote_name = "special" ;
286286 let desired_fetch_tags = gix:: remote:: fetch:: Tags :: Included ;
287287 let mut prepare = gix:: clone:: PrepareFetch :: new (
@@ -306,7 +306,7 @@ mod blocking_io {
306306 Ok ( r)
307307 }
308308 } ) ;
309- let ( repo, out) = prepare. fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
309+ let ( repo, out) = prepare. fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
310310 drop ( prepare) ;
311311
312312 assert ! (
@@ -540,16 +540,52 @@ mod blocking_io {
540540 Default :: default ( ) ,
541541 restricted ( ) ,
542542 ) ?;
543- let ( mut checkout, _out) =
544- prepare. fetch_then_checkout ( gix:: progress:: Discard , & std:: sync:: atomic:: AtomicBool :: default ( ) ) ?;
545- let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & std:: sync:: atomic:: AtomicBool :: default ( ) ) ?;
543+ let ( mut checkout, _out) = prepare. fetch_then_checkout ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
544+ let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
546545
547546 let index = repo. index ( ) ?;
548547 assert_eq ! ( index. entries( ) . len( ) , 1 , "All entries are known as per HEAD tree" ) ;
549548
550549 assure_index_entries_on_disk ( & index, repo. workdir ( ) . expect ( "non-bare" ) ) ;
551550 Ok ( ( ) )
552551 }
552+
553+ #[ test]
554+ #[ cfg( unix) ]
555+ fn fetch_and_checkout_does_not_follow_delayed_symlink_prefixes ( ) -> crate :: Result {
556+ use std:: os:: unix:: fs:: PermissionsExt ;
557+
558+ let fixture = gix_testtools:: scripted_fixture_read_only ( "make_symlink_prefix_reuse_advisory.sh" ) ?;
559+ let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
560+ let mut prepare = gix:: clone:: PrepareFetch :: new (
561+ fixture. join ( "malicious.git" ) ,
562+ tmp. path ( ) ,
563+ gix:: create:: Kind :: WithWorktree ,
564+ Default :: default ( ) ,
565+ restricted ( ) ,
566+ ) ?;
567+
568+ let ( mut checkout, _out) = prepare. fetch_then_checkout ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
569+ let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
570+
571+ let git_dir = repo. git_dir ( ) ;
572+ let hook_path = git_dir. join ( "hooks" ) . join ( "post-checkout" ) ;
573+ assert ! (
574+ hook_path. symlink_metadata( ) . is_err( ) ,
575+ "checkout must not write attacker-controlled hooks through a symlink prefix"
576+ ) ;
577+
578+ let worktree = repo. workdir ( ) . expect ( "non-bare" ) ;
579+ let payload = worktree. join ( "payload" ) ;
580+ assert ! ( payload. is_file( ) , "payload itself is checked out" ) ;
581+ assert_ne ! (
582+ payload. metadata( ) ?. permissions( ) . mode( ) & 0o111 ,
583+ 0 ,
584+ "payload keeps its executable bits"
585+ ) ;
586+ Ok ( ( ) )
587+ }
588+
553589 #[ test]
554590 fn fetch_and_checkout_specific_ref ( ) -> crate :: Result {
555591 let tmp = gix_testtools:: tempfile:: TempDir :: new ( ) ?;
@@ -563,10 +599,9 @@ mod blocking_io {
563599 restricted ( ) ,
564600 ) ?
565601 . with_ref_name ( Some ( ref_to_checkout) ) ?;
566- let ( mut checkout, _out) =
567- prepare. fetch_then_checkout ( gix:: progress:: Discard , & std:: sync:: atomic:: AtomicBool :: default ( ) ) ?;
602+ let ( mut checkout, _out) = prepare. fetch_then_checkout ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
568603
569- let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
604+ let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
570605
571606 assert_eq ! (
572607 repo. references( ) ?. all( ) ?. count( ) - 2 ,
@@ -613,7 +648,7 @@ mod blocking_io {
613648 . with_ref_name ( Some ( ref_to_checkout) ) ?;
614649
615650 let err = prepare
616- . fetch_then_checkout ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) )
651+ . fetch_then_checkout ( gix:: progress:: Discard , & AtomicBool :: default ( ) )
617652 . unwrap_err ( ) ;
618653 assert_eq ! (
619654 err. to_string( ) ,
@@ -654,10 +689,9 @@ mod blocking_io {
654689 restricted ( ) ,
655690 ) ?
656691 . with_ref_name ( Some ( ref_to_checkout) ) ?;
657- let ( mut checkout, _out) =
658- prepare. fetch_then_checkout ( gix:: progress:: Discard , & std:: sync:: atomic:: AtomicBool :: default ( ) ) ?;
692+ let ( mut checkout, _out) = prepare. fetch_then_checkout ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
659693
660- let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
694+ let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
661695
662696 assert_eq ! (
663697 repo. references( ) ?. all( ) ?. count( ) - 1 ,
@@ -703,10 +737,8 @@ mod blocking_io {
703737 Default :: default ( ) ,
704738 restricted ( ) . config_overrides ( Some ( format ! ( "protocol.version={}" , version as u8 ) ) ) ,
705739 ) ?;
706- let ( mut checkout, out) =
707- prepare. fetch_then_checkout ( gix:: progress:: Discard , & std:: sync:: atomic:: AtomicBool :: default ( ) ) ?;
708- let ( repo, _) =
709- checkout. main_worktree ( gix:: progress:: Discard , & std:: sync:: atomic:: AtomicBool :: default ( ) ) ?;
740+ let ( mut checkout, out) = prepare. fetch_then_checkout ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
741+ let ( repo, _) = checkout. main_worktree ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
710742
711743 assert ! ( !repo. index_path( ) . is_file( ) , "newly initialized repos have no index" ) ;
712744 let head = repo. head ( ) ?;
@@ -750,7 +782,7 @@ mod blocking_io {
750782 Default :: default ( ) ,
751783 restricted ( ) ,
752784 ) ?
753- . fetch_only ( gix:: progress:: Discard , & std :: sync :: atomic :: AtomicBool :: default ( ) ) ?;
785+ . fetch_only ( gix:: progress:: Discard , & AtomicBool :: default ( ) ) ?;
754786 assert ! ( repo. find_remote( "origin" ) . is_ok( ) , "default remote name is 'origin'" ) ;
755787 match out. status {
756788 gix:: remote:: fetch:: Status :: Change { write_pack_bundle, .. } => {
0 commit comments