diff options
-rw-r--r-- | changelogs/fragments/unarchive_timestamp_t32.yaml | 3 | ||||
-rw-r--r-- | lib/ansible/modules/unarchive.py | 22 | ||||
-rw-r--r-- | test/units/modules/test_unarchive.py | 10 |
3 files changed, 34 insertions, 1 deletions
diff --git a/changelogs/fragments/unarchive_timestamp_t32.yaml b/changelogs/fragments/unarchive_timestamp_t32.yaml new file mode 100644 index 0000000000..969c85de05 --- /dev/null +++ b/changelogs/fragments/unarchive_timestamp_t32.yaml @@ -0,0 +1,3 @@ +--- +bugfixes: + - unarchive - Clamp timestamps from beyond y2038 to representible values when unpacking zip files on platforms that use 32-bit time_t (e.g. Debian i386). diff --git a/lib/ansible/modules/unarchive.py b/lib/ansible/modules/unarchive.py index 0b192ab569..b317dbc737 100644 --- a/lib/ansible/modules/unarchive.py +++ b/lib/ansible/modules/unarchive.py @@ -241,6 +241,7 @@ uid: import binascii import codecs +import ctypes import fnmatch import grp import os @@ -262,6 +263,13 @@ from ansible.module_utils.urls import fetch_file from shlex import quote from zipfile import BadZipFile +try: + from functools import cache +except ImportError: + # Python < 3.9 + from functools import lru_cache + cache = lru_cache(maxsize=None) + # String from tar that shows the tar contents are different from the # filesystem OWNER_DIFF_RE = re.compile(r': Uid differs$') @@ -279,6 +287,18 @@ CONTENT_DIFF_RE = re.compile(r': Contents differ$') SIZE_DIFF_RE = re.compile(r': Size differs$') +@cache +def _y2038_impacted(): + """Determine if the system has 64-bit time_t.""" + if hasattr(ctypes, "c_time_t"): # Python >= 3.12 + return ctypes.sizeof(ctypes.c_time_t) < 8 + try: + time.gmtime(2**31) + except OverflowError: + return True + return False + + def crc32(path, buffer_size): """ Return a CRC32 checksum of a file """ @@ -414,6 +434,8 @@ class ZipArchive(object): try: if int(match.groups()[0]) < 1980: date_time = epoch_date_time + elif int(match.groups()[0]) >= 2038 and _y2038_impacted(): + date_time = (2038, 1, 1, 0, 0, 0, 0, 0, 0) elif int(match.groups()[0]) > 2107: date_time = (2107, 12, 31, 23, 59, 59, 0, 0, 0) else: diff --git a/test/units/modules/test_unarchive.py b/test/units/modules/test_unarchive.py index 6a2f0d9a67..b1885c2f1c 100644 --- a/test/units/modules/test_unarchive.py +++ b/test/units/modules/test_unarchive.py @@ -14,6 +14,14 @@ def fake_ansible_module(): return FakeAnsibleModule() +def max_zip_timestamp(): + """Return the max clamp value that will be selected.""" + try: + return time.mktime(time.struct_time((2107, 12, 31, 23, 59, 59, 0, 0, 0))) + except OverflowError: + return time.mktime(time.struct_time((2038, 1, 1, 0, 0, 0, 0, 0, 0))) + + class FakeAnsibleModule: def __init__(self): self.params = {} @@ -68,7 +76,7 @@ class TestCaseZipArchive: ), pytest.param( "21081231.000000", - time.mktime(time.struct_time((2107, 12, 31, 23, 59, 59, 0, 0, 0))), + max_zip_timestamp(), id="invalid-year-2108", ), pytest.param( |