Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bootloader-tool/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ max_size = 0x8000
state = { start = 0x0800B000, size = 0x2000 }

[application]
slot_starts = [0x800D000, 0x80F9000]
slot_starts = [0x0800D000, 0x080F9000]
run_start = 0x10020000
slot_size = 0xEC000 # 944K
address_mapping = [
{ from_start = 0x00000000, from_end = 0x0FFFFFFF, to_start = 0x10000000 }
]
6 changes: 5 additions & 1 deletion bootloader-tool/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,16 @@ pub async fn process(config: &Config, command: RunCommands) -> anyhow::Result<()

let mut command = std::process::Command::new(&run_args.probe_rs_path);
command.args(["attach", "--chip", &run_args.probe_args.chip]);
command.args(["--no-catch-hardfault", "--no-catch-reset"]);

if let Some(probe) = run_args.probe_args.probe.as_ref() {
command.args(["--probe", probe]);
}

command.arg(run_args.sign_args.input_path).status().unwrap();
command
.arg(run_args.sign_args.input_paths.last().unwrap())
.status()
.unwrap();

Ok(())
}
115 changes: 80 additions & 35 deletions bootloader-tool/src/commands/sign.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::BTreeSet;
use std::path::PathBuf;

use anyhow::Context;
Expand All @@ -15,45 +16,94 @@ pub struct SignOutput {
pub rkth: Rkth,
}

fn perform_checks(config: &Config, is_bootloader: bool, image: &[u8], base_addr: u32) -> anyhow::Result<()> {
struct Values {
run_start: u64,
max_size: u64,
}

let values = if is_bootloader {
let Some(bootloader) = &config.bootloader else {
return Err(anyhow::anyhow!("Bootloader field not set in config"));
};

Values {
run_start: bootloader.run_start,
max_size: bootloader.max_size,
}
} else {
let Some(application) = &config.application else {
return Err(anyhow::anyhow!("Application field not set in config"));
};

Values {
run_start: application.run_start,
max_size: application.slot_size,
}
};

if values.run_start != base_addr as u64 {
return Err(anyhow::anyhow!(
"Image will be run from unexpected address 0x{:x}, should be 0x{:x}",
base_addr,
values.run_start
));
}

if values.max_size < image.len() as u64 {
return Err(anyhow::anyhow!(
"Image can not fit in 0x{:x}, actual size is 0x{:x}",
values.max_size,
image.len(),
));
}

Ok(())
}

pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result<SignOutput> {
let (is_bootloader, args) = match command {
SignCommands::Bootloader(sign_arguments) => (true, sign_arguments),
SignCommands::Application(sign_arguments) => (false, sign_arguments),
};

let input_data = std::fs::read(&args.input_path)?;
let files: Vec<Vec<u8>> = args
.input_paths
.iter()
.map(|input_path| {
log::info!("Reading ELF from {}", input_path.display());
std::fs::read(input_path)
})
.collect::<Result<Vec<Vec<u8>>, std::io::Error>>()?;

log::info!("Reading ELF from {}", args.input_path.display());
let file = ElfFile32::parse(&input_data[..]).context("Could not parse ELF file")?;
let files: Vec<ElfFile32> = files
.iter()
.map(|input_data| ElfFile32::parse(&input_data[..]).context("Could not parse ELF file"))
.collect::<Result<Vec<ElfFile32>, anyhow::Error>>()?;

if is_bootloader {
log::info!("Extracting prelude");
let out = objcopy::remove_non_prelude(&input_data)?;
let out = objcopy::remove_non_prelude(files.first().unwrap())?;
std::fs::write(args.prelude_path_with_default(), &out).context("Could not write prelude elf file")?;
}

log::info!("Generating image for {}", args.input_path.display());
let (image, base_addr) = objcopy::objcopy(&file)?;

if is_bootloader {
if let Some(bootloader) = &config.bootloader
&& bootloader.run_start != base_addr as u64
{
return Err(anyhow::anyhow!(
"Bootloader image will be run from unexpected address 0x{:x}, should be 0x{:x}",
base_addr,
bootloader.run_start
));
let objcopy_config = if is_bootloader {
objcopy::Config {
mappings: &BTreeSet::new(),
max_size: Some(config.bootloader.as_ref().unwrap().max_size as u32),
}
} else if let Some(application) = &config.application
&& application.run_start != base_addr as u64
{
return Err(anyhow::anyhow!(
"Application image will be run from unexpected address 0x{:x}, should be 0x{:x}",
base_addr,
application.run_start
));
}
} else {
let app = config.application.as_ref().unwrap();
objcopy::Config {
mappings: &app.address_mapping,
max_size: Some(app.slot_size as u32),
}
};

log::info!("Generating image");
let (image, base_addr) = objcopy::objcopy(files.iter(), objcopy_config)?;

perform_checks(config, is_bootloader, &image, base_addr)?;

let output_unsigned_path = args.output_unsigned_path_with_default();
log::debug!("Wrote unsigned bare binary image to {}", output_unsigned_path.display());
Expand All @@ -78,10 +128,11 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result<S
)
.context("Could not generate prestage MBI")?;

let mut signature_path = args.signature_path.clone();
let rkth = cert_block.rkth();
let signature_path = args.signature_path_with_default();

if !args.dont_sign && signature_path.is_none() {
log::info!("Signing image {}", args.input_path.display());
if !args.dont_sign {
log::info!("Signing image");

let Some(cert_chain) = config.certificates.get(args.certificate) else {
return Err(anyhow::anyhow!("Certificate chain {} does not exist", args.certificate));
Expand All @@ -98,14 +149,8 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result<S
));
};

let default_path = args.input_path.clone().with_extension("signature.bin");
mbi::sign(&default_path, &output_prestage_path, &cert_proto.key_path).context("Could not sign image")?;
signature_path = Some(default_path);
}

let rkth = cert_block.rkth();
mbi::sign(&signature_path, &output_prestage_path, &cert_proto.key_path).context("Could not sign image")?;

if let Some(signature_path) = signature_path {
let output_path = args.output_path_with_default();
log::info!("Merging signature into image");
mbi::merge_with_signature(
Expand Down
17 changes: 17 additions & 0 deletions bootloader-tool/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(unused)]

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};

use serde::Deserialize;
Expand Down Expand Up @@ -75,6 +76,22 @@ pub struct ApplicationArgs {
///
/// This size is hard-coded and checked in the bootloader.
pub slot_size: u64,
/// Mappings that move ELF file sections Load addresses (LMA).
///
/// Typically this would be useful to move non-secure code to the secure memory range,
/// such that in plain binary format the file does not span the entire memory.
#[serde(default)]
pub address_mapping: BTreeSet<AddressMapping>,
}

#[derive(Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct AddressMapping {
/// Start of the address range to be mapped.
pub from_start: u64,
/// End of the address range to be mapped.
pub from_end: u64,
/// Start of the offset the address should be mapped to.
pub to_start: u64,
}

#[derive(Deserialize, Debug, Clone)]
Expand Down
21 changes: 15 additions & 6 deletions bootloader-tool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ pub enum GenerateCommands {
#[derive(Args, Debug, Clone)]
pub struct SignArguments {
/// Input file path (ELF)
#[arg(short, long, value_name = "INPUT_FILE")]
input_path: PathBuf,
/// When this argument is provided multiple times, the ELF files are merged and turned into a single MBI container.
/// The sections of these ELF files *MUST* be consecutive and non-overlapping.
/// The last provided argument is considered to be the 'primary' <INPUT_FILE>.
#[arg(short, long, value_name = "INPUT_FILES", verbatim_doc_comment)]
input_paths: Vec<PathBuf>,
/// Signature file
///
/// If present, will be checked against image and merged into output path
Expand Down Expand Up @@ -106,25 +109,31 @@ impl SignArguments {
pub fn output_unsigned_path_with_default(&self) -> PathBuf {
self.output_unsigned_path
.clone()
.unwrap_or_else(|| self.input_path.clone().with_extension("unsigned.bin"))
.unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("unsigned.bin"))
}

pub fn output_prestage_path_with_default(&self) -> PathBuf {
self.output_prestage_path
.clone()
.unwrap_or_else(|| self.input_path.clone().with_extension("mbi-proto.bin"))
.unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("mbi-proto.bin"))
}

pub fn output_path_with_default(&self) -> PathBuf {
self.output_path
.clone()
.unwrap_or_else(|| self.input_path.clone().with_extension("signed.bin"))
.unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("signed.bin"))
}

pub fn signature_path_with_default(&self) -> PathBuf {
self.signature_path
.clone()
.unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("signature.bin"))
}

pub fn prelude_path_with_default(&self) -> PathBuf {
self.prelude_path
.clone()
.unwrap_or_else(|| self.input_path.clone().with_extension("prelude.elf"))
.unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("prelude.elf"))
}
}

Expand Down
Loading