Skip to content

Commit 5372d7c

Browse files
Ryan MoranForestEckhardt
Ryan Moran
authored andcommitted
Prevent file descriptor exhaustion when decompressing zip files
1 parent 24cfa4f commit 5372d7c

File tree

2 files changed

+44
-8
lines changed

2 files changed

+44
-8
lines changed

vacation/zip_archive.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,24 @@ func (z ZipArchive) Decompress(destination string) error {
112112
if err != nil {
113113
return fmt.Errorf("failed to unzip file: %w", err)
114114
}
115-
defer dst.Close()
116115

117116
src, err := f.Open()
118117
if err != nil {
119118
return err
120119
}
121-
defer src.Close()
122120

123121
_, err = io.Copy(dst, src)
124122
if err != nil {
125123
return err
126124
}
125+
126+
if err := dst.Close(); err != nil {
127+
return err
128+
}
129+
130+
if err := src.Close(); err != nil {
131+
return err
132+
}
127133
}
128134
}
129135

vacation/zip_archive_test.go

+36-6
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ func testZipArchive(t *testing.T, context spec.G, it spec.S) {
8383
})
8484

8585
it("unpackages the archive into the path", func() {
86-
var err error
87-
err = zipArchive.Decompress(tempDir)
86+
err := zipArchive.Decompress(tempDir)
8887
Expect(err).ToNot(HaveOccurred())
8988

9089
files, err := filepath.Glob(fmt.Sprintf("%s/*", tempDir))
@@ -114,8 +113,7 @@ func testZipArchive(t *testing.T, context spec.G, it spec.S) {
114113
})
115114

116115
it("unpackages the archive into the path but also strips the first component", func() {
117-
var err error
118-
err = zipArchive.StripComponents(1).Decompress(tempDir)
116+
err := zipArchive.StripComponents(1).Decompress(tempDir)
119117
Expect(err).ToNot(HaveOccurred())
120118

121119
files, err := filepath.Glob(fmt.Sprintf("%s/*", tempDir))
@@ -128,6 +126,38 @@ func testZipArchive(t *testing.T, context spec.G, it spec.S) {
128126
Expect(filepath.Join(tempDir, "some-other-dir", "some-file")).To(BeARegularFile())
129127
})
130128

129+
context("when given a zip file with enough contents to exhaust file descriptors", func() {
130+
it.Before(func() {
131+
buffer := bytes.NewBuffer(nil)
132+
zw := zip.NewWriter(buffer)
133+
134+
// Linux and MacOS seem to have artificially low limits like 1024 and
135+
// 256 respectively. Using a value like 2048 should be high enough to
136+
// trigger the limit on both.
137+
for i := 0; i < 2048; i++ {
138+
name := fmt.Sprintf("some-file-%d", i)
139+
140+
header := &zip.FileHeader{Name: name}
141+
header.SetMode(0755)
142+
143+
file, err := zw.CreateHeader(header)
144+
Expect(err).NotTo(HaveOccurred())
145+
146+
_, err = file.Write([]byte(name))
147+
Expect(err).NotTo(HaveOccurred())
148+
}
149+
150+
Expect(zw.Close()).To(Succeed())
151+
152+
zipArchive = vacation.NewZipArchive(bytes.NewReader(buffer.Bytes()))
153+
})
154+
155+
it("closes file descriptors as it goes", func() {
156+
err := zipArchive.Decompress(tempDir)
157+
Expect(err).ToNot(HaveOccurred())
158+
})
159+
})
160+
131161
context("failure cases", func() {
132162
context("when it fails to create a zip reader", func() {
133163
it("returns an error", func() {
@@ -140,12 +170,12 @@ func testZipArchive(t *testing.T, context spec.G, it spec.S) {
140170

141171
context("when a file is not inside of the destination director (Zip Slip)", func() {
142172
var buffer *bytes.Buffer
173+
143174
it.Before(func() {
144-
var err error
145175
buffer = bytes.NewBuffer(nil)
146176
zw := zip.NewWriter(buffer)
147177

148-
_, err = zw.Create(filepath.Join("..", "some-dir"))
178+
_, err := zw.Create(filepath.Join("..", "some-dir"))
149179
Expect(err).NotTo(HaveOccurred())
150180

151181
Expect(zw.Close()).To(Succeed())

0 commit comments

Comments
 (0)