Skip to content

Commit 0615b5c

Browse files
authored
gh-160420: Reject drive letters in ZoneInfo key components on Windows (GH-160421)
A key such as "America/C:\file" passed validation, and the tzdata loader then joined "C:\file" to the package directory, which replaced it. ANT-2026-IZMAOLCQ
1 parent 74c91a6 commit 0615b5c

3 files changed

Lines changed: 50 additions & 0 deletions

File tree

‎Lib/test/test_zoneinfo/test_zoneinfo.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
import importlib.metadata
77
import io
88
import json
9+
import ntpath
910
import os
1011
import pathlib
1112
import pickle
1213
import re
1314
import shutil
1415
import struct
1516
import tempfile
17+
import types
1618
import unittest
1719
from datetime import date, datetime, time, timedelta, timezone
1820
from functools import cached_property
21+
from unittest import mock
1922

2023
from test.support import MISSING_C_DOCSTRINGS
2124
from test.support.os_helper import EnvironmentVarGuard, FakePath
@@ -2016,6 +2019,38 @@ def test_tzpath_type_error(self):
20162019
with self.assertRaises(TypeError):
20172020
self.module.reset_tzpath(bad_value)
20182021

2022+
def test_validate_key_windows_paths(self):
2023+
# Check the key validation with Windows path rules on every platform.
2024+
tzpath = self.module._tzpath
2025+
base = ntpath.normpath(ntpath.join("_", "_"))[:-1]
2026+
fake_os = types.SimpleNamespace(path=ntpath)
2027+
2028+
good_keys = [
2029+
"UTC",
2030+
"America/New_York",
2031+
"America/Argentina/Buenos_Aires",
2032+
"Etc/GMT+1",
2033+
]
2034+
bad_keys = [
2035+
"C:\\Windows\\win.ini",
2036+
"America/C:\\Windows\\win.ini", # Drive in a component
2037+
"America/C:/Windows/win.ini",
2038+
"America/D:secret", # Drive-relative component
2039+
"America/Argentina/D:secret",
2040+
"America/\\Windows\\win.ini", # Rooted component
2041+
"America/\\\\server\\share\\file", # UNC component
2042+
]
2043+
2044+
with mock.patch.object(tzpath, "os", fake_os):
2045+
for key in good_keys:
2046+
with self.subTest(key=key):
2047+
tzpath._validate_tzfile_path(key, _base=base)
2048+
2049+
for key in bad_keys:
2050+
with self.subTest(key=key):
2051+
with self.assertRaises(ValueError):
2052+
tzpath._validate_tzfile_path(key, _base=base)
2053+
20192054
def test_tzpath_attribute(self):
20202055
tzpath_0 = [f"{DRIVE}/one", f"{DRIVE}/two"]
20212056
tzpath_1 = [f"{DRIVE}/three"]

‎Lib/zoneinfo/_tzpath.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ def _validate_tzfile_path(path, _base=_TEST_PATH):
111111
f"ZoneInfo keys must refer to subdirectories of TZPATH, got: {path}"
112112
)
113113

114+
# load_tzdata() joins the last "/"-separated component of the key to the
115+
# tzdata package directory on its own. On Windows a component with a
116+
# drive or root, such as "C:\\file" in "America/C:\\file", would replace
117+
# that directory instead of being looked up inside it.
118+
for component in path.split("/"):
119+
drive, root, _ = os.path.splitroot(component)
120+
if drive or root:
121+
raise ValueError(
122+
f"ZoneInfo keys must not contain drives or roots, got: {path}"
123+
)
124+
114125

115126
del _TEST_PATH
116127

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :class:`zoneinfo.ZoneInfo` on Windows accepting keys in which a component
2+
after a ``/`` starts with a drive letter, such as ``America/C:\file``. Such a
3+
key could open a file outside the ``tzdata`` package; it now raises
4+
:exc:`ValueError`.

0 commit comments

Comments
 (0)