Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34k
gh-143308: fix UAF when PickleBuffer is concurrently mutated in a callback#143312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
1e4c48ff3595334129734a6a5713a6a3a91b2117f5ec61e9c47396abfbc62c5334cc620d70297fc01ce1170b73fFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -4261,6 +4261,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): | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page.
a12k marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| # 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| :mod:`pickle`: fix use-after-free crashes when a :class:`~pickle.PickleBuffer` | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| is concurrently mutated by a custom buffer callback during pickling. | ||
| Patch by Bénédikt Tran and Aaron Wieczorek. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2636,53 +2636,61 @@ save_picklebuffer(PickleState *st, PicklerObject *self, PyObject *obj) | ||
| "PickleBuffer can only be pickled with protocol >= 5"); | ||
| return -1; | ||
| } | ||
| Py_buffer view; | ||
| if (PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO) != 0) { | ||
| return -1; | ||
| } | ||
| if (view.suboffsets != NULL || !PyBuffer_IsContiguous(&view, 'A')) { | ||
| PyErr_SetString(st->PicklingError, | ||
| "PickleBuffer can not be pickled when " | ||
| "pointing to a non-contiguous buffer"); | ||
Comment on lines 2645 to 2646 Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Out of curiosity, but do we have a test for this? ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Neither of the tests added test for non-contiguous, but should be straightforward to add a test against a buffer with strides... Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I just wanted to be sure that this branch was covered by existing tests or by other tests. We can increase the coverage in a follow-up PR. | ||
| 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) { | ||
| goto error; | ||
| } | ||
| in_band = PyObject_IsTrue(ret); | ||
| Py_DECREF(ret); | ||
| if (in_band == -1) { | ||
| goto error; | ||
| } | ||
| } | ||
| if (in_band) { | ||
| /* Write data in-band */ | ||
| if (view.readonly) { | ||
| rc =_save_bytes_data(st, self, obj, (const char *)view.buf, | ||
| view.len); | ||
| } | ||
| else { | ||
| 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) { | ||
| goto error; | ||
| } | ||
| if (view.readonly) { | ||
| const char readonly_buffer_op = READONLY_BUFFER; | ||
| if (_Pickler_Write(self, &readonly_buffer_op, 1) < 0) { | ||
| goto error; | ||
| } | ||
| } | ||
| } | ||
| PyBuffer_Release(&view); | ||
| return rc; | ||
| error: | ||
| PyBuffer_Release(&view); | ||
| return -1; | ||
| } | ||
| /* A copy of PyUnicode_AsRawUnicodeEscapeString() that also translates | ||
Uh oh!
There was an error while loading.Please reload this page.