Skip to content
Open
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
39 changes: 38 additions & 1 deletion rust/tool/shared/src/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use time::Date;
pub struct ExtendedBootJson {
pub bootspec: BootSpec,
pub lanzaboote_extension: LanzabooteExtension,
pub xen_extension: Option<XenExtension>,
}

#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -52,6 +53,40 @@ impl From<bootspec::BootJson> for LanzabooteExtension {
}
}

// TODO: Should it be moved to bootspec crate itself?
// Probably after its standardization, bootspec crate is only used in lanzaboote for now, bootspec support is
// ad-hoc in nixpkgs right now.
//
// Aliases are used for org.xenproject.bootspec.v1 compatibility
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct XenExtension {
#[serde(alias = "xen")]
pub efi_path: String,
#[serde(alias = "xenParams")]
pub params: Vec<String>,
}

impl TryFrom<bootspec::Specialisation> for XenExtension {
type Error = ();
fn try_from(spec: bootspec::Specialisation) -> Result<Self, ()> {
spec.extensions
.get("org.xenproject.bootspec.v2")
.and_then(|v| serde_json::from_value::<XenExtension>(v.clone()).ok())
.ok_or(())
}
}

impl TryFrom<bootspec::BootJson> for XenExtension {
type Error = ();
fn try_from(spec: bootspec::BootJson) -> Result<Self, ()> {
spec.extensions
.get("org.xenproject.bootspec.v2")
.and_then(|v| serde_json::from_value::<XenExtension>(v.clone()).ok())
.ok_or(())
}
}

/// A system configuration.
///
/// Can be built from a GenerationLink.
Expand Down Expand Up @@ -98,6 +133,7 @@ impl Generation {
spec: ExtendedBootJson {
bootspec: specialisation.clone().generation,
lanzaboote_extension: specialisation.clone().into(),
xen_extension: specialisation.clone().try_into().ok(),
},
specialisations: Self::parse_specialisations(
link,
Expand Down Expand Up @@ -132,7 +168,8 @@ impl Generation {
specialisation_name,
spec: ExtendedBootJson {
bootspec: bootspec.clone(),
lanzaboote_extension: boot_json.into(),
lanzaboote_extension: boot_json.clone().into(),
xen_extension: boot_json.try_into().ok(),
},
specialisations: Self::parse_specialisations(link, bootspec.specialisations)?,
})
Expand Down
13 changes: 13 additions & 0 deletions rust/tool/shared/src/os_release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ impl OsRelease {

Ok(Self(map))
}

pub fn pretty_name(&self) -> &str {
self.0
.get("PRETTY_NAME")
.map(|v| v.as_str())
.unwrap_or_default()
}
pub fn version_id(&self) -> &str {
self.0
.get("VERSION_ID")
.map(|v| v.as_str())
.unwrap_or_default()
}
}

impl FromStr for OsRelease {
Expand Down
104 changes: 87 additions & 17 deletions rust/tool/shared/src/pe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::process::Command;

use anyhow::{Context, Result};
use anyhow::{ensure, Context, Result};
use goblin::pe::PE;
use serde::{Deserialize, Serialize};
use tempfile::TempDir;
Expand Down Expand Up @@ -106,21 +106,19 @@ pub fn lanzaboote_image(
tempdir.write_secure_file(file_hash(&stub_parameters.initrd_store_path)?.as_slice())?;

let os_release = tempdir.write_secure_file(&stub_parameters.os_release_contents)?;
let os_release_offs = stub_offset(&stub_parameters.lanzaboote_store_path)?;
let kernel_cmdline_offs = os_release_offs + file_size(&os_release)?;
let initrd_path_offs = kernel_cmdline_offs + file_size(&kernel_cmdline_file)?;
let kernel_path_offs = initrd_path_offs + file_size(&initrd_path_file)?;
let initrd_hash_offs = kernel_path_offs + file_size(&kernel_path_file)?;
let kernel_hash_offs = initrd_hash_offs + file_size(&initrd_hash_file)?;

let sections = vec![
s(".osrel", os_release, os_release_offs),
s(".cmdline", kernel_cmdline_file, kernel_cmdline_offs),
s(".initrd", initrd_path_file, initrd_path_offs),
s(".linux", kernel_path_file, kernel_path_offs),
s(".initrdh", initrd_hash_file, initrd_hash_offs),
s(".linuxh", kernel_hash_file, kernel_hash_offs),

let mut sections = vec![
s(".osrel", os_release),
s(".cmdline", kernel_cmdline_file),
s(".initrd", initrd_path_file),
s(".linux", kernel_path_file),
s(".initrdh", initrd_hash_file),
s(".linuxh", kernel_hash_file),
];
calculate_offsets(
stub_offset(&stub_parameters.lanzaboote_store_path)?,
&mut sections,
)?;

let image_path = tempdir.path().join(tmpname());
wrap_in_pe(
Expand All @@ -131,6 +129,47 @@ pub fn lanzaboote_image(
Ok(image_path)
}

// FIXME: This function generates huge images, as it copies kernel & initrd inside,
// lanzaboote stub needs to be extended to support xen images.
#[allow(clippy::too_many_arguments)]
pub fn xen_image(
tempdir: &TempDir,
xen_stub: &Path,
xen_options: &[String],
kernel_cmdline: &[String],
kernel: &Path,
initrd: &Path,
) -> Result<PathBuf> {
use std::fmt::Write;
let mut xen_config = String::new();
writeln!(xen_config, "[global]")?;
writeln!(xen_config, "default=xen")?;
writeln!(xen_config)?;
writeln!(xen_config, "[xen]")?;
writeln!(
xen_config,
"kernel=nope_this_is_uxi {}",
kernel_cmdline.join(" ")
)?;
writeln!(xen_config, "ramdisk=nope_this_is_uxi")?;
writeln!(xen_config, "options={}", xen_options.join(" "),)?;
let xen_config_file = tempdir.write_secure_file(xen_config)?;

ensure!(kernel.ends_with("bzImage"), "kernel is not a bzImgae");

let mut sections = vec![
s(".config", xen_config_file),
s(".kernel", kernel),
s(".ramdisk", initrd),
];

calculate_offsets(xen_offset(xen_stub)?, &mut sections)?;

let image_path = tempdir.path().join(tmpname());
wrap_in_pe(xen_stub, sections, &image_path)?;
Ok(image_path)
}

/// Take a PE binary stub and attach sections to it.
///
/// The resulting binary is then written to a newly created file at the provided output path.
Expand Down Expand Up @@ -162,9 +201,13 @@ struct Section {
}

impl Section {
fn resolved_offset(&self) -> bool {
self.offset != u64::MAX
}
/// Create objcopy `-add-section` command line parameters that
/// attach the section to a PE file.
fn to_objcopy(&self) -> Vec<OsString> {
assert!(self.resolved_offset(), "section offset is not resolved!");
// There is unfortunately no format! for OsString, so we cannot
// just format a path.
let mut map_str: OsString = format!("{}=", self.name).into();
Expand All @@ -179,13 +222,26 @@ impl Section {
}
}

fn s(name: &'static str, file_path: impl AsRef<Path>, offset: u64) -> Section {
fn s(name: &'static str, file_path: impl AsRef<Path>) -> Section {
Section {
name,
file_path: file_path.as_ref().into(),
offset,
offset: u64::MAX,
}
}
// EFI sections needs to be 4k aligned
fn align_to_4k(num: u64) -> u64 {
(num + 0xFFF) & !0xFFF
}
fn calculate_offsets(mut current: u64, sections: &mut [Section]) -> Result<()> {
for section in sections.iter_mut() {
current = align_to_4k(current);
assert!(!section.resolved_offset(), "section offset is known");
section.offset = current;
current += file_size(&section.file_path)?;
}
Ok(())
}

/// Convert a path to an UEFI path relative to the specified ESP.
fn esp_relative_uefi_path(esp: &Path, path: &Path) -> Result<String> {
Expand Down Expand Up @@ -222,6 +278,20 @@ fn stub_offset(binary: &Path) -> Result<u64> {
.expect("Failed to calculate offset"),
) + image_base)
}
fn xen_offset(binary: &Path) -> Result<u64> {
let pe_binary = fs::read(binary).context("Failed to read PE binary file")?;
let pe = PE::parse(&pe_binary).context("Failed to parse PE binary file")?;

let image_base = image_base(&pe);

let pad_section = pe
.sections
.iter()
.find(|s| s.name().ok() == Some(".pad"))
.expect("failed to discover .pad section");
let offset = pad_section.virtual_size + pad_section.virtual_address;
Ok(u64::from(offset) + image_base)
}

fn image_base(pe: &PE) -> u64 {
pe.header
Expand Down
8 changes: 6 additions & 2 deletions rust/tool/systemd/src/esp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ pub struct SystemdEspPaths {
pub systemd: PathBuf,
pub systemd_boot: PathBuf,
pub loader: PathBuf,
pub entries: PathBuf,
pub systemd_boot_loader_config: PathBuf,
}

impl EspPaths<10> for SystemdEspPaths {
impl EspPaths<11> for SystemdEspPaths {
fn new(esp: impl AsRef<Path>, architecture: Architecture) -> Self {
let esp = esp.as_ref();
let efi = esp.join("EFI");
Expand All @@ -28,6 +29,7 @@ impl EspPaths<10> for SystemdEspPaths {
let efi_systemd = efi.join("systemd");
let efi_efi_fallback_dir = efi.join("BOOT");
let loader = esp.join("loader");
let entries = loader.join("entries");
let systemd_boot_loader_config = loader.join("loader.conf");

Self {
Expand All @@ -40,6 +42,7 @@ impl EspPaths<10> for SystemdEspPaths {
systemd: efi_systemd.clone(),
systemd_boot: efi_systemd.join(architecture.systemd_filename()),
loader,
entries,
systemd_boot_loader_config,
}
}
Expand All @@ -52,7 +55,7 @@ impl EspPaths<10> for SystemdEspPaths {
&self.linux
}

fn iter(&self) -> std::array::IntoIter<&PathBuf, 10> {
fn iter(&self) -> std::array::IntoIter<&PathBuf, 11> {
[
&self.esp,
&self.efi,
Expand All @@ -63,6 +66,7 @@ impl EspPaths<10> for SystemdEspPaths {
&self.systemd,
&self.systemd_boot,
&self.loader,
&self.entries,
&self.systemd_boot_loader_config,
]
.into_iter()
Expand Down
Loading