@@ -574,52 +574,66 @@ impl Pwd for Args {
574574 }
575575}
576576
577- #[ cfg( unix) ]
578- fn fix_config_permissions ( root : std:: path:: PathBuf ) {
579- use std:: os:: unix:: fs:: PermissionsExt ;
580-
581- let mut bad_dirs = Vec :: new ( ) ;
582- let mut bad_files = Vec :: new ( ) ;
583- let mut stack = vec ! [ root] ;
584-
585- while let Some ( dir) = stack. pop ( ) {
586- if let Ok ( meta) = std:: fs:: metadata ( & dir) {
587- if meta. permissions ( ) . mode ( ) & 0o777 != 0o700 {
588- bad_dirs. push ( dir. clone ( ) ) ;
577+ /// Walk `root` recursively. For every regular entry whose permissions don't
578+ /// already match the hardened mode (0o700 for dirs, 0o600 for files), set
579+ /// them. Returns the dirs and files that were changed so callers can decide
580+ /// whether to surface a warning. Symlinks are skipped — mode bits aren't
581+ /// meaningful for them and `set_permissions` would follow them.
582+ ///
583+ /// On non-unix platforms this is a no-op; tempdirs / config dirs there rely
584+ /// on filesystem ACLs created by the higher-level APIs.
585+ #[ allow( clippy:: unnecessary_wraps) ]
586+ pub ( crate ) fn enforce_hardened_tree ( root : & Path ) -> io:: Result < ( Vec < PathBuf > , Vec < PathBuf > ) > {
587+ #[ cfg( unix) ]
588+ {
589+ use std:: os:: unix:: fs:: PermissionsExt ;
590+ let mut changed_dirs = Vec :: new ( ) ;
591+ let mut changed_files = Vec :: new ( ) ;
592+ let mut stack = vec ! [ root. to_path_buf( ) ] ;
593+ while let Some ( p) = stack. pop ( ) {
594+ let Ok ( meta) = std:: fs:: symlink_metadata ( & p) else {
595+ continue ;
596+ } ;
597+ if meta. file_type ( ) . is_symlink ( ) {
598+ continue ;
589599 }
590- }
591-
592- if let Ok ( entries) = std:: fs:: read_dir ( & dir) {
593- for entry in entries. filter_map ( Result :: ok) {
594- let path = entry. path ( ) ;
595-
596- if path. is_dir ( ) {
597- stack. push ( path) ;
598- } else if let Ok ( meta) = std:: fs:: metadata ( & path) {
599- if meta. permissions ( ) . mode ( ) & 0o777 != 0o600 {
600- bad_files. push ( path) ;
600+ let current = meta. permissions ( ) . mode ( ) & 0o777 ;
601+ if meta. is_dir ( ) {
602+ if current != 0o700 {
603+ set_hardened_permissions ( & p) ?;
604+ changed_dirs. push ( p. clone ( ) ) ;
605+ }
606+ if let Ok ( entries) = std:: fs:: read_dir ( & p) {
607+ for entry in entries. filter_map ( Result :: ok) {
608+ stack. push ( entry. path ( ) ) ;
601609 }
602610 }
611+ } else if current != 0o600 {
612+ set_hardened_permissions ( & p) ?;
613+ changed_files. push ( p) ;
603614 }
604615 }
616+ Ok ( ( changed_dirs, changed_files) )
605617 }
618+ #[ cfg( not( unix) ) ]
619+ {
620+ let _ = root;
621+ Ok ( ( Vec :: new ( ) , Vec :: new ( ) ) )
622+ }
623+ }
606624
607- let print = Print :: new ( false ) ;
625+ #[ cfg( unix) ]
626+ fn fix_config_permissions ( root : std:: path:: PathBuf ) {
627+ let Ok ( ( dirs, files) ) = enforce_hardened_tree ( & root) else {
628+ return ;
629+ } ;
608630
609- if !bad_dirs. is_empty ( ) {
631+ let print = Print :: new ( false ) ;
632+ if !dirs. is_empty ( ) {
610633 print. warnln ( "Updated config directories permissions to 0700." ) ;
611-
612- for dir in bad_dirs {
613- let _ = set_hardened_permissions ( & dir) ;
614- }
615634 }
616-
617- if !bad_files. is_empty ( ) {
635+ if !files. is_empty ( ) {
618636 print. warnln ( "Updated config files permissions to 0600." ) ;
619-
620- for file in bad_files {
621- let _ = set_hardened_permissions ( & file) ;
622- }
623637 }
624638}
625639
0 commit comments