Sitelet https://github.com/python/cpython/issues/140398
Skip to content

Memory leaks in readline module when PySys_Audit fails #140398

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

Activity

  1. added a commit that references this issue on Oct 21, 2025
  2. added a commit that references this issue on Oct 21, 2025
  3. added a commit that references this issue on Oct 21, 2025
  4. added a commit that references this issue on Oct 21, 2025
  5. added a commit that references this issue on Oct 27, 2025
  6. added a commit that references this issue on Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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