Skip to content

Commit 897e2b4

Browse files
[3.14] gh-142783: Fix possible use after free in zoneinfo module (GH-142790) (GH-142862)
(cherry picked from commit 8307a14) Co-authored-by: wangxiaolei <fatelei@gmail.com>
1 parent 4feba4d commit 897e2b4

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

Lib/test/test_zoneinfo/test_zoneinfo.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,26 @@ def __eq__(self, other):
15511551
except CustomError:
15521552
pass
15531553

1554+
def test_weak_cache_descriptor_use_after_free(self):
1555+
class BombDescriptor:
1556+
def __get__(self, obj, owner):
1557+
return {}
1558+
1559+
class EvilZoneInfo(self.klass):
1560+
pass
1561+
1562+
# Must be set after the class creation.
1563+
EvilZoneInfo._weak_cache = BombDescriptor()
1564+
1565+
key = "America/Los_Angeles"
1566+
zone1 = EvilZoneInfo(key)
1567+
self.assertEqual(str(zone1), key)
1568+
1569+
EvilZoneInfo.clear_cache()
1570+
zone2 = EvilZoneInfo(key)
1571+
self.assertEqual(str(zone2), key)
1572+
self.assertIsNot(zone2, zone1)
1573+
15541574

15551575
class CZoneInfoCacheTest(ZoneInfoCacheTest):
15561576
module = c_zoneinfo
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix zoneinfo use-after-free with descriptor _weak_cache. a descriptor as _weak_cache could cause crashes during object creation. The fix ensures proper reference counting for descriptor-provided objects.

Modules/_zoneinfo.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -292,16 +292,11 @@ static PyObject *
292292
get_weak_cache(zoneinfo_state *state, PyTypeObject *type)
293293
{
294294
if (type == state->ZoneInfoType) {
295+
Py_INCREF(state->ZONEINFO_WEAK_CACHE);
295296
return state->ZONEINFO_WEAK_CACHE;
296297
}
297298
else {
298-
PyObject *cache =
299-
PyObject_GetAttrString((PyObject *)type, "_weak_cache");
300-
// We are assuming that the type lives at least as long as the function
301-
// that calls get_weak_cache, and that it holds a reference to the
302-
// cache, so we'll return a "borrowed reference".
303-
Py_XDECREF(cache);
304-
return cache;
299+
return PyObject_GetAttrString((PyObject *)type, "_weak_cache");
305300
}
306301
}
307302

@@ -328,26 +323,30 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key)
328323
PyObject *weak_cache = get_weak_cache(state, type);
329324
instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None);
330325
if (instance == NULL) {
326+
Py_DECREF(weak_cache);
331327
return NULL;
332328
}
333329

334330
if (instance == Py_None) {
335331
Py_DECREF(instance);
336332
PyObject *tmp = zoneinfo_new_instance(state, type, key);
337333
if (tmp == NULL) {
334+
Py_DECREF(weak_cache);
338335
return NULL;
339336
}
340337

341338
instance =
342339
PyObject_CallMethod(weak_cache, "setdefault", "OO", key, tmp);
343340
Py_DECREF(tmp);
344341
if (instance == NULL) {
342+
Py_DECREF(weak_cache);
345343
return NULL;
346344
}
347345
((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE;
348346
}
349347

350348
update_strong_cache(state, type, key, instance);
349+
Py_DECREF(weak_cache);
351350
return instance;
352351
}
353352

@@ -510,12 +509,14 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls,
510509
PyObject *item = NULL;
511510
PyObject *pop = PyUnicode_FromString("pop");
512511
if (pop == NULL) {
512+
Py_DECREF(weak_cache);
513513
return NULL;
514514
}
515515

516516
PyObject *iter = PyObject_GetIter(only_keys);
517517
if (iter == NULL) {
518518
Py_DECREF(pop);
519+
Py_DECREF(weak_cache);
519520
return NULL;
520521
}
521522

@@ -540,6 +541,7 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls,
540541
Py_DECREF(pop);
541542
}
542543

544+
Py_DECREF(weak_cache);
543545
if (PyErr_Occurred()) {
544546
return NULL;
545547
}

0 commit comments

Comments
 (0)