@@ -3,6 +3,7 @@ package utils_test
33import (
44 "archive/tar"
55 "archive/zip"
6+ "compress/gzip"
67 "os"
78 "path/filepath"
89
@@ -60,12 +61,45 @@ var _ = Describe("utils/archive tests", func() {
6061 Expect (filepath .Join (tmpDir , "escaped.txt" )).ToNot (BeAnExistingFile ())
6162 })
6263
64+ It ("rejects tar hardlinks that overwrite a file outside the destination" , func () {
65+ tmpDir := GinkgoT ().TempDir ()
66+ archivePath := filepath .Join (tmpDir , "model.tar.gz" )
67+ extractPath := filepath .Join (tmpDir , "models" )
68+ outsidePath := filepath .Join (tmpDir , "outside.txt" )
69+
70+ Expect (os .WriteFile (outsidePath , []byte ("original" ), 0o600 )).To (Succeed ())
71+ Expect (writeTarGzArchiveWithHardlinkedFile (archivePath , "payload.bin" , "../outside.txt" , "overwritten" )).To (Succeed ())
72+
73+ err := ExtractArchive (archivePath , extractPath )
74+
75+ Expect (err ).To (HaveOccurred ())
76+ Expect (err .Error ()).To (ContainSubstring ("unsafe path" ))
77+
78+ contents , readErr := os .ReadFile (outsidePath )
79+ Expect (readErr ).ToNot (HaveOccurred ())
80+ Expect (string (contents )).To (Equal ("original" ))
81+ })
82+
83+ It ("extracts tar hardlinks that stay inside the destination" , func () {
84+ tmpDir := GinkgoT ().TempDir ()
85+ archivePath := filepath .Join (tmpDir , "model.tar.gz" )
86+ extractPath := filepath .Join (tmpDir , "models" )
87+
88+ Expect (writeTarGzArchiveWithInternalHardlink (archivePath , "model.bin" , "alias.bin" , "weights" )).To (Succeed ())
89+
90+ Expect (ExtractArchive (archivePath , extractPath )).To (Succeed ())
91+
92+ extracted , err := os .ReadFile (filepath .Join (extractPath , "alias.bin" ))
93+ Expect (err ).ToNot (HaveOccurred ())
94+ Expect (string (extracted )).To (Equal ("weights" ))
95+ })
96+
6397 It ("rejects tar hardlinks that point outside the destination" , func () {
6498 tmpDir := GinkgoT ().TempDir ()
6599 archivePath := filepath .Join (tmpDir , "model.tar" )
66100 extractPath := filepath .Join (tmpDir , "models" )
67101
68- Expect (writeTarArchiveWithHardlink (archivePath , "payload.bin" , "../../ escaped.txt" )).To (Succeed ())
102+ Expect (writeTarArchiveWithHardlink (archivePath , "payload.bin" , "../escaped.txt" )).To (Succeed ())
69103
70104 err := ExtractArchive (archivePath , extractPath )
71105
@@ -166,3 +200,95 @@ func writeTarArchiveWithHardlink(path, name, linkname string) (err error) {
166200 Mode : 0o600 ,
167201 })
168202}
203+
204+ func writeTarGzArchiveWithHardlinkedFile (path , name , linkname , contents string ) (err error ) {
205+ out , err := os .Create (path )
206+ if err != nil {
207+ return err
208+ }
209+ defer func () {
210+ if closeErr := out .Close (); err == nil {
211+ err = closeErr
212+ }
213+ }()
214+
215+ compressor := gzip .NewWriter (out )
216+ defer func () {
217+ if closeErr := compressor .Close (); err == nil {
218+ err = closeErr
219+ }
220+ }()
221+
222+ writer := tar .NewWriter (compressor )
223+ defer func () {
224+ if closeErr := writer .Close (); err == nil {
225+ err = closeErr
226+ }
227+ }()
228+
229+ if err := writer .WriteHeader (& tar.Header {
230+ Name : name ,
231+ Linkname : linkname ,
232+ Typeflag : tar .TypeLink ,
233+ Mode : 0o600 ,
234+ }); err != nil {
235+ return err
236+ }
237+
238+ data := []byte (contents )
239+ if err := writer .WriteHeader (& tar.Header {
240+ Name : name ,
241+ Mode : 0o600 ,
242+ Size : int64 (len (data )),
243+ }); err != nil {
244+ return err
245+ }
246+ _ , err = writer .Write (data )
247+
248+ return err
249+ }
250+
251+ func writeTarGzArchiveWithInternalHardlink (path , targetName , linkName , contents string ) (err error ) {
252+ out , err := os .Create (path )
253+ if err != nil {
254+ return err
255+ }
256+ defer func () {
257+ if closeErr := out .Close (); err == nil {
258+ err = closeErr
259+ }
260+ }()
261+
262+ compressor := gzip .NewWriter (out )
263+ defer func () {
264+ if closeErr := compressor .Close (); err == nil {
265+ err = closeErr
266+ }
267+ }()
268+
269+ writer := tar .NewWriter (compressor )
270+ defer func () {
271+ if closeErr := writer .Close (); err == nil {
272+ err = closeErr
273+ }
274+ }()
275+
276+ data := []byte (contents )
277+ if err := writer .WriteHeader (& tar.Header {
278+ Name : targetName ,
279+ Mode : 0o600 ,
280+ Size : int64 (len (data )),
281+ }); err != nil {
282+ return err
283+ }
284+ if _ , err := writer .Write (data ); err != nil {
285+ return err
286+ }
287+
288+ return writer .WriteHeader (& tar.Header {
289+ Name : linkName ,
290+ Linkname : targetName ,
291+ Typeflag : tar .TypeLink ,
292+ Mode : 0o600 ,
293+ })
294+ }
0 commit comments