Skip to content

Infinite loop when reading incomplete ZoneInfo file in zoneinfo._common.load_data #143241

@fatihhcelik

Description

@fatihhcelik

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 += c

If 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

  1. Input: A constructed ZoneInfo file with Version 2 header but truncated before the trailing newline of the timezone string.
  2. 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

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions