11package extract_test
22
33import (
4+ "archive/tar"
5+ "archive/zip"
46 "bytes"
57 "context"
68 "fmt"
@@ -65,7 +67,7 @@ func testArchive(t *testing.T, archivePath *paths.Path) {
6567}
6668
6769func TestZipSlipHardening (t * testing.T ) {
68- {
70+ t . Run ( "ZipTraversal" , func ( t * testing. T ) {
6971 logger := & LoggingFS {}
7072 extractor := extract.Extractor {FS : logger }
7173 data , err := os .Open ("testdata/zipslip/evil.zip" )
@@ -74,8 +76,9 @@ func TestZipSlipHardening(t *testing.T) {
7476 require .NoError (t , data .Close ())
7577 fmt .Print (logger )
7678 require .Empty (t , logger .Journal )
77- }
78- {
79+ })
80+
81+ t .Run ("TarTraversal" , func (t * testing.T ) {
7982 logger := & LoggingFS {}
8083 extractor := extract.Extractor {FS : logger }
8184 data , err := os .Open ("testdata/zipslip/evil.tar" )
@@ -84,9 +87,23 @@ func TestZipSlipHardening(t *testing.T) {
8487 require .NoError (t , data .Close ())
8588 fmt .Print (logger )
8689 require .Empty (t , logger .Journal )
87- }
90+ })
91+
92+ t .Run ("TarLinkTraversal" , func (t * testing.T ) {
93+ logger := & LoggingFS {}
94+ extractor := extract.Extractor {FS : logger }
95+ data , err := os .Open ("testdata/zipslip/evil-link-traversal.tar" )
96+ require .NoError (t , err )
97+ require .NoError (t , extractor .Tar (context .Background (), data , "/tmp/test" , nil ))
98+ require .NoError (t , data .Close ())
99+ fmt .Print (logger )
100+ require .Empty (t , logger .Journal )
101+ })
88102
89- if runtime .GOOS == "windows" {
103+ t .Run ("WindowsTarTraversal" , func (t * testing.T ) {
104+ if runtime .GOOS != "windows" {
105+ t .Skip ("Skipped on non-Windows host" )
106+ }
90107 logger := & LoggingFS {}
91108 extractor := extract.Extractor {FS : logger }
92109 data , err := os .Open ("testdata/zipslip/evil-win.tar" )
@@ -95,7 +112,151 @@ func TestZipSlipHardening(t *testing.T) {
95112 require .NoError (t , data .Close ())
96113 fmt .Print (logger )
97114 require .Empty (t , logger .Journal )
115+ })
116+ }
117+
118+ func mkTempDir (t * testing.T ) * paths.Path {
119+ tmp , err := paths .MkTempDir ("" , "test" )
120+ require .NoError (t , err )
121+ t .Cleanup (func () { tmp .RemoveAll () })
122+ return tmp
123+ }
124+
125+ func TestSymLinkMazeHardening (t * testing.T ) {
126+ addTarSymlink := func (t * testing.T , tw * tar.Writer , new , old string ) {
127+ err := tw .WriteHeader (& tar.Header {
128+ Mode : 0o0777 , Typeflag : tar .TypeSymlink , Name : new , Linkname : old ,
129+ })
130+ require .NoError (t , err )
131+ }
132+ addZipSymlink := func (t * testing.T , zw * zip.Writer , new , old string ) {
133+ h := & zip.FileHeader {Name : new , Method : zip .Deflate }
134+ h .SetMode (os .ModeSymlink )
135+ w , err := zw .CreateHeader (h )
136+ require .NoError (t , err )
137+ _ , err = w .Write ([]byte (old ))
138+ require .NoError (t , err )
98139 }
140+
141+ t .Run ("TarWithSymlinkToAbsPath" , func (t * testing.T ) {
142+ // Create target dir
143+ tmp := mkTempDir (t )
144+ targetDir := tmp .Join ("test" )
145+ require .NoError (t , targetDir .Mkdir ())
146+
147+ // Make a tar archive with symlink maze
148+ outputTar := bytes .NewBuffer (nil )
149+ tw := tar .NewWriter (outputTar )
150+ addTarSymlink (t , tw , "aaa" , tmp .String ())
151+ addTarSymlink (t , tw , "aaa/sym" , "something" )
152+ require .NoError (t , tw .Close ())
153+
154+ // Run extract
155+ extractor := extract.Extractor {FS : & LoggingFS {}}
156+ require .Error (t , extractor .Tar (context .Background (), outputTar , targetDir .String (), nil ))
157+ require .NoFileExists (t , tmp .Join ("sym" ).String ())
158+ })
159+
160+ t .Run ("ZipWithSymlinkToAbsPath" , func (t * testing.T ) {
161+ // Create target dir
162+ tmp := mkTempDir (t )
163+ targetDir := tmp .Join ("test" )
164+ require .NoError (t , targetDir .Mkdir ())
165+
166+ // Make a zip archive with symlink maze
167+ outputZip := bytes .NewBuffer (nil )
168+ zw := zip .NewWriter (outputZip )
169+ addZipSymlink (t , zw , "aaa" , tmp .String ())
170+ addZipSymlink (t , zw , "aaa/sym" , "something" )
171+ require .NoError (t , zw .Close ())
172+
173+ // Run extract
174+ extractor := extract.Extractor {FS : & LoggingFS {}}
175+ err := extractor .Zip (context .Background (), outputZip , targetDir .String (), nil )
176+ require .NoFileExists (t , tmp .Join ("sym" ).String ())
177+ require .Error (t , err )
178+ })
179+
180+ t .Run ("TarWithSymlinkToRelativeExternalPath" , func (t * testing.T ) {
181+ // Create target dir
182+ tmp := mkTempDir (t )
183+ targetDir := tmp .Join ("test" )
184+ require .NoError (t , targetDir .Mkdir ())
185+ checkDir := tmp .Join ("secret" )
186+ require .NoError (t , checkDir .MkdirAll ())
187+
188+ // Make a tar archive with regular symlink maze
189+ outputTar := bytes .NewBuffer (nil )
190+ tw := tar .NewWriter (outputTar )
191+ addTarSymlink (t , tw , "aaa" , "../secret" )
192+ addTarSymlink (t , tw , "aaa/sym" , "something" )
193+ require .NoError (t , tw .Close ())
194+
195+ extractor := extract.Extractor {FS : & LoggingFS {}}
196+ require .Error (t , extractor .Tar (context .Background (), outputTar , targetDir .String (), nil ))
197+ require .NoFileExists (t , checkDir .Join ("sym" ).String ())
198+ })
199+
200+ t .Run ("TarWithSymlinkToInternalPath" , func (t * testing.T ) {
201+ // Create target dir
202+ tmp := mkTempDir (t )
203+ targetDir := tmp .Join ("test" )
204+ require .NoError (t , targetDir .Mkdir ())
205+
206+ // Make a tar archive with regular symlink maze
207+ outputTar := bytes .NewBuffer (nil )
208+ tw := tar .NewWriter (outputTar )
209+ require .NoError (t , tw .WriteHeader (& tar.Header {Mode : 0o0777 , Typeflag : tar .TypeDir , Name : "tmp" }))
210+ addTarSymlink (t , tw , "aaa" , "tmp" )
211+ addTarSymlink (t , tw , "aaa/sym" , "something" )
212+ require .NoError (t , tw .Close ())
213+
214+ extractor := extract.Extractor {FS : & LoggingFS {}}
215+ require .Error (t , extractor .Tar (context .Background (), outputTar , targetDir .String (), nil ))
216+ require .NoFileExists (t , targetDir .Join ("tmp" , "sym" ).String ())
217+ })
218+
219+ t .Run ("TarWithDoubleSymlinkToExternalPath" , func (t * testing.T ) {
220+ // Create target dir
221+ tmp := mkTempDir (t )
222+ targetDir := tmp .Join ("test" )
223+ require .NoError (t , targetDir .Mkdir ())
224+ fmt .Println ("TMP:" , tmp )
225+ fmt .Println ("TARGET DIR:" , targetDir )
226+
227+ // Make a tar archive with regular symlink maze
228+ outputTar := bytes .NewBuffer (nil )
229+ tw := tar .NewWriter (outputTar )
230+ tw .WriteHeader (& tar.Header {Name : "fake" , Mode : 0777 , Typeflag : tar .TypeDir })
231+ addTarSymlink (t , tw , "sym-maze" , tmp .String ())
232+ addTarSymlink (t , tw , "sym-maze" , "fake" )
233+ addTarSymlink (t , tw , "sym-maze/oops" , "/tmp/something" )
234+ require .NoError (t , tw .Close ())
235+
236+ extractor := extract.Extractor {FS : & LoggingFS {}}
237+ require .Error (t , extractor .Tar (context .Background (), outputTar , targetDir .String (), nil ))
238+ require .NoFileExists (t , tmp .Join ("oops" ).String ())
239+ })
240+
241+ t .Run ("TarWithSymlinkToExternalPathWithoutMazing" , func (t * testing.T ) {
242+ // Create target dir
243+ tmp := mkTempDir (t )
244+ targetDir := tmp .Join ("test" )
245+ require .NoError (t , targetDir .Mkdir ())
246+
247+ // Make a tar archive with valid symlink maze
248+ outputTar := bytes .NewBuffer (nil )
249+ tw := tar .NewWriter (outputTar )
250+ require .NoError (t , tw .WriteHeader (& tar.Header {Mode : 0o0777 , Typeflag : tar .TypeDir , Name : "tmp" }))
251+ addTarSymlink (t , tw , "aaa" , "../tmp" )
252+ require .NoError (t , tw .Close ())
253+
254+ extractor := extract.Extractor {FS : & LoggingFS {}}
255+ require .NoError (t , extractor .Tar (context .Background (), outputTar , targetDir .String (), nil ))
256+ st , err := targetDir .Join ("aaa" ).Lstat ()
257+ require .NoError (t , err )
258+ require .Equal (t , "aaa" , st .Name ())
259+ })
99260}
100261
101262// MockDisk is a disk that chroots to a directory
0 commit comments