@@ -13,7 +13,10 @@ use crate::backend::{DecompileBackend, DecompileRequest, DecompileResult, Mode};
1313use crate :: model:: WorkerMessage ;
1414use crate :: progress:: Progress ;
1515use anyhow:: { anyhow, bail, Context , Result } ;
16- use std:: io:: { BufRead , BufReader , Read } ;
16+ use std:: collections:: hash_map:: RandomState ;
17+ use std:: fs:: { DirBuilder , OpenOptions } ;
18+ use std:: hash:: { BuildHasher , Hasher } ;
19+ use std:: io:: { BufRead , BufReader , Read , Write } ;
1720use std:: path:: PathBuf ;
1821use std:: process:: { Command , Stdio } ;
1922use std:: sync:: atomic:: { AtomicBool , Ordering } ;
@@ -77,18 +80,75 @@ fn kill(pid: u32) {
7780 . status ( ) ;
7881}
7982
83+ /// Random hex token drawn from `RandomState`, whose SipHash keys are seeded
84+ /// from OS entropy — unpredictable temp names without a rand dependency.
85+ fn random_token ( ) -> String {
86+ fn draw ( ) -> u64 {
87+ let mut hasher = RandomState :: new ( ) . build_hasher ( ) ;
88+ hasher. write_u64 ( 0 ) ;
89+ hasher. finish ( )
90+ }
91+ format ! ( "{:016x}{:016x}" , draw( ) , draw( ) )
92+ }
93+
94+ /// Create a fresh private subdirectory under the system temp dir.
95+ ///
96+ /// The name is random and `create_dir` refuses to reuse an existing path, so
97+ /// we never adopt a directory another user prepared. On Unix it is created
98+ /// mode 0o700 so no other user can write into or traverse it.
99+ fn create_private_dir ( ) -> Result < PathBuf > {
100+ let mut builder = DirBuilder :: new ( ) ;
101+ #[ cfg( unix) ]
102+ {
103+ use std:: os:: unix:: fs:: DirBuilderExt ;
104+ builder. mode ( 0o700 ) ;
105+ }
106+ for _ in 0 ..16 {
107+ let dir = std:: env:: temp_dir ( ) . join ( format ! ( "srcdump-{}" , random_token( ) ) ) ;
108+ match builder. create ( & dir) {
109+ Ok ( ( ) ) => return Ok ( dir) ,
110+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: AlreadyExists => continue ,
111+ Err ( e) => {
112+ return Err ( e) . with_context ( || format ! ( "creating worker dir {}" , dir. display( ) ) )
113+ }
114+ }
115+ }
116+ bail ! (
117+ "could not create a private worker dir under {}" ,
118+ std:: env:: temp_dir( ) . display( )
119+ ) ;
120+ }
121+
80122pub struct ProcessPoolBackend {
123+ worker_dir : PathBuf ,
81124 worker_script : PathBuf ,
82125}
83126
84127impl ProcessPoolBackend {
85- /// Materialize the embedded worker script to a temp file for this process.
128+ /// Materialize the embedded worker script to a randomly-named file inside
129+ /// a private (user-only) temp subdirectory for this process.
86130 pub fn new ( ) -> Result < Self > {
87- let mut path = std:: env:: temp_dir ( ) ;
88- path. push ( format ! ( "srcdump_worker_{}.py" , std:: process:: id( ) ) ) ;
89- std:: fs:: write ( & path, WORKER_SOURCE )
131+ let dir = create_private_dir ( ) ?;
132+ let path = dir. join ( format ! ( "worker-{}.py" , random_token( ) ) ) ;
133+ // Unlink anything already occupying the name, then create exclusively
134+ // (`create_new` never follows or truncates an existing file), so a
135+ // pre-planted file or symlink can neither survive nor be written
136+ // through.
137+ let _ = std:: fs:: remove_file ( & path) ;
138+ let mut opts = OpenOptions :: new ( ) ;
139+ opts. write ( true ) . create_new ( true ) ;
140+ #[ cfg( unix) ]
141+ {
142+ use std:: os:: unix:: fs:: OpenOptionsExt ;
143+ opts. mode ( 0o600 ) ;
144+ }
145+ let mut file = opts
146+ . open ( & path)
147+ . with_context ( || format ! ( "creating worker script {}" , path. display( ) ) ) ?;
148+ file. write_all ( WORKER_SOURCE . as_bytes ( ) )
90149 . with_context ( || format ! ( "writing worker script to {}" , path. display( ) ) ) ?;
91150 Ok ( Self {
151+ worker_dir : dir,
92152 worker_script : path,
93153 } )
94154 }
@@ -97,6 +157,7 @@ impl ProcessPoolBackend {
97157impl Drop for ProcessPoolBackend {
98158 fn drop ( & mut self ) {
99159 let _ = std:: fs:: remove_file ( & self . worker_script ) ;
160+ let _ = std:: fs:: remove_dir ( & self . worker_dir ) ;
100161 }
101162}
102163
@@ -361,3 +422,48 @@ fn tail_lines(s: &str, n: usize) -> String {
361422 let start = lines. len ( ) . saturating_sub ( n) ;
362423 lines[ start..] . join ( "\n " )
363424}
425+
426+ #[ cfg( test) ]
427+ mod tests {
428+ use super :: * ;
429+
430+ #[ test]
431+ fn worker_script_lives_in_private_random_dir ( ) {
432+ let backend = ProcessPoolBackend :: new ( ) . unwrap ( ) ;
433+
434+ assert ! ( backend. worker_script. is_file( ) ) ;
435+ assert_eq ! ( backend. worker_script. parent( ) , Some ( & * backend. worker_dir) ) ;
436+ assert_eq ! ( backend. worker_dir. parent( ) , Some ( & * std:: env:: temp_dir( ) ) ) ;
437+ assert_eq ! (
438+ std:: fs:: read_to_string( & backend. worker_script) . unwrap( ) ,
439+ WORKER_SOURCE
440+ ) ;
441+
442+ #[ cfg( unix) ]
443+ {
444+ use std:: os:: unix:: fs:: PermissionsExt ;
445+ let dir_mode = std:: fs:: metadata ( & backend. worker_dir )
446+ . unwrap ( )
447+ . permissions ( )
448+ . mode ( ) ;
449+ assert_eq ! ( dir_mode & 0o777 , 0o700 , "worker dir must be user-only" ) ;
450+ let file_mode = std:: fs:: metadata ( & backend. worker_script )
451+ . unwrap ( )
452+ . permissions ( )
453+ . mode ( ) ;
454+ assert_eq ! ( file_mode & 0o777 , 0o600 , "worker script must be user-only" ) ;
455+ }
456+
457+ // Two backends must never collide on paths.
458+ let other = ProcessPoolBackend :: new ( ) . unwrap ( ) ;
459+ assert_ne ! ( backend. worker_dir, other. worker_dir) ;
460+ assert_ne ! ( backend. worker_script, other. worker_script) ;
461+
462+ // Drop removes both the script and its private directory.
463+ let ( dir, script) = ( backend. worker_dir . clone ( ) , backend. worker_script . clone ( ) ) ;
464+ drop ( backend) ;
465+ assert ! ( !script. exists( ) ) ;
466+ assert ! ( !dir. exists( ) ) ;
467+ drop ( other) ;
468+ }
469+ }
0 commit comments