Skip to content

Commit fa42487

Browse files
committed
extmod/moddeflate: Keep DeflateIO state consistent on window alloc fail.
Allocation of a large compression window may fail, and in that case keep the `DeflateIO` state consistent so its other methods (such as `close()`) still work. Consistency is kept by only updating the `self->write` member if the window allocation succeeds. Thanks to @jimmo for finding the bug. Signed-off-by: Damien George <[email protected]>
1 parent fdc0c6f commit fa42487

File tree

4 files changed

+50
-4
lines changed

4 files changed

+50
-4
lines changed

extmod/moddeflate.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,20 @@ static bool deflateio_init_write(mp_obj_deflateio_t *self) {
168168

169169
const mp_stream_p_t *stream = mp_get_stream_raise(self->stream, MP_STREAM_OP_WRITE);
170170

171-
self->write = m_new_obj(mp_obj_deflateio_write_t);
172-
self->write->input_len = 0;
173-
174171
int wbits = self->window_bits;
175172
if (wbits == 0) {
176173
// Same default wbits for all formats.
177174
wbits = DEFLATEIO_DEFAULT_WBITS;
178175
}
176+
177+
// Allocate the large window before allocating the mp_obj_deflateio_write_t, in case the
178+
// window allocation fails the mp_obj_deflateio_t object will remain in a consistent state.
179179
size_t window_len = 1 << wbits;
180-
self->write->window = m_new(uint8_t, window_len);
180+
uint8_t *window = m_new(uint8_t, window_len);
181+
182+
self->write = m_new_obj(mp_obj_deflateio_write_t);
183+
self->write->window = window;
184+
self->write->input_len = 0;
181185

182186
uzlib_lz77_init(&self->write->lz77, self->write->window, window_len);
183187
self->write->lz77.dest_write_data = self;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Test deflate.DeflateIO compression, with out-of-memory errors.
2+
3+
try:
4+
# Check if deflate is available.
5+
import deflate
6+
import io
7+
except ImportError:
8+
print("SKIP")
9+
raise SystemExit
10+
11+
# Check if compression is enabled.
12+
if not hasattr(deflate.DeflateIO, "write"):
13+
print("SKIP")
14+
raise SystemExit
15+
16+
# Create a compressor object.
17+
b = io.BytesIO()
18+
g = deflate.DeflateIO(b, deflate.RAW, 15)
19+
20+
# Then, use up most of the heap.
21+
l = []
22+
while True:
23+
try:
24+
l.append(bytearray(1000))
25+
except:
26+
break
27+
l.pop()
28+
29+
# Try to compress. This will try to allocate a large window and fail.
30+
try:
31+
g.write('test')
32+
except MemoryError:
33+
print("MemoryError")
34+
35+
# Should still be able to close the stream.
36+
g.close()
37+
38+
# The underlying output stream should be unchanged.
39+
print(b.getvalue())
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MemoryError
2+
b''

tests/run-tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def open(self, path, mode):
163163
"extmod/asyncio_threadsafeflag.py",
164164
"extmod/asyncio_wait_for_fwd.py",
165165
"extmod/binascii_a2b_base64.py",
166+
"extmod/deflate_compress_memory_error.py", # tries to allocate unlimited memory
166167
"extmod/re_stack_overflow.py",
167168
"extmod/time_res.py",
168169
"extmod/vfs_posix.py",

0 commit comments

Comments
 (0)