Skip to content

Commit 7c428b7

Browse files
committed
Fatal error if timeout > PY_TIMEOUT_MAX
The pthread implementation of PyThread_acquire_lock() now fails with a fatal error if the timeout is larger than PY_TIMEOUT_MAX, as done in the Windows implementation. The check prevents any risk of overflow in PyThread_acquire_lock(). Add also PY_DWORD_MAX constant.
1 parent 17aba7c commit 7c428b7

File tree

6 files changed

+29
-24
lines changed

6 files changed

+29
-24
lines changed

Include/pyport.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,9 @@ extern _invalid_parameter_handler _Py_silent_invalid_parameter_handler;
787787
#include <android/api-level.h>
788788
#endif
789789

790+
/* Maximum value of the Windows DWORD type */
791+
#define PY_DWORD_MAX 4294967295U
792+
790793
/* This macro used to tell whether Python was built with multithreading
791794
* enabled. Now multithreading is always enabled, but keep the macro
792795
* for compatibility.

Include/pythread.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,22 @@ PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock, int);
4242
and floating-point numbers allowed.
4343
*/
4444
#define PY_TIMEOUT_T long long
45+
4546
#if defined(_POSIX_THREADS)
4647
/* PyThread_acquire_lock_timed() uses _PyTime_FromNanoseconds(us * 1000),
4748
convert microseconds to nanoseconds. */
4849
# define PY_TIMEOUT_MAX (PY_LLONG_MAX / 1000)
50+
#elif defined (NT_THREADS)
51+
/* In the NT API, the timeout is a DWORD and is expressed in milliseconds */
52+
# if 0xFFFFFFFFLL * 1000 < PY_LLONG_MAX
53+
# define PY_TIMEOUT_MAX (0xFFFFFFFFLL * 1000)
54+
# else
55+
# define PY_TIMEOUT_MAX PY_LLONG_MAX
56+
# endif
4957
#else
5058
# define PY_TIMEOUT_MAX PY_LLONG_MAX
5159
#endif
5260

53-
/* In the NT API, the timeout is a DWORD and is expressed in milliseconds */
54-
#if defined (NT_THREADS)
55-
#if 0xFFFFFFFFLL * 1000 < PY_TIMEOUT_MAX
56-
#undef PY_TIMEOUT_MAX
57-
#define PY_TIMEOUT_MAX (0xFFFFFFFFLL * 1000)
58-
#endif
59-
#endif
6061

6162
/* If microseconds == 0, the call is non-blocking: it returns immediately
6263
even when the lock can't be acquired.

Modules/_winapi.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@
6161

6262
#define T_HANDLE T_POINTER
6363

64-
#define DWORD_MAX 4294967295U
65-
6664
/* Grab CancelIoEx dynamically from kernel32 */
6765
static int has_CancelIoEx = -1;
6866
static BOOL (CALLBACK *Py_CancelIoEx)(HANDLE, LPOVERLAPPED);
@@ -184,7 +182,7 @@ class DWORD_return_converter(CReturnConverter):
184182
185183
def render(self, function, data):
186184
self.declare(data)
187-
self.err_occurred_if("_return_value == DWORD_MAX", data)
185+
self.err_occurred_if("_return_value == PY_DWORD_MAX", data)
188186
data.return_conversion.append(
189187
'return_value = Py_BuildValue("k", _return_value);\n')
190188
[python start generated code]*/
@@ -1009,7 +1007,7 @@ _winapi_GetExitCodeProcess_impl(PyObject *module, HANDLE process)
10091007

10101008
if (! result) {
10111009
PyErr_SetFromWindowsErr(GetLastError());
1012-
exit_code = DWORD_MAX;
1010+
exit_code = PY_DWORD_MAX;
10131011
}
10141012

10151013
return exit_code;
@@ -1466,7 +1464,7 @@ _winapi_WriteFile_impl(PyObject *module, HANDLE handle, PyObject *buffer,
14661464
}
14671465

14681466
Py_BEGIN_ALLOW_THREADS
1469-
len = (DWORD)Py_MIN(buf->len, DWORD_MAX);
1467+
len = (DWORD)Py_MIN(buf->len, PY_DWORD_MAX);
14701468
ret = WriteFile(handle, buf->buf, len, &written,
14711469
overlapped ? &overlapped->overlapped : NULL);
14721470
Py_END_ALLOW_THREADS

Modules/posixmodule.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,6 @@ static int win32_can_symlink = 0;
390390
#endif
391391
#endif
392392

393-
#define DWORD_MAX 4294967295U
394-
395393
#ifdef MS_WINDOWS
396394
#define INITFUNC PyInit_nt
397395
#define MODNAME "nt"
@@ -3817,7 +3815,7 @@ os__getvolumepathname_impl(PyObject *module, PyObject *path)
38173815
/* Volume path should be shorter than entire path */
38183816
buflen = Py_MAX(buflen, MAX_PATH);
38193817

3820-
if (buflen > DWORD_MAX) {
3818+
if (buflen > PY_DWORD_MAX) {
38213819
PyErr_SetString(PyExc_OverflowError, "path too long");
38223820
return NULL;
38233821
}

Python/thread_nt.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,13 @@ PyThread_acquire_lock_timed(PyThread_type_lock aLock,
283283
milliseconds = microseconds / 1000;
284284
if (microseconds % 1000 > 0)
285285
++milliseconds;
286-
if ((DWORD) milliseconds != milliseconds)
287-
Py_FatalError("Timeout too large for a DWORD, "
288-
"please check PY_TIMEOUT_MAX");
286+
if (milliseconds > PY_DWORD_MAX) {
287+
Py_FatalError("Timeout larger than PY_TIMEOUT_MAX");
288+
}
289289
}
290-
else
290+
else {
291291
milliseconds = INFINITE;
292+
}
292293

293294
dprintf(("%lu: PyThread_acquire_lock_timed(%p, %lld) called\n",
294295
PyThread_get_thread_ident(), aLock, microseconds));

Python/thread_pthread.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,16 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
324324
dprintf(("PyThread_acquire_lock_timed(%p, %lld, %d) called\n",
325325
lock, microseconds, intr_flag));
326326

327+
if (microseconds > PY_TIMEOUT_MAX) {
328+
Py_FatalError("Timeout larger than PY_TIMEOUT_MAX");
329+
}
330+
327331
if (microseconds > 0) {
328332
MICROSECONDS_TO_TIMESPEC(microseconds, ts);
329333

330334
if (!intr_flag) {
331-
/* the caller must ensures that microseconds <= PY_TIMEOUT_MAX
332-
and so microseconds * 1000 cannot overflow. PY_TIMEOUT_MAX
333-
is defined to prevent this specific overflow. */
335+
/* cannot overflow thanks to (microseconds > PY_TIMEOUT_MAX)
336+
check done above */
334337
_PyTime_t timeout = _PyTime_FromNanoseconds(microseconds * 1000);
335338
deadline = _PyTime_GetMonotonicClock() + timeout;
336339
}
@@ -363,8 +366,9 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
363366
else if (dt > 0) {
364367
_PyTime_t realtime_deadline = _PyTime_GetSystemClock() + dt;
365368
if (_PyTime_AsTimespec(realtime_deadline, &ts) < 0) {
366-
success = PY_LOCK_FAILURE;
367-
goto exit;
369+
/* Cannot occur thanks to (microseconds > PY_TIMEOUT_MAX)
370+
check done above */
371+
Py_UNREACHABLE();
368372
}
369373
/* no need to update microseconds value, the code only care
370374
if (microseconds > 0 or (microseconds == 0). */

0 commit comments

Comments
 (0)