Skip to content

Crash: NULL deref in _datetime when a static type outlives its module #151039

@ashm-dev

Description

@ashm-dev

What happened?

NULL pointer dereference in the _datetime C module → SIGSEGV. Reproducer:

import sys, gc
import _datetime

td = _datetime.timedelta            # static C type, survives the module
del sys.modules['_datetime']
del _datetime
sys.modules['_datetime'] = None     # block re-import
gc.collect()                        # module object is collected

td(seconds=2)                       # -> Segmentation fault

gdb at the crash: st == (datetime_state *) 0x0, with an ImportError already pending.

Root cause

The datetime types are static and can outlive the module, so per-module state (gh-117398) is fetched via _get_current_state(), which can return NULL when the module weakref is dead and re-import fails (blocked in sys.modules):

static datetime_state *
_get_current_state(PyObject **p_mod)
{
PyInterpreterState *interp = PyInterpreterState_Get();
PyObject *mod = get_current_module(interp);
if (mod == NULL) {
assert(!PyErr_Occurred());
if (PyErr_Occurred()) {
return NULL;
}
/* The static types can outlive the module,
* so we must re-import the module. */
mod = PyImport_ImportModule("_datetime");
if (mod == NULL) {
return NULL;
}
}
datetime_state *st = get_module_state(mod);
*p_mod = mod;
return st;
}

Callers dereference the returned state without a NULL check. At the crash site CONST_US_PER_SECOND(st) is st->us_per_second, so st == NULL → NULL deref:

datetime_state *st = GET_CURRENT_STATE(current_mod);
PyObject *x = NULL; /* running sum of microseconds */
PyObject *y = NULL; /* temp sum of microseconds */
double leftover_us = 0.0;
x = PyLong_FromLong(0);
if (x == NULL)
goto Done;
#define CLEANUP \
Py_DECREF(x); \
x = y; \
if (x == NULL) \
goto Done
if (microseconds) {
y = accum("microseconds", x, microseconds, _PyLong_GetOne(), &leftover_us);
CLEANUP;
}
if (milliseconds) {
y = accum("milliseconds", x, milliseconds, CONST_US_PER_MS(st), &leftover_us);
CLEANUP;
}
if (seconds) {
y = accum("seconds", x, seconds, CONST_US_PER_SECOND(st), &leftover_us);

Originally hit via ./python -X lazy_imports=all -m test test_datetime (segfault in test_strptime; test_datetime is also listed in #149640).

Versions

  • CPython main (3.16.0a0, ab930175e7e). Present since 3.14 (Isolate the _datetime extension module #117398) → 3.14, 3.15, main. Linux.
  • Build: CC=/opt/llvm/bin/clang CXX=/opt/llvm/bin/clang++ LDFLAGS='-fuse-ld=lld' ./configure; clang/lld 23.0.0git (4695c84).

Linked PRs

Metadata

Metadata

Assignees

Labels

extension-modulesC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dump
No fields configured for issues without a type.

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions