Skip to content

Commit 2b3490d

Browse files
bendeutschsfan5
authored andcommittedFeb 1, 2020
Add limit parameter to decompressZlib
This can prevent untrusted data, such as sent over the network, from consuming all memory with a specially crafted payload.
1 parent 1116918 commit 2b3490d

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed
 

Diff for: ‎src/serialization.cpp

+17-3
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void compressZlib(const std::string &data, std::ostream &os, int level)
9999
compressZlib((u8*)data.c_str(), data.size(), os, level);
100100
}
101101

102-
void decompressZlib(std::istream &is, std::ostream &os)
102+
void decompressZlib(std::istream &is, std::ostream &os, size_t limit)
103103
{
104104
z_stream z;
105105
const s32 bufsize = 16384;
@@ -108,6 +108,7 @@ void decompressZlib(std::istream &is, std::ostream &os)
108108
int status = 0;
109109
int ret;
110110
int bytes_read = 0;
111+
int bytes_written = 0;
111112
int input_buffer_len = 0;
112113

113114
z.zalloc = Z_NULL;
@@ -124,8 +125,20 @@ void decompressZlib(std::istream &is, std::ostream &os)
124125

125126
for(;;)
126127
{
128+
int output_size = bufsize;
127129
z.next_out = (Bytef*)output_buffer;
128-
z.avail_out = bufsize;
130+
z.avail_out = output_size;
131+
132+
if (limit) {
133+
int limit_remaining = limit - bytes_written;
134+
if (limit_remaining <= 0) {
135+
// we're aborting ahead of time - throw an error?
136+
break;
137+
}
138+
if (limit_remaining < output_size) {
139+
z.avail_out = output_size = limit_remaining;
140+
}
141+
}
129142

130143
if(z.avail_in == 0)
131144
{
@@ -153,10 +166,11 @@ void decompressZlib(std::istream &is, std::ostream &os)
153166
zerr(status);
154167
throw SerializationError("decompressZlib: inflate failed");
155168
}
156-
int count = bufsize - z.avail_out;
169+
int count = output_size - z.avail_out;
157170
//dstream<<"count="<<count<<std::endl;
158171
if(count)
159172
os.write(output_buffer, count);
173+
bytes_written += count;
160174
if(status == Z_STREAM_END)
161175
{
162176
//dstream<<"Z_STREAM_END"<<std::endl;

Diff for: ‎src/serialization.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ inline bool ser_ver_supported(s32 v) {
8787

8888
void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level = -1);
8989
void compressZlib(const std::string &data, std::ostream &os, int level = -1);
90-
void decompressZlib(std::istream &is, std::ostream &os);
90+
void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0);
9191

9292
// These choose between zlib and a self-made one according to version
9393
void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version);

Diff for: ‎src/unittest/test_compression.cpp

+63
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class TestCompression : public TestBase {
3737
void testRLECompression();
3838
void testZlibCompression();
3939
void testZlibLargeData();
40+
void testZlibLimit();
41+
void _testZlibLimit(u32 size, u32 limit);
4042
};
4143

4244
static TestCompression g_test_instance;
@@ -46,6 +48,7 @@ void TestCompression::runTests(IGameDef *gamedef)
4648
TEST(testRLECompression);
4749
TEST(testZlibCompression);
4850
TEST(testZlibLargeData);
51+
TEST(testZlibLimit);
4952
}
5053

5154
////////////////////////////////////////////////////////////////////////////////
@@ -170,3 +173,63 @@ void TestCompression::testZlibLargeData()
170173
i, str_decompressed[i], i, data_in[i]);
171174
}
172175
}
176+
177+
void TestCompression::testZlibLimit()
178+
{
179+
// edge cases
180+
_testZlibLimit(1024, 1023);
181+
_testZlibLimit(1024, 1024);
182+
_testZlibLimit(1024, 1025);
183+
184+
// test around buffer borders
185+
u32 bufsize = 16384; // as in implementation
186+
for (int s = -1; s <= 1; s++)
187+
{
188+
for (int l = -1; l <= 1; l++)
189+
{
190+
_testZlibLimit(bufsize + s, bufsize + l);
191+
}
192+
}
193+
// span multiple buffers
194+
_testZlibLimit(35000, 22000);
195+
_testZlibLimit(22000, 35000);
196+
}
197+
198+
void TestCompression::_testZlibLimit(u32 size, u32 limit)
199+
{
200+
infostream << "Test: Testing zlib wrappers with a decompression "
201+
"memory limit of " << limit << std::endl;
202+
203+
infostream << "Test: Input size of compressZlib for limit is "
204+
<< size << std::endl;
205+
206+
// how much data we expect to get
207+
u32 expected = size < limit ? size : limit;
208+
209+
// create recognizable data
210+
std::string data_in;
211+
data_in.resize(size);
212+
for (u32 i = 0; i < size; i++)
213+
data_in[i] = (u8)(i % 256);
214+
215+
std::ostringstream os_compressed(std::ios::binary);
216+
compressZlib(data_in, os_compressed);
217+
infostream << "Test: Output size of compressZlib for limit is "
218+
<< os_compressed.str().size()<<std::endl;
219+
220+
std::istringstream is_compressed(os_compressed.str(), std::ios::binary);
221+
std::ostringstream os_decompressed(std::ios::binary);
222+
decompressZlib(is_compressed, os_decompressed, limit);
223+
infostream << "Test: Output size of decompressZlib with limit is "
224+
<< os_decompressed.str().size() << std::endl;
225+
226+
std::string str_decompressed = os_decompressed.str();
227+
UASSERTEQ(size_t, str_decompressed.size(), expected);
228+
229+
for (u32 i = 0; i < size && i < str_decompressed.size(); i++) {
230+
UTEST(str_decompressed[i] == data_in[i],
231+
"index out[%i]=%i differs from in[%i]=%i",
232+
i, str_decompressed[i], i, data_in[i]);
233+
}
234+
}
235+

0 commit comments

Comments
 (0)
Please sign in to comment.