Skip to content

Commit c1d34b2

Browse files
committed
Merge branch 'main' into writer_create
2 parents fa27190 + 9d22a53 commit c1d34b2

8 files changed

Lines changed: 265 additions & 121 deletions

File tree

‎Lib/test/test_bytes.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,15 @@ def test_fromhex(self):
520520
self.type2test.fromhex(data)
521521
self.assertIn('at position %s' % pos, str(cm.exception))
522522

523+
# gh-158583: Check for out of bounds reads (uninitialized bytes).
524+
# Create an array from a list to not overallocate.
525+
a = array.array('B', list(b'1234 ')) # Py_ISSPACE() loop
526+
self.assertEqual(self.type2test.fromhex(a), b'\x12\x34')
527+
528+
a = array.array('B', list(b'12345')) # Missing second digit
529+
with self.assertRaises(ValueError):
530+
self.type2test.fromhex(a)
531+
523532
def test_hex(self):
524533
self.assertRaises(TypeError, self.type2test.hex)
525534
self.assertRaises(TypeError, self.type2test.hex, 1)

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -244,39 +244,60 @@ def test_resize(self):
244244
"""Test _PyBytes_Resize()"""
245245
_resize = _testcapi.bytes_resize
246246

247-
def resize(obj, size, new):
248-
result = _resize(obj, size, new)
249-
if 1 <= len(result):
250-
if new or size != len(obj):
251-
# gh-156995: Make sure that the result is a fresh object.
252-
# Previously, _PyBytes_Resize(&obj, 1) returned a singleton
253-
# if _PyObject_IsUniquelyReferenced() is false.
254-
self.assertEqual(sys.getrefcount(result), 1)
255-
self.assertFalse(sys._is_immortal(result))
256-
else:
247+
def assert_is_fresh_copy(result, refcnt, is_new_obj):
248+
self.assertEqual(refcnt, 1)
249+
self.assertTrue(is_new_obj)
250+
self.assertFalse(sys._is_immortal(result))
251+
252+
def resize(obj, size, new, compute_hash=False):
253+
old_size = len(obj)
254+
result, refcnt, is_new_obj = _resize(obj, size, new,
255+
compute_hash=compute_hash)
256+
257+
if size == old_size:
258+
# Return the same object unchanged
259+
self.assertFalse(is_new_obj)
260+
elif old_size == 0:
261+
assert_is_fresh_copy(result, refcnt, is_new_obj)
262+
elif size == 0:
257263
# check that the result is the empty bytes string singleton
264+
self.assertEqual(result, b'')
258265
self.assertTrue(sys._is_immortal(result))
266+
self.assertTrue(is_new_obj)
267+
elif (not new) or compute_hash:
268+
# gh-156995: Make sure that the result is a fresh object.
269+
# Previously, _PyBytes_Resize(&obj, 1) returned a singleton
270+
# if _PyObject_IsUniquelyReferenced() is false.
271+
assert_is_fresh_copy(result, refcnt, is_new_obj)
272+
else:
273+
# An in-place resize can return the same memory address, or
274+
# not. 'is_new_obj' cannot be tested.
275+
self.assertEqual(refcnt, 1)
276+
self.assertFalse(sys._is_immortal(result))
277+
259278
return result
260279

261280
for new in True, False:
262-
with self.subTest(new=new):
263-
self.assertEqual(resize(b'abc', 0, new), b'')
264-
self.assertEqual(resize(b'abc', 1, new), b'a')
265-
self.assertEqual(resize(b'abc', 2, new), b'ab')
266-
self.assertEqual(resize(b'abc', 3, new), b'abc')
267-
b = resize(b'abc', 4, new)
268-
self.assertEqual(len(b), 4)
269-
self.assertEqual(b[:3], b'abc')
270-
271-
self.assertEqual(resize(b'a', 0, new), b'')
272-
self.assertEqual(resize(b'a', 1, new), b'a')
273-
b = resize(b'a', 2, new)
274-
self.assertEqual(len(b), 2)
275-
self.assertEqual(b[:1], b'a')
276-
277-
self.assertEqual(resize(b'', 0, new), b'')
278-
self.assertEqual(len(resize(b'', 1, new)), 1)
279-
self.assertEqual(len(resize(b'', 2, new)), 2)
281+
for compute_hash in True, False:
282+
with self.subTest(new=new, compute_hash=compute_hash):
283+
self.assertEqual(resize(b'abc', 0, new, compute_hash), b'')
284+
self.assertEqual(resize(b'abc', 1, new, compute_hash), b'a')
285+
self.assertEqual(resize(b'abc', 2, new, compute_hash), b'ab')
286+
self.assertEqual(resize(b'abc', 3, new, compute_hash), b'abc')
287+
288+
b = resize(b'abc', 4, new, compute_hash)
289+
self.assertEqual(len(b), 4)
290+
self.assertEqual(b[:3], b'abc')
291+
292+
self.assertEqual(resize(b'a', 0, new, compute_hash), b'')
293+
self.assertEqual(resize(b'a', 1, new, compute_hash), b'a')
294+
b = resize(b'a', 2, new, compute_hash)
295+
self.assertEqual(len(b), 2)
296+
self.assertEqual(b[:1], b'a')
297+
298+
self.assertEqual(resize(b'', 0, new, compute_hash), b'')
299+
self.assertEqual(len(resize(b'', 1, new, compute_hash)), 1)
300+
self.assertEqual(len(resize(b'', 2, new, compute_hash)), 2)
280301

281302
self.assertRaises(SystemError, resize, b'abc', -1, False)
282303
self.assertRaises(SystemError, resize, bytearray(b'abc'), 3, False)

‎Lib/test/test_capi/test_unicode.py‎

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,28 +170,75 @@ def test_write_macro(self):
170170

171171
def test_resize(self):
172172
"""Test PyUnicode_Resize()"""
173-
resize = _testlimitedcapi.unicode_resize
173+
_resize = _testlimitedcapi.unicode_resize
174+
resize_null = _testlimitedcapi.unicode_resize_null
175+
176+
def resize(s, length, new=True, compute_hash=False):
177+
if s is not NULL and isinstance(s, str):
178+
old_length = len(s)
179+
else:
180+
old_length = 0
181+
result, int_result, refcnt, is_new_obj = _resize(s, length,
182+
new, compute_hash)
183+
self.assertEqual(int_result, 0)
184+
185+
if length == old_length:
186+
# Return the same object unchanged
187+
self.assertFalse(is_new_obj)
188+
elif length == 0:
189+
# Get the empty Unicode string
190+
self.assertEqual(result, '')
191+
self.assertTrue(sys._is_immortal(result))
192+
self.assertTrue(is_new_obj)
193+
elif (not new) or compute_hash:
194+
# Get a fresh copy
195+
self.assertEqual(refcnt, 1)
196+
self.assertTrue(is_new_obj)
197+
self.assertFalse(sys._is_immortal(result))
198+
else:
199+
# In-size replace can return the same address, or not.
200+
# So 'is_new_obj' cannot be tested.
201+
self.assertFalse(sys._is_immortal(result))
202+
203+
return result
174204

175205
strings = [
176206
# all strings have exactly 3 characters
177207
'abc', '\xa1\xa2\xa3', '\u4f60\u597d\u4e16',
178208
'\U0001f600\U0001f601\U0001f602'
179209
]
180-
for s in strings:
181-
self.assertEqual(resize(s, 3), (s, 0))
182-
self.assertEqual(resize(s, 2), (s[:2], 0))
183-
self.assertEqual(resize(s, 4), (s + '\0', 0))
184-
self.assertEqual(resize(s, 10), (s + '\0'*7, 0))
185-
self.assertEqual(resize(s, 0), ('', 0))
186-
self.assertRaises(MemoryError, resize, s, PY_SSIZE_T_MAX)
187-
self.assertRaises(SystemError, resize, s, -1)
188-
self.assertRaises(SystemError, resize, s, PY_SSIZE_T_MIN)
210+
for new in (True, False):
211+
for compute_hash in (True, False):
212+
for s in strings:
213+
with self.subTest(new=new, compute_hash=compute_hash, s=s):
214+
self.assertEqual(resize(s, 3, new, compute_hash),
215+
s)
216+
self.assertEqual(resize(s, 2, new, compute_hash),
217+
s[:2])
218+
self.assertEqual(resize(s, 4, new, compute_hash),
219+
s + '\0')
220+
self.assertEqual(resize(s, 10, new, compute_hash),
221+
s + '\0'*7)
222+
self.assertEqual(resize(s, 0, new, compute_hash),
223+
'')
224+
225+
with self.assertRaises(MemoryError):
226+
resize(s, PY_SSIZE_T_MAX, new, compute_hash)
227+
with self.assertRaises(SystemError):
228+
resize(s, -1, new, compute_hash)
229+
with self.assertRaises(SystemError):
230+
resize(s, PY_SSIZE_T_MIN, new, compute_hash)
231+
189232
self.assertRaises(SystemError, resize, b'abc', 0)
190233
self.assertRaises(SystemError, resize, [], 0)
191234
self.assertRaises(SystemError, resize, NULL, 0)
192235
# TODO: Test PyUnicode_Resize() with non-modifiable and legacy unicode
193236
# and with NULL as the address.
194237

238+
# Test PyUnicode_Resize(NULL, length)
239+
self.assertRaises(SystemError, resize_null, 0)
240+
self.assertRaises(SystemError, resize_null, 123)
241+
195242
def test_append(self):
196243
"""Test PyUnicode_Append()"""
197244
append = _testlimitedcapi.unicode_append
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:meth:`bytes.fromhex` and :meth:`bytearray.fromhex`: Fix uninitialized
2+
memory read. Patch by Victor Stinner.

‎Modules/_io/bufferedio.c‎

Lines changed: 66 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,109 +1205,115 @@ _io__Buffered_readinto1_impl(buffered *self, Py_buffer *buffer)
12051205
static PyObject *
12061206
_buffered_readline(buffered *self, Py_ssize_t limit)
12071207
{
1208-
PyObject *res = NULL;
1209-
PyObject *chunks = NULL;
1210-
Py_ssize_t n;
1211-
const char *start, *s, *end;
1208+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
12121209

12131210
CHECK_CLOSED(self, "readline of closed file")
12141211

12151212
/* First, try to find a line in the buffer. This can run unlocked because
12161213
the calls to the C API are simple enough that they can't trigger
12171214
any thread switch. */
1218-
n = Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t);
1219-
if (limit >= 0 && n > limit)
1215+
Py_ssize_t n = Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t);
1216+
if (limit >= 0 && n > limit) {
12201217
n = limit;
1221-
start = self->buffer + self->pos;
1222-
s = memchr(start, '\n', n);
1218+
}
1219+
const char *start = self->buffer + self->pos;
1220+
const char *s = memchr(start, '\n', n);
12231221
if (s != NULL) {
1224-
res = PyBytes_FromStringAndSize(start, s - start + 1);
1225-
if (res != NULL)
1226-
self->pos += s - start + 1;
1227-
goto end_unlocked;
1222+
n = s - start + 1;
1223+
PyObject *res = PyBytes_FromStringAndSize(start, n);
1224+
if (res == NULL) {
1225+
return NULL;
1226+
}
1227+
self->pos += n;
1228+
return res;
12281229
}
1230+
12291231
if (n == limit) {
1230-
res = PyBytes_FromStringAndSize(start, n);
1231-
if (res != NULL)
1232-
self->pos += n;
1233-
goto end_unlocked;
1232+
PyObject *res = PyBytes_FromStringAndSize(start, n);
1233+
if (res == NULL) {
1234+
return NULL;
1235+
}
1236+
self->pos += n;
1237+
return res;
12341238
}
12351239

1236-
if (!ENTER_BUFFERED(self))
1237-
goto end_unlocked;
1240+
PyBytesWriter *writer = NULL;
1241+
int locked = 0;
1242+
if (!ENTER_BUFFERED(self)) {
1243+
goto error;
1244+
}
1245+
locked = 1;
12381246

12391247
/* Now we try to get some more from the raw stream */
1240-
chunks = PyList_New(0);
1241-
if (chunks == NULL)
1242-
goto end;
1248+
writer = PyBytesWriter_Create(0);
1249+
if (writer == NULL) {
1250+
goto error;
1251+
}
1252+
12431253
if (n > 0) {
1244-
res = PyBytes_FromStringAndSize(start, n);
1245-
if (res == NULL)
1246-
goto end;
1247-
if (PyList_Append(chunks, res) < 0) {
1248-
Py_CLEAR(res);
1249-
goto end;
1254+
if (PyBytesWriter_WriteBytes(writer, start, n) < 0) {
1255+
goto error;
12501256
}
1251-
Py_CLEAR(res);
12521257
self->pos += n;
1253-
if (limit >= 0)
1258+
if (limit >= 0) {
12541259
limit -= n;
1260+
}
12551261
}
12561262
if (self->writable) {
1257-
PyObject *r = buffered_flush_and_rewind_unlocked(self);
1258-
if (r == NULL)
1259-
goto end;
1260-
Py_DECREF(r);
1263+
PyObject *res = buffered_flush_and_rewind_unlocked(self);
1264+
if (res == NULL) {
1265+
goto error;
1266+
}
1267+
Py_DECREF(res);
12611268
}
12621269

12631270
for (;;) {
12641271
_bufferedreader_reset_buf(self);
12651272
n = _bufferedreader_fill_buffer(self);
1266-
if (n == -1)
1267-
goto end;
1268-
if (n <= 0)
1273+
if (n == -1) {
1274+
goto error;
1275+
}
1276+
if (n <= 0) {
12691277
break;
1270-
if (limit >= 0 && n > limit)
1278+
}
1279+
if (limit >= 0 && n > limit) {
12711280
n = limit;
1281+
}
12721282
start = self->buffer;
1273-
end = start + n;
1283+
const char *end = start + n;
12741284
s = start;
12751285
while (s < end) {
12761286
if (*s++ == '\n') {
1277-
res = PyBytes_FromStringAndSize(start, s - start);
1278-
if (res == NULL)
1279-
goto end;
1287+
if (PyBytesWriter_WriteBytes(writer, start, s - start) < 0) {
1288+
goto error;
1289+
}
12801290
self->pos = s - start;
12811291
goto found;
12821292
}
12831293
}
1284-
res = PyBytes_FromStringAndSize(start, n);
1285-
if (res == NULL)
1286-
goto end;
1294+
1295+
if (PyBytesWriter_WriteBytes(writer, start, n) < 0) {
1296+
goto error;
1297+
}
12871298
if (n == limit) {
12881299
self->pos = n;
12891300
break;
12901301
}
1291-
if (PyList_Append(chunks, res) < 0) {
1292-
Py_CLEAR(res);
1293-
goto end;
1294-
}
1295-
Py_CLEAR(res);
1296-
if (limit >= 0)
1302+
if (limit >= 0) {
12971303
limit -= n;
1304+
}
12981305
}
1299-
found:
1300-
if (res != NULL && PyList_Append(chunks, res) < 0) {
1301-
Py_CLEAR(res);
1302-
goto end;
1303-
}
1304-
Py_XSETREF(res, PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks));
13051306

1306-
end:
1307+
found:
13071308
LEAVE_BUFFERED(self)
1308-
end_unlocked:
1309-
Py_XDECREF(chunks);
1310-
return res;
1309+
return PyBytesWriter_Finish(writer);
1310+
1311+
error:
1312+
PyBytesWriter_Discard(writer);
1313+
if (locked) {
1314+
LEAVE_BUFFERED(self)
1315+
}
1316+
return NULL;
13111317
}
13121318

13131319
/*[clinic input]

0 commit comments

Comments
 (0)