Skip to content

Commit 88b8e63

Browse files
[3.13] gh-142783: Fix possible use after free in zoneinfo module (GH-142790) (GH-142861)
(cherry picked from commit 8307a14) Co-authored-by: wangxiaolei <fatelei@gmail.com>
1 parent 9361207 commit 88b8e63

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
@@ -287,16 +287,11 @@ static PyObject *
287287
get_weak_cache(zoneinfo_state *state, PyTypeObject *type)
288288
{
289289
if (type == state->ZoneInfoType) {
290+
Py_INCREF(state->ZONEINFO_WEAK_CACHE);
290291
return state->ZONEINFO_WEAK_CACHE;
291292
}
292293
else {
293-
PyObject *cache =
294-
PyObject_GetAttrString((PyObject *)type, "_weak_cache");
295-
// We are assuming that the type lives at least as long as the function
296-
// that calls get_weak_cache, and that it holds a reference to the
297-
// cache, so we'll return a "borrowed reference".
298-
Py_XDECREF(cache);
299-
return cache;
294+
return PyObject_GetAttrString((PyObject *)type, "_weak_cache");
300295
}
301296
}
302297

@@ -323,26 +318,30 @@ zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key)
323318
PyObject *weak_cache = get_weak_cache(state, type);
324319
instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None);
325320
if (instance == NULL) {
321+
Py_DECREF(weak_cache);
326322
return NULL;
327323
}
328324

329325
if (instance == Py_None) {
330326
Py_DECREF(instance);
331327
PyObject *tmp = zoneinfo_new_instance(state, type, key);
332328
if (tmp == NULL) {
329+
Py_DECREF(weak_cache);
333330
return NULL;
334331
}
335332

336333
instance =
337334
PyObject_CallMethod(weak_cache, "setdefault", "OO", key, tmp);
338335
Py_DECREF(tmp);
339336
if (instance == NULL) {
337+
Py_DECREF(weak_cache);
340338
return NULL;
341339
}
342340
((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE;
343341
}
344342

345343
update_strong_cache(state, type, key, instance);
344+
Py_DECREF(weak_cache);
346345
return instance;
347346
}
348347

@@ -504,12 +503,14 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls,
504503
PyObject *item = NULL;
505504
PyObject *pop = PyUnicode_FromString("pop");
506505
if (pop == NULL) {
506+
Py_DECREF(weak_cache);
507507
return NULL;
508508
}
509509

510510
PyObject *iter = PyObject_GetIter(only_keys);
511511
if (iter == NULL) {
512512
Py_DECREF(pop);
513+
Py_DECREF(weak_cache);
513514
return NULL;
514515
}
515516

@@ -534,6 +535,7 @@ zoneinfo_ZoneInfo_clear_cache_impl(PyTypeObject *type, PyTypeObject *cls,
534535
Py_DECREF(pop);
535536
}
536537

538+
Py_DECREF(weak_cache);
537539
if (PyErr_Occurred()) {
538540
return NULL;
539541
}

0 commit comments

Comments
 (0)