mirror of
https://github.com/python/cpython.git
synced 2026-04-14 07:41:00 +00:00
[3.14] gh-143308: fix UAF when PickleBuffer is concurrently mutated in a callback (GH-143312) (#143396)
gh-143308: fix UAF when PickleBuffer is concurrently mutated in a callback (GH-143312)
(cherry picked from commit 6c53af18f6)
---------------
Co-authored-by: Aaron Wieczorek <aaronw@fastmail.com>
Co-authored-by: Aaron Wieczorek <woz@Aarons-MacBook-Pro.local>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
parent
6eafcaee64
commit
c99d87db1f
3 changed files with 55 additions and 15 deletions
|
|
@ -4090,6 +4090,35 @@ def check_array(arr):
|
|||
# 2-D, non-contiguous
|
||||
check_array(arr[::2])
|
||||
|
||||
def test_concurrent_mutation_in_buffer_with_bytearray(self):
|
||||
def factory():
|
||||
s = b"a" * 16
|
||||
return bytearray(s), s
|
||||
self.do_test_concurrent_mutation_in_buffer_callback(factory)
|
||||
|
||||
def test_concurrent_mutation_in_buffer_with_memoryview(self):
|
||||
def factory():
|
||||
obj = memoryview(b"a" * 32)[10:26]
|
||||
sub = b"a" * len(obj)
|
||||
return obj, sub
|
||||
self.do_test_concurrent_mutation_in_buffer_callback(factory)
|
||||
|
||||
def do_test_concurrent_mutation_in_buffer_callback(self, factory):
|
||||
# See: https://github.com/python/cpython/issues/143308.
|
||||
class R:
|
||||
def __bool__(self):
|
||||
buf.release()
|
||||
return True
|
||||
|
||||
for proto in range(5, pickle.HIGHEST_PROTOCOL + 1):
|
||||
obj, sub = factory()
|
||||
buf = pickle.PickleBuffer(obj)
|
||||
buffer_callback = lambda _: R()
|
||||
|
||||
with self.subTest(proto=proto, obj=obj, sub=sub):
|
||||
res = self.dumps(buf, proto, buffer_callback=buffer_callback)
|
||||
self.assertIn(sub, res)
|
||||
|
||||
def test_evil_class_mutating_dict(self):
|
||||
# https://github.com/python/cpython/issues/92930
|
||||
from random import getrandbits
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
:mod:`pickle`: fix use-after-free crashes when a :class:`~pickle.PickleBuffer`
|
||||
is concurrently mutated by a custom buffer callback during pickling.
|
||||
Patch by Bénédikt Tran and Aaron Wieczorek.
|
||||
|
|
@ -2561,53 +2561,61 @@ save_picklebuffer(PickleState *st, PicklerObject *self, PyObject *obj)
|
|||
"PickleBuffer can only be pickled with protocol >= 5");
|
||||
return -1;
|
||||
}
|
||||
const Py_buffer* view = PyPickleBuffer_GetBuffer(obj);
|
||||
if (view == NULL) {
|
||||
Py_buffer view;
|
||||
if (PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (view->suboffsets != NULL || !PyBuffer_IsContiguous(view, 'A')) {
|
||||
if (view.suboffsets != NULL || !PyBuffer_IsContiguous(&view, 'A')) {
|
||||
PyErr_SetString(st->PicklingError,
|
||||
"PickleBuffer can not be pickled when "
|
||||
"pointing to a non-contiguous buffer");
|
||||
return -1;
|
||||
goto error;
|
||||
}
|
||||
|
||||
int rc = 0;
|
||||
int in_band = 1;
|
||||
if (self->buffer_callback != NULL) {
|
||||
PyObject *ret = PyObject_CallOneArg(self->buffer_callback, obj);
|
||||
if (ret == NULL) {
|
||||
return -1;
|
||||
goto error;
|
||||
}
|
||||
in_band = PyObject_IsTrue(ret);
|
||||
Py_DECREF(ret);
|
||||
if (in_band == -1) {
|
||||
return -1;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (in_band) {
|
||||
/* Write data in-band */
|
||||
if (view->readonly) {
|
||||
return _save_bytes_data(st, self, obj, (const char *)view->buf,
|
||||
view->len);
|
||||
if (view.readonly) {
|
||||
rc = _save_bytes_data(st, self, obj, (const char *)view.buf,
|
||||
view.len);
|
||||
}
|
||||
else {
|
||||
return _save_bytearray_data(st, self, obj, (const char *)view->buf,
|
||||
view->len);
|
||||
rc = _save_bytearray_data(st, self, obj, (const char *)view.buf,
|
||||
view.len);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Write data out-of-band */
|
||||
const char next_buffer_op = NEXT_BUFFER;
|
||||
if (_Pickler_Write(self, &next_buffer_op, 1) < 0) {
|
||||
return -1;
|
||||
goto error;
|
||||
}
|
||||
if (view->readonly) {
|
||||
if (view.readonly) {
|
||||
const char readonly_buffer_op = READONLY_BUFFER;
|
||||
if (_Pickler_Write(self, &readonly_buffer_op, 1) < 0) {
|
||||
return -1;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
PyBuffer_Release(&view);
|
||||
return rc;
|
||||
|
||||
error:
|
||||
PyBuffer_Release(&view);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A copy of PyUnicode_AsRawUnicodeEscapeString() that also translates
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue