Skip to content

gh-125318: Prevent segfaults when zoneinfo is used with "false friends"#139132

Open
pganssle wants to merge 4 commits intopython:mainfrom
pganssle:zoneinfo_false_friends
Open

gh-125318: Prevent segfaults when zoneinfo is used with "false friends"#139132
pganssle wants to merge 4 commits intopython:mainfrom
pganssle:zoneinfo_false_friends

Conversation

@pganssle
Copy link
Copy Markdown
Member

@pganssle pganssle commented Sep 18, 2025

Fixes #125318.

I think this can be backported to bugfix branches, since it just prevents segfaults. It might make things slower in some situations for people who have "false friend" classes that happen to have the same memory layout as datetime but that is not really a supported mode of operation anyway, we're really just fixing this because you shouldn't be able to crash the interpreter from pure Python code.

@pganssle
Copy link
Copy Markdown
Member Author

Not sure who might want to review this, maybe @StanFromIreland or @serhiy-storchaka or @ZeroIntensity?

@serhiy-storchaka serhiy-storchaka self-requested a review September 19, 2025 09:48
Comment thread Modules/_zoneinfo.c Outdated
return NULL;
}

fold = (unsigned char)PyLong_AsLong(fold_obj);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, this is an undefined behavior. You can use (unsigned char)(unsigned long) if you are fine with silent ignoring the higher bits.

But assert(fold < 2) below can fail, because fold can be in range 0-255 here. We need more strict runtime check to ensure that it is only 0 or 1.

long fold_long = PyLong_AsLong(fold_obj);
Py_DECREF(fold_obj);
if (...) {...}
fold = `(unsigned char)fold_long;

Comment thread Modules/_zoneinfo.c Outdated
return NULL;
}

year = PyLong_AsLong(year_obj);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible integer overflow here. Use PyLong_AsInt().

And do we need an additional range check before passing it to find_tzrule_ttinfo() or it works with the full range of int?

Comment thread Modules/_zoneinfo.c Outdated
int year;
if (PyDateTime_Check(dt)) {
year = PyDateTime_GET_YEAR(dt);
} else {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick.

Suggested change
} else {
}
else {

Comment thread Modules/_zoneinfo.c Outdated
}
}
return find_tzrule_ttinfo(&(self->tzrule_after), ts, fold, year);
} else {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else {
}
else {

Comment thread Modules/_zoneinfo.c Outdated
unsigned char fold;
if (PyDateTime_Check(dt)) {
fold = PyDateTime_DATE_GET_FOLD(dt);
} else {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else {
}
else {

Comment thread Modules/_zoneinfo.c
}
if (fold_int < 0 || fold_int > 2) {
PyErr_Format(PyExc_ValueError,
"fold must be 0 or 1, got %d", fold_int);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"fold must be 0 or 1, got %d", fold_int);
"fold must be 0 or 1, got %ld", fold_int);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use int and PyLong_AsLong() for fold_int.

Comment thread Modules/_zoneinfo.c
Comment thread Modules/_zoneinfo.c

if (year < MINYEAR || year > 9999) {
PyErr_Format(PyExc_ValueError,
"year out of range, should be in (%d, %d) but got %d",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See error messages in check_date_args(), datetime_date_fromisocalendar_impl(), utc_to_seconds().

Comment thread Modules/_zoneinfo.c
}
if (fold_int < 0 || fold_int > 2) {
PyErr_Format(PyExc_ValueError,
"fold must be 0 or 1, got %d", fold_int);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use int and PyLong_AsLong() for fold_int.

@github-actions
Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting core review stale Stale PR or inactive for long period of time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Segfault from zoneinfo with custom DateTime class

3 participants