@@ -120,12 +120,39 @@ impl Installer {
120120 }
121121
122122 /// Extract a tar.gz archive.
123- async fn extract_tar_gz ( & self , archive : & Path , dest : & Path ) -> UpdateResult < PathBuf > {
123+ async fn extract_tar_gz ( & self , archive_path : & Path , dest : & Path ) -> UpdateResult < PathBuf > {
124124 use flate2:: read:: GzDecoder ;
125125 use std:: fs:: File ;
126126 use tar:: Archive ;
127127
128- let file = File :: open ( archive) ?;
128+ let file = File :: open ( archive_path) ?;
129+ let gz = GzDecoder :: new ( file) ;
130+ let mut archive = Archive :: new ( gz) ;
131+
132+ // Validate paths before extraction to prevent path traversal attacks
133+ for entry in archive. entries ( ) . map_err ( |e| UpdateError :: ExtractionFailed {
134+ message : e. to_string ( ) ,
135+ } ) ? {
136+ let entry = entry. map_err ( |e| UpdateError :: ExtractionFailed {
137+ message : e. to_string ( ) ,
138+ } ) ?;
139+ let path = entry. path ( ) . map_err ( |e| UpdateError :: ExtractionFailed {
140+ message : e. to_string ( ) ,
141+ } ) ?;
142+
143+ // Check for path traversal
144+ if path
145+ . components ( )
146+ . any ( |c| matches ! ( c, std:: path:: Component :: ParentDir ) )
147+ {
148+ return Err ( UpdateError :: ExtractionFailed {
149+ message : format ! ( "Path traversal detected in archive: {}" , path. display( ) ) ,
150+ } ) ;
151+ }
152+ }
153+
154+ // Re-open and extract after validation
155+ let file = File :: open ( archive_path) ?;
129156 let gz = GzDecoder :: new ( file) ;
130157 let mut archive = Archive :: new ( gz) ;
131158
@@ -139,8 +166,35 @@ impl Installer {
139166 }
140167
141168 /// Extract a zip archive.
142- async fn extract_zip ( & self , archive : & Path , dest : & Path ) -> UpdateResult < PathBuf > {
143- let file = std:: fs:: File :: open ( archive) ?;
169+ async fn extract_zip ( & self , archive_path : & Path , dest : & Path ) -> UpdateResult < PathBuf > {
170+ let file = std:: fs:: File :: open ( archive_path) ?;
171+ let mut archive =
172+ zip:: ZipArchive :: new ( file) . map_err ( |e| UpdateError :: ExtractionFailed {
173+ message : e. to_string ( ) ,
174+ } ) ?;
175+
176+ // Validate all paths before extraction to prevent path traversal attacks
177+ for i in 0 ..archive. len ( ) {
178+ let file = archive
179+ . by_index ( i)
180+ . map_err ( |e| UpdateError :: ExtractionFailed {
181+ message : e. to_string ( ) ,
182+ } ) ?;
183+ let path = std:: path:: Path :: new ( file. name ( ) ) ;
184+
185+ // Check for path traversal
186+ if path
187+ . components ( )
188+ . any ( |c| matches ! ( c, std:: path:: Component :: ParentDir ) )
189+ {
190+ return Err ( UpdateError :: ExtractionFailed {
191+ message : format ! ( "Path traversal detected in archive: {}" , file. name( ) ) ,
192+ } ) ;
193+ }
194+ }
195+
196+ // Re-open and extract after validation
197+ let file = std:: fs:: File :: open ( archive_path) ?;
144198 let mut archive =
145199 zip:: ZipArchive :: new ( file) . map_err ( |e| UpdateError :: ExtractionFailed {
146200 message : e. to_string ( ) ,
@@ -244,6 +298,11 @@ impl Installer {
244298 const MOVEFILE_REPLACE_EXISTING : u32 = 0x1 ;
245299 const MOVEFILE_DELAY_UNTIL_REBOOT : u32 = 0x4 ;
246300
301+ // SAFETY: MoveFileExW is a Windows API function that schedules a file move/rename
302+ // operation to occur at the next system restart. We pass valid null-terminated
303+ // wide strings for source and destination paths. The MOVEFILE_DELAY_UNTIL_REBOOT
304+ // flag ensures this is a deferred operation. The return value of 0 indicates failure,
305+ // in which case we retrieve the error via last_os_error().
247306 let result = unsafe {
248307 windows_sys:: Win32 :: Storage :: FileSystem :: MoveFileExW (
249308 old_path. as_ptr ( ) ,
0 commit comments