Skip to content

Commit ff81651

Browse files
vstinnermiss-islingtonharjothkhara
authored
[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 c6c0608) (cherry picked from commit d4b8d66) Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Co-authored-by: Harjoth Khara <harjoth.khara@gmail.com>
1 parent 8356e9c commit ff81651

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

‎Lib/tarfile.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,12 @@ def _init_write_gz(self, compresslevel):
457457
-self.zlib.MAX_WBITS,
458458
self.zlib.DEF_MEM_LEVEL,
459459
0)
460-
timestamp = struct.pack("<L", int(time.time()))
460+
mtime = time.time()
461+
# gh-133998: substitute 0 for an out-of-range mtime and coerce to int,
462+
# mirroring gzip (RFC 1952), so struct.pack cannot raise struct.error.
463+
if not 0 <= mtime < 2**32:
464+
mtime = 0
465+
timestamp = struct.pack("<L", int(mtime))
461466
self.__write(b"\037\213\010\010" + timestamp + b"\002\377")
462467
if self.name.endswith(".gz"):
463468
self.name = self.name[:-3]

‎Lib/test/test_tarfile.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import re
1111
import warnings
1212
import stat
13+
import time
1314

1415
import unittest
1516
import unittest.mock
@@ -1960,6 +1961,25 @@ def test_source_directory_not_leaked(self):
19601961
payload = pathlib.Path(tmpname).read_text(encoding='latin-1')
19611962
assert os.path.dirname(tmpname) not in payload
19621963

1964+
def test_create_with_out_of_range_clock(self):
1965+
# gh-133998: an out-of-range system clock (mtime defaulting to
1966+
# time.time()) is stored as 0 rather than raising struct.error.
1967+
for clock in (-1, 2**32):
1968+
with self.subTest(clock=clock):
1969+
with unittest.mock.patch('time.time', return_value=float(clock)):
1970+
tarfile.open(tmpname, self.mode).close()
1971+
with self.open(tmpname, 'r') as fobj:
1972+
fobj.read()
1973+
self.assertEqual(fobj.mtime, 0)
1974+
os_helper.unlink(tmpname)
1975+
1976+
def test_create_without_mtime(self):
1977+
before = int(time.time())
1978+
tarfile.open(tmpname, self.mode).close()
1979+
after = int(time.time())
1980+
with self.open(tmpname, 'r') as fobj:
1981+
fobj.read()
1982+
self.assertTrue(before <= fobj.mtime <= after)
19631983

19641984
class Bz2StreamWriteTest(Bz2Test, StreamWriteTest):
19651985
decompressor = bz2.BZ2Decompressor if bz2 else None
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :func:`tarfile.open` in streaming write mode (``w|gz``) raising
2+
:exc:`struct.error` when given an *mtime* outside the range of the gzip
3+
header's 32-bit timestamp field. Out-of-range values are now stored as ``0``,
4+
matching :mod:`gzip`.

0 commit comments

Comments
 (0)