Skip to content

Commit

Permalink
bpo-36020: Require vsnprintf() to build Python (pythonGH-20899)
Browse files Browse the repository at this point in the history
The C99 functions snprintf() and vsnprintf() are now required
to build Python.

PyOS_snprintf() and PyOS_vsnprintf() no longer call Py_FatalError().
Previously, they called Py_FatalError() on a buffer overflow on platforms
which don't provide vsnprintf().
  • Loading branch information
vstinner committed Jun 15, 2020
1 parent e822e37 commit 7ab92d5
Show file tree
Hide file tree
Showing 4 changed files with 14 additions and 44 deletions.
10 changes: 3 additions & 7 deletions Doc/c-api/conversion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,8 @@ not.
The wrappers ensure that *str*[*size*-1] is always ``'\0'`` upon return. They
never write more than *size* bytes (including the trailing ``'\0'``) into str.
Both functions require that ``str != NULL``, ``size > 0`` and ``format !=
NULL``.
If the platform doesn't have :c:func:`vsnprintf` and the buffer size needed to
avoid truncation exceeds *size* by more than 512 bytes, Python aborts with a
:c:func:`Py_FatalError`.
Both functions require that ``str != NULL``, ``size > 0``, ``format != NULL``
and ``size < INT_MAX``.
The return value (*rv*) for these functions should be interpreted as follows:
Expand All @@ -48,8 +44,8 @@ The return value (*rv*) for these functions should be interpreted as follows:
this case too, but the rest of *str* is undefined. The exact cause of the error
depends on the underlying platform.
The following functions provide locale-independent string to number conversions.
The following functions provide locale-independent string to number conversions.
.. c:function:: double PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception)
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ that may require changes to your code.
Build Changes
=============

* The C99 functions :c:func:`snprintf` and :c:func:`vsnprintf` are now required
to build Python.
(Contributed by Victor Stinner in :issue:`36020`.)


C API Changes
=============
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The C99 functions :c:func:`snprintf` and :c:func:`vsnprintf` are now required
to build Python.
42 changes: 5 additions & 37 deletions Python/mysnprintf.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
PyOS_snprintf and PyOS_vsnprintf never write more than size bytes
(including the trailing '\0') into str.
If the platform doesn't have vsnprintf, and the buffer size needed to
avoid truncation exceeds size by more than 512, Python aborts with a
Py_FatalError.
Return value (rv):
When 0 <= rv < size, the output conversion was unexceptional, and
Expand All @@ -37,6 +33,7 @@
PyMem_Malloc couldn't obtain space for a temp buffer.
CAUTION: Unlike C99, str != NULL and size > 0 are required.
Also, size must be smaller than INT_MAX.
*/

int
Expand All @@ -56,56 +53,27 @@ PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)
{
assert(str != NULL);
assert(size > 0);
assert(size <= (INT_MAX - 1));
assert(format != NULL);

int len; /* # bytes written, excluding \0 */
#if defined(_MSC_VER) || defined(HAVE_SNPRINTF)
# define _PyOS_vsnprintf_EXTRA_SPACE 1
#else
# define _PyOS_vsnprintf_EXTRA_SPACE 512
char *buffer;
#endif
/* We take a size_t as input but return an int. Sanity check
* our input so that it won't cause an overflow in the
* vsnprintf return value or the buffer malloc size. */
if (size > INT_MAX - _PyOS_vsnprintf_EXTRA_SPACE) {
* vsnprintf return value. */
if (size > INT_MAX - 1) {
len = -666;
goto Done;
}

#if defined(_MSC_VER)
len = _vsnprintf(str, size, format, va);
#elif defined(HAVE_SNPRINTF)
len = vsnprintf(str, size, format, va);
#else
/* Emulate vsnprintf(). */
buffer = PyMem_MALLOC(size + _PyOS_vsnprintf_EXTRA_SPACE);
if (buffer == NULL) {
len = -666;
goto Done;
}

len = vsprintf(buffer, format, va);
if (len < 0) {
/* ignore the error */;
}
else if ((size_t)len >= size + _PyOS_vsnprintf_EXTRA_SPACE) {
_Py_FatalErrorFunc(__func__, "Buffer overflow");
}
else {
const size_t to_copy = (size_t)len < size ?
(size_t)len : size - 1;
assert(to_copy < size);
memcpy(str, buffer, to_copy);
str[to_copy] = '\0';
}
PyMem_FREE(buffer);
len = vsnprintf(str, size, format, va);
#endif

Done:
if (size > 0) {
str[size-1] = '\0';
}
return len;
#undef _PyOS_vsnprintf_EXTRA_SPACE
}

0 comments on commit 7ab92d5

Please sign in to comment.