diff --git a/Lib/tarfile.py b/Lib/tarfile.py index c603ba019ab481..694c24a08dffaa 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -1323,6 +1323,13 @@ def frombuf(cls, buf, encoding, errors): numbytes = nti(buf[pos + 12:pos + 24]) except ValueError: break + + if offset < 0: + raise InvalidHeaderError("invalid sparse header: negative offset") + + if numbytes < 0: + raise InvalidHeaderError("invalid sparse header: negative numbytes") + structs.append((offset, numbytes)) pos += 24 isextended = bool(buf[482]) @@ -1438,6 +1445,13 @@ def _proc_sparse(self, tarfile): numbytes = nti(buf[pos + 12:pos + 24]) except ValueError: break + + if offset < 0: + raise InvalidHeaderError("invalid sparse header: negative offset") + + if numbytes < 0: + raise InvalidHeaderError("invalid sparse header: negative numbytes") + if offset and numbytes: structs.append((offset, numbytes)) pos += 24 diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 860413b88eb6b5..545b6518ad8f47 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -4754,6 +4754,100 @@ class OffsetValidationTests(unittest.TestCase): # padding: 255 bytes + tarfile.NUL * 255 ) + invalid_gnu_sparse_header = ( + # name: 100 bytes + tarfile.NUL * tarfile.LENGTH_NAME + # mode, null terminator: 8 bytes + + b"0000755" + tarfile.NUL + # uid: 8 bytes + + b"0000000" + tarfile.NUL + # gid: 8 bytes + + b"0000000" + tarfile.NUL + # size, space: 12 bytes + + b"00000000010" + SPACE + # mtime, space: 12 bytes + + b"00000000000" + SPACE + # chksum: 8 bytes + + b" 022014" + tarfile.NUL + # type: 1 byte (GNU sparse) + + tarfile.GNUTYPE_SPARSE + # linkname: 100 bytes + + tarfile.NUL * tarfile.LENGTH_LINK + # magic: GNU format (8 bytes) + + tarfile.GNU_MAGIC + # uname: 32 bytes + + tarfile.NUL * 32 + # gname: 32 bytes + + tarfile.NUL * 32 + # devmajor: 8 bytes + + tarfile.NUL * 8 + # devminor: 8 bytes + + tarfile.NUL * 8 + # prefix: 41 bytes + + tarfile.NUL * 41 + # sparse entry 1: offset=-1, numbytes=-1 + + b"\xff" * 12 + b"\xff" * 12 + # other 3 sparse entries zero-filled + + tarfile.NUL * (24 * 3) + # isextended: 1 byte (0 or 1) + + b"\x00" + # origsize: 12 bytes + + tarfile.NUL * 12 + # padding: 17 bytes + + tarfile.NUL * 17 + ) + invalid_gnu_sparse_extended = ( + # name: 100 bytes + tarfile.NUL * tarfile.LENGTH_NAME + # mode, null terminator: 8 bytes + + b"0000755" + tarfile.NUL + # uid: 8 bytes + + b"0000000" + tarfile.NUL + # gid: 8 bytes + + b"0000000" + tarfile.NUL + # size, space: 12 bytes + + b"00000000010" + SPACE + # mtime, space: 12 bytes + + b"00000000000" + SPACE + # chksum: 8 bytes + + b" 006447" + tarfile.NUL + # type: 1 byte (GNU sparse) + + tarfile.GNUTYPE_SPARSE + # linkname: 100 bytes + + tarfile.NUL * tarfile.LENGTH_LINK + # magic: GNU format (8 bytes) + + tarfile.GNU_MAGIC + # uname: 32 bytes + + tarfile.NUL * 32 + # gname: 32 bytes + + tarfile.NUL * 32 + # devmajor: 8 bytes + + tarfile.NUL * 8 + # devminor: 8 bytes + + tarfile.NUL * 8 + # prefix: 41 bytes + + tarfile.NUL * 41 + # sparse map entries: up to 4 * 24 bytes + # entry 1: offset=1, numbytes=1 + + b"\x80" + b"\x00" * 10 + b"\x01" + b"\x80" + b"\x00" * 10 + b"\x01" + # Remaining 3 entries zero-filled + + tarfile.NUL * (24 * 3) + # isextended: 1 byte (0 or 1) + + b"\x01" + # origsize: 12 bytes + + tarfile.NUL * 12 + # padding: 17 bytes + + tarfile.NUL * 17 + ) + ( + # sparse entry 1: 24 bytes + b"\xff" * 12 + b"\xff" * 12 + # Remaining 20 entries zero-filled: 480 bytes + + tarfile.NUL * (24 * 20) + # isextended: 1 byte (0 or 1) + + b"\x00" + # padding: 7 bytes + + tarfile.NUL * 7 + ) valid_gnu_header = tarfile.TarInfo("filename").tobuf(tarfile.GNU_FORMAT) data_block = b"\xff" * tarfile.BLOCKSIZE @@ -4779,6 +4873,8 @@ def test_invalid_offset_header_validations(self): ("posix", self.invalid_posix_header), ("gnu", self.invalid_gnu_header), ("v7", self.invalid_v7_header), + ("gnu_sparse", self.invalid_gnu_sparse_header), + ("gnu_sparse_extension", self.invalid_gnu_sparse_extended) ): with self.subTest(format=tar_format): self._write_buffer(invalid_header) diff --git a/Misc/NEWS.d/next/Library/2025-08-15-06-45-30.gh-issue-137396.h10zyu.rst b/Misc/NEWS.d/next/Library/2025-08-15-06-45-30.gh-issue-137396.h10zyu.rst new file mode 100644 index 00000000000000..d75aabedf7d1eb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-15-06-45-30.gh-issue-137396.h10zyu.rst @@ -0,0 +1,3 @@ +When creating :class:`tarfile.TarFile` or :class:`tarfile.TarInfo` from +bytes of a tarfile with an offset or numbytes field in a sparse entry that +is negative, it raises :class:`tarfile.HeaderError`.