rto_exec/runtime_pins.rs
1// The pinned sandbox-runtime archives, and the host-platform selection.
2//
3// # Why this file is `include!`d rather than imported
4//
5// These pins are needed in two places that cannot share a crate graph: this
6// library, which provisions and verifies the archive, and `build.rs`, which
7// refuses to build `exec-boxlite` against anything else. A build script cannot
8// depend on the crate it builds, so the single source of truth is this file and
9// `build.rs` pulls it in with `include!`.
10//
11// That constrains what may appear here: **no `use`, no `crate::` paths, no
12// references to anything outside this file.** It must compile standalone.
13//
14// # What is pinned, and why it has to be
15//
16// `boxlite` does not build a hypervisor when it is compiled from crates.io. Its
17// three `-sys` crates each detect a published package (`.cargo_vcs_info.json`)
18// and disable themselves, and `libkrun-sys` excludes the sources they would
19// otherwise build. What actually runs is a prebuilt tarball that `boxlite`'s own
20// `build.rs` fetches with a bare `curl -fsSL`, `include_bytes!`s into the rlib,
21// and extracts and executes at run time.
22//
23// That fetch has **no expected digest of any kind** — searched for one four
24// ways (`expected|_SHA256|checksum|digest`; `sha256|integrity|signature|cosign`;
25// and two 64-hex-literal patterns over `build.rs` and `src/`), all NOT FOUND —
26// and its URL is overridable through `BOXLITE_RUNTIME_URL`. Two builds of the
27// same crate version can therefore embed different bytes, undetectably.
28//
29// Roteiro will not ship that. The digests below were computed from the real
30// v0.10.0 release assets, and are what makes the embedded runtime reproducible:
31// `roteiro security prefetch --allow-download` fetches and verifies the archive
32// against them, and `build.rs` then refuses to build unless `BOXLITE_RUNTIME_URL`
33// points at a local file whose bytes match. `boxlite`'s `curl` never reaches the
34// network, because the `file://` URL it is given is already on disk.
35//
36// Bump these together with the `boxlite` pin in `Cargo.toml`; a version skew is
37// caught before the build runs rather than discovered at run time — see
38// [`RUNTIME_VERSION`] for which check catches it and why `build.rs` is not the
39// one that can.
40
41/// One platform's prebuilt sandbox-runtime archive.
42///
43/// The `target` names are `boxlite`'s own, from its `runtime_target()` — they
44/// are what appears in the release asset's filename, so they are the identifiers
45/// that can actually be checked against upstream.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct PinnedArchive {
48 /// The platform, as the upstream release names it.
49 pub target: &'static str,
50 /// Where the archive is published.
51 pub url: &'static str,
52 /// Lowercase hex SHA-256 of the archive. Verified before it is installed and
53 /// again before it is built against.
54 pub sha256: &'static str,
55 /// Its exact size. Redundant with the digest, and kept because a truncated
56 /// body is the common failure and "expected 26520984 bytes, got 1043" says
57 /// so far more clearly than two digests that differ.
58 pub bytes: u64,
59}
60
61/// The `boxlite` release these archives belong to.
62///
63/// Held equal to the `boxlite` the lockfile resolves, by
64/// `tests/runtime_pin_integrity.rs`. That is a test rather than a build-script
65/// check because nothing reaches this script from `boxlite`'s own manifest:
66/// cargo passes `DEP_BOXLITE_*` for the keys boxlite's build script emits, and
67/// its version is not one of them.
68///
69/// The check is not redundant with the digests. On the strict path — the one CI
70/// takes and the one `build.rs` recommends — `BOXLITE_RUNTIME_URL` names an
71/// archive provisioned *from these very pins*, so a bump that moved `boxlite`
72/// and left the pins alone would hand the old archive to the new library,
73/// verify it against the old digests it was provisioned from, and agree with
74/// itself. Every digest here would match and the pairing would still be wrong.
75pub const RUNTIME_VERSION: &str = "0.10.0";
76
77/// The asset id the archive is provisioned under.
78pub const RUNTIME_ASSET: &str = "boxlite-runtime";
79
80/// The file name the archive is installed as.
81pub const RUNTIME_FILE: &str = "boxlite-runtime.tar.gz";
82
83/// Every platform Roteiro pins a sandbox runtime for.
84///
85/// These are the three `boxlite` publishes. A host outside this list cannot
86/// build `exec-boxlite`, and is told so by name rather than by a link error.
87pub const RUNTIME_ARCHIVES: &[PinnedArchive] = &[
88 PinnedArchive {
89 target: "darwin-arm64",
90 url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.10.0/boxlite-runtime-v0.10.0-darwin-arm64.tar.gz",
91 sha256: "8867bb02687c02a8ab6975c1dd8ef85d549dba9e5e94087cb7fb61838b56d979",
92 bytes: 29_415_066,
93 },
94 PinnedArchive {
95 target: "linux-x64-gnu",
96 url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.10.0/boxlite-runtime-v0.10.0-linux-x64-gnu.tar.gz",
97 sha256: "3de43b2ca1620f7d73b71630be7f9e26f13f28497a4a692617a663dde0c8400f",
98 bytes: 27_941_945,
99 },
100 PinnedArchive {
101 target: "linux-arm64-gnu",
102 url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.10.0/boxlite-runtime-v0.10.0-linux-arm64-gnu.tar.gz",
103 sha256: "e67786ba493430bed70e992fcd7248f4a71e1eaf562ddbbb016f478d044ca4cf",
104 bytes: 31_627_264,
105 },
106];
107
108/// The upstream target name for an `(os, arch)` pair, or `None` for a platform
109/// with no published runtime.
110///
111/// This mirrors `boxlite`'s own `runtime_target()`. It is spelled out rather
112/// than derived so that a platform upstream adds later is a deliberate pin here,
113/// not an automatic one.
114#[must_use]
115pub fn runtime_target(os: &str, arch: &str) -> Option<&'static str> {
116 match (os, arch) {
117 ("macos", "aarch64") => Some("darwin-arm64"),
118 ("linux", "x86_64") => Some("linux-x64-gnu"),
119 ("linux", "aarch64") => Some("linux-arm64-gnu"),
120 _ => None,
121 }
122}
123
124/// The pinned archive for an `(os, arch)` pair.
125#[must_use]
126pub fn archive_for(os: &str, arch: &str) -> Option<&'static PinnedArchive> {
127 let target = runtime_target(os, arch)?;
128 let mut index = 0;
129 // A plain loop rather than an iterator: this file is `include!`d into a
130 // build script, where keeping the surface to the language core is the point.
131 while index < RUNTIME_ARCHIVES.len() {
132 if str_eq(RUNTIME_ARCHIVES[index].target, target) {
133 return Some(&RUNTIME_ARCHIVES[index]);
134 }
135 index += 1;
136 }
137 None
138}
139
140/// Byte equality for two `&str`, usable in the `const`-flavoured context above.
141fn str_eq(a: &str, b: &str) -> bool {
142 a.as_bytes() == b.as_bytes()
143}