Skip to content

_testcapi mishandles Py_fopen() failures #155905

Description

@serhiy-storchaka

Bug report

Py_fopen() sets an exception and returns NULL on error, but several _testcapi helpers ignore both halves of that contract.

Modules/_testcapi/object.c does not check the result at all. All four helpers pass it straight to PyObject_Print():

    fp = Py_fopen(filename, "w+");

    if (PyObject_Print(object, fp, flags) < 0) {

so a NULL file pointer reaches fprintf() and the interpreter crashes:

>>> import _testcapi
>>> _testcapi.call_pyobject_print('x', '/nonexistent-dir/out.txt', False)
Segmentation fault

The same happens with pyobject_print_null(), pyobject_print_noref_object() and pyobject_print_os_error().

Modules/_testcapimodule.c sets a second exception. The six pymarshal_* helpers do:

    fp = Py_fopen(filename, "rb");
    if (fp == NULL) {
        PyErr_SetFromErrno(PyExc_OSError);
        return NULL;
    }

so OSError is instantiated while Py_fopen()'s exception is still pending:

>>> _testcapi.pymarshal_read_object_from_file('nonexistent')
SystemError: <class 'OSError'> returned a result with an exception set

On a debug build it aborts instead: Objects/call.c:342: _PyObject_Call: Assertion `!_PyErr_Occurred(tstate)' failed.

Both date from f89e5e2 (gh-127350), which converted these helpers from fopen() to Py_fopen(). They affect 3.14 and later.

Related cleanup: these helpers also close the file with fclose() rather than Py_fclose(), which the documentation requires -- 8 sites in object.c, 6 in _testcapimodule.c and 14 in run.c. It is harmless today, since Py_fclose() is just a wrapper.

All other Py_fopen() call sites in the tree check the result correctly.

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixes3.15pre-release feature fixes, bugs and security fixes3.16new features, bugs and security fixestestsTests in the Lib/test dirtype-bugAn unexpected behavior, bug, or errortype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions