chore(code): sbat section generator build tool

This commit is contained in:
2025-11-03 22:37:06 -05:00
parent 632781abbf
commit 9a803ad355
10 changed files with 111 additions and 68 deletions

6
Cargo.lock generated
View File

@@ -69,7 +69,7 @@ name = "edera-sprout"
version = "0.0.22" version = "0.0.22"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags", "edera-sprout-build",
"edera-sprout-config", "edera-sprout-config",
"edera-sprout-eficore", "edera-sprout-eficore",
"hex", "hex",
@@ -81,6 +81,10 @@ dependencies = [
"uefi-raw", "uefi-raw",
] ]
[[package]]
name = "edera-sprout-build"
version = "0.0.22"
[[package]] [[package]]
name = "edera-sprout-config" name = "edera-sprout-config"
version = "0.0.22" version = "0.0.22"

View File

@@ -1,5 +1,6 @@
[workspace] [workspace]
members = [ members = [
"crates/build",
"crates/config", "crates/config",
"crates/eficore", "crates/eficore",
"crates/sprout", "crates/sprout",

View File

@@ -15,9 +15,10 @@ as an argument to boot.sh to boot the specified architecture.
Sprout is split into multiple crates: 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-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-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. It is intended that overtime Sprout will be split into even more crates.

12
crates/build/Cargo.toml Normal file
View File

@@ -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"

76
crates/build/src/lib.rs Normal file
View File

@@ -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<u8>, 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");
}

View File

@@ -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"));

View File

@@ -9,7 +9,6 @@ edition.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
bitflags.workspace = true
edera-sprout-config.path = "../config" edera-sprout-config.path = "../config"
edera-sprout-eficore.path = "../eficore" edera-sprout-eficore.path = "../eficore"
hex.workspace = true hex.workspace = true
@@ -20,6 +19,9 @@ log.workspace = true
uefi.workspace = true uefi.workspace = true
uefi-raw.workspace = true uefi-raw.workspace = true
[build-dependencies]
edera-sprout-build.path = "../build"
[[bin]] [[bin]]
name = "sprout" name = "sprout"
path = "src/main.rs" path = "src/main.rs"

View File

@@ -1,57 +1,7 @@
use std::path::PathBuf; use edera_sprout_build::generate_sbat_module;
use std::{env, fs};
/// The size of the sbat.csv file. /// Build script entry point for Sprout.
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.
fn main() { fn main() {
// Generate the sbat.csv file. // Generate the sbat.generated.rs file.
generate_sbat_csv(); generate_sbat_module();
} }

View File

@@ -1,11 +1,2 @@
/// SBAT must be aligned by 512 bytes. // Include the generated sbat section in this file.
const SBAT_SIZE: usize = 512; include!(concat!(env!("OUT_DIR"), "/sbat.generated.rs"));
/// 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"));