11package storage_test
22
33import (
4+ "archive/zip"
5+ "bytes"
46 "context"
57 "errors"
68 "fmt"
@@ -25,6 +27,8 @@ type testStorage struct {
2527 storage storage.Storage
2628 write func (content []byte , elem ... string )
2729 exists func (elem ... string ) bool
30+ // dir is the base extension directory (local storage only).
31+ dir string
2832
2933 expectedManifest func (man * storage.VSIXManifest )
3034}
@@ -123,20 +127,23 @@ func TestNewStorage(t *testing.T) {
123127func TestStorage (t * testing.T ) {
124128 t .Parallel ()
125129 factories := []struct {
126- name string
127- factory storageFactory
130+ name string
131+ factory storageFactory
132+ localOnly bool
128133 }{
129134 {
130- name : "Local" ,
131- factory : localFactory ,
135+ name : "Local" ,
136+ factory : localFactory ,
137+ localOnly : true ,
132138 },
133139 {
134140 name : "Artifactory" ,
135141 factory : artifactoryFactory ,
136142 },
137143 {
138- name : "SignedLocal" ,
139- factory : signed (true , localFactory ),
144+ name : "SignedLocal" ,
145+ factory : signed (true , localFactory ),
146+ localOnly : true ,
140147 },
141148 {
142149 name : "SignedArtifactory" ,
@@ -148,6 +155,23 @@ func TestStorage(t *testing.T) {
148155 t .Run ("AddExtension" , func (t * testing.T ) {
149156 testAddExtension (t , sf .factory )
150157 })
158+ if sf .localOnly {
159+ t .Run ("AddExtensionZipTraversal" , func (t * testing.T ) {
160+ testAddExtensionZipTraversal (t , sf .factory )
161+ })
162+ t .Run ("AddExtensionExtraTraversal" , func (t * testing.T ) {
163+ testAddExtensionExtraTraversal (t , sf .factory )
164+ })
165+ t .Run ("AddExtensionZipAbsolutePath" , func (t * testing.T ) {
166+ testAddExtensionZipAbsolutePath (t , sf .factory )
167+ })
168+ t .Run ("AddExtensionExtraAbsolutePath" , func (t * testing.T ) {
169+ testAddExtensionExtraAbsolutePath (t , sf .factory )
170+ })
171+ t .Run ("AddExtensionSymlinkEscape" , func (t * testing.T ) {
172+ testAddExtensionSymlinkEscape (t , sf .factory )
173+ })
174+ }
151175 t .Run ("RemoveExtension" , func (t * testing.T ) {
152176 testRemoveExtension (t , sf .factory )
153177 })
@@ -927,6 +951,96 @@ func testAddExtension(t *testing.T, factory storageFactory) {
927951 }
928952}
929953
954+ // createTraversalVSIX returns a valid zip whose sole entry has a path-traversal
955+ // name, used to test zip-slip protection in AddExtension.
956+ func createTraversalVSIX (t * testing.T , entryName string ) []byte {
957+ t .Helper ()
958+ buf := bytes .NewBuffer (nil )
959+ zw := zip .NewWriter (buf )
960+ fw , err := zw .Create (entryName )
961+ require .NoError (t , err )
962+ _ , err = fw .Write ([]byte ("evil" ))
963+ require .NoError (t , err )
964+ require .NoError (t , zw .Close ())
965+ return buf .Bytes ()
966+ }
967+
968+ func testAddExtensionZipTraversal (t * testing.T , factory storageFactory ) {
969+ t .Parallel ()
970+
971+ f := factory (t )
972+ ext := testutil .Extensions [0 ]
973+ manifest := testutil .ConvertExtensionToManifest (ext , storage.Version {Version : ext .LatestVersion })
974+ vsix := createTraversalVSIX (t , "../../../../tmp/evil" )
975+ _ , err := f .storage .AddExtension (context .Background (), manifest , vsix )
976+ require .Error (t , err )
977+ require .Contains (t , err .Error (), "path escapes from parent" )
978+ }
979+
980+ func testAddExtensionExtraTraversal (t * testing.T , factory storageFactory ) {
981+ t .Parallel ()
982+
983+ f := factory (t )
984+ ext := testutil .Extensions [0 ]
985+ manifest := testutil .ConvertExtensionToManifest (ext , storage.Version {Version : ext .LatestVersion })
986+ vsix := testutil .CreateVSIXFromManifest (t , manifest )
987+ evil := storage.File {RelativePath : "../../../../tmp/evil" , Content : []byte ("evil" )}
988+ _ , err := f .storage .AddExtension (context .Background (), manifest , vsix , evil )
989+ require .Error (t , err )
990+ require .Contains (t , err .Error (), "path escapes from parent" )
991+ }
992+
993+ func testAddExtensionZipAbsolutePath (t * testing.T , factory storageFactory ) {
994+ t .Parallel ()
995+
996+ f := factory (t )
997+ ext := testutil .Extensions [0 ]
998+ manifest := testutil .ConvertExtensionToManifest (ext , storage.Version {Version : ext .LatestVersion })
999+ vsix := createTraversalVSIX (t , "/tmp/evil" )
1000+ _ , err := f .storage .AddExtension (context .Background (), manifest , vsix )
1001+ require .Error (t , err )
1002+ require .Contains (t , err .Error (), "path escapes from parent" )
1003+ }
1004+
1005+ func testAddExtensionExtraAbsolutePath (t * testing.T , factory storageFactory ) {
1006+ t .Parallel ()
1007+
1008+ f := factory (t )
1009+ ext := testutil .Extensions [0 ]
1010+ manifest := testutil .ConvertExtensionToManifest (ext , storage.Version {Version : ext .LatestVersion })
1011+ vsix := testutil .CreateVSIXFromManifest (t , manifest )
1012+ evil := storage.File {RelativePath : "/tmp/evil" , Content : []byte ("evil" )}
1013+ _ , err := f .storage .AddExtension (context .Background (), manifest , vsix , evil )
1014+ require .Error (t , err )
1015+ require .Contains (t , err .Error (), "path escapes from parent" )
1016+ }
1017+
1018+ func testAddExtensionSymlinkEscape (t * testing.T , factory storageFactory ) {
1019+ t .Parallel ()
1020+
1021+ f := factory (t )
1022+ ext := testutil .Extensions [0 ]
1023+ manifest := testutil .ConvertExtensionToManifest (ext , storage.Version {Version : ext .LatestVersion })
1024+ vsix := testutil .CreateVSIXFromManifest (t , manifest )
1025+
1026+ // Pre-create the extension directory and plant a symlink inside it that
1027+ // points to a directory outside the root. An extra file written through
1028+ // this symlink must be rejected by os.Root's symlink-escape protection.
1029+ identity := manifest .Metadata .Identity
1030+ extDir := filepath .Join (f .dir , identity .Publisher , identity .ID , identity .Version )
1031+ require .NoError (t , os .MkdirAll (extDir , 0o755 ))
1032+ outside := t .TempDir ()
1033+ require .NoError (t , os .Symlink (outside , filepath .Join (extDir , "link" )))
1034+
1035+ evil := storage.File {RelativePath : "link/evil" , Content : []byte ("evil" )}
1036+ _ , err := f .storage .AddExtension (context .Background (), manifest , vsix , evil )
1037+ require .Error (t , err )
1038+
1039+ // Confirm the file was not written to the target outside the root.
1040+ _ , statErr := os .Stat (filepath .Join (outside , "evil" ))
1041+ require .True (t , os .IsNotExist (statErr ))
1042+ }
1043+
9301044func testRemoveExtension (t * testing.T , factory storageFactory ) {
9311045 t .Parallel ()
9321046
0 commit comments