Commit 2fe9bac4 authored by Victor Stinner's avatar Victor Stinner

Close #16742: Fix misuse of memory allocations in PyOS_Readline()

The GIL must be held to call PyMem_Malloc(), whereas PyOS_Readline() releases
the GIL to read input.

The result of the C callback PyOS_ReadlineFunctionPointer must now be a string
allocated by PyMem_RawMalloc() or PyMem_RawRealloc() (or NULL if an error
occurred), instead of a string allocated by PyMem_Malloc() or PyMem_Realloc().

Fixing this issue was required to setup a hook on PyMem_Malloc(), for example
using the tracemalloc module.

PyOS_Readline() copies the result of PyOS_ReadlineFunctionPointer() into a new
buffer allocated by PyMem_Malloc(). So the public API of PyOS_Readline() does
not change.
parent 6cf185dc
...@@ -166,6 +166,14 @@ the same library that the Python runtime is using. ...@@ -166,6 +166,14 @@ the same library that the Python runtime is using.
resulting string. For example, The :mod:`readline` module sets resulting string. For example, The :mod:`readline` module sets
this hook to provide line-editing and tab-completion features. this hook to provide line-editing and tab-completion features.
The result must be a string allocated by :c:func:`PyMem_RawMalloc` or
:c:func:`PyMem_RawRealloc`, or *NULL* if an error occurred.
.. versionchanged:: 3.4
The result must be allocated by :c:func:`PyMem_RawMalloc` or
:c:func:`PyMem_RawRealloc`, instead of being allocated by
:c:func:`PyMem_Malloc` or :c:func:`PyMem_Realloc`.
.. c:function:: struct _node* PyParser_SimpleParseString(const char *str, int start) .. c:function:: struct _node* PyParser_SimpleParseString(const char *str, int start)
......
...@@ -587,3 +587,9 @@ that may require changes to your code. ...@@ -587,3 +587,9 @@ that may require changes to your code.
attribute in the chain referring to the innermost function. Introspection attribute in the chain referring to the innermost function. Introspection
libraries that assumed the previous behaviour was intentional can use libraries that assumed the previous behaviour was intentional can use
:func:`inspect.unwrap` to gain equivalent behaviour. :func:`inspect.unwrap` to gain equivalent behaviour.
* (C API) The result of the :c:var:`PyOS_ReadlineFunctionPointer` callback must
now be a string allocated by :c:func:`PyMem_RawMalloc` or
:c:func:`PyMem_RawRealloc`, or *NULL* if an error occurred, instead of a
string allocated by :c:func:`PyMem_Malloc` or :c:func:`PyMem_Realloc`.
...@@ -10,6 +10,11 @@ Projected release date: 2013-10-20 ...@@ -10,6 +10,11 @@ Projected release date: 2013-10-20
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #16742: The result of the C callback PyOS_ReadlineFunctionPointer must
now be a string allocated by PyMem_RawMalloc() or PyMem_RawRealloc() (or NULL
if an error occurred), instead of a string allocated by PyMem_Malloc() or
PyMem_Realloc().
- Issue #19199: Remove ``PyThreadState.tick_counter`` field - Issue #19199: Remove ``PyThreadState.tick_counter`` field
- Fix macro expansion of _PyErr_OCCURRED(), and make sure to use it in at - Fix macro expansion of _PyErr_OCCURRED(), and make sure to use it in at
......
...@@ -1176,7 +1176,7 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) ...@@ -1176,7 +1176,7 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
/* We got an EOF, return a empty string. */ /* We got an EOF, return a empty string. */
if (p == NULL) { if (p == NULL) {
p = PyMem_Malloc(1); p = PyMem_RawMalloc(1);
if (p != NULL) if (p != NULL)
*p = '\0'; *p = '\0';
RESTORE_LOCALE(saved_locale) RESTORE_LOCALE(saved_locale)
...@@ -1204,7 +1204,7 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) ...@@ -1204,7 +1204,7 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
/* Copy the malloc'ed buffer into a PyMem_Malloc'ed one and /* Copy the malloc'ed buffer into a PyMem_Malloc'ed one and
release the original. */ release the original. */
q = p; q = p;
p = PyMem_Malloc(n+2); p = PyMem_RawMalloc(n+2);
if (p != NULL) { if (p != NULL) {
strncpy(p, q, n); strncpy(p, q, n);
p[n] = '\n'; p[n] = '\n';
......
...@@ -113,18 +113,22 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) ...@@ -113,18 +113,22 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
{ {
size_t n; size_t n;
char *p, *pr; char *p, *pr;
n = 100; n = 100;
if ((p = (char *)PyMem_MALLOC(n)) == NULL) p = (char *)PyMem_RawMalloc(n);
if (p == NULL)
return NULL; return NULL;
fflush(sys_stdout); fflush(sys_stdout);
if (prompt) if (prompt)
fprintf(stderr, "%s", prompt); fprintf(stderr, "%s", prompt);
fflush(stderr); fflush(stderr);
switch (my_fgets(p, (int)n, sys_stdin)) { switch (my_fgets(p, (int)n, sys_stdin)) {
case 0: /* Normal case */ case 0: /* Normal case */
break; break;
case 1: /* Interrupt */ case 1: /* Interrupt */
PyMem_FREE(p); PyMem_RawFree(p);
return NULL; return NULL;
case -1: /* EOF */ case -1: /* EOF */
case -2: /* Error */ case -2: /* Error */
...@@ -140,7 +144,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) ...@@ -140,7 +144,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
PyErr_SetString(PyExc_OverflowError, "input line too long"); PyErr_SetString(PyExc_OverflowError, "input line too long");
return NULL; return NULL;
} }
pr = (char *)PyMem_REALLOC(p, n + incr); pr = (char *)PyMem_RawRealloc(p, n + incr);
if (pr == NULL) { if (pr == NULL) {
PyMem_FREE(p); PyMem_FREE(p);
PyErr_NoMemory(); PyErr_NoMemory();
...@@ -151,7 +155,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) ...@@ -151,7 +155,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
break; break;
n += strlen(p+n); n += strlen(p+n);
} }
pr = (char *)PyMem_REALLOC(p, n+1); pr = (char *)PyMem_RawRealloc(p, n+1);
if (pr == NULL) { if (pr == NULL) {
PyMem_FREE(p); PyMem_FREE(p);
PyErr_NoMemory(); PyErr_NoMemory();
...@@ -174,7 +178,8 @@ char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, char *); ...@@ -174,7 +178,8 @@ char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, char *);
char * char *
PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
{ {
char *rv; char *rv, *res;
size_t len;
if (_PyOS_ReadlineTState == PyThreadState_GET()) { if (_PyOS_ReadlineTState == PyThreadState_GET()) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_RuntimeError,
...@@ -221,5 +226,14 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) ...@@ -221,5 +226,14 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
_PyOS_ReadlineTState = NULL; _PyOS_ReadlineTState = NULL;
return rv; if (rv == NULL)
return NULL;
len = strlen(rv) + 1;
res = PyMem_Malloc(len);
if (res != NULL)
memcpy(res, rv, len);
PyMem_RawFree(rv);
return res;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment