Skip to content

Commit f10063e

Browse files
Issue #21310: Fixed possible resource leak in failed open().
1 parent 8a8f7f9 commit f10063e

File tree

4 files changed

+80
-39
lines changed

4 files changed

+80
-39
lines changed

Lib/_pyio.py

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -200,38 +200,45 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
200200
(appending and "a" or "") +
201201
(updating and "+" or ""),
202202
closefd, opener=opener)
203-
line_buffering = False
204-
if buffering == 1 or buffering < 0 and raw.isatty():
205-
buffering = -1
206-
line_buffering = True
207-
if buffering < 0:
208-
buffering = DEFAULT_BUFFER_SIZE
209-
try:
210-
bs = os.fstat(raw.fileno()).st_blksize
211-
except (OSError, AttributeError):
212-
pass
203+
result = raw
204+
try:
205+
line_buffering = False
206+
if buffering == 1 or buffering < 0 and raw.isatty():
207+
buffering = -1
208+
line_buffering = True
209+
if buffering < 0:
210+
buffering = DEFAULT_BUFFER_SIZE
211+
try:
212+
bs = os.fstat(raw.fileno()).st_blksize
213+
except (OSError, AttributeError):
214+
pass
215+
else:
216+
if bs > 1:
217+
buffering = bs
218+
if buffering < 0:
219+
raise ValueError("invalid buffering size")
220+
if buffering == 0:
221+
if binary:
222+
return result
223+
raise ValueError("can't have unbuffered text I/O")
224+
if updating:
225+
buffer = BufferedRandom(raw, buffering)
226+
elif creating or writing or appending:
227+
buffer = BufferedWriter(raw, buffering)
228+
elif reading:
229+
buffer = BufferedReader(raw, buffering)
213230
else:
214-
if bs > 1:
215-
buffering = bs
216-
if buffering < 0:
217-
raise ValueError("invalid buffering size")
218-
if buffering == 0:
231+
raise ValueError("unknown mode: %r" % mode)
232+
result = buffer
219233
if binary:
220-
return raw
221-
raise ValueError("can't have unbuffered text I/O")
222-
if updating:
223-
buffer = BufferedRandom(raw, buffering)
224-
elif creating or writing or appending:
225-
buffer = BufferedWriter(raw, buffering)
226-
elif reading:
227-
buffer = BufferedReader(raw, buffering)
228-
else:
229-
raise ValueError("unknown mode: %r" % mode)
230-
if binary:
231-
return buffer
232-
text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering)
233-
text.mode = mode
234-
return text
234+
return result
235+
text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering)
236+
result = text
237+
text.mode = mode
238+
return result
239+
except:
240+
result.close()
241+
raise
235242

236243

237244
class DocDescriptor:

Lib/test/test_io.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,20 @@ def test_fileio_closefd(self):
653653
fileio.close()
654654
f2.readline()
655655

656+
def test_nonbuffered_textio(self):
657+
with warnings.catch_warnings(record=True) as recorded:
658+
with self.assertRaises(ValueError):
659+
self.open(support.TESTFN, 'w', buffering=0)
660+
support.gc_collect()
661+
self.assertEqual(recorded, [])
662+
663+
def test_invalid_newline(self):
664+
with warnings.catch_warnings(record=True) as recorded:
665+
with self.assertRaises(ValueError):
666+
self.open(support.TESTFN, 'w', newline='invalid')
667+
support.gc_collect()
668+
self.assertEqual(recorded, [])
669+
656670

657671
class CIOTest(IOTest):
658672

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Core and Builtins
2222
Library
2323
-------
2424

25+
- Issue #21310: Fixed possible resource leak in failed open().
26+
2527
- Issue #21677: Fixed chaining nonnormalized exceptions in io close() methods.
2628

2729
- Issue #11709: Fix the pydoc.help function to not fail when sys.stdin is not a

Modules/_io/_iomodule.c

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,12 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
235235
char rawmode[6], *m;
236236
int line_buffering, isatty;
237237

238-
PyObject *raw, *modeobj = NULL, *buffer = NULL, *wrapper = NULL;
238+
PyObject *raw, *modeobj = NULL, *buffer, *wrapper, *result = NULL;
239239

240240
_Py_IDENTIFIER(isatty);
241241
_Py_IDENTIFIER(fileno);
242242
_Py_IDENTIFIER(mode);
243+
_Py_IDENTIFIER(close);
243244

244245
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzziO:open", kwlist,
245246
&file, &mode, &buffering,
@@ -354,6 +355,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
354355
"OsiO", file, rawmode, closefd, opener);
355356
if (raw == NULL)
356357
return NULL;
358+
result = raw;
357359

358360
modeobj = PyUnicode_FromString(mode);
359361
if (modeobj == NULL)
@@ -412,7 +414,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
412414
}
413415

414416
Py_DECREF(modeobj);
415-
return raw;
417+
return result;
416418
}
417419

418420
/* wraps into a buffered file */
@@ -433,15 +435,16 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
433435

434436
buffer = PyObject_CallFunction(Buffered_class, "Oi", raw, buffering);
435437
}
436-
Py_CLEAR(raw);
437438
if (buffer == NULL)
438439
goto error;
440+
result = buffer;
441+
Py_DECREF(raw);
439442

440443

441444
/* if binary, returns the buffered file */
442445
if (binary) {
443446
Py_DECREF(modeobj);
444-
return buffer;
447+
return result;
445448
}
446449

447450
/* wraps into a TextIOWrapper */
@@ -450,20 +453,35 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
450453
buffer,
451454
encoding, errors, newline,
452455
line_buffering);
453-
Py_CLEAR(buffer);
454456
if (wrapper == NULL)
455457
goto error;
458+
result = wrapper;
459+
Py_DECREF(buffer);
456460

457461
if (_PyObject_SetAttrId(wrapper, &PyId_mode, modeobj) < 0)
458462
goto error;
459463
Py_DECREF(modeobj);
460-
return wrapper;
464+
return result;
461465

462466
error:
463-
Py_XDECREF(raw);
467+
if (result != NULL) {
468+
PyObject *exc, *val, *tb;
469+
PyErr_Fetch(&exc, &val, &tb);
470+
if (_PyObject_CallMethodId(result, &PyId_close, NULL) != NULL)
471+
PyErr_Restore(exc, val, tb);
472+
else {
473+
PyObject *val2;
474+
PyErr_NormalizeException(&exc, &val, &tb);
475+
Py_XDECREF(exc);
476+
Py_XDECREF(tb);
477+
PyErr_Fetch(&exc, &val2, &tb);
478+
PyErr_NormalizeException(&exc, &val2, &tb);
479+
PyException_SetContext(val2, val);
480+
PyErr_Restore(exc, val2, tb);
481+
}
482+
Py_DECREF(result);
483+
}
464484
Py_XDECREF(modeobj);
465-
Py_XDECREF(buffer);
466-
Py_XDECREF(wrapper);
467485
return NULL;
468486
}
469487

0 commit comments

Comments
 (0)