Skip to content

Commit 5152303

Browse files
committed
bpo-23224: Fix segfaults and multiple leaks in the lzma and bz2 modules
lzma.LZMADecompressor and bz2.BZ2Decompressor objects caused segfaults when their __init__() methods were not called. lzma.LZMADecompressor, lzma.LZMACompressor, bz2.BZ2Compressor, and bz2.BZ2Decompressor objects would leak locks and internal buffers when their __init__() methods were called multiple times.
1 parent fd8fbce commit 5152303

File tree

7 files changed

+167
-110
lines changed

7 files changed

+167
-110
lines changed

Lib/test/test_bz2.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,10 @@ def test_refleaks_in___init__(self):
826826
bzd.__init__()
827827
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
828828

829+
def test_uninitialized_BZ2Decompressor_crash(self):
830+
self.assertEqual(BZ2Decompressor.__new__(BZ2Decompressor).
831+
decompress(bytes()), b'')
832+
829833

830834
class CompressDecompressTest(BaseTest):
831835
def testCompress(self):

Lib/test/test_lzma.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@ def test_refleaks_in_decompressor___init__(self):
375375
lzd.__init__()
376376
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
377377

378+
def test_uninitialized_LZMADecompressor_crash(self):
379+
self.assertEqual(LZMADecompressor.__new__(LZMADecompressor).
380+
decompress(bytes()), b'')
381+
378382

379383
class CompressDecompressFunctionTestCase(unittest.TestCase):
380384

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix segfaults when creating :class:`lzma.LZMADecompressor` and
2+
:class:`bz2.BZ2Decompressor` objects without calling ``__init__()``, and fix
3+
leakage of locks and internal buffers when calling the ``__init__()``
4+
methods of :class:`lzma.LZMADecompressor`, :class:`lzma.LZMACompressor`,
5+
:class:`bz2.BZ2Compressor`, and :class:`bz2.BZ2Decompressor` objects
6+
multiple times.

Modules/_bz2module.c

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ BZ2_Free(void* ctx, void *ptr)
291291
}
292292

293293
/*[clinic input]
294-
_bz2.BZ2Compressor.__init__
294+
@classmethod
295+
_bz2.BZ2Compressor.__new__
295296
296297
compresslevel: int = 9
297298
Compression level, as a number between 1 and 9.
@@ -302,22 +303,30 @@ Create a compressor object for compressing data incrementally.
302303
For one-shot compression, use the compress() function instead.
303304
[clinic start generated code]*/
304305

305-
static int
306-
_bz2_BZ2Compressor___init___impl(BZ2Compressor *self, int compresslevel)
307-
/*[clinic end generated code: output=c4e6adfd02963827 input=4e1ff7b8394b6e9a]*/
306+
static PyObject *
307+
_bz2_BZ2Compressor_impl(PyTypeObject *type, int compresslevel)
308+
/*[clinic end generated code: output=83346c96beaacad7 input=d4500d2a52c8b263]*/
308309
{
309310
int bzerror;
311+
BZ2Compressor *self;
310312

311313
if (!(1 <= compresslevel && compresslevel <= 9)) {
312314
PyErr_SetString(PyExc_ValueError,
313315
"compresslevel must be between 1 and 9");
314-
return -1;
316+
return NULL;
317+
}
318+
319+
assert(type != NULL && type->tp_alloc != NULL);
320+
self = (BZ2Compressor *)type->tp_alloc(type, 0);
321+
if (self == NULL) {
322+
return NULL;
315323
}
316324

317325
self->lock = PyThread_allocate_lock();
318326
if (self->lock == NULL) {
327+
Py_DECREF(self);
319328
PyErr_SetString(PyExc_MemoryError, "Unable to allocate lock");
320-
return -1;
329+
return NULL;
321330
}
322331

323332
self->bzs.opaque = NULL;
@@ -327,12 +336,11 @@ _bz2_BZ2Compressor___init___impl(BZ2Compressor *self, int compresslevel)
327336
if (catch_bz2_error(bzerror))
328337
goto error;
329338

330-
return 0;
339+
return (PyObject *)self;
331340

332341
error:
333-
PyThread_free_lock(self->lock);
334-
self->lock = NULL;
335-
return -1;
342+
Py_DECREF(self);
343+
return NULL;
336344
}
337345

338346
static void
@@ -373,7 +381,7 @@ static PyTypeObject BZ2Compressor_Type = {
373381
0, /* tp_setattro */
374382
0, /* tp_as_buffer */
375383
Py_TPFLAGS_DEFAULT, /* tp_flags */
376-
_bz2_BZ2Compressor___init____doc__, /* tp_doc */
384+
_bz2_BZ2Compressor__doc__, /* tp_doc */
377385
0, /* tp_traverse */
378386
0, /* tp_clear */
379387
0, /* tp_richcompare */
@@ -388,9 +396,9 @@ static PyTypeObject BZ2Compressor_Type = {
388396
0, /* tp_descr_get */
389397
0, /* tp_descr_set */
390398
0, /* tp_dictoffset */
391-
_bz2_BZ2Compressor___init__, /* tp_init */
399+
0, /* tp_init */
392400
0, /* tp_alloc */
393-
PyType_GenericNew, /* tp_new */
401+
_bz2_BZ2Compressor, /* tp_new */
394402
};
395403

396404

@@ -621,44 +629,51 @@ BZ2Decompressor_getstate(BZ2Decompressor *self, PyObject *noargs)
621629
}
622630

623631
/*[clinic input]
624-
_bz2.BZ2Decompressor.__init__
632+
@classmethod
633+
_bz2.BZ2Decompressor.__new__
625634
626635
Create a decompressor object for decompressing data incrementally.
627636
628637
For one-shot decompression, use the decompress() function instead.
629638
[clinic start generated code]*/
630639

631-
static int
632-
_bz2_BZ2Decompressor___init___impl(BZ2Decompressor *self)
633-
/*[clinic end generated code: output=e4d2b9bb866ab8f1 input=95f6500dcda60088]*/
640+
static PyObject *
641+
_bz2_BZ2Decompressor_impl(PyTypeObject *type)
642+
/*[clinic end generated code: output=5150d51ccaab220e input=b87413ce51853528]*/
634643
{
644+
BZ2Decompressor *self;
635645
int bzerror;
636646

647+
assert(type != NULL && type->tp_alloc != NULL);
648+
self = (BZ2Decompressor *)type->tp_alloc(type, 0);
649+
if (self == NULL) {
650+
return NULL;
651+
}
652+
637653
self->lock = PyThread_allocate_lock();
638654
if (self->lock == NULL) {
655+
Py_DECREF(self);
639656
PyErr_SetString(PyExc_MemoryError, "Unable to allocate lock");
640-
return -1;
657+
return NULL;
641658
}
642659

643660
self->needs_input = 1;
644661
self->bzs_avail_in_real = 0;
645662
self->input_buffer = NULL;
646663
self->input_buffer_size = 0;
647-
Py_XSETREF(self->unused_data, PyBytes_FromStringAndSize(NULL, 0));
664+
self->unused_data = PyBytes_FromStringAndSize(NULL, 0);
648665
if (self->unused_data == NULL)
649666
goto error;
650667

651668
bzerror = BZ2_bzDecompressInit(&self->bzs, 0, 0);
652669
if (catch_bz2_error(bzerror))
653670
goto error;
654671

655-
return 0;
672+
return (PyObject *)self;
656673

657674
error:
658-
Py_CLEAR(self->unused_data);
659-
PyThread_free_lock(self->lock);
660-
self->lock = NULL;
661-
return -1;
675+
Py_DECREF(self);
676+
return NULL;
662677
}
663678

664679
static void
@@ -719,7 +734,7 @@ static PyTypeObject BZ2Decompressor_Type = {
719734
0, /* tp_setattro */
720735
0, /* tp_as_buffer */
721736
Py_TPFLAGS_DEFAULT, /* tp_flags */
722-
_bz2_BZ2Decompressor___init____doc__, /* tp_doc */
737+
_bz2_BZ2Decompressor__doc__, /* tp_doc */
723738
0, /* tp_traverse */
724739
0, /* tp_clear */
725740
0, /* tp_richcompare */
@@ -734,9 +749,9 @@ static PyTypeObject BZ2Decompressor_Type = {
734749
0, /* tp_descr_get */
735750
0, /* tp_descr_set */
736751
0, /* tp_dictoffset */
737-
_bz2_BZ2Decompressor___init__, /* tp_init */
752+
0, /* tp_init */
738753
0, /* tp_alloc */
739-
PyType_GenericNew, /* tp_new */
754+
_bz2_BZ2Decompressor, /* tp_new */
740755
};
741756

742757

0 commit comments

Comments
 (0)