#![deny(missing_docs)]
use chrono::Local;
use object::{
elf::FileHeader64,
endian::LittleEndian,
read::{elf::ElfFile, pe::PeFile64},
Object, ObjectSection, ObjectSymbol,
};
use std::{
fs::{read, OpenOptions},
io::Write,
iter::once,
num::Wrapping,
path::{Path, PathBuf},
};
pub const MODULE_CAPABILITIES_SYMNAME: &str = "_module_capabilities_";
pub const MODULE_DATE_SYMNAME: &str = "_module_date";
pub const TEXT_SECTION_NAME: &str = ".text";
pub const DATA_SECTION_NAME: &str = ".data";
pub const MAX_SECTION_CSUM_SIZE: usize = 256;
pub const SIMICS_SIGNATURE_UNAME_MAX_LEN: usize = 20;
pub const SIMICS_SIGNATURE_MIN_LENGTH: usize = 44;
type Elf<'data> = ElfFile<'data, FileHeader64<LittleEndian>>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("File type of {path:?} not recognized. Must be PE64 or ELF64.")]
FileTypeNotRecognized {
path: PathBuf,
},
#[error("_module_capabilities_ symbol missing")]
ModuleCapabilitiesMissing,
#[error("Section not found for symbol {symbol} in {path:?}")]
SectionNotFound {
symbol: String,
path: PathBuf,
},
#[error("_module_capabilities_ split sequence missing from symbol value")]
SplitSequenceNotFound,
#[error("Section {section} not found in {path:?}")]
SectionMissing {
section: String,
path: PathBuf,
},
#[error("Signature unchanged after signing")]
SignatureUnchanged,
#[error("Module unchanged after signing")]
ModuleUnchanged,
#[error("File range for section {section} not found")]
SectionFileRangeMissing {
section: String,
},
#[error("Original and signed module lengths differ")]
ModuleLengthMismatch,
#[error("Missing terminating null byte in module capabilities")]
NullByteMissing,
#[error("Missing parent directory for path {path:?}")]
MissingParentDirectory {
path: PathBuf,
},
#[error("Module is not signed.")]
ModuleNotSigned,
#[error("Failed to open module output file {path:?}: {source}")]
OpenOutputFile {
path: PathBuf,
source: std::io::Error,
},
#[error("Failed to set permissions for output file {path:?}: {source}")]
SetPermissions {
path: PathBuf,
source: std::io::Error,
},
#[error("Failed to get metadata for output file {path:?}: {source}")]
GetMetadata {
path: PathBuf,
source: std::io::Error,
},
#[error("Failed to read directory {path:?}: {source}")]
ReadDirectory {
path: PathBuf,
source: std::io::Error,
},
#[error("Failed to write module output file to {path:?}: {source}")]
WriteOutputFile {
path: PathBuf,
source: std::io::Error,
},
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
ObjectError(#[from] object::Error),
}
type Result<T> = std::result::Result<T, Error>;
pub struct Sign {
module: PathBuf,
data: Vec<u8>,
signed: Vec<u8>,
}
impl Sign {
pub fn new<P>(module: P) -> Result<Self>
where
P: AsRef<Path>,
{
let data = read(module.as_ref())?;
let mut slf = Self {
module: module.as_ref().to_path_buf(),
data: data.clone(),
signed: Vec::new(),
};
let data = &data[..];
if let Ok(elf) = Elf::parse(data) {
slf.sign_elf(elf)?;
Ok(slf)
} else if let Ok(pe) = PeFile64::parse(data) {
slf.sign_pe(pe)?;
Ok(slf)
} else {
Err(Error::FileTypeNotRecognized {
path: slf.module.clone(),
})
}
}
fn sign_elf(&mut self, elf: Elf) -> Result<()> {
let module_capabilities = elf
.symbols()
.find(|s| s.name() == Ok(MODULE_CAPABILITIES_SYMNAME))
.ok_or_else(|| Error::ModuleCapabilitiesMissing)?;
let module_capabilities_data = &elf.data()[module_capabilities.address() as usize
..module_capabilities.address() as usize + module_capabilities.size() as usize];
let signature = [b" ".repeat(43), b";\x00".to_vec()].concat();
let elf_data = elf.data().to_vec();
if !module_capabilities_data.ends_with(&signature) {
println!(
"Already signed with signature {:?}",
&module_capabilities_data[module_capabilities_data.len() - signature.len()..]
);
self.signed = elf_data;
return Ok(());
}
let split_seq = b"; ";
let signature_position = module_capabilities_data
.windows(split_seq.len())
.position(|w| w == split_seq)
.ok_or_else(|| Error::SplitSequenceNotFound)?
+ split_seq.len();
let text_section =
elf.section_by_name(TEXT_SECTION_NAME)
.ok_or_else(|| Error::SectionMissing {
section: TEXT_SECTION_NAME.to_string(),
path: self.module.clone(),
})?;
let data_section =
elf.section_by_name(DATA_SECTION_NAME)
.ok_or_else(|| Error::SectionMissing {
section: DATA_SECTION_NAME.to_string(),
path: self.module.clone(),
})?;
let csum: Wrapping<u32> = (Wrapping(1u32)
* (Wrapping(text_section.size() as u32)
* text_section
.data()?
.iter()
.take(MAX_SECTION_CSUM_SIZE)
.fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32)))
* (Wrapping(data_section.size() as u32)
* data_section
.data()?
.iter()
.take(MAX_SECTION_CSUM_SIZE)
.fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32))))
| Wrapping(1u32);
let uname = "simics"
.chars()
.take(SIMICS_SIGNATURE_UNAME_MAX_LEN)
.collect::<String>();
let datetime_string = Local::now().format("%Y-%M-%d %H:%M").to_string();
let mut signature = module_capabilities_data[..signature_position]
.iter()
.chain(once(&0u8))
.chain(&csum.0.to_le_bytes())
.chain(once(&0u8))
.chain(datetime_string.as_bytes())
.chain(once(&b';'))
.chain(uname.as_bytes())
.cloned()
.collect::<Vec<_>>();
signature.resize(
module_capabilities_data[..signature_position].len() + SIMICS_SIGNATURE_MIN_LENGTH,
0u8,
);
if signature == module_capabilities_data {
return Err(Error::SignatureUnchanged);
}
let pre_sig = elf_data[..module_capabilities.address() as usize].to_vec();
let post_sig = elf_data
[module_capabilities.address() as usize + module_capabilities.size() as usize..]
.to_vec();
self.signed = pre_sig
.iter()
.chain(signature.iter())
.chain(post_sig.iter())
.cloned()
.collect::<Vec<_>>();
if self.data.len() != self.signed.len() {
return Err(Error::ModuleLengthMismatch);
}
if self.data == self.signed {
return Err(Error::ModuleUnchanged);
}
Ok(())
}
fn sign_pe(&mut self, pe: PeFile64) -> Result<()> {
let module_capabilities = pe
.symbols()
.find(|s| s.name() == Ok(MODULE_CAPABILITIES_SYMNAME))
.ok_or_else(|| Error::ModuleCapabilitiesMissing)?;
let module_capabilities_section =
pe.section_by_index(module_capabilities.section().index().ok_or_else(|| {
Error::SectionNotFound {
symbol: MODULE_CAPABILITIES_SYMNAME.to_string(),
path: self.module.clone(),
}
})?)?;
let module_capabilities_offset = ((module_capabilities.address()
- module_capabilities_section.address())
+ module_capabilities_section
.file_range()
.ok_or_else(|| Error::SectionFileRangeMissing {
section: module_capabilities_section
.name()
.map(|n| n.to_string())
.unwrap_or_else(|_| "unknown".to_string()),
})?
.0) as usize;
let module_capabilities_size = if module_capabilities.size() > 0 {
module_capabilities.size() as usize
} else {
&pe.data()[module_capabilities_offset..]
.iter()
.position(|b| *b == 0)
.ok_or_else(|| Error::NullByteMissing)?
+ 1
};
let module_capabilities_data = &pe.data()
[module_capabilities_offset..module_capabilities_offset + module_capabilities_size];
let split_seq = b"; ";
let signature_position = module_capabilities_data
.windows(split_seq.len())
.position(|w| w == split_seq)
.ok_or_else(|| Error::SplitSequenceNotFound)?
+ split_seq.len();
let text_section =
pe.section_by_name(TEXT_SECTION_NAME)
.ok_or_else(|| Error::SectionMissing {
section: TEXT_SECTION_NAME.to_string(),
path: self.module.clone(),
})?;
let data_section =
pe.section_by_name(DATA_SECTION_NAME)
.ok_or_else(|| Error::SectionMissing {
section: DATA_SECTION_NAME.to_string(),
path: self.module.clone(),
})?;
let csum: Wrapping<u32> = (Wrapping(1u32)
* (Wrapping(text_section.size() as u32)
* text_section
.data()?
.iter()
.take(MAX_SECTION_CSUM_SIZE)
.fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32)))
* (Wrapping(data_section.size() as u32)
* data_section
.data()?
.iter()
.take(MAX_SECTION_CSUM_SIZE)
.fold(Wrapping(0u32), |a, e| a + Wrapping(*e as u32))))
| Wrapping(1u32);
let uname = "simics"
.chars()
.take(SIMICS_SIGNATURE_UNAME_MAX_LEN)
.collect::<String>();
let datetime_string = Local::now().format("%Y-%M-%d %H:%M").to_string();
let mut signature = module_capabilities_data[..signature_position]
.iter()
.chain(once(&0u8))
.chain(&csum.0.to_le_bytes())
.chain(once(&0u8))
.chain(datetime_string.as_bytes())
.chain(once(&b';'))
.chain(uname.as_bytes())
.cloned()
.collect::<Vec<_>>();
signature.resize(
module_capabilities_data[..signature_position].len() + SIMICS_SIGNATURE_MIN_LENGTH,
0u8,
);
if signature == module_capabilities_data {
return Err(Error::SignatureUnchanged);
}
let pe_data = pe.data().to_vec();
let pre_sig = pe_data[..module_capabilities_offset].to_vec();
let post_sig = pe_data[module_capabilities_offset + module_capabilities_size..].to_vec();
self.signed = pre_sig
.iter()
.chain(signature.iter())
.chain(post_sig.iter())
.cloned()
.collect::<Vec<_>>();
if self.data.len() != self.signed.len() {
return Err(Error::ModuleLengthMismatch);
}
if self.data == self.signed {
return Err(Error::ModuleUnchanged);
}
Ok(())
}
pub fn write_as<S>(&mut self, name: S) -> Result<&mut Self>
where
S: AsRef<str>,
{
let output = self
.module
.parent()
.ok_or_else(|| Error::MissingParentDirectory {
path: self.module.clone(),
})?
.join(name.as_ref());
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(output)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = file.metadata()?.permissions();
perms.set_mode(0o755);
file.set_permissions(perms)?;
}
file.write_all(&self.signed)?;
Ok(self)
}
pub fn write<P>(&mut self, output: P) -> Result<&mut Self>
where
P: AsRef<Path>,
{
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(output.as_ref())
.map_err(|e| Error::OpenOutputFile {
path: output.as_ref().to_path_buf(),
source: e,
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = file
.metadata()
.map_err(|e| Error::GetMetadata {
path: output.as_ref().to_path_buf(),
source: e,
})?
.permissions();
perms.set_mode(0o755);
file.set_permissions(perms)
.map_err(|e| Error::SetPermissions {
path: output.as_ref().to_path_buf(),
source: e,
})?;
}
file.write_all(&self.signed)
.map_err(|e| Error::WriteOutputFile {
path: output.as_ref().to_path_buf(),
source: e,
})?;
file.flush()?;
if !output.as_ref().exists() {
return Err(Error::WriteOutputFile {
path: output.as_ref().to_path_buf(),
source: std::io::Error::from(std::io::ErrorKind::NotFound),
});
}
Ok(self)
}
pub fn data(&self) -> Result<Vec<u8>> {
if self.signed.is_empty() {
Err(Error::ModuleNotSigned)
} else {
Ok(self.data.clone())
}
}
}
#[cfg(test)]
mod test {
use super::Sign;
use std::{env::var, path::PathBuf};
#[cfg(debug_assertions)]
const TARGET_DIR: &str = "debug";
#[cfg(not(debug_assertions))]
const TARGET_DIR: &str = "release";
#[test]
#[cfg(windows)]
fn test_windows() {
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
let workspace_dir = manifest_dir.parent().unwrap();
let hello_world = workspace_dir
.join("target")
.join(TARGET_DIR)
.join("hello_world.dll");
let _signed = Sign::new(hello_world).unwrap().data().unwrap();
}
#[test]
#[cfg(unix)]
fn test_linux() {
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
let workspace_dir = manifest_dir.parent().unwrap();
let hello_world = workspace_dir
.join("target")
.join(TARGET_DIR)
.join("libhello_world.so");
println!("{:?}", hello_world);
let _signed = Sign::new(hello_world).unwrap().data().unwrap();
}
}