Skip to content

Commit d42acf5

Browse files
ericktMark-Simulacrum
authored andcommitted
Include additional hashes in src/stage0
This patch changes `bump-stage0` to include: * The sha256 hash of the channel manifest used to create `src/stage0`. * The rust and rustfmt git commit in `src/stage0`. * Hashes of all the artifacts, like the source tarball, in `src/stage0`. Combined this will allow for: * Projects that bootstrap their own compiler, such as Fuchsia, or users of [bootstrap], to build their compilers offline without needing to communicate with static.rust-lang.org. * Auditors to detect if the channel manifest, and all the artifacts inside the manifest, were modified after it was used to generate `src/stage0`. Furthermore, if they did find modified artifacts, they could determine if the Rust Signing Key was compromised by checking if any modified file was signed properly. Finally, it allows regeneration of `src/stage0` when specifying both the day of the build for rust, and the day of the build for rustfmt, which can allow a maintainer to regenerate `src/stage0` to verify nothing changed. [bootstrap]: https://github.com/dtolnay/bootstrap [mrustc]: https://github.com/thepowersgang/mrustc
1 parent ade8487 commit d42acf5

File tree

5 files changed

+105
-12
lines changed

5 files changed

+105
-12
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,10 @@ dependencies = [
334334
"anyhow",
335335
"build_helper",
336336
"curl",
337+
"hex",
337338
"indexmap",
338339
"serde",
340+
"sha2",
339341
"toml 0.8.23",
340342
]
341343

src/bootstrap/src/core/download.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ pub(crate) fn maybe_download_rustfmt<'a>(
506506
return Some(PathBuf::new());
507507
}
508508

509-
let VersionMetadata { date, version } = dwn_ctx.stage0_metadata.rustfmt.as_ref()?;
509+
let VersionMetadata { date, version, .. } = dwn_ctx.stage0_metadata.rustfmt.as_ref()?;
510510
let channel = format!("{version}-{date}");
511511

512512
let host = dwn_ctx.host_target;

src/build_helper/src/stage0_parser.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub struct Stage0 {
1010

1111
#[derive(Default, Clone)]
1212
pub struct VersionMetadata {
13+
pub channel_manifest_hash: String,
14+
pub git_commit_hash: String,
1315
pub date: String,
1416
pub version: String,
1517
}
@@ -50,9 +52,21 @@ pub fn parse_stage0_file() -> Stage0 {
5052
"git_merge_commit_email" => stage0.config.git_merge_commit_email = value.to_owned(),
5153
"nightly_branch" => stage0.config.nightly_branch = value.to_owned(),
5254

55+
"compiler_channel_manifest_hash" => {
56+
stage0.compiler.channel_manifest_hash = value.to_owned()
57+
}
58+
"compiler_git_commit_hash" => stage0.compiler.git_commit_hash = value.to_owned(),
5359
"compiler_date" => stage0.compiler.date = value.to_owned(),
5460
"compiler_version" => stage0.compiler.version = value.to_owned(),
5561

62+
"rustfmt_channel_manifest_hash" => {
63+
stage0.rustfmt.get_or_insert(VersionMetadata::default()).channel_manifest_hash =
64+
value.to_owned();
65+
}
66+
"rustfmt_git_commit_hash" => {
67+
stage0.rustfmt.get_or_insert(VersionMetadata::default()).git_commit_hash =
68+
value.to_owned();
69+
}
5670
"rustfmt_date" => {
5771
stage0.rustfmt.get_or_insert(VersionMetadata::default()).date = value.to_owned();
5872
}

src/tools/bump-stage0/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ edition = "2021"
99
anyhow = "1.0.34"
1010
build_helper = { path = "../../build_helper" }
1111
curl = "0.4.38"
12+
hex = "0.4.3"
1213
indexmap = { version = "2.0.0", features = ["serde"] }
1314
serde = { version = "1.0.125", features = ["derive"] }
1415
toml = "0.8.23"
16+
sha2 = "0.10.1"

src/tools/bump-stage0/src/main.rs

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use anyhow::{Context, Error};
44
use build_helper::stage0_parser::{Stage0Config, VersionMetadata, parse_stage0_file};
55
use curl::easy::Easy;
66
use indexmap::IndexMap;
7+
use sha2::{Digest, Sha256};
78

89
const PATH: &str = "src/stage0";
910
const COMPILER_COMPONENTS: &[&str] = &["rustc", "rust-std", "cargo", "clippy-preview"];
@@ -13,13 +14,14 @@ struct Tool {
1314
config: Stage0Config,
1415

1516
channel: Channel,
16-
date: Option<String>,
17+
compiler_date: Option<String>,
18+
rustfmt_date: Option<String>,
1719
version: [u16; 3],
1820
checksums: IndexMap<String, String>,
1921
}
2022

2123
impl Tool {
22-
fn new(date: Option<String>) -> Result<Self, Error> {
24+
fn new(compiler_date: Option<String>, rustfmt_date: Option<String>) -> Result<Self, Error> {
2325
let channel = match std::fs::read_to_string("src/ci/channel")?.trim() {
2426
"stable" => Channel::Stable,
2527
"beta" => Channel::Beta,
@@ -38,7 +40,14 @@ impl Tool {
3840

3941
let existing = parse_stage0_file();
4042

41-
Ok(Self { channel, version, date, config: existing.config, checksums: IndexMap::new() })
43+
Ok(Self {
44+
channel,
45+
version,
46+
compiler_date,
47+
rustfmt_date,
48+
config: existing.config,
49+
checksums: IndexMap::new(),
50+
})
4251
}
4352

4453
fn update_stage0_file(mut self) -> Result<(), Error> {
@@ -78,10 +87,21 @@ impl Tool {
7887
file_content.push_str("\n");
7988

8089
let compiler = self.detect_compiler()?;
90+
file_content.push_str(&format!(
91+
"compiler_channel_manifest_hash={}\n",
92+
compiler.channel_manifest_hash
93+
));
94+
file_content.push_str(&format!("compiler_git_commit_hash={}\n", compiler.git_commit_hash));
8195
file_content.push_str(&format!("compiler_date={}\n", compiler.date));
8296
file_content.push_str(&format!("compiler_version={}\n", compiler.version));
8397

8498
if let Some(rustfmt) = self.detect_rustfmt()? {
99+
file_content.push_str(&format!(
100+
"rustfmt_channel_manifest_hash={}\n",
101+
rustfmt.channel_manifest_hash
102+
));
103+
file_content
104+
.push_str(&format!("rustfmt_git_commit_hash={}\n", rustfmt.git_commit_hash));
85105
file_content.push_str(&format!("rustfmt_date={}\n", rustfmt.date));
86106
file_content.push_str(&format!("rustfmt_version={}\n", rustfmt.version));
87107
}
@@ -112,9 +132,16 @@ impl Tool {
112132
Channel::Nightly => "beta".to_string(),
113133
};
114134

115-
let manifest = fetch_manifest(&self.config, &channel, self.date.as_deref())?;
135+
let (manifest, manifest_hash) =
136+
fetch_manifest(&self.config, &channel, self.compiler_date.as_deref())?;
116137
self.collect_checksums(&manifest, COMPILER_COMPONENTS)?;
117138
Ok(VersionMetadata {
139+
channel_manifest_hash: manifest_hash,
140+
git_commit_hash: manifest.pkg["rust"]
141+
.git_commit_hash
142+
.as_ref()
143+
.expect("invalid git_commit_hash")
144+
.into(),
118145
date: manifest.date,
119146
version: if self.channel == Channel::Nightly {
120147
"beta".to_string()
@@ -138,9 +165,19 @@ impl Tool {
138165
return Ok(None);
139166
}
140167

141-
let manifest = fetch_manifest(&self.config, "nightly", self.date.as_deref())?;
168+
let (manifest, manifest_hash) =
169+
fetch_manifest(&self.config, "nightly", self.rustfmt_date.as_deref())?;
142170
self.collect_checksums(&manifest, RUSTFMT_COMPONENTS)?;
143-
Ok(Some(VersionMetadata { date: manifest.date, version: "nightly".into() }))
171+
Ok(Some(VersionMetadata {
172+
channel_manifest_hash: manifest_hash,
173+
git_commit_hash: manifest.pkg["rust"]
174+
.git_commit_hash
175+
.as_ref()
176+
.expect("invalid git_commit_hash")
177+
.into(),
178+
date: manifest.date,
179+
version: "nightly".into(),
180+
}))
144181
}
145182

146183
fn collect_checksums(&mut self, manifest: &Manifest, components: &[&str]) -> Result<(), Error> {
@@ -164,12 +201,29 @@ impl Tool {
164201
}
165202
}
166203
}
204+
for artifact in manifest.artifacts.values() {
205+
for targets in artifact.target.values() {
206+
for target in targets {
207+
let url = target
208+
.url
209+
.strip_prefix(&prefix)
210+
.ok_or_else(|| {
211+
anyhow::anyhow!(
212+
"url doesn't start with dist server base: {}",
213+
target.url
214+
)
215+
})?
216+
.to_string();
217+
self.checksums.insert(url, target.hash_sha256.clone());
218+
}
219+
}
220+
}
167221
Ok(())
168222
}
169223
}
170224

171225
fn main() -> Result<(), Error> {
172-
let tool = Tool::new(std::env::args().nth(1))?;
226+
let tool = Tool::new(std::env::args().nth(1), std::env::args().nth(2))?;
173227
tool.update_stage0_file()?;
174228
Ok(())
175229
}
@@ -178,18 +232,24 @@ fn fetch_manifest(
178232
config: &Stage0Config,
179233
channel: &str,
180234
date: Option<&str>,
181-
) -> Result<Manifest, Error> {
235+
) -> Result<(Manifest, String), Error> {
182236
let url = if let Some(date) = date {
183237
format!("{}/dist/{}/channel-rust-{}.toml", config.dist_server, date, channel)
184238
} else {
185239
format!("{}/dist/channel-rust-{}.toml", config.dist_server, channel)
186240
};
187241

242+
let manifest_bytes = http_get(&url)?;
243+
244+
let mut sha256 = Sha256::new();
245+
sha256.update(&manifest_bytes);
246+
let manifest_hash = hex::encode(sha256.finalize());
247+
188248
// FIXME: on newer `toml` (>= `0.9.*`), use `toml::from_slice`. For now, we use the most recent
189249
// `toml` available in-tree which is `0.8.*`, so we have to do an additional dance here.
190-
let response = http_get(&url)?;
191-
let response = String::from_utf8(response)?;
192-
Ok(toml::from_str(&response)?)
250+
let manifest_str = String::from_utf8(manifest_bytes)?;
251+
let manifest = toml::from_str(&manifest_str)?;
252+
Ok((manifest, manifest_hash))
193253
}
194254

195255
fn http_get(url: &str) -> Result<Vec<u8>, Error> {
@@ -219,11 +279,14 @@ enum Channel {
219279
struct Manifest {
220280
date: String,
221281
pkg: IndexMap<String, ManifestPackage>,
282+
artifacts: IndexMap<String, ManifestArtifact>,
222283
}
223284

224285
#[derive(Debug, serde::Serialize, serde::Deserialize)]
225286
struct ManifestPackage {
226287
version: String,
288+
#[serde(default)]
289+
git_commit_hash: Option<String>,
227290
target: IndexMap<String, ManifestTargetPackage>,
228291
}
229292

@@ -234,3 +297,15 @@ struct ManifestTargetPackage {
234297
xz_url: Option<String>,
235298
xz_hash: Option<String>,
236299
}
300+
301+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
302+
struct ManifestArtifact {
303+
target: IndexMap<String, Vec<ManifestTargetArtifact>>,
304+
}
305+
306+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
307+
#[serde(rename_all = "kebab-case")]
308+
struct ManifestTargetArtifact {
309+
url: String,
310+
hash_sha256: String,
311+
}

0 commit comments

Comments
 (0)