Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Lib/test/test_interpreters/test_static_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import unittest

from test.support import import_helper
# Raise SkipTest if subinterpreters not supported.
import_helper.import_module('_interpreters')
from concurrent import interpreters
from .utils import TestBase


xxsubtype = import_helper.import_module('xxsubtype')


class TestStaticTypeCache(TestBase):

def test_subinterp_no_crash(self):
# gh-158203: Static extension types registered via PyType_Ready()
# only have a globally shared _tp_cache field. Two interpreters
# writing to the same cache slot caused a use-after-free (SIGSEGV).
obj = xxsubtype.spamlist()
obj.append(1)
for name in dir(xxsubtype.spamlist):
getattr(xxsubtype.spamlist, name, None)

interp = interpreters.create()
try:
interp.exec("""
import xxsubtype
obj = xxsubtype.spamlist()
obj.append(42)
for name in dir(xxsubtype.spamlist):
getattr(xxsubtype.spamlist, name, None)
""")
finally:
interp.close()

def test_multiple_subinterps_no_crash(self):
# Same as above but with several subinterpreters concurrently
# reading and populating the cache.
xxsubtype.spamlist().append(0)

interps = [interpreters.create() for _ in range(3)]
try:
for interp in interps:
interp.exec("""
import xxsubtype
for name in dir(xxsubtype.spamlist):
getattr(xxsubtype.spamlist, name, None)
""")
finally:
for interp in interps:
interp.close()


if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix a crash when using a static extension type from a subinterpreter. The
type's ``_tp_cache`` field is globally shared; a second interpreter resizing
the cache caused a use-after-free (SIGSEGV). Fix by disabling the type-cache
for static non-heap types that lack per-interpreter state.
26 changes: 23 additions & 3 deletions Python/typecache.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,25 +87,39 @@ cache_slot(PyTypeObject *type)
assert(state != NULL);
return &state->_tp_cache;
}
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
return NULL;
}
return &type->_tp_cache;
}

static inline struct type_cache *
cache_get(PyTypeObject *type)
{
return (struct type_cache *)FT_ATOMIC_LOAD_PTR_ACQUIRE(*cache_slot(type));
void **slot = cache_slot(type);
if (slot == NULL) {
return NULL;
}
return (struct type_cache *)FT_ATOMIC_LOAD_PTR_ACQUIRE(*slot);
}

static inline void
cache_set(PyTypeObject *type, struct type_cache *cache)
{
FT_ATOMIC_STORE_PTR_RELEASE(*cache_slot(type), cache);
void **slot = cache_slot(type);
if (slot == NULL) {
return;
}
FT_ATOMIC_STORE_PTR_RELEASE(*slot, cache);
}

void
_PyTypeCache_InitType(PyTypeObject *type)
{
*cache_slot(type) = &empty_cache;
void **slot = cache_slot(type);
if (slot != NULL) {
*slot = &empty_cache;
}
}

static inline void
Expand Down Expand Up @@ -175,6 +189,9 @@ void
_PyTypeCache_Insert(PyTypeObject *type, PyObject *name, PyObject *value)
{
struct type_cache *cache = cache_get(type);
if (cache == NULL) {
return;
}
// If the cache is full, resize it before inserting the new entry.
// this also handles the case of empty cache where available is 0 but there are no entries.
if (cache->available == 0) {
Expand Down Expand Up @@ -241,6 +258,9 @@ _PyTypeCache_Invalidate(PyTypeObject *type)
{
OBJECT_STAT_INC(type_cache_invalidations);
struct type_cache *cache = cache_get(type);
if (cache == NULL) {
return;
}
cache_set(type, &empty_cache);
cache_free_delayed(cache);
}
Loading