Skip to content

Commit f133a52

Browse files
committed
gh-142783: Fix use-after-free vulnerability in zoneinfo module
1 parent 39ecb17 commit f133a52

File tree

3 files changed

+51
-7
lines changed

3 files changed

+51
-7
lines changed

Lib/test/test_zoneinfo/test_zoneinfo.py

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

1554+
def test_weak_cache_descriptor_use_after_free(self):
1555+
from zoneinfo import ZoneInfo
1556+
1557+
class Cache:
1558+
def __init__(self):
1559+
self.data = {}
1560+
1561+
def get(self, key, default=None):
1562+
return self.data.get(key, default)
1563+
1564+
def setdefault(self, key, default):
1565+
return self.data.setdefault(key, default)
1566+
1567+
def clear(self, *args, **kwargs):
1568+
self.data.clear()
1569+
1570+
class BombDescriptor:
1571+
def __get__(self, obj, owner):
1572+
return Cache()
1573+
1574+
class EvilZoneInfo(ZoneInfo):
1575+
pass
1576+
1577+
EvilZoneInfo._weak_cache = BombDescriptor()
1578+
1579+
zone1 = EvilZoneInfo("UTC")
1580+
zone2 = EvilZoneInfo("UTC")
1581+
1582+
self.assertIsNotNone(zone1)
1583+
self.assertIsNotNone(zone2)
1584+
self.assertEqual(str(zone1), "UTC")
1585+
self.assertEqual(str(zone2), "UTC")
1586+
1587+
EvilZoneInfo.clear_cache()
1588+
1589+
zone3 = EvilZoneInfo("UTC")
1590+
self.assertIsNotNone(zone3)
1591+
self.assertEqual(str(zone3), "UTC")
1592+
15541593

15551594
class CZoneInfoCacheTest(ZoneInfoCacheTest):
15561595
module = c_zoneinfo
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix zoneinfo use-after-free with descriptor _weak_cache. Return new
2+
reference from get_weak_cache() instead of borrowed reference to prevent
3+
premature object free.

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)