-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
Description
Bug report
Bug description:
Summary
An Infinite Loop bug was identified in the pure-Python implementation of zoneinfo._common.load_data (used by _zoneinfo.c). The bug is triggered when parsing a ZoneInfo file (version >= 2) that ends unexpectedly while reading the timezone string. The loop condition while (c := fobj.read(1)) != b"\n" fails to handle the End-Of-File (EOF) condition, causing it to loop indefinitely if b"\n" is never found.
Component
Lib/zoneinfo/_common.py
Affected Functions
load_data
Root Cause Analysis
The code attempts to read the timezone string byte-by-byte until a newline character is found:
# Lib/zoneinfo/_common.py:122
while (c := fobj.read(1)) != b"\n":
tz_bytes += cIf the file pointer reaches EOF, fobj.read(1) returns b"" (empty bytes).
The comparison b"" != b"\n" evaluates to True.
The loop continues indefinitely, appending empty bytes to tz_bytes (or doing nothing if tz_bytes is efficiently handled, but consuming CPU).
Reproduction
- Input: A constructed ZoneInfo file with Version 2 header but truncated before the trailing newline of the timezone string.
- Command:
python3 reproduce_issue_4.py
Script:
import io
import zoneinfo
import struct
import time
def reproduce():
# First Header (V1 part of V2 file)
# Magic(4) + Version(1) + Reserved(15) + Counts(24)
header = b"TZif" + b"2" + (b"\x00" * 15) + (b"\x00" * 24)
# Data is empty (counts are 0)
# Second Header (V2 itself)
# Magic(4) + Version(1) + Reserved(15) + Counts(24)
header2 = b"TZif" + b"2" + (b"\x00" * 15) + (b"\x00" * 24)
# Newline check
data = header + header2 + b"\n"
print("This script will HANG INDEFINITELY if the vulnerability is present.")
print("Press Ctrl+C to stop.")
try:
bio = io.BytesIO(data)
print("Starting ZoneInfo.from_file...")
try:
zoneinfo.ZoneInfo.from_file(bio)
except ValueError as e:
if "unexpected end of file" in str(e):
print("SUCCESS: Code raised ValueError as expected (Fix verified)")
return
else:
raise e
print("Finished without expected ValueError?")
except KeyboardInterrupt:
print("\nInterrupted by user.")
except Exception as e:
print(f"Caught exception: {e}")
if __name__ == "__main__":
reproduce()Patch
I think this will solve the problem. I'll send a PR.
line = fobj.readline()
if not line.endswith(b"\n"):
raise ValueError("Invalid TZif file: unexpected end of file")
tz_bytes = line.rstrip(b"\n")
CPython versions tested on:
3.15
Operating systems tested on:
Linux