ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
bootstrap_uv.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Install the repository-pinned uv binary from a verified release asset."""
5
6from __future__ import annotations
7
8import argparse
9import hashlib
10import io
11import json
12import os
13import platform
14import re
15import runpy
16import signal
17import stat
18import struct
19import sys
20import sysconfig
21import tarfile
22import tempfile
23import urllib.request
24import zipfile
25from collections.abc import Callable, Iterator
26from contextlib import contextmanager
27from pathlib import Path, PurePosixPath
28from typing import NoReturn, Protocol, Self
29
30sys.path.insert(0, str(Path(__file__).resolve().parent))
31import bootstrap_uv_exec
32
33ROOT = Path(__file__).resolve().parents[2]
34DEFAULT_MANIFEST = Path(__file__).with_name("uv_release.json")
35DEFAULT_CACHE = ROOT / ".tools" / "uv"
36MAX_ASSET_BYTES = 128 * 1024 * 1024
37MAX_UV_BINARY_BYTES = 96 * 1024 * 1024
38SHA256_HEX_LENGTH = hashlib.sha256().digest_size * 2
39ZIP_UNIX_CREATE_SYSTEM = 3
40PRIVATE_ARCHIVE_MODE = stat.S_IRUSR | stat.S_IWUSR
41PRIVATE_EXECUTABLE_MODE = PRIVATE_ARCHIVE_MODE | stat.S_IXUSR
42PUBLIC_ARCHIVE_MODE = PRIVATE_ARCHIVE_MODE | stat.S_IRGRP | stat.S_IROTH
43PUBLIC_EXECUTABLE_MODE = PUBLIC_ARCHIVE_MODE | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
44EXPECTED_PUBLIC_MODES = (0o644, 0o755)
45
46
47class BootstrapError(RuntimeError):
48 """Represent a fail-closed uv bootstrap error."""
49
50
51class CacheApplyRequiredError(BootstrapError):
52 """Report authenticated cache drift that a supported apply can repair."""
53
54
55class CacheMetadataChangedError(BootstrapError):
56 """Report that authenticated descriptor metadata changed before use completed."""
57
58
59class CachePathBindingError(BootstrapError):
60 """Report that a cache pathname no longer names its authenticated inode."""
61
62
63class DownloadHeaders(Protocol):
64 """Describe the response header operation used by the downloader."""
65
66 def get(self, name: str, default: str | None = None) -> str | None:
67 """Return one response header."""
68
69
70class DownloadResponse(Protocol):
71 """Describe the bounded subset of an HTTP response used here."""
72
73 headers: DownloadHeaders
74
75 def read(self, size: int = -1) -> bytes:
76 """Read at most size response bytes."""
77
78 def __enter__(self) -> Self:
79 """Enter the response context."""
80
81 def __exit__(self, *_args: object) -> None:
82 """Leave the response context."""
83
84
85DownloadOpener = Callable[..., DownloadResponse]
86
87
88def fail(message: str) -> NoReturn:
89 """Stop bootstrap processing with one user-facing policy error."""
90 raise BootstrapError(message)
91
92
93def load_manifest(path: Path) -> dict[str, object]:
94 """Load and validate the single uv release manifest."""
95 try:
96 document = json.loads(path.read_text(encoding="ascii"))
97 except (OSError, UnicodeError, json.JSONDecodeError) as exc:
98 fail(f"cannot read uv manifest {path}: {exc}")
99 if not isinstance(document, dict):
100 fail("uv manifest root must be an object")
101 if document.get("schema") != 1:
102 fail("uv manifest schema must be 1")
103 if document.get("repository") != "astral-sh/uv":
104 fail("uv manifest repository must be astral-sh/uv")
105 version = document.get("version")
106 if not isinstance(version, str) or re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version) is None:
107 fail(f"uv manifest has an invalid release version: {version!r}")
108 assets = document.get("assets")
109 if not isinstance(assets, dict) or not assets:
110 fail("uv manifest has no asset table")
111 return document
112
113
114def normalized_machine(machine: str) -> str:
115 """Map operating-system architecture spellings to release triples."""
116 value = machine.lower()
117 if value in {"amd64", "x64", "x86_64"}:
118 return "x86_64"
119 if value in {"aarch64", "arm64"}:
120 return "aarch64"
121 fail(f"unsupported architecture: {machine}")
122
123
124def has_musl_loader(directories: tuple[Path, ...]) -> bool:
125 """Return whether a known musl loader name resolves to a regular file."""
126 return any(
127 path.is_file() for directory in directories for path in directory.glob("ld-musl-*.so.1")
128 )
129
130
131def detect_linux_libc() -> str:
132 """Distinguish the two supported Linux release ABIs without executing host tools."""
133 libc_name = platform.libc_ver()[0].lower()
134 if "musl" in libc_name:
135 return "musl"
136 if libc_name in {"glibc", "gnu libc"}:
137 return "gnu"
138 abi_values = " ".join(
139 str(sysconfig.get_config_var(name) or "").lower() for name in ("MULTIARCH", "HOST_GNU_TYPE")
140 )
141 if "musl" in abi_values:
142 return "musl"
143 if "linux-gnu" in abi_values:
144 return "gnu"
145 if has_musl_loader((Path("/lib"), Path("/usr/lib"))):
146 return "musl"
147 fail("unsupported or unidentified Linux libc")
148
149
150def asset_key(system: str, machine: str, libc_name: str | None = None) -> str:
151 """Return the manifest key for a supported host."""
152 architecture = normalized_machine(machine)
153 if system == "Linux":
154 selected_libc = libc_name or detect_linux_libc()
155 if selected_libc not in {"gnu", "musl"}:
156 fail(f"unsupported Linux libc: {selected_libc}")
157 return f"Linux|{architecture}|{selected_libc}"
158 if system in {"Darwin", "Windows"}:
159 return f"{system}|{architecture}"
160 fail(f"unsupported operating system: {system}")
161
162
163def expected_asset_name(key: str) -> str:
164 """Derive the only acceptable official asset name for a host key."""
165 system, architecture, *libc_value = key.split("|")
166 if system == "Darwin" and not libc_value:
167 return f"uv-{architecture}-apple-darwin.tar.gz"
168 if system == "Windows" and not libc_value:
169 return f"uv-{architecture}-pc-windows-msvc.zip"
170 if system == "Linux" and len(libc_value) == 1 and libc_value[0] in {"gnu", "musl"}:
171 return f"uv-{architecture}-unknown-linux-{libc_value[0]}.tar.gz"
172 fail(f"invalid uv platform key: {key}")
173
174
175def select_asset(
176 manifest: dict[str, object],
177 system: str | None = None,
178 machine: str | None = None,
179 libc_name: str | None = None,
180) -> tuple[str, str, str]:
181 """Select one release URL, name, and digest from the manifest."""
182 key = asset_key(system or platform.system(), machine or platform.machine(), libc_name)
183 assets = manifest["assets"]
184 if not isinstance(assets, dict) or key not in assets:
185 fail(f"uv manifest has no asset for {key}")
186 record = assets[key]
187 if not isinstance(record, dict):
188 fail(f"uv asset record for {key} must be an object")
189 name = record.get("name")
190 digest = record.get("sha256")
191 if not isinstance(name, str) or not isinstance(digest, str):
192 fail(f"uv asset record for {key} is incomplete")
193 if len(digest) != SHA256_HEX_LENGTH or any(char not in "0123456789abcdef" for char in digest):
194 fail(f"uv asset record for {key} has an invalid SHA-256")
195 expected_name = expected_asset_name(key)
196 if name != expected_name:
197 fail(f"uv asset record for {key} must name {expected_name}, got {name}")
198 version = manifest["version"]
199 repository = manifest["repository"]
200 if not isinstance(version, str) or not isinstance(repository, str):
201 fail("uv manifest release identity is malformed")
202 release_url = f"https://github.com/{repository}/releases/download/{version}"
203 return f"{release_url}/{name}", name, digest
204
205
206def verify_payload(payload: bytes, expected: str) -> None:
207 """Reject release bytes whose SHA-256 does not match the manifest."""
208 actual = hashlib.sha256(payload).hexdigest()
209 if actual != expected:
210 fail(f"uv asset SHA-256 mismatch: expected {expected}, got {actual}")
211
212
213def require_bounded_executable(stream: object, declared_size: int) -> bytes:
214 """Read one executable member without allowing archive inflation."""
215 if declared_size < 1 or declared_size > MAX_UV_BINARY_BYTES:
216 fail(f"uv executable size is outside policy: {declared_size} bytes")
217 if not hasattr(stream, "read"):
218 fail("uv executable archive member is not readable")
219 payload = stream.read(MAX_UV_BINARY_BYTES + 1)
220 if not isinstance(payload, bytes) or len(payload) != declared_size:
221 fail("uv executable size does not match archive metadata")
222 return payload
223
224
225def executable_bytes(payload: bytes, asset_name: str) -> bytes:
226 """Read the one exact uv member from a verified release archive."""
227 archive_stem = asset_name.removesuffix(".tar.gz").removesuffix(".zip")
228 executable = "uv.exe" if asset_name.endswith(".zip") else "uv"
229 expected_member = f"{archive_stem}/{executable}"
230 if asset_name.endswith(".zip"):
231 try:
232 with zipfile.ZipFile(io.BytesIO(payload)) as archive:
233 names = archive.namelist()
234 executable_members = [
235 name for name in names if PurePosixPath(name).name == executable
236 ]
237 if names.count(expected_member) != 1 or executable_members != [expected_member]:
238 fail(f"uv archive lacks exact member {expected_member}")
239 info = archive.getinfo(expected_member)
240 if info.is_dir():
241 fail(f"uv archive member is not a file: {expected_member}")
242 unix_type = stat.S_IFMT(info.external_attr >> 16)
243 if info.create_system == ZIP_UNIX_CREATE_SYSTEM and unix_type not in {
244 0,
245 stat.S_IFREG,
246 }:
247 fail(f"uv archive member is not a regular file: {expected_member}")
248 with archive.open(info) as source:
249 return require_bounded_executable(source, info.file_size)
250 except zipfile.BadZipFile as exc:
251 fail(f"invalid uv release archive: {exc}")
252 try:
253 with tarfile.open(fileobj=io.BytesIO(payload), mode="r:gz") as archive:
254 members = archive.getmembers()
255 executable_members = [
256 member for member in members if PurePosixPath(member.name).name == executable
257 ]
258 matches = [member for member in members if member.name == expected_member]
259 if len(matches) != 1 or executable_members != matches or not matches[0].isfile():
260 fail(f"uv archive lacks exact regular member {expected_member}")
261 source = archive.extractfile(matches[0])
262 if source is None:
263 fail("uv executable could not be read from archive")
264 with source:
265 return require_bounded_executable(source, matches[0].size)
266 except tarfile.TarError as exc:
267 fail(f"invalid uv release archive: {exc}")
268
269
270def download_payload(url: str, opener: DownloadOpener = urllib.request.urlopen) -> bytes:
271 """Download one bounded release asset and report transport failures."""
272 if not url.startswith("https://github.com/astral-sh/uv/releases/download/"):
273 fail(f"refusing non-official uv release URL: {url}")
274 request = urllib.request.Request( # noqa: S310 -- URL is constrained above.
275 url, headers={"User-Agent": "ra8-firmware-uv-bootstrap"}
276 )
277 try:
278 with opener(request, timeout=60) as response:
279 length = response.headers.get("Content-Length")
280 if length is not None and int(length) > MAX_ASSET_BYTES:
281 fail(f"uv asset exceeds {MAX_ASSET_BYTES} bytes")
282 payload = response.read(MAX_ASSET_BYTES + 1)
283 except BootstrapError:
284 raise
285 except (OSError, ValueError) as exc:
286 fail(f"cannot download pinned uv asset {url}: {exc}")
287 if len(payload) > MAX_ASSET_BYTES:
288 fail(f"uv asset exceeds {MAX_ASSET_BYTES} bytes")
289 return payload
290
291
292def reject_symlink_components(
293 cache_root: Path, destination: Path, anchor: Path | None = None
294) -> None:
295 """Reject symlinks from a trusted anchor through the cache destination."""
296 cache_root = cache_root.absolute()
297 destination = destination.absolute()
298 if anchor is None:
299 try:
300 cache_root.relative_to(ROOT)
301 except ValueError:
302 anchor = cache_root.parent
303 else:
304 anchor = ROOT
305 anchor = anchor.absolute()
306 try:
307 relative = destination.relative_to(cache_root)
308 except ValueError:
309 fail(f"uv destination escapes cache root: {destination}")
310 try:
311 cache_relative = cache_root.relative_to(anchor)
312 except ValueError:
313 fail(f"uv cache root escapes trusted anchor: {cache_root}")
314 current = anchor
315 if current.is_symlink():
316 fail(f"uv cache anchor is a symlink: {current}")
317 for component in (*cache_relative.parts, *relative.parts):
318 current /= component
319 if current.is_symlink():
320 fail(f"symlink in uv cache path: {current}")
321
322
323def cache_destination(cache_root: Path, version: str, asset_name: str) -> Path:
324 """Return the platform-specific executable cache path."""
325 archive_stem = asset_name.removesuffix(".tar.gz").removesuffix(".zip")
326 executable = "uv.exe" if asset_name.endswith(".zip") else "uv"
327 return cache_root / version / archive_stem / executable
328
329
330def validated_cached_payload(archive_path: Path, digest: str) -> bytes:
331 """Read and authenticate a retained release archive on every reuse."""
332 if os.name == "posix":
333 descriptor = open_cache_fd(archive_path)
334 try:
335 payload, _ = read_stable_fd(descriptor, archive_path, MAX_ASSET_BYTES)
336 finally:
337 os.close(descriptor)
338 verify_payload(payload, digest)
339 return payload
340 if archive_path.is_symlink() or not archive_path.is_file():
341 fail(f"verified uv archive is missing: {archive_path}")
342 try:
343 payload = archive_path.read_bytes()
344 except OSError as exc:
345 fail(f"cannot read cached uv archive {archive_path}: {exc}")
346 verify_payload(payload, digest)
347 return payload
348
349
350def validated_cached_binary(
351 archive_path: Path, destination: Path, asset_name: str, digest: str
352) -> bytes:
353 """Authenticate the retained archive and compare its executable byte-for-byte."""
354 if os.name == "posix":
355 with authenticated_cache_fds(
356 archive_path, destination, asset_name, digest
357 ) as authenticated:
358 return authenticated[2]
359 payload = validated_cached_payload(archive_path, digest)
360 binary = executable_bytes(payload, asset_name)
361 if destination.is_symlink() or not destination.is_file():
362 fail(f"cached uv executable is missing: {destination}")
363 try:
364 installed = destination.read_bytes()
365 except OSError as exc:
366 fail(f"cannot read cached uv executable {destination}: {exc}")
367 if not binary or installed != binary:
368 fail(f"cached uv executable differs from verified archive: {destination}")
369 return binary
370
371
372def write_atomic(path: Path, payload: bytes, executable: bool = False) -> None:
373 """Write one cache artifact through a held no-follow parent."""
374 if os.name != "posix":
375 fail("authenticated uv cache writes require POSIX; use WSL on Windows")
376 mode = PRIVATE_EXECUTABLE_MODE if executable else PRIVATE_ARCHIVE_MODE
377 try:
378 bootstrap_uv_exec.write_atomic_nofollow(path, payload, mode)
379 except bootstrap_uv_exec.UvExecError as exc:
380 fail(str(exc))
381
382
383def open_cache_fd(path: Path) -> int:
384 """Open one POSIX cache artifact through a no-follow parent walk."""
385 try:
386 return bootstrap_uv_exec.open_regular_nofollow(path)
387 except bootstrap_uv_exec.UvExecError as exc:
388 fail(str(exc))
389
390
391def read_stable_fd(descriptor: int, path: Path, maximum: int) -> tuple[bytes, os.stat_result]:
392 """Read one bounded regular FD and reject concurrent inode mutation."""
393 before = os.fstat(descriptor)
394 try:
395 with os.fdopen(os.dup(descriptor), "rb") as source:
396 payload = source.read(maximum + 1)
397 after = os.fstat(descriptor)
398 except OSError as exc:
399 fail(f"cannot read cached uv artifact {path}: {exc}")
400 stable = ("st_dev", "st_ino", "st_size", "st_mtime_ns", "st_ctime_ns", "st_nlink")
401 if any(getattr(before, field) != getattr(after, field) for field in stable):
402 fail(f"cached uv artifact changed while authenticating: {path}")
403 if len(payload) > maximum:
404 fail(f"cached uv artifact exceeds policy: {path}")
405 return payload, after
406
407
408@contextmanager
409def authenticated_cache_fds(
410 archive_path: Path, destination: Path, asset_name: str, digest: str
411) -> Iterator[tuple[int, int, bytes, os.stat_result, os.stat_result]]:
412 """Hold exact authenticated archive and executable descriptors."""
413 archive_fd = open_cache_fd(archive_path)
414 destination_fd = -1
415 try:
416 destination_fd = open_cache_fd(destination)
417 payload, archive_state = read_stable_fd(archive_fd, archive_path, MAX_ASSET_BYTES)
418 verify_payload(payload, digest)
419 binary = executable_bytes(payload, asset_name)
420 installed, installed_state = read_stable_fd(
421 destination_fd, destination, MAX_UV_BINARY_BYTES
422 )
423 if not binary or installed != binary:
424 fail(f"cached uv executable differs from verified archive: {destination}")
425 yield archive_fd, destination_fd, binary, archive_state, installed_state
426 finally:
427 if destination_fd >= 0:
428 os.close(destination_fd)
429 os.close(archive_fd)
430
431
432def verify_fd_mode(path: Path, descriptor: int, mode: int) -> None:
433 """Require an exact authenticated descriptor to carry one shared mode."""
434 state = os.fstat(descriptor)
435 if stat.S_IMODE(state.st_mode) != mode:
436 message = f"cached uv permissions require an apply: {path}"
437 raise CacheApplyRequiredError(message)
438
439
440def probe_authenticated_uv(binary: bytes, version: str) -> None:
441 """Probe an immutable authenticated uv snapshot for its exact version."""
442 try:
443 completed = bootstrap_uv_exec.run_uv_snapshot(
444 binary, ["--version"], capture_output=True, timeout=10
445 )
446 except bootstrap_uv_exec.UvExecError as exc:
447 fail(str(exc))
448 if completed.returncode != 0 or completed.stdout.split()[:2] != ["uv", version]:
449 fail(f"installed uv failed its version probe: {completed.stderr.strip()}")
450
451
452def propagate_child_status(status: int) -> int:
453 """Return an exit code or terminate this wrapper through the child's signal."""
454 if status >= 0:
455 return status
456 signum = -status
457 unmask = getattr(signal, "pthread_sigmask", None)
458 if signum >= signal.NSIG or unmask is None:
459 fail("authenticated uv child returned an unsupported signal status")
460 uncatchable = (signal.SIGKILL, signal.SIGSTOP)
461 try:
462 if signum not in uncatchable:
463 signal.signal(signum, signal.SIG_DFL)
464 unmask(signal.SIG_UNBLOCK, {signum})
465 os.kill(os.getpid(), signum)
466 except (OSError, ValueError) as exc:
467 fail(f"cannot propagate authenticated uv child signal: {exc}")
468 fail("authenticated uv child signal did not terminate the wrapper")
469
470
471def verify_fd_unchanged(path: Path, descriptor: int, expected: os.stat_result) -> None:
472 """Require cache identity and content metadata to remain authenticated."""
473 current = os.fstat(descriptor)
474 stable = ("st_dev", "st_ino", "st_size", "st_mtime_ns", "st_ctime_ns", "st_nlink")
475 if any(getattr(expected, field) != getattr(current, field) for field in stable):
476 message = f"cached uv artifact changed after authenticating: {path}"
477 raise CacheMetadataChangedError(message)
478
479
480def verify_fd_path(path: Path, descriptor: int, mode: int) -> None:
481 """Require one chmod target to remain the exact regular path opened."""
482 descriptor_state = os.fstat(descriptor)
483 reopened = -1
484 try:
485 reopened = open_cache_fd(path)
486 path_state = os.fstat(reopened)
487 except (BootstrapError, OSError) as exc:
488 message = f"cached uv path moved during permission repair: {path}: {exc}"
489 raise CachePathBindingError(message) from exc
490 finally:
491 if reopened >= 0:
492 os.close(reopened)
493 same_file = (descriptor_state.st_dev, descriptor_state.st_ino) == (
494 path_state.st_dev,
495 path_state.st_ino,
496 )
497 if not stat.S_ISREG(path_state.st_mode) or path_state.st_nlink != 1 or not same_file:
498 message = f"cached uv path moved during permission repair: {path}"
499 raise CachePathBindingError(message)
500 if stat.S_IMODE(descriptor_state.st_mode) != mode:
501 fail(f"cached uv permissions did not converge: {path}")
502
503
504def normalize_cached_modes(
505 archive_path: Path, destination: Path, asset_name: str, digest: str
506) -> None:
507 """Authenticate exact POSIX FDs, then make those public release bytes shared."""
508 if os.name != "posix":
509 return
510 try:
511 with authenticated_cache_fds(archive_path, destination, asset_name, digest) as descriptors:
512 archive_fd, destination_fd, _, _, _ = descriptors
513 os.fchmod(archive_fd, PUBLIC_ARCHIVE_MODE)
514 os.fchmod(destination_fd, PUBLIC_EXECUTABLE_MODE)
515 verify_fd_path(archive_path, archive_fd, PUBLIC_ARCHIVE_MODE)
516 verify_fd_path(destination, destination_fd, PUBLIC_EXECUTABLE_MODE)
517 except OSError as exc:
518 fail(f"cannot normalize authenticated uv cache permissions: {exc}")
519
520
521def verify_cached_modes(archive_path: Path, destination: Path) -> None:
522 """Require exact shared POSIX modes without mutating either cache file."""
523 if os.name != "posix":
524 return
525 for path, mode in (
526 (archive_path, PUBLIC_ARCHIVE_MODE),
527 (destination, PUBLIC_EXECUTABLE_MODE),
528 ):
529 try:
530 state = os.lstat(path)
531 except OSError as exc:
532 fail(f"cannot inspect cached uv permissions {path}: {exc}")
533 if not stat.S_ISREG(state.st_mode) or state.st_nlink != 1:
534 fail(f"cached uv artifact is not one single-link regular file: {path}")
535 if stat.S_IMODE(state.st_mode) != mode:
536 message = f"cached uv permissions require an apply: {path}"
537 raise CacheApplyRequiredError(message)
538
539
540def verify_cached_uv(manifest_path: Path, cache_root: Path) -> Path:
541 """Authenticate and probe an existing cache without network access or writes."""
542 manifest = load_manifest(manifest_path)
543 _, asset_name, digest = select_asset(manifest)
544 version = manifest["version"]
545 if not isinstance(version, str):
546 fail("uv manifest version is not a string")
547 cache_root = cache_root.absolute()
548 destination = cache_destination(cache_root, version, asset_name)
549 archive_path = destination.parent / asset_name
550 reject_symlink_components(cache_root, archive_path)
551 reject_symlink_components(cache_root, destination)
552 if not archive_path.exists() and not destination.exists():
553 message = f"authenticated uv cache requires an apply: {destination}"
554 raise CacheApplyRequiredError(message)
555 if not archive_path.exists():
556 fail(f"cached uv has no authenticated archive: {destination}")
557 if not destination.exists():
558 validated_cached_payload(archive_path, digest)
559 message = f"authenticated uv cache requires an apply: {destination}"
560 raise CacheApplyRequiredError(message)
561 if os.name != "posix":
562 validated_cached_binary(archive_path, destination, asset_name, digest)
563 return destination
564 with authenticated_cache_fds(archive_path, destination, asset_name, digest) as descriptors:
565 archive_fd, destination_fd, binary, archive_state, installed_state = descriptors
566 verify_fd_mode(archive_path, archive_fd, PUBLIC_ARCHIVE_MODE)
567 verify_fd_mode(destination, destination_fd, PUBLIC_EXECUTABLE_MODE)
568 probe_authenticated_uv(binary, version)
569 verify_fd_unchanged(archive_path, archive_fd, archive_state)
570 verify_fd_unchanged(destination, destination_fd, installed_state)
571 verify_fd_path(archive_path, archive_fd, PUBLIC_ARCHIVE_MODE)
572 verify_fd_path(destination, destination_fd, PUBLIC_EXECUTABLE_MODE)
573 return destination
574
575
576def run_cached_uv(
577 manifest_path: Path,
578 cache_root: Path,
579 arguments: list[str],
580 *,
581 ensure: bool,
582) -> int:
583 """Run uv only from bytes authenticated and snapshotted by this process."""
584 if not arguments:
585 fail("authenticated uv execution requires at least one uv argument")
586 if os.name != "posix":
587 fail("authenticated uv execution requires POSIX; use WSL on Windows")
588 destination = (
589 ensure_uv(manifest_path, cache_root)
590 if ensure
591 else verify_cached_uv(manifest_path, cache_root)
592 )
593 manifest = load_manifest(manifest_path)
594 _, asset_name, digest = select_asset(manifest)
595 archive_path = destination.parent / asset_name
596 with authenticated_cache_fds(archive_path, destination, asset_name, digest) as descriptors:
597 archive_fd, destination_fd, binary, archive_state, installed_state = descriptors
598 verify_fd_mode(archive_path, archive_fd, PUBLIC_ARCHIVE_MODE)
599 verify_fd_mode(destination, destination_fd, PUBLIC_EXECUTABLE_MODE)
600 try:
601 completed = bootstrap_uv_exec.run_uv_snapshot(binary, arguments)
602 except bootstrap_uv_exec.UvExecError as exc:
603 fail(str(exc))
604 verify_fd_unchanged(archive_path, archive_fd, archive_state)
605 verify_fd_unchanged(destination, destination_fd, installed_state)
606 verify_fd_path(archive_path, archive_fd, PUBLIC_ARCHIVE_MODE)
607 verify_fd_path(destination, destination_fd, PUBLIC_EXECUTABLE_MODE)
608 return propagate_child_status(completed.returncode)
609
610
611def ensure_uv(manifest_path: Path, cache_root: Path) -> Path:
612 """Download, verify, and atomically install uv when it is not cached."""
613 manifest = load_manifest(manifest_path)
614 url, asset_name, digest = select_asset(manifest)
615 version = manifest["version"]
616 if not isinstance(version, str):
617 fail("uv manifest version is not a string")
618 cache_root = cache_root.absolute()
619 destination = cache_destination(cache_root, version, asset_name)
620 archive_path = destination.parent / asset_name
621 reject_symlink_components(cache_root, archive_path)
622 reject_symlink_components(cache_root, destination)
623
624 if not (archive_path.exists() and destination.exists()):
625 if destination.exists():
626 fail(f"cached uv has no authenticated archive: {destination}")
627 if archive_path.exists():
628 payload = validated_cached_payload(archive_path, digest)
629 else:
630 payload = download_payload(url)
631 verify_payload(payload, digest)
632 binary = executable_bytes(payload, asset_name)
633 if not binary:
634 fail("uv release archive contained an empty executable")
635 if not archive_path.exists():
636 write_atomic(archive_path, payload)
637 write_atomic(destination, binary, executable=True)
638 normalize_cached_modes(archive_path, destination, asset_name, digest)
639 return verify_cached_uv(manifest_path, cache_root)
640
641
642def synthetic_archive(
643 asset_name: str,
644 binary: bytes,
645 member_name: str | None = None,
646 member_kind: str = "file",
647) -> bytes:
648 """Create a small release-shaped archive for offline negative tests."""
649 stem = asset_name.removesuffix(".tar.gz").removesuffix(".zip")
650 executable = "uv.exe" if asset_name.endswith(".zip") else "uv"
651 name = member_name or f"{stem}/{executable}"
652 output = io.BytesIO()
653 if asset_name.endswith(".zip"):
654 with zipfile.ZipFile(output, mode="w") as archive:
655 if member_kind == "symlink":
656 info = zipfile.ZipInfo(name)
657 info.create_system = 3
658 info.external_attr = (stat.S_IFLNK | 0o777) << 16
659 archive.writestr(info, b"elsewhere")
660 else:
661 archive.writestr(name, binary)
662 if member_kind == "duplicate":
663 archive.writestr(f"other/{executable}", binary)
664 result = bytearray(output.getvalue())
665 if member_kind == "oversized":
666 local_header = result.index(b"PK\x03\x04")
667 central_header = result.index(b"PK\x01\x02")
668 struct.pack_into("<I", result, local_header + 22, MAX_UV_BINARY_BYTES + 1)
669 struct.pack_into("<I", result, central_header + 24, MAX_UV_BINARY_BYTES + 1)
670 return bytes(result)
671 with tarfile.open(fileobj=output, mode="w:gz") as archive:
672 info = tarfile.TarInfo(name)
673 if member_kind == "symlink":
674 info.type = tarfile.SYMTYPE
675 info.linkname = "elsewhere"
676 archive.addfile(info)
677 else:
678 info.size = len(binary)
679 archive.addfile(info, io.BytesIO(binary))
680 if member_kind == "duplicate":
681 duplicate = tarfile.TarInfo(f"other/{executable}")
682 duplicate.size = len(binary)
683 archive.addfile(duplicate, io.BytesIO(binary))
684 return output.getvalue()
685
686
687def expect_bootstrap_error(
688 action: Callable[[], object], label: str, message: str | None = None
689) -> None:
690 """Require one negative selftest action to fail closed."""
691 try:
692 action()
693 except BootstrapError as error:
694 if message is not None and message not in str(error):
695 fail(f"selftest: {label} returned unexpected error: {error}")
696 return
697 fail(f"selftest: {label} passed unexpectedly")
698
699
700def archive_selftest() -> None:
701 """Exercise exact-member and malformed-archive handling."""
702 for asset_name in (
703 "uv-x86_64-unknown-linux-gnu.tar.gz",
704 "uv-x86_64-pc-windows-msvc.zip",
705 ):
706 payload = synthetic_archive(asset_name, b"verified-uv")
707 if executable_bytes(payload, asset_name) != b"verified-uv":
708 fail(f"selftest: valid archive failed for {asset_name}")
709 expect_bootstrap_error(
710 lambda asset=asset_name: executable_bytes(
711 synthetic_archive(asset, b"verified-uv", "wrong/place/uv"), asset
712 ),
713 f"wrong member path for {asset_name}",
714 )
715 expect_bootstrap_error(
716 lambda asset=asset_name: executable_bytes(synthetic_archive(asset, b""), asset),
717 f"empty executable for {asset_name}",
718 )
719 expect_bootstrap_error(
720 lambda asset=asset_name: executable_bytes(
721 synthetic_archive(asset, b"verified-uv", member_kind="duplicate"), asset
722 ),
723 f"duplicate executable basename for {asset_name}",
724 )
725 expect_bootstrap_error(
726 lambda asset=asset_name: executable_bytes(b"not an archive", asset),
727 f"corrupt archive for {asset_name}",
728 )
729 for asset_name in (
730 "uv-x86_64-unknown-linux-gnu.tar.gz",
731 "uv-x86_64-pc-windows-msvc.zip",
732 ):
733 expect_bootstrap_error(
734 lambda asset=asset_name: executable_bytes(
735 synthetic_archive(asset, b"", member_kind="symlink"), asset
736 ),
737 f"symlink archive member for {asset_name}",
738 )
739 zip_name = "uv-x86_64-pc-windows-msvc.zip"
740 expect_bootstrap_error(
741 lambda: executable_bytes(
742 synthetic_archive(zip_name, b"uv", member_kind="oversized"), zip_name
743 ),
744 "oversized executable metadata",
745 "outside policy",
746 )
747
748
749def cache_selftest() -> None:
750 """Exercise archive reauthentication and cache path hardening."""
751 asset_name = "uv-x86_64-unknown-linux-gnu.tar.gz"
752 archive = synthetic_archive(asset_name, b"verified-uv")
753 digest = hashlib.sha256(archive).hexdigest()
754 with tempfile.TemporaryDirectory(prefix="ra8-uv-cache-test-") as raw:
755 root = Path(raw)
756 directory = root / "0.0.0" / asset_name.removesuffix(".tar.gz")
757 directory.mkdir(parents=True)
758 archive_path = directory / asset_name
759 destination = directory / "uv"
760 archive_path.write_bytes(archive)
761 destination.write_bytes(b"verified-uv")
762 validated_cached_binary(archive_path, destination, asset_name, digest)
763
764 destination.write_bytes(b"mutated")
765 expect_bootstrap_error(
766 lambda: validated_cached_binary(archive_path, destination, asset_name, digest),
767 "mutated cached executable",
768 )
769 destination.write_bytes(b"verified-uv")
770 archive_path.write_bytes(b"mutated")
771 expect_bootstrap_error(
772 lambda: validated_cached_binary(archive_path, destination, asset_name, digest),
773 "mutated cached archive",
774 )
775 archive_path.unlink()
776 expect_bootstrap_error(
777 lambda: validated_cached_binary(archive_path, destination, asset_name, digest),
778 "binary without authenticated archive",
779 )
780 destination.unlink()
781 outside = root / "outside"
782 outside.write_bytes(archive)
783 archive_path.symlink_to(outside)
784 expect_bootstrap_error(
785 lambda: validated_cached_payload(archive_path, digest),
786 "symlinked cached archive",
787 )
788 archive_path.unlink()
789 real_parent = root / "real-parent"
790 real_parent.mkdir()
791 linked_parent = root / "linked-parent"
792 linked_parent.symlink_to(real_parent, target_is_directory=True)
793 expect_bootstrap_error(
794 lambda: reject_symlink_components(root, linked_parent / "uv"),
795 "symlinked cache parent",
796 )
797 workspace = root / "workspace"
798 workspace.mkdir()
799 external_tools = root / "external-tools"
800 external_tools.mkdir()
801 (workspace / ".tools").symlink_to(external_tools, target_is_directory=True)
802 expect_bootstrap_error(
803 lambda: reject_symlink_components(
804 workspace / ".tools" / "uv",
805 workspace / ".tools" / "uv" / "0.0.0" / "uv",
806 anchor=workspace,
807 ),
808 "symlinked ancestor before cache root",
809 )
810
811
812def cache_mode_selftest() -> None:
813 """Run the adjacent adversarial mode suite under the bootstrap module."""
814 namespace = runpy.run_path(
815 str(Path(__file__).with_name("bootstrap_uv_mode_selftest.py")),
816 init_globals={"bootstrap": sys.modules[__name__]},
817 )
818 runner = namespace.get("run_mode_selftest")
819 if not callable(runner):
820 fail("uv mode selftest module has no runner")
821 runner()
822
823
824def manifest_matrix_selftest(manifest: dict[str, object]) -> None:
825 """Prove every supported mapping and reject unsupported/swapped records."""
826 expected_keys = {
827 "Darwin|aarch64",
828 "Darwin|x86_64",
829 "Windows|aarch64",
830 "Windows|x86_64",
831 "Linux|aarch64|gnu",
832 "Linux|aarch64|musl",
833 "Linux|x86_64|gnu",
834 "Linux|x86_64|musl",
835 }
836 assets = manifest["assets"]
837 if not isinstance(assets, dict) or set(assets) != expected_keys:
838 fail("selftest: manifest platform matrix is incomplete or over-broad")
839 for key in expected_keys:
840 system, machine, *libc_value = key.split("|")
841 _, name, digest = select_asset(
842 manifest, system, machine, libc_value[0] if libc_value else None
843 )
844 if name != expected_asset_name(key) or len(digest) != SHA256_HEX_LENGTH:
845 fail(f"selftest: malformed selected asset for {key}")
846 for system, machine, libc_name in (
847 ("FreeBSD", "x86_64", None),
848 ("Linux", "riscv64", "gnu"),
849 ("Linux", "x86_64", "uclibc"),
850 ):
851 expect_bootstrap_error(
852 lambda s=system, m=machine, libc=libc_name: select_asset(manifest, s, m, libc),
853 f"unsupported host {system}/{machine}/{libc_name}",
854 )
855 mutated = json.loads(json.dumps(manifest))
856 mutated["assets"]["Linux|x86_64|gnu"]["name"] = "uv-aarch64-unknown-linux-gnu.tar.gz"
857 expect_bootstrap_error(
858 lambda: select_asset(mutated, "Linux", "x86_64", "gnu"),
859 "architecture-swapped asset",
860 )
861
862
863def transport_and_libc_selftest(manifest: dict[str, object]) -> None:
864 """Exercise timeout handling and non-executing musl-loader detection."""
865
866 def timeout_opener(*_args: object, **_kwargs: object) -> DownloadResponse:
867 message = "timed out"
868 raise TimeoutError(message)
869
870 url, _, _ = select_asset(manifest, "Linux", "x86_64", "gnu")
871 expect_bootstrap_error(
872 lambda: download_payload(url, timeout_opener),
873 "download timeout",
874 "cannot download pinned uv asset",
875 )
876 with tempfile.TemporaryDirectory(prefix="ra8-musl-detect-") as raw:
877 directory = Path(raw)
878 loader_target = directory / "loader"
879 loader_target.write_bytes(b"musl")
880 (directory / "ld-musl-x86_64.so.1").symlink_to(loader_target)
881 if not has_musl_loader((directory,)):
882 fail("selftest: resolving musl loader symlink was rejected")
883
884
885def run_selftest() -> None:
886 """Exercise supported mappings and all important fail-closed paths."""
887 manifest = load_manifest(DEFAULT_MANIFEST)
888 manifest_matrix_selftest(manifest)
889 expect_bootstrap_error(lambda: verify_payload(b"tampered", "0" * 64), "checksum mismatch")
890 payload = b"uv-test-payload"
891 verify_payload(payload, hashlib.sha256(payload).hexdigest())
892 transport_and_libc_selftest(manifest)
893 archive_selftest()
894 cache_selftest()
895 cache_mode_selftest()
896 print("bootstrap_uv.py --selftest: PASS")
897
898
899def parse_args() -> argparse.Namespace:
900 """Parse the small bootstrap command-line interface."""
901 parser = argparse.ArgumentParser(description=__doc__)
902 mode = parser.add_mutually_exclusive_group(required=True)
903 mode.add_argument("--ensure", action="store_true", help="install and print pinned uv")
904 mode.add_argument(
905 "--verify-cache",
906 action="store_true",
907 help="authenticate an existing cache without downloads or writes",
908 )
909 mode.add_argument(
910 "--check-cache-modes",
911 action="store_true",
912 help="check shared POSIX cache modes without authenticating or writing",
913 )
914 mode.add_argument("--print-path", action="store_true", help="print cache path without writes")
915 mode.add_argument(
916 "--run",
917 nargs=argparse.REMAINDER,
918 metavar="UV_ARG",
919 help="run uv from an authenticated existing-cache snapshot",
920 )
921 mode.add_argument(
922 "--ensure-and-run",
923 nargs=argparse.REMAINDER,
924 metavar="UV_ARG",
925 help="ensure the cache, then run uv from an authenticated snapshot",
926 )
927 mode.add_argument("--selftest", action="store_true", help="run offline fail-closed tests")
928 parser.add_argument("--manifest", type=Path, default=DEFAULT_MANIFEST)
929 parser.add_argument("--cache-root", type=Path, default=DEFAULT_CACHE)
930 return parser.parse_args()
931
932
933def main() -> int:
934 """Dispatch the requested bootstrap mode."""
935 args = parse_args()
936 if args.selftest:
937 run_selftest()
938 status = 0
939 elif args.run is not None:
940 status = run_cached_uv(args.manifest, args.cache_root, args.run, ensure=False)
941 elif args.ensure_and_run is not None:
942 status = run_cached_uv(args.manifest, args.cache_root, args.ensure_and_run, ensure=True)
943 else:
944 manifest = load_manifest(args.manifest)
945 _, asset_name, _ = select_asset(manifest)
946 version = manifest["version"]
947 if not isinstance(version, str):
948 fail("uv manifest version is not a string")
949 destination = cache_destination(args.cache_root, version, asset_name)
950 if args.print_path:
951 print(destination)
952 elif args.check_cache_modes:
953 verify_cached_modes(destination.parent / asset_name, destination)
954 print(destination)
955 elif args.verify_cache:
956 print(verify_cached_uv(args.manifest, args.cache_root))
957 else:
958 print(ensure_uv(args.manifest, args.cache_root))
959 status = 0
960 return status
961
962
963if __name__ == "__main__":
964 try:
965 raise SystemExit(main())
966 except CacheApplyRequiredError as error:
967 print(f"APPLY REQUIRED: {error}", file=sys.stderr)
968 raise SystemExit(2) from error
969 except BootstrapError as error:
970 print(f"ERROR: {error}", file=sys.stderr)
971 raise SystemExit(1) from error
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298