Skip to content

Commit 5f6ac2d

Browse files
authored
gh-110481: Fix Py_SET_REFCNT() integer overflow (#112174)
If Py_NOGIL is defined and Py_SET_REFCNT() is called with a reference count larger than UINT32_MAX, make the object immortal. Set _Py_IMMORTAL_REFCNT constant type to Py_ssize_t to fix the following compiler warning: Include/internal/pycore_global_objects_fini_generated.h:14:24: warning: comparison of integers of different signs: 'Py_ssize_t' (aka 'long') and 'unsigned int' [-Wsign-compare] if (Py_REFCNT(obj) < _Py_IMMORTAL_REFCNT) { ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~
1 parent c298238 commit 5f6ac2d

File tree

3 files changed

+26
-10
lines changed

3 files changed

+26
-10
lines changed

Doc/c-api/refcounting.rst

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ of Python objects.
3434
3535
Set the object *o* reference counter to *refcnt*.
3636
37+
On :ref:`Python build with Free Threading <free-threading-build>`, if
38+
*refcnt* is larger than ``UINT32_MAX``, the object is made :term:`immortal`.
39+
3740
This function has no effect on :term:`immortal` objects.
3841
3942
.. versionadded:: 3.9

Doc/using/configure.rst

+2
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ General Options
287287

288288
.. versionadded:: 3.11
289289

290+
.. _free-threading-build:
291+
290292
.. option:: --disable-gil
291293

292294
Enables **experimental** support for running Python without the

Include/object.h

+21-10
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ having all the lower 32 bits set, which will avoid the reference count to go
8888
beyond the refcount limit. Immortality checks for reference count decreases will
8989
be done by checking the bit sign flag in the lower 32 bits.
9090
*/
91-
#define _Py_IMMORTAL_REFCNT UINT_MAX
91+
#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX)
9292

9393
#else
9494
/*
@@ -103,7 +103,7 @@ immortality, but the execution would still be correct.
103103
Reference count increases and decreases will first go through an immortality
104104
check by comparing the reference count field to the immortality reference count.
105105
*/
106-
#define _Py_IMMORTAL_REFCNT (UINT_MAX >> 2)
106+
#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX >> 2)
107107
#endif
108108

109109
// Py_GIL_DISABLED builds indicate immortal objects using `ob_ref_local`, which is
@@ -317,11 +317,11 @@ static inline Py_ssize_t Py_SIZE(PyObject *ob) {
317317
static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op)
318318
{
319319
#if defined(Py_GIL_DISABLED)
320-
return op->ob_ref_local == _Py_IMMORTAL_REFCNT_LOCAL;
320+
return (op->ob_ref_local == _Py_IMMORTAL_REFCNT_LOCAL);
321321
#elif SIZEOF_VOID_P > 4
322-
return _Py_CAST(PY_INT32_T, op->ob_refcnt) < 0;
322+
return (_Py_CAST(PY_INT32_T, op->ob_refcnt) < 0);
323323
#else
324-
return op->ob_refcnt == _Py_IMMORTAL_REFCNT;
324+
return (op->ob_refcnt == _Py_IMMORTAL_REFCNT);
325325
#endif
326326
}
327327
#define _Py_IsImmortal(op) _Py_IsImmortal(_PyObject_CAST(op))
@@ -350,15 +350,23 @@ static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) {
350350
if (_Py_IsImmortal(ob)) {
351351
return;
352352
}
353+
353354
#ifndef Py_GIL_DISABLED
354355
ob->ob_refcnt = refcnt;
355356
#else
356357
if (_Py_IsOwnedByCurrentThread(ob)) {
357-
// Set local refcount to desired refcount and shared refcount to zero,
358-
// but preserve the shared refcount flags.
359-
assert(refcnt < UINT32_MAX);
360-
ob->ob_ref_local = _Py_STATIC_CAST(uint32_t, refcnt);
361-
ob->ob_ref_shared &= _Py_REF_SHARED_FLAG_MASK;
358+
if ((size_t)refcnt > (size_t)UINT32_MAX) {
359+
// On overflow, make the object immortal
360+
op->ob_tid = _Py_UNOWNED_TID;
361+
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
362+
op->ob_ref_shared = 0;
363+
}
364+
else {
365+
// Set local refcount to desired refcount and shared refcount
366+
// to zero, but preserve the shared refcount flags.
367+
ob->ob_ref_local = _Py_STATIC_CAST(uint32_t, refcnt);
368+
ob->ob_ref_shared &= _Py_REF_SHARED_FLAG_MASK;
369+
}
362370
}
363371
else {
364372
// Set local refcount to zero and shared refcount to desired refcount.
@@ -750,6 +758,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
750758
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
751759
uint32_t new_local = local + 1;
752760
if (new_local == 0) {
761+
// local is equal to _Py_IMMORTAL_REFCNT: do nothing
753762
return;
754763
}
755764
if (_Py_IsOwnedByCurrentThread(op)) {
@@ -763,6 +772,8 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
763772
PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
764773
PY_UINT32_T new_refcnt = cur_refcnt + 1;
765774
if (new_refcnt == 0) {
775+
// cur_refcnt is equal to _Py_IMMORTAL_REFCNT: the object is immortal,
776+
// do nothing
766777
return;
767778
}
768779
op->ob_refcnt_split[PY_BIG_ENDIAN] = new_refcnt;

0 commit comments

Comments
 (0)