Skip to content

Memory leaks in readline module when PySys_Audit fails #140398

@ashm-dev

Description

@ashm-dev

Bug report

Bug description:

LeakSanitizer detects memory leaks in the readline module when running test_audit.

Affected Code

Four functions in Modules/readline.c:

  • line 298: PyUnicode_FSConverter call

    cpython/Modules/readline.c

    Lines 278 to 316 in b2f9fb9

    /* Exported function to load a readline history file */
    /*[clinic input]
    @critical_section
    readline.read_history_file
    filename as filename_obj: object = None
    /
    Load a readline history file.
    The default filename is ~/.history.
    [clinic start generated code]*/
    static PyObject *
    readline_read_history_file_impl(PyObject *module, PyObject *filename_obj)
    /*[clinic end generated code: output=66a951836fb54fbb input=5d86fd7813172a67]*/
    {
    PyObject *filename_bytes;
    if (filename_obj != Py_None) {
    if (!PyUnicode_FSConverter(filename_obj, &filename_bytes))
    return NULL;
    if (PySys_Audit("open", "OCi", filename_obj, 'r', 0) < 0) {
    return NULL;
    }
    errno = read_history(PyBytes_AS_STRING(filename_bytes));
    Py_DECREF(filename_bytes);
    } else {
    /* Use the documented default filename here,
    * even though readline expands it different internally. */
    if (PySys_Audit("open", "sCi", "~/.history", 'r', 0) < 0) {
    return NULL;
    }
    errno = read_history(NULL);
    }
    if (errno)
    return PyErr_SetFromErrno(PyExc_OSError);
    Py_RETURN_NONE;
    }
  • line 342: PyUnicode_FSConverter call

    cpython/Modules/readline.c

    Lines 320 to 366 in b2f9fb9

    /* Exported function to save a readline history file */
    /*[clinic input]
    @critical_section
    readline.write_history_file
    filename as filename_obj: object = None
    /
    Save a readline history file.
    The default filename is ~/.history.
    [clinic start generated code]*/
    static PyObject *
    readline_write_history_file_impl(PyObject *module, PyObject *filename_obj)
    /*[clinic end generated code: output=fbcad13d8ef59ae6 input=34aaada95120cfaa]*/
    {
    PyObject *filename_bytes;
    const char *filename;
    int err;
    if (filename_obj != Py_None) {
    if (!PyUnicode_FSConverter(filename_obj, &filename_bytes))
    return NULL;
    filename = PyBytes_AS_STRING(filename_bytes);
    if (PySys_Audit("open", "OCi", filename_obj, 'w', 0) < 0) {
    return NULL;
    }
    } else {
    filename_bytes = NULL;
    filename = NULL;
    /* Use the documented default filename here,
    * even though readline expands it different internally. */
    if (PySys_Audit("open", "sCi", "~/.history", 'w', 0) < 0) {
    return NULL;
    }
    }
    errno = err = write_history(filename);
    int history_length = FT_ATOMIC_LOAD_INT_RELAXED(_history_length);
    if (!err && history_length >= 0)
    history_truncate_file(filename, history_length);
    Py_XDECREF(filename_bytes);
    errno = err;
    if (errno)
    return PyErr_SetFromErrno(PyExc_OSError);
    Py_RETURN_NONE;
    }
  • line 399: PyUnicode_FSConverter call

    cpython/Modules/readline.c

    Lines 371 to 424 in b2f9fb9

    /*[clinic input]
    @critical_section
    readline.append_history_file
    nelements: int
    filename as filename_obj: object = None
    /
    Append the last nelements items of the history list to file.
    The default filename is ~/.history.
    [clinic start generated code]*/
    static PyObject *
    readline_append_history_file_impl(PyObject *module, int nelements,
    PyObject *filename_obj)
    /*[clinic end generated code: output=5df06fc9da56e4e4 input=78a6061a8d3a0275]*/
    {
    if (nelements < 0)
    {
    PyErr_SetString(PyExc_ValueError, "nelements must be positive");
    return NULL;
    }
    PyObject *filename_bytes;
    const char *filename;
    int err;
    if (filename_obj != Py_None) {
    if (!PyUnicode_FSConverter(filename_obj, &filename_bytes))
    return NULL;
    filename = PyBytes_AS_STRING(filename_bytes);
    if (PySys_Audit("open", "OCi", filename_obj, 'a', 0) < 0) {
    return NULL;
    }
    } else {
    filename_bytes = NULL;
    filename = NULL;
    /* Use the documented default filename here,
    * even though readline expands it different internally. */
    if (PySys_Audit("open", "sCi", "~/.history", 'a', 0) < 0) {
    return NULL;
    }
    }
    errno = err = append_history(
    nelements - libedit_append_replace_history_offset, filename);
    int history_length = FT_ATOMIC_LOAD_INT_RELAXED(_history_length);
    if (!err && history_length >= 0)
    history_truncate_file(filename, history_length);
    Py_XDECREF(filename_bytes);
    errno = err;
    if (errno)
    return PyErr_SetFromErrno(PyExc_OSError);
    Py_RETURN_NONE;
    }
  • line 255: PyUnicode_FSConverter call

    cpython/Modules/readline.c

    Lines 237 to 276 in b2f9fb9

    /*[clinic input]
    @critical_section
    readline.read_init_file
    filename as filename_obj: object = None
    /
    Execute a readline initialization file.
    The default filename is the last filename used.
    [clinic start generated code]*/
    static PyObject *
    readline_read_init_file_impl(PyObject *module, PyObject *filename_obj)
    /*[clinic end generated code: output=8e059b676142831e input=62b767adfab6cc15]*/
    {
    PyObject *filename_bytes;
    if (filename_obj != Py_None) {
    if (!PyUnicode_FSConverter(filename_obj, &filename_bytes))
    return NULL;
    if (PySys_Audit("open", "OCi", filename_obj, 'r', 0) < 0) {
    return NULL;
    }
    errno = rl_read_init_file(PyBytes_AS_STRING(filename_bytes));
    Py_DECREF(filename_bytes);
    } else {
    /* We have the choice to either try to exactly reproduce the
    * logic to find the filename, ignore it, or provide a dummy value.
    * In contract to the history file manipulations, there's no
    * clear default to choose. */
    if (PySys_Audit("open", "sCi", "<readline_init_file>", 'r', 0) < 0) {
    return NULL;
    }
    errno = rl_read_init_file(NULL);
    }
    if (errno)
    return PyErr_SetFromErrno(PyExc_OSError);
    disable_bracketed_paste();
    Py_RETURN_NONE;
    }

Steps to Reproduce

Build:

CC=clang CXX=clang++ ./configure --disable-optimizations --with-pydebug --with-address-sanitizer && make -j$(nproc)

Run:

./python -X dev -X showrefcount -m test test_audit -j0

Result

LeakSanitizer reports 4 memory leaks of 75 bytes each (300 bytes total). Full logs attached as .log files.

Summary from LeakSanitizer:

SUMMARY: AddressSanitizer: 300 byte(s) leaked in 4 allocation(s).

All leaks originate from PyUnicode_FSConverter calls in the affected functions.

CPython versions tested on:

3.14, 3.15, CPython main branch

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions