https://github.com/python/cpython/commit/ff81651c0864373a5dd6e0dcc7385e8656a1eefc
commit: ff81651c0864373a5dd6e0dcc7385e8656a1eefc
branch: 3.14
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-16T22:50:07Z
summary:

[3.14] gh-133998: Fix tarfile error on out-of-range mtime in streaming gzip 
mode (#159589)

[3.15] gh-133998: Fix tarfile error on out-of-range mtime in streaming gzip 
mode (GH-151828) (#159158)

gh-133998: Fix tarfile error on out-of-range mtime in streaming gzip mode 
(GH-151828)

tarfile.open(..., "w|gz", mtime=...) packed mtime into the gzip header's
32-bit field with no range check, so a value < 0 or >= 2**32 raised
struct.error. Mirror the merged gzip fix: substitute 0 for out-of-range
values and coerce to int (RFC 1952), so floats and out-of-range system
clocks behave the same as in gzip and the non-streaming "w:gz" path
(which already delegates to GzipFile and was unaffected).
(cherry picked from commit c6c060843b7635b5a1f82e0983d803eb0d185b5d)



(cherry picked from commit d4b8d6668a84b8f93b0d2de13f142d3721a5dafb)

Co-authored-by: Miss Islington (bot) 
<[email protected]>
Co-authored-by: Harjoth Khara <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-06-20-20-24-03.gh-issue-133998.nkqhMz.rst
M Lib/tarfile.py
M Lib/test/test_tarfile.py

diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index e4a6d7b3a0f00b..81573ebc5e149a 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -457,7 +457,12 @@ def _init_write_gz(self, compresslevel):
                                          -self.zlib.MAX_WBITS,
                                          self.zlib.DEF_MEM_LEVEL,
                                          0)
-        timestamp = struct.pack("<L", int(time.time()))
+        mtime = time.time()
+        # gh-133998: substitute 0 for an out-of-range mtime and coerce to int,
+        # mirroring gzip (RFC 1952), so struct.pack cannot raise struct.error.
+        if not 0 <= mtime < 2**32:
+            mtime = 0
+        timestamp = struct.pack("<L", int(mtime))
         self.__write(b"\037\213\010\010" + timestamp + b"\002\377")
         if self.name.endswith(".gz"):
             self.name = self.name[:-3]
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 0e278c69f5898b..713e9d35818fe0 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -10,6 +10,7 @@
 import re
 import warnings
 import stat
+import time
 
 import unittest
 import unittest.mock
@@ -1960,6 +1961,25 @@ def test_source_directory_not_leaked(self):
         payload = pathlib.Path(tmpname).read_text(encoding='latin-1')
         assert os.path.dirname(tmpname) not in payload
 
+    def test_create_with_out_of_range_clock(self):
+        # gh-133998: an out-of-range system clock (mtime defaulting to
+        # time.time()) is stored as 0 rather than raising struct.error.
+        for clock in (-1, 2**32):
+            with self.subTest(clock=clock):
+                with unittest.mock.patch('time.time', 
return_value=float(clock)):
+                    tarfile.open(tmpname, self.mode).close()
+                with self.open(tmpname, 'r') as fobj:
+                    fobj.read()
+                    self.assertEqual(fobj.mtime, 0)
+                os_helper.unlink(tmpname)
+
+    def test_create_without_mtime(self):
+        before = int(time.time())
+        tarfile.open(tmpname, self.mode).close()
+        after = int(time.time())
+        with self.open(tmpname, 'r') as fobj:
+            fobj.read()
+            self.assertTrue(before <= fobj.mtime <= after)
 
 class Bz2StreamWriteTest(Bz2Test, StreamWriteTest):
     decompressor = bz2.BZ2Decompressor if bz2 else None
diff --git 
a/Misc/NEWS.d/next/Library/2026-06-20-20-24-03.gh-issue-133998.nkqhMz.rst 
b/Misc/NEWS.d/next/Library/2026-06-20-20-24-03.gh-issue-133998.nkqhMz.rst
new file mode 100644
index 00000000000000..e7c03c662ec44f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-20-20-24-03.gh-issue-133998.nkqhMz.rst
@@ -0,0 +1,4 @@
+Fix :func:`tarfile.open` in streaming write mode (``w|gz``) raising
+:exc:`struct.error` when given an *mtime* outside the range of the gzip
+header's 32-bit timestamp field.  Out-of-range values are now stored as ``0``,
+matching :mod:`gzip`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to