fix: never follow a link at a partial download

This commit is contained in:
Abdessamad Derraz committed 2026-10-15 22:31:04 +02:00
1 parent a3941e843c
commit 12f362dfb9
4 files changed
+41 -4

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "17ccf172256b945fe369952d10d091de79168c3be50208841c27fdc74df9fad6"
$defaultInstallSha256 = "95e9cf9c274d3ccf5a305ee4a32e74c5c173177b7cf012069835a096e7ff2b35"
$maximumInstallerBytes = 2MB
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
$temporary = $null
+22 -2
View File
@@ -23,6 +23,7 @@ import os
import platform
import re
import shutil
import stat
import sys
import tempfile
import threading
@@ -1579,7 +1580,7 @@ def _download_one(
offset = 0
if resumable:
tmp_path = dest.parent / f".{dest.name}.part"
offset = tmp_path.stat().st_size if tmp_path.exists() else 0
offset = _partial_size(tmp_path)
if offset >= expected_size:
tmp_path.unlink()
offset = 0
@@ -1595,7 +1596,7 @@ def _download_one(
f"Content-Length {raw_length} != expected {expected_size - offset}"
)
if resumable:
out = open(tmp_path, "ab" if offset else "wb")
out = _open_partial(tmp_path, append=bool(offset))
else:
out = tempfile.NamedTemporaryFile(
mode="wb", dir=dest.parent, prefix=f".{dest.name}.",
@@ -1683,6 +1684,25 @@ def _download_one(
return f["dest"], False, reason
def _partial_size(path: Path) -> int:
"""Bytes already held by a partial copy; anything but a plain file is dropped."""
try:
info = path.lstat()
except FileNotFoundError:
return 0
if stat.S_ISREG(info.st_mode):
return info.st_size
path.unlink()
return 0
def _open_partial(path: Path, append: bool):
"""Open the partial copy without following a link planted at its name."""
flags = os.O_WRONLY | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_BINARY", 0)
flags |= os.O_APPEND if append else os.O_TRUNC
return os.fdopen(os.open(path, flags, 0o644), "ab" if append else "wb")
class _Corrupt(Exception):
"""The bytes arrived but are not the file the manifest names."""
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="17ccf172256b945fe369952d10d091de79168c3be50208841c27fdc74df9fad6"
DEFAULT_INSTALL_SHA256="95e9cf9c274d3ccf5a305ee4a32e74c5c173177b7cf012069835a096e7ff2b35"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+17
View File
@@ -102,6 +102,23 @@ class LargeFilesResume(unittest.TestCase):
self.assertTrue(ok)
self.assertEqual(_RangeHandler.ranges, [""])
@unittest.skipUnless(hasattr(os, "symlink"), "needs symlinks")
def test_a_link_at_the_partial_name_is_never_written_through(self):
with _Server(), tempfile.TemporaryDirectory() as tmp, \
mock.patch.object(install, "RESUME_FROM_SIZE", 1024):
victim = Path(tmp) / "victim.txt"
victim.write_bytes(b"keep me")
part = Path(tmp) / "bios" / ".big.bin.part"
part.parent.mkdir()
part.symlink_to(victim)
dest, ok, _ = install._download_one(dict(ENTRY), Path(tmp) / "bios")
self.assertTrue(ok)
self.assertEqual(victim.read_bytes(), b"keep me")
final = Path(tmp) / "bios" / "big.bin"
self.assertFalse(final.is_symlink())
self.assertEqual(final.read_bytes(), PAYLOAD)
self.assertEqual(_RangeHandler.ranges, [""])
class RetriesWait(unittest.TestCase):
def test_a_server_error_is_retried_after_the_delay_it_asks(self):