diff --git a/Cargo.lock b/Cargo.lock index 963bebe..900723e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,7 +69,7 @@ name = "edera-sprout" version = "0.0.22" dependencies = [ "anyhow", - "bitflags", + "edera-sprout-build", "edera-sprout-config", "edera-sprout-eficore", "hex", @@ -81,6 +81,10 @@ dependencies = [ "uefi-raw", ] +[[package]] +name = "edera-sprout-build" +version = "0.0.22" + [[package]] name = "edera-sprout-config" version = "0.0.22" diff --git a/Cargo.toml b/Cargo.toml index 969077a..4cc53bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/build", "crates/config", "crates/eficore", "crates/sprout", diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 41d2965..cf1a808 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -15,9 +15,10 @@ as an argument to boot.sh to boot the specified architecture. Sprout is split into multiple crates: +- `edera-sprout-build` at `crates/build`: Build logic for Sprout. - `edera-sprout-config` at `crates/config`: Serialization structures for the Sprout configuration file. - `edera-sprout-eficore` at `crates/eficore`: Core library for Sprout EFI code. -- `edera-sprout` as `crates/sprout`: Sprout's main crate that contains bootloader logic. +- `edera-sprout` as `crates/sprout`: Main crate that contains the Sprout bootloader logic. It is intended that overtime Sprout will be split into even more crates. diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml new file mode 100644 index 0000000..b9a9e78 --- /dev/null +++ b/crates/build/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "edera-sprout-build" +description = "Sprout Build Tools" +license.workspace = true +version.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[lib] +name = "edera_sprout_build" +path = "src/lib.rs" diff --git a/crates/build/src/lib.rs b/crates/build/src/lib.rs new file mode 100644 index 0000000..30700bb --- /dev/null +++ b/crates/build/src/lib.rs @@ -0,0 +1,76 @@ +use std::path::PathBuf; +use std::{env, fs}; + +/// Block size of the sbat section. +const SBAT_BLOCK_SIZE: usize = 512; + +/// Template contents for the sbat.generated.rs file. +const SBAT_RS_TEMPLATE: &str = include_str!("sbat.template.rs"); + +/// Pad with zeros the given `data` to a multiple of `block_size`. +fn block_pad(data: &mut Vec, block_size: usize) { + let needed = data.len().div_ceil(block_size).max(1) * block_size; + + if needed != data.len() { + data.resize(needed, 0); + } +} + +/// Generate an .sbat link section module. This should be coupled with including the sbat module in +/// the crate that intends to embed the sbat section. +/// We intake a sbat.template.csv file in the calling crate and output a sbat.dat +/// which is included by a generated sbat.generated.rs file. +pub fn generate_sbat_module() { + // Notify Cargo that if the version changes, we need to regenerate the sbat.out file. + println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION"); + + // The version of the package. + let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION not set"); + + // The output directory to place the sbat.csv into. + let output_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); + + // The output path to the sbat.out file. + let out_file = output_dir.join("sbat.out"); + + // The output path to the sbat.generated.rs file. + let rs_file = output_dir.join("sbat.generated.rs"); + + // The path to the root of the crate. + let crate_root = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set")); + + // The path to the sbat.template.tsv file is in the source directory of the crate. + let sbat_template_file = crate_root.join("src/sbat.csv"); + + // Notify Cargo that if sbat.csv changes, we need to regenerate the sbat.out file. + println!( + "cargo:rerun-if-changed={}", + sbat_template_file + .to_str() + .expect("unable to convert sbat template path file to a string") + ); + + // Read the sbat.csv template file. + let sbat_template = + fs::read_to_string(&sbat_template_file).expect("unable to read sbat.csv file"); + + // Replace the version placeholder in the template with the actual version. + let sbat = sbat_template.replace("{version}", &version); + + // Encode the sbat.csv as bytes. + let mut encoded = sbat.as_bytes().to_vec(); + + // Pad the sbat.csv to the required block size. + block_pad(&mut encoded, SBAT_BLOCK_SIZE); + + // Write the sbat.out file to the output directory. + fs::write(&out_file, &encoded).expect("unable to write sbat.out"); + + // Generate the contents of the sbat.generated.rs file. + // The size must tbe size of the encoded sbat.out file. + let sbat_rs = SBAT_RS_TEMPLATE.replace("{size}", &encoded.len().to_string()); + + // Write the sbat.generated.rs file to the output directory. + fs::write(&rs_file, sbat_rs).expect("unable to write sbat.generated.rs"); +} diff --git a/crates/build/src/sbat.template.rs b/crates/build/src/sbat.template.rs new file mode 100644 index 0000000..f20430a --- /dev/null +++ b/crates/build/src/sbat.template.rs @@ -0,0 +1,6 @@ +/// Define the SBAT attestation by including the sbat.csv file. +/// See this document for more details: https://github.com/rhboot/shim/blob/main/SBAT.md +/// NOTE: This data must be aligned by 512 bytes. +#[used] +#[unsafe(link_section = ".sbat")] +static SBAT: [u8; {size}] = *include_bytes!(concat!(env!("OUT_DIR"), "/sbat.out")); diff --git a/crates/sprout/Cargo.toml b/crates/sprout/Cargo.toml index 35696de..58538fc 100644 --- a/crates/sprout/Cargo.toml +++ b/crates/sprout/Cargo.toml @@ -9,7 +9,6 @@ edition.workspace = true [dependencies] anyhow.workspace = true -bitflags.workspace = true edera-sprout-config.path = "../config" edera-sprout-eficore.path = "../eficore" hex.workspace = true @@ -20,6 +19,9 @@ log.workspace = true uefi.workspace = true uefi-raw.workspace = true +[build-dependencies] +edera-sprout-build.path = "../build" + [[bin]] name = "sprout" path = "src/main.rs" diff --git a/crates/sprout/build.rs b/crates/sprout/build.rs index 054185f..8684da7 100644 --- a/crates/sprout/build.rs +++ b/crates/sprout/build.rs @@ -1,57 +1,7 @@ -use std::path::PathBuf; -use std::{env, fs}; +use edera_sprout_build::generate_sbat_module; -/// The size of the sbat.csv file. -const SBAT_SIZE: usize = 512; - -/// Generate the sbat.csv for the .sbat link section. -/// -/// We intake a sbat.template.tsv and output a sbat.csv which is included by src/sbat.rs -fn generate_sbat_csv() { - // Notify Cargo that if the Sprout version changes, we need to regenerate the sbat.csv. - println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION"); - - // The version of the sprout crate. - let sprout_version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION not set"); - - // The output directory to place the sbat.csv into. - let output_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); - - // The output path to the sbat.csv. - let output_file = output_dir.join("sbat.csv"); - - // The path to the root of the sprout crate. - let sprout_root = - PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set")); - - // The path to the sbat.template.tsv file is in the source directory of the sprout crate. - let template_path = sprout_root.join("src/sbat.template.csv"); - - // Read the sbat.csv template file. - let template = fs::read_to_string(&template_path).expect("unable to read template file"); - - // Replace the version placeholder in the template with the actual version. - let sbat = template.replace("{version}", &sprout_version); - - // Encode the sbat.csv as bytes. - let mut encoded = sbat.as_bytes().to_vec(); - - if encoded.len() > SBAT_SIZE { - panic!("sbat.csv is too large"); - } - - // Pad the sbat.csv to the required size. - while encoded.len() < SBAT_SIZE { - encoded.push(0); - } - - // Write the sbat.csv to the output directory. - fs::write(&output_file, encoded).expect("unable to write sbat.csv"); -} - -/// Build script entry point. -/// Right now, all we need to do is generate the sbat.csv file. +/// Build script entry point for Sprout. fn main() { - // Generate the sbat.csv file. - generate_sbat_csv(); + // Generate the sbat.generated.rs file. + generate_sbat_module(); } diff --git a/crates/sprout/src/sbat.template.csv b/crates/sprout/src/sbat.csv similarity index 100% rename from crates/sprout/src/sbat.template.csv rename to crates/sprout/src/sbat.csv diff --git a/crates/sprout/src/sbat.rs b/crates/sprout/src/sbat.rs index 9002e00..2867ec4 100644 --- a/crates/sprout/src/sbat.rs +++ b/crates/sprout/src/sbat.rs @@ -1,11 +1,2 @@ -/// SBAT must be aligned by 512 bytes. -const SBAT_SIZE: usize = 512; - -/// Define the SBAT attestation by including the sbat.csv file. -/// See this document for more details: https://github.com/rhboot/shim/blob/main/SBAT.md -/// NOTE: Alignment can't be enforced by an attribute, so instead the alignment is currently -/// enforced by the SBAT_SIZE being 512. The build.rs will ensure that the sbat.csv is padded. -/// This code will not compile if the sbat.csv is a different size than SBAT_SIZE. -#[used] -#[unsafe(link_section = ".sbat")] -static SBAT: [u8; SBAT_SIZE] = *include_bytes!(concat!(env!("OUT_DIR"), "/sbat.csv")); +// Include the generated sbat section in this file. +include!(concat!(env!("OUT_DIR"), "/sbat.generated.rs"));