117 lines
4.0 KiB
Diff
117 lines
4.0 KiB
Diff
|
From c158b3f85126dabb8107260839dbcdb80e356c70 Mon Sep 17 00:00:00 2001
|
||
|
From: Stefano Rivera <github@rivera.za.net>
|
||
|
Date: Thu, 5 Dec 2024 14:26:58 -0800
|
||
|
Subject: [PATCH] unarchive: Clamp zip timestamps on 32-bit time_t (#84409)
|
||
|
|
||
|
Clamp zip timestamps to representible values when unpacking zip files on
|
||
|
platforms that use 32-bit time_t (e.g. Debian i386). This is a
|
||
|
non-issue in practice (in 2024), but should allow the test suite to pass
|
||
|
on Debian i386.
|
||
|
|
||
|
We use a round value of 2038-01-01 00:00:00 for simplicity, and to avoid
|
||
|
running into timezone offsets closer to the actual limit.
|
||
|
|
||
|
MR #81520 introduced sanity-checking tests that used dates not
|
||
|
representable with a 32-bit time_t.
|
||
|
---
|
||
|
.../fragments/unarchive_timestamp_t32.yaml | 3 +++
|
||
|
lib/ansible/modules/unarchive.py | 22 +++++++++++++++++++
|
||
|
test/units/modules/test_unarchive.py | 10 ++++++++-
|
||
|
3 files changed, 34 insertions(+), 1 deletion(-)
|
||
|
create mode 100644 changelogs/fragments/unarchive_timestamp_t32.yaml
|
||
|
|
||
|
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 0d56da53a4..3663cebcb8 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(
|
||
|
--
|
||
|
2.48.1
|
||
|
|