fix: give extracted files their pack mode

This commit is contained in:
Abdessamad Derraz committed 2026-10-16 09:58:57 +02:00
1 parent 5cfaf97173
commit a0a9e04e2b
2 files changed
+30

No files matched your search

+6
View File
@@ -107,6 +107,9 @@ def safe_extract_zip(
"""
dest = os.path.realpath(dest_dir)
os.makedirs(dest, exist_ok=True)
# Reading the umask sets it; it is put back at once.
umask = os.umask(0)
os.umask(umask)
with zipfile.ZipFile(zip_path, "r") as zf:
members = zf.infolist()
if len(members) > max_members:
@@ -186,6 +189,9 @@ def safe_extract_zip(
f"ZIP member size mismatch for {name}: "
f"{actual_size} != {member.file_size}"
)
# The member's own mode, else what a plain open() gives; the
# temporary file is private and would keep 0600 otherwise.
os.chmod(tmp_path, ((mode & 0o777) or 0o666) & ~umask)
os.replace(tmp_path, target)
tmp_path = ""
finally:
+24
View File
@@ -986,6 +986,30 @@ class ArchiveSecurityRegressions(unittest.TestCase):
with self.assertRaisesRegex(ValueError, "members"):
safe_extract_zip(str(archive), str(root / "out"), max_members=1)
def test_member_modes_follow_the_archive_and_the_umask(self):
"""Every member came out 0600, the mode of its temporary file: the
pico8 and vox launchers the RetroDECK pack keeps 0755 lost their
execute bit once download.py extracted them."""
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory:
root = Path(directory)
archive = self._zip_path(root)
program = zipfile.ZipInfo("pico8")
program.create_system = 3
program.external_attr = (stat.S_IFREG | 0o755) << 16
plain = zipfile.ZipInfo("bios.bin")
plain.create_system = 0
plain.external_attr = 0x20 # MS-DOS archive bit, no Unix mode
with zipfile.ZipFile(archive, "w") as handle:
handle.writestr(program, b"\x7fELF")
handle.writestr(plain, b"data")
umask = os.umask(0o022)
try:
safe_extract_zip(str(archive), str(root / "out"))
finally:
os.umask(umask)
self.assertEqual((root / "out" / "pico8").stat().st_mode & 0o777, 0o755)
self.assertEqual((root / "out" / "bios.bin").stat().st_mode & 0o777, 0o644)
def test_symlink_member_is_rejected(self):
with tempfile.TemporaryDirectory(dir=TMP_ROOT) as directory:
root = Path(directory)